public InsufficientBalanceDialogViewModel(BalanceType type, BuildTransactionResult transaction, decimal usdExchangeRate) { var destinationAmount = transaction.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); var fee = transaction.Fee; BtcAmountText = $"{destinationAmount} bitcoins "; FiatAmountText = $"(≈{(destinationAmount * usdExchangeRate).FormattedFiat()} USD) "; BtcFeeText = $"{fee.ToDecimal(MoneyUnit.Satoshi)} satoshis "; FiatFeeText = $"(≈{(fee.ToDecimal(MoneyUnit.BTC) * usdExchangeRate).FormattedFiat()} USD)"; switch (type) { case BalanceType.Private: Caption = $"There are not enough private funds to cover the transaction fee. Alternatively you could:"; break; case BalanceType.Pocket: Caption = $"There are not enough funds selected to cover the transaction fee. Alternatively you could:"; break; default: Caption = $"There are not enough funds available to cover the transaction fee. Alternatively you could:"; break; } NextCommand = ReactiveCommand.Create(() => Close(result: true)); CancelCommand = ReactiveCommand.Create(() => Close(DialogResultKind.Cancel)); }
public PrivacySuggestionControlViewModel(decimal originalAmount, BuildTransactionResult transactionResult, PrivacyOptimisationLevel optimisationLevel, decimal fiatExchangeRate, params string[] benefits) { TransactionResult = transactionResult; _optimisationLevel = optimisationLevel; _benefits = benefits; decimal total = transactionResult.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); var fiatTotal = total * fiatExchangeRate; _amountFiat = total.GenerateFiatText(fiatExchangeRate, "USD"); if (optimisationLevel == PrivacyOptimisationLevel.Better) { var fiatOriginal = originalAmount * fiatExchangeRate; var fiatDifference = fiatTotal - fiatOriginal; _caption = (fiatDifference > 0 ? $"{fiatDifference.GenerateFiatText("USD")} More" : $"{Math.Abs(fiatDifference).GenerateFiatText("USD")} Less") .Replace("(", "").Replace(")", ""); } else { _caption = "As Requested"; } _amount = $"{total}"; }
public TransactionPreviewViewModel(Wallet wallet, TransactionInfo info, TransactionBroadcaster broadcaster, BuildTransactionResult transaction) { EnableCancel = true; EnableBack = true; var destinationAmount = transaction.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); var btcAmountText = $"{destinationAmount} bitcoins "; var fiatAmountText = destinationAmount.GenerateFiatText(wallet.Synchronizer.UsdExchangeRate, "USD"); AmountText = $"{btcAmountText}{fiatAmountText}"; Labels = info.Labels.Labels.ToArray(); AddressText = info.Address.ToString(); ConfirmationTimeText = $"Approximately {TextHelpers.TimeSpanToFriendlyString(info.ConfirmationTimeSpan)} "; var fee = transaction.Fee; var btcFeeText = $"{fee.ToDecimal(MoneyUnit.Satoshi)} satoshis "; var fiatFeeText = fee.ToDecimal(MoneyUnit.BTC).GenerateFiatText(wallet.Synchronizer.UsdExchangeRate, "USD"); FeeText = $"{btcFeeText}{fiatFeeText}"; NextCommand = ReactiveCommand.CreateFromTask(async() => await OnNext(wallet, broadcaster, transaction)); }
public TransactionPreviewViewModel(Wallet wallet, TransactionInfo info, BuildTransactionResult transaction) { _wallet = wallet; _labels = SmartLabel.Empty; _info = info; SetupCancel(enableCancel: true, enableCancelOnEscape: true, enableCancelOnPressed: false); EnableBack = true; _confirmationTimeText = ""; var destinationAmount = transaction.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); var btcAmountText = $"{destinationAmount} bitcoins "; var fiatAmountText = destinationAmount.GenerateFiatText(_wallet.Synchronizer.UsdExchangeRate, "USD"); AmountText = $"{btcAmountText}{fiatAmountText}"; AddressText = info.Address.ToString(); var fee = transaction.Fee; var btcFeeText = $"{fee.ToDecimal(MoneyUnit.Satoshi)} satoshis "; var fiatFeeText = fee.ToDecimal(MoneyUnit.BTC).GenerateFiatText(_wallet.Synchronizer.UsdExchangeRate, "USD"); FeeText = $"{btcFeeText}{fiatFeeText}"; PayJoinUrl = info.PayJoinClient?.PaymentUrl.AbsoluteUri; IsPayJoin = PayJoinUrl is not null; NextCommand = ReactiveCommand.CreateFromTask(async() => await OnNextAsync(transaction)); }
public ChangeAvoidanceSuggestionViewModel(decimal originalAmount, BuildTransactionResult transactionResult, decimal fiatExchangeRate, bool isOriginal) { TransactionResult = transactionResult; decimal total = transactionResult.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); _amountFiat = total.GenerateFiatText(fiatExchangeRate, "USD"); if (!isOriginal) { var fiatTotal = total * fiatExchangeRate; var fiatOriginal = originalAmount * fiatExchangeRate; var fiatDifference = fiatTotal - fiatOriginal; _differenceFiat = (fiatDifference > 0 ? $"{fiatDifference.GenerateFiatText("USD")} More" : $"{Math.Abs(fiatDifference).GenerateFiatText("USD")} Less") .Replace("(", "").Replace(")", ""); } _amount = $"{total} BTC"; }
public InsufficientBalanceDialogViewModel(BalanceType type, BuildTransactionResult transaction, decimal usdExchangeRate) { var destinationAmount = transaction.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); var btcAmountText = $"{destinationAmount} bitcoins "; var fiatAmountText = destinationAmount.GenerateFiatText(usdExchangeRate, "USD"); AmountText = $"{btcAmountText}{fiatAmountText}"; var fee = transaction.Fee; var btcFeeText = $"{fee.ToDecimal(MoneyUnit.Satoshi)} satoshis "; var fiatFeeText = fee.ToDecimal(MoneyUnit.BTC).GenerateFiatText(usdExchangeRate, "USD"); FeeText = $"{btcFeeText}{fiatFeeText}"; switch (type) { case BalanceType.Private: Caption = $"There are not enough private funds to cover the transaction fee. Alternatively you could:"; break; case BalanceType.Pocket: Caption = $"There are not enough funds selected to cover the transaction fee. Alternatively you could:"; break; default: Caption = $"There are not enough funds available to cover the transaction fee. Alternatively you could:"; break; } NextCommand = ReactiveCommand.Create(() => Close(result: true)); CancelCommand = ReactiveCommand.Create(() => Close(DialogResultKind.Cancel)); SetupCancel(enableCancel: false, enableCancelOnEscape: true, enableCancelOnPressed: true); }
public TransactionPreviewViewModel(Wallet wallet, TransactionInfo info, BuildTransactionResult transaction) { _wallet = wallet; _labels = SmartLabel.Empty; _info = info; SetupCancel(enableCancel: false, enableCancelOnEscape: true, enableCancelOnPressed: false); EnableBack = true; _confirmationTimeText = ""; var destinationAmount = transaction.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); var btcAmountText = $"{destinationAmount} bitcoins "; var fiatAmountText = destinationAmount.GenerateFiatText(_wallet.Synchronizer.UsdExchangeRate, "USD"); AmountText = $"{btcAmountText}{fiatAmountText}"; AddressText = info.Address.ToString(); var fee = transaction.Fee; var btcFeeText = $"{fee.ToDecimal(MoneyUnit.Satoshi)} sats "; var fiatFeeText = fee.ToDecimal(MoneyUnit.BTC).GenerateFiatText(_wallet.Synchronizer.UsdExchangeRate, "USD"); FeeText = $"{btcFeeText}{fiatFeeText}"; PayJoinUrl = info.PayJoinClient?.PaymentUrl.AbsoluteUri; IsPayJoin = PayJoinUrl is not null; if (PreferPsbtWorkflow) { SkipCommand = ReactiveCommand.CreateFromTask(async() => await OnConfirmAsync(transaction)); NextCommand = ReactiveCommand.CreateFromTask(async() => { var saved = await TransactionHelpers.ExportTransactionToBinaryAsync(transaction); if (saved) { Navigate().To(new SuccessViewModel("The PSBT has been successfully created.")); } }); _nextButtonText = "Save PSBT file"; } else { NextCommand = ReactiveCommand.CreateFromTask(async() => await OnConfirmAsync(transaction)); _nextButtonText = "Confirm"; } }
public TransactionPreviewViewModel(Wallet wallet, TransactionInfo info, TransactionBroadcaster broadcaster, BuildTransactionResult transaction) { var destinationAmount = transaction.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); var fee = transaction.Fee; BtcAmountText = $"{destinationAmount} bitcoins "; FiatAmountText = $"(≈{(destinationAmount * wallet.Synchronizer.UsdExchangeRate).FormattedFiat()} USD) "; Labels = info.Labels.Labels.ToArray(); AddressText = info.Address.ToString(); ConfirmationTimeText = $"Approximately {TextHelpers.TimeSpanToFriendlyString(info.ConfirmationTimeSpan)} "; BtcFeeText = $"{fee.ToDecimal(MoneyUnit.Satoshi)} satoshis "; FiatFeeText = $"(≈{(fee.ToDecimal(MoneyUnit.BTC) * wallet.Synchronizer.UsdExchangeRate).FormattedFiat()} USD)"; NextCommand = ReactiveCommand.CreateFromTask(async() => { var transactionAuthorizationInfo = new TransactionAuthorizationInfo(transaction); var authDialog = AuthorizationHelpers.GetAuthorizationDialog(wallet, transactionAuthorizationInfo); var authDialogResult = await NavigateDialog(authDialog, authDialog.DefaultTarget); if (authDialogResult.Result) { IsBusy = true; // Dequeue any coin-joining coins. await wallet.ChaumianClient.DequeueAllCoinsFromMixAsync(DequeueReason.TransactionBuilding); await broadcaster.SendTransactionAsync(transactionAuthorizationInfo.Transaction); Navigate().Clear(); IsBusy = false; } else if (authDialogResult.Kind == DialogResultKind.Normal) { await ShowErrorAsync("Authorization", "The Authorization has failed, please try again.", ""); } }); }
public static async IAsyncEnumerable <ChangeAvoidanceSuggestionViewModel> GenerateSuggestionsAsync( TransactionInfo transactionInfo, BitcoinAddress destination, Wallet wallet, ImmutableArray <SmartCoin> coinsToUse, int maxInputCount, decimal usdExchangeRate, [EnumeratorCancellation] CancellationToken cancellationToken) { var selections = ChangelessTransactionCoinSelector.GetAllStrategyResultsAsync( coinsToUse, transactionInfo.FeeRate, new TxOut(transactionInfo.Amount, destination), maxInputCount, cancellationToken).ConfigureAwait(false); HashSet <Money> foundSolutionsByAmount = new(); await foreach (var selection in selections) { if (selection.Any()) { BuildTransactionResult transaction = TransactionHelpers.BuildChangelessTransaction( wallet, destination, transactionInfo.UserLabels, transactionInfo.FeeRate, selection, tryToSign: false); var destinationAmount = transaction.CalculateDestinationAmount(); // If BnB solutions become the same transaction somehow, do not show the same suggestion twice. if (!foundSolutionsByAmount.Contains(destinationAmount)) { foundSolutionsByAmount.Add(destinationAmount); yield return(new ChangeAvoidanceSuggestionViewModel( transactionInfo.Amount.ToDecimal(MoneyUnit.BTC), transaction, usdExchangeRate)); } } } }
public PrivacySuggestionControlViewModel(decimal originalAmount, BuildTransactionResult transactionResult, PrivacyOptimisationLevel optimisationLevel, params string[] benefits) { _transactionResult = transactionResult; _optimisationLevel = optimisationLevel; _benefits = benefits; decimal total = transactionResult.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); if (optimisationLevel == PrivacyOptimisationLevel.Better) { var pcDifference = ((total - originalAmount) / originalAmount) * 100; _caption = pcDifference > 0 ? $"{pcDifference:F}% More" : $"{Math.Abs(pcDifference):F}% Less"; } else { _caption = "As Requested"; } _title = $"{total}"; }
public PrivacySuggestionControlViewModel( decimal originalAmount, BuildTransactionResult transactionResult, PrivacyOptimisationLevel optimisationLevel, decimal fiatExchangeRate, params PrivacySuggestionBenefit[] benefits) { TransactionResult = transactionResult; _optimisationLevel = optimisationLevel; _benefits = benefits.ToList(); decimal total = transactionResult.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); var fiatTotal = total * fiatExchangeRate; _amountFiat = total.GenerateFiatText(fiatExchangeRate, "USD"); _optimisationLevelGood = optimisationLevel == PrivacyOptimisationLevel.Better; if (_optimisationLevelGood) { var fiatOriginal = originalAmount * fiatExchangeRate; var fiatDifference = fiatTotal - fiatOriginal; var difference = (fiatDifference > 0 ? $"{fiatDifference.GenerateFiatText("USD")} More" : $"{Math.Abs(fiatDifference).GenerateFiatText("USD")} Less") .Replace("(", "").Replace(")", ""); _benefits.Add(new(false, difference)); } else { // This is just to pad the control. _benefits.Add(new(false, " ")); } _amount = $"{total}"; }
public TransactionPreviewViewModel(Wallet wallet, TransactionInfo info, TransactionBroadcaster broadcaster, BuildTransactionResult transaction) { var destinationAmount = transaction.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); var fee = transaction.Fee; var labels = ""; if (info.Labels.Count() == 1) { labels = info.Labels.First() + " "; } else if (info.Labels.Count() > 1) { labels = string.Join(", ", info.Labels.Take(info.Labels.Count() - 1)); labels += $" and {info.Labels.Last()} "; } BtcAmountText = $"{destinationAmount} bitcoins "; FiatAmountText = $"(≈{(destinationAmount * wallet.Synchronizer.UsdExchangeRate).FormattedFiat()} USD) "; LabelsText = labels; AddressText = info.Address.ToString(); ConfirmationTimeText = "~20 minutes "; BtcFeeText = $"{fee.ToDecimal(MoneyUnit.Satoshi)} satoshis "; FiatFeeText = $"(≈{(fee.ToDecimal(MoneyUnit.BTC) * wallet.Synchronizer.UsdExchangeRate).FormattedFiat()} USD)"; PercentFeeText = $"{transaction.FeePercentOfSent:F2}%"; NextCommand = ReactiveCommand.CreateFromTask(async() => { var dialogResult = await NavigateDialog(new EnterPasswordDialogViewModel(""), NavigationTarget.DialogScreen); if (dialogResult.Kind == DialogResultKind.Normal) { IsBusy = true; var passwordValid = await Task.Run( () => PasswordHelper.TryPassword( wallet.KeyManager, dialogResult.Result, out string?compatibilityPasswordUsed)); if (passwordValid) { // Dequeue any coin-joining coins. await wallet.ChaumianClient.DequeueAllCoinsFromMixAsync(DequeueReason.TransactionBuilding); var signedTransaction = transaction.Transaction; // If it's a hardware wallet and still has a private key then it's password. if (wallet.KeyManager.IsHardwareWallet && !transaction.Signed) { try { var client = new HwiClient(wallet.Network); using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3)); PSBT?signedPsbt = null; try { signedPsbt = await client.SignTxAsync( wallet.KeyManager.MasterFingerprint !.Value, transaction.Psbt, cts.Token); } catch (HwiException ex) when(ex.ErrorCode is not HwiErrorCode.ActionCanceled) { await PinPadViewModel.UnlockAsync(); signedPsbt = await client.SignTxAsync( wallet.KeyManager.MasterFingerprint !.Value, transaction.Psbt, cts.Token); } signedTransaction = signedPsbt.ExtractSmartTransaction(transaction.Transaction); } catch (Exception _) { // probably throw something here? } } await broadcaster.SendTransactionAsync(signedTransaction); Navigate().Clear(); IsBusy = false; } else { IsBusy = false; await ShowErrorAsync("Password was incorrect.", "Please try again.", ""); } } }); }