synor/apps/desktop-wallet/src-tauri/src/lib.rs
Gulshan Yadav f23e7928ea fix(desktop-wallet): update crypto APIs for bech32 v0.11 and bip39 v2
- Update bech32 encoding to use Hrp struct and segwit::encode
- Update bip39 to use from_entropy() instead of generate_in()
- Update Mnemonic::parse() instead of parse_in(Language)
- Fix HMAC ambiguity with explicit type annotation
- Add Debug and Clone derives to EncryptedWallet
- Use show_menu_on_left_click() (deprecation fix)
- Add placeholder icons for development builds
2026-01-10 07:08:47 +05:30

263 lines
8.7 KiB
Rust

//! Synor Desktop Wallet - Tauri Backend
//!
//! Provides native functionality for the desktop wallet:
//! - Secure key storage using OS keychain
//! - Direct RPC communication with Synor nodes
//! - Transaction signing with Dilithium3 post-quantum signatures
//! - File system access for wallet backups
//! - System tray for background operation
//! - Auto-updates for seamless upgrades
mod commands;
mod crypto;
mod error;
mod wallet;
use tauri::{
menu::{Menu, MenuItem},
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
Emitter, Manager, Runtime,
};
pub use error::{Error, Result};
/// Build the system tray menu
fn build_tray_menu<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<Menu<R>> {
let show = MenuItem::with_id(app, "show", "Show Wallet", true, None::<&str>)?;
let hide = MenuItem::with_id(app, "hide", "Hide to Tray", true, None::<&str>)?;
let separator1 = MenuItem::with_id(app, "sep1", "─────────────", false, None::<&str>)?;
let lock = MenuItem::with_id(app, "lock", "Lock Wallet", true, None::<&str>)?;
let separator2 = MenuItem::with_id(app, "sep2", "─────────────", false, None::<&str>)?;
let check_updates = MenuItem::with_id(app, "check_updates", "Check for Updates", true, None::<&str>)?;
let separator3 = MenuItem::with_id(app, "sep3", "─────────────", false, None::<&str>)?;
let quit = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
Menu::with_items(
app,
&[
&show,
&hide,
&separator1,
&lock,
&separator2,
&check_updates,
&separator3,
&quit,
],
)
}
/// Handle system tray menu events
fn handle_tray_event<R: Runtime>(app: &tauri::AppHandle<R>, event: TrayIconEvent) {
match event {
TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Up,
..
} => {
// Left click: show and focus the window
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}
_ => {}
}
}
/// Handle tray menu item clicks
fn handle_menu_event<R: Runtime>(app: &tauri::AppHandle<R>, event: tauri::menu::MenuEvent) {
match event.id().as_ref() {
"show" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}
"hide" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.hide();
}
}
"lock" => {
// Emit lock event to frontend
let _ = app.emit("wallet-lock", ());
}
"check_updates" => {
// Emit update check event to frontend
let _ = app.emit("check-updates", ());
}
"quit" => {
// Emit quit event to allow cleanup
let _ = app.emit("app-quit", ());
std::process::exit(0);
}
_ => {}
}
}
/// Initialize the Tauri application with all plugins and commands
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
// Core plugins
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_store::Builder::default().build())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_clipboard_manager::init())
// New plugins for desktop features
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_process::init())
.setup(|app| {
// Initialize wallet state
let wallet_state = wallet::WalletState::new();
app.manage(wallet_state);
// Build and set up system tray
let menu = build_tray_menu(app.handle())?;
let _tray = TrayIconBuilder::new()
.icon(app.default_window_icon().unwrap().clone())
.menu(&menu)
.show_menu_on_left_click(false)
.on_tray_icon_event(|tray, event| {
handle_tray_event(tray.app_handle(), event);
})
.on_menu_event(|app, event| {
handle_menu_event(app, event);
})
.tooltip("Synor Wallet")
.build(app)?;
// Check for updates on startup (non-blocking)
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
check_for_updates(handle).await;
});
#[cfg(debug_assertions)]
{
// Open devtools in development
if let Some(window) = app.get_webview_window("main") {
window.open_devtools();
}
}
Ok(())
})
.invoke_handler(tauri::generate_handler![
// Wallet management
commands::create_wallet,
commands::import_wallet,
commands::unlock_wallet,
commands::lock_wallet,
commands::get_wallet_info,
commands::export_mnemonic,
// Addresses & UTXOs
commands::get_addresses,
commands::generate_address,
commands::get_balance,
commands::get_utxos,
// Transactions
commands::create_transaction,
commands::sign_transaction,
commands::broadcast_transaction,
commands::get_transaction_history,
// Network
commands::connect_node,
commands::disconnect_node,
commands::get_network_status,
// Updates
check_update,
install_update,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
/// Check for updates on startup
async fn check_for_updates<R: Runtime>(app: tauri::AppHandle<R>) {
use tauri_plugin_updater::UpdaterExt;
// Wait a few seconds before checking for updates
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
let updater = match app.updater() {
Ok(u) => u,
Err(e) => {
eprintln!("Updater not available: {}", e);
return;
}
};
match updater.check().await {
Ok(Some(update)) => {
// Notify frontend about available update
let _ = app.emit("update-available", serde_json::json!({
"version": update.version,
"body": update.body,
"date": update.date
}));
}
Ok(None) => {
// No update available
}
Err(e) => {
eprintln!("Failed to check for updates: {}", e);
}
}
}
/// Tauri command: Check for updates
#[tauri::command]
async fn check_update<R: Runtime>(app: tauri::AppHandle<R>) -> std::result::Result<Option<serde_json::Value>, String> {
use tauri_plugin_updater::UpdaterExt;
let updater = app.updater().map_err(|e| format!("Updater not configured: {}", e))?;
match updater.check().await {
Ok(Some(update)) => {
Ok(Some(serde_json::json!({
"version": update.version,
"body": update.body,
"date": update.date
})))
}
Ok(None) => Ok(None),
Err(e) => Err(format!("Update check failed: {}", e)),
}
}
/// Tauri command: Install update
#[tauri::command]
async fn install_update<R: Runtime>(app: tauri::AppHandle<R>) -> std::result::Result<(), String> {
use tauri_plugin_updater::UpdaterExt;
let updater = app.updater().map_err(|e| format!("Updater not configured: {}", e))?;
match updater.check().await {
Ok(Some(update)) => {
// Download and install with progress callbacks
let downloaded: u64 = 0;
let total_size: u64 = 0;
update.download_and_install(
move |chunk_len, content_len| {
// Progress callback - could emit progress events here
let _ = (chunk_len, content_len, downloaded, total_size);
},
|| {
// Ready to install callback
},
).await.map_err(|e| format!("Update installation failed: {}", e))?;
// Notify user to restart
let _ = app.emit("update-installed", ());
Ok(())
}
Ok(None) => Err("No update available".to_string()),
Err(e) => Err(format!("Update check failed: {}", e)),
}
}