waves_rust/api/
node.rs

1use regex::Regex;
2use std::borrow::Borrow;
3
4use std::str::FromStr;
5use std::time::Duration;
6
7use crate::error::{Error, Result};
8use reqwest::{Client, Url};
9use serde_json::Value::Array;
10use serde_json::{Map, Value};
11use tokio::time;
12
13use crate::model::account::{Address, Balance, BalanceDetails};
14use crate::model::asset::asset_details::AssetDetails;
15use crate::model::asset::asset_distribution::AssetDistribution;
16use crate::model::asset::balance::AssetsBalanceResponse;
17use crate::model::data_entry::DataEntry;
18use crate::model::{
19    Alias, AliasesByAddressResponse, Amount, AssetId, Base58String, Block, BlockHeaders,
20    BlockchainRewards, ByteString, ChainId, EvaluateScriptResponse, HistoryBalance, Id, LeaseInfo,
21    ScriptInfo, ScriptMeta, SignedTransaction, TransactionInfoResponse, TransactionStatus,
22    Validation,
23};
24use crate::util::JsonDeserializer;
25
26pub const MAINNET_URL: &str = "https://nodes.wavesnodes.com";
27pub const TESTNET_URL: &str = "https://nodes-testnet.wavesnodes.com";
28pub const STAGENET_URL: &str = "https://nodes-stagenet.wavesnodes.com";
29pub const LOCAL_URL: &str = "http://127.0.0.1:6869";
30
31pub struct Node {
32    url: Url,
33    chain_id: u8,
34    http_client: Client,
35}
36
37impl Node {
38    /// Creates node structure from [Profile]
39    /// to interact with Waves node through REST-API
40    /// ```no_run
41    /// use waves_rust::api::{Node, Profile};
42    ///
43    /// #[tokio::main]
44    /// async fn main()  {
45    ///     let  node = Node::from_profile(Profile::TESTNET);
46    ///     let  addresses = node.get_addresses().await.unwrap();
47    ///     println!("{:?}", addresses);
48    /// }
49    /// ```
50    pub fn from_profile(profile: Profile) -> Node {
51        Node {
52            url: profile.url(),
53            chain_id: profile.chain_id(),
54            http_client: Client::builder()
55                .timeout(Duration::from_secs(60))
56                .build()
57                .expect("Failed to create http client for struct Node"),
58        }
59    }
60
61    /// Creates node structure from [Url]
62    /// to interact with Waves node through REST-API
63    /// ```no_run
64    /// use url::Url;
65    /// use waves_rust::api::Node;
66    /// use waves_rust::model::ChainId;
67    ///
68    /// #[tokio::main]
69    /// async fn main()  {
70    ///     let url = Url::parse("https://nodes-testnet.wavesnodes.com").unwrap();
71    ///     let  node = Node::from_url(url, ChainId::TESTNET.byte());
72    ///     let  addresses = node.get_addresses().await.unwrap();
73    ///     println!("{:?}", addresses);
74    /// }
75    /// ```
76    pub fn from_url(url: Url, chain_id: u8) -> Node {
77        Node {
78            url,
79            chain_id,
80            http_client: Client::builder()
81                .timeout(Duration::from_secs(60))
82                .build()
83                .expect("Failed to create http client for struct Node"),
84        }
85    }
86
87    pub fn url(&self) -> Url {
88        self.url.clone()
89    }
90
91    pub fn chain_id(&self) -> u8 {
92        self.chain_id
93    }
94
95    // ADDRESSES
96
97    /// Get a list of account addresses in the node wallet
98    /// ```no_run
99    /// use waves_rust::api::{Node, Profile};
100    ///
101    /// #[tokio::main]
102    /// async fn main()  {
103    ///     let  node = Node::from_profile(Profile::TESTNET);
104    ///     let  addresses = node.get_addresses().await.unwrap();
105    ///     println!("{:?}", addresses);
106    /// }
107    /// ```
108    pub async fn get_addresses(&self) -> Result<Vec<Address>> {
109        let get_addresses_url = format!("{}addresses", self.url().as_str());
110        let rs = self.get(&get_addresses_url).await?;
111        JsonDeserializer::safe_to_array(&rs)?
112            .iter()
113            .map(|address| address.try_into())
114            .collect()
115    }
116
117    /// Get a list of account addresses in the node wallet
118    /// ```no_run
119    /// use waves_rust::api::{Node, Profile};
120    ///
121    /// #[tokio::main]
122    /// async fn main() {
123    ///     let node = Node::from_profile(Profile::TESTNET);
124    ///     let addresses = node.get_addresses_seq(0, 1).await.unwrap();
125    ///     println!("{:?}", addresses);
126    /// }
127    /// ```
128    pub async fn get_addresses_seq(&self, from_index: u64, to_index: u64) -> Result<Vec<Address>> {
129        let get_addresses_seq_url = format!(
130            "{}addresses/seq/{}/{}",
131            self.url().as_str(),
132            from_index,
133            to_index
134        );
135        let rs = self.get(&get_addresses_seq_url).await?;
136        JsonDeserializer::safe_to_array(&rs)?
137            .iter()
138            .map(|address| address.try_into())
139            .collect()
140    }
141
142    /// Get the regular balance in WAVES at a given address
143    /// ```no_run
144    /// use waves_rust::api::{Node, Profile};
145    /// use waves_rust::model::Address;
146    ///
147    /// #[tokio::main]
148    /// async fn main() {
149    ///     let node = Node::from_profile(Profile::TESTNET);
150    ///     let  address = Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q").unwrap();
151    ///     let balance = node.get_balance(&address).await.unwrap();
152    ///     println!("{}", balance);
153    /// }
154    /// ```
155    pub async fn get_balance(&self, address: &Address) -> Result<u64> {
156        let get_balance_url = format!(
157            "{}addresses/balance/{}",
158            self.url().as_str(),
159            address.encoded(),
160        );
161        let rs = self.get(&get_balance_url).await?;
162        Ok(JsonDeserializer::safe_to_int_from_field(&rs, "balance")? as u64)
163    }
164
165    /// Get the minimum regular balance at a given address for confirmations blocks back from
166    /// the current height
167    /// ```no_run
168    /// use waves_rust::api::{Node, Profile};
169    /// use waves_rust::model::Address;
170    ///
171    /// #[tokio::main]
172    /// async fn main() {
173    ///     let node = Node::from_profile(Profile::TESTNET);
174    ///     let address = Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q").unwrap();
175    ///     let balance = node.get_balance_with_confirmations(&address, 100).await.unwrap();
176    ///     println!("{}", balance);
177    /// }
178    /// ```
179    pub async fn get_balance_with_confirmations(
180        &self,
181        address: &Address,
182        confirmations: u32,
183    ) -> Result<u64> {
184        let get_balance_url = format!(
185            "{}addresses/balance/{}/{}",
186            self.url().as_str(),
187            address.encoded(),
188            confirmations
189        );
190        let rs = self.get(&get_balance_url).await?;
191        Ok(JsonDeserializer::safe_to_int_from_field(&rs, "balance")? as u64)
192    }
193
194    /// Get regular balances for multiple addresses
195    /// ```no_run
196    /// use waves_rust::api::{Node, Profile};
197    /// use waves_rust::model::Address;
198    ///
199    /// #[tokio::main]
200    /// async fn main() {
201    ///     let node = Node::from_profile(Profile::TESTNET);
202    ///     let address1 = Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q").unwrap();
203    ///     let address2 = Address::from_string("3N2yqTEKArWS3ySs2f6t8fpXdjX6cpPuhG8").unwrap();
204    ///     let balances = node.get_balances(&[address1, address2]).await.unwrap();
205    ///     println!("{}", balances);
206    /// }
207    /// ```
208    pub async fn get_balances(&self, addresses: &[Address]) -> Result<Vec<Balance>> {
209        let get_balances_url = format!("{}addresses/balance", self.url().as_str(),);
210        let mut json_addresses: Map<String, Value> = Map::new();
211        json_addresses.insert(
212            "addresses".to_owned(),
213            Array(
214                addresses
215                    .iter()
216                    .map(|address| Value::String(address.encoded()))
217                    .collect(),
218            ),
219        );
220        let rs = &self.post(&get_balances_url, &json_addresses.into()).await?;
221        JsonDeserializer::safe_to_array(rs)?
222            .iter()
223            .map(|balance| balance.try_into())
224            .collect()
225    }
226
227    /// Get regular balances for multiple addresses at the given height
228    /// ```no_run
229    /// use waves_rust::api::{Node, Profile};
230    /// use waves_rust::model::Address;
231    ///
232    /// #[tokio::main]
233    /// async fn main() {
234    ///     let node = Node::from_profile(Profile::TESTNET);
235    ///     let height = node.get_height().await.unwrap();
236    ///     let address1 = Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q").unwrap();
237    ///     let address2 = Address::from_string("3N2yqTEKArWS3ySs2f6t8fpXdjX6cpPuhG8").unwrap();
238    ///     let balances = node.get_balances_at_height(&[address1, address2], height - 10).await.unwrap();
239    ///     println!("{}", balances);
240    /// }
241    /// ```
242    pub async fn get_balances_at_height(
243        &self,
244        addresses: &[Address],
245        height: u32,
246    ) -> Result<Vec<Balance>> {
247        let get_balances_url = format!("{}addresses/balance", self.url().as_str());
248        let mut json_addresses: Map<String, Value> = Map::new();
249        json_addresses.insert(
250            "addresses".to_owned(),
251            Array(
252                addresses
253                    .iter()
254                    .map(|address| Value::String(address.encoded()))
255                    .collect(),
256            ),
257        );
258        json_addresses.insert("height".to_owned(), height.into());
259        let rs = &self.post(&get_balances_url, &json_addresses.into()).await?;
260        JsonDeserializer::safe_to_array(rs)?
261            .iter()
262            .map(|balance| balance.try_into())
263            .collect()
264    }
265
266    /// Get the available, regular, generating, and effective balance
267    /// ```no_run
268    /// use waves_rust::api::{Node, Profile};
269    /// use waves_rust::model::Address;
270    ///
271    /// #[tokio::main]
272    /// async fn main() {
273    ///     let node = Node::from_profile(Profile::TESTNET);
274    ///     let address = Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q").unwrap();
275    ///     let balance_details = node.get_balance_details(&address).await.unwrap();
276    ///     println!("{:?}", balance_details);
277    /// }
278    /// ```
279    pub async fn get_balance_details(&self, address: &Address) -> Result<BalanceDetails> {
280        let get_balance_details_url = format!(
281            "{}addresses/balance/details/{}",
282            self.url().as_str(),
283            address.encoded()
284        );
285        let rs = &self.get(&get_balance_details_url).await?;
286        rs.try_into()
287    }
288
289    /// Read account data entries
290    /// ```no_run
291    /// use waves_rust::api::{Node, Profile};
292    /// use waves_rust::model::Address;
293    ///
294    /// #[tokio::main]
295    /// async fn main() {
296    ///     let node = Node::from_profile(Profile::TESTNET);
297    ///     let address = Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q").unwrap();
298    ///     let account_data = node.get_data(&address).await.unwrap();
299    ///     println!("{:?}", account_data);
300    /// }
301    /// ```
302    pub async fn get_data(&self, address: &Address) -> Result<Vec<DataEntry>> {
303        let get_data_url = format!(
304            "{}addresses/data/{}",
305            self.url().as_str(),
306            address.encoded()
307        );
308        let rs = self.get(&get_data_url).await?;
309
310        JsonDeserializer::deserialize_data_array(&rs)
311    }
312
313    /// Read account data entries by given keys
314    /// ```no_run
315    /// use waves_rust::api::{Node, Profile};
316    /// use waves_rust::model::Address;
317    ///
318    /// #[tokio::main]
319    /// async fn main() {
320    ///     let node = Node::from_profile(Profile::TESTNET);
321    ///     let address = Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q").unwrap();
322    ///     let account_data = node.get_data_by_keys(&address, &["bool", "int"]).await.unwrap();
323    ///     println!("{:?}", account_data);
324    /// }
325    /// ```
326    pub async fn get_data_by_keys(
327        &self,
328        address: &Address,
329        keys: &[&str],
330    ) -> Result<Vec<DataEntry>> {
331        let get_data_url = format!(
332            "{}addresses/data/{}",
333            self.url().as_str(),
334            address.encoded()
335        );
336        let mut json_keys: Map<String, Value> = Map::new();
337        json_keys.insert("keys".to_owned(), keys.into());
338        let rs = self.post(&get_data_url, &json_keys.into()).await?;
339        JsonDeserializer::deserialize_data_array(&rs)
340    }
341
342    /// Read account data entries by given regular expression
343    /// ```no_run
344    /// use waves_rust::api::{Node, Profile};
345    /// use waves_rust::model::Address;
346    /// use regex::Regex;
347    ///
348    /// #[tokio::main]
349    /// async fn main() {  
350    ///     let node = Node::from_profile(Profile::TESTNET);
351    ///     let address = Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q").unwrap();
352    ///     let regex = Regex::new(r"b\w+").unwrap();
353    ///     let account_data = node.get_data_by_regex(&address, &regex).await.unwrap();
354    ///     println!("{:?}", account_data);
355    /// }
356    /// ```
357    pub async fn get_data_by_regex(
358        &self,
359        address: &Address,
360        regex: &Regex,
361    ) -> Result<Vec<DataEntry>> {
362        let get_data_url = format!(
363            "{}addresses/data/{}?matches={}",
364            self.url().as_str(),
365            address.encoded(),
366            urlencoding::encode(regex.as_str())
367        );
368        let rs = self.get(&get_data_url).await?;
369        JsonDeserializer::deserialize_data_array(&rs)
370    }
371
372    /// Read account data entry by given key
373    /// ```no_run
374    /// use waves_rust::api::{Node, Profile};
375    /// use waves_rust::model::Address;
376    ///
377    /// #[tokio::main]
378    /// async fn main() {  
379    ///     let node = Node::from_profile(Profile::TESTNET);
380    ///     let address = Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q").unwrap();
381    ///     let account_data = node.get_data_by_key(&address, "int").await.unwrap();
382    ///     println!("{:?}", account_data);
383    /// }
384    /// ```
385    pub async fn get_data_by_key(&self, address: &Address, key: &str) -> Result<DataEntry> {
386        let get_data_by_key_url = format!(
387            "{}addresses/data/{}/{}",
388            self.url().as_str(),
389            address.encoded(),
390            key
391        );
392        let rs = &self.get(&get_data_by_key_url).await?;
393        rs.try_into()
394    }
395
396    /// Get an account script or a dApp script with additional info by a given address
397    /// ```no_run
398    /// use waves_rust::api::{Node, Profile};
399    /// use waves_rust::model::Address;
400    ///
401    /// #[tokio::main]
402    /// async fn main() {  
403    ///     let node = Node::from_profile(Profile::TESTNET);
404    ///     let address = Address::from_string("3Mv1HwsRtMjyGKSe5DSDnbT2AoTsXAjtwZS").unwrap();
405    ///     let script_info = node.get_script_info(&address).await.unwrap();
406    ///     println!("{:?}", script_info);
407    /// }
408    /// ```
409    pub async fn get_script_info(&self, address: &Address) -> Result<ScriptInfo> {
410        let get_script_info_url = format!(
411            "{}addresses/scriptInfo/{}",
412            self.url().as_str(),
413            address.encoded()
414        );
415        let rs = &self.get(&get_script_info_url).await?;
416        rs.try_into()
417    }
418
419    /// Get an account script meta
420    /// ```no_run
421    /// use waves_rust::api::{Node, Profile};
422    /// use waves_rust::model::Address;
423    ///
424    /// #[tokio::main]
425    /// async fn main() {  
426    ///     let node = Node::from_profile(Profile::TESTNET);
427    ///     let address = Address::from_string("3Mv1HwsRtMjyGKSe5DSDnbT2AoTsXAjtwZS").unwrap();
428    ///     let script_meta = node.get_script_meta(&address).await.unwrap();
429    ///     println!("{:?}", script_meta);
430    /// }
431    /// ```
432    pub async fn get_script_meta(&self, address: &Address) -> Result<ScriptMeta> {
433        let get_script_meta_url = format!(
434            "{}addresses/scriptInfo/{}/meta",
435            self.url().as_str(),
436            address.encoded()
437        );
438        let rs = &self.get(&get_script_meta_url).await?;
439        rs.try_into()
440    }
441
442    // ALIAS
443    /// Get a list of aliases associated with a given address
444    /// ```no_run
445    /// use waves_rust::api::{Node, Profile};
446    /// use waves_rust::model::Address;
447    ///
448    /// #[tokio::main]
449    /// async fn main() {  
450    /// let node = Node::from_profile(Profile::TESTNET);
451    ///     let address = Address::from_string("3MxtrLkrbcG28uTvmbKmhrwGrR65ooHVYvK").unwrap();
452    ///     let aliases = node.get_aliases_by_address(&address).await.unwrap();
453    ///     println!("{:?}", aliases);
454    /// }
455    /// ```
456    pub async fn get_aliases_by_address(
457        &self,
458        address: &Address,
459    ) -> Result<AliasesByAddressResponse> {
460        let get_aliases_by_address_url = format!(
461            "{}alias/by-address/{}",
462            self.url().as_str(),
463            address.encoded()
464        );
465        let rs = &self.get(&get_aliases_by_address_url).await?;
466        rs.try_into()
467    }
468
469    /// Get an address associated with a given alias. Alias should be plain text without an 'alias'
470    /// prefix and chain ID
471    /// ```no_run
472    /// use waves_rust::api::{Node, Profile};
473    /// use waves_rust::model::{Alias, ChainId};
474    ///
475    /// #[tokio::main]
476    /// async fn main() {
477    ///     let node = Node::from_profile(Profile::TESTNET);
478    ///     let alias = Alias::new(ChainId::TESTNET.byte(), "alias1662650000377").unwrap();
479    ///     let address = node.get_address_by_alias(&alias).await.unwrap();
480    ///     println!("{:?}", address);
481    /// }
482    /// ```
483    pub async fn get_address_by_alias(&self, alias: &Alias) -> Result<Address> {
484        let get_address_by_alias_url =
485            format!("{}alias/by-alias/{}", self.url().as_str(), alias.name());
486        let rs = &self.get(&get_address_by_alias_url).await?;
487        Address::from_string(&JsonDeserializer::safe_to_string_from_field(rs, "address")?)
488    }
489
490    // ASSETS
491
492    /// Get asset balance distribution by addresses at a given height
493    /// ```no_run
494    /// use waves_rust::api::{Node, Profile};
495    /// use waves_rust::model::{Address, AssetId, ChainId};
496    ///
497    /// #[tokio::main]
498    /// async fn main() {
499    ///     let node = Node::from_profile(Profile::TESTNET);
500    ///     let height = node.get_height().await.unwrap();
501    ///     let asset_id = AssetId::from_string("DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p").unwrap();
502    ///     let after = Address::from_string("3P2iT1nawotR2QWmjfMAm18xytUiK6cWtHt").unwrap();
503    ///     let address = node.get_asset_distribution(&asset_id, height, 10, Some(after)).await.unwrap();
504    ///     println!("{:?}", address);
505    /// }
506    /// ```
507    pub async fn get_asset_distribution(
508        &self,
509        asset_id: &AssetId,
510        height: u32,
511        limit: u16,
512        after: Option<Address>,
513    ) -> Result<AssetDistribution> {
514        let url = format!(
515            "{}assets/{}/distribution/{}/limit/{}",
516            self.url().as_str(),
517            asset_id.encoded(),
518            height,
519            limit
520        );
521        let rs = match after {
522            Some(after_address) => {
523                let url_with_cursor = format!("{}?after={}", &url, after_address.encoded());
524                self.get(&url_with_cursor).await?
525            }
526            None => self.get(&url).await?,
527        };
528        rs.borrow().try_into()
529    }
530
531    /// Get account balances in all or specified assets (excluding WAVES) at a given address.
532    /// Note: Full portfolio also excludes NFTs.
533    /// ```no_run
534    /// use waves_rust::api::{Node, Profile};
535    /// use waves_rust::model::{Address};
536    ///
537    /// #[tokio::main]
538    /// async fn main() {
539    ///     let node = Node::from_profile(Profile::TESTNET);
540    ///     let address = Address::from_string("3MxtrLkrbcG28uTvmbKmhrwGrR65ooHVYvK").unwrap();
541    ///     let assets_balance = node.get_assets_balance(&address).await.unwrap();
542    ///     println!("{:?}", assets_balance);
543    /// }
544    /// ```
545    pub async fn get_assets_balance(&self, address: &Address) -> Result<AssetsBalanceResponse> {
546        let url = format!(
547            "{}assets/balance/{}",
548            self.url().as_str(),
549            address.encoded()
550        );
551        let rs = &self.get(&url).await?;
552        rs.try_into()
553    }
554
555    /// Get account balances in all or specified assets (excluding WAVES) at a given address.
556    /// Note: Full portfolio also excludes NFTs.
557    /// ```no_run
558    /// use waves_rust::api::{Node, Profile};
559    /// use waves_rust::model::{Address};
560    ///
561    /// #[tokio::main]
562    /// async fn main() {
563    ///     use waves_rust::model::AssetId;
564    /// let node = Node::from_profile(Profile::TESTNET);
565    ///     let address = Address::from_string("3MxtrLkrbcG28uTvmbKmhrwGrR65ooHVYvK").unwrap();
566    ///     let asset_id = AssetId::from_string("8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6").unwrap();
567    ///     let asset_balance = node.get_asset_balance(&address, &asset_id).await.unwrap();
568    ///     println!("{:?}", asset_balance);
569    /// }
570    /// ```
571    pub async fn get_asset_balance(&self, address: &Address, asset_id: &AssetId) -> Result<u64> {
572        let url = format!(
573            "{}assets/balance/{}/{}",
574            self.url().as_str(),
575            address.encoded(),
576            asset_id.encoded()
577        );
578        let rs = &self.get(&url).await?;
579        Ok(JsonDeserializer::safe_to_int_from_field(rs, "balance")? as u64)
580    }
581
582    /// Get detailed information about given asset
583    /// ```no_run
584    /// use waves_rust::api::{Node, Profile};
585    /// use waves_rust::model::AssetId;
586    ///
587    /// #[tokio::main]
588    /// async fn main() {
589    ///     let node = Node::from_profile(Profile::TESTNET);
590    ///     let asset_id = AssetId::from_string("8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6").unwrap();
591    ///     let asset_details = node.get_asset_details(&asset_id).await.unwrap();
592    ///     println!("{:?}", asset_details);
593    /// }
594    /// ```
595    pub async fn get_asset_details(&self, asset_id: &AssetId) -> Result<AssetDetails> {
596        let url = format!(
597            "{}assets/details/{}?full=true",
598            self.url().as_str(),
599            asset_id.encoded()
600        );
601        let rs = &self.get(&url).await?;
602        rs.try_into()
603    }
604
605    /// Get detailed information about given assets
606    /// ```no_run
607    /// use waves_rust::api::{Node, Profile};
608    /// use waves_rust::model::AssetId;
609    ///
610    /// #[tokio::main]
611    /// async fn main() {
612    ///     let node = Node::from_profile(Profile::TESTNET);
613    ///     let asset_id1 = AssetId::from_string("8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6").unwrap();
614    ///     let asset_id2 = AssetId::from_string("973uk5Fbg5eLF8cZg2b2iKsVSoHepdJXRtCuhWcM6MsR").unwrap();
615    ///     let assets_details = node.get_assets_details(&[asset_id1, asset_id2]).await.unwrap();
616    ///     println!("{:?}", assets_details);
617    /// }
618    /// ```
619    pub async fn get_assets_details(&self, asset_ids: &[AssetId]) -> Result<Vec<AssetDetails>> {
620        let url = format!("{}assets/details", self.url().as_str());
621        let mut ids: Map<String, Value> = Map::new();
622        ids.insert(
623            "ids".to_owned(),
624            Array(
625                asset_ids
626                    .iter()
627                    .map(|it| Value::String(it.encoded()))
628                    .collect(),
629            ),
630        );
631        let rs = &self.post(&url, &ids.into()).await?;
632        JsonDeserializer::safe_to_array(rs)?
633            .iter()
634            .map(|asset| asset.try_into())
635            .collect()
636    }
637
638    /// Get a list of non-fungible tokens at a given address. Max for 1000 tokens.
639    /// For pagination, use the parameter {after}
640    /// ```no_run
641    /// use waves_rust::api::{Node, Profile};
642    /// use waves_rust::model::{AssetId, Address};
643    ///
644    /// #[tokio::main]
645    /// async fn main() {
646    ///     let node = Node::from_profile(Profile::MAINNET);
647    ///     let address = Address::from_string("3PAETTtuW7aSiyKtn9GuML3RgtV1xdq1mQW").unwrap();
648    ///     let after = AssetId::from_string("13PtvhAC28kNXXJP3Evgcba5mNMsCAQECUqCPBu5wJou").unwrap();
649    ///     let nfts = node.get_nft(&address, 10, Some(after)).await.unwrap();
650    ///     println!("{:?}", nfts);
651    /// }
652    /// ```
653    pub async fn get_nft(
654        &self,
655        address: &Address,
656        limit: u16,
657        after: Option<AssetId>,
658    ) -> Result<Vec<AssetDetails>> {
659        let url = format!(
660            "{}assets/nft/{}/limit/{}",
661            self.url().as_str(),
662            address.encoded(),
663            limit
664        );
665        let rs = match after {
666            Some(after_id) => {
667                let url_with_cursor = format!("{}?after={}", &url, after_id.encoded());
668                self.get(&url_with_cursor).await?
669            }
670            None => self.get(&url).await?,
671        };
672        JsonDeserializer::safe_to_array(&rs)?
673            .iter()
674            .map(|asset| asset.try_into())
675            .collect()
676    }
677
678    // BLOCKCHAIN
679
680    /// Get current status of block reward
681    /// ```no_run
682    /// use waves_rust::api::{Node, Profile};
683    /// use waves_rust::model::{AssetId, Address};
684    ///
685    /// #[tokio::main]
686    /// async fn main() {
687    ///     let node = Node::from_profile(Profile::TESTNET);
688    ///     let rewards = node.get_blockchain_rewards().await.unwrap();
689    ///     println!("{:?}", rewards);
690    /// }
691    /// ```
692    pub async fn get_blockchain_rewards(&self) -> Result<BlockchainRewards> {
693        let get_blockchain_rewards_url = format!("{}blockchain/rewards", self.url().as_str());
694        let rs = &self.get(&get_blockchain_rewards_url).await?;
695        rs.try_into()
696    }
697
698    /// Get status of block reward at height
699    /// ```no_run
700    /// use waves_rust::api::{Node, Profile};
701    /// use waves_rust::model::{AssetId, Address};
702    ///
703    /// #[tokio::main]
704    /// async fn main() {
705    ///     let node = Node::from_profile(Profile::TESTNET);
706    ///     let current_height = node.get_height().await.unwrap();
707    ///     let rewards = node.get_blockchain_rewards_at_height(current_height - 10).await.unwrap();
708    ///     println!("{:?}", rewards);
709    /// }
710    /// ```
711    pub async fn get_blockchain_rewards_at_height(&self, height: u32) -> Result<BlockchainRewards> {
712        let get_blockchain_rewards_url =
713            format!("{}blockchain/rewards/{}", self.url().as_str(), height);
714        let rs = &self.get(&get_blockchain_rewards_url).await?;
715        rs.try_into()
716    }
717
718    // BLOCKS
719
720    /// Get the current blockchain height
721    /// ```no_run
722    /// use waves_rust::api::{Node, Profile};
723    /// use waves_rust::model::{AssetId, Address};
724    ///
725    /// #[tokio::main]
726    /// async fn main() {
727    ///     let node = Node::from_profile(Profile::TESTNET);
728    ///     let current_height = node.get_height().await.unwrap();
729    ///     println!("{:?}", current_height);
730    /// }
731    /// ```
732    pub async fn get_height(&self) -> Result<u32> {
733        let get_height_url = format!("{}blocks/height", self.url().as_str());
734        let rs = &self.get(&get_height_url).await?;
735        Ok(JsonDeserializer::safe_to_int_from_field(rs, "height")? as u32)
736    }
737
738    /// Get the height of a block by its ID
739    /// ```no_run
740    /// use waves_rust::api::{Node, Profile};
741    /// use waves_rust::model::{AssetId, Address, Base58String};
742    ///
743    /// #[tokio::main]
744    /// async fn main() {
745    ///     let node = Node::from_profile(Profile::TESTNET);
746    ///     let block_id = Base58String::from_string("oReBHRjMcUKqZxH6iVhthxQ72QndBFtfLHngV8aGW9y").unwrap();
747    ///     let height = node.get_block_height_by_id(&block_id).await.unwrap();
748    ///     println!("{:?}", height);
749    /// }
750    /// ```
751    pub async fn get_block_height_by_id(&self, block_id: &Base58String) -> Result<u32> {
752        let get_block_height_url = format!(
753            "{}blocks/height/{}",
754            self.url().as_str(),
755            block_id.encoded()
756        );
757        let rs = &self.get(&get_block_height_url).await?;
758        Ok(JsonDeserializer::safe_to_int_from_field(rs, "height")? as u32)
759    }
760
761    /// Get height of the most recent block such that its timestamp does not exceed the given {timestamp}
762    /// ```no_run
763    /// use waves_rust::api::{Node, Profile};
764    /// use waves_rust::model::{AssetId, Address};
765    /// use waves_rust::util::get_current_epoch_millis;
766    ///
767    /// #[tokio::main]
768    /// async fn main() {    
769    ///     let node = Node::from_profile(Profile::TESTNET);
770    ///     let now = get_current_epoch_millis();
771    ///     let height = node.get_block_height_by_timestamp(now - 10_000).await.unwrap();
772    ///     println!("{:?}", height);
773    /// }
774    /// ```
775    pub async fn get_block_height_by_timestamp(&self, timestamp: u64) -> Result<u32> {
776        let get_block_height_url = format!(
777            "{}blocks/heightByTimestamp/{}",
778            self.url().as_str(),
779            timestamp
780        );
781        let rs = &self.get(&get_block_height_url).await?;
782        Ok(JsonDeserializer::safe_to_int_from_field(rs, "height")? as u32)
783    }
784
785    /// Average delay in milliseconds between last {block_num} blocks starting from block with {start_block_id}}
786    /// ```no_run
787    /// use waves_rust::api::{Node, Profile};
788    /// use waves_rust::model::Base58String;
789    ///
790    /// #[tokio::main]
791    /// async fn main() {    
792    ///     let node = Node::from_profile(Profile::TESTNET);
793    ///     let block_id = Base58String::from_string("oReBHRjMcUKqZxH6iVhthxQ72QndBFtfLHngV8aGW9y").unwrap();
794    ///     let blocks_delay = node.get_blocks_delay(&block_id, 10).await.unwrap();
795    ///     println!("{:?}", blocks_delay);
796    /// }
797    /// ```
798    pub async fn get_blocks_delay(
799        &self,
800        start_block_id: &Base58String,
801        block_num: u32,
802    ) -> Result<u32> {
803        let get_blocks_delay_url = format!(
804            "{}blocks/delay/{}/{}",
805            self.url().as_str(),
806            start_block_id.encoded(),
807            block_num
808        );
809        let rs = &self.get(&get_blocks_delay_url).await?;
810        Ok(JsonDeserializer::safe_to_int_from_field(rs, "delay")? as u32)
811    }
812
813    /// Get height of the most recent block such that its timestamp does not exceed the given {timestamp}
814    /// ```no_run
815    /// use waves_rust::api::{Node, Profile};
816    ///
817    /// #[tokio::main]
818    /// async fn main() {    
819    ///     let node = Node::from_profile(Profile::TESTNET);
820    ///     let height = node.get_height().await.unwrap();
821    ///     let block_headers = node.get_block_headers_at_height(height).await.unwrap();
822    ///     println!("{:?}", block_headers);
823    /// }
824    /// ```
825    pub async fn get_block_headers_at_height(&self, height: u32) -> Result<BlockHeaders> {
826        let get_block_headers_url = format!("{}blocks/headers/at/{}", self.url().as_str(), height);
827        let rs = &self.get(&get_block_headers_url).await?;
828        rs.try_into()
829    }
830
831    /// Get headers of a given block
832    /// ```no_run
833    /// use waves_rust::api::{Node, Profile};
834    /// use waves_rust::model::Base58String;
835    ///
836    /// #[tokio::main]
837    /// async fn main() {    
838    ///     let node = Node::from_profile(Profile::TESTNET);
839    ///     let block_id = Base58String::from_string("oReBHRjMcUKqZxH6iVhthxQ72QndBFtfLHngV8aGW9y").unwrap();
840    ///     let block_headers = node.get_block_headers_by_id(&block_id).await.unwrap();
841    ///     println!("{:?}", block_headers);
842    /// }
843    /// ```
844    pub async fn get_block_headers_by_id(&self, block_id: &Base58String) -> Result<BlockHeaders> {
845        let get_block_headers_url = format!(
846            "{}blocks/headers/{}",
847            self.url().as_str(),
848            block_id.encoded()
849        );
850        let rs = &self.get(&get_block_headers_url).await?;
851        rs.try_into()
852    }
853
854    /// Get block headers at a given range of heights. Max range {from_height}-{to_height} is 100 blocks
855    /// ```no_run
856    /// use waves_rust::api::{Node, Profile};
857    ///
858    /// #[tokio::main]
859    /// async fn main() {    
860    ///     let node = Node::from_profile(Profile::TESTNET);
861    ///     let to_height = node.get_height().await.unwrap();
862    ///     let from_height = to_height.clone() - 5;
863    ///     let block_headers = node.get_blocks_headers_seq(from_height, to_height).await.unwrap();
864    ///     println!("{:?}", block_headers);
865    /// }
866    /// ```
867    pub async fn get_blocks_headers_seq(
868        &self,
869        from_height: u32,
870        to_height: u32,
871    ) -> Result<Vec<BlockHeaders>> {
872        let get_blocks_headers_seq_url = format!(
873            "{}blocks/headers/seq/{}/{}",
874            self.url().as_str(),
875            from_height,
876            to_height
877        );
878        let rs = &self.get(&get_blocks_headers_seq_url).await?;
879        JsonDeserializer::safe_to_array(rs)?
880            .iter()
881            .map(|block| block.try_into())
882            .collect()
883    }
884
885    /// Get headers of the block at the current blockchain height
886    /// ```no_run
887    /// use waves_rust::api::{Node, Profile};
888    ///
889    /// #[tokio::main]
890    /// async fn main() {    
891    ///     let node = Node::from_profile(Profile::TESTNET);
892    ///     let block_headers = node.get_last_block_headers().await.unwrap();
893    ///     println!("{:?}", block_headers);
894    /// }
895    /// ```
896    pub async fn get_last_block_headers(&self) -> Result<BlockHeaders> {
897        let get_last_block_headers_url = format!("{}blocks/headers/last", self.url().as_str());
898        let rs = &self.get(&get_last_block_headers_url).await?;
899        rs.try_into()
900    }
901
902    /// Get a block at a given height
903    /// ```no_run
904    /// use waves_rust::api::{Node, Profile};
905    ///
906    /// #[tokio::main]
907    /// async fn main() {    
908    ///     let node = Node::from_profile(Profile::TESTNET);
909    ///     let current_height = node.get_height().await.unwrap();
910    ///     let block = node.get_block_at_height(current_height).await.unwrap();
911    ///     println!("{:?}", block);
912    /// }
913    /// ```
914    pub async fn get_block_at_height(&self, height: u32) -> Result<Block> {
915        let get_block_at_height_url = format!("{}blocks/at/{}", self.url().as_str(), height);
916        let rs = &self.get(&get_block_at_height_url).await?;
917        rs.try_into()
918    }
919
920    /// Get a block by its ID
921    /// ```no_run
922    /// use waves_rust::api::{Node, Profile};
923    /// use waves_rust::model::Base58String;
924    ///
925    /// #[tokio::main]
926    /// async fn main() {    
927    ///     let node = Node::from_profile(Profile::TESTNET);
928    ///     let block_id = Base58String::from_string("oReBHRjMcUKqZxH6iVhthxQ72QndBFtfLHngV8aGW9y").unwrap();
929    ///     let block = node.get_block_by_id(&block_id).await.unwrap();
930    ///     println!("{:?}", block);
931    /// }
932    /// ```
933    pub async fn get_block_by_id(&self, block_id: &Base58String) -> Result<Block> {
934        let get_block_by_id_url = format!("{}blocks/{}", self.url().as_str(), block_id.encoded());
935        let rs = &self.get(&get_block_by_id_url).await?;
936        rs.try_into()
937    }
938
939    /// Get blocks at a given range of heights. Max range is 100 blocks
940    /// ```no_run
941    /// use waves_rust::api::{Node, Profile};
942    ///
943    /// #[tokio::main]
944    /// async fn main() {    
945    ///     let node = Node::from_profile(Profile::TESTNET);
946    ///     let to_height = node.get_height().await.unwrap();
947    ///     let from_height = to_height.clone() - 5;
948    ///     let blocks = node.get_blocks(from_height, to_height).await.unwrap();
949    ///     println!("{:?}", blocks);
950    /// }
951    /// ```
952    pub async fn get_blocks(&self, from_height: u32, to_height: u32) -> Result<Vec<Block>> {
953        let get_blocks_url = format!(
954            "{}blocks/seq/{}/{}",
955            self.url().as_str(),
956            from_height,
957            to_height
958        );
959        let rs = &self.get(&get_blocks_url).await?;
960        JsonDeserializer::safe_to_array(rs)?
961            .iter()
962            .map(|block| block.try_into())
963            .collect()
964    }
965
966    /// Get the block at the current blockchain height
967    /// ```no_run
968    /// use waves_rust::api::{Node, Profile};
969    ///
970    /// #[tokio::main]
971    /// async fn main() {    
972    ///     let node = Node::from_profile(Profile::TESTNET);
973    ///     let block = node.get_last_block().await.unwrap();
974    ///     println!("{:?}", block);
975    /// }
976    /// ```
977    pub async fn get_last_block(&self) -> Result<Block> {
978        let get_last_block_url = format!("{}blocks/last", self.url().as_str());
979        let rs = &self.get(&get_last_block_url).await?;
980        rs.try_into()
981    }
982
983    /// Get a list of blocks forged by a given address. Max range is 100 blocks
984    /// ```no_run
985    /// use waves_rust::api::{Node, Profile};
986    /// use waves_rust::model::Address;
987    ///
988    /// #[tokio::main]
989    /// async fn main() {
990    ///     let node = Node::from_profile(Profile::TESTNET);
991    ///     let generator = Address::from_string("3Mxv6Dpa1qRuyQBRFg3GwUaf3rcjHqWwNmC").unwrap();
992    ///     let to_height = node.get_height().await.unwrap();
993    ///     let from_height = to_height.clone() - 5;
994    ///     let blocks = node.get_blocks_by_generator(&generator, from_height, to_height).await.unwrap();
995    ///     println!("{:?}", blocks);
996    /// }
997    /// ```
998    pub async fn get_blocks_by_generator(
999        &self,
1000        generator: &Address,
1001        from_height: u32,
1002        to_height: u32,
1003    ) -> Result<Vec<Block>> {
1004        let get_blocks_by_generator_url = format!(
1005            "{}blocks/address/{}/{}/{}",
1006            self.url().as_str(),
1007            generator.encoded(),
1008            from_height,
1009            to_height
1010        );
1011        let rs = &self.get(&get_blocks_by_generator_url).await?;
1012        JsonDeserializer::safe_to_array(rs)?
1013            .iter()
1014            .map(|block| block.try_into())
1015            .collect()
1016    }
1017
1018    // NODE
1019    /// Get Waves node version
1020    /// ```no_run
1021    /// use waves_rust::api::{Node, Profile};
1022    ///
1023    /// #[tokio::main]
1024    /// async fn main() {
1025    ///     let node = Node::from_profile(Profile::TESTNET);
1026    ///     let version = node.get_version().await.unwrap();
1027    ///     println!("{:?}", version);
1028    /// }
1029    /// ```
1030    pub async fn get_version(&self) -> Result<String> {
1031        let get_version_url = format!("{}node/version", self.url().as_str());
1032        let rs = &self.get(&get_version_url).await?;
1033        JsonDeserializer::safe_to_string_from_field(rs, "version")
1034    }
1035
1036    // DEBUG
1037    /// Get history of the regular balance at a given address
1038    /// ```no_run
1039    /// use waves_rust::api::{Node, Profile};
1040    /// use waves_rust::model::Address;
1041    ///
1042    /// #[tokio::main]
1043    /// async fn main() {
1044    ///     let node = Node::from_profile(Profile::TESTNET);
1045    ///     let address = Address::from_string("3Mxv6Dpa1qRuyQBRFg3GwUaf3rcjHqWwNmC").unwrap();
1046    ///     let history = node.get_balance_history(&address).await.unwrap();
1047    ///     println!("{:?}", history);
1048    /// }
1049    /// ```
1050    pub async fn get_balance_history(&self, address: &Address) -> Result<Vec<HistoryBalance>> {
1051        let get_balance_history_url = format!(
1052            "{}debug/balances/history/{}",
1053            self.url().as_str(),
1054            address.encoded()
1055        );
1056        let rs = &self.get(&get_balance_history_url).await?;
1057        JsonDeserializer::safe_to_array(rs)?
1058            .iter()
1059            .map(|balance| balance.try_into())
1060            .collect()
1061    }
1062
1063    /// Validates a transaction and measures time spent in milliseconds
1064    /// ```no_run
1065    /// use waves_rust::api::{Node, Profile};
1066    /// use waves_rust::model::{Address,Amount, Base58String, ChainId, Transaction, TransactionData,
1067    /// TransferTransaction, PrivateKey};
1068    /// use waves_rust::util::{Crypto, get_current_epoch_millis};
1069    ///
1070    /// #[tokio::main]
1071    /// async fn main() {
1072    ///     let node = Node::from_profile(Profile::TESTNET);
1073    ///     let  private_key = PrivateKey::from_seed(&Crypto::get_random_seed_phrase(12), 0).unwrap();
1074    ///     let signed_tx = Transaction::new(
1075    ///         TransactionData::Transfer(TransferTransaction::new(
1076    ///             Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q")?,
1077    ///             Amount::new(100, None),
1078    ///             Base58String::empty(),
1079    ///         )),
1080    ///         Amount::new(100000, None),
1081    ///         get_current_epoch_millis(),
1082    ///         private_key.public_key(),
1083    ///         3,
1084    ///         ChainId::TESTNET.byte(),
1085    ///     )
1086    ///     .sign(&private_key).unwrap();
1087    ///     let validation = node.validate_transaction(&signed_tx).await.unwrap();
1088    ///     println!("{:?}", validation);
1089    /// }
1090    /// ```
1091    pub async fn validate_transaction(&self, signed_tx: &SignedTransaction) -> Result<Validation> {
1092        let validate_url = format!("{}debug/validate", self.url().as_str());
1093        let rs = &self.post(&validate_url, &signed_tx.to_json()?).await?;
1094        rs.try_into()
1095    }
1096
1097    // LEASING
1098
1099    /// Get all active leases involving a given address
1100    /// ```no_run
1101    /// use waves_rust::api::{Node, Profile};
1102    /// use waves_rust::model::Address;
1103    ///
1104    /// #[tokio::main]
1105    /// async fn main() {
1106    ///     let node = Node::from_profile(Profile::TESTNET);
1107    ///     let address = Address::from_string("3Mxv6Dpa1qRuyQBRFg3GwUaf3rcjHqWwNmC").unwrap();
1108    ///     let active_leases = node.get_active_leases(&address).await.unwrap();
1109    ///     println!("{:?}", active_leases);
1110    /// }
1111    /// ```
1112    pub async fn get_active_leases(&self, address: &Address) -> Result<Vec<LeaseInfo>> {
1113        let get_active_leases_url = format!(
1114            "{}leasing/active/{}",
1115            self.url().as_str(),
1116            address.encoded()
1117        );
1118        let rs = &self.get(&get_active_leases_url).await?;
1119        JsonDeserializer::safe_to_array(rs)?
1120            .iter()
1121            .map(|lease| lease.try_into())
1122            .collect()
1123    }
1124
1125    /// Get lease parameters by lease ID
1126    /// ```no_run
1127    /// use waves_rust::api::{Node, Profile};
1128    /// use waves_rust::model::{Address, Id};
1129    ///
1130    /// #[tokio::main]
1131    /// async fn main() {
1132    ///     let node = Node::from_profile(Profile::TESTNET);
1133    ///     let lease_id = Id::from_string("BiJR8gCxR7crGEdy31jLkYpjpLy98kq3NuxPE8Z2Uk3b").unwrap();
1134    ///     let lease_info = node.get_lease_info(&lease_id).await.unwrap();
1135    ///     println!("{:?}", lease_info);
1136    /// }
1137    /// ```
1138    pub async fn get_lease_info(&self, lease_id: &Id) -> Result<LeaseInfo> {
1139        let get_lease_info_url =
1140            format!("{}leasing/info/{}", self.url().as_str(), lease_id.encoded());
1141        let rs = &self.get(&get_lease_info_url).await?;
1142        rs.try_into()
1143    }
1144
1145    /// Get lease parameters by lease IDs
1146    /// ```no_run
1147    /// use waves_rust::api::{Node, Profile};
1148    /// use waves_rust::model::{Address, Id};
1149    ///
1150    /// #[tokio::main]
1151    /// async fn main() {
1152    ///     let node = Node::from_profile(Profile::TESTNET);
1153    ///     let lease_id = Id::from_string("BiJR8gCxR7crGEdy31jLkYpjpLy98kq3NuxPE8Z2Uk3b").unwrap();
1154    ///     let leases_info = node.get_leases_info(&[lease_id]).await.unwrap();
1155    ///     println!("{:?}", leases_info);
1156    /// }
1157    /// ```
1158    pub async fn get_leases_info(&self, lease_ids: &[Id]) -> Result<Vec<LeaseInfo>> {
1159        let get_leases_info_url = format!("{}leasing/info", self.url().as_str());
1160        let mut ids: Map<String, Value> = Map::new();
1161        ids.insert(
1162            "ids".to_owned(),
1163            Array(
1164                lease_ids
1165                    .iter()
1166                    .map(|it| Value::String(it.encoded()))
1167                    .collect(),
1168            ),
1169        );
1170        let rs = &self.post(&get_leases_info_url, &ids.into()).await?;
1171        JsonDeserializer::safe_to_array(rs)?
1172            .iter()
1173            .map(|lease| lease.try_into())
1174            .collect()
1175    }
1176
1177    // TRANSACTIONS
1178
1179    /// Get the minimum fee for a given transaction
1180    /// ```no_run
1181    /// use waves_rust::api::{Node, Profile};
1182    /// use waves_rust::model::{Address,Amount, Base58String, ChainId, Transaction, TransactionData,
1183    /// TransferTransaction, PrivateKey};
1184    /// use waves_rust::util::{Crypto, get_current_epoch_millis};
1185    ///
1186    /// #[tokio::main]
1187    /// async fn main() {
1188    ///     let node = Node::from_profile(Profile::TESTNET);
1189    ///     let private_key = PrivateKey::from_seed(&Crypto::get_random_seed_phrase(12), 0).unwrap();
1190    ///     let signed_tx = Transaction::new(
1191    ///         TransactionData::Transfer(TransferTransaction::new(
1192    ///             Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q")?,
1193    ///             Amount::new(100, None),
1194    ///             Base58String::empty(),
1195    ///         )),
1196    ///         Amount::new(100000, None),
1197    ///         get_current_epoch_millis(),
1198    ///         private_key.public_key(),
1199    ///         3,
1200    ///         ChainId::TESTNET.byte(),
1201    ///     )
1202    ///     .sign(&private_key).unwrap();
1203    ///     let fee = node.calculate_transaction_fee(&signed_tx).await.unwrap();
1204    ///     println!("{:?}", fee);
1205    /// }
1206    /// ```
1207    pub async fn calculate_transaction_fee(
1208        &self,
1209        transaction: &SignedTransaction,
1210    ) -> Result<Amount> {
1211        let get_lease_info_url = format!("{}transactions/calculateFee", self.url().as_str());
1212        let rs = &self
1213            .post(&get_lease_info_url, &transaction.to_json()?)
1214            .await?;
1215        Ok(Amount::new(
1216            JsonDeserializer::safe_to_int_from_field(rs, "feeAmount")? as u64,
1217            JsonDeserializer::asset_id_from_json(rs, "feeAssetId")?,
1218        ))
1219    }
1220
1221    /// Broadcast a signed transaction.
1222    /// ```no_run
1223    /// use waves_rust::api::{Node, Profile};
1224    /// use waves_rust::model::{Address,Amount, Base58String, ChainId, Transaction, TransactionData,
1225    /// TransferTransaction, PrivateKey};
1226    /// use waves_rust::util::{Crypto, get_current_epoch_millis};
1227    ///
1228    /// #[tokio::main]
1229    /// async fn main() {
1230    ///     let node = Node::from_profile(Profile::TESTNET);
1231    ///     let private_key = PrivateKey::from_seed(&Crypto::get_random_seed_phrase(12), 0).unwrap();
1232    ///     let signed_tx = Transaction::new(
1233    ///         TransactionData::Transfer(TransferTransaction::new(
1234    ///             Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q")?,
1235    ///             Amount::new(100, None),
1236    ///             Base58String::empty(),
1237    ///         )),
1238    ///         Amount::new(100000, None),
1239    ///         get_current_epoch_millis(),
1240    ///         private_key.public_key(),
1241    ///         3,
1242    ///         ChainId::TESTNET.byte(),
1243    ///     )
1244    ///     .sign(&private_key).unwrap();
1245    ///     node.broadcast(&signed_tx).await.unwrap();
1246    /// }
1247    /// ```
1248    pub async fn broadcast(&self, signed_tx: &SignedTransaction) -> Result<SignedTransaction> {
1249        let broadcast_tx_url = format!("{}transactions/broadcast", self.url().as_str());
1250        let rs = &self.post(&broadcast_tx_url, &signed_tx.to_json()?).await?;
1251        rs.try_into()
1252    }
1253
1254    /// Get a transaction by its ID
1255    /// ```no_run
1256    /// use waves_rust::api::{Node, Profile};
1257    /// use waves_rust::model::{Address, Id};
1258    ///
1259    /// #[tokio::main]
1260    /// async fn main() {
1261    ///     let node = Node::from_profile(Profile::TESTNET);
1262    ///     let tx_id = Id::from_string("3kuZKAeyjcqavmezy86sWCAeXrgt3HBKa4HA8CZdT8nH").unwrap();
1263    ///     let tx_info = node.get_transaction_info(&tx_id).await.unwrap();
1264    ///     println!("{:?}", tx_info);
1265    /// }
1266    /// ```
1267    pub async fn get_transaction_info(
1268        &self,
1269        transaction_id: &Id,
1270    ) -> Result<TransactionInfoResponse> {
1271        let get_tx_info_url = format!(
1272            "{}transactions/info/{}",
1273            self.url().as_str(),
1274            transaction_id.encoded()
1275        );
1276        let rs = &self.get(&get_tx_info_url).await?;
1277        rs.try_into()
1278    }
1279
1280    /// Get a list of the latest transactions involving a given address.
1281    /// For pagination, use the parameter {after_tx_id}
1282    /// ```no_run
1283    /// use waves_rust::api::{Node, Profile};
1284    /// use waves_rust::model::{Address, Id};
1285    ///
1286    /// #[tokio::main]
1287    /// async fn main() {
1288    ///     let node = Node::from_profile(Profile::TESTNET);
1289    ///     let address = Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q").unwrap();
1290    ///     let after_id = Some(Id::from_string(
1291    ///         "3p6ffM2uyseFWPRQUcXMpr3gBKkKgt7jVQ8iDGQhVpRa",
1292    ///     ).unwrap());
1293    ///     let txs_info = node.get_transactions_by_address(&address, 10, after_id).await.unwrap();
1294    ///     println!("{:?}", txs_info);
1295    /// }
1296    /// ```
1297    pub async fn get_transactions_by_address(
1298        &self,
1299        address: &Address,
1300        limit: u16,
1301        after_tx_id: Option<Id>,
1302    ) -> Result<Vec<TransactionInfoResponse>> {
1303        let get_tx_info_by_address = format!(
1304            "{}transactions/address/{}/limit/{}",
1305            self.url().as_str(),
1306            address.encoded(),
1307            limit
1308        );
1309        let rs = match after_tx_id {
1310            Some(after_id) => {
1311                let url_with_cursor =
1312                    format!("{}?after={}", &get_tx_info_by_address, after_id.encoded());
1313                self.get(&url_with_cursor).await?
1314            }
1315            None => self.get(&get_tx_info_by_address).await?,
1316        };
1317        JsonDeserializer::safe_to_array(&JsonDeserializer::safe_to_array(&rs)?[0])?
1318            .iter()
1319            .map(|tx| tx.try_into())
1320            .collect()
1321    }
1322
1323    /// Get transaction status by its ID
1324    /// ```no_run
1325    /// use waves_rust::api::{Node, Profile};
1326    /// use waves_rust::model::Id;
1327    ///
1328    /// #[tokio::main]
1329    /// async fn main() {
1330    ///     let node = Node::from_profile(Profile::TESTNET);
1331    ///     let id = Id::from_string("3p6ffM2uyseFWPRQUcXMpr3gBKkKgt7jVQ8iDGQhVpRa").unwrap();
1332    ///     let tx_status = node.get_transaction_status(&id).await.unwrap();
1333    ///     println!("{:?}", tx_status);
1334    /// }
1335    /// ```
1336    pub async fn get_transaction_status(&self, transaction_id: &Id) -> Result<TransactionStatus> {
1337        let get_tx_status_url = format!(
1338            "{}transactions/status?id={}",
1339            self.url().as_str(),
1340            transaction_id.encoded()
1341        );
1342        let rs = &self.get(&get_tx_status_url).await?;
1343        JsonDeserializer::safe_to_array(rs)?[0].borrow().try_into()
1344    }
1345
1346    /// Get transaction statuses by their ID
1347    /// ```no_run
1348    /// use waves_rust::api::{Node, Profile};
1349    /// use waves_rust::model::Id;
1350    ///
1351    /// #[tokio::main]
1352    /// async fn main() {
1353    ///     let node = Node::from_profile(Profile::TESTNET);
1354    ///     let id = Id::from_string("3p6ffM2uyseFWPRQUcXMpr3gBKkKgt7jVQ8iDGQhVpRa").unwrap();
1355    ///     let txs_statuses = node.get_transactions_statuses(&[id]).await.unwrap();
1356    ///     println!("{:?}", txs_statuses);
1357    /// }
1358    /// ```
1359    pub async fn get_transactions_statuses(
1360        &self,
1361        transaction_ids: &[Id],
1362    ) -> Result<Vec<TransactionStatus>> {
1363        let get_tx_status_url = format!("{}transactions/status", self.url().as_str());
1364        let mut ids = Map::new();
1365        ids.insert(
1366            "ids".to_owned(),
1367            Array(
1368                transaction_ids
1369                    .iter()
1370                    .map(|id| Value::String(id.encoded()))
1371                    .collect(),
1372            ),
1373        );
1374        let rs = &self.post(&get_tx_status_url, &ids.into()).await?;
1375        JsonDeserializer::safe_to_array(rs)?
1376            .iter()
1377            .map(|status| status.try_into())
1378            .collect()
1379    }
1380
1381    /// Get an unconfirmed transaction by its ID
1382    /// ```no_run
1383    /// use waves_rust::api::{Node, Profile};
1384    /// use waves_rust::model::Id;
1385    ///
1386    /// #[tokio::main]
1387    /// async fn main() {
1388    ///     let node = Node::from_profile(Profile::TESTNET);
1389    ///     let id = Id::from_string("3p6ffM2uyseFWPRQUcXMpr3gBKkKgt7jVQ8iDGQhVpRa").unwrap();
1390    ///     let unconfirmed_tx = node.get_unconfirmed_transaction(&id).await.unwrap();
1391    ///     println!("{:?}", unconfirmed_tx);
1392    /// }
1393    /// ```
1394    pub async fn get_unconfirmed_transaction(
1395        &self,
1396        transaction_id: &Id,
1397    ) -> Result<SignedTransaction> {
1398        let get_unconfirmed_tx_url = format!(
1399            "{}transactions/unconfirmed/info/{}",
1400            self.url().as_str(),
1401            transaction_id.encoded()
1402        );
1403        let rs = &self.get(&get_unconfirmed_tx_url).await?;
1404        rs.try_into()
1405    }
1406
1407    /// Get a list of transactions in node's UTX pool
1408    /// ```no_run
1409    /// use waves_rust::api::{Node, Profile};
1410    ///
1411    /// #[tokio::main]
1412    /// async fn main() {
1413    ///     let node = Node::from_profile(Profile::TESTNET);
1414    ///     let unconfirmed_txs = node.get_unconfirmed_transactions().await.unwrap();
1415    ///     println!("{:?}", unconfirmed_txs);
1416    /// }
1417    /// ```
1418    pub async fn get_unconfirmed_transactions(&self) -> Result<Vec<SignedTransaction>> {
1419        let get_unconfirmed_txs_url = format!("{}transactions/unconfirmed", self.url().as_str());
1420        let rs = &self.get(&get_unconfirmed_txs_url).await?;
1421        JsonDeserializer::safe_to_array(rs)?
1422            .iter()
1423            .map(|tx| tx.try_into())
1424            .collect()
1425    }
1426
1427    /// Get the number of transactions in the UTX pool
1428    /// ```no_run
1429    /// use waves_rust::api::{Node, Profile};
1430    ///
1431    /// #[tokio::main]
1432    /// async fn main() {
1433    ///     let node = Node::from_profile(Profile::TESTNET);
1434    ///     let utx_size = node.get_utx_size().await.unwrap();
1435    ///     println!("{:?}", utx_size);
1436    /// }
1437    /// ```
1438    pub async fn get_utx_size(&self) -> Result<u32> {
1439        let get_utx_size_url = format!("{}transactions/unconfirmed/size", self.url().as_str());
1440        let rs = &self.get(&get_utx_size_url).await?;
1441        Ok(JsonDeserializer::safe_to_int_from_field(rs, "size")? as u32)
1442    }
1443
1444    // UTILS
1445
1446    /// Compiles string code to base64 script representation
1447    /// ```no_run
1448    /// use waves_rust::api::{Node, Profile};
1449    ///
1450    /// #[tokio::main]
1451    /// async fn main() {
1452    ///     let node = Node::from_profile(Profile::TESTNET);
1453    ///     let script = "{-# CONTENT_TYPE EXPRESSION #-} sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)";
1454    ///     let compiled_script = node.compile_script(script, true).await.unwrap();
1455    ///     println!("{:?}", compiled_script);
1456    /// }
1457    /// ```
1458    pub async fn compile_script(
1459        &self,
1460        source: &str,
1461        enable_compaction: bool,
1462    ) -> Result<ScriptInfo> {
1463        let compile_script_url = format!(
1464            "{}utils/script/compileCode?compact={}",
1465            self.url().as_str(),
1466            enable_compaction
1467        );
1468        let rs = &self
1469            .post_plain_text(&compile_script_url, source.to_owned())
1470            .await?;
1471        rs.try_into()
1472    }
1473
1474    /// Evaluate script with given expression and get result
1475    pub async fn evaluate_script(
1476        &self,
1477        address: &Address,
1478        expr: &str,
1479    ) -> Result<EvaluateScriptResponse> {
1480        let evaluate_script_url = format!(
1481            "{}utils/script/evaluate/{}",
1482            self.url().as_str(),
1483            address.encoded()
1484        );
1485
1486        let body: Map<String, Value> =
1487            Map::from_iter(vec![("expr".to_owned(), Value::String(expr.to_owned()))]);
1488
1489        let rs = &self.post(&evaluate_script_url, &body.into()).await?;
1490        rs.try_into()
1491    }
1492
1493    // WAITING
1494    pub async fn wait_for_transaction(
1495        &self,
1496        id: &Id,
1497        polling_interval: Duration,
1498        timeout: Duration,
1499    ) -> Result<()> {
1500        let mut interval = time::interval(polling_interval);
1501        let mut time_spent = Duration::from_millis(0);
1502
1503        let mut last_error: Error = Error::NodeError {
1504            error: 0,
1505            message: "undefined".to_string(),
1506        };
1507
1508        while time_spent < timeout {
1509            match self.get_transaction_info(id).await {
1510                Ok(_) => return Ok(()),
1511                Err(err) => last_error = err,
1512            }
1513            interval.tick().await;
1514            time_spent += polling_interval;
1515        }
1516        Err(last_error)
1517    }
1518
1519    async fn get(&self, url: &str) -> Result<Value> {
1520        let response = self.http_client.get(url).send().await?;
1521        let rs = response.json().await?;
1522        Self::error_check(&rs)?;
1523        Ok(rs)
1524    }
1525
1526    async fn post(&self, url: &str, body: &Value) -> Result<Value> {
1527        let response = self.http_client.post(url).json(body).send().await?;
1528        let rs = response.json().await?;
1529        Self::error_check(&rs)?;
1530        Ok(rs)
1531    }
1532
1533    async fn post_plain_text(&self, url: &str, body: String) -> Result<Value> {
1534        let response = self.http_client.post(url).body(body.clone()).send().await?;
1535        let rs = response.json().await?;
1536        Self::error_check(&rs)?;
1537        Ok(rs)
1538    }
1539
1540    fn error_check(rs: &Value) -> Result<()> {
1541        let error = rs["error"].as_i64();
1542        if let Some(err) = error {
1543            let message = rs["message"].as_str().unwrap_or("");
1544            return Err(Error::NodeError {
1545                error: err as u32,
1546                message: message.to_owned(),
1547            });
1548        }
1549        Ok(())
1550    }
1551}
1552
1553pub enum Profile {
1554    MAINNET,
1555    TESTNET,
1556    STAGENET,
1557}
1558
1559impl Profile {
1560    pub fn url(&self) -> Url {
1561        let url = match *self {
1562            Profile::MAINNET => MAINNET_URL,
1563            Profile::TESTNET => TESTNET_URL,
1564            Profile::STAGENET => STAGENET_URL,
1565        };
1566        Url::from_str(url).expect("Invalid url")
1567    }
1568
1569    pub fn chain_id(&self) -> u8 {
1570        match *self {
1571            Profile::MAINNET => ChainId::MAINNET.byte(),
1572            Profile::TESTNET => ChainId::TESTNET.byte(),
1573            Profile::STAGENET => ChainId::STAGENET.byte(),
1574        }
1575    }
1576}
1577
1578#[cfg(test)]
1579mod tests {
1580    use std::str::FromStr;
1581    use url::Url;
1582    use ChainId::MAINNET;
1583
1584    use crate::api::node::{Node, Profile};
1585    use crate::error::Result;
1586
1587    use crate::model::ChainId;
1588
1589    #[test]
1590    fn test_create_node_from_profile() -> Result<()> {
1591        let _ = Node::from_profile(Profile::MAINNET);
1592        Ok(())
1593    }
1594
1595    #[test]
1596    fn test_create_node_from_url() -> Result<()> {
1597        let url = Url::from_str("https://nodes.wavesnodes.com").expect("failed to parse url");
1598        let _ = Node::from_url(url, MAINNET.byte());
1599        Ok(())
1600    }
1601}