Ejemplo n.º 1
0
        private async Task OnNext(Wallet wallet, TransactionBroadcaster broadcaster, BuildTransactionResult transaction)
        {
            var transactionAuthorizationInfo = new TransactionAuthorizationInfo(transaction);

            var authResult = await AuthorizeAsync(wallet, transactionAuthorizationInfo);

            if (authResult)
            {
                IsBusy = true;

                try
                {
                    var finalTransaction = await GetFinalTransactionAsync(transactionAuthorizationInfo.Transaction, _info);
                    await SendTransaction(wallet, broadcaster, finalTransaction);

                    Navigate().To(new SendSuccessViewModel());
                }
                catch (Exception ex)
                {
                    await ShowErrorAsync("Transaction", ex.ToUserFriendlyString(), "Wasabi was unable to send your transaction.");
                }

                IsBusy = false;
            }
        }
Ejemplo n.º 2
0
        private async Task SendTransaction(Wallet wallet, TransactionBroadcaster broadcaster, SmartTransaction transaction)
        {
            // Dequeue any coin-joining coins.
            await wallet.ChaumianClient.DequeueAllCoinsFromMixAsync(DequeueReason.TransactionBuilding);

            await broadcaster.SendTransactionAsync(transaction);
        }
Ejemplo n.º 3
0
        public BroadcastTransactionViewModel(
            BitcoinStore store,
            Network network,
            TransactionBroadcaster broadcaster,
            SmartTransaction transaction)
        {
            Title = "Broadcast Transaction";

            var nullMoney  = new Money(-1L);
            var nullOutput = new TxOut(nullMoney, Script.Empty);

            var psbt = PSBT.FromTransaction(transaction.Transaction, network);

            TxOut GetOutput(OutPoint outpoint) =>
            store.TransactionStore.TryGetTransaction(outpoint.Hash, out var prevTxn)
                                        ? prevTxn.Transaction.Outputs[outpoint.N]
                                        : nullOutput;

            var inputAddressAmount = psbt.Inputs
                                     .Select(x => x.PrevOut)
                                     .Select(GetOutput)
                                     .ToArray();

            var outputAddressAmount = psbt.Outputs
                                      .Select(x => x.GetCoin().TxOut)
                                      .ToArray();

            var psbtTxn = psbt.GetOriginalTransaction();

            _transactionId     = psbtTxn.GetHash().ToString();
            _inputCount        = inputAddressAmount.Length;
            _inputCountString  = $" input{TextHelpers.AddSIfPlural(_inputCount)} and ";
            _outputCount       = outputAddressAmount.Length;
            _outputCountString = $" output{TextHelpers.AddSIfPlural(_outputCount)}.";
            _totalInputValue   = inputAddressAmount.Any(x => x.Value == nullMoney)
                                ? null
                                : inputAddressAmount.Select(x => x.Value).Sum();
            _totalOutputValue = outputAddressAmount.Any(x => x.Value == nullMoney)
                                ? null
                                : outputAddressAmount.Select(x => x.Value).Sum();
            _networkFee = TotalInputValue is null || TotalOutputValue is null
                                ? null
                                : TotalInputValue - TotalOutputValue;

            EnableCancel = true;

            EnableBack = false;

            this.WhenAnyValue(x => x.IsBusy)
            .Subscribe(x => EnableCancel = !x);

            var nextCommandCanExecute = this.WhenAnyValue(x => x.IsBusy)
                                        .Select(x => !x);

            NextCommand = ReactiveCommand.CreateFromTask(
                async() => await OnNext(broadcaster, transaction),
                nextCommandCanExecute);

            EnableAutoBusyOn(NextCommand);
        }
Ejemplo n.º 4
0
        public Global(string dataDir, string torLogsFile, Config config, UiConfig uiConfig, WalletManager walletManager)
        {
            using (BenchmarkLogger.Measure())
            {
                StoppingCts = new CancellationTokenSource();
                DataDir     = dataDir;
                Config      = config;
                UiConfig    = uiConfig;
                TorSettings = new TorSettings(DataDir, torLogsFile, distributionFolderPath: EnvironmentHelpers.GetFullBaseDirectory());

                HostedServices = new HostedServices();
                WalletManager  = walletManager;

                WalletManager.OnDequeue += WalletManager_OnDequeue;
                WalletManager.WalletRelevantTransactionProcessed += WalletManager_WalletRelevantTransactionProcessed;

                var networkWorkFolderPath = Path.Combine(DataDir, "BitcoinStore", Network.ToString());
                var transactionStore      = new AllTransactionStore(networkWorkFolderPath, Network);
                var indexStore            = new IndexStore(Path.Combine(networkWorkFolderPath, "IndexStore"), Network, new SmartHeaderChain());
                var mempoolService        = new MempoolService();
                var blocks = new FileSystemBlockRepository(Path.Combine(networkWorkFolderPath, "Blocks"), Network);

                BitcoinStore = new BitcoinStore(indexStore, transactionStore, mempoolService, blocks);

                HttpClientFactory = Config.UseTor
                                        ? new HttpClientFactory(Config.TorSocks5EndPoint, backendUriGetter: () => Config.GetCurrentBackendUri())
                                        : new HttpClientFactory(torEndPoint: null, backendUriGetter: () => Config.GetFallbackBackendUri());

                Synchronizer           = new WasabiSynchronizer(Network, BitcoinStore, HttpClientFactory);
                LegalChecker           = new(DataDir);
                TransactionBroadcaster = new TransactionBroadcaster(Network, BitcoinStore, HttpClientFactory, WalletManager);
            }
        }
Ejemplo n.º 5
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));
        }
Ejemplo n.º 6
0
        public TransactionPreviewViewModel(Wallet wallet, TransactionInfo info, TransactionBroadcaster broadcaster,
                                           BuildTransactionResult transaction)
        {
            _wallet               = wallet;
            _info                 = info;
            EnableCancel          = true;
            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 { };

            NextCommand = ReactiveCommand.CreateFromTask(async() => await OnNext(wallet, broadcaster, transaction));
        }
Ejemplo n.º 7
0
        public OptimisePrivacyViewModel(Wallet wallet,
                                        TransactionInfo transactionInfo, TransactionBroadcaster broadcaster, BuildTransactionResult requestedTransaction)
        {
            _wallet = wallet;
            _requestedTransaction = requestedTransaction;
            _transactionInfo      = transactionInfo;

            this.WhenAnyValue(x => x.SelectedPrivacySuggestion)
            .Where(x => x is { })
Ejemplo n.º 8
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()));

            EnableCancel = true;

            EnableBack = true;

            PasteCommand = ReactiveCommand.CreateFromTask(async() => await OnPaste());

            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() => await OnNext(broadcaster), nextCommandCanExecute);

            EnableAutoBusyOn(NextCommand);
        }
 public WalletManagerViewModel(WalletManager walletManager, UiConfig uiConfig, BitcoinStore bitcoinStore,
                               LegalChecker legalChecker, TransactionBroadcaster broadcaster)
 {
     WalletManager            = walletManager;
     BitcoinStore             = bitcoinStore;
     LegalChecker             = legalChecker;
     _walletDictionary        = new Dictionary <Wallet, WalletViewModelBase>();
     _walletActionsDictionary = new Dictionary <WalletViewModelBase, List <NavBarItemViewModel> >();
     _actions = new ObservableCollection <NavBarItemViewModel>();
     _wallets = new ObservableCollection <WalletViewModelBase>();
     _loggedInAndSelectedAlwaysFirst = true;
     _transactionBroadcaster         = broadcaster;
Ejemplo n.º 10
0
        private async Task OnNext(Wallet wallet, TransactionBroadcaster broadcaster, BuildTransactionResult transaction)
        {
            var transactionAuthorizationInfo = new TransactionAuthorizationInfo(transaction);

            var authResult = await AuthorizeAsync(wallet, transactionAuthorizationInfo);

            if (authResult)
            {
                await SendTransaction(wallet, broadcaster, transactionAuthorizationInfo.Transaction);

                Navigate().To(new SendSuccessViewModel());
            }
        }
Ejemplo n.º 11
0
        private async Task OnNext(TransactionBroadcaster broadcaster, SmartTransaction transaction)
        {
            try
            {
                await broadcaster.SendTransactionAsync(transaction);

                Navigate().To(new SuccessBroadcastTransactionViewModel());
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                await ShowErrorAsync(Title, ex.ToUserFriendlyString(), "It was not possible to broadcast the transaction.");
            }
        }
        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.", "");
                }
            });
        }
Ejemplo n.º 13
0
        public async Task InitializeWalletServiceAsync(KeyManager keyManager)
        {
            using (_cancelWalletServiceInitialization = new CancellationTokenSource())
            {
                var token = _cancelWalletServiceInitialization.Token;
                while (!Initialized)
                {
                    await Task.Delay(100, token);
                }

                if (Config.UseTor)
                {
                    ChaumianClient = new CoinJoinClient(Synchronizer, Network, keyManager, () => Config.GetCurrentBackendUri(), Config.TorSocks5EndPoint);
                }
                else
                {
                    ChaumianClient = new CoinJoinClient(Synchronizer, Network, keyManager, Config.GetFallbackBackendUri(), null);
                }

                try
                {
                    keyManager.CorrectBlockHeights(BitcoinStore.HashChain); // Block heights are wrong sometimes. It's a hack. We have to retroactively fix existing wallets, but also we have to figure out where we ruin the block heights.
                }
                catch (Exception ex)                                        // Whatever this is not critical, but let's log it.
                {
                    Logger.LogWarning(ex);
                }

                WalletService = new WalletService(BitcoinStore, keyManager, Synchronizer, ChaumianClient, Nodes, DataDir, Config.ServiceConfiguration, FeeProviders, BitcoinCoreNode?.RpcClient);

                ChaumianClient.Start();
                Logger.LogInfo("Start Chaumian CoinJoin service...");

                Logger.LogInfo($"Starting {nameof(WalletService)}...");
                await WalletService.InitializeAsync(token);

                Logger.LogInfo($"{nameof(WalletService)} started.");

                token.ThrowIfCancellationRequested();
                WalletService.TransactionProcessor.CoinReceived += CoinReceived;

                TransactionBroadcaster.AddWalletService(WalletService);
            }
            _cancelWalletServiceInitialization = null;             // Must make it null explicitly, because dispose won't make it null.
        }
Ejemplo n.º 14
0
        private async Task OnNext(TransactionBroadcaster broadcaster)
        {
            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 txRes = await Task.Run(() => TransactionHelpers.BuildTransaction(wallet, transactionInfo.Address, totalMixedCoinsAmount, transactionInfo.Labels, transactionInfo.FeeRate, mixedCoins, subtractFee: true));

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

                        if (result.Result)
                        {
                            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));
        }
Ejemplo n.º 15
0
    public Global(string dataDir, Config config, UiConfig uiConfig, WalletManager walletManager)
    {
        using (BenchmarkLogger.Measure())
        {
            DataDir     = dataDir;
            Config      = config;
            UiConfig    = uiConfig;
            TorSettings = new TorSettings(DataDir, distributionFolderPath: EnvironmentHelpers.GetFullBaseDirectory(), Config.TerminateTorOnExit, Environment.ProcessId);

            HostedServices = new HostedServices();
            WalletManager  = walletManager;

            var networkWorkFolderPath = Path.Combine(DataDir, "BitcoinStore", Network.ToString());
            AllTransactionStore = new AllTransactionStore(networkWorkFolderPath, Network);
            SmartHeaderChain smartHeaderChain = new(maxChainSize : 20_000);
            IndexStore = new IndexStore(Path.Combine(networkWorkFolderPath, "IndexStore"), Network, smartHeaderChain);
            var mempoolService = new MempoolService();
            var blocks         = new FileSystemBlockRepository(Path.Combine(networkWorkFolderPath, "Blocks"), Network);

            BitcoinStore = new BitcoinStore(IndexStore, AllTransactionStore, mempoolService, blocks);

            if (Config.UseTor)
            {
                BackendHttpClientFactory  = new HttpClientFactory(TorSettings.SocksEndpoint, backendUriGetter: () => Config.GetCurrentBackendUri());
                ExternalHttpClientFactory = new HttpClientFactory(TorSettings.SocksEndpoint, backendUriGetter: null);
            }
            else
            {
                BackendHttpClientFactory  = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => Config.GetFallbackBackendUri());
                ExternalHttpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: null);
            }

            Synchronizer           = new WasabiSynchronizer(BitcoinStore, BackendHttpClientFactory);
            LegalChecker           = new(DataDir);
            TransactionBroadcaster = new TransactionBroadcaster(Network, BitcoinStore, BackendHttpClientFactory, WalletManager);

            RoundStateUpdaterCircuit = new PersonCircuit();

            Cache = new MemoryCache(new MemoryCacheOptions
            {
                SizeLimit = 1_000,
                ExpirationScanFrequency = TimeSpan.FromSeconds(30)
            });
Ejemplo n.º 16
0
        public SendViewModel(WalletViewModel walletVm, TransactionBroadcaster broadcaster, Config config, UiConfig uiConfig, HttpClientFactory httpClientFactory) : base(NavigationMode.Normal)
        {
            _to                    = "";
            _owner                 = walletVm;
            _transactionInfo       = new TransactionInfo();
            _labels                = new ObservableCollection <string>();
            _lastXAxisCurrentValue = _xAxisCurrentValue;

            SelectionMode = NavBarItemSelectionMode.Button;

            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)
                {
                    _feeRate = new FeeRate(GetYAxisValueFromXAxisCurrentValue(x));
                    SetXAxisCurrentValueIndex(x);
                }
            });

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

            this.WhenAnyValue(x => x.PayJoinEndPoint)
            .Subscribe(endPoint =>
            {
                if (endPoint is { })
                {
                    _transactionInfo.PayJoinClient = GetPayjoinClient(endPoint, config, httpClientFactory);
                    IsPayJoin = true;
                }
Ejemplo n.º 17
0
        public void OnAppInitialized(object sender, AppInitializedEventArgs args)
        {
            _transactionBroadcaster = args.TransactionBroadcaster;

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

                if (FeeTarget < MinimumFeeTarget)    // Should never happen.
                {
                    FeeTarget = MinimumFeeTarget;
                }
                else if (FeeTarget > MaximumFeeTarget)
                {
                    FeeTarget = MaximumFeeTarget;
                }
            })
            .DisposeWith(Disposables);
        }
Ejemplo n.º 18
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>();
            }
        }
Ejemplo n.º 19
0
        private async Task OnNext(Wallet wallet, TransactionBroadcaster broadcaster, BuildTransactionResult transaction)
        {
            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.", "");
            }
        }
Ejemplo n.º 20
0
        public Global(string dataDir, Config config, UiConfig uiConfig, WalletManager walletManager)
        {
            using (BenchmarkLogger.Measure())
            {
                DataDir     = dataDir;
                Config      = config;
                UiConfig    = uiConfig;
                TorSettings = new TorSettings(DataDir, distributionFolderPath: EnvironmentHelpers.GetFullBaseDirectory(), Config.TerminateTorOnExit, Environment.ProcessId);

                HostedServices = new HostedServices();
                WalletManager  = walletManager;

                var networkWorkFolderPath = Path.Combine(DataDir, "BitcoinStore", Network.ToString());
                var transactionStore      = new AllTransactionStore(networkWorkFolderPath, Network);
                var indexStore            = new IndexStore(Path.Combine(networkWorkFolderPath, "IndexStore"), Network, new SmartHeaderChain());
                var mempoolService        = new MempoolService();
                var blocks = new FileSystemBlockRepository(Path.Combine(networkWorkFolderPath, "Blocks"), Network);

                BitcoinStore = new BitcoinStore(indexStore, transactionStore, mempoolService, blocks);

                if (Config.UseTor)
                {
                    BackendHttpClientFactory  = new HttpClientFactory(TorSettings.SocksEndpoint, backendUriGetter: () => Config.GetCurrentBackendUri());
                    ExternalHttpClientFactory = new HttpClientFactory(TorSettings.SocksEndpoint, backendUriGetter: null);
                }
                else
                {
                    BackendHttpClientFactory  = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => Config.GetFallbackBackendUri());
                    ExternalHttpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: null);
                }

                Synchronizer           = new WasabiSynchronizer(BitcoinStore, BackendHttpClientFactory);
                LegalChecker           = new(DataDir);
                TransactionBroadcaster = new TransactionBroadcaster(Network, BitcoinStore, BackendHttpClientFactory, WalletManager);
            }
        }
Ejemplo n.º 21
0
        public async Task InitializeNoWalletAsync(CancellationToken cancellationToken = default)
        {
            AddressManager = null;
            Logger.LogDebug($"{nameof(Global)}.InitializeNoWalletAsync(): Waiting for a lock");
            try
            {
                InitializationStarted = true;

                Logger.LogDebug($"{nameof(Global)}.InitializeNoWalletAsync(): Got lock");

                MemoryCache Cache = new MemoryCache(new MemoryCacheOptions
                {
                    SizeLimit = 1_000,
                    ExpirationScanFrequency = TimeSpan.FromSeconds(30)
                });
                var bstoreInitTask           = _bitcoinStore.InitializeAsync();
                var addressManagerFolderPath = Path.Combine(DataDir, "AddressManager");

                AddressManagerFilePath = Path.Combine(addressManagerFolderPath, $"AddressManager{Network}.dat");
                var addrManTask = InitializeAddressManagerBehaviorAsync();

                var blocksFolderPath     = Path.Combine(DataDir, $"Blocks{Network}");
                var userAgent            = Constants.UserAgents.RandomElement();
                var connectionParameters = new NodeConnectionParameters {
                    UserAgent = userAgent
                };

                #region TorProcessInitialization

                if (_config.UseTor)
                {
                    await _torManager.StartAsync(ensureRunning : false, DataDir);

                    Logger.LogInfo($"{nameof(_torManager)} is initialized.");
                }

                Logger.LogInfo($"{nameof(Global)}.InitializeNoWalletAsync():{nameof(_torManager)} is initialized.");

                #endregion TorProcessInitialization

                #region BitcoinStoreInitialization

                await bstoreInitTask.ConfigureAwait(false);

                // Make sure that the height of the wallets will not be better than the current height of the filters.
                _walletManager.SetMaxBestHeight(_bitcoinStore.IndexStore.SmartHeaderChain.TipHeight);

                #endregion BitcoinStoreInitialization

                #region MempoolInitialization

                connectionParameters.TemplateBehaviors.Add(_bitcoinStore.CreateUntrustedP2pBehavior());

                #endregion MempoolInitialization

                #region AddressManagerInitialization

                AddressManagerBehavior addressManagerBehavior = await addrManTask.ConfigureAwait(false);

                connectionParameters.TemplateBehaviors.Add(addressManagerBehavior);

                #endregion AddressManagerInitialization

                #region P2PInitialization
                Node regTestMempoolServingNode = null;
                if (Network == Network.RegTest)
                {
                    Nodes = new NodesGroup(Network, requirements: Constants.NodeRequirements);
                    try
                    {
                        EndPoint bitcoinCoreEndpoint = _config.GetBitcoinP2pEndPoint();

                        Node node = await Node.ConnectAsync(NBitcoin.Network.RegTest, bitcoinCoreEndpoint).ConfigureAwait(false);

                        Nodes.ConnectedNodes.Add(node);

                        regTestMempoolServingNode = await Node.ConnectAsync(NBitcoin.Network.RegTest, bitcoinCoreEndpoint).ConfigureAwait(false);

                        regTestMempoolServingNode.Behaviors.Add(_bitcoinStore.CreateUntrustedP2pBehavior());
                    }
                    catch (SocketException ex)
                    {
                        Logger.LogError(ex);
                    }
                }
                else
                {
                    if (_config.UseTor)
                    {
                        // onlyForOnionHosts: false - Connect to clearnet IPs through Tor, too.
                        connectionParameters.TemplateBehaviors.Add(new SocksSettingsBehavior(_config.TorSocks5EndPoint, onlyForOnionHosts: false, networkCredential: null, streamIsolation: true));
                        // allowOnlyTorEndpoints: true - Connect only to onions and don't connect to clearnet IPs at all.
                        // This of course makes the first setting unneccessary, but it's better if that's around, in case someone wants to tinker here.
                        connectionParameters.EndpointConnector = new DefaultEndpointConnector(allowOnlyTorEndpoints: Network == Network.Main);

                        await AddKnownBitcoinFullNodeAsHiddenServiceAsync(AddressManager).ConfigureAwait(false);
                    }
                    Nodes = new NodesGroup(Network, connectionParameters, requirements: Constants.NodeRequirements);
                    Nodes.MaximumNodeConnection = 12;
                    regTestMempoolServingNode   = null;
                }

                Nodes.Connect();
                Logger.LogInfo($"{nameof(Global)}.InitializeNoWalletAsync(): Start connecting to nodes...");

                if (regTestMempoolServingNode != null)
                {
                    regTestMempoolServingNode.VersionHandshake();
                    Logger.LogInfo($"{nameof(Global)}.InitializeNoWalletAsync(): Start connecting to mempool serving regtest node...");
                }

                #endregion P2PInitialization

                #region SynchronizerInitialization

                var requestInterval = TimeSpan.FromSeconds(30);
                if (Network == Network.RegTest)
                {
                    requestInterval = TimeSpan.FromSeconds(5);
                }

                int maxFiltSyncCount = Network == Network.Main ? 1000 : 10000; // On testnet, filters are empty, so it's faster to query them together

                _synchronizer.Start(requestInterval, TimeSpan.FromMinutes(5), maxFiltSyncCount);
                Logger.LogInfo($"{nameof(Global)}.InitializeNoWalletAsync(): Start synchronizing filters...");

                #endregion SynchronizerInitialization

                TransactionBroadcaster = new TransactionBroadcaster(Network, _bitcoinStore, _synchronizer, Nodes, _walletManager, null);
                // CoinJoinProcessor maintains an event handler to process joins.
                // We need this reference.
                _coinJoinProcessor = new CoinJoinProcessor(_synchronizer, _walletManager, null);

                #region Blocks provider

                var blockProvider = new CachedBlockProvider(
                    new SmartBlockProvider(
                        new P2pBlockProvider(Nodes, null, _synchronizer, _config.ServiceConfiguration, Network),
                        Cache),
                    new FileSystemBlockRepository(blocksFolderPath, Network));

                #endregion Blocks provider

                _walletManager.RegisterServices(_bitcoinStore, _synchronizer, Nodes, _config.ServiceConfiguration, _feeProviders, blockProvider);

                Initialized(this, new AppInitializedEventArgs(this));
                IsInitialized = true;
            }
Ejemplo n.º 22
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);
        }
Ejemplo n.º 23
0
        public async Task SendTestsAsync()
        {
            (string password, IRPCClient rpc, Network network, _, ServiceConfiguration serviceConfiguration, BitcoinStore bitcoinStore, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            bitcoinStore.IndexStore.NewFilter += Common.Wallet_NewFilterProcessed;
            // Create the services.
            // 1. Create connection service.
            var nodes = new NodesGroup(global.Config.Network, requirements: Constants.NodeRequirements);

            nodes.ConnectedNodes.Add(await RegTestFixture.BackendRegTestNode.CreateNewP2pNodeAsync());

            // 2. Create mempool service.

            Node node = await RegTestFixture.BackendRegTestNode.CreateNewP2pNodeAsync();

            node.Behaviors.Add(bitcoinStore.CreateUntrustedP2pBehavior());

            // 3. Create wasabi synchronizer service.
            var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, new Uri(RegTestFixture.BackendEndPoint), null);

            // 4. Create key manager service.
            var keyManager = KeyManager.CreateNew(out _, password);

            // 5. Create wallet service.
            var workDir = Tests.Common.GetWorkDir();

            CachedBlockProvider blockProvider = new CachedBlockProvider(
                new P2pBlockProvider(nodes, null, synchronizer, serviceConfiguration, network),
                bitcoinStore.BlockRepository);

            var walletManager = new WalletManager(network, new WalletDirectories(workDir));

            walletManager.RegisterServices(bitcoinStore, synchronizer, nodes, serviceConfiguration, synchronizer, blockProvider);

            // Get some money, make it confirm.
            var key  = keyManager.GetNextReceiveKey("foo label", out _);
            var key2 = keyManager.GetNextReceiveKey("foo label", out _);
            var txId = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(1m));

            Assert.NotNull(txId);
            await rpc.GenerateAsync(1);

            var txId2 = await rpc.SendToAddressAsync(key2.GetP2wpkhAddress(network), Money.Coins(1m));

            Assert.NotNull(txId2);
            await rpc.GenerateAsync(1);

            try
            {
                Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
                nodes.Connect();                                                                              // Start connection service.
                node.VersionHandshake();                                                                      // Start mempool service.
                synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5), 10000); // Start wasabi synchronizer service.

                // Wait until the filter our previous transaction is present.
                var blockCount = await rpc.GetBlockCountAsync();

                await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), blockCount);

                var wallet = await walletManager.AddAndStartWalletAsync(keyManager);

                var broadcaster = new TransactionBroadcaster(network, bitcoinStore, synchronizer, nodes, walletManager, rpc);

                var waitCount = 0;
                while (wallet.Coins.Sum(x => x.Amount) == Money.Zero)
                {
                    await Task.Delay(1000);

                    waitCount++;
                    if (waitCount >= 21)
                    {
                        Logger.LogInfo("Funding transaction to the wallet did not arrive.");
                        return;                         // Very rarely this test fails. I have no clue why. Probably because all these RegTests are interconnected, anyway let's not bother the CI with it.
                    }
                }

                var scp  = new Key().ScriptPubKey;
                var res2 = wallet.BuildTransaction(password, new PaymentIntent(scp, Money.Coins(0.05m), label: "foo"), FeeStrategy.CreateFromConfirmationTarget(5), allowUnconfirmed: false);

                Assert.NotNull(res2.Transaction);
                Assert.Single(res2.OuterWalletOutputs);
                Assert.Equal(scp, res2.OuterWalletOutputs.Single().ScriptPubKey);
                Assert.Single(res2.InnerWalletOutputs);
                Assert.True(res2.Fee > Money.Satoshis(2 * 100));                 // since there is a sanity check of 2sat/vb in the server
                Assert.InRange(res2.FeePercentOfSent, 0, 1);
                Assert.Single(res2.SpentCoins);
                var spentCoin = Assert.Single(res2.SpentCoins);
                Assert.Contains(new[] { key.P2wpkhScript, key2.P2wpkhScript }, x => x == spentCoin.ScriptPubKey);
                Assert.Equal(Money.Coins(1m), res2.SpentCoins.Single().Amount);
                Assert.False(res2.SpendsUnconfirmed);

                await broadcaster.SendTransactionAsync(res2.Transaction);

                Assert.Contains(res2.InnerWalletOutputs.Single(), wallet.Coins);

                #region Basic

                Script receive      = keyManager.GetNextReceiveKey("Basic", out _).P2wpkhScript;
                Money  amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 2;
                var    res          = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);

                foreach (SmartCoin coin in res.SpentCoins)
                {
                    Assert.False(coin.CoinJoinInProgress);
                    Assert.True(coin.Confirmed);
                    Assert.Null(coin.SpenderTransactionId);
                    Assert.True(coin.Unspent);
                }

                Assert.Equal(2, res.InnerWalletOutputs.Count());
                Assert.Empty(res.OuterWalletOutputs);
                var activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                var changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Assert.Equal(amountToSend, activeOutput.Amount);
                if (res.SpentCoins.Sum(x => x.Amount) - activeOutput.Amount == res.Fee)                 // this happens when change is too small
                {
                    Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == activeOutput.Amount);
                    Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                }
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                var foundReceive = false;
                Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                await broadcaster.SendTransactionAsync(res.Transaction);

                #endregion Basic

                #region SubtractFeeFromAmount

                receive      = keyManager.GetNextReceiveKey("SubtractFeeFromAmount", out _).P2wpkhScript;
                amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 3;
                res          = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, subtractFee: true, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Equal(2, res.InnerWalletOutputs.Count());
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Assert.Equal(amountToSend - res.Fee, activeOutput.Amount);
                Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount);
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend - res.Fee, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion SubtractFeeFromAmount

                #region LowFee

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Equal(2, res.InnerWalletOutputs.Count());
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Assert.Equal(amountToSend, activeOutput.Amount);
                Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount);
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion LowFee

                #region MediumFee

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.OneDayConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Equal(2, res.InnerWalletOutputs.Count());
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Assert.Equal(amountToSend, activeOutput.Amount);
                Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount);
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion MediumFee

                #region HighFee

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Equal(2, res.InnerWalletOutputs.Count());
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Assert.Equal(amountToSend, activeOutput.Amount);
                Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount);
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                Assert.InRange(res.Fee, Money.Zero, res.Fee);
                Assert.InRange(res.Fee, res.Fee, res.Fee);

                await broadcaster.SendTransactionAsync(res.Transaction);

                #endregion HighFee

                #region MaxAmount

                receive = keyManager.GetNextReceiveKey("MaxAmount", out _).P2wpkhScript;

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Single(res.InnerWalletOutputs);
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single();

                Assert.Equal(receive, activeOutput.ScriptPubKey);

                Assert.Single(res.Transaction.Transaction.Outputs);
                var maxBuiltTxOutput = res.Transaction.Transaction.Outputs.Single();
                Assert.Equal(receive, maxBuiltTxOutput.ScriptPubKey);
                Assert.Equal(wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) - res.Fee, maxBuiltTxOutput.Value);

                await broadcaster.SendTransactionAsync(res.Transaction);

                #endregion MaxAmount

                #region InputSelection

                receive = keyManager.GetNextReceiveKey("InputSelection", out _).P2wpkhScript;

                var inputCountBefore = res.SpentCoins.Count();

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy,
                                              allowUnconfirmed: true,
                                              allowedInputs: wallet.Coins.Where(x => !x.Unavailable).Select(x => x.OutPoint).Take(1));

                Assert.Single(res.InnerWalletOutputs);
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);

                Assert.True(inputCountBefore >= res.SpentCoins.Count());
                Assert.Equal(res.SpentCoins.Count(), res.Transaction.Transaction.Inputs.Count);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                Assert.Single(res.Transaction.Transaction.Outputs);

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy,
                                              allowUnconfirmed: true,
                                              allowedInputs: new[] { res.SpentCoins.Select(x => x.OutPoint).First() });

                Assert.Single(res.InnerWalletOutputs);
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);

                Assert.Single(res.Transaction.Transaction.Inputs);
                Assert.Single(res.Transaction.Transaction.Outputs);
                Assert.Single(res.SpentCoins);

                #endregion InputSelection

                #region Labeling

                Script receive2 = keyManager.GetNextReceiveKey("foo", out _).P2wpkhScript;
                res = wallet.BuildTransaction(password, new PaymentIntent(receive2, MoneyRequest.CreateAllRemaining(), "my label"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Single(res.InnerWalletOutputs);
                Assert.Equal("foo, my label", res.InnerWalletOutputs.Single().Label);

                amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 3;
                res          = wallet.BuildTransaction(
                    password,
                    new PaymentIntent(
                        new DestinationRequest(new Key(), amountToSend, label: "outgoing"),
                        new DestinationRequest(new Key(), amountToSend, label: "outgoing2")),
                    FeeStrategy.SevenDaysConfirmationTargetStrategy,
                    allowUnconfirmed: true);

                Assert.Single(res.InnerWalletOutputs);
                Assert.Equal(2, res.OuterWalletOutputs.Count());
                IEnumerable <string> change = res.InnerWalletOutputs.Single().Label.Labels;
                Assert.Contains("outgoing", change);
                Assert.Contains("outgoing2", change);

                await broadcaster.SendTransactionAsync(res.Transaction);

                IEnumerable <SmartCoin> unconfirmedCoins      = wallet.Coins.Where(x => x.Height == Height.Mempool).ToArray();
                IEnumerable <string>    unconfirmedCoinLabels = unconfirmedCoins.SelectMany(x => x.Label.Labels).ToArray();
                Assert.Contains("outgoing", unconfirmedCoinLabels);
                Assert.Contains("outgoing2", unconfirmedCoinLabels);
                IEnumerable <string> allKeyLabels = keyManager.GetKeys().SelectMany(x => x.Label.Labels);
                Assert.Contains("outgoing", allKeyLabels);
                Assert.Contains("outgoing2", allKeyLabels);

                Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
                await rpc.GenerateAsync(1);

                await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 1);

                var bestHeight = new Height(bitcoinStore.SmartHeaderChain.TipHeight);
                IEnumerable <string> confirmedCoinLabels = wallet.Coins.Where(x => x.Height == bestHeight).SelectMany(x => x.Label.Labels);
                Assert.Contains("outgoing", confirmedCoinLabels);
                Assert.Contains("outgoing2", confirmedCoinLabels);
                allKeyLabels = keyManager.GetKeys().SelectMany(x => x.Label.Labels);
                Assert.Contains("outgoing", allKeyLabels);
                Assert.Contains("outgoing2", allKeyLabels);

                #endregion Labeling

                #region AllowedInputsDisallowUnconfirmed

                inputCountBefore = res.SpentCoins.Count();

                receive = keyManager.GetNextReceiveKey("AllowedInputsDisallowUnconfirmed", out _).P2wpkhScript;

                var allowedInputs = wallet.Coins.Where(x => !x.Unavailable).Select(x => x.OutPoint).Take(1);
                var toSend        = new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "fizz");

                // covers:
                // disallow unconfirmed with allowed inputs
                res = wallet.BuildTransaction(password, toSend, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, false, allowedInputs: allowedInputs);

                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                Assert.Single(res.InnerWalletOutputs);
                Assert.Empty(res.OuterWalletOutputs);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Logger.LogDebug($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogDebug($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogDebug($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogDebug($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogDebug($"TxId: {res.Transaction.GetHash()}");

                Assert.True(inputCountBefore >= res.SpentCoins.Count());
                Assert.False(res.SpendsUnconfirmed);

                Assert.Single(res.Transaction.Transaction.Inputs);
                Assert.Single(res.Transaction.Transaction.Outputs);
                Assert.Single(res.SpentCoins);

                Assert.True(inputCountBefore >= res.SpentCoins.Count());
                Assert.Equal(res.SpentCoins.Count(), res.Transaction.Transaction.Inputs.Count);

                #endregion AllowedInputsDisallowUnconfirmed

                #region CustomChange

                // covers:
                // customchange
                // feePc > 1
                var k1 = new Key();
                var k2 = new Key();
                res = wallet.BuildTransaction(
                    password,
                    new PaymentIntent(
                        new DestinationRequest(k1, MoneyRequest.CreateChange()),
                        new DestinationRequest(k2, Money.Coins(0.0003m), label: "outgoing")),
                    FeeStrategy.TwentyMinutesConfirmationTargetStrategy);

                Assert.Contains(k1.ScriptPubKey, res.OuterWalletOutputs.Select(x => x.ScriptPubKey));
                Assert.Contains(k2.ScriptPubKey, res.OuterWalletOutputs.Select(x => x.ScriptPubKey));

                #endregion CustomChange

                #region FeePcHigh

                res = wallet.BuildTransaction(
                    password,
                    new PaymentIntent(new Key(), Money.Coins(0.0003m), label: "outgoing"),
                    FeeStrategy.TwentyMinutesConfirmationTargetStrategy);

                Assert.True(res.FeePercentOfSent > 1);

                var newChangeK = keyManager.GenerateNewKey("foo", KeyState.Clean, isInternal: true);
                res = wallet.BuildTransaction(
                    password,
                    new PaymentIntent(
                        new DestinationRequest(newChangeK.P2wpkhScript, MoneyRequest.CreateChange(), "boo"),
                        new DestinationRequest(new Key(), Money.Coins(0.0003m), label: "outgoing")),
                    FeeStrategy.TwentyMinutesConfirmationTargetStrategy);

                Assert.True(res.FeePercentOfSent > 1);
                Assert.Single(res.OuterWalletOutputs);
                Assert.Single(res.InnerWalletOutputs);
                SmartCoin changeRes = res.InnerWalletOutputs.Single();
                Assert.Equal(newChangeK.P2wpkhScript, changeRes.ScriptPubKey);
                Assert.Equal(newChangeK.Label, changeRes.Label);
                Assert.Equal(KeyState.Clean, newChangeK.KeyState);                 // Still clean, because the tx wasn't yet propagated.

                #endregion FeePcHigh
            }
            finally
            {
                bitcoinStore.IndexStore.NewFilter -= Common.Wallet_NewFilterProcessed;
                await walletManager.RemoveAndStopAllAsync(CancellationToken.None);

                // Dispose wasabi synchronizer service.
                if (synchronizer is { })
Ejemplo n.º 24
0
 public AppInitializedEventArgs(Global global)
 {
     Nodes = global.Nodes;
     TransactionBroadcaster = global.TransactionBroadcaster;
 }
Ejemplo n.º 25
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);

            EnableCancel = true;

            EnableBack = true;

            NextCommand = ReactiveCommand.CreateFromTask(
                async() => await OnNext(wallet, transactionInfo, broadcaster, selectedList),
                this.WhenAnyValue(x => x.EnoughSelected));

            EnableAutoBusyOn(NextCommand);
        }
Ejemplo n.º 26
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());
        }
Ejemplo n.º 27
0
        public BroadcastTransactionViewModel(
            BitcoinStore store,
            Network network,
            TransactionBroadcaster broadcaster,
            SmartTransaction transaction)
        {
            Title = "Broadcast Transaction";

            var nullMoney  = new Money(-1L);
            var nullOutput = new TxOut(nullMoney, Script.Empty);

            var psbt = PSBT.FromTransaction(transaction.Transaction, network);

            TxOut GetOutput(OutPoint outpoint) =>
            store.TransactionStore.TryGetTransaction(outpoint.Hash, out var prevTxn)
                                        ? prevTxn.Transaction.Outputs[outpoint.N]
                                        : nullOutput;

            var inputAddressAmount = psbt.Inputs
                                     .Select(x => x.PrevOut)
                                     .Select(GetOutput)
                                     .ToArray();

            var outputAddressAmount = psbt.Outputs
                                      .Select(x => x.GetCoin().TxOut)
                                      .ToArray();

            var psbtTxn = psbt.GetOriginalTransaction();

            _transactionId     = psbtTxn.GetHash().ToString();
            _inputCount        = inputAddressAmount.Length;
            _inputCountString  = $" input{(_inputCount > 1 ? 's' : "")} and ";
            _outputCount       = outputAddressAmount.Length;
            _outputCountString = $" output{(_outputCount > 1 ? 's' : "")}.";
            _totalInputValue   = inputAddressAmount.Any(x => x.Value == nullMoney)
                                ? null
                                : inputAddressAmount.Select(x => x.Value).Sum();
            _totalOutputValue = outputAddressAmount.Any(x => x.Value == nullMoney)
                                ? null
                                : outputAddressAmount.Select(x => x.Value).Sum();
            _networkFee = TotalInputValue is null || TotalOutputValue is null
                                ? null
                                : TotalInputValue - TotalOutputValue;

            var nextCommandCanExecute = this.WhenAnyValue(x => x.IsBusy)
                                        .Select(x => !x);

            NextCommand = ReactiveCommand.CreateFromTask(
                async() =>
            {
                try
                {
                    await broadcaster.SendTransactionAsync(transaction);
                    Navigate().To(new SuccessBroadcastTransactionViewModel());
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex);
                    await ShowErrorAsync(ex.ToUserFriendlyString(), "It was not possible to broadcast the transaction.");
                }
            },
                nextCommandCanExecute);

            EnableAutoBusyOn(NextCommand);
        }
Ejemplo n.º 28
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);
        }
        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.", "");
                    }
                }
            });
        }
        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.Create(
                () =>
            {
                var coins = selectedList.Items.SelectMany(x => x.Coins);

                var intent = new PaymentIntent(
                    transactionInfo.Address,
                    transactionInfo.Amount,
                    subtractFee: false,
                    transactionInfo.Labels);

                var transactionResult = _wallet.BuildTransaction(
                    _wallet.Kitchen.SaltSoup(),
                    intent,
                    FeeStrategy.CreateFromFeeRate(transactionInfo.FeeRate),
                    true,
                    coins.Select(x => x.OutPoint));

                Navigate().To(new TransactionPreviewViewModel(wallet, transactionInfo, broadcaster, transactionResult));
            },
                this.WhenAnyValue(x => x.EnoughSelected));
        }