1#[cfg(target_arch = "wasm32")]
2use crate::types::digest::Digest;
3use crate::{
4 types::{
5 identifier::{
6 global_state_identifier::GlobalStateIdentifier, purse_identifier::PurseIdentifier,
7 },
8 sdk_error::SdkError,
9 verbosity::Verbosity,
10 },
11 SDK,
12};
13use casper_client::{
14 cli::parse::purse_identifier as parse_purse_identifier,
15 cli::query_balance_details as query_balance_details_cli,
16 query_balance_details as query_balance_details_lib,
17 rpcs::results::QueryBalanceDetailsResult as _QueryBalanceDetailsResult, JsonRpcId,
18 SuccessResponse,
19};
20#[cfg(target_arch = "wasm32")]
21use gloo_utils::format::JsValueSerdeExt;
22use rand::Rng;
23#[cfg(target_arch = "wasm32")]
24use serde::{Deserialize, Serialize};
25#[cfg(target_arch = "wasm32")]
26use wasm_bindgen::prelude::*;
27
28#[cfg(target_arch = "wasm32")]
30#[derive(Debug, Deserialize, Clone, Serialize)]
31#[wasm_bindgen]
32pub struct QueryBalanceDetailsResult(_QueryBalanceDetailsResult);
33
34#[cfg(target_arch = "wasm32")]
35impl From<QueryBalanceDetailsResult> for _QueryBalanceDetailsResult {
36 fn from(result: QueryBalanceDetailsResult) -> Self {
37 result.0
38 }
39}
40
41#[cfg(target_arch = "wasm32")]
42impl From<_QueryBalanceDetailsResult> for QueryBalanceDetailsResult {
43 fn from(result: _QueryBalanceDetailsResult) -> Self {
44 QueryBalanceDetailsResult(result)
45 }
46}
47
48#[cfg(target_arch = "wasm32")]
49#[wasm_bindgen]
50impl QueryBalanceDetailsResult {
51 #[wasm_bindgen(getter)]
53 pub fn api_version(&self) -> JsValue {
54 JsValue::from_serde(&self.0.api_version).unwrap()
55 }
56
57 #[wasm_bindgen(getter)]
58 pub fn total_balance(&self) -> JsValue {
59 JsValue::from_serde(&self.0.total_balance).unwrap()
60 }
61
62 #[wasm_bindgen(getter)]
63 pub fn available_balance(&self) -> JsValue {
64 JsValue::from_serde(&self.0.available_balance).unwrap()
65 }
66
67 #[wasm_bindgen(getter)]
68 pub fn total_balance_proof(&self) -> JsValue {
69 JsValue::from_serde(&self.0.total_balance_proof).unwrap()
70 }
71
72 #[wasm_bindgen(getter)]
73 pub fn holds(&self) -> JsValue {
74 JsValue::from_serde(&self.0.holds).unwrap()
75 }
76
77 #[wasm_bindgen(js_name = "toJson")]
79 pub fn to_json(&self) -> JsValue {
80 JsValue::from_serde(&self.0).unwrap_or(JsValue::null())
81 }
82}
83
84#[derive(Debug, Deserialize, Clone, Default, Serialize)]
86#[cfg(target_arch = "wasm32")]
87#[wasm_bindgen(js_name = "queryBalanceDetailsOptions", getter_with_clone)]
88pub struct QueryBalanceDetailsOptions {
89 pub purse_identifier_as_string: Option<String>,
90 pub purse_identifier: Option<PurseIdentifier>,
91 pub global_state_identifier: Option<GlobalStateIdentifier>,
92 pub state_root_hash_as_string: Option<String>,
93 pub state_root_hash: Option<Digest>,
94 pub maybe_block_id_as_string: Option<String>,
95 pub rpc_address: Option<String>,
96 pub verbosity: Option<Verbosity>,
97}
98
99#[cfg(target_arch = "wasm32")]
100#[wasm_bindgen]
101impl SDK {
102 pub fn query_balance_details_options(
112 &self,
113 options: JsValue,
114 ) -> Result<QueryBalanceDetailsOptions, JsError> {
115 options
116 .into_serde::<QueryBalanceDetailsOptions>()
117 .map_err(|err| JsError::new(&format!("Error deserializing options: {:?}", err)))
118 }
119
120 #[wasm_bindgen(js_name = "query_balance_details")]
134 pub async fn query_balance_details_js_alias(
135 &self,
136 options: Option<QueryBalanceDetailsOptions>,
137 ) -> Result<QueryBalanceDetailsResult, JsError> {
138 let QueryBalanceDetailsOptions {
139 global_state_identifier,
140 purse_identifier_as_string,
141 purse_identifier,
142 state_root_hash_as_string,
143 state_root_hash,
144 maybe_block_id_as_string,
145 verbosity,
146 rpc_address,
147 } = options.unwrap_or_default();
148
149 let result = if let Some(hash) = state_root_hash {
150 self.query_balance_details(
151 global_state_identifier,
152 purse_identifier_as_string,
153 purse_identifier,
154 Some(hash.to_string()),
155 None,
156 verbosity,
157 rpc_address,
158 )
159 .await
160 } else if let Some(hash) = state_root_hash_as_string {
161 self.query_balance_details(
162 global_state_identifier,
163 purse_identifier_as_string,
164 purse_identifier,
165 Some(hash.to_string()),
166 None,
167 verbosity,
168 rpc_address,
169 )
170 .await
171 } else if let Some(maybe_block_id_as_string) = maybe_block_id_as_string {
172 self.query_balance_details(
173 global_state_identifier,
174 purse_identifier_as_string,
175 purse_identifier,
176 None,
177 Some(maybe_block_id_as_string),
178 verbosity,
179 rpc_address,
180 )
181 .await
182 } else {
183 self.query_balance_details(
184 global_state_identifier,
185 purse_identifier_as_string,
186 purse_identifier,
187 None,
188 None,
189 verbosity,
190 rpc_address,
191 )
192 .await
193 };
194 match result {
195 Ok(data) => Ok(data.result.into()),
196 Err(err) => {
197 let err = &format!("Error occurred with {:?}", err);
198 Err(JsError::new(err))
199 }
200 }
201 }
202}
203
204impl SDK {
205 #[allow(clippy::too_many_arguments)]
225 pub async fn query_balance_details(
226 &self,
227 maybe_global_state_identifier: Option<GlobalStateIdentifier>,
228 purse_identifier_as_string: Option<String>,
229 purse_identifier: Option<PurseIdentifier>,
230 state_root_hash: Option<String>,
231 maybe_block_id: Option<String>,
232 verbosity: Option<Verbosity>,
233 rpc_address: Option<String>,
234 ) -> Result<SuccessResponse<_QueryBalanceDetailsResult>, SdkError> {
235 let purse_identifier: PurseIdentifier = if let Some(purse_identifier) = purse_identifier {
238 purse_identifier
239 } else if let Some(purse_id) = purse_identifier_as_string.clone() {
240 match parse_purse_identifier(&purse_id) {
241 Ok(parsed) => parsed.into(),
242 Err(err) => {
243 return Err(err.into());
244 }
245 }
246 } else {
247 let err = "Error: Missing purse identifier".to_string();
248 return Err(SdkError::InvalidArgument {
249 context: "query_global_state",
250 error: err,
251 });
252 };
253
254 if let Some(maybe_global_state_identifier) = maybe_global_state_identifier {
255 query_balance_details_lib(
256 JsonRpcId::from(rand::thread_rng().gen::<u64>().to_string()),
257 &self.get_rpc_address(rpc_address),
258 self.get_verbosity(verbosity).into(),
259 Some(maybe_global_state_identifier.into()),
260 purse_identifier.into(),
261 )
262 .await
263 .map_err(SdkError::from)
264 } else if maybe_global_state_identifier.is_none() {
265 query_balance_details_lib(
266 JsonRpcId::from(rand::thread_rng().gen::<u64>().to_string()),
267 &self.get_rpc_address(rpc_address),
268 self.get_verbosity(verbosity).into(),
269 None,
270 purse_identifier.into(),
271 )
272 .await
273 .map_err(SdkError::from)
274 } else if let Some(state_root_hash) = state_root_hash {
275 query_balance_details_cli(
276 &rand::thread_rng().gen::<u64>().to_string(),
277 &self.get_rpc_address(rpc_address),
278 self.get_verbosity(verbosity).into(),
279 "",
280 &state_root_hash,
281 &purse_identifier.to_string(),
282 )
283 .await
284 .map_err(SdkError::from)
285 } else {
286 query_balance_details_cli(
287 &rand::thread_rng().gen::<u64>().to_string(),
288 &self.get_rpc_address(rpc_address),
289 self.get_verbosity(verbosity).into(),
290 &maybe_block_id.unwrap_or_default(),
291 "",
292 &purse_identifier.to_string(),
293 )
294 .await
295 .map_err(SdkError::from)
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use crate::{
304 helpers::public_key_from_secret_key,
305 types::{digest::Digest, public_key::PublicKey},
306 };
307 use sdk_tests::tests::helpers::{get_network_constants, get_user_secret_key};
308
309 fn get_purse_identifier() -> PurseIdentifier {
310 let secret_key = get_user_secret_key(None).unwrap();
311 let account = public_key_from_secret_key(&secret_key).unwrap();
312 let public_key = PublicKey::new(&account).unwrap();
313 PurseIdentifier::from_main_purse_under_public_key(public_key)
314 }
315
316 #[tokio::test]
317 async fn test_query_balance_details_with_none_values() {
318 let sdk = SDK::new(None, None, None);
320 let error_message = "failed to parse node address as valid URL";
321
322 let result = sdk
324 .query_balance_details(
325 None,
326 None,
327 Some(get_purse_identifier()),
328 None,
329 None,
330 None,
331 None,
332 )
333 .await;
334
335 assert!(result.is_err());
337 let err_string = result.err().unwrap().to_string();
338 assert!(err_string.contains(error_message));
339 }
340
341 #[tokio::test]
342 async fn test_query_balance_details_with_missing_purse() {
343 let sdk = SDK::new(None, None, None);
345 let error_message = "Error: Missing purse identifier";
346
347 let result = sdk
349 .query_balance_details(None, None, None, None, None, None, None)
350 .await;
351
352 assert!(result.is_err());
354 let err_string = result.err().unwrap().to_string();
355
356 assert!(err_string.contains(error_message));
357 }
358
359 #[tokio::test]
360 async fn test_query_balance_details_with_global_state_identifier() {
361 let sdk = SDK::new(None, None, None);
363 let global_state_identifier = GlobalStateIdentifier::from_block_height(1);
364 let verbosity = Some(Verbosity::High);
365 let (rpc_address, _, _, _, _) = get_network_constants();
366 let result = sdk
368 .query_balance_details(
369 Some(global_state_identifier.clone()),
370 None,
371 Some(get_purse_identifier()),
372 None,
373 None,
374 verbosity,
375 Some(rpc_address),
376 )
377 .await;
378
379 assert!(result.is_ok());
381 }
382
383 #[tokio::test]
384 async fn test_query_balance_details_with_state_root_hash() {
385 let sdk = SDK::new(None, None, None);
387 let verbosity = Some(Verbosity::High);
388 let (rpc_address, _, _, _, _) = get_network_constants();
389 let state_root_hash: Digest = sdk
390 .get_state_root_hash(None, verbosity, Some(rpc_address.clone()))
391 .await
392 .unwrap()
393 .result
394 .state_root_hash
395 .unwrap()
396 .into();
397
398 let result = sdk
400 .query_balance_details(
401 None,
402 None,
403 Some(get_purse_identifier()),
404 Some(state_root_hash.to_string()),
405 None,
406 verbosity,
407 Some(rpc_address),
408 )
409 .await;
410
411 assert!(result.is_ok());
413 }
414
415 #[tokio::test]
416 async fn test_query_balance_details_with_block_id() {
417 let sdk = SDK::new(None, None, None);
419 let verbosity = Some(Verbosity::High);
420 let (rpc_address, _, _, _, _) = get_network_constants();
421
422 let result = sdk
424 .query_balance_details(
425 None,
426 None,
427 Some(get_purse_identifier()),
428 None,
429 Some("1".to_string()),
430 verbosity,
431 Some(rpc_address.clone()),
432 )
433 .await;
434 assert!(result.is_ok());
436 }
437
438 #[tokio::test]
439 async fn test_query_balance_details_with_purse_identifier() {
440 let sdk = SDK::new(None, None, None);
442 let verbosity = Some(Verbosity::High);
443 let (rpc_address, _, _, _, _) = get_network_constants();
444
445 let result = sdk
447 .query_balance_details(
448 None,
449 None,
450 Some(get_purse_identifier()),
451 None,
452 None,
453 verbosity,
454 Some(rpc_address.clone()),
455 )
456 .await;
457
458 assert!(result.is_ok());
460 }
461
462 #[tokio::test]
463 async fn test_query_balance_details_with_purse_identifier_as_string() {
464 let sdk = SDK::new(None, None, None);
466 let verbosity = Some(Verbosity::High);
467 let (rpc_address, _, _, _, _) = get_network_constants();
468
469 let result = sdk
471 .query_balance_details(
472 None,
473 Some(get_purse_identifier().to_string()),
474 None,
475 None,
476 None,
477 verbosity,
478 Some(rpc_address),
479 )
480 .await;
481
482 assert!(result.is_ok());
484 }
485
486 #[tokio::test]
487 async fn test_query_balance_details_with_error() {
488 let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
490
491 let error_message = "error sending request for url (http://localhost/rpc)";
492
493 let result = sdk
495 .query_balance_details(
496 None,
497 Some(get_purse_identifier().to_string()),
498 None,
499 None,
500 None,
501 None,
502 None,
503 )
504 .await;
505
506 assert!(result.is_err());
508 let err_string = result.err().unwrap().to_string();
509 assert!(err_string.contains(error_message));
510 }
511}