Now able to restore wallets :)
MODIFY
WalletWasabi.Fluent/ViewModels/AddWalletPageViewModel.cs
WalletWasabi.Fluent/ViewModels/AddWalletPageViewModel.cs
@@ -35,7 +35,7 @@ namespace WalletWasabi.Fluent.ViewModels
if (result is { } password)
{
await screen.Router.Navigate.Execute(
- new RecoverWalletViewModel(screen, WalletName, password, walletManager));
+ new RecoverWalletViewModel(screen, WalletName, network, password, walletManager));
}
});

MODIFY
WalletWasabi.Fluent/ViewModels/Dialogs/AdvancedRecoveryOptionsViewModel.cs
WalletWasabi.Fluent/ViewModels/Dialogs/AdvancedRecoveryOptionsViewModel.cs
-using ReactiveUI;
using System.Reactive.Linq;
using System.Windows.Input;
using NBitcoin;
+using ReactiveUI;
using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Gui.Validation;
using WalletWasabi.Models;
-using WalletWasabi.Userfacing;
namespace WalletWasabi.Fluent.ViewModels.Dialogs
{
- public class AdvancedRecoveryOptionsViewModel : DialogViewModelBase<(string? accountKeyPath, string? minGapLimit)>
+ public class AdvancedRecoveryOptionsViewModel : DialogViewModelBase<(KeyPath? accountKeyPath, int? minGapLimit)>
{
private string? _accountKeyPath;
private string? _minGapLimit;
@@ -22,78 +21,89 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
var continueCommandCanExecute = this.WhenAnyValue(
x => x.AccountKeyPath,
x => x.MinGapLimit,
- (keyPath, minGapLimit) =>
+ delegate
{
// This will fire validations before return canExecute value.
this.RaisePropertyChanged(nameof(AccountKeyPath));
this.RaisePropertyChanged(nameof(MinGapLimit));
- return string.IsNullOrEmpty(keyPath) && string.IsNullOrEmpty(minGapLimit) ||
- !string.IsNullOrEmpty(keyPath) && !string.IsNullOrEmpty(minGapLimit) && !Validations.Any;
+ return !Validations.Any;
})
.ObserveOn(RxApp.MainThreadScheduler);
- ContinueCommand = ReactiveCommand.Create(() => Close((AccountKeyPath, MinGapLimit)), continueCommandCanExecute);
+ ContinueCommand = ReactiveCommand.Create(() => Close((GetAccountKeyPath(), GetMinGapLimit())),
+ continueCommandCanExecute);
CancelCommand = ReactiveCommand.Create(() => Close());
}
- protected override void OnDialogClosed()
+ public string? AccountKeyPath
{
- AccountKeyPath = "";
- MinGapLimit = "";
+ get => _accountKeyPath;
+ set => this.RaiseAndSetIfChanged(ref _accountKeyPath, value);
}
- private void ValidateMinGapLimit(IValidationErrors errors)
+ public string? MinGapLimit
{
- if (string.IsNullOrWhiteSpace(MinGapLimit))
+ get => _minGapLimit;
+ set => this.RaiseAndSetIfChanged(ref _minGapLimit, value);
+ }
+
+ public ICommand ContinueCommand { get; }
+
+ public ICommand CancelCommand { get; }
+
+ private int? GetMinGapLimit()
+ {
+ if (int.TryParse(MinGapLimit, out var minGapLimit) && minGapLimit > KeyManager.AbsoluteMinGapLimit &&
+ minGapLimit < KeyManager.MaxGapLimit)
+ return minGapLimit;
+
+ return null;
+ }
+
+ private KeyPath? GetAccountKeyPath()
+ {
+ if (AccountKeyPath is null || !KeyPath.TryParse(AccountKeyPath, out var keyPath) || keyPath is null) return null;
+
+ var accountKeyPath = keyPath.GetAccountKeyPath();
+
+ if (keyPath.Length != accountKeyPath.Length || accountKeyPath.Length != KeyManager.DefaultAccountKeyPath.Length)
{
- return;
+ return null;
}
+ return keyPath;
+ }
+
+ private void ValidateMinGapLimit(IValidationErrors errors)
+ {
+ if (string.IsNullOrWhiteSpace(MinGapLimit)) return;
+
if (!int.TryParse(MinGapLimit, out var minGapLimit) || minGapLimit < KeyManager.AbsoluteMinGapLimit ||
minGapLimit > KeyManager.MaxGapLimit)
- {
errors.Add(ErrorSeverity.Error,
$"Must be a number between {KeyManager.AbsoluteMinGapLimit} and {KeyManager.MaxGapLimit}.");
}
- }
private void ValidateAccountKeyPath(IValidationErrors errors)
{
- if (string.IsNullOrWhiteSpace(AccountKeyPath))
- {
- return;
- }
+ if (string.IsNullOrWhiteSpace(AccountKeyPath)) return;
if (KeyPath.TryParse(AccountKeyPath, out var keyPath) && keyPath is { })
{
var accountKeyPath = keyPath.GetAccountKeyPath();
if (keyPath.Length != accountKeyPath.Length ||
accountKeyPath.Length != KeyManager.DefaultAccountKeyPath.Length)
- {
errors.Add(ErrorSeverity.Error, "Path is not a compatible account derivation path.");
}
- }
else
{
errors.Add(ErrorSeverity.Error, "Path is not a valid derivation path.");
}
}
- public string? AccountKeyPath
- {
- get => _accountKeyPath;
- set => this.RaiseAndSetIfChanged(ref _accountKeyPath, value);
- }
-
- public string? MinGapLimit
+ protected override void OnDialogClosed()
{
- get => _minGapLimit;
- set => this.RaiseAndSetIfChanged(ref _minGapLimit, value);
}
-
- public ICommand ContinueCommand { get; }
-
- public ICommand CancelCommand { get; }
}
}
\ No newline at end of file

RENAME
WalletWasabi.Fluent/ViewModels/RecoverWallet/RecoverWalletPageViewModel.cs
WalletWasabi.Fluent/ViewModels/RecoverWallet/RecoverWalletViewModel.cs
@@ -8,8 +8,11 @@ using DynamicData;
using DynamicData.Binding;
using NBitcoin;
using ReactiveUI;
+using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Fluent.ViewModels.Dialogs;
+using WalletWasabi.Gui.Helpers;
using WalletWasabi.Gui.Validation;
+using WalletWasabi.Logging;
using WalletWasabi.Models;
using WalletWasabi.Wallets;
@@ -18,10 +21,12 @@ namespace WalletWasabi.Fluent.ViewModels.RecoverWallet
public class RecoverWalletViewModel : RoutableViewModel
{
private readonly ObservableAsPropertyHelper<Mnemonic?> _currentMnemonic;
- private IEnumerable<string>? _suggestions;
private string? _selectedTag;
+ private IEnumerable<string>? _suggestions;
- public RecoverWalletViewModel(IScreen screen, string walletName, string password, WalletManager walletManager) : base(screen)
+ public RecoverWalletViewModel(IScreen screen, string walletName, Network network, string password,
+ WalletManager walletManager) :
+ base(screen)
{
Suggestions = new Mnemonic(Wordlist.English, WordCount.Twelve).WordList.GetWords();
@@ -38,7 +43,7 @@ namespace WalletWasabi.Fluent.ViewModels.RecoverWallet
this.ValidateProperty(x => x.Mnemonics, ValidateMnemonics);
- AdvancedOptionsInteraction = new Interaction<object, (string?, string?)>();
+ AdvancedOptionsInteraction = new Interaction<object, (KeyPath?, int?)>();
AdvancedOptionsInteraction.RegisterHandler(
async interaction =>
interaction.SetOutput(await new AdvancedRecoveryOptionsViewModel().ShowDialogAsync()));
@@ -46,12 +51,69 @@ namespace WalletWasabi.Fluent.ViewModels.RecoverWallet
AdvancedRecoveryOptionsDialogCommand = ReactiveCommand.CreateFromTask(
async () =>
{
- var result = await AdvancedOptionsInteraction.Handle("").ToTask();
+ var (accountKeyPathIn, minGapLimitIn) = await AdvancedOptionsInteraction.Handle("").ToTask();
+
+ if (accountKeyPathIn is { })
+ AccountKeyPath = accountKeyPathIn;
+
+ if (minGapLimitIn is { })
+ MinGapLimit = minGapLimitIn;
+ });
+
+ var finishCommandCanExecute = this.WhenAnyValue(
+ x => x.CurrentMnemonics,
+ x => x.AccountKeyPath,
+ x => x.MinGapLimit,
+ delegate
+ {
+ // This will fire validations before return canExecute value.
+ this.RaisePropertyChanged(nameof(CurrentMnemonics));
+ this.RaisePropertyChanged(nameof(AccountKeyPath));
+ this.RaisePropertyChanged(nameof(MinGapLimit));
+
+ return CurrentMnemonics is { } && (CurrentMnemonics?.IsValidChecksum ?? false) &&
+ !Validations.Any;
+ })
+ .ObserveOn(RxApp.MainThreadScheduler);
+
+ FinishCommand = ReactiveCommand.Create(() =>
+ {
+ try
+ {
+ if (CurrentMnemonics is null || AccountKeyPath is null || MinGapLimit is null) return;
+ var walletFilePath = walletManager.WalletDirectories.GetWalletFilePaths(walletName).walletFilePath;
+ var keyManager = KeyManager.Recover(CurrentMnemonics, password, walletFilePath, AccountKeyPath,
+ (int) MinGapLimit);
+ keyManager.SetNetwork(network);
+ walletManager.AddWallet(keyManager);
+ screen.Router.NavigationStack.Clear();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex);
+ }
+
+ }, finishCommandCanExecute);
+
+ CancelCommand = ReactiveCommand.Create(() =>
+ {
+ password = "";
+ AccountKeyPath = default;
+ MinGapLimit = default;
});
}
+
public ICommand AdvancedRecoveryOptionsDialogCommand { get; }
- private Interaction<object, (string?, string?)> AdvancedOptionsInteraction { get; }
+ public ICommand FinishCommand { get; }
+
+ public ICommand CancelCommand { get; }
+
+ private KeyPath? AccountKeyPath { get; set; } = KeyPath.Parse("m/84'/0'/0'");
+
+ private int? MinGapLimit { get; set; } = 63;
+
+ private Interaction<object, (KeyPath?, int?)> AdvancedOptionsInteraction { get; }
public ObservableCollection<string> Mnemonics { get; } = new ObservableCollection<string>();
@@ -67,20 +129,17 @@ namespace WalletWasabi.Fluent.ViewModels.RecoverWallet
set => this.RaiseAndSetIfChanged(ref _selectedTag, value);
}
+ private Mnemonic? CurrentMnemonics => _currentMnemonic.Value;
+
private void ValidateMnemonics(IValidationErrors errors)
{
if (CurrentMnemonics is { } && !CurrentMnemonics.IsValidChecksum)
- {
errors.Add(ErrorSeverity.Error, "Recovery words are not valid.");
}
- }
private void AddMnemonic(string? tagString)
{
- if (!string.IsNullOrWhiteSpace(tagString))
- {
- Mnemonics.Add(tagString);
- }
+ if (!string.IsNullOrWhiteSpace(tagString)) Mnemonics.Add(tagString);
SelectedTag = string.Empty;
}
@@ -89,7 +148,5 @@ namespace WalletWasabi.Fluent.ViewModels.RecoverWallet
{
return string.Join(' ', Mnemonics);
}
-
- private Mnemonic? CurrentMnemonics => _currentMnemonic.Value;
}
}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/Views/RecoverWallet/RecoverWalletView.axaml
WalletWasabi.Fluent/Views/RecoverWallet/RecoverWalletView.axaml
</StackPanel>
</c:ContentArea>
- <!-- <Button Content="Cancel" Command="{Binding CancelCommand}" Classes="invisible" HorizontalAlignment="Left" -->
- <!-- VerticalAlignment="Bottom" /> -->
- <!-- <Button Content="Continue" Command="{Binding FinishCommand}" Classes="action" HorizontalAlignment="Right" -->
- <!-- VerticalAlignment="Bottom" /> -->
+ <Button Content="Cancel" Command="{Binding CancelCommand}" Classes="invisible" HorizontalAlignment="Left"
+ VerticalAlignment="Bottom" />
+ <Button Content="Restore" Command="{Binding FinishCommand}" Classes="action" HorizontalAlignment="Right"
+ VerticalAlignment="Bottom" />
</Panel>
</UserControl>
\ No newline at end of file

Now able to restore wallets :)