add initial logic for insufficient balance handling
MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
@@ -192,7 +192,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
private async Task BuildAndUpdateAsync()
{
- var newTransaction = await BuildTransactionAsync();
+ var newTransaction = await BuildTransactionAsync(isFirstTransaction: false);
if (newTransaction is { })
{
@@ -248,7 +248,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
return true;
}
- private async Task<BuildTransactionResult?> BuildTransactionAsync()
+ private async Task<BuildTransactionResult?> BuildTransactionAsync(bool isFirstTransaction)
{
if (!await InitialiseTransactionAsync())
{
@@ -263,7 +263,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
}
catch (TransactionFeeOverpaymentException ex)
{
- var result = TrySetMaximumPossibleFee(ex.PercentageOfOverpayment, _wallet, _info);
+ var result = TransactionFeeHelper.TryGetMaximumPossibleFeeRate(ex.PercentageOfOverpayment, _wallet, _info.FeeRate, out var maximumPossibleFeeRate);
if (!result)
{
@@ -273,129 +273,87 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
return null;
}
- return await BuildTransactionAsync();
- }
- catch (InsufficientBalanceException)
- {
- if (_info.IsPayJoin || _isFixedAmount)
- {
- return await HandleInsufficientBalanceWhenFixedAmountAsync(_wallet, _info);
- }
+ _info.MaximumPossibleFeeRate = maximumPossibleFeeRate;
+ _info.FeeRate = maximumPossibleFeeRate;
+ _info.ConfirmationTimeSpan = TransactionFeeHelper.CalculateConfirmationTime(maximumPossibleFeeRate, _wallet);
- return await HandleInsufficientBalanceWhenNormalAsync(_wallet, _info);
+ return await BuildTransactionAsync(isFirstTransaction);
}
- catch (Exception ex)
+ catch (InsufficientBalanceException ex)
{
- Logger.LogError(ex);
-
- await ShowErrorAsync("Transaction Building", ex.ToUserFriendlyString(),
- "Wasabi was unable to create your transaction.");
+ var maximumPossibleFeeRate = FeeRate.Zero;
+ var selectedFee = ex.Minimum - _info.Amount;
+ var maxPossibleFeeWithCurrentCoins = ex.Actual - _info.Amount;
+ var differenceOfFeePercentage = maxPossibleFeeWithCurrentCoins == Money.Zero ? 0M : (decimal)selectedFee.Satoshi / maxPossibleFeeWithCurrentCoins.Satoshi * 100;
- return null;
- }
- finally
+ if (TransactionFeeHelper.TryGetMaximumPossibleFeeRate(differenceOfFeePercentage, _wallet, _info.FeeRate, out var feeRate))
{
- IsBusy = false;
- }
+ maximumPossibleFeeRate = feeRate;
}
- private bool TrySetMaximumPossibleFee(decimal percentageOfOverpayment, Wallet wallet, TransactionInfo transactionInfo)
+ if (differenceOfFeePercentage <= TransactionFeeHelper.FeePercentageThreshold)
{
- var currentFeeRate = transactionInfo.FeeRate;
- var maxPossibleFeeRateInSatoshiPerByte = (currentFeeRate.SatoshiPerByte / percentageOfOverpayment) * 100;
- var maximumPossibleFeeRate = new FeeRate(maxPossibleFeeRateInSatoshiPerByte);
+ _info.MaximumPossibleFeeRate = maximumPossibleFeeRate;
+ _info.FeeRate = maximumPossibleFeeRate;
+ _info.ConfirmationTimeSpan = TransactionFeeHelper.CalculateConfirmationTime(maximumPossibleFeeRate, _wallet);
- var feeChartViewModel = new FeeChartViewModel();
- feeChartViewModel.UpdateFeeEstimates(TransactionFeeHelper.GetFeeEstimates(wallet));
+ return await BuildTransactionAsync(isFirstTransaction);
+ }
- var blockTarget = feeChartViewModel.GetConfirmationTarget(maximumPossibleFeeRate);
- var newFeeRate = new FeeRate(feeChartViewModel.GetSatoshiPerByte(blockTarget));
+ var totalBalanceUsed = _info.Coins.Sum(x => x.Amount) == _wallet.Coins.TotalAmount();
- if (newFeeRate > maximumPossibleFeeRate)
+ if (totalBalanceUsed)
{
- return false;
+ if (_info.Coins.Sum(x => x.Amount) == _info.Amount && !(_isFixedAmount || _info.IsPayJoin))
+ {
+ _info.SubtractFee = true;
+ return await BuildTransactionAsync(isFirstTransaction);
}
-
- transactionInfo.ConfirmationTimeSpan = TransactionFeeHelper.CalculateConfirmationTime(blockTarget);
- transactionInfo.FeeRate = newFeeRate;
- transactionInfo.MaximumPossibleFeeRate = maximumPossibleFeeRate;
-
- return true;
+ else if (_info.Coins.Sum(x => x.Amount) != _info.Amount && maximumPossibleFeeRate != FeeRate.Zero)
+ {
+ _info.MaximumPossibleFeeRate = maximumPossibleFeeRate;
+ _info.FeeRate = maximumPossibleFeeRate;
+ _info.ConfirmationTimeSpan = TransactionFeeHelper.CalculateConfirmationTime(maximumPossibleFeeRate, _wallet);
+ return await BuildTransactionAsync(isFirstTransaction);
}
-
- private async Task<BuildTransactionResult?> HandleInsufficientBalanceWhenNormalAsync(Wallet wallet, TransactionInfo transactionInfo)
+ }
+ else
{
- var dialog = new InsufficientBalanceDialogViewModel(transactionInfo.IsPrivate
- ? BalanceType.Private
- : BalanceType.Pocket);
-
- var result = await NavigateDialogAsync(dialog, NavigationTarget.DialogScreen);
-
- if (result.Result)
+ if (isFirstTransaction)
{
- transactionInfo.SubtractFee = true;
- return await Task.Run(() => TransactionHelpers.BuildTransaction(wallet, transactionInfo, _destination));
+ // show error
}
- if (wallet.Coins.TotalAmount() > transactionInfo.Amount)
- {
- var privacyControlDialogResult = await NavigateDialogAsync(
- new PrivacyControlViewModel(wallet, transactionInfo, _transaction?.SpentCoins, isSilent: false),
- NavigationTarget.DialogScreen);
+ var selectPocketsDialog =
+ await NavigateDialogAsync(new PrivacyControlViewModel(_wallet, _info, usedCoins: null, isSilent: isFirstTransaction, targetAmount: ex.Minimum));
- if (privacyControlDialogResult.Kind == DialogResultKind.Normal &&
- privacyControlDialogResult.Result is { })
+ if (selectPocketsDialog.Kind == DialogResultKind.Normal && selectPocketsDialog.Result is { })
{
- transactionInfo.Coins = privacyControlDialogResult.Result;
- }
+ _info.Coins = selectPocketsDialog.Result;
- return await BuildTransactionAsync();
+ return await BuildTransactionAsync(isFirstTransaction);
}
-
- Navigate().BackTo<SendViewModel>();
- return null;
}
- private async Task<BuildTransactionResult?> HandleInsufficientBalanceWhenFixedAmountAsync(Wallet wallet,
- TransactionInfo transactionInfo)
- {
- if (wallet.Coins.TotalAmount() > transactionInfo.Amount)
- {
- await ShowErrorAsync("Transaction Building",
- $"There are not enough {(transactionInfo.IsPrivate ? "private funds" : "funds selected")} to cover the transaction fee",
+ await ShowErrorAsync("Transaction Building", "The transaction cannot be sent at the moment.",
"Wasabi was unable to create your transaction.");
- var feeDialogResult = await NavigateDialogAsync(new SendFeeViewModel(wallet, transactionInfo, false),
- NavigationTarget.DialogScreen);
- if (feeDialogResult.Kind == DialogResultKind.Normal && feeDialogResult.Result is { } newFeeRate)
- {
- transactionInfo.FeeRate = newFeeRate;
- }
-
- if (TransactionHelpers.TryBuildTransaction(wallet, transactionInfo, _destination, out var txn))
- {
- return txn;
+ return null;
}
-
- var privacyControlDialogResult = await NavigateDialogAsync(
- new PrivacyControlViewModel(wallet, transactionInfo, _transaction?.SpentCoins, isSilent: false),
- NavigationTarget.DialogScreen);
- if (privacyControlDialogResult.Kind == DialogResultKind.Normal &&
- privacyControlDialogResult.Result is { })
+ catch (Exception ex)
{
- transactionInfo.Coins = privacyControlDialogResult.Result;
- }
-
- return await BuildTransactionAsync();
- }
+ Logger.LogError(ex);
- await ShowErrorAsync("Transaction Building",
- "There are not enough funds to cover the transaction fee",
+ await ShowErrorAsync("Transaction Building", ex.ToUserFriendlyString(),
"Wasabi was unable to create your transaction.");
- Navigate().BackTo<SendViewModel>();
return null;
}
+ finally
+ {
+ IsBusy = false;
+ }
+ }
private async Task<bool> NavigateConfirmLabelsDialogAsync(BuildTransactionResult transaction)
{
@@ -409,7 +367,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
private async Task InitialiseViewModelAsync()
{
- if (await BuildTransactionAsync() is { } initialTransaction)
+ if (await BuildTransactionAsync(isFirstTransaction: true) is { } initialTransaction)
{
UpdateTransaction(CurrentTransactionSummary, initialTransaction);

add initial logic for insufficient balance handling