Skip to main content

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