示例#1
0
        public SendViewModel(Global global, SelectCoinsViewModel selectCoinsViewModel)
        {
            Global = global;
            SelectCoinsViewModel = selectCoinsViewModel;
            AmountText           = "0.0";
            AllSelectedAmount    = Money.Zero;
            EstimatedBtcFee      = Money.Zero;

            this.WhenAnyValue(x => x.AmountText)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(amount =>
            {
                // Correct amount
                if (IsMax)
                {
                    SetAmountIfMax();
                }
                else
                {
                    Regex digitsOnly    = new Regex(@"[^\d,.]");
                    string betterAmount = digitsOnly.Replace(amount, "");     // Make it digits , and . only.

                    betterAmount          = betterAmount.Replace(',', '.');
                    int countBetterAmount = betterAmount.Count(x => x == '.');
                    if (countBetterAmount > 1)     // Do not enable typing two dots.
                    {
                        var index = betterAmount.IndexOf('.', betterAmount.IndexOf('.') + 1);
                        if (index > 0)
                        {
                            betterAmount = betterAmount.Substring(0, index);
                        }
                    }
                    var dotIndex = betterAmount.IndexOf('.');
                    if (dotIndex != -1 && betterAmount.Length - dotIndex > 8)     // Enable max 8 decimals.
                    {
                        betterAmount = betterAmount.Substring(0, dotIndex + 1 + 8);
                    }

                    if (betterAmount != amount)
                    {
                        AmountText = betterAmount;
                    }
                }
            });

            _outputAmount = this.WhenAnyValue(x => x.AmountText,
                                              (amountText) =>
            {
                Money.TryParse(amountText.TrimStart('~', ' '), out Money outputAmount);
                return(outputAmount);
            }).ToProperty(this, x => x.OutputAmount);

            this.WhenAnyValue(x => x.IsMax)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(isMax =>
            {
                if (isMax)
                {
                    SetAmountIfMax();
                }
            });

            Observable.FromEventPattern(SelectCoinsViewModel, nameof(SelectCoinsViewModel.SelectionChanged))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ => SetFees());

            _minMaxFeeTargetsEqual = this.WhenAnyValue(x => x.MinimumFeeTarget, x => x.MaximumFeeTarget, (x, y) => x == y)
                                     .ToProperty(this, x => x.MinMaxFeeTargetsEqual, scheduler: RxApp.MainThreadScheduler);

            SetFeeTargetLimits();
            FeeTarget = Global.UiConfig.FeeTarget;
            FeeRate   = new FeeRate((decimal)50); //50 sat/vByte placeholder til loads
            SetFees();

            Observable
            .FromEventPattern <AllFeeEstimate>(Global.FeeProviders, nameof(Global.FeeProviders.AllFeeEstimateChanged))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                SetFeeTargetLimits();

                if (FeeTarget < MinimumFeeTarget)     // Should never happen.
                {
                    FeeTarget = MinimumFeeTarget;
                }
                else if (FeeTarget > MaximumFeeTarget)
                {
                    FeeTarget = MaximumFeeTarget;
                }

                SetFees();
            })
            .DisposeWith(Disposables);

            this.WhenAnyValue(x => x.FeeTarget)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                SetFees();
            });

            _destinationUrl = this.WhenAnyValue(x => x.DestinationString, ParseDestinationString)
                              .ToProperty(this, nameof(Url));

            var isTransactionOkToSign = this.WhenAnyValue(
                x => x.Label, x => x.Address, x => x.OutputAmount,
                x => x.SelectCoinsViewModel.SelectedAmount,
                x => x.EstimatedBtcFee,
                (label, address, outputAmount, selectedAmount, feeAmount) =>
            {
                return(label.NotNullAndNotEmpty() &&
                       address is not null &&
                       outputAmount > Money.Zero &&
                       outputAmount + feeAmount <= selectedAmount);
            });

            _isTransactionOkToSign = isTransactionOkToSign
                                     .ToProperty(this, x => x.IsTransactionOkToSign);

            SendTransactionCommand = ReactiveCommand.CreateFromTask <string, bool>(SendTransaction, isTransactionOkToSign);
        }
示例#2
0
        public SendViewModel(Global global, SelectCoinsViewModel selectCoinsViewModel)
        {
            Global = global;
            SelectCoinsViewModel = selectCoinsViewModel;
            AmountText           = "0.0";
            AllSelectedAmount    = Money.Zero;
            EstimatedBtcFee      = Money.Zero;

            this.WhenAnyValue(x => x.AmountText)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(amount =>
            {
                // Correct amount
                if (IsMax)
                {
                    SetAmountIfMax();
                }
                else
                {
                    Regex digitsOnly    = new Regex(@"[^\d,.]");
                    string betterAmount = digitsOnly.Replace(amount, "");     // Make it digits , and . only.

                    betterAmount          = betterAmount.Replace(',', '.');
                    int countBetterAmount = betterAmount.Count(x => x == '.');
                    if (countBetterAmount > 1)     // Do not enable typing two dots.
                    {
                        var index = betterAmount.IndexOf('.', betterAmount.IndexOf('.') + 1);
                        if (index > 0)
                        {
                            betterAmount = betterAmount.Substring(0, index);
                        }
                    }
                    var dotIndex = betterAmount.IndexOf('.');
                    if (dotIndex != -1 && betterAmount.Length - dotIndex > 8)     // Enable max 8 decimals.
                    {
                        betterAmount = betterAmount.Substring(0, dotIndex + 1 + 8);
                    }

                    if (betterAmount != amount)
                    {
                        AmountText = betterAmount;
                    }
                }
            });

            this.WhenAnyValue(x => x.IsMax)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(isMax =>
            {
                if (isMax)
                {
                    SetAmountIfMax();
                }
            });

            Observable.FromEventPattern(SelectCoinsViewModel, nameof(SelectCoinsViewModel.SelectionChanged))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ => SetFees());

            _minMaxFeeTargetsEqual = this.WhenAnyValue(x => x.MinimumFeeTarget, x => x.MaximumFeeTarget, (x, y) => x == y)
                                     .ToProperty(this, x => x.MinMaxFeeTargetsEqual, scheduler: RxApp.MainThreadScheduler);

            SetFeeTargetLimits();
            FeeTarget = Global.UiConfig.FeeTarget;
            FeeRate   = new FeeRate((decimal)50); //50 sat/vByte placeholder til loads
            SetFees();

            Observable
            .FromEventPattern <AllFeeEstimate>(Global.FeeProviders, nameof(Global.FeeProviders.AllFeeEstimateChanged))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                SetFeeTargetLimits();

                if (FeeTarget < MinimumFeeTarget)     // Should never happen.
                {
                    FeeTarget = MinimumFeeTarget;
                }
                else if (FeeTarget > MaximumFeeTarget)
                {
                    FeeTarget = MaximumFeeTarget;
                }

                SetFees();
            })
            .DisposeWith(Disposables);



            this.WhenAnyValue(x => x.FeeTarget)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                SetFees();
            });

            var canPromptPassword = this.WhenAnyValue(x => x.Label, x => x.Address, x => x.IsBusy,
                                                      (label, addr, isBusy) =>
            {
                BitcoinAddress address;
                try
                {
                    address = BitcoinAddress.Create(addr.Trim(), Global.Network);
                }
                catch (FormatException)
                {
                    // SetWarningMessage("Invalid address.");
                    return(false);
                }
                return(!isBusy && label.Length > 0 && address is BitcoinAddress);
            });

            //_promptViewModel = new PasswordPromptViewModel("SEND");
            //_promptViewModel.ValidatePasswordCommand.Subscribe(async validPassword =>
            //{
            //    if (validPassword != null)
            //    {
            //        await ViewStackService.PopModal();
            //        await BuildTransaction(validPassword);
            //        await ViewStackService.PushPage(new SentViewModel());
            //    }
            //});
            //PromptCommand = ReactiveCommand.CreateFromObservable(() =>
            //{
            //    ViewStackService.PushModal(_promptViewModel).Subscribe();
            //    return Observable.Return(Unit.Default);
            //}, canPromptPassword);
        }
示例#3
0
        public SendViewModel(Global global, ChaincaseWalletManager walletManager, Config config, UiConfig uiConfig, SelectCoinsViewModel selectCoinsViewModel, FeeProviders feeProviders)
        {
            _global        = global;
            _walletManager = walletManager;
            _config        = config;
            _uiConfig      = uiConfig;
            _feeProviders  = feeProviders;

            SelectCoinsViewModel = selectCoinsViewModel;
            AmountText           = "0.0";
            AllSelectedAmount    = Money.Zero;
            EstimatedBtcFee      = Money.Zero;

            if (_global.IsInitialized)
            {
                OnAppInitialized(this, new AppInitializedEventArgs(_global));
            }
            else
            {
                _global.Initialized += OnAppInitialized;
            }

            _outputAmount = this.WhenAnyValue(x => x.AmountText,
                                              (amountText) =>
            {
                Money.TryParse(amountText.TrimStart('~', ' '), out Money outputAmount);
                return(outputAmount ?? OutputAmount);
            }).ToProperty(this, x => x.OutputAmount);

            this.WhenAnyValue(x => x.IsMax)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(isMax =>
            {
                if (isMax)
                {
                    SetAmountIfMax();
                }
            });

            Observable.FromEventPattern(SelectCoinsViewModel, nameof(SelectCoinsViewModel.SelectionChanged))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ => ApplyFees());

            _minMaxFeeTargetsEqual = this.WhenAnyValue(x => x.MinimumFeeTarget, x => x.MaximumFeeTarget, (x, y) => x == y)
                                     .ToProperty(this, x => x.MinMaxFeeTargetsEqual, scheduler: RxApp.MainThreadScheduler);

            SetFeeTargetLimits();
            FeeTarget = _uiConfig.FeeTarget;
            FeeRate   = new FeeRate((decimal)50); //50 sat/vByte placeholder til loads

            this.WhenAnyValue(x => x.FeeTarget)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                ApplyFees();
            });

            _destinationUrl = this.WhenAnyValue(x => x.DestinationString, ParseDestinationString)
                              .ToProperty(this, nameof(DestinationUrl));

            var isTransactionOkToSign = this.WhenAnyValue(
                x => x.Label, x => x.DestinationUrl, x => x.OutputAmount,
                x => x.SelectCoinsViewModel.SelectedAmount,
                x => x.EstimatedBtcFee,
                (label, address, outputAmount, selectedAmount, feeAmount) =>
            {
                return(label.NotNullAndNotEmpty() &&
                       DestinationUrl.Address is not null &&
                       outputAmount > Money.Zero &&
                       outputAmount + feeAmount <= selectedAmount);
            });

            _isTransactionOkToSign = isTransactionOkToSign
                                     .ToProperty(this, x => x.IsTransactionOkToSign);

            SendTransactionCommand = ReactiveCommand.CreateFromTask <string, bool>(SendTransaction, isTransactionOkToSign);
        }
示例#4
0
        public CoinJoinViewModel(ChaincaseWalletManager walletManager, Config config, INotificationManager notificationManager, SelectCoinsViewModel selectCoinsViewModel)
        {
            _walletManager       = walletManager;
            _config              = config;
            _notificationManager = notificationManager;
            CoinList             = selectCoinsViewModel;

            if (Disposables != null)
            {
                throw new Exception("Wallet opened before it was closed.");
            }

            Disposables = new CompositeDisposable();

            // Infer coordinator fee
            var registrableRound = _walletManager.CurrentWallet?.ChaumianClient?.State?.GetRegistrableRoundOrDefault();

            CoordinatorFeePercent = registrableRound?.State?.CoordinatorFeePercent.ToString() ?? "0.003";

            // Select most advanced coin join round
            ClientRound mostAdvancedRound = _walletManager.CurrentWallet?.ChaumianClient?.State?.GetMostAdvancedRoundOrDefault();

            if (mostAdvancedRound != default)
            {
                RoundPhaseState = new RoundPhaseState(mostAdvancedRound.State.Phase, _walletManager.CurrentWallet.ChaumianClient?.State.IsInErrorState ?? false);
                RoundTimesout   = mostAdvancedRound.State.Phase == RoundPhase.InputRegistration ? mostAdvancedRound.State.InputRegistrationTimesout : DateTimeOffset.UtcNow;
                PeersRegistered = mostAdvancedRound.State.RegisteredPeerCount;
                PeersQueued     = mostAdvancedRound.State.QueuedPeerCount;
                PeersNeeded     = mostAdvancedRound.State.RequiredPeerCount;
                RequiredBTC     = mostAdvancedRound.State.CalculateRequiredAmount();
            }
            else
            {
                RoundPhaseState = new RoundPhaseState(RoundPhase.InputRegistration, false);
                RoundTimesout   = DateTimeOffset.UtcNow;
                PeersRegistered = 0;
                PeersQueued     = 0;
                PeersNeeded     = 100;
                RequiredBTC     = Money.Parse("0.01");
            }

            // Set time left in round
            this.WhenAnyValue(x => x.RoundTimesout)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                TimeLeftTillRoundTimeout = TimeUntilOffset(RoundTimesout);
            });

            Task.Run(async() =>
            {
                while (_walletManager.CurrentWallet?.ChaumianClient == null)
                {
                    await Task.Delay(50).ConfigureAwait(false);
                }

                // Update view model state on chaumian client state updates
                Observable.FromEventPattern(_walletManager.CurrentWallet.ChaumianClient, nameof(_walletManager.CurrentWallet.ChaumianClient.CoinQueued))
                .Merge(Observable.FromEventPattern(_walletManager.CurrentWallet.ChaumianClient, nameof(_walletManager.CurrentWallet.ChaumianClient.OnDequeue)))
                .Merge(Observable.FromEventPattern(_walletManager.CurrentWallet.ChaumianClient, nameof(_walletManager.CurrentWallet.ChaumianClient.StateUpdated)))
                .ObserveOn(RxApp.MainThreadScheduler)
                .Subscribe(_ => UpdateStates())
                .DisposeWith(Disposables);

                // Remove notification on unconfirming status in coin join round
                Observable.FromEventPattern(_walletManager.CurrentWallet.ChaumianClient, nameof(_walletManager.CurrentWallet.ChaumianClient.OnDequeue))
                .Subscribe(pattern =>
                {
                    var e = (DequeueResult)pattern.EventArgs;
                    try
                    {
                        foreach (var success in e.Successful.Where(x => x.Value.Any()))
                        {
                            DequeueReason reason = success.Key;
                            if (reason == DequeueReason.UserRequested)
                            {
                                _notificationManager.RemoveAllPendingNotifications();
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.LogWarning(ex);
                    }
                })
                .DisposeWith(Disposables);
            });

            // Update timeout label
            Observable.Interval(TimeSpan.FromSeconds(1))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                TimeLeftTillRoundTimeout = TimeUntilOffset(RoundTimesout);
            }).DisposeWith(Disposables);
        }