diff --git a/src/api.rs b/src/api.rs index 296835c..14e3661 100644 --- a/src/api.rs +++ b/src/api.rs @@ -123,6 +123,33 @@ pub struct AddressTxsSummary { pub tx_count: u32, } +/// Information about an UTXO's status: confirmation status, +/// confirmation height, confirmation block hash and confirmation block time. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +pub struct UtxoStatus { + /// Whether or not the UTXO is confirmed. + pub confirmed: bool, + /// The block height in which the UTXO was confirmed. + pub block_height: Option, + /// The block hash in which the UTXO was confirmed. + pub block_hash: Option, + /// The UNIX timestamp in which the UTXO was confirmed. + pub block_time: Option, +} + +/// Information about an UTXO's outpoint, confirmation status and value. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +pub struct Utxo { + /// The [`Txid`] of the transaction that created the UTXO. + pub txid: Txid, + /// The output index of the UTXO on the transaction that created the it. + pub vout: u32, + /// The confirmation status of the UTXO. + pub status: UtxoStatus, + /// The value of the UTXO as an [`Amount`]. + pub value: Amount, +} + impl Tx { pub fn to_tx(&self) -> Transaction { Transaction { diff --git a/src/async.rs b/src/async.rs index a5175b9..390eb2f 100644 --- a/src/async.rs +++ b/src/async.rs @@ -30,7 +30,7 @@ use reqwest::{header, Client, Response}; use crate::api::AddressStats; use crate::{ - BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus, + BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus, Utxo, BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES, }; @@ -449,6 +449,12 @@ impl AsyncClient { Ok(blocks) } + /// Get all UTXOs locked to an address. + pub async fn get_address_utxos(&self, address: &Address) -> Result, Error> { + self.get_response_json(&format!("/address/{address}/utxo")) + .await + } + /// Get the underlying base URL. pub fn url(&self) -> &str { &self.url diff --git a/src/blocking.rs b/src/blocking.rs index 52af732..b959c85 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -31,7 +31,7 @@ use bitcoin::{ use crate::api::AddressStats; use crate::{ - BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus, + BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus, Utxo, BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES, }; @@ -377,6 +377,11 @@ impl BlockingClient { Ok(blocks) } + /// Get all UTXOs locked to an address. + pub fn get_address_utxos(&self, address: &Address) -> Result, Error> { + self.get_response_json(&format!("/address/{address}/utxo")) + } + /// Sends a GET request to the given `url`, retrying failed attempts /// for retryable error codes until max retries hit. fn get_with_retry(&self, url: &str) -> Result { diff --git a/src/lib.rs b/src/lib.rs index d8091b6..51bc686 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1037,4 +1037,32 @@ mod test { assert_eq!(address_txs_blocking, address_txs_async); assert_eq!(address_txs_async[0].txid, txid); } + + #[cfg(all(feature = "blocking", feature = "async"))] + #[tokio::test] + async fn test_get_address_utxos() { + let (blocking_client, async_client) = setup_clients().await; + + let address = BITCOIND + .client + .new_address_with_type(AddressType::Legacy) + .unwrap(); + + let _txid = BITCOIND + .client + .send_to_address(&address, Amount::from_sat(21000)) + .unwrap() + .txid() + .unwrap(); + + let _miner = MINER.lock().await; + generate_blocks_and_wait(1); + + let address_utxos_blocking = blocking_client.get_address_utxos(&address).unwrap(); + let address_utxos_async = async_client.get_address_utxos(&address).await.unwrap(); + + assert_ne!(address_utxos_blocking.len(), 0); + assert_ne!(address_utxos_async.len(), 0); + assert_eq!(address_utxos_blocking, address_utxos_async); + } }