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, ®ex).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}