コード例 #1
0
        private async Task OnNext(Wallet wallet, TransactionInfo transactionInfo, TransactionBroadcaster broadcaster, IObservableList <PocketViewModel> selectedList)
        {
            var coins = selectedList.Items.SelectMany(x => x.Coins).ToArray();

            try
            {
                try
                {
                    var transactionResult = await Task.Run(() => TransactionHelpers.BuildTransaction(_wallet, transactionInfo.Address, transactionInfo.Amount, transactionInfo.Labels, transactionInfo.FeeRate, coins, subtractFee: false));

                    Navigate().To(new OptimisePrivacyViewModel(wallet, transactionInfo, broadcaster, transactionResult));
                }
                catch (InsufficientBalanceException)
                {
                    var transactionResult = await Task.Run(() => TransactionHelpers.BuildTransaction(_wallet, transactionInfo.Address, transactionInfo.Amount, transactionInfo.Labels, transactionInfo.FeeRate, coins, subtractFee: true));

                    var dialog = new InsufficientBalanceDialogViewModel(BalanceType.Pocket, transactionResult, wallet.Synchronizer.UsdExchangeRate);
                    var result = await NavigateDialog(dialog, NavigationTarget.DialogScreen);

                    if (result.Result)
                    {
                        Navigate().To(new OptimisePrivacyViewModel(wallet, transactionInfo, broadcaster, transactionResult));
                    }
                    else
                    {
                        Navigate().BackTo <SendViewModel>();
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                await ShowErrorAsync("Transaction Building", ex.ToUserFriendlyString(), "Wasabi was unable to create your transaction.");

                Navigate().BackTo <SendViewModel>();
            }
        }
コード例 #2
0
        public PrivacyControlViewModel(Wallet wallet, TransactionInfo transactionInfo, bool isSilent)
        {
            _wallet          = wallet;
            _transactionInfo = transactionInfo;
            _isSilent        = isSilent;

            _pocketSource = new SourceList <PocketViewModel>();

            _pocketSource.Connect()
            .Bind(out _pockets)
            .Subscribe();

            var selected = _pocketSource.Connect()
                           .AutoRefresh()
                           .Filter(x => x.IsSelected);

            _selectedPockets = selected.AsObservableList();

            selected.Sum(x => x.TotalBtc)
            .Subscribe(x =>
            {
                IsWarningOpen = _selectedPockets.Count > 1 && _selectedPockets.Items.Any(x => x.Labels == CoinPocketHelper.PrivateFundsText);

                StillNeeded    = transactionInfo.Amount.ToDecimal(MoneyUnit.BTC) - x;
                EnoughSelected = StillNeeded <= 0;
            });

            StillNeeded = transactionInfo.Amount.ToDecimal(MoneyUnit.BTC);

            SetupCancel(enableCancel: false, enableCancelOnEscape: true, enableCancelOnPressed: false);
            EnableBack = true;

            NextCommand = ReactiveCommand.Create(Complete, this.WhenAnyValue(x => x.EnoughSelected));

            EnableAutoBusyOn(NextCommand);
        }
コード例 #3
0
 private async Task <SmartTransaction> GetFinalTransactionAsync(SmartTransaction transaction, TransactionInfo transactionInfo)
 {
     if (transactionInfo.PayJoinClient is { })
コード例 #4
0
        public SendViewModel(WalletViewModel walletVm, TransactionBroadcaster broadcaster) : base(NavigationMode.Normal)
        {
            _to                    = "";
            _owner                 = walletVm;
            _transactionInfo       = new TransactionInfo();
            _labels                = new ObservableCollection <string>();
            _lastXAxisCurrentValue = _xAxisCurrentValue;

            ExchangeRate = walletVm.Wallet.Synchronizer.UsdExchangeRate;
            PriorLabels  = new();

            this.ValidateProperty(x => x.To, ValidateToField);
            this.ValidateProperty(x => x.AmountBtc, ValidateAmount);

            this.WhenAnyValue(x => x.To)
            .Subscribe(ParseToField);

            this.WhenAnyValue(x => x.AmountBtc)
            .Subscribe(x => _transactionInfo.Amount = new Money(x, MoneyUnit.BTC));

            this.WhenAnyValue(x => x.XAxisCurrentValue)
            .Subscribe(x =>
            {
                if (x > 0)
                {
                    _transactionInfo.FeeRate = new FeeRate(GetYAxisValueFromXAxisCurrentValue(x));
                    SetXAxisCurrentValueIndex(x);
                }
            });

            this.WhenAnyValue(x => x.XAxisCurrentValueIndex)
            .Subscribe(SetXAxisCurrentValue);

            Labels.ToObservableChangeSet().Subscribe(x =>
            {
                _transactionInfo.Labels = new SmartLabel(_labels.ToArray());
            });

            PasteCommand = ReactiveCommand.CreateFromTask(async() =>
            {
                var text = await Application.Current.Clipboard.GetTextAsync();

                _parsingUrl = true;

                if (!TryParseUrl(text))
                {
                    To = text;
                    // todo validation errors.
                }

                _parsingUrl = false;
            });

            var nextCommandCanExecute =
                this.WhenAnyValue(x => x.Labels, x => x.AmountBtc, x => x.To, x => x.XAxisCurrentValue).Select(_ => Unit.Default)
                .Merge(Observable.FromEventPattern(Labels, nameof(Labels.CollectionChanged)).Select(_ => Unit.Default))
                .Select(_ =>
            {
                var allFilled = !string.IsNullOrEmpty(To) && AmountBtc > 0 && Labels.Any();
                var hasError  = Validations.Any;

                return(allFilled && !hasError);
            });

            NextCommand = ReactiveCommand.CreateFromTask(async() =>
            {
                var transactionInfo       = _transactionInfo;
                var wallet                = _owner.Wallet;
                var targetAnonymitySet    = wallet.ServiceConfiguration.GetMixUntilAnonymitySetValue();
                var mixedCoins            = wallet.Coins.Where(x => x.HdPubKey.AnonymitySet >= targetAnonymitySet).ToList();
                var totalMixedCoinsAmount = Money.FromUnit(mixedCoins.Sum(coin => coin.Amount), MoneyUnit.Satoshi);


                if (transactionInfo.Amount <= totalMixedCoinsAmount)
                {
                    try
                    {
                        try
                        {
                            var txRes = await Task.Run(() => TransactionHelpers.BuildTransaction(wallet, transactionInfo.Address, transactionInfo.Amount, transactionInfo.Labels, transactionInfo.FeeRate, mixedCoins, subtractFee: false));
                            Navigate().To(new OptimisePrivacyViewModel(wallet, transactionInfo, broadcaster, txRes));
                            return;
                        }
                        catch (InsufficientBalanceException)
                        {
                            var dialog = new InsufficientBalanceDialogViewModel(BalanceType.Private);
                            var result = await NavigateDialog(dialog, NavigationTarget.DialogScreen);

                            if (result.Result)
                            {
                                var txRes = await Task.Run(() => TransactionHelpers.BuildTransaction(wallet, transactionInfo.Address, totalMixedCoinsAmount, transactionInfo.Labels, transactionInfo.FeeRate, mixedCoins, subtractFee: true));
                                Navigate().To(new OptimisePrivacyViewModel(wallet, transactionInfo, broadcaster, txRes));
                                return;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(ex);
                        await ShowErrorAsync("Transaction Building", ex.ToUserFriendlyString(), "Wasabi was unable to create your transaction.");
                        return;
                    }
                }

                Navigate().To(new PrivacyControlViewModel(wallet, transactionInfo, broadcaster));
            }, nextCommandCanExecute);

            EnableAutoBusyOn(NextCommand);
        }
コード例 #5
0
        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.", "");
                    }
                }
            });
        }
コード例 #6
0
        public SendViewModel(WalletViewModel walletVm, TransactionBroadcaster broadcaster)
        {
            _to              = "";
            _owner           = walletVm;
            _transactionInfo = new TransactionInfo();
            _labels          = new ObservableCollection <string>();

            ExchangeRate = walletVm.Wallet.Synchronizer.UsdExchangeRate;
            PriorLabels  = new();

            this.ValidateProperty(x => x.To, ValidateToField);
            this.ValidateProperty(x => x.AmountBtc, ValidateAmount);

            this.WhenAnyValue(x => x.To)
            .Subscribe(ParseToField);

            this.WhenAnyValue(x => x.AmountBtc)
            .Subscribe(x => _transactionInfo.Amount = new Money(x, MoneyUnit.BTC));

            this.WhenAnyValue(x => x.XAxisCurrentValue)
            .Subscribe(x =>
            {
                if (x > 0)
                {
                    _transactionInfo.FeeRate = new FeeRate(GetYAxisValueFromXAxisCurrentValue(x));
                }
            });

            Labels.ToObservableChangeSet().Subscribe(x =>
            {
                _transactionInfo.Labels = new SmartLabel(_labels.ToArray());
            });

            PasteCommand = ReactiveCommand.CreateFromTask(async() =>
            {
                var text = await Application.Current.Clipboard.GetTextAsync();

                _parsingUrl = true;

                if (!TryParseUrl(text))
                {
                    To = text;
                    // todo validation errors.
                }

                _parsingUrl = false;
            });

            NextCommand = ReactiveCommand.Create(() =>
            {
                var transactionInfo    = _transactionInfo;
                var wallet             = _owner.Wallet;
                var targetAnonymitySet = wallet.ServiceConfiguration.GetMixUntilAnonymitySetValue();
                var mixedCoins         = wallet.Coins.Where(x => x.HdPubKey.AnonymitySet >= targetAnonymitySet).ToList();

                if (mixedCoins.Any())
                {
                    var intent = new PaymentIntent(
                        destination: transactionInfo.Address,
                        amount: transactionInfo.Amount,
                        subtractFee: false,
                        label: transactionInfo.Labels);

                    try
                    {
                        var txRes = wallet.BuildTransaction(
                            wallet.Kitchen.SaltSoup(),
                            intent,
                            FeeStrategy.CreateFromFeeRate(transactionInfo.FeeRate),
                            allowUnconfirmed: true,
                            mixedCoins.Select(x => x.OutPoint));

                        // Private coins are enough.
                        Navigate().To(new OptimisePrivacyViewModel(wallet, transactionInfo, broadcaster, txRes));
                        return;
                    }
                    catch (NotEnoughFundsException)
                    {
                        // Do Nothing
                    }
                }

                Navigate().To(new PrivacyControlViewModel());
            }, this.WhenAnyValue(x => x.Labels.Count).Any());
        }
コード例 #7
0
        public PrivacyControlViewModel(Wallet wallet, TransactionInfo transactionInfo, TransactionBroadcaster broadcaster)
        {
            _wallet = wallet;

            _pocketSource = new SourceList <PocketViewModel>();

            _pocketSource.Connect()
            .Bind(out _pockets)
            .Subscribe();

            var selected = _pocketSource.Connect()
                           .AutoRefresh()
                           .Filter(x => x.IsSelected);

            var selectedList = selected.AsObservableList();

            selected.Sum(x => x.TotalBtc)
            .Subscribe(x =>
            {
                StillNeeded    = transactionInfo.Amount.ToDecimal(MoneyUnit.BTC) - x;
                EnoughSelected = StillNeeded <= 0;
            });

            StillNeeded = transactionInfo.Amount.ToDecimal(MoneyUnit.BTC);

            NextCommand = ReactiveCommand.CreateFromTask(
                async() =>
            {
                var coins = selectedList.Items.SelectMany(x => x.Coins).ToArray();

                try
                {
                    try
                    {
                        var transactionResult = await Task.Run(() => TransactionHelpers.BuildTransaction(_wallet, transactionInfo.Address, transactionInfo.Amount, transactionInfo.Labels, transactionInfo.FeeRate, coins, subtractFee: false));
                        Navigate().To(new TransactionPreviewViewModel(wallet, transactionInfo, broadcaster, transactionResult));
                    }
                    catch (InsufficientBalanceException)
                    {
                        var dialog = new InsufficientBalanceDialogViewModel(BalanceType.Pocket);
                        var result = await NavigateDialog(dialog, NavigationTarget.DialogScreen);

                        if (result.Result)
                        {
                            var transactionResult = await Task.Run(() => TransactionHelpers.BuildTransaction(_wallet, transactionInfo.Address, transactionInfo.Amount, transactionInfo.Labels, transactionInfo.FeeRate, coins, subtractFee: true));
                            Navigate().To(new TransactionPreviewViewModel(wallet, transactionInfo, broadcaster, transactionResult));
                        }
                        else
                        {
                            Navigate().BackTo <SendViewModel>();
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex);
                    await ShowErrorAsync("Transaction Building", ex.ToUserFriendlyString(), "Wasabi was unable to create your transaction.");
                    Navigate().BackTo <SendViewModel>();
                }
            },
                this.WhenAnyValue(x => x.EnoughSelected));

            EnableAutoBusyOn(NextCommand);
        }