Example #1
0
        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 { }));
Example #2
0
        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;
        }
Example #5
0
        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();
        }