1use super::Sdk;
7use crate::{
8 backend::dlt::put_user_address,
9 error::Result,
10 types::newtypes::{EncryptionPin, EncryptionSalt, PlainPassword},
11 wallet::error::{ErrorKind, WalletError},
12};
13use etopay_wallet::{
14 MnemonicDerivationOption, sort_by_date,
15 types::{CryptoAmount, WalletTxInfo, WalletTxInfoList, WalletTxStatus},
16};
17
18use log::{debug, info, warn};
19
20impl Sdk {
21 pub async fn create_wallet_from_new_mnemonic(&mut self, pin: &EncryptionPin) -> Result<String> {
37 info!("Creating a new wallet from random mnemonic");
38
39 let Some(repo) = &mut self.repo else {
40 return Err(crate::Error::UserRepoNotInitialized);
41 };
42
43 let Some(active_user) = &mut self.active_user else {
44 return Err(crate::Error::UserNotInitialized);
45 };
46
47 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
48
49 let mnemonic = active_user
50 .wallet_manager
51 .create_wallet_from_new_mnemonic(config, &self.access_token, repo, pin)
52 .await?;
53 Ok(mnemonic)
54 }
55
56 pub async fn create_wallet_from_existing_mnemonic(&mut self, pin: &EncryptionPin, mnemonic: &str) -> Result<()> {
69 info!("Creating a new wallet from existing mnemonic");
70
71 let Some(repo) = &mut self.repo else {
72 return Err(crate::Error::UserRepoNotInitialized);
73 };
74
75 let Some(active_user) = &mut self.active_user else {
76 return Err(crate::Error::UserNotInitialized);
77 };
78
79 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
80
81 active_user
82 .wallet_manager
83 .create_wallet_from_existing_mnemonic(config, &self.access_token, repo, pin, mnemonic)
84 .await?;
85 Ok(())
86 }
87
88 pub async fn create_wallet_from_backup(
102 &mut self,
103 pin: &EncryptionPin,
104 backup: &[u8],
105 backup_password: &PlainPassword,
106 ) -> Result<()> {
107 info!("Creating a new wallet from backup");
108
109 let Some(repo) = &mut self.repo else {
110 return Err(crate::Error::UserRepoNotInitialized);
111 };
112
113 let Some(active_user) = &mut self.active_user else {
114 return Err(crate::Error::UserNotInitialized);
115 };
116
117 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
118
119 active_user
120 .wallet_manager
121 .create_wallet_from_backup(config, &self.access_token, repo, pin, backup, backup_password)
122 .await?;
123 Ok(())
124 }
125
126 pub async fn create_wallet_backup(
143 &mut self,
144 pin: &EncryptionPin,
145 backup_password: &PlainPassword,
146 ) -> Result<Vec<u8>> {
147 info!("Creating wallet backup");
148
149 let Some(repo) = &mut self.repo else {
150 return Err(crate::Error::UserRepoNotInitialized);
151 };
152
153 let Some(active_user) = &mut self.active_user else {
154 return Err(crate::Error::UserNotInitialized);
155 };
156
157 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
158
159 let backup = active_user
160 .wallet_manager
161 .create_wallet_backup(config, &self.access_token, repo, pin, backup_password)
162 .await?;
163 Ok(backup)
164 }
165
166 pub async fn verify_mnemonic(&mut self, pin: &EncryptionPin, mnemonic: &str) -> Result<bool> {
184 info!("Verifying mnemonic");
185
186 let Some(repo) = &mut self.repo else {
187 return Err(crate::Error::UserRepoNotInitialized);
188 };
189
190 let Some(active_user) = &mut self.active_user else {
191 return Err(crate::Error::UserNotInitialized);
192 };
193
194 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
195
196 let is_verified = active_user
197 .wallet_manager
198 .check_mnemonic(config, &self.access_token, repo, pin, mnemonic)
199 .await?;
200 Ok(is_verified)
201 }
202
203 pub async fn delete_wallet(&mut self, pin: &EncryptionPin) -> Result<()> {
217 warn!("Deleting wallet for user. Potential loss of funds if mnemonic/wallet is not backed up!");
218
219 self.verify_pin(pin).await?;
220
221 let Some(repo) = &mut self.repo else {
222 return Err(crate::Error::UserRepoNotInitialized);
223 };
224 let Some(active_user) = &mut self.active_user else {
225 return Err(crate::Error::UserNotInitialized);
226 };
227
228 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
229
230 active_user
231 .wallet_manager
232 .delete_wallet(config, &self.access_token, repo)
233 .await?;
234
235 Ok(())
236 }
237
238 pub async fn verify_pin(&self, pin: &EncryptionPin) -> Result<()> {
257 info!("Verifying wallet pin");
258 let Some(repo) = &self.repo else {
259 return Err(crate::Error::UserRepoNotInitialized);
260 };
261 let Some(active_user) = &self.active_user else {
262 return Err(crate::Error::UserNotInitialized);
263 };
264
265 let username = &active_user.username;
266 let user = repo.get(username)?;
267
268 let Some(encrypted_password) = user.encrypted_password else {
270 return Err(WalletError::WalletNotInitialized(ErrorKind::MissingPassword))?;
271 };
272
273 if encrypted_password.decrypt(pin, &user.salt).is_err() {
275 return Err(WalletError::WrongPinOrPassword)?;
276 }
277 Ok(())
278 }
279
280 pub async fn change_pin(&mut self, old_pin: &EncryptionPin, new_pin: &EncryptionPin) -> Result<()> {
300 info!("Resetting pin with password");
301 let Some(repo) = &mut self.repo else {
302 return Err(crate::Error::UserRepoNotInitialized);
303 };
304 let Some(active_user) = &mut self.active_user else {
305 return Err(crate::Error::UserNotInitialized);
306 };
307
308 let username = &active_user.username;
309 let mut user = repo.get(username)?;
310
311 let Some(encrypted_password) = user.encrypted_password else {
312 return Err(WalletError::WalletNotInitialized(ErrorKind::MissingPassword))?;
313 };
314
315 let password = encrypted_password.decrypt(old_pin, &user.salt)?;
317
318 let salt = EncryptionSalt::generate();
320 let encrypted_password = password.encrypt(new_pin, &salt)?;
321
322 user.salt = salt;
324 user.encrypted_password = Some(encrypted_password);
325 repo.update(&user)?;
326
327 Ok(())
328 }
329
330 pub async fn set_wallet_password(&mut self, pin: &EncryptionPin, new_password: &PlainPassword) -> Result<()> {
346 info!("Setting password");
347
348 let Some(repo) = &mut self.repo else {
349 return Err(crate::Error::UserRepoNotInitialized);
350 };
351 let Some(active_user) = &mut self.active_user else {
352 return Err(crate::Error::UserNotInitialized);
353 };
354
355 let mut user = repo.get(&active_user.username)?;
356
357 if let Some(encrypted_password) = user.encrypted_password {
359 info!("Password exists, changing password");
360
361 if encrypted_password.decrypt(pin, &user.salt).is_err() {
363 return Err(WalletError::WrongPinOrPassword)?;
364 }
365
366 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
367
368 active_user
369 .wallet_manager
370 .change_wallet_password(config, &self.access_token, repo, pin, new_password)
371 .await?;
372 } else {
373 let salt = EncryptionSalt::generate();
375 let encrypted_password = new_password.encrypt(pin, &salt)?;
376
377 user.salt = salt;
379 user.encrypted_password = Some(encrypted_password);
380 repo.update(&user)?;
381 }
382
383 Ok(())
384 }
385
386 pub async fn is_wallet_password_set(&self) -> Result<bool> {
399 info!("Checking if password is set");
400
401 let Some(repo) = &self.repo else {
402 return Err(crate::Error::UserRepoNotInitialized);
403 };
404 let Some(active_user) = &self.active_user else {
405 return Err(crate::Error::UserNotInitialized);
406 };
407
408 let user = repo.get(&active_user.username)?;
409
410 Ok(user.encrypted_password.is_some())
411 }
412
413 pub async fn generate_new_address(&mut self, pin: &EncryptionPin) -> Result<String> {
425 info!("Generating new wallet address");
426 self.verify_pin(pin).await?;
427 let Some(repo) = &mut self.repo else {
428 return Err(crate::Error::UserRepoNotInitialized);
429 };
430 let Some(active_user) = &mut self.active_user else {
431 return Err(crate::Error::UserNotInitialized);
432 };
433 let network = self.active_network.as_ref().ok_or(crate::Error::MissingNetwork)?;
434 let config = self.config.as_mut().ok_or(crate::Error::MissingConfig)?;
435 let wallet = active_user
436 .wallet_manager
437 .try_get(
438 config,
439 &self.access_token,
440 repo,
441 network,
442 pin,
443 &active_user.mnemonic_derivation_options,
444 )
445 .await?;
446
447 let address = wallet.get_address().await?;
448
449 if let Some(access_token) = self.access_token.as_ref() {
451 if network.can_do_purchases {
452 put_user_address(config, access_token, &network.key, &address).await?;
453 }
454 }
455 debug!("Generated address: {address}");
456 Ok(address)
457 }
458
459 pub async fn get_balance(&mut self, pin: &EncryptionPin) -> Result<CryptoAmount> {
472 info!("Fetching balance");
473 self.verify_pin(pin).await?;
474 let wallet = self.try_get_active_user_wallet(pin).await?;
475 let balance = wallet.get_balance().await?;
476 debug!("Balance: {balance:?}");
477 Ok(balance)
478 }
479
480 pub async fn get_wallet_tx_list(
498 &mut self,
499 pin: &EncryptionPin,
500 start: usize,
501 limit: usize,
502 ) -> Result<WalletTxInfoList> {
503 info!("Wallet getting list of transactions");
504 self.verify_pin(pin).await?;
505
506 let Some(repo) = &mut self.repo else {
507 return Err(crate::Error::UserRepoNotInitialized);
508 };
509 let Some(active_user) = &mut self.active_user else {
510 return Err(crate::Error::UserNotInitialized);
511 };
512 let network = self.active_network.as_ref().ok_or(crate::Error::MissingNetwork)?;
513 let config = self.config.as_mut().ok_or(crate::Error::MissingConfig)?;
514 let wallet = active_user
515 .wallet_manager
516 .try_get(
517 config,
518 &self.access_token,
519 repo,
520 network,
521 pin,
522 &active_user.mnemonic_derivation_options,
523 )
524 .await?;
525
526 let user = repo.get(active_user.username.as_str())?;
527
528 let mut wallet_transactions = user.wallet_transactions;
532
533 match wallet.get_wallet_tx_list(start, limit).await {
536 Ok(transaction_hashes) => {
537 log::debug!("Digests: {:#?}", transaction_hashes);
540 for hash in transaction_hashes {
541 if wallet_transactions
544 .iter()
545 .any(|t| t.transaction_hash == hash && t.network_key == network.key)
546 {
547 continue;
548 }
549
550 log::debug!("Getting details for new transaction with hash {hash}");
551
552 match wallet.get_wallet_tx(&hash).await {
554 Err(e) => log::warn!("Could not get transaction details for {hash}: {e}"),
555 Ok(details) => {
556 wallet_transactions.push(details);
558 }
559 }
560 }
561 }
562 Err(etopay_wallet::WalletError::WalletFeatureNotImplemented) => {}
564 Err(e) => return Err(e.into()),
565 }
566
567 sort_by_date(&mut wallet_transactions);
569
570 for transaction in wallet_transactions
571 .iter_mut()
572 .filter(|tx| tx.network_key == network.key)
573 .skip(start)
574 .take(limit)
575 {
576 if transaction.status == WalletTxStatus::Confirmed {
579 continue;
580 }
581
582 match wallet.get_wallet_tx(&transaction.transaction_hash).await {
583 Ok(stx) => *transaction = stx,
584 Err(e) => {
585 log::debug!(
587 "[sync_transactions] could not retrieve data about transaction from the network, transaction: {:?}, error: {:?}",
588 transaction,
589 e
590 );
591 }
592 }
593 }
594
595 let tx_list_filtered = wallet_transactions
596 .iter()
597 .filter(|tx| tx.network_key == network.key)
598 .skip(start)
599 .take(limit)
600 .cloned()
601 .collect();
602
603 let _ = repo.set_wallet_transactions(&user.username, wallet_transactions);
605
606 Ok(WalletTxInfoList {
607 transactions: tx_list_filtered,
608 })
609 }
610
611 pub async fn get_wallet_tx(&mut self, pin: &EncryptionPin, tx_id: &str) -> Result<WalletTxInfo> {
628 info!("Wallet getting details of particular transactions");
629 self.verify_pin(pin).await?;
630 let wallet = self.try_get_active_user_wallet(pin).await?;
631 let wallet_tx = wallet.get_wallet_tx(tx_id).await?;
632 Ok(wallet_tx)
633 }
634
635 pub async fn set_wallet_derivation_options(&mut self, account: u32, index: u32) -> Result<()> {
646 let options = MnemonicDerivationOption { account, index };
647
648 info!("Setting wallet mnemonic derivation options: {options:?}");
649
650 let Some(active_user) = &mut self.active_user else {
651 return Err(crate::Error::UserNotInitialized);
652 };
653
654 active_user.mnemonic_derivation_options = options;
655
656 Ok(())
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663 use crate::core::core_testing_utils::handle_error_test_cases;
664 use crate::testing_utils::{
665 ADDRESS, AUTH_PROVIDER, ENCRYPTED_WALLET_PASSWORD, ETH_NETWORK_KEY, HEADER_X_APP_NAME, IOTA_NETWORK_KEY,
666 MNEMONIC, PIN, SALT, TOKEN, TX_INDEX, USERNAME, WALLET_PASSWORD, example_api_networks, example_get_user,
667 example_wallet_tx_info, set_config,
668 };
669 use crate::types::users::UserEntity;
670 use crate::{
671 core::Sdk,
672 types::users::KycType,
673 user::MockUserRepo,
674 wallet_manager::{MockWalletManager, WalletBorrow},
675 };
676 use api_types::api::dlt::SetUserAddressRequest;
677 use api_types::api::viviswap::detail::SwapPaymentDetailKey;
678 use etopay_wallet::MockWalletUser;
679 use mockall::predicate::eq;
680 use mockito::Matcher;
681 use rstest::rstest;
682 use rust_decimal_macros::dec;
683 use std::sync::LazyLock;
684
685 const BACKUP: &[u8] = &[42, 77, 15, 203, 89, 123, 34, 56, 178, 90, 210, 33, 47, 192, 1, 17];
686
687 #[rstest]
688 #[case::success(Ok(MNEMONIC.to_string()))]
689 #[case::missing_config(Err(crate::Error::MissingConfig))]
690 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
691 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
692 #[tokio::test]
693 async fn test_create_wallet_from_new_mnemonic(#[case] expected: Result<String>) {
694 let (_srv, config, _cleanup) = set_config().await;
696 let mut sdk = Sdk::new(config).unwrap();
697
698 match &expected {
699 Ok(_) => {
700 sdk.repo = Some(Box::new(MockUserRepo::new()));
701 let mut mock_wallet_manager = MockWalletManager::new();
702 mock_wallet_manager
703 .expect_create_wallet_from_new_mnemonic()
704 .once()
705 .returning(|_, _, _, _| Ok(MNEMONIC.to_string()));
706 sdk.active_user = Some(crate::types::users::ActiveUser {
707 username: USERNAME.into(),
708 wallet_manager: Box::new(mock_wallet_manager),
709 mnemonic_derivation_options: Default::default(),
710 });
711 }
712 Err(error) => {
713 handle_error_test_cases(error, &mut sdk, 0, 0).await;
714 }
715 }
716
717 let response = sdk.create_wallet_from_new_mnemonic(&PIN).await;
719
720 match expected {
722 Ok(resp) => {
723 assert_eq!(response.unwrap(), resp);
724 }
725 Err(ref expected_err) => {
726 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
727 }
728 }
729 }
730
731 #[rstest]
732 #[case::success(Ok(()))]
733 #[case::missing_config(Err(crate::Error::MissingConfig))]
734 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
735 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
736 #[tokio::test]
737 async fn test_create_wallet_from_existing_mnemonic(#[case] expected: Result<()>) {
738 let (_srv, config, _cleanup) = set_config().await;
740 let mut sdk = Sdk::new(config).unwrap();
741
742 match &expected {
743 Ok(_) => {
744 sdk.repo = Some(Box::new(MockUserRepo::new()));
745 let mut mock_wallet_manager = MockWalletManager::new();
746 mock_wallet_manager
747 .expect_create_wallet_from_existing_mnemonic()
748 .once()
749 .returning(|_, _, _, _, _| Ok(()));
750 sdk.active_user = Some(crate::types::users::ActiveUser {
751 username: USERNAME.into(),
752 wallet_manager: Box::new(mock_wallet_manager),
753 mnemonic_derivation_options: Default::default(),
754 });
755 }
756 Err(error) => {
757 handle_error_test_cases(error, &mut sdk, 0, 0).await;
758 }
759 }
760
761 let response = sdk.create_wallet_from_existing_mnemonic(&PIN, MNEMONIC).await;
763
764 match expected {
766 Ok(()) => response.unwrap(),
767 Err(ref expected_err) => {
768 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
769 }
770 }
771 }
772
773 #[rstest]
774 #[case::success(Ok(BACKUP.to_vec()))]
775 #[case::missing_config(Err(crate::Error::MissingConfig))]
776 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
777 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
778 #[tokio::test]
779 async fn test_create_wallet_backup(#[case] expected: Result<Vec<u8>>) {
780 let (_srv, config, _cleanup) = set_config().await;
782 let mut sdk = Sdk::new(config).unwrap();
783
784 match &expected {
785 Ok(_) => {
786 sdk.repo = Some(Box::new(MockUserRepo::new()));
787 let mut mock_wallet_manager = MockWalletManager::new();
788 mock_wallet_manager
789 .expect_create_wallet_backup()
790 .once()
791 .returning(|_, _, _, _, _| Ok(BACKUP.to_vec()));
792 sdk.active_user = Some(crate::types::users::ActiveUser {
793 username: USERNAME.into(),
794 wallet_manager: Box::new(mock_wallet_manager),
795 mnemonic_derivation_options: Default::default(),
796 });
797 }
798 Err(error) => {
799 handle_error_test_cases(error, &mut sdk, 0, 0).await;
800 }
801 }
802
803 let response = sdk.create_wallet_backup(&PIN, &WALLET_PASSWORD).await;
805
806 match expected {
808 Ok(resp) => {
809 assert_eq!(response.unwrap(), resp);
810 }
811 Err(ref expected_err) => {
812 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
813 }
814 }
815 }
816
817 #[rstest]
818 #[case::success(Ok(()))]
819 #[case::missing_config(Err(crate::Error::MissingConfig))]
820 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
821 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
822 #[tokio::test]
823 async fn test_create_wallet_from_backup(#[case] expected: Result<()>) {
824 let (_srv, config, _cleanup) = set_config().await;
826 let mut sdk = Sdk::new(config).unwrap();
827
828 match &expected {
829 Ok(_) => {
830 sdk.repo = Some(Box::new(MockUserRepo::new()));
831 let mut mock_wallet_manager = MockWalletManager::new();
832 mock_wallet_manager
833 .expect_create_wallet_from_backup()
834 .once()
835 .returning(|_, _, _, _, _, _| Ok(()));
836 sdk.active_user = Some(crate::types::users::ActiveUser {
837 username: USERNAME.into(),
838 wallet_manager: Box::new(mock_wallet_manager),
839 mnemonic_derivation_options: Default::default(),
840 });
841 }
842 Err(error) => {
843 handle_error_test_cases(error, &mut sdk, 0, 0).await;
844 }
845 }
846
847 let response = sdk.create_wallet_from_backup(&PIN, BACKUP, &WALLET_PASSWORD).await;
849
850 match expected {
852 Ok(()) => response.unwrap(),
853 Err(ref expected_err) => {
854 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
855 }
856 }
857 }
858
859 #[rstest]
860 #[case::success(Ok(true))]
861 #[case::missing_config(Err(crate::Error::MissingConfig))]
862 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
863 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
864 #[tokio::test]
865 async fn test_verify_mnemonic(#[case] expected: Result<bool>) {
866 let (_srv, config, _cleanup) = set_config().await;
868 let mut sdk = Sdk::new(config).unwrap();
869
870 match &expected {
871 Ok(_) => {
872 sdk.repo = Some(Box::new(MockUserRepo::new()));
873 let mut mock_wallet_manager = MockWalletManager::new();
874 mock_wallet_manager
875 .expect_check_mnemonic()
876 .once()
877 .returning(|_, _, _, _, _| Ok(true));
878 sdk.active_user = Some(crate::types::users::ActiveUser {
879 username: USERNAME.into(),
880 wallet_manager: Box::new(mock_wallet_manager),
881 mnemonic_derivation_options: Default::default(),
882 });
883 }
884 Err(error) => {
885 handle_error_test_cases(error, &mut sdk, 0, 0).await;
886 }
887 }
888
889 let response = sdk.verify_mnemonic(&PIN, MNEMONIC).await;
891
892 match expected {
894 Ok(resp) => {
895 assert_eq!(response.unwrap(), resp);
896 }
897 Err(ref expected_err) => {
898 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
899 }
900 }
901 }
902
903 #[rstest]
904 #[case::success(Ok(()))]
905 #[case::missing_config(Err(crate::Error::MissingConfig))]
906 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
907 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
908 #[tokio::test]
909 async fn test_delete_wallet(#[case] expected: Result<()>) {
910 let (_srv, config, _cleanup) = set_config().await;
912 let mut sdk = Sdk::new(config).unwrap();
913
914 let pin = EncryptionPin::try_from_string("123456").unwrap();
915
916 match &expected {
917 Ok(_) => {
918 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 2, KycType::Undefined);
919 mock_user_repo.expect_update().once().returning(|_| Ok(()));
920
921 sdk.repo = Some(Box::new(mock_user_repo));
922
923 let mut mock_wallet_manager = MockWalletManager::new();
924 mock_wallet_manager
925 .expect_delete_wallet()
926 .once()
927 .returning(|_, _, _| Ok(()));
928 sdk.active_user = Some(crate::types::users::ActiveUser {
929 username: USERNAME.into(),
930 wallet_manager: Box::new(mock_wallet_manager),
931 mnemonic_derivation_options: Default::default(),
932 });
933
934 let new_pin = EncryptionPin::try_from_string("123456").unwrap();
935 sdk.change_pin(&pin, &new_pin).await.unwrap();
936 }
937 Err(error) => {
938 handle_error_test_cases(error, &mut sdk, 1, 0).await;
939 }
940 }
941
942 let response = sdk.delete_wallet(&PIN).await;
944
945 match expected {
947 Ok(()) => response.unwrap(),
948 Err(ref expected_err) => {
949 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
950 }
951 }
952 }
953
954 #[rstest]
955 #[case::success(Ok(()))]
956 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
957 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
958 #[case::wallet_not_initialized(Err(crate::Error::Wallet(WalletError::WalletNotInitialized(
959 ErrorKind::MissingPassword
960 ))))]
961 #[tokio::test]
962 async fn test_verify_pin(#[case] expected: Result<()>) {
963 let (_srv, config, _cleanup) = set_config().await;
965 let mut sdk = Sdk::new(config).unwrap();
966
967 match &expected {
968 Ok(_) => {
969 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
970 sdk.repo = Some(Box::new(mock_user_repo));
971
972 sdk.active_user = Some(crate::types::users::ActiveUser {
973 username: USERNAME.into(),
974 wallet_manager: Box::new(MockWalletManager::new()),
975 mnemonic_derivation_options: Default::default(),
976 });
977 }
978 Err(error) => {
979 handle_error_test_cases(error, &mut sdk, 1, 0).await;
980 }
981 }
982
983 let response = sdk.verify_pin(&PIN).await;
985
986 match expected {
988 Ok(()) => response.unwrap(),
989 Err(ref expected_err) => {
990 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
991 }
992 }
993 }
994
995 #[rstest]
996 #[case::success(Ok(()))]
997 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
998 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
999 #[case::wallet_not_initialized(Err(crate::Error::Wallet(WalletError::WalletNotInitialized(
1000 ErrorKind::MissingPassword
1001 ))))]
1002 #[tokio::test]
1003 async fn test_change_pin(#[case] expected: Result<()>) {
1004 let (_srv, config, _cleanup) = set_config().await;
1006 let mut sdk = Sdk::new(config).unwrap();
1007
1008 match &expected {
1009 Ok(_) => {
1010 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
1011 mock_user_repo.expect_update().once().returning(|_| Ok(()));
1012 sdk.repo = Some(Box::new(mock_user_repo));
1013
1014 sdk.active_user = Some(crate::types::users::ActiveUser {
1015 username: USERNAME.into(),
1016 wallet_manager: Box::new(MockWalletManager::new()),
1017 mnemonic_derivation_options: Default::default(),
1018 });
1019 }
1020 Err(error) => {
1021 handle_error_test_cases(error, &mut sdk, 1, 0).await;
1022 }
1023 }
1024
1025 let new_pin: LazyLock<EncryptionPin> = LazyLock::new(|| EncryptionPin::try_from_string("432154").unwrap());
1027 let response = sdk.change_pin(&PIN, &new_pin).await;
1028
1029 match expected {
1031 Ok(()) => response.unwrap(),
1032 Err(ref expected_err) => {
1033 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1034 }
1035 }
1036 }
1037
1038 #[rstest]
1039 #[case::success(Ok(()))]
1040 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
1041 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
1042 #[tokio::test]
1043 async fn test_set_wallet_password(#[case] expected: Result<()>) {
1044 let (_srv, config, _cleanup) = set_config().await;
1046 let mut sdk = Sdk::new(config).unwrap();
1047
1048 match &expected {
1049 Ok(_) => {
1050 let mut mock_user_repo = MockUserRepo::new();
1051 mock_user_repo.expect_get().times(1).returning(move |r1| {
1052 assert_eq!(r1, USERNAME);
1053 Ok(UserEntity {
1054 user_id: None,
1055 username: USERNAME.into(),
1056 encrypted_password: None,
1057 salt: SALT.into(),
1058 is_kyc_verified: false,
1059 kyc_type: KycType::Undefined,
1060 viviswap_state: Option::None,
1061 local_share: None,
1062 wallet_transactions: Vec::new(),
1063 })
1064 });
1065 mock_user_repo.expect_update().once().returning(|_| Ok(()));
1066 sdk.repo = Some(Box::new(mock_user_repo));
1067
1068 sdk.active_user = Some(crate::types::users::ActiveUser {
1069 username: USERNAME.into(),
1070 wallet_manager: Box::new(MockWalletManager::new()),
1071 mnemonic_derivation_options: Default::default(),
1072 });
1073 }
1074 Err(error) => {
1075 handle_error_test_cases(error, &mut sdk, 1, 0).await;
1076 }
1077 }
1078
1079 let response = sdk.set_wallet_password(&PIN, &WALLET_PASSWORD).await;
1081
1082 match expected {
1084 Ok(()) => response.unwrap(),
1085 Err(ref expected_err) => {
1086 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1087 }
1088 }
1089 }
1090
1091 #[rstest]
1092 #[case::success(Ok(ADDRESS.to_string()))]
1093 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
1094 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
1095 #[case::missing_config(Err(crate::Error::MissingConfig))]
1096 #[tokio::test]
1097 async fn test_generate_new_address(#[case] expected: Result<String>) {
1098 let (mut srv, config, _cleanup) = set_config().await;
1100 let mut sdk = Sdk::new(config).unwrap();
1101 let mut mock_server = None;
1102
1103 match &expected {
1104 Ok(_) => {
1105 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
1106 sdk.repo = Some(Box::new(mock_user_repo));
1107
1108 let mut mock_wallet_manager = MockWalletManager::new();
1109 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _, _| {
1110 let mut mock_wallet_user = MockWalletUser::new();
1111 mock_wallet_user
1112 .expect_get_address()
1113 .once()
1114 .returning(|| Ok(ADDRESS.to_string()));
1115 Ok(WalletBorrow::from(mock_wallet_user))
1116 });
1117 sdk.active_user = Some(crate::types::users::ActiveUser {
1118 username: USERNAME.into(),
1119 wallet_manager: Box::new(mock_wallet_manager),
1120 mnemonic_derivation_options: Default::default(),
1121 });
1122 sdk.access_token = Some(TOKEN.clone());
1123 sdk.set_networks(example_api_networks());
1124 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
1125
1126 let mock_request = SetUserAddressRequest {
1127 address: ADDRESS.into(),
1128 };
1129 let body = serde_json::to_string(&mock_request).unwrap();
1130
1131 mock_server = Some(
1132 srv.mock("PUT", "/api/user/address")
1133 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
1134 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
1135 .match_header("content-type", "application/json")
1136 .match_query(Matcher::Exact("network_key=IOTA".to_string()))
1137 .match_body(Matcher::Exact(body))
1138 .with_status(201)
1139 .expect(1)
1140 .with_header("content-type", "application/json")
1141 .create(),
1142 );
1143 }
1144 Err(error) => {
1145 handle_error_test_cases(error, &mut sdk, 1, 0).await;
1146 }
1147 }
1148
1149 let response = sdk.generate_new_address(&PIN).await;
1151
1152 match expected {
1154 Ok(resp) => {
1155 assert_eq!(response.unwrap(), resp);
1156 }
1157 Err(ref expected_err) => {
1158 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1159 }
1160 }
1161 if let Some(m) = mock_server {
1162 m.assert();
1163 }
1164 }
1165
1166 #[rstest]
1167 #[case::success(Ok(unsafe { CryptoAmount::new_unchecked(dec!(25.0)) }))]
1169 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
1170 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
1171 #[case::missing_config(Err(crate::Error::MissingConfig))]
1172 #[tokio::test]
1173 async fn test_get_balance(#[case] expected: Result<CryptoAmount>) {
1174 let (_srv, config, _cleanup) = set_config().await;
1176 let mut sdk = Sdk::new(config).unwrap();
1177
1178 match &expected {
1179 Ok(_) => {
1180 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
1181 sdk.repo = Some(Box::new(mock_user_repo));
1182
1183 let mut mock_wallet_manager = MockWalletManager::new();
1184 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _, _| {
1185 let mut mock_wallet_user = MockWalletUser::new();
1186 mock_wallet_user
1187 .expect_get_balance()
1188 .once()
1189 .returning(|| Ok(unsafe { CryptoAmount::new_unchecked(dec!(25.0)) }));
1191 Ok(WalletBorrow::from(mock_wallet_user))
1192 });
1193 sdk.active_user = Some(crate::types::users::ActiveUser {
1194 username: USERNAME.into(),
1195 wallet_manager: Box::new(mock_wallet_manager),
1196 mnemonic_derivation_options: Default::default(),
1197 });
1198 sdk.set_networks(example_api_networks());
1199 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
1200 }
1201 Err(error) => {
1202 handle_error_test_cases(error, &mut sdk, 1, 0).await;
1203 }
1204 }
1205
1206 let response = sdk.get_balance(&PIN).await;
1208
1209 match expected {
1211 Ok(resp) => {
1212 assert_eq!(response.unwrap(), resp);
1213 }
1214 Err(ref expected_err) => {
1215 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1216 }
1217 }
1218 }
1219
1220 #[rstest]
1221 #[case::success(Ok(example_wallet_tx_info()))]
1222 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
1223 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
1224 #[case::missing_config(Err(crate::Error::MissingConfig))]
1225 #[tokio::test]
1226 async fn test_get_wallet_tx(#[case] expected: Result<WalletTxInfo>) {
1227 let (_srv, config, _cleanup) = set_config().await;
1229 let mut sdk = Sdk::new(config).unwrap();
1230
1231 match &expected {
1232 Ok(_) => {
1233 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
1234 sdk.repo = Some(Box::new(mock_user_repo));
1235
1236 let mut mock_wallet_manager = MockWalletManager::new();
1237 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _, _| {
1238 let mut mock_wallet_user = MockWalletUser::new();
1239 mock_wallet_user
1240 .expect_get_wallet_tx()
1241 .once()
1242 .returning(|_| Ok(example_wallet_tx_info()));
1243 Ok(WalletBorrow::from(mock_wallet_user))
1244 });
1245 sdk.active_user = Some(crate::types::users::ActiveUser {
1246 username: USERNAME.into(),
1247 wallet_manager: Box::new(mock_wallet_manager),
1248 mnemonic_derivation_options: Default::default(),
1249 });
1250 sdk.set_networks(example_api_networks());
1251 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
1252 }
1253 Err(error) => {
1254 handle_error_test_cases(error, &mut sdk, 1, 0).await;
1255 }
1256 }
1257
1258 let response = sdk.get_wallet_tx(&PIN, TX_INDEX).await;
1260
1261 match expected {
1263 Ok(resp) => {
1264 assert_eq!(response.unwrap(), resp);
1265 }
1266 Err(ref expected_err) => {
1267 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1268 }
1269 }
1270 }
1271
1272 #[rstest]
1273 #[case::success(Ok(WalletTxInfoList { transactions: vec![]}))]
1274 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
1275 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
1276 #[case::missing_config(Err(crate::Error::MissingConfig))]
1277 #[tokio::test]
1278 async fn test_get_wallet_tx_list(#[case] expected: Result<WalletTxInfoList>) {
1279 let (_srv, config, _cleanup) = set_config().await;
1281 let mut sdk = Sdk::new(config).unwrap();
1282
1283 match &expected {
1284 Ok(_) => {
1285 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 2, KycType::Undefined);
1286 mock_user_repo
1287 .expect_set_wallet_transactions()
1288 .once()
1289 .returning(|_, _| Ok(()));
1290 sdk.repo = Some(Box::new(mock_user_repo));
1291
1292 let mut mock_wallet_manager = MockWalletManager::new();
1293 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _, _| {
1294 let mut mock_wallet_user = MockWalletUser::new();
1295 mock_wallet_user
1296 .expect_get_wallet_tx_list()
1297 .once()
1298 .returning(|_, _| Ok(vec![]));
1299 Ok(WalletBorrow::from(mock_wallet_user))
1300 });
1301 sdk.active_user = Some(crate::types::users::ActiveUser {
1302 username: USERNAME.into(),
1303 wallet_manager: Box::new(mock_wallet_manager),
1304 mnemonic_derivation_options: Default::default(),
1305 });
1306 sdk.set_networks(example_api_networks());
1307 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
1308 }
1309 Err(error) => {
1310 handle_error_test_cases(error, &mut sdk, 1, 0).await;
1311 }
1312 }
1313
1314 let response = sdk.get_wallet_tx_list(&PIN, 0, 10).await;
1316
1317 match expected {
1319 Ok(resp) => {
1320 assert_eq!(response.unwrap(), resp);
1321 }
1322 Err(ref expected_err) => {
1323 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1324 }
1325 }
1326 }
1327
1328 #[tokio::test]
1329 async fn test_get_wallet_tx_list_filters_transactions_correctly() {
1330 let (_srv, config, _cleanup) = set_config().await;
1332 let mut sdk = Sdk::new(config).unwrap();
1333
1334 let mixed_wallet_transactions = vec![
1337 WalletTxInfo {
1338 date: "some date".to_string(),
1339 block_number_hash: None,
1340 transaction_hash: "some tx id".to_string(),
1341 receiver: String::new(),
1342 sender: String::new(),
1343 amount: unsafe { CryptoAmount::new_unchecked(dec!(20.0)) },
1344 network_key: "IOTA".to_string(),
1345 status: WalletTxStatus::Confirmed,
1346 explorer_url: None,
1347 },
1348 WalletTxInfo {
1349 date: "some date".to_string(),
1350 block_number_hash: None,
1351 transaction_hash: "1".to_string(),
1352 receiver: String::new(),
1353 sender: String::new(),
1354 amount: unsafe { CryptoAmount::new_unchecked(dec!(1.0)) },
1355 network_key: "ETH".to_string(),
1356 status: WalletTxStatus::Pending,
1357 explorer_url: None,
1358 },
1359 WalletTxInfo {
1360 date: "some date".to_string(),
1361 block_number_hash: None,
1362 transaction_hash: "2".to_string(),
1363 receiver: String::new(),
1364 sender: String::new(),
1365 amount: unsafe { CryptoAmount::new_unchecked(dec!(2.0)) },
1366 network_key: "ETH".to_string(),
1367 status: WalletTxStatus::Pending, explorer_url: None,
1369 },
1370 WalletTxInfo {
1371 date: "some date".to_string(),
1372 block_number_hash: None,
1373 transaction_hash: "3".to_string(),
1374 receiver: String::new(),
1375 sender: String::new(),
1376 amount: unsafe { CryptoAmount::new_unchecked(dec!(3.0)) },
1377 network_key: "ETH".to_string(),
1378 status: WalletTxStatus::Pending,
1379 explorer_url: None,
1380 },
1381 ];
1382
1383 let mut mock_user_repo = MockUserRepo::new();
1384 mock_user_repo.expect_get().returning(move |_| {
1385 Ok(UserEntity {
1386 user_id: None,
1387 username: USERNAME.to_string(),
1388 encrypted_password: Some(ENCRYPTED_WALLET_PASSWORD.clone()),
1389 salt: SALT.into(),
1390 is_kyc_verified: false,
1391 kyc_type: KycType::Undefined,
1392 viviswap_state: None,
1393 local_share: None,
1394 wallet_transactions: mixed_wallet_transactions.clone(),
1395 })
1396 });
1397
1398 let mixed_wallet_transactions_after_synchronization = vec![
1399 WalletTxInfo {
1400 date: "some date".to_string(),
1401 block_number_hash: None,
1402 transaction_hash: "some tx id".to_string(),
1403 receiver: String::new(),
1404 sender: String::new(),
1405 amount: unsafe { CryptoAmount::new_unchecked(dec!(20.0)) },
1406 network_key: "IOTA".to_string(),
1407 status: WalletTxStatus::Confirmed,
1408 explorer_url: None,
1409 },
1410 WalletTxInfo {
1411 date: "some date".to_string(),
1412 block_number_hash: None,
1413 transaction_hash: "1".to_string(),
1414 receiver: String::new(),
1415 sender: String::new(),
1416 amount: unsafe { CryptoAmount::new_unchecked(dec!(1.0)) },
1417 network_key: "ETH".to_string(),
1418 status: WalletTxStatus::Pending,
1419 explorer_url: None,
1420 },
1421 WalletTxInfo {
1422 date: "some date".to_string(),
1423 block_number_hash: None,
1424 transaction_hash: "2".to_string(),
1425 receiver: String::new(),
1426 sender: String::new(),
1427 amount: unsafe { CryptoAmount::new_unchecked(dec!(2.0)) },
1428 network_key: "ETH".to_string(),
1429 status: WalletTxStatus::Confirmed,
1430 explorer_url: None,
1431 },
1432 WalletTxInfo {
1433 date: "some date".to_string(),
1434 block_number_hash: None,
1435 transaction_hash: "3".to_string(),
1436 receiver: String::new(),
1437 sender: String::new(),
1438 amount: unsafe { CryptoAmount::new_unchecked(dec!(3.0)) },
1439 network_key: "ETH".to_string(),
1440 status: WalletTxStatus::Pending,
1441 explorer_url: None,
1442 },
1443 ];
1444
1445 mock_user_repo
1446 .expect_set_wallet_transactions()
1447 .once()
1448 .with(
1449 eq(USERNAME.to_string()),
1450 eq(mixed_wallet_transactions_after_synchronization.clone()),
1451 )
1452 .returning(|_, _| Ok(()));
1453
1454 sdk.repo = Some(Box::new(mock_user_repo));
1455
1456 let mut mock_wallet_manager = MockWalletManager::new();
1457 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _, _| {
1458 let mut mock_wallet_user = MockWalletUser::new();
1459 mock_wallet_user
1460 .expect_get_wallet_tx_list()
1461 .once()
1462 .returning(|_, _| Ok(vec![]));
1463 mock_wallet_user
1464 .expect_get_wallet_tx()
1465 .once()
1466 .with(eq(String::from("2"))) .returning(move |_| {
1468 Ok(WalletTxInfo {
1469 date: "some date".to_string(),
1470 block_number_hash: None,
1471 transaction_hash: "2".to_string(),
1472 receiver: String::new(),
1473 sender: String::new(),
1474 amount: unsafe { CryptoAmount::new_unchecked(dec!(2.0)) },
1475 network_key: "ETH".to_string(),
1476 status: WalletTxStatus::Confirmed, explorer_url: None,
1478 })
1479 });
1480 Ok(WalletBorrow::from(mock_wallet_user))
1481 });
1482
1483 sdk.active_user = Some(crate::types::users::ActiveUser {
1484 username: USERNAME.into(),
1485 wallet_manager: Box::new(mock_wallet_manager),
1486 mnemonic_derivation_options: Default::default(),
1487 });
1488
1489 sdk.set_networks(example_api_networks());
1490 sdk.set_network(ETH_NETWORK_KEY.to_string()).await.unwrap();
1491
1492 let response = sdk.get_wallet_tx_list(&PIN, 1, 1).await;
1500
1501 assert_eq!(
1503 response.unwrap(),
1504 WalletTxInfoList {
1505 transactions: vec![WalletTxInfo {
1506 date: "some date".to_string(),
1507 block_number_hash: None,
1508 transaction_hash: "2".to_string(),
1509 receiver: String::new(),
1510 sender: String::new(),
1511 amount: unsafe { CryptoAmount::new_unchecked(dec!(2.0)) },
1512 network_key: "ETH".to_string(),
1513 status: WalletTxStatus::Confirmed,
1514 explorer_url: None,
1515 }]
1516 }
1517 );
1518 }
1519
1520 #[tokio::test]
1521 async fn test_get_wallet_tx_list_does_not_query_network_for_transaction_state() {
1522 let (_srv, config, _cleanup) = set_config().await;
1524 let mut sdk = Sdk::new(config).unwrap();
1525
1526 let wallet_transactions = vec![WalletTxInfo {
1527 date: "some date".to_string(),
1528 block_number_hash: None,
1529 transaction_hash: "1".to_string(),
1530 receiver: String::new(),
1531 sender: String::new(),
1532 amount: unsafe { CryptoAmount::new_unchecked(dec!(1.0)) },
1533 network_key: "ETH".to_string(),
1534 status: WalletTxStatus::Confirmed,
1535 explorer_url: None,
1536 }];
1537
1538 let mut mock_user_repo = MockUserRepo::new();
1539 mock_user_repo.expect_get().returning(move |_| {
1540 Ok(UserEntity {
1541 user_id: None,
1542 username: USERNAME.to_string(),
1543 encrypted_password: Some(ENCRYPTED_WALLET_PASSWORD.clone()),
1544 salt: SALT.into(),
1545 is_kyc_verified: false,
1546 kyc_type: KycType::Undefined,
1547 viviswap_state: None,
1548 local_share: None,
1549 wallet_transactions: wallet_transactions.clone(),
1550 })
1551 });
1552
1553 mock_user_repo
1554 .expect_set_wallet_transactions()
1555 .once()
1556 .returning(|_, _| Ok(()));
1557
1558 sdk.repo = Some(Box::new(mock_user_repo));
1559
1560 let mut mock_wallet_manager = MockWalletManager::new();
1561 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _, _| {
1562 let mut mock_wallet_user = MockWalletUser::new();
1563 mock_wallet_user
1564 .expect_get_wallet_tx_list()
1565 .once()
1566 .returning(|_, _| Ok(vec![]));
1567 mock_wallet_user.expect_get_wallet_tx().never();
1568 Ok(WalletBorrow::from(mock_wallet_user))
1569 });
1570
1571 sdk.active_user = Some(crate::types::users::ActiveUser {
1572 username: USERNAME.into(),
1573 wallet_manager: Box::new(mock_wallet_manager),
1574 mnemonic_derivation_options: Default::default(),
1575 });
1576
1577 sdk.set_networks(example_api_networks());
1578 sdk.set_network(ETH_NETWORK_KEY.to_string()).await.unwrap();
1579
1580 let response = sdk.get_wallet_tx_list(&PIN, 0, 1).await;
1582
1583 assert!(response.is_ok())
1585 }
1586
1587 #[tokio::test]
1588 async fn test_get_wallet_tx_list_should_sort_wallet_transactions() {
1589 let (_srv, config, _cleanup) = set_config().await;
1591 let mut sdk = Sdk::new(config).unwrap();
1592
1593 let tx_3 = WalletTxInfo {
1594 date: "2025-05-29T08:37:15.183+00:00".to_string(),
1595 block_number_hash: None,
1596 transaction_hash: "3".to_string(),
1597 receiver: String::new(),
1598 sender: String::new(),
1599 amount: CryptoAmount::from(1),
1600 network_key: "ETH".to_string(),
1601 status: WalletTxStatus::Confirmed,
1602 explorer_url: None,
1603 };
1604 let tx_1 = WalletTxInfo {
1605 date: "2025-05-29T08:37:13.183+00:00".to_string(),
1606 block_number_hash: None,
1607 transaction_hash: "1".to_string(),
1608 receiver: String::new(),
1609 sender: String::new(),
1610 amount: CryptoAmount::from(1),
1611 network_key: "ETH".to_string(),
1612 status: WalletTxStatus::Confirmed,
1613 explorer_url: None,
1614 };
1615
1616 let tx_2 = WalletTxInfo {
1617 date: "2025-05-29T08:37:14.183+00:00".to_string(),
1618 block_number_hash: None,
1619 transaction_hash: "2".to_string(),
1620 receiver: String::new(),
1621 sender: String::new(),
1622 amount: CryptoAmount::from(1),
1623 network_key: "ETH".to_string(),
1624 status: WalletTxStatus::Confirmed,
1625 explorer_url: None,
1626 };
1627
1628 let wallet_transactions = vec![tx_3.clone(), tx_1.clone(), tx_2.clone()];
1629 let expected = vec![tx_3, tx_2, tx_1];
1630
1631 let mut mock_user_repo = MockUserRepo::new();
1632 mock_user_repo.expect_get().returning(move |_| {
1633 Ok(UserEntity {
1634 user_id: None,
1635 username: USERNAME.to_string(),
1636 encrypted_password: Some(ENCRYPTED_WALLET_PASSWORD.clone()),
1637 salt: SALT.into(),
1638 is_kyc_verified: false,
1639 kyc_type: KycType::Undefined,
1640 viviswap_state: None,
1641 local_share: None,
1642 wallet_transactions: wallet_transactions.clone(),
1643 })
1644 });
1645
1646 mock_user_repo
1647 .expect_set_wallet_transactions()
1648 .once()
1649 .returning(|_, _| Ok(()));
1650
1651 sdk.repo = Some(Box::new(mock_user_repo));
1652
1653 let mut mock_wallet_manager = MockWalletManager::new();
1654 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _, _| {
1655 let mut mock_wallet_user = MockWalletUser::new();
1656 mock_wallet_user
1657 .expect_get_wallet_tx_list()
1658 .once()
1659 .returning(|_, _| Ok(vec![]));
1660 mock_wallet_user.expect_get_wallet_tx().never();
1661 Ok(WalletBorrow::from(mock_wallet_user))
1662 });
1663
1664 sdk.active_user = Some(crate::types::users::ActiveUser {
1665 username: USERNAME.into(),
1666 wallet_manager: Box::new(mock_wallet_manager),
1667 mnemonic_derivation_options: Default::default(),
1668 });
1669
1670 sdk.set_networks(example_api_networks());
1671 sdk.set_network(ETH_NETWORK_KEY.to_string()).await.unwrap();
1672
1673 let response = sdk.get_wallet_tx_list(&PIN, 0, 5).await;
1675
1676 assert_eq!(expected, response.unwrap().transactions)
1678 }
1679}