private async void OnInit(object sender, EventArgs args)
        {
            Initializing -= OnInit;

            while (Global.Wallet.State < WalletState.Initialized)
            {
                await Task.Delay(200);
            }

            Device.BeginInvokeOnMainThread(() =>
            {
                CoinList = new CoinListViewModel();
                Observable.FromEventPattern(Global.Wallet.TransactionProcessor, nameof(Global.Wallet.TransactionProcessor.WalletRelevantTransactionProcessed))
                .Throttle(TimeSpan.FromSeconds(0.1))
                .ObserveOn(RxApp.MainThreadScheduler)
                .Subscribe(_ =>
                {
                    Balance         = Global.Wallet.Coins.TotalAmount().ToString();
                    HasPrivateCoins = Enumerable.Where(
                        Global.Wallet.Coins,
                        c => c.Unspent && !c.SpentAccordingToBackend && c.AnonymitySet > 1
                        ).Sum(c => (long?)c.Amount) > 0;
                });

                Observable.FromEventPattern(Global.Wallet, nameof(Global.Wallet.NewBlockProcessed))
                .Merge(Observable.FromEventPattern(Global.Wallet.TransactionProcessor, nameof(Global.Wallet.TransactionProcessor.WalletRelevantTransactionProcessed)))
                .Throttle(TimeSpan.FromSeconds(3))
                .ObserveOn(RxApp.MainThreadScheduler)
                .Subscribe(async _ => await TryRewriteTableAsync());

                CoinJoinViewModel   = new CoinJoinViewModel(CoinList);
                SendAmountViewModel = new SendAmountViewModel(CoinList);
            });
        }
Example #2
0
        public SendViewModel(IScreen hostScreen) : base(hostScreen)
        {
            CoinList   = new CoinListViewModel(hostScreen);
            AmountText = "0.0";

            this.WhenAnyValue(x => x.AmountText)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(amount =>
            {
                // Correct amount
                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;
                }
            });

            BuildTransactionCommand = ReactiveCommand.CreateFromTask(async() =>
            {
                try
                {
                    IsBusy   = true;
                    Password = Guard.Correct(Password);
                    Memo     = Memo.Trim(',', ' ').Trim();

                    var selectedCoinViewModels = CoinList.Coins.Where(cvm => cvm.IsSelected);
                    var selectedCoinReferences = selectedCoinViewModels.Select(cvm => new TxoRef(cvm.Model.TransactionId, cvm.Model.Index)).ToList();
                    if (!selectedCoinReferences.Any())
                    {
                        //SetWarningMessage("No coins are selected to spend.");
                        return;
                    }

                    BitcoinAddress address;
                    try
                    {
                        address = BitcoinAddress.Create(Address.Trim(), Global.Network);
                    }
                    catch (FormatException)
                    {
                        // SetWarningMessage("Invalid address.");
                        return;
                    }

                    var script = address.ScriptPubKey;
                    var amount = Money.Zero;
                    if (!Money.TryParse(AmountText, out amount) || amount == Money.Zero)
                    {
                        // SetWarningMessage($"Invalid amount.");
                        return;
                    }

                    if (amount == selectedCoinViewModels.Sum(x => x.Amount))
                    {
                        // SetWarningMessage("Looks like you want to spend a whole coin. Try Max button instead.");
                        return;
                    }

                    var memo      = Memo;
                    var operation = new WalletService.Operation(script, amount, memo);

                    var feeTarget = 500;
                    var result    = await Task.Run(() => Global.WalletService.BuildTransaction(Password, new[] { operation }, feeTarget, allowUnconfirmed: true, allowedInputs: selectedCoinReferences));
                    SmartTransaction signedTransaction = result.Transaction;

                    await Task.Run(async() => await Global.WalletService.SendTransactionAsync(signedTransaction));
                }
                catch (InsufficientBalanceException ex)
                {
                    Money needed = ex.Minimum - ex.Actual;
                    Logger.LogDebug <SendViewModel>(ex);
                    //SetWarningMessage($"Not enough coins selected. You need an estimated {needed.ToString(false, true)} BTC more to make this transaction.");
                }
                catch (Exception ex)
                {
                    Logger.LogDebug <SendViewModel>(ex);
                    //SetWarningMessage(ex.ToTypeMessageString());
                }
                finally
                {
                    IsBusy = false;
                }
            },
                                                                     this.WhenAny(x => x.AmountText, x => x.Address, x => x.IsBusy,
                                                                                  (amountText, address, busy) => !string.IsNullOrWhiteSpace(amountText.Value) && !string.IsNullOrWhiteSpace(address.Value) && !busy.Value)
                                                                     .ObserveOn(RxApp.MainThreadScheduler));
        }
Example #3
0
 public CoinViewModel(CoinListViewModel owner, SmartCoin model, IScreen hostScreen) : base(hostScreen)
 {
     Model  = model;
     _owner = owner;
 }
Example #4
0
        public SendAmountViewModel(CoinListViewModel coinList)
            : base(Locator.Current.GetService <IViewStackService>())
        {
            Global            = Locator.Current.GetService <Global>();
            CoinList          = coinList;
            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;
                    }
                }
            });

            _sendFromText = this
                            .WhenAnyValue(x => x.CoinList.SelectPrivateSwitchState, x => x.CoinList.SelectedCount)
                            .Select(tup =>
            {
                var coinGrammaticalNumber = tup.Item2 == 1 ? " Coin ▾" : " Coins ▾";
                return(tup.Item1 ? "Auto-Select Private ▾" : (tup.Item2.ToString() + coinGrammaticalNumber));
            })
                            .ToProperty(this, nameof(SendFromText));

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

            Observable.FromEventPattern(CoinList, nameof(CoinList.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);

            GoNext = ReactiveCommand.CreateFromObservable(() =>
            {
                ViewStackService.PushPage(new SendWhoViewModel(this)).Subscribe();
                return(Observable.Return(Unit.Default));
            }, this.WhenAnyValue(
                                                              x => x.AmountText,
                                                              x => x.CoinList.SelectedAmount,
                                                              (amountToSpend, selectedAmount) =>
            {
                return(AmountTextPositive(amountToSpend) &&
                       Money.Parse(amountToSpend.TrimStart('~', ' ')) + EstimatedBtcFee <= selectedAmount);
            }));

            SelectCoins = ReactiveCommand.CreateFromObservable(() =>
            {
                ViewStackService.PushModal(CoinList).Subscribe();
                return(Observable.Return(Unit.Default));
            });

            SelectFee = ReactiveCommand.CreateFromObservable(() =>
            {
                ViewStackService.PushModal(new FeeViewModel(this)).Subscribe();
                return(Observable.Return(Unit.Default));
            });

            this.WhenAnyValue(x => x.FeeTarget)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                SetFees();
            });
        }
        public CoinJoinViewModel(CoinListViewModel coinList)
            : base(Locator.Current.GetService <IViewStackService>())
        {
            Global = Locator.Current.GetService <Global>();

            SetBalance();
            notificationManager = Global.NotificationManager;

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

            Disposables = new CompositeDisposable();
            CoinList    = coinList;

            Observable
            .FromEventPattern <SmartCoin>(CoinList, nameof(CoinList.DequeueCoinsPressed))
            .Subscribe(async x => await DoDequeueAsync(x.EventArgs));

            this.WhenAnyValue(x => x.RoundTimesout)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                TimeSpan left            = RoundTimesout - DateTimeOffset.UtcNow;
                TimeLeftTillRoundTimeout = left > TimeSpan.Zero ? left : TimeSpan.Zero;     // Make sure cannot be less than zero.
            });

            AmountQueued = Money.Zero;

            var registrableRound = Global.Wallet.ChaumianClient.State.GetRegistrableRoundOrDefault();

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

            Observable.FromEventPattern(Global.Wallet.ChaumianClient, nameof(Global.Wallet.ChaumianClient.CoinQueued))
            .Merge(Observable.FromEventPattern(Global.Wallet.ChaumianClient, nameof(Global.Wallet.ChaumianClient.OnDequeue)))
            .Merge(Observable.FromEventPattern(Global.Wallet.ChaumianClient, nameof(Global.Wallet.ChaumianClient.StateUpdated)))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ => UpdateStates())
            .DisposeWith(Disposables);

            Observable.FromEventPattern(Global.Wallet.ChaumianClient, nameof(Global.Wallet.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);

            ClientRound mostAdvancedRound = Global.Wallet.ChaumianClient?.State?.GetMostAdvancedRoundOrDefault();

            if (mostAdvancedRound != default)
            {
                RoundPhaseState = new RoundPhaseState(mostAdvancedRound.State.Phase, Global.Wallet.ChaumianClient?.State.IsInErrorState ?? false);
                RoundTimesout   = mostAdvancedRound.State.Phase == RoundPhase.InputRegistration ? mostAdvancedRound.State.InputRegistrationTimesout : DateTimeOffset.UtcNow;
                PeersRegistered = mostAdvancedRound.State.RegisteredPeerCount;
                PeersNeeded     = mostAdvancedRound.State.RequiredPeerCount;
            }
            else
            {
                RoundPhaseState = new RoundPhaseState(RoundPhase.InputRegistration, false);
                RoundTimesout   = DateTimeOffset.UtcNow;
                PeersRegistered = 0;
                PeersNeeded     = 100;
            }

            Observable.Interval(TimeSpan.FromSeconds(1))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                TimeSpan left            = RoundTimesout - DateTimeOffset.UtcNow;
                TimeLeftTillRoundTimeout = left > TimeSpan.Zero ? left : TimeSpan.Zero;     // Make sure cannot be less than zero.
            }).DisposeWith(Disposables);

            CoinJoinCommand     = ReactiveCommand.CreateFromTask <string, bool>(DoEnqueueAsync);
            ExitCoinJoinCommand = ReactiveCommand.CreateFromTask(ExitCoinJoinAsync);

            var canPromptPassword = this.WhenAnyValue(
                x => x.CoinList.SelectedAmount,
                x => x.RequiredBTC,
                (amnt, rBTC) =>
            {
                return(!(rBTC is null) && !(amnt is null) && amnt >= rBTC);
            });