etopay_sdk/core/
wallet.rs

1//! This module provides methods for initializing, verifying, deleting, and creating wallets, as well as
2//! migrating wallets from mnemonic or backup, creating backups, and verifying PINs.
3//!
4//! It also includes various helper functions and imports required for the wallet functionality.
5
6use 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    /// Create and store a wallet from a new random mnemonic
22    ///
23    /// # Arguments
24    ///
25    /// * `pin` - The PIN for the wallet.
26    ///
27    /// # Returns
28    ///
29    /// The new random mnemonic.
30    ///
31    /// # Errors
32    ///
33    /// * [`crate::Error::MissingConfig`] - If the sdk config is missing.
34    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
35    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
36    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    /// Create and store a wallet from an existing mnemonic
57    ///
58    /// # Arguments
59    ///
60    /// * `pin` - The PIN for the wallet.
61    /// * `mnemonic` - The mnemonic to use for the wallet.
62    ///
63    /// # Errors
64    ///
65    /// * [`crate::Error::MissingConfig`] - If the sdk config is missing.
66    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
67    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
68    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    /// Create and store a wallet from an existing kdbx backup file
89    ///
90    /// # Arguments
91    ///
92    /// * `pin` - The PIN for the wallet.
93    /// * `backup` - The bytes representing the backup file.
94    /// * `backup_password` - The password used when creating the backup file.
95    ///
96    /// # Errors
97    ///
98    /// * [`crate::Error::MissingConfig`] - If the sdk config is missing.
99    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
100    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
101    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    /// Create a kdbx wallet backup from an existing wallet.
127    ///
128    /// # Arguments
129    ///
130    /// * `pin` - The PIN for the wallet.
131    /// * `backup_password` - The password to use when creating the backup file.
132    ///
133    /// # Returns
134    ///
135    /// The bytes of the kdbx backup file.
136    ///
137    /// # Errors
138    ///
139    /// * [`crate::Error::MissingConfig`] - If the sdk config is missing.
140    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
141    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
142    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    /// Verify the mnemonic by checking if the mnemonic is the same as the one in the shares
167    ///
168    /// # Arguments
169    ///
170    /// * `pin` - The PIN for the wallet.
171    /// * `mnemonic` - The mnemonic to verify.
172    ///
173    /// # Returns
174    ///
175    /// Returns `Ok(true)` if the mnemonic is successfully verified, otherwise returns `Ok(false)`,
176    /// or an `Error`.
177    ///
178    /// # Errors
179    ///
180    /// * [`crate::Error::MissingConfig`] - If the sdk config is missing.
181    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
182    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
183    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    /// Delete the currently active wallet
204    ///
205    /// Deletes the currently active wallet, potentially resulting in loss of funds if the mnemonic or wallet is not backed up.
206    ///
207    /// # Returns
208    ///
209    /// Returns `Ok(())` if the wallet is successfully deleted, otherwise returns an `Error`.
210    ///
211    /// # Errors
212    ///
213    /// * [`crate::Error::MissingConfig`] - If the sdk config is missing.
214    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
215    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
216    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    /// Verify pin
239    ///
240    /// Verifies the pin for the wallet.
241    ///
242    /// # Arguments
243    ///
244    /// * `pin` - The pin to verify.
245    ///
246    /// # Returns
247    ///
248    /// Returns `Ok(())` if the pin is verified successfully, otherwise returns an `Error`.
249    ///
250    /// # Errors
251    ///
252    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
253    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
254    /// * [`WalletError::WalletNotInitialized`] - If there is an error initializing the wallet.
255    /// * [`WalletError::WrongPinOrPassword`] - If the pin or password is incorrect.
256    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        // Ensure encrypted password exists in user
269        let Some(encrypted_password) = user.encrypted_password else {
270            return Err(WalletError::WalletNotInitialized(ErrorKind::MissingPassword))?;
271        };
272
273        // Decrypt the password using the provided PIN
274        if encrypted_password.decrypt(pin, &user.salt).is_err() {
275            return Err(WalletError::WrongPinOrPassword)?;
276        }
277        Ok(())
278    }
279
280    /// Reset pin
281    ///
282    /// Resets the pin for the wallet using the provided password and new pin.
283    ///
284    /// # Arguments
285    ///
286    /// * `old_pin` - The old wallet pin.
287    /// * `new_pin` - The new pin to set for the wallet.
288    ///
289    /// # Returns
290    ///
291    /// Returns `Ok(())` if the pin is changed successfully, otherwise returns an `Error`.
292    ///
293    /// # Errors
294    ///
295    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
296    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
297    /// * [`WalletError::WalletNotInitialized`] - If there is an error initializing the wallet.
298    /// * [`WalletError::WrongPinOrPassword`] - If the pin or password is incorrect.
299    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        // decrypt the password
316        let password = encrypted_password.decrypt(old_pin, &user.salt)?;
317
318        // Set new pin and encrypted password
319        let salt = EncryptionSalt::generate();
320        let encrypted_password = password.encrypt(new_pin, &salt)?;
321
322        // Update user
323        user.salt = salt;
324        user.encrypted_password = Some(encrypted_password);
325        repo.update(&user)?;
326
327        Ok(())
328    }
329
330    /// Set the password to use for wallet operations. If the password was already set, this changes it.
331    ///
332    /// # Arguments
333    ///
334    /// * `pin` - The pin to encrypt the password with.
335    /// * `new_password` - The new password to set for the wallet.
336    ///
337    /// # Returns
338    ///
339    /// Returns `Ok(())` if the password is set successfully, otherwise returns an `Error`.
340    ///
341    /// # Errors
342    ///
343    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
344    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
345    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 password already exists, return an error!
358        if let Some(encrypted_password) = user.encrypted_password {
359            info!("Password exists, changing password");
360
361            // verify that the pin is correct by decrypting the password using the provided PIN
362            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            // Set new pin and encrypted password
374            let salt = EncryptionSalt::generate();
375            let encrypted_password = new_password.encrypt(pin, &salt)?;
376
377            // Update user
378            user.salt = salt;
379            user.encrypted_password = Some(encrypted_password);
380            repo.update(&user)?;
381        }
382
383        Ok(())
384    }
385
386    /// Check if the password to use for wallet operations is set. If this returns `false`,
387    /// the password should be set with [`set_wallet_password`], otherwise you need to use
388    /// [`change_password`] to change it.
389    ///
390    /// # Returns
391    ///
392    /// Returns `Ok(true)` if the password is set successfully, otherwise returns `Ok(false)`.
393    ///
394    /// # Errors
395    ///
396    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
397    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
398    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    /// Generates a new receiver address (based on selected currency in the config) for the wallet.
414    ///
415    /// # Returns
416    ///
417    /// Returns the generated address as a `String` if successful, otherwise returns an `Error`.
418    ///
419    /// # Errors
420    ///
421    /// * [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
422    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
423    /// * [`crate::Error::MissingConfig`] - If the sdk config is missing.
424    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 there is an access token, push the generated address to the backend
443        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    /// Get the balance of the user
453    ///
454    /// Fetches the balance of the user from the wallet.
455    ///
456    /// # Returns
457    ///
458    /// Returns the balance as a `f64` if successful, otherwise returns an `Error`.
459    ///
460    /// # Errors
461    ///
462    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
463    /// * [`WalletError::WalletNotInitialized`] - If there is an error initializing the wallet.
464    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    /// wallet transaction list
474    ///
475    /// Returns paginated list of wallet transaction list.
476    ///
477    /// # Arguments
478    ///
479    /// * `start` - The starting index of transactions to fetch.
480    /// * `limit` - The number of transactions per page.
481    ///
482    /// # Returns
483    ///
484    /// Returns a `WalletTxInfoList` containing paginated history of wallet transactions if the outputs are claimed successfully, otherwise returns an `Error`.
485    ///
486    /// # Errors
487    ///
488    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
489    /// * [`WalletError::WalletNotInitialized`] - If there is an error initializing the wallet.
490    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                // We retrieve the transaction list from the wallet,
512                // then synchronize selected transactions (by fetching their current status from the network),
513                // and finally, save the refreshed list back to the wallet
514                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                    // We don't need to query the network for the state of this transaction,
523                    // because it has already been synchronized earlier (as indicated by `InclusionState::Confirmed`).
524                    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                            // On error, return historical (cached) transaction data
533                            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    /// wallet transaction
569    ///
570    /// Returns the wallet transaction details.
571    ///
572    /// # Arguments
573    ///
574    /// * `tx_id` - The transaction id of particular transaction.
575    ///
576    /// # Returns
577    ///
578    /// Returns `WalletTxInfo` detailed report of particular wallet transaction if the outputs are claimed successfully, otherwise returns an `Error`.
579    ///
580    /// # Errors
581    ///
582    /// * [`crate::Error::UserNotInitialized`] - If there is an error initializing the user.
583    /// * [`WalletError::WalletNotInitialized`] - If there is an error initializing the wallet.
584    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        // Arrange
629        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        // Act
651        let response = sdk.create_wallet_from_new_mnemonic(&PIN).await;
652
653        // Assert
654        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        // Arrange
672        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        // Act
694        let response = sdk.create_wallet_from_existing_mnemonic(&PIN, MNEMONIC).await;
695
696        // Assert
697        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        // Arrange
713        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        // Act
735        let response = sdk.create_wallet_backup(&PIN, &WALLET_PASSWORD).await;
736
737        // Assert
738        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        // Arrange
756        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        // Act
778        let response = sdk.create_wallet_from_backup(&PIN, BACKUP, &WALLET_PASSWORD).await;
779
780        // Assert
781        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        // Arrange
797        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        // Act
819        let response = sdk.verify_mnemonic(&PIN, MNEMONIC).await;
820
821        // Assert
822        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        // Arrange
840        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        // Act
871        let response = sdk.delete_wallet(&PIN).await;
872
873        // Assert
874        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        // Arrange
892        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        // Act
911        let response = sdk.verify_pin(&PIN).await;
912
913        // Assert
914        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        // Arrange
932        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        // Act
952        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        // Assert
956        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        // Arrange
971        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        // Act
1005        let response = sdk.set_wallet_password(&PIN, &WALLET_PASSWORD).await;
1006
1007        // Assert
1008        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        // Arrange
1024        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        // Act
1074        let response = sdk.generate_new_address(&PIN).await;
1075
1076        // Assert
1077        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    // SAFETY: we know that this value is not negative
1092    #[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        // Arrange
1099        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                        // SAFETY: we know that this value is not negative
1114                        .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        // Act
1130        let response = sdk.get_balance(&PIN).await;
1131
1132        // Assert
1133        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        // Arrange
1151        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        // Act
1181        let response = sdk.get_wallet_tx(&PIN, TX_INDEX).await;
1182
1183        // Assert
1184        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        // Arrange
1202        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        // Act
1233        let response = sdk.get_wallet_tx_list(&PIN, 0, 10).await;
1234
1235        // Assert
1236        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        // Arrange
1249        let (_srv, config, _cleanup) = set_config().await;
1250        let mut sdk = Sdk::new(config).unwrap();
1251
1252        // During the test, we expect the status of WalletTxInfo with transaction_id = 2
1253        // to transition from 'Pending' to 'Confirmed' after synchronization
1254        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), // this one
1286                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"))) // WalletTxInfo.transaction_id = 2
1381                .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), // Pending -> Confirmed
1391                        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        // Act
1406
1407        // We request a single WalletTxInfo using get_wallet_tx_list(start = 1, limit = 1)
1408        // We have stored transactions: [1 IOTA, 3 ETH]
1409        // The network key is ETH, so we search through the 3 ETH transactions
1410        // We select this one:
1411        // [WalletTxInfo{ ... }, -> WalletTxInfo{ transaction_id = 2 }, WalletTxInfo{ ... }]
1412        let response = sdk.get_wallet_tx_list(&PIN, 1, 1).await;
1413
1414        // Assert
1415        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        // Arrange
1436        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        // Act
1489        let response = sdk.get_wallet_tx_list(&PIN, 0, 1).await;
1490
1491        // Assert
1492        assert!(response.is_ok())
1493    }
1494}