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}