//! Tauri commands for the desktop wallet //! //! All commands are async and return Result which Tauri //! serializes as { ok: T } or { error: string } to the frontend. use serde::{Deserialize, Serialize}; use tauri::{AppHandle, Manager, State}; use crate::wallet::{WalletState, WalletAddress, NetworkConnection}; use crate::{Error, Result}; // ============================================================================ // Wallet Management Commands // ============================================================================ /// Response from wallet creation #[derive(Debug, Serialize)] pub struct CreateWalletResponse { pub mnemonic: String, pub address: String, } /// Create a new wallet with a random mnemonic #[tauri::command] pub async fn create_wallet( app: AppHandle, state: State<'_, WalletState>, password: String, ) -> Result { // Validate password strength if password.len() < 8 { return Err(Error::Crypto("Password must be at least 8 characters".to_string())); } // Set up data directory let app_data_dir = app.path().app_data_dir() .map_err(|e| Error::Internal(format!("Failed to get app data dir: {}", e)))?; state.set_data_dir(app_data_dir).await?; // Check if wallet already exists if state.wallet_exists().await { return Err(Error::Internal("Wallet already exists. Import or unlock instead.".to_string())); } // Create wallet with encryption (testnet by default for safety) let (mnemonic, address) = state.create(&password, true).await?; Ok(CreateWalletResponse { mnemonic, address }) } /// Import request #[derive(Debug, Deserialize)] pub struct ImportWalletRequest { pub mnemonic: String, pub password: String, } /// Import a wallet from mnemonic phrase #[tauri::command] pub async fn import_wallet( app: AppHandle, state: State<'_, WalletState>, request: ImportWalletRequest, ) -> Result { // Validate password strength if request.password.len() < 8 { return Err(Error::Crypto("Password must be at least 8 characters".to_string())); } // Set up data directory let app_data_dir = app.path().app_data_dir() .map_err(|e| Error::Internal(format!("Failed to get app data dir: {}", e)))?; state.set_data_dir(app_data_dir).await?; // Import wallet with encryption (testnet by default for safety) let address = state.import(&request.mnemonic, &request.password, true).await?; Ok(address) } /// Unlock an existing wallet #[tauri::command] pub async fn unlock_wallet( app: AppHandle, state: State<'_, WalletState>, password: String, ) -> Result { // Set up data directory let app_data_dir = app.path().app_data_dir() .map_err(|e| Error::Internal(format!("Failed to get app data dir: {}", e)))?; state.set_data_dir(app_data_dir).await?; // Load wallet metadata from file state.load_metadata().await?; // Decrypt and unlock state.unlock(&password).await?; Ok(true) } /// Lock the wallet (clear sensitive data from memory) #[tauri::command] pub async fn lock_wallet(state: State<'_, WalletState>) -> Result<()> { state.lock().await; Ok(()) } /// Wallet info returned to frontend #[derive(Debug, Serialize)] pub struct WalletInfo { pub locked: bool, pub address_count: usize, pub network: Option, } /// Get wallet information #[tauri::command] pub async fn get_wallet_info(state: State<'_, WalletState>) -> Result { let locked = !state.is_unlocked().await; let addresses = state.addresses.read().await; let connection = state.connection.read().await; Ok(WalletInfo { locked, address_count: addresses.len(), network: connection.as_ref().map(|c| c.network.clone()), }) } /// Export the mnemonic phrase (requires unlock) #[tauri::command] pub async fn export_mnemonic( state: State<'_, WalletState>, password: String, ) -> Result { if !state.is_unlocked().await { return Err(Error::WalletLocked); } // TODO: Re-verify password and return mnemonic Err(Error::Internal("Not implemented".to_string())) } // ============================================================================ // Address & Balance Commands // ============================================================================ /// Get all addresses in the wallet #[tauri::command] pub async fn get_addresses(state: State<'_, WalletState>) -> Result> { let addresses = state.addresses.read().await; Ok(addresses.clone()) } /// Generate a new receive address #[tauri::command] pub async fn generate_address( state: State<'_, WalletState>, label: Option, ) -> Result { // Use wallet state's generate_address which handles crypto properly state.generate_address(label, false).await } /// Balance response #[derive(Debug, Serialize)] pub struct BalanceResponse { /// Balance in sompi (1 SYN = 100_000_000 sompi) pub balance: u64, /// Human-readable balance pub balance_human: String, /// Pending incoming pub pending: u64, } /// Get wallet balance #[tauri::command] pub async fn get_balance(state: State<'_, WalletState>) -> Result { // TODO: Query node for UTXOs and sum balance Ok(BalanceResponse { balance: 0, balance_human: "0 SYN".to_string(), pending: 0, }) } /// UTXO response #[derive(Debug, Serialize)] pub struct UtxoResponse { pub txid: String, pub vout: u32, pub amount: u64, pub confirmations: u64, } /// Get UTXOs for the wallet #[tauri::command] pub async fn get_utxos(state: State<'_, WalletState>) -> Result> { // TODO: Query node for UTXOs Ok(vec![]) } // ============================================================================ // Transaction Commands // ============================================================================ /// Transaction creation request #[derive(Debug, Deserialize)] pub struct CreateTransactionRequest { pub to: String, pub amount: u64, pub fee: Option, pub use_dilithium: bool, } /// Unsigned transaction response #[derive(Debug, Serialize)] pub struct UnsignedTransaction { pub tx_hex: String, pub fee: u64, pub inputs: Vec, } /// Create an unsigned transaction #[tauri::command] pub async fn create_transaction( state: State<'_, WalletState>, request: CreateTransactionRequest, ) -> Result { if !state.is_unlocked().await { return Err(Error::WalletLocked); } // TODO: Select UTXOs, build transaction Err(Error::Internal("Not implemented".to_string())) } /// Signed transaction response #[derive(Debug, Serialize)] pub struct SignedTransaction { pub tx_hex: String, pub txid: String, } /// Sign a transaction #[tauri::command] pub async fn sign_transaction( state: State<'_, WalletState>, tx_hex: String, ) -> Result { if !state.is_unlocked().await { return Err(Error::WalletLocked); } // TODO: Sign transaction with appropriate key Err(Error::Internal("Not implemented".to_string())) } /// Broadcast response #[derive(Debug, Serialize)] pub struct BroadcastResponse { pub txid: String, pub accepted: bool, } /// Broadcast a signed transaction #[tauri::command] pub async fn broadcast_transaction( state: State<'_, WalletState>, tx_hex: String, ) -> Result { let connection = state.connection.read().await; if connection.is_none() { return Err(Error::Network("Not connected to node".to_string())); } // TODO: Submit transaction via RPC Err(Error::Internal("Not implemented".to_string())) } /// Transaction history entry #[derive(Debug, Serialize)] pub struct TransactionHistoryEntry { pub txid: String, pub direction: String, // "sent" or "received" pub amount: u64, pub fee: Option, pub timestamp: i64, pub confirmations: u64, pub counterparty: Option, } /// Get transaction history #[tauri::command] pub async fn get_transaction_history( state: State<'_, WalletState>, limit: Option, ) -> Result> { // TODO: Query indexed transactions Ok(vec![]) } // ============================================================================ // Network Commands // ============================================================================ /// Connect to a Synor node #[tauri::command] pub async fn connect_node( state: State<'_, WalletState>, rpc_url: String, ws_url: Option, ) -> Result { // TODO: Test connection, get network info let network = if rpc_url.contains("testnet") || rpc_url.contains("17110") { "testnet" } else { "mainnet" }; let connection = NetworkConnection { rpc_url, ws_url, connected: true, network: network.to_string(), }; let mut conn = state.connection.write().await; *conn = Some(connection.clone()); Ok(connection) } /// Disconnect from the current node #[tauri::command] pub async fn disconnect_node(state: State<'_, WalletState>) -> Result<()> { let mut connection = state.connection.write().await; *connection = None; Ok(()) } /// Network status #[derive(Debug, Serialize)] pub struct NetworkStatus { pub connected: bool, pub network: Option, pub block_height: Option, pub peer_count: Option, pub synced: Option, } /// Get network status #[tauri::command] pub async fn get_network_status(state: State<'_, WalletState>) -> Result { let connection = state.connection.read().await; if let Some(conn) = connection.as_ref() { // TODO: Query node for actual status Ok(NetworkStatus { connected: conn.connected, network: Some(conn.network.clone()), block_height: None, peer_count: None, synced: None, }) } else { Ok(NetworkStatus { connected: false, network: None, block_height: None, peer_count: None, synced: None, }) } }