1#[cfg(all(feature = "js", 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(all(feature = "js", target_arch = "wasm32"))]
21use gloo_utils::format::JsValueSerdeExt;
22use rand::RngExt;
23#[cfg(all(feature = "js", target_arch = "wasm32"))]
24use serde::{Deserialize, Serialize};
25#[cfg(all(feature = "js", target_arch = "wasm32"))]
26use wasm_bindgen::prelude::*;
27
28#[cfg(all(feature = "js", target_arch = "wasm32"))]
30#[derive(Debug, Deserialize, Clone, Serialize)]
31#[cfg_attr(feature = "js", wasm_bindgen)]
32pub struct QueryBalanceDetailsResult(_QueryBalanceDetailsResult);
33
34#[cfg(all(feature = "js", target_arch = "wasm32"))]
35impl From<QueryBalanceDetailsResult> for _QueryBalanceDetailsResult {
36 fn from(result: QueryBalanceDetailsResult) -> Self {
37 result.0
38 }
39}
40
41#[cfg(all(feature = "js", target_arch = "wasm32"))]
42impl From<_QueryBalanceDetailsResult> for QueryBalanceDetailsResult {
43 fn from(result: _QueryBalanceDetailsResult) -> Self {
44 QueryBalanceDetailsResult(result)
45 }
46}
47
48#[cfg(all(feature = "js", target_arch = "wasm32"))]
49#[cfg_attr(feature = "js", wasm_bindgen)]
50impl QueryBalanceDetailsResult {
51 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
53 #[cfg(feature = "js")]
54 pub fn api_version(&self) -> JsValue {
55 JsValue::from_serde(&self.0.api_version).unwrap()
56 }
57
58 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
59 #[cfg(feature = "js")]
60 pub fn total_balance(&self) -> JsValue {
61 JsValue::from_serde(&self.0.total_balance).unwrap()
62 }
63
64 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
65 #[cfg(feature = "js")]
66 pub fn available_balance(&self) -> JsValue {
67 JsValue::from_serde(&self.0.available_balance).unwrap()
68 }
69
70 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
71 #[cfg(feature = "js")]
72 pub fn total_balance_proof(&self) -> JsValue {
73 JsValue::from_serde(&self.0.total_balance_proof).unwrap()
74 }
75
76 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
77 #[cfg(feature = "js")]
78 pub fn holds(&self) -> JsValue {
79 JsValue::from_serde(&self.0.holds).unwrap()
80 }
81
82 #[cfg_attr(feature = "js", wasm_bindgen(js_name = "toJson"))]
84 #[cfg(feature = "js")]
85 pub fn to_json(&self) -> JsValue {
86 JsValue::from_serde(&self.0).unwrap_or(JsValue::null())
87 }
88}
89
90#[derive(Debug, Deserialize, Clone, Default, Serialize)]
92#[cfg(all(feature = "js", target_arch = "wasm32"))]
93#[cfg_attr(
94 feature = "js",
95 wasm_bindgen(js_name = "queryBalanceDetailsOptions", getter_with_clone)
96)]
97pub struct QueryBalanceDetailsOptions {
98 pub purse_identifier_as_string: Option<String>,
99 pub purse_identifier: Option<PurseIdentifier>,
100 pub global_state_identifier: Option<GlobalStateIdentifier>,
101 pub state_root_hash_as_string: Option<String>,
102 pub state_root_hash: Option<Digest>,
103 pub maybe_block_id_as_string: Option<String>,
104 pub rpc_address: Option<String>,
105 pub verbosity: Option<Verbosity>,
106}
107
108#[cfg(all(feature = "js", target_arch = "wasm32"))]
109#[cfg_attr(feature = "js", wasm_bindgen)]
110impl SDK {
111 #[cfg(feature = "js")]
121 pub fn query_balance_details_options(
122 &self,
123 options: JsValue,
124 ) -> Result<QueryBalanceDetailsOptions, JsError> {
125 options
126 .into_serde::<QueryBalanceDetailsOptions>()
127 .map_err(|err| JsError::new(&format!("Error deserializing options: {err:?}")))
128 }
129
130 #[cfg_attr(feature = "js", wasm_bindgen(js_name = "query_balance_details"))]
144 #[cfg(feature = "js")]
145 pub async fn query_balance_details_js_alias(
146 &self,
147 options: Option<QueryBalanceDetailsOptions>,
148 ) -> Result<QueryBalanceDetailsResult, JsError> {
149 let QueryBalanceDetailsOptions {
150 global_state_identifier,
151 purse_identifier_as_string,
152 purse_identifier,
153 state_root_hash_as_string,
154 state_root_hash,
155 maybe_block_id_as_string,
156 verbosity,
157 rpc_address,
158 } = options.unwrap_or_default();
159
160 let result = if let Some(hash) = state_root_hash {
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(hash) = state_root_hash_as_string {
172 self.query_balance_details(
173 global_state_identifier,
174 purse_identifier_as_string,
175 purse_identifier,
176 Some(hash.to_string()),
177 None,
178 verbosity,
179 rpc_address,
180 )
181 .await
182 } else if let Some(maybe_block_id_as_string) = maybe_block_id_as_string {
183 self.query_balance_details(
184 global_state_identifier,
185 purse_identifier_as_string,
186 purse_identifier,
187 None,
188 Some(maybe_block_id_as_string),
189 verbosity,
190 rpc_address,
191 )
192 .await
193 } else {
194 self.query_balance_details(
195 global_state_identifier,
196 purse_identifier_as_string,
197 purse_identifier,
198 None,
199 None,
200 verbosity,
201 rpc_address,
202 )
203 .await
204 };
205 match result {
206 Ok(data) => Ok(data.result.into()),
207 Err(err) => {
208 let err = &format!("Error occurred with {err:?}");
209 Err(JsError::new(err))
210 }
211 }
212 }
213}
214
215impl SDK {
216 #[allow(clippy::too_many_arguments)]
236 pub async fn query_balance_details(
237 &self,
238 maybe_global_state_identifier: Option<GlobalStateIdentifier>,
239 purse_identifier_as_string: Option<String>,
240 purse_identifier: Option<PurseIdentifier>,
241 state_root_hash: Option<String>,
242 maybe_block_id: Option<String>,
243 verbosity: Option<Verbosity>,
244 rpc_address: Option<String>,
245 ) -> Result<SuccessResponse<_QueryBalanceDetailsResult>, SdkError> {
246 let purse_identifier: PurseIdentifier = if let Some(purse_identifier) = purse_identifier {
249 purse_identifier
250 } else if let Some(purse_id) = purse_identifier_as_string.clone() {
251 match parse_purse_identifier(&purse_id) {
252 Ok(parsed) => parsed.into(),
253 Err(err) => {
254 return Err(err.into());
255 }
256 }
257 } else {
258 let err = "Error: Missing purse identifier".to_string();
259 return Err(SdkError::InvalidArgument {
260 context: "query_global_state",
261 error: err,
262 });
263 };
264 let random_id = rand::rng().random::<u64>().to_string();
265 if let Some(maybe_global_state_identifier) = maybe_global_state_identifier {
266 query_balance_details_lib(
267 JsonRpcId::from(random_id),
268 &self.get_rpc_address(rpc_address),
269 self.get_verbosity(verbosity).into(),
270 Some(maybe_global_state_identifier.into()),
271 purse_identifier.into(),
272 )
273 .await
274 .map_err(SdkError::from)
275 } else if maybe_global_state_identifier.is_none() {
276 query_balance_details_lib(
277 JsonRpcId::from(random_id),
278 &self.get_rpc_address(rpc_address),
279 self.get_verbosity(verbosity).into(),
280 None,
281 purse_identifier.into(),
282 )
283 .await
284 .map_err(SdkError::from)
285 } else if let Some(state_root_hash) = state_root_hash {
286 query_balance_details_cli(
287 &random_id,
288 &self.get_rpc_address(rpc_address),
289 self.get_verbosity(verbosity).into(),
290 "",
291 &state_root_hash,
292 &purse_identifier.to_string(),
293 )
294 .await
295 .map_err(SdkError::from)
296 } else {
297 query_balance_details_cli(
298 &random_id,
299 &self.get_rpc_address(rpc_address),
300 self.get_verbosity(verbosity).into(),
301 &maybe_block_id.unwrap_or_default(),
302 "",
303 &purse_identifier.to_string(),
304 )
305 .await
306 .map_err(SdkError::from)
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::{
315 helpers::public_key_from_secret_key,
316 types::{digest::Digest, public_key::PublicKey},
317 };
318 use sdk_tests::tests::helpers::{get_network_constants, get_user_secret_key};
319
320 fn get_purse_identifier() -> PurseIdentifier {
321 let secret_key = get_user_secret_key(None).unwrap();
322 let account = public_key_from_secret_key(&secret_key).unwrap();
323 let public_key = PublicKey::new(&account).unwrap();
324 PurseIdentifier::from_main_purse_under_public_key(public_key)
325 }
326
327 #[tokio::test]
328 async fn test_query_balance_details_with_none_values() {
329 let sdk = SDK::new(None, None, None);
331 let error_message = "failed to parse node address as valid URL";
332
333 let result = sdk
335 .query_balance_details(
336 None,
337 None,
338 Some(get_purse_identifier()),
339 None,
340 None,
341 None,
342 None,
343 )
344 .await;
345
346 assert!(result.is_err());
348 let err_string = result.err().unwrap().to_string();
349 assert!(err_string.contains(error_message));
350 }
351
352 #[tokio::test]
353 async fn test_query_balance_details_with_missing_purse() {
354 let sdk = SDK::new(None, None, None);
356 let error_message = "Error: Missing purse identifier";
357
358 let result = sdk
360 .query_balance_details(None, None, None, None, None, None, None)
361 .await;
362
363 assert!(result.is_err());
365 let err_string = result.err().unwrap().to_string();
366
367 assert!(err_string.contains(error_message));
368 }
369
370 #[tokio::test]
371 async fn test_query_balance_details_with_global_state_identifier() {
372 let sdk = SDK::new(None, None, None);
374 let global_state_identifier = GlobalStateIdentifier::from_block_height(1);
375 let verbosity = Some(Verbosity::High);
376 let (rpc_address, _, _, _, _) = get_network_constants();
377 let result = sdk
379 .query_balance_details(
380 Some(global_state_identifier.clone()),
381 None,
382 Some(get_purse_identifier()),
383 None,
384 None,
385 verbosity,
386 Some(rpc_address),
387 )
388 .await;
389
390 assert!(result.is_ok());
392 }
393
394 #[tokio::test]
395 async fn test_query_balance_details_with_state_root_hash() {
396 let sdk = SDK::new(None, None, None);
398 let verbosity = Some(Verbosity::High);
399 let (rpc_address, _, _, _, _) = get_network_constants();
400 let state_root_hash: Digest = sdk
401 .get_state_root_hash(None, verbosity, Some(rpc_address.clone()))
402 .await
403 .unwrap()
404 .result
405 .state_root_hash
406 .unwrap()
407 .into();
408
409 let result = sdk
411 .query_balance_details(
412 None,
413 None,
414 Some(get_purse_identifier()),
415 Some(state_root_hash.to_string()),
416 None,
417 verbosity,
418 Some(rpc_address),
419 )
420 .await;
421
422 assert!(result.is_ok());
424 }
425
426 #[tokio::test]
427 async fn test_query_balance_details_with_block_id() {
428 let sdk = SDK::new(None, None, None);
430 let verbosity = Some(Verbosity::High);
431 let (rpc_address, _, _, _, _) = get_network_constants();
432
433 let result = sdk
435 .query_balance_details(
436 None,
437 None,
438 Some(get_purse_identifier()),
439 None,
440 Some("1".to_string()),
441 verbosity,
442 Some(rpc_address.clone()),
443 )
444 .await;
445 assert!(result.is_ok());
447 }
448
449 #[tokio::test]
450 async fn test_query_balance_details_with_purse_identifier() {
451 let sdk = SDK::new(None, None, None);
453 let verbosity = Some(Verbosity::High);
454 let (rpc_address, _, _, _, _) = get_network_constants();
455
456 let result = sdk
458 .query_balance_details(
459 None,
460 None,
461 Some(get_purse_identifier()),
462 None,
463 None,
464 verbosity,
465 Some(rpc_address.clone()),
466 )
467 .await;
468
469 assert!(result.is_ok());
471 }
472
473 #[tokio::test]
474 async fn test_query_balance_details_with_purse_identifier_as_string() {
475 let sdk = SDK::new(None, None, None);
477 let verbosity = Some(Verbosity::High);
478 let (rpc_address, _, _, _, _) = get_network_constants();
479
480 let result = sdk
482 .query_balance_details(
483 None,
484 Some(get_purse_identifier().to_string()),
485 None,
486 None,
487 None,
488 verbosity,
489 Some(rpc_address),
490 )
491 .await;
492
493 assert!(result.is_ok());
495 }
496
497 #[tokio::test]
498 async fn test_query_balance_details_with_error() {
499 let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
501
502 let error_message = "error sending request";
503
504 let result = sdk
506 .query_balance_details(
507 None,
508 Some(get_purse_identifier().to_string()),
509 None,
510 None,
511 None,
512 None,
513 None,
514 )
515 .await;
516
517 assert!(result.is_err());
519 let err_string = result.err().unwrap().to_string();
520 assert!(err_string.contains(error_message));
521 }
522}