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}