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