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