1use super::Sdk;
7use crate::{
8 backend::dlt::put_user_address,
9 error::Result,
10 types::{
11 currencies::CryptoAmount,
12 newtypes::{EncryptionPin, EncryptionSalt, PlainPassword},
13 transactions::{WalletTxInfo, WalletTxInfoList},
14 },
15 wallet::error::{ErrorKind, WalletError},
16};
17use iota_sdk::wallet::account::types::InclusionState;
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(config, &self.access_token, repo, network, pin)
438 .await?;
439
440 let address = wallet.get_address().await?;
441
442 if let Some(access_token) = self.access_token.as_ref() {
444 if network.can_do_purchases {
445 put_user_address(config, access_token, &network.key, &address).await?;
446 }
447 }
448 debug!("Generated address: {address}");
449 Ok(address)
450 }
451
452 pub async fn get_balance(&mut self, pin: &EncryptionPin) -> Result<CryptoAmount> {
465 info!("Fetching balance");
466 self.verify_pin(pin).await?;
467 let wallet = self.try_get_active_user_wallet(pin).await?;
468 let balance = wallet.get_balance().await?;
469 debug!("Balance: {balance:?}");
470 Ok(balance)
471 }
472
473 pub async fn get_wallet_tx_list(
491 &mut self,
492 pin: &EncryptionPin,
493 start: usize,
494 limit: usize,
495 ) -> Result<WalletTxInfoList> {
496 info!("Wallet getting list of transactions");
497 self.verify_pin(pin).await?;
498
499 let network = self.active_network.clone().ok_or(crate::Error::MissingNetwork)?;
500 let user = self.get_user().await?;
501 let wallet = self.try_get_active_user_wallet(pin).await?;
502
503 let inclusion_state_confirmed = format!("{:?}", InclusionState::Confirmed);
504
505 let tx_list = match network.protocol {
506 crate::types::networks::ApiProtocol::EvmERC20 {
507 chain_id: _,
508 contract_address: _,
509 } => wallet.get_wallet_tx_list(start, limit).await?,
510 crate::types::networks::ApiProtocol::Evm { chain_id: _ } => {
511 let mut wallet_transactions = user.wallet_transactions;
515
516 for transaction in wallet_transactions
517 .iter_mut()
518 .filter(|tx| tx.network_key == network.key)
519 .skip(start)
520 .take(limit)
521 {
522 if transaction.status == inclusion_state_confirmed {
525 continue;
526 }
527
528 let synchronized_transaction = wallet.get_wallet_tx(&transaction.transaction_id).await;
529 match synchronized_transaction {
530 Ok(stx) => *transaction = stx,
531 Err(e) => {
532 log::debug!(
534 "[sync_transactions] could not retrieve data about transaction from the network, transaction: {:?}, error: {:?}",
535 transaction.clone(),
536 e
537 );
538 }
539 }
540 }
541
542 let Some(repo) = &mut self.repo else {
543 return Err(crate::Error::UserRepoNotInitialized);
544 };
545
546 let _ = repo.set_wallet_transactions(&user.username, wallet_transactions.clone());
547
548 WalletTxInfoList {
549 transactions: wallet_transactions,
550 }
551 }
552 api_types::api::networks::ApiProtocol::Stardust {} => wallet.get_wallet_tx_list(start, limit).await?,
553 };
554
555 let tx_list_filtered = tx_list
556 .transactions
557 .into_iter()
558 .filter(|tx| tx.network_key == network.key)
559 .skip(start)
560 .take(limit)
561 .collect();
562
563 Ok(WalletTxInfoList {
564 transactions: tx_list_filtered,
565 })
566 }
567
568 pub async fn get_wallet_tx(&mut self, pin: &EncryptionPin, tx_id: &str) -> Result<WalletTxInfo> {
585 info!("Wallet getting details of particular transactions");
586 self.verify_pin(pin).await?;
587 let wallet = self.try_get_active_user_wallet(pin).await?;
588 let wallet_tx = wallet.get_wallet_tx(tx_id).await?;
589 Ok(wallet_tx)
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596 use crate::core::core_testing_utils::handle_error_test_cases;
597 use crate::testing_utils::{
598 ADDRESS, AUTH_PROVIDER, ENCRYPTED_WALLET_PASSWORD, ETH_NETWORK_KEY, HEADER_X_APP_NAME, IOTA_NETWORK_KEY,
599 MNEMONIC, PIN, SALT, TOKEN, TX_INDEX, USERNAME, WALLET_PASSWORD, example_api_networks, example_get_user,
600 example_wallet_tx_info, set_config,
601 };
602 use crate::types::users::UserEntity;
603 use crate::{
604 core::Sdk,
605 types::users::KycType,
606 user::MockUserRepo,
607 wallet::wallet::MockWalletUser,
608 wallet_manager::{MockWalletManager, WalletBorrow},
609 };
610 use api_types::api::dlt::SetUserAddressRequest;
611 use api_types::api::viviswap::detail::SwapPaymentDetailKey;
612 use iota_sdk::wallet::account::types::InclusionState;
613 use mockall::predicate::eq;
614 use mockito::Matcher;
615 use rstest::rstest;
616 use rust_decimal_macros::dec;
617 use std::sync::LazyLock;
618
619 const BACKUP: &[u8] = &[42, 77, 15, 203, 89, 123, 34, 56, 178, 90, 210, 33, 47, 192, 1, 17];
620
621 #[rstest]
622 #[case::success(Ok(MNEMONIC.to_string()))]
623 #[case::missing_config(Err(crate::Error::MissingConfig))]
624 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
625 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
626 #[tokio::test]
627 async fn test_create_wallet_from_new_mnemonic(#[case] expected: Result<String>) {
628 let (_srv, config, _cleanup) = set_config().await;
630 let mut sdk = Sdk::new(config).unwrap();
631
632 match &expected {
633 Ok(_) => {
634 sdk.repo = Some(Box::new(MockUserRepo::new()));
635 let mut mock_wallet_manager = MockWalletManager::new();
636 mock_wallet_manager
637 .expect_create_wallet_from_new_mnemonic()
638 .once()
639 .returning(|_, _, _, _| Ok(MNEMONIC.to_string()));
640 sdk.active_user = Some(crate::types::users::ActiveUser {
641 username: USERNAME.into(),
642 wallet_manager: Box::new(mock_wallet_manager),
643 });
644 }
645 Err(error) => {
646 handle_error_test_cases(error, &mut sdk, 0, 0).await;
647 }
648 }
649
650 let response = sdk.create_wallet_from_new_mnemonic(&PIN).await;
652
653 match expected {
655 Ok(resp) => {
656 assert_eq!(response.unwrap(), resp);
657 }
658 Err(ref expected_err) => {
659 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
660 }
661 }
662 }
663
664 #[rstest]
665 #[case::success(Ok(()))]
666 #[case::missing_config(Err(crate::Error::MissingConfig))]
667 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
668 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
669 #[tokio::test]
670 async fn test_create_wallet_from_existing_mnemonic(#[case] expected: Result<()>) {
671 let (_srv, config, _cleanup) = set_config().await;
673 let mut sdk = Sdk::new(config).unwrap();
674
675 match &expected {
676 Ok(_) => {
677 sdk.repo = Some(Box::new(MockUserRepo::new()));
678 let mut mock_wallet_manager = MockWalletManager::new();
679 mock_wallet_manager
680 .expect_create_wallet_from_existing_mnemonic()
681 .once()
682 .returning(|_, _, _, _, _| Ok(()));
683 sdk.active_user = Some(crate::types::users::ActiveUser {
684 username: USERNAME.into(),
685 wallet_manager: Box::new(mock_wallet_manager),
686 });
687 }
688 Err(error) => {
689 handle_error_test_cases(error, &mut sdk, 0, 0).await;
690 }
691 }
692
693 let response = sdk.create_wallet_from_existing_mnemonic(&PIN, MNEMONIC).await;
695
696 match expected {
698 Ok(()) => response.unwrap(),
699 Err(ref expected_err) => {
700 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
701 }
702 }
703 }
704
705 #[rstest]
706 #[case::success(Ok(BACKUP.to_vec()))]
707 #[case::missing_config(Err(crate::Error::MissingConfig))]
708 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
709 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
710 #[tokio::test]
711 async fn test_create_wallet_backup(#[case] expected: Result<Vec<u8>>) {
712 let (_srv, config, _cleanup) = set_config().await;
714 let mut sdk = Sdk::new(config).unwrap();
715
716 match &expected {
717 Ok(_) => {
718 sdk.repo = Some(Box::new(MockUserRepo::new()));
719 let mut mock_wallet_manager = MockWalletManager::new();
720 mock_wallet_manager
721 .expect_create_wallet_backup()
722 .once()
723 .returning(|_, _, _, _, _| Ok(BACKUP.to_vec()));
724 sdk.active_user = Some(crate::types::users::ActiveUser {
725 username: USERNAME.into(),
726 wallet_manager: Box::new(mock_wallet_manager),
727 });
728 }
729 Err(error) => {
730 handle_error_test_cases(error, &mut sdk, 0, 0).await;
731 }
732 }
733
734 let response = sdk.create_wallet_backup(&PIN, &WALLET_PASSWORD).await;
736
737 match expected {
739 Ok(resp) => {
740 assert_eq!(response.unwrap(), resp);
741 }
742 Err(ref expected_err) => {
743 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
744 }
745 }
746 }
747
748 #[rstest]
749 #[case::success(Ok(()))]
750 #[case::missing_config(Err(crate::Error::MissingConfig))]
751 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
752 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
753 #[tokio::test]
754 async fn test_create_wallet_from_backup(#[case] expected: Result<()>) {
755 let (_srv, config, _cleanup) = set_config().await;
757 let mut sdk = Sdk::new(config).unwrap();
758
759 match &expected {
760 Ok(_) => {
761 sdk.repo = Some(Box::new(MockUserRepo::new()));
762 let mut mock_wallet_manager = MockWalletManager::new();
763 mock_wallet_manager
764 .expect_create_wallet_from_backup()
765 .once()
766 .returning(|_, _, _, _, _, _| Ok(()));
767 sdk.active_user = Some(crate::types::users::ActiveUser {
768 username: USERNAME.into(),
769 wallet_manager: Box::new(mock_wallet_manager),
770 });
771 }
772 Err(error) => {
773 handle_error_test_cases(error, &mut sdk, 0, 0).await;
774 }
775 }
776
777 let response = sdk.create_wallet_from_backup(&PIN, BACKUP, &WALLET_PASSWORD).await;
779
780 match expected {
782 Ok(()) => response.unwrap(),
783 Err(ref expected_err) => {
784 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
785 }
786 }
787 }
788
789 #[rstest]
790 #[case::success(Ok(true))]
791 #[case::missing_config(Err(crate::Error::MissingConfig))]
792 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
793 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
794 #[tokio::test]
795 async fn test_verify_mnemonic(#[case] expected: Result<bool>) {
796 let (_srv, config, _cleanup) = set_config().await;
798 let mut sdk = Sdk::new(config).unwrap();
799
800 match &expected {
801 Ok(_) => {
802 sdk.repo = Some(Box::new(MockUserRepo::new()));
803 let mut mock_wallet_manager = MockWalletManager::new();
804 mock_wallet_manager
805 .expect_check_mnemonic()
806 .once()
807 .returning(|_, _, _, _, _| Ok(true));
808 sdk.active_user = Some(crate::types::users::ActiveUser {
809 username: USERNAME.into(),
810 wallet_manager: Box::new(mock_wallet_manager),
811 });
812 }
813 Err(error) => {
814 handle_error_test_cases(error, &mut sdk, 0, 0).await;
815 }
816 }
817
818 let response = sdk.verify_mnemonic(&PIN, MNEMONIC).await;
820
821 match expected {
823 Ok(resp) => {
824 assert_eq!(response.unwrap(), resp);
825 }
826 Err(ref expected_err) => {
827 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
828 }
829 }
830 }
831
832 #[rstest]
833 #[case::success(Ok(()))]
834 #[case::missing_config(Err(crate::Error::MissingConfig))]
835 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
836 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
837 #[tokio::test]
838 async fn test_delete_wallet(#[case] expected: Result<()>) {
839 let (_srv, config, _cleanup) = set_config().await;
841 let mut sdk = Sdk::new(config).unwrap();
842
843 let pin = EncryptionPin::try_from_string("123456").unwrap();
844
845 match &expected {
846 Ok(_) => {
847 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 2, KycType::Undefined);
848 mock_user_repo.expect_update().once().returning(|_| Ok(()));
849
850 sdk.repo = Some(Box::new(mock_user_repo));
851
852 let mut mock_wallet_manager = MockWalletManager::new();
853 mock_wallet_manager
854 .expect_delete_wallet()
855 .once()
856 .returning(|_, _, _| Ok(()));
857 sdk.active_user = Some(crate::types::users::ActiveUser {
858 username: USERNAME.into(),
859 wallet_manager: Box::new(mock_wallet_manager),
860 });
861
862 let new_pin = EncryptionPin::try_from_string("123456").unwrap();
863 sdk.change_pin(&pin, &new_pin).await.unwrap();
864 }
865 Err(error) => {
866 handle_error_test_cases(error, &mut sdk, 1, 0).await;
867 }
868 }
869
870 let response = sdk.delete_wallet(&PIN).await;
872
873 match expected {
875 Ok(()) => response.unwrap(),
876 Err(ref expected_err) => {
877 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
878 }
879 }
880 }
881
882 #[rstest]
883 #[case::success(Ok(()))]
884 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
885 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
886 #[case::wallet_not_initialized(Err(crate::Error::Wallet(WalletError::WalletNotInitialized(
887 ErrorKind::MissingPassword
888 ))))]
889 #[tokio::test]
890 async fn test_verify_pin(#[case] expected: Result<()>) {
891 let (_srv, config, _cleanup) = set_config().await;
893 let mut sdk = Sdk::new(config).unwrap();
894
895 match &expected {
896 Ok(_) => {
897 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
898 sdk.repo = Some(Box::new(mock_user_repo));
899
900 sdk.active_user = Some(crate::types::users::ActiveUser {
901 username: USERNAME.into(),
902 wallet_manager: Box::new(MockWalletManager::new()),
903 });
904 }
905 Err(error) => {
906 handle_error_test_cases(error, &mut sdk, 1, 0).await;
907 }
908 }
909
910 let response = sdk.verify_pin(&PIN).await;
912
913 match expected {
915 Ok(()) => response.unwrap(),
916 Err(ref expected_err) => {
917 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
918 }
919 }
920 }
921
922 #[rstest]
923 #[case::success(Ok(()))]
924 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
925 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
926 #[case::wallet_not_initialized(Err(crate::Error::Wallet(WalletError::WalletNotInitialized(
927 ErrorKind::MissingPassword
928 ))))]
929 #[tokio::test]
930 async fn test_change_pin(#[case] expected: Result<()>) {
931 let (_srv, config, _cleanup) = set_config().await;
933 let mut sdk = Sdk::new(config).unwrap();
934
935 match &expected {
936 Ok(_) => {
937 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
938 mock_user_repo.expect_update().once().returning(|_| Ok(()));
939 sdk.repo = Some(Box::new(mock_user_repo));
940
941 sdk.active_user = Some(crate::types::users::ActiveUser {
942 username: USERNAME.into(),
943 wallet_manager: Box::new(MockWalletManager::new()),
944 });
945 }
946 Err(error) => {
947 handle_error_test_cases(error, &mut sdk, 1, 0).await;
948 }
949 }
950
951 let new_pin: LazyLock<EncryptionPin> = LazyLock::new(|| EncryptionPin::try_from_string("432154").unwrap());
953 let response = sdk.change_pin(&PIN, &new_pin).await;
954
955 match expected {
957 Ok(()) => response.unwrap(),
958 Err(ref expected_err) => {
959 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
960 }
961 }
962 }
963
964 #[rstest]
965 #[case::success(Ok(()))]
966 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
967 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
968 #[tokio::test]
969 async fn test_set_wallet_password(#[case] expected: Result<()>) {
970 let (_srv, config, _cleanup) = set_config().await;
972 let mut sdk = Sdk::new(config).unwrap();
973
974 match &expected {
975 Ok(_) => {
976 let mut mock_user_repo = MockUserRepo::new();
977 mock_user_repo.expect_get().times(1).returning(move |r1| {
978 assert_eq!(r1, USERNAME);
979 Ok(UserEntity {
980 user_id: None,
981 username: USERNAME.into(),
982 encrypted_password: None,
983 salt: SALT.into(),
984 is_kyc_verified: false,
985 kyc_type: KycType::Undefined,
986 viviswap_state: Option::None,
987 local_share: None,
988 wallet_transactions: Vec::new(),
989 })
990 });
991 mock_user_repo.expect_update().once().returning(|_| Ok(()));
992 sdk.repo = Some(Box::new(mock_user_repo));
993
994 sdk.active_user = Some(crate::types::users::ActiveUser {
995 username: USERNAME.into(),
996 wallet_manager: Box::new(MockWalletManager::new()),
997 });
998 }
999 Err(error) => {
1000 handle_error_test_cases(error, &mut sdk, 1, 0).await;
1001 }
1002 }
1003
1004 let response = sdk.set_wallet_password(&PIN, &WALLET_PASSWORD).await;
1006
1007 match expected {
1009 Ok(()) => response.unwrap(),
1010 Err(ref expected_err) => {
1011 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1012 }
1013 }
1014 }
1015
1016 #[rstest]
1017 #[case::success(Ok(ADDRESS.to_string()))]
1018 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
1019 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
1020 #[case::missing_config(Err(crate::Error::MissingConfig))]
1021 #[tokio::test]
1022 async fn test_generate_new_address(#[case] expected: Result<String>) {
1023 let (mut srv, config, _cleanup) = set_config().await;
1025 let mut sdk = Sdk::new(config).unwrap();
1026 let mut mock_server = None;
1027
1028 match &expected {
1029 Ok(_) => {
1030 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
1031 sdk.repo = Some(Box::new(mock_user_repo));
1032
1033 let mut mock_wallet_manager = MockWalletManager::new();
1034 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _| {
1035 let mut mock_wallet_user = MockWalletUser::new();
1036 mock_wallet_user
1037 .expect_get_address()
1038 .once()
1039 .returning(|| Ok(ADDRESS.to_string()));
1040 Ok(WalletBorrow::from(mock_wallet_user))
1041 });
1042 sdk.active_user = Some(crate::types::users::ActiveUser {
1043 username: USERNAME.into(),
1044 wallet_manager: Box::new(mock_wallet_manager),
1045 });
1046 sdk.access_token = Some(TOKEN.clone());
1047 sdk.set_networks(example_api_networks());
1048 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
1049
1050 let mock_request = SetUserAddressRequest {
1051 address: ADDRESS.into(),
1052 };
1053 let body = serde_json::to_string(&mock_request).unwrap();
1054
1055 mock_server = Some(
1056 srv.mock("PUT", "/api/user/address")
1057 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
1058 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
1059 .match_header("content-type", "application/json")
1060 .match_query(Matcher::Exact("network_key=IOTA".to_string()))
1061 .match_body(Matcher::Exact(body))
1062 .with_status(201)
1063 .expect(1)
1064 .with_header("content-type", "application/json")
1065 .create(),
1066 );
1067 }
1068 Err(error) => {
1069 handle_error_test_cases(error, &mut sdk, 1, 0).await;
1070 }
1071 }
1072
1073 let response = sdk.generate_new_address(&PIN).await;
1075
1076 match expected {
1078 Ok(resp) => {
1079 assert_eq!(response.unwrap(), resp);
1080 }
1081 Err(ref expected_err) => {
1082 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1083 }
1084 }
1085 if let Some(m) = mock_server {
1086 m.assert();
1087 }
1088 }
1089
1090 #[rstest]
1091 #[case::success(Ok(unsafe { CryptoAmount::new_unchecked(dec!(25.0)) }))]
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_get_balance(#[case] expected: Result<CryptoAmount>) {
1098 let (_srv, config, _cleanup) = set_config().await;
1100 let mut sdk = Sdk::new(config).unwrap();
1101
1102 match &expected {
1103 Ok(_) => {
1104 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
1105 sdk.repo = Some(Box::new(mock_user_repo));
1106
1107 let mut mock_wallet_manager = MockWalletManager::new();
1108 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _| {
1109 let mut mock_wallet_user = MockWalletUser::new();
1110 mock_wallet_user
1111 .expect_get_balance()
1112 .once()
1113 .returning(|| Ok(unsafe { CryptoAmount::new_unchecked(dec!(25.0)) }));
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 });
1121 sdk.set_networks(example_api_networks());
1122 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
1123 }
1124 Err(error) => {
1125 handle_error_test_cases(error, &mut sdk, 1, 0).await;
1126 }
1127 }
1128
1129 let response = sdk.get_balance(&PIN).await;
1131
1132 match expected {
1134 Ok(resp) => {
1135 assert_eq!(response.unwrap(), resp);
1136 }
1137 Err(ref expected_err) => {
1138 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1139 }
1140 }
1141 }
1142
1143 #[rstest]
1144 #[case::success(Ok(example_wallet_tx_info()))]
1145 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
1146 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
1147 #[case::missing_config(Err(crate::Error::MissingConfig))]
1148 #[tokio::test]
1149 async fn test_get_wallet_tx(#[case] expected: Result<WalletTxInfo>) {
1150 let (_srv, config, _cleanup) = set_config().await;
1152 let mut sdk = Sdk::new(config).unwrap();
1153
1154 match &expected {
1155 Ok(_) => {
1156 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
1157 sdk.repo = Some(Box::new(mock_user_repo));
1158
1159 let mut mock_wallet_manager = MockWalletManager::new();
1160 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _| {
1161 let mut mock_wallet_user = MockWalletUser::new();
1162 mock_wallet_user
1163 .expect_get_wallet_tx()
1164 .once()
1165 .returning(|_| Ok(example_wallet_tx_info()));
1166 Ok(WalletBorrow::from(mock_wallet_user))
1167 });
1168 sdk.active_user = Some(crate::types::users::ActiveUser {
1169 username: USERNAME.into(),
1170 wallet_manager: Box::new(mock_wallet_manager),
1171 });
1172 sdk.set_networks(example_api_networks());
1173 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
1174 }
1175 Err(error) => {
1176 handle_error_test_cases(error, &mut sdk, 1, 0).await;
1177 }
1178 }
1179
1180 let response = sdk.get_wallet_tx(&PIN, TX_INDEX).await;
1182
1183 match expected {
1185 Ok(resp) => {
1186 assert_eq!(response.unwrap(), resp);
1187 }
1188 Err(ref expected_err) => {
1189 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1190 }
1191 }
1192 }
1193
1194 #[rstest]
1195 #[case::success(Ok(WalletTxInfoList { transactions: vec![example_wallet_tx_info()]}))]
1196 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
1197 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
1198 #[case::missing_config(Err(crate::Error::MissingConfig))]
1199 #[tokio::test]
1200 async fn test_get_wallet_tx_list(#[case] expected: Result<WalletTxInfoList>) {
1201 let (_srv, config, _cleanup) = set_config().await;
1203 let mut sdk = Sdk::new(config).unwrap();
1204
1205 match &expected {
1206 Ok(_) => {
1207 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 2, KycType::Undefined);
1208 sdk.repo = Some(Box::new(mock_user_repo));
1209
1210 let mut mock_wallet_manager = MockWalletManager::new();
1211 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _| {
1212 let mut mock_wallet_user = MockWalletUser::new();
1213 mock_wallet_user.expect_get_wallet_tx_list().once().returning(|_, _| {
1214 Ok(WalletTxInfoList {
1215 transactions: vec![example_wallet_tx_info()],
1216 })
1217 });
1218 Ok(WalletBorrow::from(mock_wallet_user))
1219 });
1220 sdk.active_user = Some(crate::types::users::ActiveUser {
1221 username: USERNAME.into(),
1222 wallet_manager: Box::new(mock_wallet_manager),
1223 });
1224 sdk.set_networks(example_api_networks());
1225 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
1226 }
1227 Err(error) => {
1228 handle_error_test_cases(error, &mut sdk, 2, 0).await;
1229 }
1230 }
1231
1232 let response = sdk.get_wallet_tx_list(&PIN, 0, 10).await;
1234
1235 match expected {
1237 Ok(resp) => {
1238 assert_eq!(response.unwrap(), resp);
1239 }
1240 Err(ref expected_err) => {
1241 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
1242 }
1243 }
1244 }
1245
1246 #[tokio::test]
1247 async fn test_get_wallet_tx_list_filters_transactions_correctly() {
1248 let (_srv, config, _cleanup) = set_config().await;
1250 let mut sdk = Sdk::new(config).unwrap();
1251
1252 let mixed_wallet_transactions = vec![
1255 WalletTxInfo {
1256 date: "some date".to_string(),
1257 block_id: None,
1258 transaction_id: "some tx id".to_string(),
1259 receiver: String::new(),
1260 incoming: true,
1261 amount: 20.0,
1262 network_key: "IOTA".to_string(),
1263 status: format!("{:?}", InclusionState::Confirmed),
1264 explorer_url: None,
1265 },
1266 WalletTxInfo {
1267 date: "some date".to_string(),
1268 block_id: None,
1269 transaction_id: "1".to_string(),
1270 receiver: String::new(),
1271 incoming: true,
1272 amount: 1.0,
1273 network_key: "ETH".to_string(),
1274 status: format!("{:?}", InclusionState::Pending),
1275 explorer_url: None,
1276 },
1277 WalletTxInfo {
1278 date: "some date".to_string(),
1279 block_id: None,
1280 transaction_id: "2".to_string(),
1281 receiver: String::new(),
1282 incoming: true,
1283 amount: 2.0,
1284 network_key: "ETH".to_string(),
1285 status: format!("{:?}", InclusionState::Pending), explorer_url: None,
1287 },
1288 WalletTxInfo {
1289 date: "some date".to_string(),
1290 block_id: None,
1291 transaction_id: "3".to_string(),
1292 receiver: String::new(),
1293 incoming: true,
1294 amount: 3.0,
1295 network_key: "ETH".to_string(),
1296 status: format!("{:?}", InclusionState::Pending),
1297 explorer_url: None,
1298 },
1299 ];
1300
1301 let mut mock_user_repo = MockUserRepo::new();
1302 mock_user_repo.expect_get().returning(move |_| {
1303 Ok(UserEntity {
1304 user_id: None,
1305 username: USERNAME.to_string(),
1306 encrypted_password: Some(ENCRYPTED_WALLET_PASSWORD.clone()),
1307 salt: SALT.into(),
1308 is_kyc_verified: false,
1309 kyc_type: KycType::Undefined,
1310 viviswap_state: None,
1311 local_share: None,
1312 wallet_transactions: mixed_wallet_transactions.clone(),
1313 })
1314 });
1315
1316 let mixed_wallet_transactions_after_synchronization = vec![
1317 WalletTxInfo {
1318 date: "some date".to_string(),
1319 block_id: None,
1320 transaction_id: "some tx id".to_string(),
1321 receiver: String::new(),
1322 incoming: true,
1323 amount: 20.0,
1324 network_key: "IOTA".to_string(),
1325 status: format!("{:?}", InclusionState::Confirmed),
1326 explorer_url: None,
1327 },
1328 WalletTxInfo {
1329 date: "some date".to_string(),
1330 block_id: None,
1331 transaction_id: "1".to_string(),
1332 receiver: String::new(),
1333 incoming: true,
1334 amount: 1.0,
1335 network_key: "ETH".to_string(),
1336 status: format!("{:?}", InclusionState::Pending),
1337 explorer_url: None,
1338 },
1339 WalletTxInfo {
1340 date: "some date".to_string(),
1341 block_id: None,
1342 transaction_id: "2".to_string(),
1343 receiver: String::new(),
1344 incoming: true,
1345 amount: 2.0,
1346 network_key: "ETH".to_string(),
1347 status: format!("{:?}", InclusionState::Confirmed),
1348 explorer_url: None,
1349 },
1350 WalletTxInfo {
1351 date: "some date".to_string(),
1352 block_id: None,
1353 transaction_id: "3".to_string(),
1354 receiver: String::new(),
1355 incoming: true,
1356 amount: 3.0,
1357 network_key: "ETH".to_string(),
1358 status: format!("{:?}", InclusionState::Pending),
1359 explorer_url: None,
1360 },
1361 ];
1362
1363 mock_user_repo
1364 .expect_set_wallet_transactions()
1365 .once()
1366 .with(
1367 eq(USERNAME.to_string()),
1368 eq(mixed_wallet_transactions_after_synchronization.clone()),
1369 )
1370 .returning(|_, _| Ok(()));
1371
1372 sdk.repo = Some(Box::new(mock_user_repo));
1373
1374 let mut mock_wallet_manager = MockWalletManager::new();
1375 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _| {
1376 let mut mock_wallet_user = MockWalletUser::new();
1377 mock_wallet_user
1378 .expect_get_wallet_tx()
1379 .once()
1380 .with(eq(String::from("2"))) .returning(move |_| {
1382 Ok(WalletTxInfo {
1383 date: "some date".to_string(),
1384 block_id: None,
1385 transaction_id: "2".to_string(),
1386 receiver: String::new(),
1387 incoming: true,
1388 amount: 2.0,
1389 network_key: "ETH".to_string(),
1390 status: format!("{:?}", InclusionState::Confirmed), explorer_url: None,
1392 })
1393 });
1394 Ok(WalletBorrow::from(mock_wallet_user))
1395 });
1396
1397 sdk.active_user = Some(crate::types::users::ActiveUser {
1398 username: USERNAME.into(),
1399 wallet_manager: Box::new(mock_wallet_manager),
1400 });
1401
1402 sdk.set_networks(example_api_networks());
1403 sdk.set_network(ETH_NETWORK_KEY.to_string()).await.unwrap();
1404
1405 let response = sdk.get_wallet_tx_list(&PIN, 1, 1).await;
1413
1414 assert_eq!(
1416 response.unwrap(),
1417 WalletTxInfoList {
1418 transactions: vec![WalletTxInfo {
1419 date: "some date".to_string(),
1420 block_id: None,
1421 transaction_id: "2".to_string(),
1422 receiver: String::new(),
1423 incoming: true,
1424 amount: 2.0,
1425 network_key: "ETH".to_string(),
1426 status: format!("{:?}", InclusionState::Confirmed),
1427 explorer_url: None,
1428 }]
1429 }
1430 );
1431 }
1432
1433 #[tokio::test]
1434 async fn test_get_wallet_tx_list_does_not_query_network_for_transaction_state() {
1435 let (_srv, config, _cleanup) = set_config().await;
1437 let mut sdk = Sdk::new(config).unwrap();
1438
1439 let wallet_transactions = vec![WalletTxInfo {
1440 date: "some date".to_string(),
1441 block_id: None,
1442 transaction_id: "1".to_string(),
1443 receiver: String::new(),
1444 incoming: true,
1445 amount: 1.0,
1446 network_key: "ETH".to_string(),
1447 status: format!("{:?}", InclusionState::Confirmed),
1448 explorer_url: None,
1449 }];
1450
1451 let mut mock_user_repo = MockUserRepo::new();
1452 mock_user_repo.expect_get().returning(move |_| {
1453 Ok(UserEntity {
1454 user_id: None,
1455 username: USERNAME.to_string(),
1456 encrypted_password: Some(ENCRYPTED_WALLET_PASSWORD.clone()),
1457 salt: SALT.into(),
1458 is_kyc_verified: false,
1459 kyc_type: KycType::Undefined,
1460 viviswap_state: None,
1461 local_share: None,
1462 wallet_transactions: wallet_transactions.clone(),
1463 })
1464 });
1465
1466 mock_user_repo
1467 .expect_set_wallet_transactions()
1468 .once()
1469 .returning(|_, _| Ok(()));
1470
1471 sdk.repo = Some(Box::new(mock_user_repo));
1472
1473 let mut mock_wallet_manager = MockWalletManager::new();
1474 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _| {
1475 let mut mock_wallet_user = MockWalletUser::new();
1476 mock_wallet_user.expect_get_wallet_tx().never();
1477 Ok(WalletBorrow::from(mock_wallet_user))
1478 });
1479
1480 sdk.active_user = Some(crate::types::users::ActiveUser {
1481 username: USERNAME.into(),
1482 wallet_manager: Box::new(mock_wallet_manager),
1483 });
1484
1485 sdk.set_networks(example_api_networks());
1486 sdk.set_network(ETH_NETWORK_KEY.to_string()).await.unwrap();
1487
1488 let response = sdk.get_wallet_tx_list(&PIN, 0, 1).await;
1490
1491 assert!(response.is_ok())
1493 }
1494}