optimize hardware wallet interaction
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs
@@ -4,6 +4,7 @@ using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.IO;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
@@ -18,6 +19,8 @@ using WalletWasabi.Gui.ViewModels;
using WalletWasabi.Gui.ViewModels.Validation;
using WalletWasabi.Helpers;
using WalletWasabi.Hwi;
+using WalletWasabi.Hwi.Models;
+using WalletWasabi.KeyManagement;
using WalletWasabi.Models;
using WalletWasabi.Services;
@@ -261,18 +264,37 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
if (IsHardwareWallet && !result.Signed) // If hardware but still has a privkey then it's password, then meh.
{
+ PSBT signedPsbt = null;
try
{
IsHardwareBusy = true;
- var signedPsbt = await HwiProcessManager.SignTxAsync(KeyManager.HardwareWalletInfo, result.Psbt);
- var extractedTx = signedPsbt.ExtractTransaction();
- signedTransaction = new SmartTransaction(extractedTx, result.Transaction.Height);
+ // If we have no hardware wallet info then try refresh it. If we failed, then tha's a problem.
+ if (KeyManager.HardwareWalletInfo is null && !await TryRefreshHardwareWalletInfoAsync(KeyManager))
+ {
+ SetWarningMessage("Could not find hardware wallet. Make sure it's plugged in and you're logged in with your PIN.");
+ return;
+ }
+
+ signedPsbt = await HwiProcessManager.SignTxAsync(KeyManager.HardwareWalletInfo, result.Psbt);
+ }
+ catch (IOException ex) when (ex.Message.Contains("device not found", StringComparison.OrdinalIgnoreCase))
+ {
+ // The user may changed USB port. Try again with new enumeration.
+ if (!await TryRefreshHardwareWalletInfoAsync(KeyManager))
+ {
+ SetWarningMessage("Could not find hardware wallet. Make sure it's plugged in and you're logged in with your PIN.");
+ return;
+ }
+
+ signedPsbt = await HwiProcessManager.SignTxAsync(KeyManager.HardwareWalletInfo, result.Psbt);
}
finally
{
IsHardwareBusy = false;
}
+
+ signedTransaction = signedPsbt.ExtractSmartTransaction(result.Transaction.Height);
}
MainWindowViewModel.Instance.StatusBar.SetStatusAndDoUpdateActions("Broadcasting transaction...");
@@ -305,6 +327,15 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
(isMax, amount, address, busy) => (isMax.Value || !string.IsNullOrWhiteSpace(amount.Value)) && !string.IsNullOrWhiteSpace(Address) && !IsBusy));
}
+ private async Task<bool> TryRefreshHardwareWalletInfoAsync(KeyManager keyManager)
+ {
+ var hwis = await HwiProcessManager.EnumerateAsync();
+ var fingerprint = keyManager.MasterFingerprint;
+ keyManager.HardwareWalletInfo = hwis.FirstOrDefault(x => x.MasterFingerprint == fingerprint);
+
+ return keyManager.HardwareWalletInfo != null;
+ }
+
private void SetSendText()
{
if (IsHardwareBusy)

MODIFY
WalletWasabi/Extensions/NBitcoinExtensions.cs
WalletWasabi/Extensions/NBitcoinExtensions.cs
@@ -210,5 +210,16 @@ namespace NBitcoin
{
await rpc.SendCommandAsync("stop");
}
+
+ public static SmartTransaction ExtractSmartTransaction(this PSBT psbt)
+ {
+ return psbt.ExtractSmartTransaction(Height.Unknown);
+ }
+
+ public static SmartTransaction ExtractSmartTransaction(this PSBT psbt, Height height)
+ {
+ var extractedTx = psbt.ExtractTransaction();
+ return new SmartTransaction(extractedTx, height);
+ }
}
}

MODIFY
WalletWasabi/KeyManagement/KeyManager.cs
WalletWasabi/KeyManagement/KeyManager.cs
@@ -62,8 +62,8 @@ namespace WalletWasabi.KeyManagement
public string FilePath { get; private set; }
private object ToFileLock { get; }
- public bool IsWatchOnly { get; }
- public bool IsHardwareWallet => HardwareWalletInfo != null;
+ public bool IsWatchOnly => EncryptedSecret is null;
+ public bool IsHardwareWallet => EncryptedSecret is null && MasterFingerprint != null;
public HardwareWalletInfo HardwareWalletInfo { get; set; }
[JsonConstructor]
@@ -78,7 +78,6 @@ namespace WalletWasabi.KeyManagement
BlockchainStateLock = new object();
EncryptedSecret = encryptedSecret;
- IsWatchOnly = EncryptedSecret is null;
ChainCode = chainCode;
MasterFingerprint = masterFingerprint;
ExtPubKey = Guard.NotNull(nameof(extPubKey), extPubKey);
@@ -111,7 +110,6 @@ namespace WalletWasabi.KeyManagement
}
EncryptedSecret = Guard.NotNull(nameof(encryptedSecret), encryptedSecret);
- IsWatchOnly = false;
ChainCode = Guard.NotNull(nameof(chainCode), chainCode);
var extKey = new ExtKey(encryptedSecret.GetKey(password), chainCode);

optimize hardware wallet interaction