private bool _disposedValue = false; // To detect redundant calls public LoadWalletViewModel(WalletManagerViewModel owner, LoadWalletType loadWalletType) : base(loadWalletType == LoadWalletType.Password ? "Test Password" : "Load Wallet") { Global = Locator.Current.GetService <Global>(); Owner = owner; Password = ""; LoadWalletType = loadWalletType; this.ValidateProperty(x => x.Password, ValidatePassword); RootList = new SourceList <WalletViewModelBase>(); RootList.Connect() .AutoRefresh(model => model.WalletState) .Filter(x => (!IsPasswordRequired || !x.Wallet.KeyManager.IsWatchOnly)) .Sort(SortExpressionComparer <WalletViewModelBase> .Descending(p => p.Wallet.KeyManager.GetLastAccessTime()), resort: ResortTrigger.AsObservable()) .ObserveOn(RxApp.MainThreadScheduler) .Bind(out _wallets) .DisposeMany() .Subscribe() .DisposeWith(Disposables); Observable.FromEventPattern <Wallet>(Global.WalletManager, nameof(Global.WalletManager.WalletAdded)) .ObserveOn(RxApp.MainThreadScheduler) .Select(x => x.EventArgs) .Subscribe(wallet => RootList.Add(new WalletViewModelBase(wallet))) .DisposeWith(Disposables); this.WhenAnyValue(x => x.SelectedWallet) .Where(x => x is null) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => SelectedWallet = Wallets.FirstOrDefault()); Wallets .ToObservableChangeSet() .ToCollection() .Where(items => items.Any() && SelectedWallet is null) .Select(items => items.First()) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => SelectedWallet = x); LoadCommand = ReactiveCommand.Create(() => RxApp.MainThreadScheduler .Schedule(async() => await LoadWalletAsync()) .DisposeWith(Disposables), this.WhenAnyValue(x => x.SelectedWallet, x => x?.WalletState) .Select(x => x == WalletState.Uninitialized)); TestPasswordCommand = ReactiveCommand.Create(LoadKeyManager, this.WhenAnyValue(x => x.SelectedWallet).Select(x => x is { }));
public LoadWalletViewModel(WalletManagerViewModel owner, LoadWalletType loadWalletType) : base(loadWalletType == LoadWalletType.Password ? "Test Password" : (loadWalletType == LoadWalletType.Desktop ? "Load Wallet" : "Hardware Wallet")) { Owner = owner; Password = ""; LoadWalletType = loadWalletType; Wallets = new ObservableCollection <LoadWalletEntry>(); WalletLock = new object(); this.WhenAnyValue(x => x.SelectedWallet) .Subscribe(selectedWallet => TrySetWalletStates()); this.WhenAnyValue(x => x.IsWalletOpened) .Subscribe(isWalletOpened => TrySetWalletStates()); this.WhenAnyValue(x => x.IsBusy) .Subscribe(x => TrySetWalletStates()); this.WhenAnyValue(x => x.Password).Subscribe(async x => { try { if (x.NotNullAndNotEmpty()) { char lastChar = x.Last(); if (lastChar == '\r' || lastChar == '\n') // If the last character is cr or lf then act like it'd be a sign to do the job. { Password = x.TrimEnd('\r', '\n'); await LoadKeyManagerAsync(requirePassword: true, isHardwareWallet: false); } } } catch (Exception ex) { Logger.LogTrace(ex); } }); LoadCommand = ReactiveCommand.CreateFromTask(async() => await LoadWalletAsync(), this.WhenAnyValue(x => x.CanLoadWallet)); TestPasswordCommand = ReactiveCommand.CreateFromTask(async() => await LoadKeyManagerAsync(requirePassword: true, isHardwareWallet: false), this.WhenAnyValue(x => x.CanTestPassword)); OpenFolderCommand = ReactiveCommand.Create(OpenWalletsFolder); LoadCommand.ThrownExceptions.Subscribe(ex => Logger.LogWarning <LoadWalletViewModel>(ex)); TestPasswordCommand.ThrownExceptions.Subscribe(ex => Logger.LogWarning <LoadWalletViewModel>(ex)); OpenFolderCommand.ThrownExceptions.Subscribe(ex => Logger.LogWarning <LoadWalletViewModel>(ex)); SetLoadButtonText(); IsHwWalletSearchTextVisible = LoadWalletType == LoadWalletType.Hardware; }
public LoadWalletViewModel(WalletManagerViewModel owner, LoadWalletType loadWalletType) : base(loadWalletType == LoadWalletType.Password ? "Test Password" : loadWalletType == LoadWalletType.Desktop ? "Load Wallet" : "Hardware Wallet") { Global = Locator.Current.GetService <Global>(); Owner = owner; Password = ""; LoadWalletType = loadWalletType; Wallets = new ObservableCollection <LoadWalletEntry>(); IsHwWalletSearchTextVisible = false; this.WhenAnyValue(x => x.SelectedWallet) .Subscribe(_ => TrySetWalletStates()); this.WhenAnyValue(x => x.IsWalletOpened) .Subscribe(_ => TrySetWalletStates()); this.WhenAnyValue(x => x.IsBusy) .Subscribe(_ => TrySetWalletStates()); LoadCommand = ReactiveCommand.CreateFromTask(LoadWalletAsync, this.WhenAnyValue(x => x.CanLoadWallet)); TestPasswordCommand = ReactiveCommand.CreateFromTask(LoadKeyManagerAsync, this.WhenAnyValue(x => x.CanTestPassword)); OpenFolderCommand = ReactiveCommand.Create(OpenWalletsFolder); OpenBrowserCommand = ReactiveCommand.CreateFromTask <string>(IoHelpers.OpenBrowserAsync); Observable .Merge(OpenBrowserCommand.ThrownExceptions) .Merge(LoadCommand.ThrownExceptions) .Merge(TestPasswordCommand.ThrownExceptions) .Merge(OpenFolderCommand.ThrownExceptions) .ObserveOn(RxApp.TaskpoolScheduler) .Subscribe(ex => { Logger.LogError(ex); NotificationHelpers.Error(ex.ToUserFriendlyString()); }); SetLoadButtonText(); }
public LoadWalletViewModel(WalletManagerViewModel owner, LoadWalletType loadWalletType) : base(loadWalletType == LoadWalletType.Password ? "Test Password" : (loadWalletType == LoadWalletType.Desktop ? "Load Wallet" : "Hardware Wallet")) { Owner = owner; Password = ""; LoadWalletType = loadWalletType; Wallets = new ObservableCollection <LoadWalletEntry>(); WalletLock = new object(); this.WhenAnyValue(x => x.SelectedWallet) .Subscribe(selectedWallet => TrySetWalletStates()); this.WhenAnyValue(x => x.IsWalletOpened) .Subscribe(isWalletOpened => TrySetWalletStates()); this.WhenAnyValue(x => x.IsBusy) .Subscribe(x => TrySetWalletStates()); this.WhenAnyValue(x => x.Password).Subscribe(async x => { try { if (x.NotNullAndNotEmpty()) { char lastChar = x.Last(); if (lastChar == '\r' || lastChar == '\n') // If the last character is cr or lf then act like it'd be a sign to do the job. { Password = x.TrimEnd('\r', '\n'); await LoadKeyManagerAsync(requirePassword: true, isHardwareWallet: false); } } } catch (Exception ex) { Logger.LogTrace(ex); } }); LoadCommand = ReactiveCommand.CreateFromTask(async() => await LoadWalletAsync(), this.WhenAnyValue(x => x.CanLoadWallet)); TestPasswordCommand = ReactiveCommand.CreateFromTask(async() => await LoadKeyManagerAsync(requirePassword: true, isHardwareWallet: false), this.WhenAnyValue(x => x.CanTestPassword)); OpenFolderCommand = ReactiveCommand.Create(OpenWalletsFolder); ImportColdcardCommand = ReactiveCommand.CreateFromTask(async() => { try { var ofd = new OpenFileDialog { AllowMultiple = false, Title = "Import Coldcard" }; if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal); } var selected = await ofd.ShowAsync(Application.Current.MainWindow, fallBack: true); if (selected != null && selected.Any()) { var path = selected.First(); var jsonString = await File.ReadAllTextAsync(path); var json = JObject.Parse(jsonString); var xpubString = json["ExtPubKey"].ToString(); var mfpString = json["MasterFingerprint"].ToString(); // https://github.com/zkSNACKs/WalletWasabi/pull/1663#issuecomment-508073066 // Coldcard 2.1.0 improperly implemented Wasabi skeleton fingerpring at first, so we must reverse byte order. // The solution was to add a ColdCardFirmwareVersion json field from 2.1.1 and correct the one generated by 2.1.0. var coldCardVersionString = json["ColdCardFirmwareVersion"]?.ToString(); var reverseByteOrder = false; if (coldCardVersionString is null) { reverseByteOrder = true; } else { Version coldCardVersion = new Version(coldCardVersionString); if (coldCardVersion == new Version("2.1.0")) // Should never happen though. { reverseByteOrder = true; } } HDFingerprint mfp = NBitcoinHelpers.BetterParseHDFingerprint(mfpString, reverseByteOrder: reverseByteOrder); ExtPubKey extPubKey = NBitcoinHelpers.BetterParseExtPubKey(xpubString); Logger.LogInfo <LoadWalletViewModel>("Creating new wallet file."); var walletName = Global.GetNextHardwareWalletName(customPrefix: "Coldcard"); var walletFullPath = Global.GetWalletFullPath(walletName); KeyManager.CreateNewHardwareWalletWatchOnly(mfp, extPubKey, walletFullPath); owner.SelectLoadWallet(); } } catch (Exception ex) { SetWarningMessage(ex.ToTypeMessageString()); Logger.LogError <LoadWalletViewModel>(ex); } }, outputScheduler: RxApp.MainThreadScheduler); OpenBrowserCommand = ReactiveCommand.Create <string>(x => { IoHelpers.OpenBrowser(x); }); OpenBrowserCommand.ThrownExceptions.Subscribe(Logger.LogWarning <LoadWalletViewModel>); LoadCommand.ThrownExceptions.Subscribe(Logger.LogWarning <LoadWalletViewModel>); TestPasswordCommand.ThrownExceptions.Subscribe(Logger.LogWarning <LoadWalletViewModel>); OpenFolderCommand.ThrownExceptions.Subscribe(Logger.LogWarning <LoadWalletViewModel>); ImportColdcardCommand.ThrownExceptions.Subscribe(Logger.LogWarning <LoadWalletViewModel>); SetLoadButtonText(); IsHwWalletSearchTextVisible = LoadWalletType == LoadWalletType.Hardware; }
public LoadWalletViewModel(WalletManagerViewModel owner, LoadWalletType loadWalletType) : base(loadWalletType == LoadWalletType.Password ? "Test Password" : (loadWalletType == LoadWalletType.Desktop ? "Load Wallet" : "Hardware Wallet")) { Owner = owner; Password = ""; LoadWalletType = loadWalletType; Wallets = new ObservableCollection <LoadWalletEntry>(); IsHwWalletSearchTextVisible = false; this.WhenAnyValue(x => x.SelectedWallet) .Subscribe(_ => TrySetWalletStates()); this.WhenAnyValue(x => x.IsWalletOpened) .Subscribe(_ => TrySetWalletStates()); this.WhenAnyValue(x => x.IsBusy) .Subscribe(_ => TrySetWalletStates()); LoadCommand = ReactiveCommand.CreateFromTask(async() => await LoadWalletAsync(), this.WhenAnyValue(x => x.CanLoadWallet)); TestPasswordCommand = ReactiveCommand.CreateFromTask(async() => await LoadKeyManagerAsync(requirePassword: true, isHardwareWallet: false), this.WhenAnyValue(x => x.CanTestPassword)); OpenFolderCommand = ReactiveCommand.Create(OpenWalletsFolder); ImportColdcardCommand = ReactiveCommand.CreateFromTask(async() => { var ofd = new OpenFileDialog { AllowMultiple = false, Title = "Import Coldcard" }; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { ofd.Directory = Path.Combine("/media", Environment.UserName); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { ofd.Directory = Environment.GetFolderPath(Environment.SpecialFolder.Personal); } var window = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).MainWindow; var selected = await ofd.ShowAsync(window, fallBack: true); if (selected != null && selected.Any()) { var path = selected.First(); var jsonString = await File.ReadAllTextAsync(path); var json = JObject.Parse(jsonString); var xpubString = json["ExtPubKey"].ToString(); var mfpString = json["MasterFingerprint"].ToString(); // https://github.com/zkSNACKs/WalletWasabi/pull/1663#issuecomment-508073066 // Coldcard 2.1.0 improperly implemented Wasabi skeleton fingerprint at first, so we must reverse byte order. // The solution was to add a ColdCardFirmwareVersion json field from 2.1.1 and correct the one generated by 2.1.0. var coldCardVersionString = json["ColdCardFirmwareVersion"]?.ToString(); var reverseByteOrder = false; if (coldCardVersionString is null) { reverseByteOrder = true; } else { Version coldCardVersion = new Version(coldCardVersionString); if (coldCardVersion == new Version("2.1.0")) // Should never happen though. { reverseByteOrder = true; } } HDFingerprint mfp = NBitcoinHelpers.BetterParseHDFingerprint(mfpString, reverseByteOrder: reverseByteOrder); ExtPubKey extPubKey = NBitcoinHelpers.BetterParseExtPubKey(xpubString); Logger.LogInfo("Creating a new wallet file."); var walletName = Global.GetNextHardwareWalletName(customPrefix: "Coldcard"); var walletFullPath = Global.GetWalletFullPath(walletName); KeyManager.CreateNewHardwareWalletWatchOnly(mfp, extPubKey, walletFullPath); owner.SelectLoadWallet(); } }); EnumerateHardwareWalletsCommand = ReactiveCommand.CreateFromTask(async() => await EnumerateHardwareWalletsAsync()); OpenBrowserCommand = ReactiveCommand.Create <string>(x => IoHelpers.OpenBrowser(x)); Observable.Merge(OpenBrowserCommand.ThrownExceptions) .Merge(LoadCommand.ThrownExceptions) .Merge(TestPasswordCommand.ThrownExceptions) .Merge(OpenFolderCommand.ThrownExceptions) .Merge(ImportColdcardCommand.ThrownExceptions) .Merge(EnumerateHardwareWalletsCommand.ThrownExceptions) .Subscribe(ex => { NotificationHelpers.Error(ex.ToTypeMessageString()); Logger.LogError(ex); }); SetLoadButtonText(); }