Skip to content

Commit 5a14886

Browse files
committed
Merge commit 'refs/pull/4/head' of github.com:bitcoindevkit/rust-esplora-client
2 parents 6257a7d + 367b114 commit 5a14886

File tree

3 files changed

+136
-9
lines changed

3 files changed

+136
-9
lines changed

src/api.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
//! see: <https://github.com/Blockstream/esplora/blob/master/API.md>
44
55
use bitcoin::hashes::hex::FromHex;
6-
use bitcoin::{OutPoint, Script, Transaction, TxIn, TxOut, Txid, Witness};
6+
use bitcoin::{BlockHash, OutPoint, Script, Transaction, TxIn, TxOut, Txid, Witness};
77

88
use serde::Deserialize;
99

10-
#[derive(Deserialize, Clone, Debug)]
10+
#[derive(Deserialize, Clone, Debug, PartialEq)]
1111
pub struct PrevOut {
1212
pub value: u64,
1313
pub scriptpubkey: Script,
1414
}
1515

16-
#[derive(Deserialize, Clone, Debug)]
16+
#[derive(Deserialize, Clone, Debug, PartialEq)]
1717
pub struct Vin {
1818
pub txid: Txid,
1919
pub vout: u32,
@@ -26,19 +26,35 @@ pub struct Vin {
2626
pub is_coinbase: bool,
2727
}
2828

29-
#[derive(Deserialize, Clone, Debug)]
29+
#[derive(Deserialize, Clone, Debug, PartialEq)]
3030
pub struct Vout {
3131
pub value: u64,
3232
pub scriptpubkey: Script,
3333
}
3434

35-
#[derive(Deserialize, Clone, Debug)]
35+
#[derive(Deserialize, Clone, Debug, PartialEq)]
3636
pub struct TxStatus {
3737
pub confirmed: bool,
3838
pub block_height: Option<u32>,
39+
pub block_hash: Option<BlockHash>,
3940
pub block_time: Option<u64>,
4041
}
4142

43+
#[derive(Deserialize, Clone, Debug, PartialEq)]
44+
pub struct MerkleProof {
45+
block_height: u32,
46+
merkle: Vec<Txid>,
47+
pos: usize,
48+
}
49+
50+
#[derive(Deserialize, Clone, Debug, PartialEq)]
51+
pub struct OutputStatus {
52+
spent: bool,
53+
txid: Option<Txid>,
54+
vin: Option<Vin>,
55+
status: Option<TxStatus>,
56+
}
57+
4258
#[derive(Deserialize, Clone, Debug)]
4359
pub struct Tx {
4460
pub txid: Txid,
@@ -93,6 +109,7 @@ impl Tx {
93109
confirmed: true,
94110
block_height: Some(height),
95111
block_time: Some(timestamp),
112+
..
96113
} => Some(BlockTime { timestamp, height }),
97114
_ => None,
98115
}

src/async.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use log::{debug, error, info, trace};
2323

2424
use reqwest::{Client, StatusCode};
2525

26-
use crate::{Builder, Error, Tx};
26+
use crate::{Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus, Vout};
2727

2828
#[derive(Debug)]
2929
pub struct AsyncClient {
@@ -69,7 +69,7 @@ impl AsyncClient {
6969
Ok(Some(deserialize(&resp.error_for_status()?.bytes().await?)?))
7070
}
7171

72-
/// Get a [`Transaction`] given its [`Txid`]
72+
/// Get a [`Transaction`] given its [`Txid`].
7373
pub async fn get_tx_no_opt(&self, txid: &Txid) -> Result<Transaction, Error> {
7474
match self.get_tx(txid).await {
7575
Ok(Some(tx)) => Ok(tx),
@@ -78,6 +78,21 @@ impl AsyncClient {
7878
}
7979
}
8080

81+
/// Get the status of a [`Transaction`] given its [`Txid`].
82+
pub async fn get_tx_status(&self, txid: &Txid) -> Result<Option<TxStatus>, Error> {
83+
let resp = self
84+
.client
85+
.get(&format!("{}/tx/{}/status", self.url, txid))
86+
.send()
87+
.await?;
88+
89+
if let StatusCode::NOT_FOUND = resp.status() {
90+
return Ok(None);
91+
}
92+
93+
Ok(Some(resp.error_for_status()?.json().await?))
94+
}
95+
8196
/// Get a [`BlockHeader`] given a particular block height.
8297
pub async fn get_header(&self, block_height: u32) -> Result<BlockHeader, Error> {
8398
let resp = self
@@ -104,6 +119,40 @@ impl AsyncClient {
104119
Ok(header)
105120
}
106121

122+
/// Get a merkle inclusion proof for a [`Transaction`] with the given [`Txid`].
123+
pub async fn get_merkle_proof(&self, tx_hash: &Txid) -> Result<Option<MerkleProof>, Error> {
124+
let resp = self
125+
.client
126+
.get(&format!("{}/tx/{}/merkle-proof", self.url, tx_hash))
127+
.send()
128+
.await?;
129+
130+
if let StatusCode::NOT_FOUND = resp.status() {
131+
return Ok(None);
132+
}
133+
134+
Ok(Some(resp.error_for_status()?.json().await?))
135+
}
136+
137+
/// Get the spending status of an output given a [`Txid`] and [`Vout`].
138+
pub async fn get_output_status(
139+
&self,
140+
txid: &Txid,
141+
vout: &Vout,
142+
) -> Result<Option<OutputStatus>, Error> {
143+
let resp = self
144+
.client
145+
.get(&format!("{}/tx/{}/outspend/{}", self.url, txid, vout.value))
146+
.send()
147+
.await?;
148+
149+
if let StatusCode::NOT_FOUND = resp.status() {
150+
return Ok(None);
151+
}
152+
153+
Ok(Some(resp.error_for_status()?.json().await?))
154+
}
155+
107156
/// Broadcast a [`Transaction`] to Esplora
108157
pub async fn broadcast(&self, transaction: &Transaction) -> Result<(), Error> {
109158
self.client

src/blocking.rs

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use bitcoin::hashes::hex::{FromHex, ToHex};
2626
use bitcoin::hashes::{sha256, Hash};
2727
use bitcoin::{BlockHeader, Script, Transaction, Txid};
2828

29-
use crate::{Builder, Error, Tx};
29+
use crate::{Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus, Vout};
3030

3131
#[derive(Debug, Clone)]
3232
pub struct BlockingClient {
@@ -74,7 +74,7 @@ impl BlockingClient {
7474
}
7575
}
7676

77-
/// Get a [`Transaction`] given its [`Txid`]
77+
/// Get a [`Transaction`] given its [`Txid`].
7878
pub fn get_tx_no_opt(&self, txid: &Txid) -> Result<Transaction, Error> {
7979
match self.get_tx(txid) {
8080
Ok(Some(tx)) => Ok(tx),
@@ -83,6 +83,25 @@ impl BlockingClient {
8383
}
8484
}
8585

86+
/// Get the status of a [`Transaction`] given its [`Txid`].
87+
pub fn get_tx_status(&self, txid: &Txid) -> Result<Option<TxStatus>, Error> {
88+
let resp = self
89+
.agent
90+
.get(&format!("{}/tx/{}/status", self.url, txid))
91+
.call();
92+
93+
match resp {
94+
Ok(resp) => Ok(Some(resp.into_json()?)),
95+
Err(ureq::Error::Status(code, _)) => {
96+
if is_status_not_found(code) {
97+
return Ok(None);
98+
}
99+
Err(Error::HttpResponse(code))
100+
}
101+
Err(e) => Err(Error::Ureq(e)),
102+
}
103+
}
104+
86105
/// Get a [`BlockHeader`] given a particular block height.
87106
pub fn get_header(&self, block_height: u32) -> Result<BlockHeader, Error> {
88107
let resp = self
@@ -111,6 +130,48 @@ impl BlockingClient {
111130
}
112131
}
113132

133+
/// Get a merkle inclusion proof for a [`Transaction`] with the given [`Txid`].
134+
pub fn get_merkle_proof(&self, txid: &Txid) -> Result<Option<MerkleProof>, Error> {
135+
let resp = self
136+
.agent
137+
.get(&format!("{}/tx/{}/merkle-proof", self.url, txid))
138+
.call();
139+
140+
match resp {
141+
Ok(resp) => Ok(Some(resp.into_json()?)),
142+
Err(ureq::Error::Status(code, _)) => {
143+
if is_status_not_found(code) {
144+
return Ok(None);
145+
}
146+
Err(Error::HttpResponse(code))
147+
}
148+
Err(e) => Err(Error::Ureq(e)),
149+
}
150+
}
151+
152+
/// Get the spending status of an output given a [`Txid`] and [`Vout`].
153+
pub fn get_output_status(
154+
&self,
155+
txid: &Txid,
156+
vout: &Vout,
157+
) -> Result<Option<OutputStatus>, Error> {
158+
let resp = self
159+
.agent
160+
.get(&format!("{}/tx/{}/outspend/{}", self.url, txid, vout.value))
161+
.call();
162+
163+
match resp {
164+
Ok(resp) => Ok(Some(resp.into_json()?)),
165+
Err(ureq::Error::Status(code, _)) => {
166+
if is_status_not_found(code) {
167+
return Ok(None);
168+
}
169+
Err(Error::HttpResponse(code))
170+
}
171+
Err(e) => Err(Error::Ureq(e)),
172+
}
173+
}
174+
114175
/// Broadcast a [`Transaction`] to Esplora
115176
pub fn broadcast(&self, transaction: &Transaction) -> Result<(), Error> {
116177
let resp = self

0 commit comments

Comments
 (0)