some work towards preparing a transaction preview.
MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/OptimisePrivacyViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/OptimisePrivacyViewModel.cs
+using WalletWasabi.Fluent.ViewModels.Navigation;
+
namespace WalletWasabi.Fluent.ViewModels.Wallets.Send
{
- public partial class OptimisePrivacyViewModel : ViewModelBase
+ [NavigationMetaData(Title = "Optimise your privacy")]
+ public partial class OptimisePrivacyViewModel : RoutableViewModel
{
}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/PrivacyControlViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/PrivacyControlViewModel.cs
+using WalletWasabi.Fluent.ViewModels.Navigation;
+
namespace WalletWasabi.Fluent.ViewModels.Wallets.Send
{
- public partial class PrivacyControlViewModel : ViewModelBase
+ [NavigationMetaData(Title = "Privacy Control")]
+ public partial class PrivacyControlViewModel : RoutableViewModel
{
}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/SendViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/SendViewModel.cs
@@ -8,6 +8,7 @@ using System.Windows.Input;
using Avalonia;
using Avalonia.Threading;
using DynamicData;
+using DynamicData.Binding;
using NBitcoin;
using NBitcoin.Payment;
using ReactiveUI;
@@ -28,6 +29,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Send
public partial class SendViewModel : NavBarItemViewModel
{
private readonly WalletViewModel _owner;
+ private readonly TransactionInfo _transactionInfo;
[AutoNotify] private string _to;
[AutoNotify] private decimal _amountBtc;
[AutoNotify] private decimal _exchangeRate;
@@ -46,6 +48,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Send
{
_to = "";
_owner = walletVm;
+ _transactionInfo = new TransactionInfo();
_labels = new ObservableCollection<string>();
ExchangeRate = walletVm.Wallet.Synchronizer.UsdExchangeRate;
@@ -68,9 +71,16 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Send
_parsingUrl = false;
});
- }
+ this.WhenAnyValue(x => x.AmountBtc)
+ .Subscribe(x=> _transactionInfo.Amount = new Money(x, MoneyUnit.BTC));
+ Labels.ToObservableChangeSet().Subscribe(x =>
+ {
+ _transactionInfo.Labels = new SmartLabel(_labels.ToArray());
+ });
+
+ }
private void ParseToField(string s)
{
@@ -112,6 +122,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Send
if (url.Address is { })
{
+ _transactionInfo.Address = url.Address;
To = url.Address.ToString();
}

ADD
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionInfo.cs
+using System.Collections.Generic;
+using NBitcoin;
+using WalletWasabi.Blockchain.Analysis.Clustering;
+using WalletWasabi.Blockchain.TransactionOutputs;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Send
+{
+ public class TransactionInfo
+ {
+ public SmartLabel Labels { get; set; }
+
+ public BitcoinAddress Address { get; set; }
+
+ public Money Amount { get; set; }
+
+ public FeeRate FeeRate { get; set; }
+
+ public IEnumerable<SmartCoin> Coins { get; set; }
+ }
+}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Security;
+using System.Threading.Tasks;
+using NBitcoin;
+using WalletWasabi.Blockchain.TransactionBuilding;
+using WalletWasabi.CoinJoin.Client.Clients.Queuing;
+using WalletWasabi.Exceptions;
+using WalletWasabi.Fluent.ViewModels.Navigation;
+using WalletWasabi.Gui.Helpers;
+using WalletWasabi.Gui.Models.StatusBarStatuses;
+using WalletWasabi.Gui.ViewModels;
+using WalletWasabi.Logging;
+
namespace WalletWasabi.Fluent.ViewModels.Wallets.Send
{
- public partial class TransactionPreviewViewModel : ViewModelBase
+ [NavigationMetaData(Title = "Transaction Preview")]
+ public partial class TransactionPreviewViewModel : RoutableViewModel
+ {
+ private readonly WalletViewModel _owner;
+
+ public TransactionPreviewViewModel(WalletViewModel walletViewModel)
+ {
+ _owner = walletViewModel;
+ }
+
+ /*private async Task BuildTransactionPart2Async()
+ {
+ BuildTransactionResult result = await Task.Run(() => _owner.Wallet.BuildTransaction(Password, payments, feeStrategy, allowUnconfirmed: true, allowedInputs: allowedInputs, GetPayjoinClient()));
+
+ MainWindowViewModel.Instance.StatusBar.TryAddStatus(StatusType.SigningTransaction);
+ SmartTransaction signedTransaction = result.Transaction;
+
+ if (Wallet.KeyManager.IsHardwareWallet && !result.Signed) // If hardware but still has a privkey then it's password, then meh.
+ {
+ try
+ {
+ IsHardwareBusy = true;
+ MainWindowViewModel.Instance.StatusBar.TryAddStatus(StatusType.AcquiringSignatureFromHardwareWallet);
+ var client = new HwiClient(Global.Network);
+
+ using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
+ PSBT signedPsbt = null;
+ try
+ {
+ signedPsbt = await client.SignTxAsync(Wallet.KeyManager.MasterFingerprint.Value, result.Psbt, cts.Token);
+ }
+ catch (HwiException ex) when (ex.ErrorCode is not HwiErrorCode.ActionCanceled)
+ {
+ await PinPadViewModel.UnlockAsync();
+ signedPsbt = await client.SignTxAsync(Wallet.KeyManager.MasterFingerprint.Value, result.Psbt, cts.Token);
+ }
+ signedTransaction = signedPsbt.ExtractSmartTransaction(result.Transaction);
+ }
+ finally
+ {
+ MainWindowViewModel.Instance.StatusBar.TryRemoveStatus(StatusType.AcquiringSignatureFromHardwareWallet);
+ IsHardwareBusy = false;
+ }
+ }
+
+ MainWindowViewModel.Instance.StatusBar.TryAddStatus(StatusType.BroadcastingTransaction);
+ await Task.Run(async () => await Global.TransactionBroadcaster.SendTransactionAsync(signedTransaction));
+ }*/
+
+ private async Task BuildTransactionAsync(TransactionInfo info)
{
+ IsBusy = true;
+
+ var wallet = _owner.Wallet;
+ var label = info.Labels;
+
+ var address = info.Address;
+
+ var requests = new List<DestinationRequest>();
+
+ var moneyRequest = MoneyRequest.Create(info.Amount, subtractFee: false);
+
+ var feeStrategy = FeeStrategy.CreateFromFeeRate(info.FeeRate);
+
+ var activeDestinationRequest = new DestinationRequest(address, moneyRequest, label);
+ requests.Add(activeDestinationRequest);
+
+ var intent = new PaymentIntent(requests);
+ try
+ {
+ // todo status notification ... dequeing coins.
+ var toDequeue = info.Coins.Where(x => x.CoinJoinInProgress).Select(x => x.OutPoint).ToArray();
+
+ if (toDequeue.Any())
+ {
+ await wallet.ChaumianClient.DequeueCoinsFromMixAsync(toDequeue, DequeueReason.TransactionBuilding);
+ }
+ }
+ catch
+ {
+ NotificationHelpers.Error("Cannot spend mixing coins.", "");
+ return;
+ }
+ finally
+ {
+ // todo status notification ... dequeing coins done.
+ }
+
+ if (!wallet.KeyManager.IsWatchOnly)
+ {
+ try
+ {
+ //PasswordHelper.GetMasterExtKey(wallet.KeyManager, Password,
+ //out string compatiblityPasswordUsed); // We could use TryPassword but we need the exception.
+ //if (compatiblityPasswordUsed is { })
+ //{
+ //Password = compatiblityPasswordUsed; // Overwrite the password for BuildTransaction function.
+ // NotificationHelpers.Warning(PasswordHelper.CompatibilityPasswordWarnMessage);
+ //}
+ }
+ catch (SecurityException ex)
+ {
+ NotificationHelpers.Error(ex.Message, "");
+ return;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex);
+ NotificationHelpers.Error(ex.ToUserFriendlyString());
+ return;
+ }
+ }
+
+ try
+ {
+ //await BuildTransaction(Password, intent, feeStrategy, allowUnconfirmed: true, allowedInputs: selectedCoinReferences);
+ }
+ catch (InsufficientBalanceException ex)
+ {
+ Money needed = ex.Minimum - ex.Actual;
+ NotificationHelpers.Error(
+ $"Not enough coins selected. You need an estimated {needed.ToString(false, true)} BTC more to make this transaction.",
+ "");
+ }
+ catch (HttpRequestException ex)
+ {
+ NotificationHelpers.Error(ex.ToUserFriendlyString());
+ Logger.LogError(ex);
+ }
+ catch (Exception ex)
+ {
+ NotificationHelpers.Error(ex.ToUserFriendlyString(), sender: _owner.Wallet);
+ Logger.LogError(ex);
+ }
+ finally
+ {
+ MainWindowViewModel.Instance.StatusBar.TryRemoveStatus(StatusType.BuildingTransaction,
+ StatusType.SigningTransaction, StatusType.BroadcastingTransaction);
+ IsBusy = false;
+ }
+ }
}
}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/Views/Wallets/Send/TransactionPreviewView.axaml
WalletWasabi.Fluent/Views/Wallets/Send/TransactionPreviewView.axaml
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="vm:OptimisePrivacyViewModel"
x:CompileBindings="True"
- x:Class="WalletWasabi.Fluent.Views.Wallets.Send.OptimisePrivacyView">
+ x:Class="WalletWasabi.Fluent.Views.Wallets.Send.TransactionPreviewView">
<c:ContentArea Title="Send" Caption=""
HorizontalAlignment="Left"
EnableCancel="False"

MODIFY
WalletWasabi.Fluent/Views/Wallets/Send/TransactionPreviewView.axaml.cs
WalletWasabi.Fluent/Views/Wallets/Send/TransactionPreviewView.axaml.cs
@@ -3,9 +3,9 @@ using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.Wallets.Send
{
- public class OptimisePrivacyView : UserControl
+ public class TransactionPreviewView : UserControl
{
- public OptimisePrivacyView()
+ public TransactionPreviewView()
{
InitializeComponent();
}

some work towards preparing a transaction preview.