Skip to content

Commit 3fdd66c

Browse files
committed
feat(api): add /address/:address and /address/:address/txs endpoints
1 parent 1f4acad commit 3fdd66c

File tree

4 files changed

+153
-0
lines changed

4 files changed

+153
-0
lines changed

src/api.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,32 @@ pub struct BlockSummary {
9797
pub merkle_root: bitcoin::hash_types::TxMerkleNode,
9898
}
9999

100+
/// Address statistics, includes the address, and the utxo information for the address.
101+
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
102+
pub struct AddressStats {
103+
/// The address.
104+
pub address: String,
105+
/// The summary of transactions for this address, already on chain.
106+
pub chain_stats: AddressTxsSummary,
107+
/// The summary of transactions for this address, currently in the mempool.
108+
pub mempool_stats: AddressTxsSummary,
109+
}
110+
111+
/// Contains a summary of the transactions for an address.
112+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
113+
pub struct AddressTxsSummary {
114+
/// The number of funded transaction outputs.
115+
pub funded_txo_count: u32,
116+
/// The sum of the funded transaction outputs, in satoshis.
117+
pub funded_txo_sum: u64,
118+
/// The number of spent transaction outputs.
119+
pub spent_txo_count: u32,
120+
/// The sum of the spent transaction outputs, in satoshis.
121+
pub spent_txo_sum: u64,
122+
/// The total number of transactions.
123+
pub tx_count: u32,
124+
}
125+
100126
impl Tx {
101127
pub fn to_tx(&self) -> Transaction {
102128
Transaction {

src/async.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::str::FromStr;
1818
use bitcoin::consensus::{deserialize, serialize, Decodable, Encodable};
1919
use bitcoin::hashes::{sha256, Hash};
2020
use bitcoin::hex::{DisplayHex, FromHex};
21+
use bitcoin::Address;
2122
use bitcoin::{
2223
block::Header as BlockHeader, Block, BlockHash, MerkleBlock, Script, Transaction, Txid,
2324
};
@@ -27,6 +28,7 @@ use log::{debug, error, info, trace};
2728

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

31+
use crate::api::AddressStats;
3032
use crate::{
3133
BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus,
3234
BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES,
@@ -378,6 +380,30 @@ impl AsyncClient {
378380
.map(|block_hash| BlockHash::from_str(&block_hash).map_err(Error::HexToArray))?
379381
}
380382

383+
/// Get information about a specific address, includes confirmed balance and transactions in
384+
/// the mempool.
385+
pub async fn get_address_stats(&self, address: &Address) -> Result<AddressStats, Error> {
386+
let path = format!("/address/{address}");
387+
self.get_response_json(&path).await
388+
}
389+
390+
/// Get transaction history for the specified address/scripthash, sorted with newest first.
391+
///
392+
/// Returns up to 50 mempool transactions plus the first 25 confirmed transactions.
393+
/// More can be requested by specifying the last txid seen by the previous query.
394+
pub async fn get_address_txs(
395+
&self,
396+
address: &Address,
397+
last_seen: Option<Txid>,
398+
) -> Result<Vec<Tx>, Error> {
399+
let path = match last_seen {
400+
Some(last_seen) => format!("/address/{address}/txs/chain/{last_seen}"),
401+
None => format!("/address/{address}/txs"),
402+
};
403+
404+
self.get_response_json(&path).await
405+
}
406+
381407
/// Get confirmed transaction history for the specified address/scripthash,
382408
/// sorted with newest first. Returns 25 transactions per page.
383409
/// More can be requested by specifying the last txid seen by the previous

src/blocking.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ use minreq::{Proxy, Request, Response};
2424
use bitcoin::consensus::{deserialize, serialize, Decodable};
2525
use bitcoin::hashes::{sha256, Hash};
2626
use bitcoin::hex::{DisplayHex, FromHex};
27+
use bitcoin::Address;
2728
use bitcoin::{
2829
block::Header as BlockHeader, Block, BlockHash, MerkleBlock, Script, Transaction, Txid,
2930
};
3031

32+
use crate::api::AddressStats;
3133
use crate::{
3234
BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus,
3335
BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES,
@@ -317,6 +319,30 @@ impl BlockingClient {
317319
self.get_response_json("/fee-estimates")
318320
}
319321

322+
/// Get information about a specific address, includes confirmed balance and transactions in
323+
/// the mempool.
324+
pub fn get_address_stats(&self, address: &Address) -> Result<AddressStats, Error> {
325+
let path = format!("/address/{address}");
326+
self.get_response_json(&path)
327+
}
328+
329+
/// Get transaction history for the specified address/scripthash, sorted with newest first.
330+
///
331+
/// Returns up to 50 mempool transactions plus the first 25 confirmed transactions.
332+
/// More can be requested by specifying the last txid seen by the previous query.
333+
pub fn get_address_txs(
334+
&self,
335+
address: &Address,
336+
last_seen: Option<Txid>,
337+
) -> Result<Vec<Tx>, Error> {
338+
let path = match last_seen {
339+
Some(last_seen) => format!("/address/{address}/txs/chain/{last_seen}"),
340+
None => format!("/address/{address}/txs"),
341+
};
342+
343+
self.get_response_json(&path)
344+
}
345+
320346
/// Get confirmed transaction history for the specified address/scripthash,
321347
/// sorted with newest first. Returns 25 transactions per page.
322348
/// More can be requested by specifying the last txid seen by the previous

src/lib.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,4 +992,79 @@ mod test {
992992
let tx_async = async_client.get_tx(&txid).await.unwrap();
993993
assert_eq!(tx, tx_async);
994994
}
995+
996+
#[cfg(all(feature = "blocking", feature = "async"))]
997+
#[tokio::test]
998+
async fn test_get_address_stats() {
999+
let (blocking_client, async_client) = setup_clients().await;
1000+
1001+
let address = BITCOIND
1002+
.client
1003+
.get_new_address(Some("test"), Some(AddressType::Legacy))
1004+
.unwrap()
1005+
.assume_checked();
1006+
1007+
let _txid = BITCOIND
1008+
.client
1009+
.send_to_address(
1010+
&address,
1011+
Amount::from_sat(1000),
1012+
None,
1013+
None,
1014+
None,
1015+
None,
1016+
None,
1017+
None,
1018+
)
1019+
.unwrap();
1020+
1021+
let address_stats_blocking = blocking_client.get_address_stats(&address).unwrap();
1022+
let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
1023+
assert_eq!(address_stats_blocking, address_stats_async);
1024+
assert_eq!(address_stats_async.chain_stats.funded_txo_count, 0);
1025+
1026+
let _miner = MINER.lock().await;
1027+
generate_blocks_and_wait(1);
1028+
1029+
let address_stats_blocking = blocking_client.get_address_stats(&address).unwrap();
1030+
let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
1031+
assert_eq!(address_stats_blocking, address_stats_async);
1032+
assert_eq!(address_stats_async.chain_stats.funded_txo_count, 1);
1033+
assert_eq!(address_stats_async.chain_stats.funded_txo_sum, 1000);
1034+
}
1035+
1036+
#[cfg(all(feature = "blocking", feature = "async"))]
1037+
#[tokio::test]
1038+
async fn test_get_address_txs() {
1039+
let (blocking_client, async_client) = setup_clients().await;
1040+
1041+
let address = BITCOIND
1042+
.client
1043+
.get_new_address(Some("test"), Some(AddressType::Legacy))
1044+
.unwrap()
1045+
.assume_checked();
1046+
1047+
let txid = BITCOIND
1048+
.client
1049+
.send_to_address(
1050+
&address,
1051+
Amount::from_sat(1000),
1052+
None,
1053+
None,
1054+
None,
1055+
None,
1056+
None,
1057+
None,
1058+
)
1059+
.unwrap();
1060+
1061+
let _miner = MINER.lock().await;
1062+
generate_blocks_and_wait(1);
1063+
1064+
let address_txs_blocking = blocking_client.get_address_txs(&address, None).unwrap();
1065+
let address_txs_async = async_client.get_address_txs(&address, None).await.unwrap();
1066+
1067+
assert_eq!(address_txs_blocking, address_txs_async);
1068+
assert_eq!(address_txs_async[0].txid, txid);
1069+
}
9951070
}

0 commit comments

Comments
 (0)