wire up ChanglessCoinSelector to UI
MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/ChangeAvoidanceSuggestionViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/ChangeAvoidanceSuggestionViewModel.cs
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using NBitcoin;
using WalletWasabi.Blockchain.TransactionBuilding;
+using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Wallets;
@@ -73,7 +75,7 @@ public partial class ChangeAvoidanceSuggestionViewModel : SuggestionViewModel
}
public static async Task<IEnumerable<ChangeAvoidanceSuggestionViewModel>> GenerateSuggestionsAsync(
- TransactionInfo transactionInfo, BitcoinAddress destination, Wallet wallet, BuildTransactionResult requestedTransaction)
+ TransactionInfo transactionInfo, BitcoinAddress destination, Wallet wallet, BuildTransactionResult requestedTransaction, CancellationToken cancellationToken)
{
var intent = new PaymentIntent(
destination,
@@ -102,6 +104,29 @@ public partial class ChangeAvoidanceSuggestionViewModel : SuggestionViewModel
wallet.Synchronizer.UsdExchangeRate, false);
}
+ ChangeAvoidanceSuggestionViewModel? bnbSuggestion = null;
+ IEnumerable<SmartCoin>? bnbResult = null;
+ bool canBuildWithoutChangeWithBnb = await Task.Run(() => ChangelessTransactionCoinSelector.TryGetCoins(wallet.Coins.Available().ToList(), transactionInfo.FeeRate, transactionInfo.Amount, out bnbResult, cancellationToken));
+
+ if (canBuildWithoutChangeWithBnb && bnbResult is not null)
+ {
+ var bnbTransaction = await Task.Run(() => TransactionHelpers.BuildChangelessTransaction(
+ wallet,
+ destination,
+ transactionInfo.UserLabels,
+ transactionInfo.FeeRate,
+ bnbResult,
+ false
+ ));
+
+ bnbSuggestion = new ChangeAvoidanceSuggestionViewModel(
+ transactionInfo.Amount.ToDecimal(MoneyUnit.BTC),
+ bnbTransaction,
+ wallet.Synchronizer.UsdExchangeRate,
+ false
+ );
+ }
+
var defaultSelection = new ChangeAvoidanceSuggestionViewModel(
transactionInfo.Amount.ToDecimal(MoneyUnit.BTC), requestedTransaction,
wallet.Synchronizer.UsdExchangeRate, true);
@@ -128,6 +153,11 @@ public partial class ChangeAvoidanceSuggestionViewModel : SuggestionViewModel
suggestions.Add(smallerSuggestion);
}
+ if (bnbSuggestion is not null)
+ {
+ suggestions.Add(bnbSuggestion);
+ }
+
var results = new List<ChangeAvoidanceSuggestionViewModel>();
foreach (var suggestion in NormalizeSuggestions(suggestions, defaultSelection)

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;
using System.Threading.Tasks;
using NBitcoin;
using ReactiveUI;
@@ -33,7 +34,7 @@ public partial class PrivacySuggestionsFlyoutViewModel : ViewModelBase
public ObservableCollection<SuggestionViewModel> Suggestions { get; }
- public async Task BuildPrivacySuggestionsAsync(Wallet wallet, TransactionInfo info, BitcoinAddress destination, BuildTransactionResult transaction)
+ public async Task BuildPrivacySuggestionsAsync(Wallet wallet, TransactionInfo info, BitcoinAddress destination, BuildTransactionResult transaction, CancellationToken cancellationToken = default)
{
IsLoading = true;
@@ -46,7 +47,7 @@ public partial class PrivacySuggestionsFlyoutViewModel : ViewModelBase
}
var suggestions =
- await ChangeAvoidanceSuggestionViewModel.GenerateSuggestionsAsync(info, destination, wallet, transaction);
+ await ChangeAvoidanceSuggestionViewModel.GenerateSuggestionsAsync(info, destination, wallet, transaction, cancellationToken);
var hasChange = transaction.InnerWalletOutputs.Any(x => x.ScriptPubKey != destination.ScriptPubKey);

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionInfo.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionInfo.cs
@@ -13,6 +13,7 @@ public partial class TransactionInfo
private readonly int _privateCoinThreshold;
[AutoNotify] private Money _amount = Money.Zero;
+ [AutoNotify] private FeeRate _feeRate = FeeRate.Zero;
public TransactionInfo()
{
@@ -20,12 +21,13 @@ public partial class TransactionInfo
this.WhenAnyValue(x => x.Amount)
.Subscribe(_ => OnAmountChanged());
+
+ this.WhenAnyValue(x => x.FeeRate)
+ .Subscribe(_ => OnFeeChanged());
}
public SmartLabel UserLabels { get; set; } = SmartLabel.Empty;
- public FeeRate FeeRate { get; set; } = FeeRate.Zero;
-
public FeeRate? MaximumPossibleFeeRate { get; set; }
public TimeSpan ConfirmationTimeSpan { get; set; }
@@ -62,4 +64,9 @@ public partial class TransactionInfo
Coins = Enumerable.Empty<SmartCoin>();
}
}
+
+ private void OnFeeChanged()
+ {
+ ChangelessCoins = Enumerable.Empty<SmartCoin>();
+ }
}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
@@ -3,6 +3,7 @@ using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using NBitcoin;
@@ -27,6 +28,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
private readonly TransactionInfo _info;
private readonly BitcoinAddress _destination;
private BuildTransactionResult? _transaction;
+ private CancellationTokenSource _cancellationTokenSource;
[AutoNotify] private string _nextButtonText;
[AutoNotify] private bool _adjustFeeAvailable;
[AutoNotify] private TransactionSummaryViewModel? _displayedTransactionSummary;
@@ -36,6 +38,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
_wallet = wallet;
_info = info;
_destination = destination;
+ _cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(15));
PrivacySuggestions = new PrivacySuggestionsFlyoutViewModel();
CurrentTransactionSummary = new TransactionSummaryViewModel(this, _wallet, _info, destination);
@@ -70,6 +73,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
if (x is ChangeAvoidanceSuggestionViewModel ca)
{
+ _info.ChangelessCoins = ca.TransactionResult.SpentCoins;
UpdateTransaction(CurrentTransactionSummary, ca.TransactionResult);
await PrivacySuggestions.BuildPrivacySuggestionsAsync(_wallet, _info, _destination, ca.TransactionResult);
@@ -392,7 +396,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
{
UpdateTransaction(CurrentTransactionSummary, initialTransaction);
- await PrivacySuggestions.BuildPrivacySuggestionsAsync(_wallet, _info, _destination, initialTransaction);
+ await PrivacySuggestions.BuildPrivacySuggestionsAsync(_wallet, _info, _destination, initialTransaction, _cancellationTokenSource.Token);
}
else
{
@@ -417,6 +421,12 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
protected override void OnNavigatedFrom(bool isInHistory)
{
+ if (!isInHistory)
+ {
+ _cancellationTokenSource.Cancel();
+ _cancellationTokenSource.Dispose();
+ }
+
base.OnNavigatedFrom(isInHistory);
DisplayedTransactionSummary = null;
@@ -424,6 +434,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
private async Task OnConfirmAsync()
{
+ _cancellationTokenSource.Cancel();
var labelDialog = new LabelEntryDialogViewModel(_wallet, _info);
Navigate(NavigationTarget.CompactDialogScreen).To(labelDialog);

wire up ChanglessCoinSelector to UI