separate TransactionInfo and destination Address to prevent nullable warnings and issues.
MODIFY
WalletWasabi.Fluent/Helpers/TransactionHelpers.cs
WalletWasabi.Fluent/Helpers/TransactionHelpers.cs
@@ -59,18 +59,13 @@ public static class TransactionHelpers
return txRes;
}
- public static BuildTransactionResult BuildTransaction(Wallet wallet, TransactionInfo transactionInfo, bool isPayJoin = false)
+ public static BuildTransactionResult BuildTransaction(Wallet wallet, TransactionInfo transactionInfo, BitcoinAddress destination, bool isPayJoin = false)
{
- if (transactionInfo.Address is null)
- {
- throw new Exception("Unable to build transaction without a valid address.");
- }
-
if (transactionInfo.IsOptimized)
{
return BuildChangelessTransaction(
wallet,
- transactionInfo.Address,
+ destination,
transactionInfo.UserLabels,
transactionInfo.FeeRate,
transactionInfo.ChangelessCoins);
@@ -83,7 +78,7 @@ public static class TransactionHelpers
return BuildTransaction(
wallet,
- transactionInfo.Address,
+ destination,
transactionInfo.Amount,
transactionInfo.UserLabels,
transactionInfo.FeeRate,
@@ -93,13 +88,13 @@ public static class TransactionHelpers
}
- public static bool TryBuildTransaction(Wallet wallet, TransactionInfo transactionInfo, [NotNullWhen(true)] out BuildTransactionResult? transaction, bool isPayJoin = false)
+ public static bool TryBuildTransaction(Wallet wallet, TransactionInfo transactionInfo, BitcoinAddress destination, [NotNullWhen(true)] out BuildTransactionResult? transaction, bool isPayJoin = false)
{
transaction = null;
try
{
- transaction = BuildTransaction(wallet, transactionInfo, isPayJoin);
+ transaction = BuildTransaction(wallet, transactionInfo, destination, isPayJoin);
}
catch (Exception)
{

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/AdvancedSendOptionsViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/AdvancedSendOptionsViewModel.cs
@@ -13,11 +13,11 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Send;
[NavigationMetaData(Title = "Advanced")]
public partial class AdvancedSendOptionsViewModel : DialogViewModelBase<Unit>
{
- private readonly PartialTransactionInfo _transactionInfo;
+ private readonly TransactionInfo _transactionInfo;
[AutoNotify] private string _customFee;
- public AdvancedSendOptionsViewModel(PartialTransactionInfo transactionInfo)
+ public AdvancedSendOptionsViewModel(TransactionInfo transactionInfo)
{
_transactionInfo = transactionInfo;

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/ChangeAvoidanceSuggestionViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/ChangeAvoidanceSuggestionViewModel.cs
@@ -73,15 +73,10 @@ public partial class ChangeAvoidanceSuggestionViewModel : SuggestionViewModel
}
public static async Task<IEnumerable<ChangeAvoidanceSuggestionViewModel>> GenerateSuggestionsAsync(
- TransactionInfo transactionInfo, Wallet wallet, BuildTransactionResult requestedTransaction)
+ TransactionInfo transactionInfo, BitcoinAddress destination, Wallet wallet, BuildTransactionResult requestedTransaction)
{
- if (transactionInfo.Address is null)
- {
- throw new NullReferenceException();
- }
-
var intent = new PaymentIntent(
- transactionInfo.Address,
+ destination,
MoneyRequest.CreateAllRemaining(subtractFee: true),
transactionInfo.UserLabels);

DELETE
WalletWasabi.Fluent/ViewModels/Wallets/Send/PartialTransactionInfo.cs
-using System.Collections.Generic;
-using System.Linq;
-using NBitcoin;
-using ReactiveUI;
-using WalletWasabi.Blockchain.Analysis.Clustering;
-using WalletWasabi.Blockchain.TransactionOutputs;
-using WalletWasabi.WebClients.PayJoin;
-
-namespace WalletWasabi.Fluent.ViewModels.Wallets.Send;
-
-public partial class PartialTransactionInfo
-{
- private readonly int _privateCoinThreshold;
-
- [AutoNotify] private Money _amount = Money.Zero;
-
- public PartialTransactionInfo()
- {
- _privateCoinThreshold = Services.Config.MinAnonScoreTarget;
-
- this.WhenAnyValue(x => x.Amount)
- .Subscribe(_ => OnAmountChanged());
- }
-
- public TransactionInfo ToFullTransaction(BitcoinAddress address)
- {
- return new TransactionInfo(address)
- {
- Amount = Amount,
- UserLabels = UserLabels,
- FeeRate = FeeRate,
- MaximumPossibleFeeRate = MaximumPossibleFeeRate,
- ConfirmationTimeSpan = ConfirmationTimeSpan,
- Coins = Coins,
- ChangelessCoins = ChangelessCoins,
- PayJoinClient = PayJoinClient,
- IsCustomFeeUsed = IsCustomFeeUsed,
- SubtractFee = SubtractFee
- };
- }
-
- public SmartLabel UserLabels { get; set; } = SmartLabel.Empty;
-
- public FeeRate FeeRate { get; set; } = FeeRate.Zero;
-
- public FeeRate? MaximumPossibleFeeRate { get; set; }
-
- public TimeSpan ConfirmationTimeSpan { get; set; }
-
- public IEnumerable<SmartCoin> Coins { get; set; } = Enumerable.Empty<SmartCoin>();
-
- public IEnumerable<SmartCoin> ChangelessCoins { get; set; } = Enumerable.Empty<SmartCoin>();
-
- public IPayjoinClient? PayJoinClient { get; set; }
-
- public bool IsPayJoin => PayJoinClient is { };
-
- public bool IsOptimized => ChangelessCoins.Any();
-
- public bool IsPrivate => Coins.All(x => x.HdPubKey.AnonymitySet >= _privateCoinThreshold);
-
- public bool IsCustomFeeUsed { get; set; }
-
- public bool SubtractFee { get; set; }
-
- private void OnAmountChanged()
- {
- SubtractFee = default;
- ChangelessCoins = Enumerable.Empty<SmartCoin>();
- MaximumPossibleFeeRate = null;
-
- if (!IsCustomFeeUsed)
- {
- FeeRate = FeeRate.Zero;
- }
-
- if (Coins.Sum(x => x.Amount) < Amount) // Reset coins if the selected cluster is not enough for the new amount
- {
- Coins = Enumerable.Empty<SmartCoin>();
- }
- }
-}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/PrivacySuggestionsFlyoutViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/PrivacySuggestionsFlyoutViewModel.cs
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
+using NBitcoin;
using ReactiveUI;
using WalletWasabi.Blockchain.Analysis.Clustering;
using WalletWasabi.Blockchain.TransactionBuilding;
@@ -32,7 +33,7 @@ public partial class PrivacySuggestionsFlyoutViewModel : ViewModelBase
public ObservableCollection<SuggestionViewModel> Suggestions { get; }
- public async Task BuildPrivacySuggestionsAsync(Wallet wallet, TransactionInfo info, BuildTransactionResult transaction)
+ public async Task BuildPrivacySuggestionsAsync(Wallet wallet, TransactionInfo info, BitcoinAddress destination, BuildTransactionResult transaction)
{
IsLoading = true;
@@ -45,9 +46,9 @@ public partial class PrivacySuggestionsFlyoutViewModel : ViewModelBase
}
var suggestions =
- await ChangeAvoidanceSuggestionViewModel.GenerateSuggestionsAsync(info, wallet, transaction);
+ await ChangeAvoidanceSuggestionViewModel.GenerateSuggestionsAsync(info, destination, wallet, transaction);
- var hasChange = transaction.InnerWalletOutputs.Any(x => x.ScriptPubKey != info.Address?.ScriptPubKey);
+ var hasChange = transaction.InnerWalletOutputs.Any(x => x.ScriptPubKey != destination.ScriptPubKey);
if (hasChange)
{

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/SendViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/SendViewModel.cs
@@ -34,7 +34,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Send;
public partial class SendViewModel : RoutableViewModel
{
private readonly Wallet _wallet;
- private readonly PartialTransactionInfo _transactionInfo;
+ private readonly TransactionInfo _transactionInfo;
[AutoNotify] private string _to;
[AutoNotify] private decimal _amountBtc;
[AutoNotify] private decimal _exchangeRate;
@@ -48,7 +48,7 @@ public partial class SendViewModel : RoutableViewModel
{
_to = "";
_wallet = wallet;
- _transactionInfo = new PartialTransactionInfo();
+ _transactionInfo = new TransactionInfo();
IsQrButtonVisible = WebcamQrReader.IsOsPlatformSupported;
@@ -111,8 +111,7 @@ public partial class SendViewModel : RoutableViewModel
{
_transactionInfo.Amount = new Money(AmountBtc, MoneyUnit.BTC);
- Navigate().To(new TransactionPreviewViewModel(wallet,
- _transactionInfo.ToFullTransaction(_currentAddress)));
+ Navigate().To(new TransactionPreviewViewModel(wallet, _transactionInfo, _currentAddress));
}
}, nextCommandCanExecute);
}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionInfo.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionInfo.cs
+using System.Collections.Generic;
+using System.Linq;
using NBitcoin;
+using ReactiveUI;
+using WalletWasabi.Blockchain.Analysis.Clustering;
+using WalletWasabi.Blockchain.TransactionOutputs;
+using WalletWasabi.WebClients.PayJoin;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Send;
-public class TransactionInfo : PartialTransactionInfo
+public partial class TransactionInfo
{
- public TransactionInfo(BitcoinAddress address)
+ private readonly int _privateCoinThreshold;
+
+ [AutoNotify] private Money _amount = Money.Zero;
+
+ public TransactionInfo()
{
- Address = address;
+ _privateCoinThreshold = Services.Config.MinAnonScoreTarget;
+
+ this.WhenAnyValue(x => x.Amount)
+ .Subscribe(_ => OnAmountChanged());
}
- public BitcoinAddress Address { get; }
+ public SmartLabel UserLabels { get; set; } = SmartLabel.Empty;
+
+ public FeeRate FeeRate { get; set; } = FeeRate.Zero;
+
+ public FeeRate? MaximumPossibleFeeRate { get; set; }
+
+ public TimeSpan ConfirmationTimeSpan { get; set; }
+
+ public IEnumerable<SmartCoin> Coins { get; set; } = Enumerable.Empty<SmartCoin>();
+
+ public IEnumerable<SmartCoin> ChangelessCoins { get; set; } = Enumerable.Empty<SmartCoin>();
+
+ public IPayjoinClient? PayJoinClient { get; set; }
+
+ public bool IsPayJoin => PayJoinClient is { };
+
+ public bool IsOptimized => ChangelessCoins.Any();
+
+ public bool IsPrivate => Coins.All(x => x.HdPubKey.AnonymitySet >= _privateCoinThreshold);
+
+ public bool IsCustomFeeUsed { get; set; }
+
+ public bool SubtractFee { get; set; }
+
+ private void OnAmountChanged()
+ {
+ SubtractFee = default;
+ ChangelessCoins = Enumerable.Empty<SmartCoin>();
+ MaximumPossibleFeeRate = null;
+
+ if (!IsCustomFeeUsed)
+ {
+ FeeRate = FeeRate.Zero;
+ }
+
+ if (Coins.Sum(x => x.Amount) < Amount) // Reset coins if the selected cluster is not enough for the new amount
+ {
+ Coins = Enumerable.Empty<SmartCoin>();
+ }
+ }
}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
@@ -25,19 +25,21 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
{
private readonly Wallet _wallet;
private readonly TransactionInfo _info;
+ private readonly BitcoinAddress _destination;
private BuildTransactionResult? _transaction;
[AutoNotify] private string _nextButtonText;
[AutoNotify] private bool _adjustFeeAvailable;
[AutoNotify] private TransactionSummaryViewModel? _displayedTransactionSummary;
- public TransactionPreviewViewModel(Wallet wallet, TransactionInfo info)
+ public TransactionPreviewViewModel(Wallet wallet, TransactionInfo info, BitcoinAddress destination)
{
_wallet = wallet;
_info = info;
+ _destination = destination;
PrivacySuggestions = new PrivacySuggestionsFlyoutViewModel();
- CurrentTransactionSummary = new TransactionSummaryViewModel(this, _wallet, _info);
- PreviewTransactionSummary = new TransactionSummaryViewModel(this, _wallet, _info, true);
+ CurrentTransactionSummary = new TransactionSummaryViewModel(this, _wallet, _info, destination);
+ PreviewTransactionSummary = new TransactionSummaryViewModel(this, _wallet, _info, destination, true);
TransactionSummaries = new List<TransactionSummaryViewModel>
{
@@ -70,7 +72,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
{
UpdateTransaction(CurrentTransactionSummary, ca.TransactionResult);
- await PrivacySuggestions.BuildPrivacySuggestionsAsync(_wallet, _info, ca.TransactionResult);
+ await PrivacySuggestions.BuildPrivacySuggestionsAsync(_wallet, _info, _destination, ca.TransactionResult);
}
else if (x is PocketSuggestionViewModel)
{
@@ -185,7 +187,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
{
UpdateTransaction(CurrentTransactionSummary, newTransaction);
- await PrivacySuggestions.BuildPrivacySuggestionsAsync(_wallet, _info, newTransaction);
+ await PrivacySuggestions.BuildPrivacySuggestionsAsync(_wallet, _info, _destination, newTransaction);
}
}
@@ -246,7 +248,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
{
IsBusy = true;
- return await Task.Run(() => TransactionHelpers.BuildTransaction(_wallet, _info));
+ return await Task.Run(() => TransactionHelpers.BuildTransaction(_wallet, _info, _destination));
}
catch (TransactionFeeOverpaymentException ex)
{
@@ -321,7 +323,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
if (result.Result)
{
transactionInfo.SubtractFee = true;
- return await Task.Run(() => TransactionHelpers.BuildTransaction(wallet, transactionInfo));
+ return await Task.Run(() => TransactionHelpers.BuildTransaction(wallet, transactionInfo, _destination));
}
if (wallet.Coins.TotalAmount() > transactionInfo.Amount)
@@ -359,7 +361,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
transactionInfo.FeeRate = newFeeRate;
}
- if (TransactionHelpers.TryBuildTransaction(wallet, transactionInfo, out var txn))
+ if (TransactionHelpers.TryBuildTransaction(wallet, transactionInfo, _destination, out var txn))
{
return txn;
}
@@ -390,7 +392,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
{
UpdateTransaction(CurrentTransactionSummary, initialTransaction);
- await PrivacySuggestions.BuildPrivacySuggestionsAsync(_wallet, _info, initialTransaction);
+ await PrivacySuggestions.BuildPrivacySuggestionsAsync(_wallet, _info, _destination, initialTransaction);
}
else
{
@@ -436,7 +438,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
_info.UserLabels = result.Result;
- var transaction = await Task.Run(() => TransactionHelpers.BuildTransaction(_wallet, _info));
+ var transaction = await Task.Run(() => TransactionHelpers.BuildTransaction(_wallet, _info, _destination));
var transactionAuthorizationInfo = new TransactionAuthorizationInfo(transaction);
var authResult = await AuthorizeAsync(transactionAuthorizationInfo);
if (authResult)
@@ -494,7 +496,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
try
{
var payJoinTransaction = await Task.Run(() =>
- TransactionHelpers.BuildTransaction(_wallet, transactionInfo, isPayJoin: true));
+ TransactionHelpers.BuildTransaction(_wallet, transactionInfo, _destination, isPayJoin: true));
return payJoinTransaction.Transaction;
}
catch (Exception ex)

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionSummaryViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionSummaryViewModel.cs
@@ -11,6 +11,7 @@ public partial class TransactionSummaryViewModel : ViewModelBase
{
private readonly Wallet _wallet;
private readonly TransactionInfo _info;
+ private readonly BitcoinAddress _address;
private BuildTransactionResult? _transaction;
[AutoNotify] private string _amountText = "";
[AutoNotify] private bool _transactionHasChange;
@@ -20,17 +21,18 @@ public partial class TransactionSummaryViewModel : ViewModelBase
[AutoNotify] private bool _maxPrivacy;
[AutoNotify] private bool _isCustomFeeUsed;
- public TransactionSummaryViewModel(TransactionPreviewViewModel parent, Wallet wallet, TransactionInfo info, bool isPreview = false)
+ public TransactionSummaryViewModel(TransactionPreviewViewModel parent, Wallet wallet, TransactionInfo info, BitcoinAddress address, bool isPreview = false)
{
Parent = parent;
_wallet = wallet;
_info = info;
+ _address = address;
IsPreview = isPreview;
this.WhenAnyValue(x => x.TransactionHasChange, x => x.TransactionHasPockets)
.Subscribe(_ => { MaxPrivacy = !TransactionHasPockets && !TransactionHasChange; });
- AddressText = info.Address?.ToString() ?? "";
+ AddressText = _address.ToString();
PayJoinUrl = info.PayJoinClient?.PaymentUrl.AbsoluteUri;
IsPayJoin = PayJoinUrl is not null;
}
@@ -65,7 +67,7 @@ public partial class TransactionSummaryViewModel : ViewModelBase
FeeText = $"{btcFeeText}{fiatFeeText}";
TransactionHasChange =
- _transaction.InnerWalletOutputs.Any(x => x.ScriptPubKey != _info.Address?.ScriptPubKey);
+ _transaction.InnerWalletOutputs.Any(x => x.ScriptPubKey != _address.ScriptPubKey);
TransactionHasPockets = !_info.IsPrivate;

separate TransactionInfo and destination Address to prevent nullable warnings and issues.