From 4a97172346e71075bec9478effa1aceafae51e5d Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Sun, 21 Jun 2026 22:27:31 +0200 Subject: [PATCH] =?UTF-8?q?feat(ui):=20Wallet=20menu=20overlay=20=E2=80=94?= =?UTF-8?q?=20info,=20xpub,=20and=20password-protected=20seed=20reveal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a Wallet menu item (between File and Settings) that opens an in-app overlay showing wallet metadata: file name, network, type (HD seed / xprv / WIF / watch-only), script kind, derivation path, master fingerprint, and account xpub. Includes a seed section: if the wallet has no seed, a note is shown; if it has one and is unencrypted the seed is revealed directly; if encrypted the user must enter the wallet file password before the mnemonic is displayed. The revealed seed is shown in a danger-coloured bordered box with a warning. Closing the overlay (backdrop, Close button, or Esc) always clears the password input and hides the seed. Also adds the private-key password-prompt overlay and its localization keys (shared UI scaffolding used by the following commit). --- src/App/Localization/Loc.cs | 31 +++ .../MainWindowViewModel.WalletInfo.cs | 114 ++++++++++ src/App/Views/MainView.axaml | 211 +++++++++++++++++- src/App/Views/MainView.axaml.cs | 11 +- 4 files changed, 360 insertions(+), 7 deletions(-) create mode 100644 src/App/ViewModels/MainWindowViewModel.WalletInfo.cs diff --git a/src/App/Localization/Loc.cs b/src/App/Localization/Loc.cs index 3a8616a..3838a83 100644 --- a/src/App/Localization/Loc.cs +++ b/src/App/Localization/Loc.cs @@ -195,6 +195,8 @@ public sealed class Loc ["addr.pubkey"] = ["Chiave pubblica:", "Public key:", "Clave pública:", "Clé publique :", "Chave pública:", "Öffentlicher Schlüssel:"], ["addr.privkey"] = ["Chiave privata (WIF):", "Private key (WIF):", "Clave privada (WIF):", "Clé privée (WIF) :", "Chave privada (WIF):", "Privater Schlüssel (WIF):"], ["addr.show.privkey"] = ["Mostra", "Show", "Mostrar", "Afficher", "Mostrar", "Anzeigen"], + ["addr.privkey.prompt.title"] = ["Conferma identità", "Confirm identity", "Confirmar identidad", "Confirmer l'identité", "Confirmar identidade", "Identität bestätigen"], + ["addr.privkey.prompt.desc"] = ["Inserisci la password del wallet per visualizzare la chiave privata.", "Enter the wallet password to reveal the private key.", "Ingresa la contraseña del wallet para ver la clave privada.", "Entrez le mot de passe du wallet pour afficher la clé privée.", "Digite a senha da carteira para ver a chave privada.", "Geben Sie das Wallet-Passwort ein, um den privaten Schlüssel anzuzeigen."], ["addr.hide.privkey"] = ["Nascondi", "Hide", "Ocultar", "Masquer", "Ocultar", "Ausblenden"], ["addr.receive"] = ["ricezione", "receive", "recepción", "réception", "recebimento", "Empfang"], ["addr.change"] = ["change", "change", "cambio", "monnaie", "troco", "Wechselgeld"], @@ -248,6 +250,35 @@ public sealed class Loc ["send.summary"] = ["Riepilogo", "Summary", "Resumen", "Résumé", "Resumo", "Zusammenfassung"], ["receive.your.address"] = ["Il tuo indirizzo", "Your address", "Tu dirección", "Votre adresse", "Seu endereço", "Deine Adresse"], + // Wallet info overlay + ["menu.wallet"] = ["_Wallet", "_Wallet", "_Wallet", "_Wallet", "_Wallet", "_Wallet"], + ["walletinfo.title"] = ["Informazioni Wallet", "Wallet Information", "Información del Wallet", "Informations Wallet", "Informações da Carteira", "Wallet-Informationen"], + ["walletinfo.file"] = ["File", "File", "Archivo", "Fichier", "Arquivo", "Datei"], + ["walletinfo.network"] = ["Rete", "Network", "Red", "Réseau", "Rede", "Netzwerk"], + ["walletinfo.type"] = ["Tipo wallet", "Wallet type", "Tipo de wallet", "Type de wallet", "Tipo de carteira", "Wallet-Typ"], + ["walletinfo.type.seed"] = ["HD (seed BIP39)", "HD (BIP39 seed)", "HD (seed BIP39)", "HD (graine BIP39)", "HD (semente BIP39)", "HD (BIP39-Seed)"], + ["walletinfo.type.xprv"] = ["HD (xprv importato)", "HD (imported xprv)", "HD (xprv importado)", "HD (xprv importé)", "HD (xprv importado)", "HD (importierter xprv)"], + ["walletinfo.type.wif"] = ["Chiave WIF importata", "Imported WIF key", "Clave WIF importada", "Clé WIF importée", "Chave WIF importada", "Importierter WIF-Schlüssel"], + ["walletinfo.type.watchonly"] = ["Watch-only (xpub)", "Watch-only (xpub)", "Watch-only (xpub)", "Watch-only (xpub)", "Watch-only (xpub)", "Watch-only (xpub)"], + ["walletinfo.script"] = ["Script", "Script", "Script", "Script", "Script", "Script"], + ["walletinfo.derivpath"] = ["Percorso derivazione", "Derivation path", "Ruta de derivación", "Chemin de dérivation", "Caminho de derivação", "Ableitungspfad"], + ["walletinfo.xpub"] = ["Chiave pubblica estesa", "Extended public key", "Clave pública extendida", "Clé publique étendue", "Chave pública estendida", "Erweiterter öffentlicher Schlüssel"], + ["walletinfo.fingerprint"] = ["Master fingerprint", "Master fingerprint", "Huella maestra", "Empreinte maître", "Impressão digital mestre", "Master-Fingerprint"], + ["walletinfo.seed.section"] = ["Seed (mnemonica BIP39)", "Seed (BIP39 mnemonic)", "Semilla (mnemónico BIP39)", "Graine (mnémonique BIP39)", "Semente (mnemônico BIP39)", "Seed (BIP39-Mnemonic)"], + ["walletinfo.seed.noseed"] = ["Questo wallet non ha una seed (watch-only o importato).", "This wallet has no seed (watch-only or imported).", "Este wallet no tiene seed (watch-only o importado).", "Ce wallet n'a pas de graine (watch-only ou importé).", "Esta carteira não tem semente (watch-only ou importada).", "Dieses Wallet hat keinen Seed (watch-only oder importiert)."], + ["walletinfo.seed.password"] = ["Password del file wallet per sbloccare il seed:", "Wallet file password to unlock the seed:", "Contraseña del archivo para desbloquear la seed:", "Mot de passe du fichier pour déverrouiller la graine :", "Senha do arquivo para desbloquear a semente:", "Dateipasswort zum Entsperren des Seeds:"], + ["walletinfo.seed.reveal"] = ["Mostra seed", "Show seed", "Mostrar seed", "Afficher la graine", "Mostrar semente", "Seed anzeigen"], + ["walletinfo.seed.hide"] = ["Nascondi", "Hide", "Ocultar", "Masquer", "Ocultar", "Ausblenden"], + ["walletinfo.seed.warning"] = [ + "Non condividere mai queste parole. Chi le possiede controlla i fondi.", + "Never share these words. Whoever holds them controls the funds.", + "Nunca compartas estas palabras. Quien las tenga controla los fondos.", + "Ne partagez jamais ces mots. Celui qui les possède contrôle les fonds.", + "Nunca compartilhe essas palavras. Quem as tiver controla os fundos.", + "Teilen Sie diese Wörter niemals. Wer sie hat, kontrolliert die Gelder."], + ["walletinfo.passphrase"] = ["Passphrase BIP39", "BIP39 passphrase", "Frase de contraseña BIP39", "Phrase de passe BIP39", "Frase-senha BIP39", "BIP39-Passphrase"], + ["walletinfo.passphrase.set"] = ["(impostata)", "(set)", "(establecida)", "(définie)", "(definida)", "(gesetzt)"], + // Connection status ["conn.none"] = ["non connesso", "not connected", "no conectado", "non connecté", "não conectado", "nicht verbunden"], ["conn.disconnected"] = ["disconnesso", "disconnected", "desconectado", "déconnecté", "desconectado", "getrennt"], diff --git a/src/App/ViewModels/MainWindowViewModel.WalletInfo.cs b/src/App/ViewModels/MainWindowViewModel.WalletInfo.cs new file mode 100644 index 0000000..017188e --- /dev/null +++ b/src/App/ViewModels/MainWindowViewModel.WalletInfo.cs @@ -0,0 +1,114 @@ +using System.IO; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PalladiumWallet.App.Localization; + +namespace PalladiumWallet.App.ViewModels; + +public partial class MainWindowViewModel +{ + [ObservableProperty] + private bool isWalletInfoOpen; + + [ObservableProperty] + private string walletInfoSeedPasswordInput = ""; + + [ObservableProperty] + private bool isWalletInfoSeedRevealed; + + [ObservableProperty] + private string walletInfoSeedError = ""; + + // ---- computed wallet info (populated when overlay opens) ---- + + public string WalletInfoFileName => + _walletPath is null ? "" : + Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(_walletPath)); + + public string WalletInfoNetwork => _doc?.Network ?? ""; + + public string WalletInfoType + { + get + { + if (_doc is null) return ""; + if (_doc.Mnemonic is not null) return Loc.Tr("walletinfo.type.seed"); + if (_doc.AccountXprv is not null) return Loc.Tr("walletinfo.type.xprv"); + if (_doc.WifKeys is { Count: > 0 }) return Loc.Tr("walletinfo.type.wif"); + return Loc.Tr("walletinfo.type.watchonly"); + } + } + + public string WalletInfoScriptKind => _doc?.ScriptKind ?? ""; + + public string WalletInfoDerivPath => _doc?.AccountPath ?? ""; + + public string WalletInfoXpub => _doc?.AccountXpub ?? ""; + + public string WalletInfoFingerprint => _doc?.MasterFingerprint ?? ""; + + public bool WalletInfoHasSeed => _doc?.Mnemonic is not null; + + public bool WalletInfoSeedNeedsPassword => !string.IsNullOrEmpty(_password); + + public string WalletInfoSeedText => IsWalletInfoSeedRevealed ? (_doc?.Mnemonic ?? "") : ""; + + public bool WalletInfoHasPassphrase => !string.IsNullOrEmpty(_doc?.Passphrase); + + [RelayCommand] + private void OpenWalletInfo() + { + WalletInfoSeedPasswordInput = ""; + IsWalletInfoSeedRevealed = false; + WalletInfoSeedError = ""; + OnPropertyChanged(nameof(WalletInfoFileName)); + OnPropertyChanged(nameof(WalletInfoNetwork)); + OnPropertyChanged(nameof(WalletInfoType)); + OnPropertyChanged(nameof(WalletInfoScriptKind)); + OnPropertyChanged(nameof(WalletInfoDerivPath)); + OnPropertyChanged(nameof(WalletInfoXpub)); + OnPropertyChanged(nameof(WalletInfoFingerprint)); + OnPropertyChanged(nameof(WalletInfoHasSeed)); + OnPropertyChanged(nameof(WalletInfoSeedNeedsPassword)); + OnPropertyChanged(nameof(WalletInfoHasPassphrase)); + IsWalletInfoOpen = true; + } + + [RelayCommand] + private void CloseWalletInfo() + { + IsWalletInfoOpen = false; + WalletInfoSeedPasswordInput = ""; + IsWalletInfoSeedRevealed = false; + WalletInfoSeedError = ""; + OnPropertyChanged(nameof(WalletInfoSeedText)); + } + + [RelayCommand] + private void RevealSeed() + { + if (_doc?.Mnemonic is null) return; + + if (WalletInfoSeedNeedsPassword) + { + if (WalletInfoSeedPasswordInput != _password) + { + WalletInfoSeedError = Loc.Tr("msg.wrongpassword"); + return; + } + } + + WalletInfoSeedError = ""; + IsWalletInfoSeedRevealed = true; + OnPropertyChanged(nameof(WalletInfoSeedText)); + } + + [RelayCommand] + private void HideSeed() + { + IsWalletInfoSeedRevealed = false; + WalletInfoSeedPasswordInput = ""; + WalletInfoSeedError = ""; + OnPropertyChanged(nameof(WalletInfoSeedText)); + } +} diff --git a/src/App/Views/MainView.axaml b/src/App/Views/MainView.axaml index f4482af..4a8b105 100644 --- a/src/App/Views/MainView.axaml +++ b/src/App/Views/MainView.axaml @@ -37,6 +37,9 @@ + +