diff options
Diffstat (limited to 'impls/monero.rs/src/lib.rs')
| -rw-r--r-- | impls/monero.rs/src/lib.rs | 2039 |
1 files changed, 2039 insertions, 0 deletions
diff --git a/impls/monero.rs/src/lib.rs b/impls/monero.rs/src/lib.rs new file mode 100644 index 0000000..56373da --- /dev/null +++ b/impls/monero.rs/src/lib.rs @@ -0,0 +1,2039 @@ +use std::ffi::{CStr, CString}; +use std::os::raw::{c_int, c_void}; +use std::ptr::NonNull; +use std::sync::Arc; + +pub mod bindings; +pub use bindings::WalletStatus_Ok; +pub use bindings::WalletStatus_Error; +pub use bindings::WalletStatus_Critical; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NetworkType { + Mainnet = bindings::NetworkType_MAINNET as isize, + Testnet = bindings::NetworkType_TESTNET as isize, + Stagenet = bindings::NetworkType_STAGENET as isize, +} + +impl NetworkType { + pub fn from_c_int(value: c_int) -> Option<Self> { + match value { + bindings::NetworkType_MAINNET => Some(NetworkType::Mainnet), + bindings::NetworkType_TESTNET => Some(NetworkType::Testnet), + bindings::NetworkType_STAGENET => Some(NetworkType::Stagenet), + _ => None, + } + } + + pub fn to_c_int(self) -> c_int { + self as c_int + } +} + +#[derive(Debug)] +pub enum WalletError { + NullPointer, + FfiError(String), + WalletErrorCode(c_int, String), +} + +pub type WalletResult<T> = Result<T, WalletError>; + +#[derive(Debug)] +pub struct Account { + pub index: u32, + pub label: String, + pub balance: u64, + pub unlocked_balance: u64, +} + +#[derive(Debug)] +pub struct GetAccounts { + pub accounts: Vec<Account>, +} + +pub struct Wallet { + pub ptr: NonNull<c_void>, + pub manager: Arc<WalletManager>, + pub is_closed: bool, +} + +pub struct WalletManager { + ptr: NonNull<c_void>, +} + +/// Configuration parameters for initializing a wallet. +#[derive(Debug, Clone)] +pub struct WalletConfig { + pub daemon_address: String, + pub upper_transaction_size_limit: u64, + pub daemon_username: String, + pub daemon_password: String, + pub use_ssl: bool, + pub light_wallet: bool, + pub proxy_address: String, +} + +impl Default for WalletConfig { + fn default() -> Self { + WalletConfig { + daemon_address: "localhost:18081".to_string(), + upper_transaction_size_limit: 10000, // TODO: set sane value. + daemon_username: "".to_string(), + daemon_password: "".to_string(), + use_ssl: false, + light_wallet: false, + proxy_address: "".to_string(), + } + } +} + +pub type BlockHeight = u64; + +#[derive(Debug)] +pub struct Refreshed; + +/// Represents a destination address and the amount to send. +#[derive(Debug, Clone)] +pub struct Destination { + /// The recipient's address. + pub address: String, + /// The amount to send to the recipient (in atomic units). + pub amount: u64, +} + +/// Represents the result of a transfer operation. +#[derive(Debug)] +pub struct Transfer { + /// The transaction ID of the transfer. + pub txid: String, + /// The transaction key, if requested. + pub tx_key: Option<String>, + /// The total amount sent in the transfer. + pub amount: u64, + /// The fee associated with the transfer. + pub fee: u64, +} + +/// Represents the result of checking a transaction key against a transaction ID and address. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CheckTxKey { + /// Indicates whether the transaction key is valid. + pub valid: bool, + /// An optional error message providing details if the verification fails. + pub error: Option<String>, +} + +impl WalletManager { + /// Creates a new `WalletManager`. + /// + /// # Example + /// + /// ``` + /// use monero_c_rust::WalletManager; + /// let manager = WalletManager::new(); + /// assert!(manager.is_ok()); + /// ``` + pub fn new() -> WalletResult<Arc<Self>> { + unsafe { + let ptr = bindings::MONERO_WalletManagerFactory_getWalletManager(); + let ptr = NonNull::new(ptr).ok_or(WalletError::NullPointer)?; + Ok(Arc::new(WalletManager { ptr })) + } + } + + /// Check the status of a wallet to ensure it's in a valid state. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let wallet_result = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet); + /// assert!(wallet_result.is_ok(), "Failed to create wallet: {:?}", wallet_result.err()); + /// let wallet = wallet_result.unwrap(); + /// + /// // Check the status of the wallet, expecting OK + /// let status_result = manager.get_status(wallet.ptr.as_ptr()); + /// assert!(status_result.is_ok(), "Failed to get status: {:?}", status_result.err()); + /// assert_eq!(status_result.unwrap(), (), "Expected status to be OK"); + /// + /// // Clean up wallet files. + /// std::fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// std::fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn get_status(&self, wallet_ptr: *mut c_void) -> WalletResult<()> { + if wallet_ptr.is_null() { + return Err(WalletError::NullPointer); // Ensure NullPointer is returned for null wallet + } + + unsafe { + let status = bindings::MONERO_Wallet_status(wallet_ptr); + + if status == bindings::WalletStatus_Ok { + Ok(()) + } else { + let error_ptr = bindings::MONERO_Wallet_errorString(wallet_ptr); + let error_msg = if error_ptr.is_null() { + "Unknown error".to_string() + } else { + CStr::from_ptr(error_ptr).to_string_lossy().into_owned() + }; + Err(WalletError::WalletErrorCode(status, error_msg)) + } + } + } + + /// Check the status of a wallet and throw an error if an issue is found. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let wallet_result = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet); + /// assert!(wallet_result.is_ok(), "Failed to create wallet: {:?}", wallet_result.err()); + /// let wallet = wallet_result.unwrap(); + /// + /// // Check the status of the wallet, expecting OK + /// let status_result = manager.throw_if_error(wallet.ptr.as_ptr()); + /// assert!(status_result.is_ok(), "Failed to get status: {:?}", status_result.err()); + /// + /// // Clean up wallet files. + /// std::fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// std::fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn throw_if_error(&self, wallet_ptr: *mut c_void) -> WalletResult<()> { + if wallet_ptr.is_null() { + return Err(WalletError::NullPointer); + } + + unsafe { + let status = bindings::MONERO_Wallet_status(wallet_ptr); + if status == bindings::WalletStatus_Ok { + Ok(()) + } else { + let error_ptr = bindings::MONERO_Wallet_errorString(wallet_ptr); + let error_msg = if error_ptr.is_null() { + "Unknown error".to_string() + } else { + CStr::from_ptr(error_ptr).to_string_lossy().into_owned() + }; + Err(WalletError::WalletErrorCode(status, error_msg)) + } + } + } + + /// Create a new wallet. + /// + /// Generates a new wallet with the provided path, password, language, and network type, with a + /// new mnemonic seed generated in the specified language. + /// + /// # Example + /// + /// ``` + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use std::fs; + /// use std::path::Path; + /// use tempfile::TempDir; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet); + /// assert!(wallet.is_ok()); + /// + /// // Clean up wallet files. + /// std::fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// std::fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn create_wallet( + self: &Arc<Self>, + path: &str, + password: &str, + language: &str, + network_type: NetworkType, + ) -> WalletResult<Wallet> { + let c_path = CString::new(path).map_err(|_| WalletError::FfiError("Invalid path".to_string()))?; + let c_password = CString::new(password).map_err(|_| WalletError::FfiError("Invalid password".to_string()))?; + let c_language = CString::new(language).map_err(|_| WalletError::FfiError("Invalid language".to_string()))?; + + unsafe { + let wallet_ptr = bindings::MONERO_WalletManager_createWallet( + self.ptr.as_ptr(), + c_path.as_ptr(), + c_password.as_ptr(), + c_language.as_ptr(), + network_type.to_c_int(), + ); + + self.throw_if_error(wallet_ptr)?; + if wallet_ptr.is_null() { + return Err(WalletError::NullPointer); + } + + Ok(Wallet { + ptr: NonNull::new(wallet_ptr).unwrap(), + manager: Arc::clone(self), + is_closed: false, + }) + } + } + + /// Restores a wallet from a mnemonic seed. + /// + /// This method restores a Monero wallet using the provided mnemonic seed, network type, and other + /// configuration parameters. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// use std::fs; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("restored_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap().to_string(); + /// + /// let manager = WalletManager::new().expect("Failed to create WalletManager"); + /// + /// let mnemonic = "hemlock jubilee eden hacksaw boil superior inroads epoxy exhale orders cavernous second brunt saved richly lower upgrade hitched launching deepest mostly playful layout lower eden".to_string(); + /// let wallet = manager.restore_mnemonic( + /// wallet_str, + /// "strong_password".to_string(), + /// mnemonic, + /// NetworkType::Mainnet, + /// 0, // Restore from the beginning of the blockchain. + /// 1, // Default KDF rounds. + /// "".to_string(), // No seed offset. + /// ).expect("Failed to restore wallet"); + /// + /// // Use the wallet as needed... + /// + /// // Clean up wallet files. + /// fs::remove_file(&wallet_path).expect("Failed to delete restored wallet"); + /// fs::remove_file(format!("{}.keys", wallet_path.display())).expect("Failed to delete restored wallet keys"); + /// ``` + pub fn restore_mnemonic( + self: &Arc<Self>, + path: String, + password: String, + seed: String, + network_type: NetworkType, + restore_height: u64, + kdf_rounds: u64, + seed_offset: String, // TODO: Make an Option. + ) -> WalletResult<Wallet> { + // Convert Rust strings to C-compatible strings. + let c_path = CString::new(path) + .map_err(|_| WalletError::FfiError("Invalid path string".to_string()))?; + let c_password = CString::new(password) + .map_err(|_| WalletError::FfiError("Invalid password string".to_string()))?; + let c_seed = CString::new(seed) + .map_err(|_| WalletError::FfiError("Invalid seed string".to_string()))?; + let c_seed_offset = CString::new(seed_offset) + .map_err(|_| WalletError::FfiError("Invalid seed_offset string".to_string()))?; + + unsafe { + let wallet_ptr = bindings::MONERO_WalletManager_recoveryWallet( + self.ptr.as_ptr(), + c_path.as_ptr(), + c_password.as_ptr(), + c_seed.as_ptr(), + network_type.to_c_int(), + restore_height, + kdf_rounds, + c_seed_offset.as_ptr(), + ); + + // Check for errors using the returned wallet pointer. + self.throw_if_error(wallet_ptr)?; + if wallet_ptr.is_null() { + return Err(WalletError::NullPointer); + } + + Ok(Wallet { + ptr: NonNull::new(wallet_ptr).unwrap(), + manager: Arc::clone(self), + is_closed: false, + }) + } + } + + /// Restores a wallet from a polyseed. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// use std::fs; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap().to_string(); + /// + /// let manager = WalletManager::new().expect("Failed to create WalletManager"); + /// + /// let polyseed = "capital chief route liar question fix clutch water outside pave hamster occur always learn license knife".to_string(); + /// let restored_wallet = manager.restore_polyseed( + /// wallet_str.clone(), + /// "password".to_string(), + /// polyseed, + /// NetworkType::Mainnet, + /// 0, // Restore from the beginning of the blockchain. + /// 1, // Default KDF rounds. + /// "".to_string(), // No seed offset. + /// true, // Create a new wallet. + /// ).expect("Failed to restore wallet from polyseed"); + /// + /// // Use the restored_wallet as needed... + /// + /// // Clean up wallet files. + /// fs::remove_file(&wallet_path).expect("Failed to delete restored wallet"); + /// fs::remove_file(format!("{}.keys", wallet_path.display())).expect("Failed to delete restored wallet keys"); + /// ``` + pub fn restore_polyseed( + self: &Arc<Self>, + path: String, + password: String, + polyseed: String, + network_type: NetworkType, + restore_height: u64, + kdf_rounds: u64, + seed_offset: String, + new_wallet: bool, + ) -> WalletResult<Wallet> { + // Convert Rust strings to C-compatible strings. + let c_path = CString::new(path) + .map_err(|_| WalletError::FfiError("Invalid path string".to_string()))?; + let c_password = CString::new(password) + .map_err(|_| WalletError::FfiError("Invalid password string".to_string()))?; + let c_polyseed = CString::new(polyseed) + .map_err(|_| WalletError::FfiError("Invalid mnemonic string".to_string()))?; + let c_seed_offset = CString::new(seed_offset) + .map_err(|_| WalletError::FfiError("Invalid seed offset string".to_string()))?; + + unsafe { + let wallet_ptr = bindings::MONERO_WalletManager_createWalletFromPolyseed( + self.ptr.as_ptr(), + c_path.as_ptr(), + c_password.as_ptr(), + network_type.to_c_int(), + c_polyseed.as_ptr(), + c_seed_offset.as_ptr(), + new_wallet, + restore_height, + kdf_rounds, + ); + + // Check for errors using the returned wallet pointer. + self.throw_if_error(wallet_ptr)?; + if wallet_ptr.is_null() { + return Err(WalletError::NullPointer); + } + + Ok(Wallet { + ptr: NonNull::new(wallet_ptr).unwrap(), + manager: Arc::clone(self), + is_closed: false, + }) + } + } + + /// Generates a wallet from provided keys. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use std::fs; + /// use std::path::Path; + /// use tempfile::TempDir; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let wallet = manager.generate_from_keys( + /// wallet_str.to_string(), + /// "45wsWad9EwZgF3VpxQumrUCRaEtdyyh6NG8sVD3YRVVJbK1jkpJ3zq8WHLijVzodQ22LxwkdWx7fS2a6JzaRGzkNU8K2Dhi".to_string(), + /// "".to_string(), // Spend key optional: you can pass either the spend key or the view key. + /// "3bc0b202cde92fe5719c3cc0a16aa94f88a5d19f8c515d4e35fae361f6f2120e".to_string(), + /// 0, // Restore from the beginning of the blockchain. + /// "password".to_string(), + /// "English".to_string(), + /// NetworkType::Mainnet, + /// 1, // Default KDF rounds. + /// ); + /// assert!(wallet.is_ok(), "Failed to generate wallet from keys: {:?}", wallet.err()); + /// + /// // Clean up wallet files. + /// std::fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// std::fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn generate_from_keys( + self: &Arc<Self>, + filename: String, + address: String, + spendkey: String, + viewkey: String, + restore_height: u64, + password: String, + language: String, + network_type: NetworkType, + kdf_rounds: u64, + ) -> WalletResult<Wallet> { + let c_filename = CString::new(filename) + .map_err(|_| WalletError::FfiError("Invalid filename".to_string()))?; + let c_password = CString::new(password) + .map_err(|_| WalletError::FfiError("Invalid password".to_string()))?; + let c_language = CString::new(language) + .map_err(|_| WalletError::FfiError("Invalid language".to_string()))?; + let c_address = CString::new(address) + .map_err(|_| WalletError::FfiError("Invalid address".to_string()))?; + let c_spendkey = CString::new(spendkey) + .map_err(|_| WalletError::FfiError("Invalid spendkey".to_string()))?; + let c_viewkey = CString::new(viewkey) + .map_err(|_| WalletError::FfiError("Invalid viewkey".to_string()))?; + + unsafe { + let wallet_ptr = bindings::MONERO_WalletManager_createWalletFromKeys( + self.ptr.as_ptr(), + c_filename.as_ptr(), + c_password.as_ptr(), + c_language.as_ptr(), + network_type.to_c_int(), + restore_height, + c_address.as_ptr(), + c_viewkey.as_ptr(), + c_spendkey.as_ptr(), + kdf_rounds, + ); + + if wallet_ptr.is_null() { + return Err(WalletError::NullPointer); + } + + self.throw_if_error(wallet_ptr)?; + + Ok(Wallet { + ptr: NonNull::new(wallet_ptr).unwrap(), + manager: Arc::clone(self), + is_closed: false, + }) + } + } + + /// Opens an existing wallet with the provided path, password, and network type. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// use std::fs; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// + /// // First, create a wallet to open later. + /// let wallet_result = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet); + /// assert!(wallet_result.is_ok(), "Failed to create wallet: {:?}", wallet_result.err()); + /// let wallet = wallet_result.unwrap(); + /// + /// // Close the wallet by dropping it. + /// drop(wallet); + /// + /// // Now try to open the existing wallet. + /// let open_result = manager.open_wallet(wallet_str, "password", NetworkType::Mainnet); + /// assert!(open_result.is_ok(), "Failed to open wallet: {:?}", open_result.err()); + /// let opened_wallet = open_result.unwrap(); + /// + /// // Clean up wallet files. + /// fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn open_wallet( + self: &Arc<Self>, + path: &str, + password: &str, + network_type: NetworkType, + ) -> WalletResult<Wallet> { + let c_path = CString::new(path).map_err(|_| WalletError::FfiError("Invalid path".to_string()))?; + let c_password = CString::new(password).map_err(|_| WalletError::FfiError("Invalid password".to_string()))?; + + unsafe { + let wallet_ptr = bindings::MONERO_WalletManager_openWallet( + self.ptr.as_ptr(), + c_path.as_ptr(), + c_password.as_ptr(), + network_type.to_c_int(), + ); + + self.throw_if_error(wallet_ptr)?; + if wallet_ptr.is_null() { + Err(self.get_status(wallet_ptr).unwrap_err()) + } else { + // Ensuring that we properly close the wallet when it's no longer needed + let wallet = Wallet { + ptr: NonNull::new(wallet_ptr).unwrap(), + manager: Arc::clone(self), + is_closed: false, + }; + Ok(wallet) + } + } + } + + /// Retrieves the current blockchain height. + /// + /// This method communicates with the connected daemon to obtain the latest + /// blockchain height. It returns a `BlockHeight` on success or a `WalletError` on failure. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType}; + /// + /// let manager = WalletManager::new().unwrap(); + /// let height = manager.get_height().unwrap(); + /// println!("Current blockchain height: {}", height); + /// ``` + pub fn get_height(&self) -> WalletResult<BlockHeight> { + unsafe { + let height = bindings::MONERO_WalletManager_blockchainHeight(self.ptr.as_ptr()); + // Assuming the FFI call does not set an error, directly return the height. + // If error handling is required, additional checks should be implemented here. + Ok(height) + } + } +} + +impl Wallet { + /// Retrieves the wallet's seed with an optional offset. + /// + /// # Example + /// + /// ``` + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// use std::fs; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let wallet_result = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet); + /// assert!(wallet_result.is_ok(), "Failed to create wallet: {:?}", wallet_result.err()); + /// let wallet = wallet_result.unwrap(); + /// + /// // Get seed with no offset + /// let seed = wallet.get_seed(None); + /// assert!(seed.is_ok(), "Failed to get seed: {:?}", seed.err()); + /// let seed = seed.unwrap(); + /// assert!(!seed.is_empty(), "Seed should not be empty"); + /// + /// // Get seed with an offset + /// let seed_with_offset = wallet.get_seed(Some("offset")); + /// assert!(seed_with_offset.is_ok(), "Failed to get seed with offset: {:?}", seed_with_offset.err()); + /// let seed_with_offset = seed_with_offset.unwrap(); + /// assert!(!seed_with_offset.is_empty(), "Seed with offset should not be empty"); + /// + /// // Clean up wallet files. + /// fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn get_seed(&self, seed_offset: Option<&str>) -> WalletResult<String> { + let c_seed_offset = CString::new(seed_offset.unwrap_or("")) + .map_err(|_| WalletError::FfiError("Invalid seed_offset".to_string()))?; + + unsafe { + let seed_ptr = bindings::MONERO_Wallet_seed(self.ptr.as_ptr(), c_seed_offset.as_ptr()); + + self.throw_if_error()?; + if seed_ptr.is_null() { + return Err(self.get_last_error()); + } + + let seed = CStr::from_ptr(seed_ptr).to_string_lossy().into_owned(); + if seed.is_empty() { + return Err(WalletError::FfiError("Received empty seed".to_string())); + } + + Ok(seed) + } + } + + /// Retrieves the wallet's address for the given account and address index. + /// + /// # Example + /// + /// ``` + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// use std::fs; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet).unwrap(); + /// let address = wallet.get_address(0, 0); + /// assert!(address.is_ok(), "Failed to get address: {:?}", address.err()); + /// + /// // Clean up wallet files. + /// fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn get_address(&self, account_index: u64, address_index: u64) -> WalletResult<String> { + unsafe { + let address_ptr = bindings::MONERO_Wallet_address(self.ptr.as_ptr(), account_index, address_index); + + self.throw_if_error()?; + if address_ptr.is_null() { + Err(self.get_last_error()) + } else { + let address = CStr::from_ptr(address_ptr) + .to_string_lossy() + .into_owned(); + Ok(address) + } + } + } + + /// Checks if the wallet is deterministic. + /// + /// # Example + /// + /// ``` + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// use std::fs; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let wallet_result = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet); + /// assert!(wallet_result.is_ok(), "Failed to create wallet: {:?}", wallet_result.err()); + /// let wallet = wallet_result.unwrap(); + /// let is_deterministic = wallet.is_deterministic(); + /// assert!(is_deterministic.is_ok(), "Failed to check if wallet is deterministic: {:?}", is_deterministic.err()); + /// assert!(is_deterministic.unwrap(), "Wallet should be deterministic"); + /// + /// // Clean up wallet files. + /// fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn is_deterministic(&self) -> WalletResult<bool> { + unsafe { + let result = bindings::MONERO_Wallet_isDeterministic(self.ptr.as_ptr()); + + self.throw_if_error()?; + Ok(result) + } + } + + /// Retrieves the last error from the wallet. + /// + /// # Example + /// + /// ``` + /// use monero_c_rust::{WalletManager, NetworkType, WalletError}; + /// let manager = WalletManager::new().unwrap(); + /// // Intentionally pass an invalid wallet to force an error. + /// let invalid_wallet = manager.create_wallet("", "", "", NetworkType::Mainnet); + /// if let Err(err) = invalid_wallet { + /// if let WalletError::WalletErrorCode(_, error_msg) = err { + /// // Check that an error message was produced + /// assert!(!error_msg.is_empty(), "Error message should not be empty"); + /// } + /// } + /// ``` + pub fn get_last_error(&self) -> WalletError { + unsafe { + let error_ptr = bindings::MONERO_Wallet_errorString(self.ptr.as_ptr()); + let status = bindings::MONERO_Wallet_status(self.ptr.as_ptr()); + + let error_msg = if error_ptr.is_null() { + "Unknown error".to_string() + } else { + CStr::from_ptr(error_ptr) + .to_string_lossy() + .into_owned() + }; + + WalletError::WalletErrorCode(status, error_msg) + } + } + + /// Checks for any errors by inspecting the wallet status and throws an error if found. + /// + /// # Returns + /// - `Ok(())` if no error is found. + /// - `Err(WalletError)` if an error is encountered. + pub fn throw_if_error(&self) -> WalletResult<()> { + let status_result = self.manager.get_status(self.ptr.as_ptr()); + if status_result.is_err() { + return status_result; // Return the error if the status is not OK. + } + Ok(()) + } + + /// Retrieves the balance and unlocked balance for the given account index. + /// + /// # Example + /// + /// ``` + /// use monero_c_rust::{WalletManager, NetworkType, WalletResult}; + /// use tempfile::TempDir; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let _wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet).unwrap(); + /// + /// let balance = _wallet.get_balance(0); + /// assert!(balance.is_ok(), "Failed to get balance: {:?}", balance.err()); + /// + /// // Clean up wallet files. + /// std::fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// std::fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn get_balance(&self, account_index: u32) -> WalletResult<GetBalance> { + unsafe { + let balance = bindings::MONERO_Wallet_balance(self.ptr.as_ptr(), account_index); + + self.throw_if_error()?; + let unlocked_balance = bindings::MONERO_Wallet_unlockedBalance(self.ptr.as_ptr(), account_index); + + self.throw_if_error()?; + Ok(GetBalance { balance, unlocked_balance }) + } + } + + /// Creates a new subaddress account with the given label. + /// + /// # Example + /// + /// ``` + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// use std::fs; + /// + /// // Set up the test environment. + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// // Initialize the wallet manager and create a wallet. + /// let manager = WalletManager::new().unwrap(); + /// let wallet_result = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet); + /// assert!(wallet_result.is_ok(), "Failed to create wallet: {:?}", wallet_result.err()); + /// let wallet = wallet_result.unwrap(); + /// + /// // Create a new account with a label. + /// let result = wallet.create_account("New Account"); + /// assert!(result.is_ok(), "Failed to create account: {:?}", result.err()); + /// + /// // Clean up wallet files. + /// fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn create_account(&self, label: &str) -> WalletResult<()> { + let c_label = CString::new(label).map_err(|_| WalletError::FfiError("Invalid label".to_string()))?; + + unsafe { + bindings::MONERO_Wallet_addSubaddressAccount(self.ptr.as_ptr(), c_label.as_ptr()); + self.throw_if_error() + } + } + + /// Retrieves all accounts associated with the wallet. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// use std::fs; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet).expect("Failed to create wallet"); + /// + /// // Initially, there should be one account (the primary account). + /// let initial_accounts = wallet.get_accounts().expect("Failed to retrieve accounts"); + /// assert_eq!(initial_accounts.accounts.len(), 1, "Initial account count mismatch"); + /// assert_eq!(initial_accounts.accounts[0].label, "Primary account", "Expected primary account label"); + /// + /// // Create additional accounts. + /// wallet.create_account("Account 1").expect("Failed to create account 1"); + /// wallet.create_account("Account 2").expect("Failed to create account 2"); + /// + /// // Retrieve all accounts again; we should have three now. + /// let all_accounts = wallet.get_accounts().expect("Failed to retrieve all accounts"); + /// assert_eq!(all_accounts.accounts.len(), 3, "Expected 3 accounts after creation"); + /// + /// // Verify the labels of the accounts. + /// assert_eq!(all_accounts.accounts[0].label, "Primary account", "First account should be the primary account"); + /// assert_eq!(all_accounts.accounts[1].label, "Account 1", "Second account should be 'Account 1'"); + /// assert_eq!(all_accounts.accounts[2].label, "Account 2", "Third account should be 'Account 2'"); + /// + /// // Clean up wallet files. + /// fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn get_accounts(&self) -> WalletResult<GetAccounts> { + unsafe { + let accounts_size = bindings::MONERO_Wallet_numSubaddressAccounts(self.ptr.as_ptr()); + self.throw_if_error()?; + + let mut accounts = Vec::new(); + + for i in 0..accounts_size as u32 { + let label_ptr = bindings::MONERO_Wallet_getSubaddressLabel(self.ptr.as_ptr(), i, 0); + let label = if label_ptr.is_null() { + "Unnamed".to_string() + } else { + CStr::from_ptr(label_ptr).to_string_lossy().into_owned() + }; + + let balance = bindings::MONERO_Wallet_balance(self.ptr.as_ptr(), i); + let unlocked_balance = bindings::MONERO_Wallet_unlockedBalance(self.ptr.as_ptr(), i); + + accounts.push(Account { + index: i, + label, + balance, + unlocked_balance, + }); + } + + Ok(GetAccounts { accounts }) + } + } + + /// Closes the wallet, releasing any resources associated with it. + /// + /// After calling this method, the `Wallet` instance should no longer be used. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType, WalletResult}; + /// use tempfile::TempDir; + /// use std::fs; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let mut wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet).unwrap(); + /// + /// // Use the wallet for operations... + /// + /// // Now close the wallet + /// let close_result = wallet.close_wallet(); + /// assert!(close_result.is_ok(), "Failed to close wallet: {:?}", close_result.err()); + /// + /// // Clean up wallet files. + /// fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn close_wallet(&mut self) -> WalletResult<()> { + if self.is_closed { + return Ok(()); + } + unsafe { + let result = bindings::MONERO_WalletManager_closeWallet( + self.manager.ptr.as_ptr(), + self.ptr.as_ptr(), + false, // Don't save the wallet by default. + ); + if result { + self.is_closed = true; + Ok(()) + } else { + Err(WalletError::FfiError("Failed to close wallet".to_string())) + } + } + } + + /// Initializes the wallet with the provided daemon settings. + /// + /// This method must be called after creating or opening a wallet to synchronize it + /// with the daemon and prepare it for operations like refreshing. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType, WalletConfig}; + /// use tempfile::TempDir; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet) + /// .expect("Failed to create wallet"); + /// + /// let config = WalletConfig { + /// daemon_address: "http://localhost:18081".to_string(), + /// upper_transaction_size_limit: 10000, + /// daemon_username: "user".to_string(), + /// daemon_password: "pass".to_string(), + /// use_ssl: false, + /// light_wallet: false, + /// proxy_address: "".to_string(), + /// }; + /// + /// let init_result = wallet.init(config); + /// assert!(init_result.is_ok(), "Failed to initialize wallet: {:?}", init_result.err()); + /// ``` + pub fn init(&self, config: WalletConfig) -> WalletResult<()> { + let c_daemon_address = CString::new(config.daemon_address) + .map_err(|_| WalletError::FfiError("Invalid daemon address".to_string()))?; + let c_daemon_username = CString::new(config.daemon_username) + .map_err(|_| WalletError::FfiError("Invalid daemon username".to_string()))?; + let c_daemon_password = CString::new(config.daemon_password) + .map_err(|_| WalletError::FfiError("Invalid daemon password".to_string()))?; + let c_proxy_address = CString::new(config.proxy_address) + .map_err(|_| WalletError::FfiError("Invalid proxy address".to_string()))?; + + unsafe { + let result = bindings::MONERO_Wallet_init( + self.ptr.as_ptr(), + c_daemon_address.as_ptr(), + config.upper_transaction_size_limit, + c_daemon_username.as_ptr(), + c_daemon_password.as_ptr(), + config.use_ssl, + config.light_wallet, + c_proxy_address.as_ptr(), + ); + + if result { + Ok(()) + } else { + // Retrieve the last error from the wallet + Err(self.get_last_error()) + } + } + } + + /// Refreshes the wallet's state by synchronizing it with the blockchain. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType, WalletConfig}; + /// use std::fs; + /// use tempfile::TempDir; + /// + /// fn main() { + /// // Create a temporary directory for testing purposes. + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + /// + /// // Initialize the WalletManager. + /// let manager = WalletManager::new().expect("Failed to create WalletManager"); + /// + /// // Create a new wallet. + /// let wallet = manager + /// .create_wallet(wallet_str, "password", "English", NetworkType::Mainnet) + /// .expect("Failed to create wallet"); + /// + /// // Define the wallet initialization configuration. + /// let config = WalletConfig { + /// daemon_address: "http://localhost:18081".to_string(), + /// upper_transaction_size_limit: 10000, + /// daemon_username: "user".to_string(), + /// daemon_password: "pass".to_string(), + /// use_ssl: false, + /// light_wallet: false, + /// proxy_address: "".to_string(), + /// }; + /// + /// // Initialize the wallet with the specified configuration. + /// let init_result = wallet.init(config); + /// assert!(init_result.is_ok(), "Failed to initialize wallet: {:?}", init_result.err()); + /// + /// // Perform a refresh operation after initialization. + /// let refresh_result = wallet.refresh(); + /// assert!(refresh_result.is_ok(), "Failed to refresh wallet: {:?}", refresh_result.err()); + /// + /// // Optionally, you can verify the refresh by checking the blockchain height or other metrics. + /// // For example: + /// let height = manager.get_height().expect("Failed to get blockchain height"); + /// println!("Current blockchain height: {}", height); + /// + /// // Clean up wallet files. + /// fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// } + /// ``` + pub fn refresh(&self) -> WalletResult<Refreshed> { + unsafe { + let result = bindings::MONERO_Wallet_refresh(self.ptr.as_ptr()); + + if result { + Ok(Refreshed) + } else { + // Retrieve the last error from the wallet + Err(self.get_last_error()) + } + } + } + + /// Initiates a transfer from the wallet to the specified destinations. + /// + /// # Returns + /// + /// * `WalletResult<Transfer>` - On success, returns a `Transfer` struct containing transaction details. + /// On failure, returns a `WalletError`. + pub fn transfer(&self, account_index: u32, destinations: Vec<Destination>, get_tx_key: bool, sweep_all: bool) -> WalletResult<Transfer> { + // Define separators + let separator = ";"; + let separator_c = CString::new(separator).map_err(|_| WalletError::FfiError("Invalid separator".to_string()))?; + + // Concatenate destination addresses and amounts. + let addresses: Vec<String> = destinations.iter().map(|d| d.address.clone()).collect(); + let address_list = addresses.join(separator); + let c_address_list = CString::new(address_list).map_err(|_| WalletError::FfiError("Invalid address list".to_string()))?; + + let amounts: Vec<String> = destinations.iter().map(|d| d.amount.to_string()).collect(); + let amount_list = amounts.join(separator); + let c_amount_list = CString::new(amount_list).map_err(|_| WalletError::FfiError("Invalid amount list".to_string()))?; + + // TODO: Payment IDs. + let payment_id = CString::new("").map_err(|_| WalletError::FfiError("Invalid payment_id".to_string()))?; + let mixin_count = 16; + + // Pending transaction priority - default to 0 (Default) + let pending_tx_priority = bindings::Priority_Default; + + // Subaddress account + let subaddr_account = account_index; + + // TODO: Preferred inputs. + let c_preferred_inputs = CString::new("").map_err(|_| WalletError::FfiError("Invalid preferred inputs".to_string()))?; + + // Separator for preferred inputs + let preferred_inputs_separator = CString::new("").map_err(|_| WalletError::FfiError("Invalid preferred inputs separator".to_string()))?; + + unsafe { + // Create the transaction with multiple destinations. + let tx_ptr = bindings::MONERO_Wallet_createTransactionMultDest( + self.ptr.as_ptr(), + c_address_list.as_ptr(), + separator_c.as_ptr(), + payment_id.as_ptr(), + sweep_all, + c_amount_list.as_ptr(), + separator_c.as_ptr(), + mixin_count, + pending_tx_priority, + subaddr_account, + c_preferred_inputs.as_ptr(), + preferred_inputs_separator.as_ptr(), + ); + + // Check for errors. + let ptr_as_mut_c_void = self.manager.ptr.as_ptr() as *mut c_void; + self.manager.throw_if_error(ptr_as_mut_c_void)?; + if tx_ptr.is_null() { + return Err(WalletError::NullPointer); + } + + // Get the transaction ID. + let txid_ptr = bindings::MONERO_PendingTransaction_txid(tx_ptr, separator_c.as_ptr()); + if txid_ptr.is_null() { + return Err(WalletError::FfiError("Failed to get transaction ID".to_string())); + } + let txid = CStr::from_ptr(txid_ptr).to_string_lossy().into_owned(); + + // Get the fee. + let fee = bindings::MONERO_PendingTransaction_fee(tx_ptr); + + // Optionally get the transaction key. + let tx_key = if get_tx_key { + let c_txid = CString::new(txid.clone()).map_err(|_| WalletError::FfiError("Invalid txid".to_string()))?; + let tx_key_ptr = bindings::MONERO_Wallet_getTxKey(self.ptr.as_ptr(), c_txid.as_ptr()); + if tx_key_ptr.is_null() { + None + } else { + Some(CStr::from_ptr(tx_key_ptr).to_string_lossy().into_owned()) + } + } else { + None + }; + + // Submit the transaction. + // + // TODO: Make submission optional. + let tx_ptr_as_i8 = tx_ptr as *const i8; + let submit_result = bindings::MONERO_Wallet_submitTransaction( + self.ptr.as_ptr(), + tx_ptr_as_i8, + ); + if !submit_result { + return Err(WalletError::FfiError("Failed to submit transaction".to_string())); + } + + Ok(Transfer { + txid, + tx_key, + amount: destinations.iter().map(|d| d.amount).sum(), + fee, + }) + } + } + + /// Sweep all funds from the specific account to the specified destination. + /// + /// TODO: Example / docs-tests. + pub fn sweep_all(&self, account_index: u32, destination: Destination, get_tx_key: bool) -> WalletResult<Transfer> { + // Convert the destination address to a CString. + let c_address = CString::new(destination.address.clone()).map_err(|_| WalletError::FfiError("Invalid address".to_string()))?; + + // Placeholder values for fields not needed in sweep_all. + let empty_separator = CString::new("").map_err(|_| WalletError::FfiError("Invalid separator".to_string()))?; + let payment_id = CString::new("").map_err(|_| WalletError::FfiError("Invalid payment_id".to_string()))?; + let mixin_count = 16; + let pending_tx_priority = bindings::Priority_Default; + let c_preferred_inputs = CString::new("").map_err(|_| WalletError::FfiError("Invalid preferred inputs".to_string()))?; + let preferred_inputs_separator = CString::new("").map_err(|_| WalletError::FfiError("Invalid preferred inputs separator".to_string()))?; + + unsafe { + // Create the sweep transaction. + let tx_ptr = bindings::MONERO_Wallet_createTransactionMultDest( + self.ptr.as_ptr(), + c_address.as_ptr(), + empty_separator.as_ptr(), + payment_id.as_ptr(), + true, // Sweep all funds. + empty_separator.as_ptr(), + empty_separator.as_ptr(), + mixin_count, + pending_tx_priority, + account_index, + c_preferred_inputs.as_ptr(), + preferred_inputs_separator.as_ptr(), + ); + + // Check for errors. + let ptr_as_mut_c_void = self.manager.ptr.as_ptr() as *mut c_void; + self.manager.throw_if_error(ptr_as_mut_c_void)?; + if tx_ptr.is_null() { + return Err(WalletError::NullPointer); + } + + // Get the transaction ID. + let txid_ptr = bindings::MONERO_PendingTransaction_txid(tx_ptr, empty_separator.as_ptr()); + if txid_ptr.is_null() { + return Err(WalletError::FfiError("Failed to get transaction ID".to_string())); + } + let txid = CStr::from_ptr(txid_ptr).to_string_lossy().into_owned(); + + // Get the fee. + let fee = bindings::MONERO_PendingTransaction_fee(tx_ptr); + + // Optionally get the transaction key. + let tx_key = if get_tx_key { + let c_txid = CString::new(txid.clone()).map_err(|_| WalletError::FfiError("Invalid txid".to_string()))?; + let tx_key_ptr = bindings::MONERO_Wallet_getTxKey(self.ptr.as_ptr(), c_txid.as_ptr()); + if tx_key_ptr.is_null() { + None + } else { + Some(CStr::from_ptr(tx_key_ptr).to_string_lossy().into_owned()) + } + } else { + None + }; + + // Submit the transaction. + // + // TODO: Make submission optional. + let tx_ptr_as_i8 = tx_ptr as *const i8; + let submit_result = bindings::MONERO_Wallet_submitTransaction( + self.ptr.as_ptr(), + tx_ptr_as_i8, + ); + if !submit_result { + return Err(WalletError::FfiError("Failed to submit sweep transaction".to_string())); + } + + Ok(Transfer { + txid, + tx_key, + amount: 0, // Since it's sweeping all, amount is not predefined. + fee, + }) + } + } + + /// Sets the seed language for the wallet. + /// + /// # Example + /// + /// ```rust + /// use monero_c_rust::{WalletManager, NetworkType}; + /// use tempfile::TempDir; + /// use std::fs; + /// + /// let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + /// let wallet_path = temp_dir.path().join("test_wallet"); + /// let wallet_str = wallet_path.to_str().unwrap(); + /// + /// let manager = WalletManager::new().unwrap(); + /// let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet).unwrap(); + /// + /// // Change the seed language to Spanish + /// let result = wallet.set_seed_language("Spanish"); + /// assert!(result.is_ok(), "Failed to set seed language: {:?}", result.err()); + /// + /// // Clean up wallet files. + /// fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + /// fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + /// ``` + pub fn set_seed_language(&self, language: &str) -> WalletResult<()> { + let c_language = CString::new(language) + .map_err(|_| WalletError::FfiError("Invalid language string".to_string()))?; + + unsafe { + bindings::MONERO_Wallet_setSeedLanguage(self.ptr.as_ptr(), c_language.as_ptr()); + self.throw_if_error() + } + } + + /// Checks the validity of a transaction key for a given transaction ID and address. + /// + /// This method verifies whether the provided transaction key (`tx_key`) is valid for the + /// specified transaction ID (`txid`) and address (`address`). If valid, it returns the + /// amount received, whether the transaction is in the pool, and the number of confirmations. + /// + /// TODO: Example / docs-tests. + pub fn check_tx_key( + &self, + txid: String, + tx_key: String, + address: String, + received: Option<u64>, + in_pool: Option<bool>, + confirmations: Option<u64>, + ) -> WalletResult<CheckTxKey> { + // Convert Rust strings to C-compatible strings. + let c_txid = CString::new(txid) + .map_err(|_| WalletError::FfiError("Invalid txid string".to_string()))?; + let c_tx_key = CString::new(tx_key) + .map_err(|_| WalletError::FfiError("Invalid tx_key string".to_string()))?; + let c_address = CString::new(address) + .map_err(|_| WalletError::FfiError("Invalid address string".to_string()))?; + + // Assign default values if optional parameters are not provided. + let received_val = received.unwrap_or(0); + let in_pool_val = in_pool.unwrap_or(false); + let confirmations_val = confirmations.unwrap_or(0); + + // Call the C function. + let result = unsafe { + bindings::MONERO_Wallet_checkTxKey( + self.ptr.as_ptr(), + c_txid.as_ptr(), + c_tx_key.as_ptr(), + c_address.as_ptr(), + received_val, + in_pool_val, + confirmations_val, + ) + }; + + if result { + Ok(CheckTxKey { + valid: true, + error: None, + }) + } else { + // Retrieve the last error. + Err(WalletError::FfiError("Transaction key is invalid.".to_string())) + } + } +} + +#[derive(Debug)] +pub struct GetBalance { + pub balance: u64, + pub unlocked_balance: u64, +} + +impl Drop for Wallet { + fn drop(&mut self) { + if !self.is_closed { + let _ = self.close_wallet(); + } + } +} + +#[cfg(test)] +use tempfile::TempDir; +#[cfg(test)] +use std::fs; + +#[cfg(test)] +fn check_and_delete_existing_wallets(temp_dir: &TempDir) -> std::io::Result<()> { + let test_wallet_names = &["test_wallet", "mainnet_wallet", "testnet_wallet", "stagenet_wallet"]; + + for name in test_wallet_names { + let wallet_file = temp_dir.path().join(name); + let keys_file = temp_dir.path().join(format!("{}.keys", name)); + + if wallet_file.exists() { + fs::remove_file(&wallet_file)?; + } + if keys_file.exists() { + fs::remove_file(&keys_file)?; + } + } + Ok(()) +} + +#[cfg(test)] +fn setup() -> WalletResult<(Arc<WalletManager>, TempDir)> { + let temp_dir = tempfile::tempdir().expect("Failed to create temporary directory"); + check_and_delete_existing_wallets(&temp_dir).expect("Failed to clean up existing wallets"); + + let manager = WalletManager::new()?; + Ok((manager, temp_dir)) +} + +#[cfg(test)] +fn teardown(temp_dir: &TempDir) -> std::io::Result<()> { + check_and_delete_existing_wallets(temp_dir) +} + +#[test] +fn test_wallet_manager_creation() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + let wallet_result = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet); + assert!(wallet_result.is_ok(), "WalletManager creation failed"); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_wallet_creation() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet); + assert!(wallet.is_ok(), "Failed to create wallet"); + + let wallet = wallet.unwrap(); + + assert!(wallet.is_deterministic().is_ok(), "Wallet creation seems to have failed"); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_get_seed() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + // Create a new wallet. + let wallet = manager + .create_wallet(wallet_str, "password", "English", NetworkType::Mainnet) + .expect("Failed to create wallet"); + + // Test getting seed with no offset (None). + let result = wallet.get_seed(None); + assert!(result.is_ok(), "Failed to get seed without offset: {:?}", result.err()); + assert!(!result.unwrap().is_empty(), "Seed without offset is empty"); + + // Test getting seed with a specific offset (Some("offset")). + let result_with_offset = wallet.get_seed(Some("offset")); + assert!(result_with_offset.is_ok(), "Failed to get seed with offset: {:?}", result_with_offset.err()); + assert!(!result_with_offset.unwrap().is_empty(), "Seed with offset is empty"); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_get_address() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet).expect("Failed to create wallet"); + let result = wallet.get_address(0, 0); + assert!(result.is_ok(), "Failed to get address: {:?}", result.err()); + assert!(!result.unwrap().is_empty(), "Address is empty"); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_is_deterministic() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet).expect("Failed to create wallet"); + let result = wallet.is_deterministic(); + assert!(result.is_ok(), "Failed to check if wallet is deterministic: {:?}", result.err()); + assert!(result.unwrap(), "Wallet should be deterministic"); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_wallet_creation_with_different_networks() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallets = vec![ + ("mainnet_wallet", NetworkType::Mainnet), + ("testnet_wallet", NetworkType::Testnet), + ("stagenet_wallet", NetworkType::Stagenet), + ]; + + for (name, net_type) in wallets { + let wallet_path = temp_dir.path().join(name); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + let wallet = manager.create_wallet(wallet_str, "password", "English", net_type); + assert!(wallet.is_ok(), "Failed to create wallet: {}", name); + } + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_restore_mnemonic_success() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string").to_string(); + + // Example mnemonic seed (ensure this is a valid seed for your context). + let mnemonic_seed = "hemlock jubilee eden hacksaw boil superior inroads epoxy exhale orders cavernous second brunt saved richly lower upgrade hitched launching deepest mostly playful layout lower eden".to_string(); + + let wallet = manager.restore_mnemonic( + wallet_str.clone(), + "password".to_string(), + mnemonic_seed, + NetworkType::Mainnet, + 0, // Restore from the beginning of the blockchain. + 1, // Default KDF rounds. + "".to_string(), // No seed offset. + ); + assert!(wallet.is_ok(), "Failed to restore wallet: {:?}", wallet.err()); + + // Clean up wallet files. + teardown(&temp_dir).expect("Failed to clean up after test"); +} +// TODO: Test with offset. + +#[test] +fn test_restore_polyseed_success() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string").to_string(); + let polyseed = "capital chief route liar question fix clutch water outside pave hamster occur always learn license knife".to_string(); + + let restored_wallet = manager.restore_polyseed( + wallet_str.clone(), + "password".to_string(), + polyseed.clone(), + NetworkType::Mainnet, + 0, // Restore from the beginning of the blockchain. + 1, // Default KDF rounds. + "".to_string(), // No seed offset. + true, // Create a new wallet. + ); + assert!(restored_wallet.is_ok(), "Failed to restore wallet from polyseed: {:?}", restored_wallet.err()); + + // Clean up wallet files. + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_generate_from_keys_unit() { + println!("Running unit test: test_generate_from_keys_unit"); + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("generated_wallet_unit"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + // Test parameters. + // + // TODO add functions to get spend and view keys. + let address = "45wsWad9EwZgF3VpxQumrUCRaEtdyyh6NG8sVD3YRVVJbK1jkpJ3zq8WHLijVzodQ22LxwkdWx7fS2a6JzaRGzkNU8K2Dhi"; + let spendkey = "29adefc8f67515b4b4bf48031780ab9d071d24f8a674b879ce7f245c37523807"; + let viewkey = "3bc0b202cde92fe5719c3cc0a16aa94f88a5d19f8c515d4e35fae361f6f2120e"; + let restore_height = 0; + let password = "password"; + let language = "English"; + let network_type = NetworkType::Mainnet; + let kdf_rounds = 1; + + let result = manager.generate_from_keys( + wallet_str.to_string(), + address.to_string(), + spendkey.to_string(), + viewkey.to_string(), + restore_height, + password.to_string(), + language.to_string(), + network_type, + kdf_rounds, + ); + assert!(result.is_ok(), "Failed to generate wallet from keys: {:?}", result.err()); + + // Clean up wallet files. + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_multiple_address_generation() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet).expect("Failed to create wallet"); + + for i in 0..5 { + let result = wallet.get_address(0, i); + assert!(result.is_ok(), "Failed to get address {}: {:?}", i, result.err()); + assert!(!result.unwrap().is_empty(), "Address {} is empty", i); + } + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_wallet_error_display() { + // Test WalletError::FfiError variant. + let error = WalletError::FfiError("Test error".to_string()); + match error { + WalletError::FfiError(msg) => assert_eq!(msg, "Test error"), + _ => panic!("Expected FfiError variant"), + } + + // Test WalletError::NullPointer variant. + let error = WalletError::NullPointer; + match error { + WalletError::NullPointer => assert!(true), + _ => panic!("Expected NullPointer variant"), + } + + // Test WalletError::WalletErrorCode variant. + let error = WalletError::WalletErrorCode(2, "Sample wallet error".to_string()); + match error { + WalletError::WalletErrorCode(code, msg) => { + assert_eq!(code, 2); + assert_eq!(msg, "Sample wallet error"); + }, + _ => panic!("Expected WalletErrorCode variant"), + } +} + +#[test] +fn test_wallet_status() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + // Create a wallet to use for status checking + let wallet = manager + .create_wallet(wallet_str, "password", "English", NetworkType::Mainnet) + .expect("Failed to create wallet"); + + // Check the status of the wallet, expecting it to be OK + let status_result = manager.get_status(wallet.ptr.as_ptr()); + assert!(status_result.is_ok(), "Failed to get status: {:?}", status_result.err()); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_open_wallet() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + // Create a wallet to be opened later + let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet) + .expect("Failed to create wallet"); + + // Drop the wallet so it can be opened later + drop(wallet); + + // Try to open the wallet + let open_result = manager.open_wallet(wallet_str, "password", NetworkType::Mainnet); + assert!(open_result.is_ok(), "Failed to open wallet: {:?}", open_result.err()); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_get_balance() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet).unwrap(); + + let balance_result = wallet.get_balance(0); + assert!(balance_result.is_ok(), "Failed to get balance: {:?}", balance_result.err()); + + let _balance = balance_result.unwrap(); + // assert!(_balance.balance >= 0, "Balance should be non-negative"); + // assert!(_balance.unlocked_balance >= 0, "Unlocked balance should be non-negative"); + // These assertions are meaningless with the constraints of the type. + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_create_account() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + // Create a wallet. + let wallet = manager + .create_wallet(wallet_str, "password", "English", NetworkType::Mainnet) + .expect("Failed to create wallet"); + + // Create a new account. + let result = wallet.create_account("Test Account"); + assert!(result.is_ok(), "Failed to create account: {:?}", result.err()); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_get_accounts() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet).expect("Failed to create wallet"); + + // Add two accounts for testing + wallet.create_account("Test Account 1").expect("Failed to create account 1"); + wallet.create_account("Test Account 2").expect("Failed to create account 2"); + + // Retrieve all accounts + let accounts = wallet.get_accounts().expect("Failed to retrieve accounts"); + assert_eq!(accounts.accounts.len(), 3); // Including the primary account + + // Check account names + assert_eq!(accounts.accounts[0].label, "Primary account"); + assert_eq!(accounts.accounts[1].label, "Test Account 1"); + assert_eq!(accounts.accounts[2].label, "Test Account 2"); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_close_wallet() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + // Create a wallet. + let mut wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet) + .expect("Failed to create wallet"); + + // Close the wallet. + let close_result = wallet.close_wallet(); + assert!(close_result.is_ok(), "Failed to close wallet: {:?}", close_result.err()); + + // Attempt to close the wallet again. + let close_again_result = wallet.close_wallet(); + assert!(close_again_result.is_ok(), "Failed to close wallet a second time: {:?}", close_again_result.err()); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_height_success() { + let manager = WalletManager::new().unwrap(); + let height = manager.get_height().unwrap(); + // assert!(height > 0, "Blockchain height should be greater than 0"); + // The test should not assume network connectivity/any syncing progress, so: + assert!(height == 0, "Blockchain height should be equal to 0"); + } +} + +#[test] +fn test_init_success() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + // Create a wallet. + let wallet = manager.create_wallet(wallet_str, "password", "English", NetworkType::Mainnet) + .expect("Failed to create wallet"); + + // Define initialization configuration. + let config = WalletConfig { + daemon_address: "http://localhost:18081".to_string(), + upper_transaction_size_limit: 10000, + daemon_username: "user".to_string(), + daemon_password: "pass".to_string(), + use_ssl: false, + light_wallet: false, + proxy_address: "".to_string(), + }; + + // Initialize the wallet. + let init_result = wallet.init(config); + assert!(init_result.is_ok(), "Failed to initialize wallet: {:?}", init_result.err()); + + // Clean up wallet files. + fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_refresh_success() { + println!("Running test_refresh_success"); + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + // Construct the full path for the wallet within temp_dir. + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + // Create the wallet. + let wallet = manager + .create_wallet(wallet_str, "password", "English", NetworkType::Mainnet) + .expect("Failed to create wallet"); + println!("Wallet created successfully."); + + // Define initialization configuration. + let config = WalletConfig { + daemon_address: "http://localhost:18081".to_string(), + upper_transaction_size_limit: 10000, + daemon_username: "user".to_string(), + daemon_password: "pass".to_string(), + use_ssl: false, + light_wallet: false, + proxy_address: "".to_string(), + }; + + // Perform the initialization. + println!("Initializing the wallet..."); + let init_result = wallet.init(config); + + assert!(init_result.is_ok(), "Failed to initialize wallet: {:?}", init_result.err()); + + // Perform a refresh operation after initialization. + println!("Refreshing the wallet..."); + let refresh_result = wallet.refresh(); + assert!(refresh_result.is_ok(), "Failed to refresh wallet: {:?}", refresh_result.err()); + + // Clean up wallet files. + fs::remove_file(wallet_str).expect("Failed to delete test wallet"); + fs::remove_file(format!("{}.keys", wallet_str)).expect("Failed to delete test wallet keys"); + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + +#[test] +fn test_set_seed_language() { + let (manager, temp_dir) = setup().expect("Failed to set up test environment"); + + let wallet_path = temp_dir.path().join("test_wallet_set_seed_language"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string"); + + // Create a new wallet. + let wallet = manager + .create_wallet(wallet_str, "password", "English", NetworkType::Mainnet) + .expect("Failed to create wallet"); + + // Set the seed language to Spanish. + let result = wallet.set_seed_language("Spanish"); + assert!(result.is_ok(), "Failed to set seed language: {:?}", result.err()); + + // Optionally, retrieve the seed language to verify it was set correctly. + // This requires implementing a corresponding `get_seed_language` method. + // For now, we'll assume that if no error was returned, the operation was successful. + + teardown(&temp_dir).expect("Failed to clean up after test"); +} + + +#[test] +fn test_check_tx_key() { + let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string").to_string(); + + let mnemonic_seed = "capital chief route liar question fix clutch water outside pave hamster occur always learn license knife".to_string(); + let passphrase = "".to_string(); + + // Restore the wallet using polyseed. + let wallet = WalletManager::new() + .expect("Failed to create WalletManager") + .restore_polyseed( + wallet_str.clone(), + "password".to_string(), + mnemonic_seed.clone(), + NetworkType::Mainnet, + 0, + 1, + passphrase.clone(), + true + ) + .expect("Failed to restore wallet from polyseed"); + + // Print the primary address. + println!("Primary address: {}", wallet.get_address(0, 0).expect("Failed to get address")); + + // Valid transaction details. + let valid_txid = "b3f1b71f5127f9d655e58f7a2b324a64bfbc5a3ea1ce8846a0f4c51cbcb87ea6".to_string(); + let valid_tx_key = "48ef9d8b772c4f5097e29a4ba413605497d978c74e879fda67545dddff312b0a".to_string(); + let valid_address = "465cUW8wTMSCV8oVVh7CuWWHs7yeB1oxhNPrsEM5FKSqadTXmobLqsNEtRnyGsbN1rbDuBtWdtxtXhTJda1Lm9vcH2ZdrD1".to_string(); + + // Check the transaction key. + let valid_check = wallet.check_tx_key( + valid_txid.clone(), + valid_tx_key.clone(), + valid_address.clone(), + Some(1), + Some(false), + Some(10), + ); + + match valid_check { + Ok(check) => { + assert!(check.valid, "Valid transaction key should be valid."); + assert!(check.error.is_none(), "There should be no error for valid transaction key."); + println!("Valid transaction key check passed."); + }, + Err(e) => { + panic!("Error checking valid transaction key: {:?}", e); + }, + } + + // Clean up wallet files. + fs::remove_file(&wallet_path).expect("Failed to delete test wallet"); + fs::remove_file(format!("{}.keys", wallet_path.display())).expect("Failed to delete test wallet keys"); +} + +#[test] +fn test_invalid_check_tx_key() { + let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + let wallet_path = temp_dir.path().join("test_wallet"); + let wallet_str = wallet_path.to_str().expect("Failed to convert wallet path to string").to_string(); + + let mnemonic_seed = "capital chief route liar question fix clutch water outside pave hamster occur always learn license knife".to_string(); + let passphrase = "".to_string(); + + // Restore the wallet using polyseed. + let wallet = WalletManager::new() + .expect("Failed to create WalletManager") + .restore_polyseed( + wallet_str.clone(), + "password".to_string(), + mnemonic_seed.clone(), + NetworkType::Mainnet, + 0, + 1, + passphrase.clone(), + true + ) + .expect("Failed to restore wallet from polyseed"); + + // Print the primary address. + println!("Primary address: {}", wallet.get_address(0, 0).expect("Failed to get address")); + + // Invalid transaction details. + let invalid_txid = "invalid_tx_id".to_string(); + let invalid_tx_key = "invalid_tx_key".to_string(); + let invalid_address = "invalid_address".to_string(); + + // Check the invalid transaction key. + let invalid_check = wallet.check_tx_key( + invalid_txid.clone(), + invalid_tx_key.clone(), + invalid_address.clone(), + Some(1), + Some(false), + Some(10), + ); + + match invalid_check { + Ok(check) => { + assert!(!check.valid, "Invalid transaction key should be invalid."); + assert!(check.error.is_some(), "There should be an error message for invalid transaction key."); + println!("Invalid transaction key check correctly identified as invalid."); + }, + Err(e) => { + println!("Expected error for invalid transaction key: {:?}", e); + }, + } + + // Clean up wallet files. + fs::remove_file(&wallet_path).expect("Failed to delete test wallet"); + fs::remove_file(format!("{}.keys", wallet_path.display())).expect("Failed to delete test wallet keys"); +}
\ No newline at end of file |
