Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
name = "esplora-client"
version = "0.12.1"
edition = "2021"
authors = ["Alekos Filini <[email protected]>"]
authors = [
"Alekos Filini <[email protected]>",
"Bitcoin Dev Kit Developers"
]
license = "MIT"
homepage = "https://github.com/bitcoindevkit/rust-esplora-client"
repository = "https://github.com/bitcoindevkit/rust-esplora-client"
Expand Down
104 changes: 100 additions & 4 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
//!
//! See: <https://github.com/Blockstream/esplora/blob/master/API.md>

use bitcoin::hash_types;
use serde::Deserialize;

pub use bitcoin::consensus::{deserialize, serialize};
pub use bitcoin::hex::FromHex;
use bitcoin::Weight;
pub use bitcoin::{
transaction, Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness,
absolute, block, transaction, Amount, BlockHash, CompactTarget, OutPoint, ScriptBuf,
ScriptHash, Transaction, TxIn, TxOut, Txid, Weight, Witness,
};

use serde::Deserialize;

#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct PrevOut {
pub value: u64,
Expand Down Expand Up @@ -87,6 +88,59 @@ pub struct BlockTime {
pub height: u32,
}

/// Information about a [`Block`].
#[derive(Debug, Clone, Deserialize)]
pub struct BlockInformation {
/// The block's [`BlockHash`].
pub id: BlockHash,
/// The block's height.
pub height: u32,
/// The block's version.
pub version: block::Version,
/// The block's timestamp.
pub timestamp: u64,
/// The block's transaction count.
pub tx_count: u64,
/// The block's size, in bytes.
pub size: usize,
/// The block's weight.
pub weight: u64,
/// The merkle root of the transactions in the block.
pub merkle_root: hash_types::TxMerkleNode,
/// The [`BlockHash`] of the previous block (`None` for the genesis block).
pub previousblockhash: Option<BlockHash>,
/// The block's MTP (Median Time Past).
pub mediantime: u64,
/// The block's nonce value.
pub nonce: u32,
/// The block's `bits` value as a [`CompactTarget`].
pub bits: CompactTarget,
/// The block's difficulty target value.
pub difficulty: f64,
}

impl PartialEq for BlockInformation {
fn eq(&self, other: &Self) -> bool {
let Self { difficulty: d1, .. } = self;
let Self { difficulty: d2, .. } = other;

self.id == other.id
&& self.height == other.height
&& self.version == other.version
&& self.timestamp == other.timestamp
&& self.tx_count == other.tx_count
&& self.size == other.size
&& self.weight == other.weight
&& self.merkle_root == other.merkle_root
&& self.previousblockhash == other.previousblockhash
&& self.mediantime == other.mediantime
&& self.nonce == other.nonce
&& self.bits == other.bits
&& ((d1.is_nan() && d2.is_nan()) || (d1 == d2))
}
}
impl Eq for BlockInformation {}

#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct BlockSummary {
pub id: BlockHash,
Expand Down Expand Up @@ -123,6 +177,18 @@ pub struct AddressTxsSummary {
pub tx_count: u32,
}

/// Statistics about a particular [`Script`] hash's confirmed and mempool transactions.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
pub struct ScriptHashStats {
/// The summary of confirmed transactions for this [`Script`] hash.
pub chain_stats: ScriptHashTxsSummary,
/// The summary of mempool transactions for this [`Script`] hash.
pub mempool_stats: ScriptHashTxsSummary,
}

/// Contains a summary of the transactions for a particular [`Script`] hash.
pub type ScriptHashTxsSummary = AddressTxsSummary;

/// Information about an UTXO's status: confirmation status,
/// confirmation height, confirmation block hash and confirmation block time.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
Expand Down Expand Up @@ -150,6 +216,36 @@ pub struct Utxo {
pub value: Amount,
}

/// Statistics about the mempool.
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct MempoolStats {
/// The number of transactions in the mempool.
pub count: usize,
/// The total size of mempool transactions in virtual bytes.
pub vsize: usize,
/// The total fee paid by mempool transactions, in sats.
pub total_fee: u64,
/// The mempool's fee rate distribution histogram.
///
/// An array of `(feerate, vsize)` tuples, where each entry's `vsize` is the total vsize
/// of transactions paying more than `feerate` but less than the previous entry's `feerate`
/// (except for the first entry, which has no upper bound).
pub fee_histogram: Vec<(f64, usize)>,
}

/// A [`Transaction`] that recently entered the mempool.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct MempoolRecentTx {
/// Transaction ID as a [`Txid`].
pub txid: Txid,
/// [`Amount`] of fees paid by the transaction, in satoshis.
pub fee: u64,
/// The transaction size, in virtual bytes.
pub vsize: usize,
/// Combined [`Amount`] of the transaction, in satoshis.
pub value: u64,
}

impl Tx {
pub fn to_tx(&self) -> Transaction {
Transaction {
Expand Down
105 changes: 95 additions & 10 deletions src/async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,20 @@ use std::collections::HashMap;
use std::marker::PhantomData;
use std::str::FromStr;

use bitcoin::block::Header as BlockHeader;
use bitcoin::consensus::{deserialize, serialize, Decodable, Encodable};
use bitcoin::hashes::{sha256, Hash};
use bitcoin::hex::{DisplayHex, FromHex};
use bitcoin::Address;
use bitcoin::{
block::Header as BlockHeader, Block, BlockHash, MerkleBlock, Script, Transaction, Txid,
};
use bitcoin::{Address, Block, BlockHash, MerkleBlock, Script, Transaction, Txid};

#[allow(unused_imports)]
use log::{debug, error, info, trace};

use reqwest::{header, Client, Response};

use crate::api::AddressStats;
use crate::{
BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus, Utxo,
AddressStats, BlockInformation, BlockStatus, BlockSummary, Builder, Error, MempoolRecentTx,
MempoolStats, MerkleProof, OutputStatus, ScriptHashStats, Tx, TxStatus, Utxo,
BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES,
};

Expand Down Expand Up @@ -315,6 +313,12 @@ impl<S: Sleeper> AsyncClient<S> {
self.get_opt_response_json(&format!("/tx/{txid}")).await
}

/// Get the spend status of a [`Transaction`]'s outputs, given it's [`Txid`].
pub async fn get_tx_outspends(&self, txid: &Txid) -> Result<Vec<OutputStatus>, Error> {
self.get_response_json(&format!("/tx/{txid}/outspends"))
.await
}

/// Get a [`BlockHeader`] given a particular block hash.
pub async fn get_header_by_hash(&self, block_hash: &BlockHash) -> Result<BlockHeader, Error> {
self.get_response_hex(&format!("/block/{block_hash}/header"))
Expand Down Expand Up @@ -391,7 +395,14 @@ impl<S: Sleeper> AsyncClient<S> {
self.get_response_json(&path).await
}

/// Get transaction history for the specified address/scripthash, sorted with newest first.
/// Get statistics about a particular [`Script`] hash's confirmed and mempool transactions.
pub async fn get_scripthash_stats(&self, script: &Script) -> Result<ScriptHashStats, Error> {
let script_hash = sha256::Hash::hash(script.as_bytes());
let path = format!("/scripthash/{script_hash}");
self.get_response_json(&path).await
}

/// Get transaction history for the specified address, sorted with newest first.
///
/// Returns up to 50 mempool transactions plus the first 25 confirmed transactions.
/// More can be requested by specifying the last txid seen by the previous query.
Expand All @@ -408,7 +419,14 @@ impl<S: Sleeper> AsyncClient<S> {
self.get_response_json(&path).await
}

/// Get confirmed transaction history for the specified address/scripthash,
/// Get mempool [`Transaction`]s for the specified [`Address`], sorted with newest first.
pub async fn get_mempool_address_txs(&self, address: &Address) -> Result<Vec<Tx>, Error> {
let path = format!("/address/{address}/txs/mempool");

self.get_response_json(&path).await
}

/// Get transaction history for the specified address/scripthash,
/// sorted with newest first. Returns 25 transactions per page.
/// More can be requested by specifying the last txid seen by the previous
/// query.
Expand All @@ -426,12 +444,70 @@ impl<S: Sleeper> AsyncClient<S> {
self.get_response_json(&path).await
}

/// Get mempool [`Transaction`] history for the
/// specified [`Script`] hash, sorted with newest first.
pub async fn get_mempool_scripthash_txs(&self, script: &Script) -> Result<Vec<Tx>, Error> {
let script_hash = sha256::Hash::hash(script.as_bytes());
let path = format!("/scripthash/{script_hash:x}/txs/mempool");

self.get_response_json(&path).await
}

/// Get statistics about the mempool.
pub async fn get_mempool_stats(&self) -> Result<MempoolStats, Error> {
self.get_response_json("/mempool").await
}

// Get a list of the last 10 [`Transaction`]s to enter the mempool.
pub async fn get_mempool_recent_txs(&self) -> Result<Vec<MempoolRecentTx>, Error> {
self.get_response_json("/mempool/recent").await
}

/// Get the full list of [`Txid`]s in the mempool.
///
/// The order of the [`Txid`]s is arbitrary.
pub async fn get_mempool_txids(&self) -> Result<Vec<Txid>, Error> {
self.get_response_json("/mempool/txids").await
}

/// Get an map where the key is the confirmation target (in number of
/// blocks) and the value is the estimated feerate (in sat/vB).
pub async fn get_fee_estimates(&self) -> Result<HashMap<u16, f64>, Error> {
self.get_response_json("/fee-estimates").await
}

/// Get a summary about a [`Block`], given it's [`BlockHash`].
pub async fn get_block(&self, blockhash: &BlockHash) -> Result<BlockInformation, Error> {
let path = format!("/block/{blockhash}");

self.get_response_json(&path).await
}

/// Get all [`Txid`]s that belong to a [`Block`] identified by it's [`BlockHash`].
pub async fn get_block_txids(&self, blockhash: &BlockHash) -> Result<Vec<Txid>, Error> {
let path = format!("/block/{blockhash}/txids");

self.get_response_json(&path).await
}

/// Get up to 25 [`Transaction`]s from a [`Block`], given it's [`BlockHash`],
/// beginning at `start_index` (starts from 0 if `start_index` is `None`).
///
/// The `start_index` value MUST be a multiple of 25,
/// else an error will be returned by Esplora.
pub async fn get_block_txs(
&self,
blockhash: &BlockHash,
start_index: Option<u32>,
) -> Result<Vec<Tx>, Error> {
let path = match start_index {
None => format!("/block/{blockhash}/txs"),
Some(start_index) => format!("/block/{blockhash}/txs/{start_index}"),
};

self.get_response_json(&path).await
}

/// Gets some recent block summaries starting at the tip or at `height` if
/// provided.
///
Expand All @@ -451,8 +527,17 @@ impl<S: Sleeper> AsyncClient<S> {

/// Get all UTXOs locked to an address.
pub async fn get_address_utxos(&self, address: &Address) -> Result<Vec<Utxo>, Error> {
self.get_response_json(&format!("/address/{address}/utxo"))
.await
let path = format!("/address/{address}/utxo");

self.get_response_json(&path).await
}

/// Get all [`TxOut`]s locked to a [`Script`] hash.
pub async fn get_scripthash_utxos(&self, script: &Script) -> Result<Vec<Utxo>, Error> {
let script_hash = sha256::Hash::hash(script.as_bytes());
let path = format!("/scripthash/{script_hash}/utxo");

self.get_response_json(&path).await
}

/// Get the underlying base URL.
Expand Down
Loading
Loading