iota_sdk/apis/
coin_read.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{future, sync::Arc};
6
7use futures::{StreamExt, stream};
8use futures_core::Stream;
9use iota_json_rpc_api::CoinReadApiClient;
10use iota_json_rpc_types::{Balance, Coin, CoinPage, IotaCirculatingSupply, IotaCoinMetadata};
11use iota_types::{
12    balance::Supply,
13    base_types::{IotaAddress, ObjectID},
14};
15
16use crate::{
17    RpcClient,
18    error::{Error, IotaRpcResult},
19};
20
21/// Defines methods that retrieve information from the IOTA network regarding
22/// the coins owned by an address.
23#[derive(Debug, Clone)]
24pub struct CoinReadApi {
25    api: Arc<RpcClient>,
26}
27
28impl CoinReadApi {
29    pub(crate) fn new(api: Arc<RpcClient>) -> Self {
30        Self { api }
31    }
32
33    /// Get coins for the given address filtered by coin type.
34    /// Results are paginated.
35    ///
36    /// The coin type defaults to `0x2::iota::IOTA`.
37    ///
38    /// # Examples
39    ///
40    /// ```rust,no_run
41    /// use std::str::FromStr;
42    ///
43    /// use iota_sdk::{IotaClientBuilder, types::base_types::IotaAddress};
44    ///
45    /// #[tokio::main]
46    /// async fn main() -> Result<(), anyhow::Error> {
47    ///     let iota = IotaClientBuilder::default().build_testnet().await?;
48    ///     let address = IotaAddress::from_str("0x0000....0000")?;
49    ///     let coin_type = String::from("0x168da5bf1f48dafc111b0a488fa454aca95e0b5e::usdc::USDC");
50    ///     let coins = iota
51    ///         .coin_read_api()
52    ///         .get_coins(address, coin_type, None, None)
53    ///         .await?;
54    ///     Ok(())
55    /// }
56    /// ```
57    pub async fn get_coins(
58        &self,
59        owner: IotaAddress,
60        coin_type: impl Into<Option<String>>,
61        cursor: impl Into<Option<ObjectID>>,
62        limit: impl Into<Option<usize>>,
63    ) -> IotaRpcResult<CoinPage> {
64        Ok(self
65            .api
66            .http
67            .get_coins(owner, coin_type.into(), cursor.into(), limit.into())
68            .await?)
69    }
70
71    /// Get all the coins for the given address regardless of coin type.
72    /// Results are paginated.
73    ///
74    /// # Examples
75    ///
76    /// ```rust,no_run
77    /// use std::str::FromStr;
78    ///
79    /// use iota_sdk::{IotaClientBuilder, types::base_types::IotaAddress};
80    ///
81    /// #[tokio::main]
82    /// async fn main() -> Result<(), anyhow::Error> {
83    ///     let iota = IotaClientBuilder::default().build_testnet().await?;
84    ///     let address = IotaAddress::from_str("0x0000....0000")?;
85    ///     let coins = iota
86    ///         .coin_read_api()
87    ///         .get_all_coins(address, None, None)
88    ///         .await?;
89    ///     Ok(())
90    /// }
91    /// ```
92    pub async fn get_all_coins(
93        &self,
94        owner: IotaAddress,
95        cursor: impl Into<Option<ObjectID>>,
96        limit: impl Into<Option<usize>>,
97    ) -> IotaRpcResult<CoinPage> {
98        Ok(self
99            .api
100            .http
101            .get_all_coins(owner, cursor.into(), limit.into())
102            .await?)
103    }
104
105    /// Get the coins for the given address filtered by coin type.
106    /// Returns a stream.
107    ///
108    /// The coin type defaults to `0x2::iota::IOTA`.
109    ///
110    /// # Examples
111    ///
112    /// ```rust,no_run
113    /// use std::str::FromStr;
114    ///
115    /// use iota_sdk::{IotaClientBuilder, types::base_types::IotaAddress};
116    ///
117    /// #[tokio::main]
118    /// async fn main() -> Result<(), anyhow::Error> {
119    ///     let iota = IotaClientBuilder::default().build_testnet().await?;
120    ///     let address = IotaAddress::from_str("0x0000....0000")?;
121    ///     let coin_type = String::from("0x168da5bf1f48dafc111b0a488fa454aca95e0b5e::usdc::USDC");
122    ///     let coins = iota.coin_read_api().get_coins_stream(address, coin_type);
123    ///     Ok(())
124    /// }
125    /// ```
126    pub fn get_coins_stream(
127        &self,
128        owner: IotaAddress,
129        coin_type: impl Into<Option<String>>,
130    ) -> impl Stream<Item = Coin> + '_ {
131        let coin_type = coin_type.into();
132
133        stream::unfold(
134            (
135                vec![],
136                // cursor
137                None,
138                // has_next_page
139                true,
140                coin_type,
141            ),
142            move |(mut data, cursor, has_next_page, coin_type)| async move {
143                if let Some(item) = data.pop() {
144                    Some((item, (data, cursor, /* has_next_page */ true, coin_type)))
145                } else if has_next_page {
146                    let page = self
147                        .get_coins(owner, coin_type.clone(), cursor, Some(100))
148                        .await
149                        .ok()?;
150                    let mut data = page.data;
151                    data.reverse();
152                    data.pop().map(|item| {
153                        (
154                            item,
155                            (data, page.next_cursor, page.has_next_page, coin_type),
156                        )
157                    })
158                } else {
159                    None
160                }
161            },
162        )
163    }
164
165    /// Get a list of coins for the given address filtered by coin type with at
166    /// least `amount` total value.
167    ///
168    /// If it is not possible to select enough coins, this function will return
169    /// an [`Error::InsufficientFunds`].
170    ///
171    /// The coin type defaults to `0x2::iota::IOTA`.
172    ///
173    /// # Examples
174    ///
175    /// ```rust,no_run
176    /// use std::str::FromStr;
177    ///
178    /// use iota_sdk::{IotaClientBuilder, types::base_types::IotaAddress};
179    ///
180    /// #[tokio::main]
181    /// async fn main() -> Result<(), anyhow::Error> {
182    ///     let iota = IotaClientBuilder::default().build_testnet().await?;
183    ///     let address = IotaAddress::from_str("0x0000....0000")?;
184    ///     let coin_type = String::from("0x168da5bf1f48dafc111b0a488fa454aca95e0b5e::usdc::USDC");
185    ///     let coins = iota
186    ///         .coin_read_api()
187    ///         .select_coins(address, coin_type, 5, vec![])
188    ///         .await?;
189    ///     Ok(())
190    /// }
191    /// ```
192    pub async fn select_coins(
193        &self,
194        address: IotaAddress,
195        coin_type: impl Into<Option<String>>,
196        amount: u128,
197        exclude: Vec<ObjectID>,
198    ) -> IotaRpcResult<Vec<Coin>> {
199        let mut total = 0u128;
200        let coins = self
201            .get_coins_stream(address, coin_type.into())
202            .filter(|coin: &Coin| future::ready(!exclude.contains(&coin.coin_object_id)))
203            .take_while(|coin: &Coin| {
204                let ready = future::ready(total < amount);
205                total += coin.balance as u128;
206                ready
207            })
208            .collect::<Vec<_>>()
209            .await;
210
211        if total < amount {
212            return Err(Error::InsufficientFunds { address, amount });
213        }
214        Ok(coins)
215    }
216
217    /// Get the balance for the given address filtered by coin type.
218    ///
219    /// The coin type defaults to `0x2::iota::IOTA`.
220    ///
221    /// # Examples
222    ///
223    /// ```rust,no_run
224    /// use std::str::FromStr;
225    ///
226    /// use iota_sdk::{IotaClientBuilder, types::base_types::IotaAddress};
227    ///
228    /// #[tokio::main]
229    /// async fn main() -> Result<(), anyhow::Error> {
230    ///     let iota = IotaClientBuilder::default().build_testnet().await?;
231    ///     let address = IotaAddress::from_str("0x0000....0000")?;
232    ///     let balance = iota.coin_read_api().get_balance(address, None).await?;
233    ///     Ok(())
234    /// }
235    /// ```
236    pub async fn get_balance(
237        &self,
238        owner: IotaAddress,
239        coin_type: impl Into<Option<String>>,
240    ) -> IotaRpcResult<Balance> {
241        Ok(self.api.http.get_balance(owner, coin_type.into()).await?)
242    }
243
244    /// Get a list of balances grouped by coin type and owned by the given
245    /// address.
246    ///
247    /// # Examples
248    ///
249    /// ```rust,no_run
250    /// use std::str::FromStr;
251    ///
252    /// use iota_sdk::{IotaClientBuilder, types::base_types::IotaAddress};
253    ///
254    /// #[tokio::main]
255    /// async fn main() -> Result<(), anyhow::Error> {
256    ///     let iota = IotaClientBuilder::default().build_testnet().await?;
257    ///     let address = IotaAddress::from_str("0x0000....0000")?;
258    ///     let all_balances = iota.coin_read_api().get_all_balances(address).await?;
259    ///     Ok(())
260    /// }
261    /// ```
262    pub async fn get_all_balances(&self, owner: IotaAddress) -> IotaRpcResult<Vec<Balance>> {
263        Ok(self.api.http.get_all_balances(owner).await?)
264    }
265
266    /// Get the coin metadata (name, symbol, description, decimals, etc.) for a
267    /// given coin type.
268    ///
269    /// # Examples
270    ///
271    /// ```rust,no_run
272    /// use iota_sdk::IotaClientBuilder;
273    ///
274    /// #[tokio::main]
275    /// async fn main() -> Result<(), anyhow::Error> {
276    ///     let iota = IotaClientBuilder::default().build_testnet().await?;
277    ///     let coin_metadata = iota
278    ///         .coin_read_api()
279    ///         .get_coin_metadata("0x2::iota::IOTA")
280    ///         .await?;
281    ///     Ok(())
282    /// }
283    /// ```
284    pub async fn get_coin_metadata(
285        &self,
286        coin_type: impl Into<String>,
287    ) -> IotaRpcResult<Option<IotaCoinMetadata>> {
288        Ok(self.api.http.get_coin_metadata(coin_type.into()).await?)
289    }
290
291    /// Get the total supply for a given coin type.
292    ///
293    /// # Examples
294    ///
295    /// ```rust,no_run
296    /// use iota_sdk::IotaClientBuilder;
297    ///
298    /// #[tokio::main]
299    /// async fn main() -> Result<(), anyhow::Error> {
300    ///     let iota = IotaClientBuilder::default().build_testnet().await?;
301    ///     let total_supply = iota
302    ///         .coin_read_api()
303    ///         .get_total_supply("0x2::iota::IOTA")
304    ///         .await?;
305    ///     Ok(())
306    /// }
307    /// ```
308    pub async fn get_total_supply(&self, coin_type: impl Into<String>) -> IotaRpcResult<Supply> {
309        Ok(self.api.http.get_total_supply(coin_type.into()).await?)
310    }
311
312    /// Get the IOTA circulating supply summary.
313    ///
314    /// # Examples
315    ///
316    /// ```rust,no_run
317    /// use iota_sdk::IotaClientBuilder;
318    ///
319    /// #[tokio::main]
320    /// async fn main() -> Result<(), anyhow::Error> {
321    ///     let iota = IotaClientBuilder::default().build_testnet().await?;
322    ///     let circulating_supply = iota.coin_read_api().get_circulating_supply().await?;
323    ///     Ok(())
324    /// }
325    /// ```
326    pub async fn get_circulating_supply(&self) -> IotaRpcResult<IotaCirculatingSupply> {
327        Ok(self.api.http.get_circulating_supply().await?)
328    }
329}