Exemple #1
0
        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}";
        }
Exemple #3
0
        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.", "");
                }
            });
        }
Exemple #9
0
    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}";
        }
Exemple #11
0
        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.", "");
                    }
                }
            });
        }