Ejemplo n.º 1
0
        public async Task <IActionResult> Create(CreatePaymentForwarderViewModel viewModel)
        {
            var services = await _externalServiceManager.GetExternalServicesData(new ExternalServicesDataQuery()
            {
                UserId = _userManager.GetUserId(User),
                Type   = new[] { NBXplorerWalletService.NBXplorerWalletServiceType }
            });

            if (services.Any())
            {
                var newServices = services.ToList();
                newServices.Insert(0, new ExternalServiceData()
                {
                    Id   = null,
                    Name = "None"
                });
                services = newServices;
            }

            viewModel.Services =
                new SelectList(services, nameof(ExternalServiceData.Id), nameof(ExternalServiceData.Name));
            viewModel.CryptoCodes = new SelectList(_nbXplorerOptions.Cryptos?.ToList() ?? new List <string>(),
                                                   viewModel.CryptoCode);
            viewModel.PaymentDestinations = viewModel.PaymentDestinations ??
                                            new List <CreatePaymentForwarderViewModel.PaymentDestination>();

            if (!string.IsNullOrEmpty(viewModel.Action))
            {
                if (viewModel.Action == "add-destination")
                {
                    viewModel.PaymentDestinations.Add(new CreatePaymentForwarderViewModel.PaymentDestination());
                    return(View(viewModel));
                }

                if (viewModel.Action.StartsWith("remove-destination", StringComparison.InvariantCultureIgnoreCase))
                {
                    var index = int.Parse(viewModel.Action.Substring(viewModel.Action.IndexOf(":") + 1));
                    viewModel.PaymentDestinations.RemoveAt(index);
                    return(View(viewModel));
                }
            }

            if ((string.IsNullOrEmpty(viewModel.SelectedSourceWalletExternalServiceId) &&
                 !viewModel.GenerateSourceWallet) ||
                !string.IsNullOrEmpty(viewModel.SelectedSourceWalletExternalServiceId) &&
                viewModel.GenerateSourceWallet)
            {
                if (viewModel.Services.Items.Any())
                {
                    ModelState.AddModelError(nameof(viewModel.SelectedSourceWalletExternalServiceId),
                                             "Please select a source nbxplorer wallet OR check the generate wallet checkbox");
                }

                ModelState.AddModelError(nameof(viewModel.GenerateSourceWallet),
                                         "Please select a source nbxplorer wallet OR check the generate wallet checkbox");
            }
            else if (!string.IsNullOrEmpty(viewModel.SelectedSourceWalletExternalServiceId))
            {
                var service = services.SingleOrDefault(data =>
                                                       data.Id == viewModel.SelectedSourceWalletExternalServiceId);
                if (service == null)
                {
                    viewModel.AddModelError(
                        model => model.SelectedSourceWalletExternalServiceId,
                        "Wallet chosen is wrong... ヽ༼ ಠ益ಠ ༽ノ ", ModelState);
                }
                else
                {
                    var data = new NBXplorerWalletService(service, _nbXplorerPublicWalletProvider,
                                                          _derivationSchemeParser,
                                                          _derivationStrategyFactoryProvider, _nbXplorerClientProvider).GetData();
                    if (data.CryptoCode != viewModel.CryptoCode)
                    {
                        viewModel.AddModelError(
                            model => model.SelectedSourceWalletExternalServiceId,
                            "Wallet chosen not the same crypto", ModelState);
                    }
                    else if (!data.PrivateKeys.Any())
                    {
                        viewModel.AddModelError(
                            model => model.SelectedSourceWalletExternalServiceId,
                            "Wallet chosen has so signing keys which would make forwarding txs impossible", ModelState);
                    }
                }
            }

            if (!viewModel.PaymentDestinations.Any())
            {
                ModelState.AddModelError(string.Empty,
                                         "Please add at least one transaction destination");
            }
            else
            {
                var totalSumIssue = viewModel.PaymentDestinations.Sum(output => output.AmountPercentage) > 100;


                var subtractFeesOutputs = viewModel.PaymentDestinations.Select((output, i) => (output, i))
                                          .Where(tuple => tuple.Item1.SubtractFeesFromOutput);

                if (subtractFeesOutputs.Count() > 1)
                {
                    foreach (var subtractFeesOutput in subtractFeesOutputs)
                    {
                        viewModel.AddModelError(
                            model => model.PaymentDestinations[subtractFeesOutput.Item2].SubtractFeesFromOutput,
                            "You can only subtract fees from one destination", ModelState);
                    }
                }

                for (var index = 0; index < viewModel.PaymentDestinations.Count; index++)
                {
                    if (totalSumIssue)
                    {
                        viewModel.AddModelError(
                            model => model.PaymentDestinations[index].AmountPercentage,
                            "Your total amounts across all outputs exceeds 100% We're not a central bank and can't print more money than you own, sorry.",
                            ModelState);
                    }

                    var viewModelPaymentDestination = viewModel.PaymentDestinations[index];

                    var check =
                        (string.IsNullOrEmpty(viewModelPaymentDestination.DerivationStrategy) ? 0 : 1) +
                        (string.IsNullOrEmpty(viewModelPaymentDestination.DestinationAddress) ? 0 : 1) +
                        (string.IsNullOrEmpty(viewModelPaymentDestination.SelectedDestinationWalletExternalServiceId)
                            ? 0
                            : 1);
                    if (check != 1)
                    {
                        viewModel.AddModelError(
                            model => model.PaymentDestinations[index].DestinationAddress,
                            "Please choose to track either an address OR a derivation scheme OR an existing NBXplorer Wallet External Service",
                            ModelState);
                        viewModel.AddModelError(
                            model => model.PaymentDestinations[index].DerivationStrategy,
                            "Please choose to track either an address OR a derivation scheme OR an existing NBXplorer Wallet External Service",
                            ModelState);
                        viewModel.AddModelError(
                            model => model.PaymentDestinations[index].SelectedDestinationWalletExternalServiceId,
                            "Please choose to track either an address OR a derivation scheme OR an existing NBXplorer Wallet External Service",
                            ModelState);
                    }

                    if (!string.IsNullOrEmpty(viewModelPaymentDestination.SelectedDestinationWalletExternalServiceId) &&
                        !string.IsNullOrEmpty(viewModel.CryptoCode))
                    {
                        var service = services.SingleOrDefault(data =>
                                                               data.Id == viewModelPaymentDestination.SelectedDestinationWalletExternalServiceId);
                        if (service == null)
                        {
                            viewModel.AddModelError(
                                model => model.PaymentDestinations[index].SelectedDestinationWalletExternalServiceId,
                                "Wallet chosen is wrong... ヽ༼ ಠ益ಠ ༽ノ ", ModelState);
                        }
                        else if (
                            new NBXplorerWalletService(service, _nbXplorerPublicWalletProvider, _derivationSchemeParser,
                                                       _derivationStrategyFactoryProvider, _nbXplorerClientProvider).GetData().CryptoCode !=
                            viewModel.CryptoCode)
                        {
                            viewModel.AddModelError(
                                model => model.PaymentDestinations[index].SelectedDestinationWalletExternalServiceId,
                                "Wallet chosen not the same crypto", ModelState);
                        }
                    }

                    BitcoinAddress         address            = null;
                    DerivationStrategyBase derivationStrategy = null;
                    if (!string.IsNullOrEmpty(viewModelPaymentDestination.DestinationAddress) &&
                        !string.IsNullOrEmpty(viewModel.CryptoCode))
                    {
                        try
                        {
                            var factory =
                                _derivationStrategyFactoryProvider.GetDerivationStrategyFactory(viewModel.CryptoCode);
                            address = BitcoinAddress.Create(viewModelPaymentDestination.DestinationAddress,
                                                            factory.Network);
                        }
                        catch (Exception)
                        {
                            viewModel.AddModelError(
                                model => model.PaymentDestinations[index].DestinationAddress,
                                "Invalid Address", ModelState);
                        }
                    }

                    if (!string.IsNullOrEmpty(viewModelPaymentDestination.DerivationStrategy) &&
                        !string.IsNullOrEmpty(viewModel.CryptoCode))
                    {
                        try
                        {
                            var factory =
                                _derivationStrategyFactoryProvider.GetDerivationStrategyFactory(viewModel.CryptoCode);

                            derivationStrategy = _derivationSchemeParser.Parse(factory,
                                                                               viewModelPaymentDestination.DerivationStrategy);
                        }
                        catch
                        {
                            viewModel.AddModelError(
                                model => model.PaymentDestinations[index].DerivationStrategy,
                                "Invalid Derivation Scheme", ModelState);
                        }
                    }
                }
            }


            if (!ModelState.IsValid)
            {
                return(View(viewModel));
            }

            return(await SetItUp(viewModel));
        }
Ejemplo n.º 2
0
        private async Task <IActionResult> SetItUp(CreatePaymentForwarderViewModel vm)
        {
            var client     = _nbXplorerClientProvider.GetClient(vm.CryptoCode);
            var presetName = $"Generated_PaymentForwarder_{vm.CryptoCode}";
            var sourceExternalServiceId = vm.SelectedSourceWalletExternalServiceId;

            if (vm.GenerateSourceWallet)
            {
                var newSeed = new Mnemonic(Wordlist.English, WordCount.Twelve);

                var data = new NBXplorerWalletExternalServiceData()
                {
                    CryptoCode         = vm.CryptoCode,
                    DerivationStrategy = newSeed.DeriveExtKey().Neuter().ToString(client.Network.NBitcoinNetwork) + (client.Network.NBitcoinNetwork.Consensus.SupportSegwit? "-[p2sh]": "-[legacy]"),
                    PrivateKeys        = new List <PrivateKeyDetails>()
                    {
                        new PrivateKeyDetails()
                        {
                            MnemonicSeed = newSeed.ToString()
                        }
                    }
                };

                var sourceWallet = new ExternalServiceData()
                {
                    Name     = presetName + "_Source_Wallet",
                    UserId   = _userManager.GetUserId(User),
                    Type     = NBXplorerWalletService.NBXplorerWalletServiceType,
                    DataJson = client.Serializer.ToString(data)
                };
                await _externalServiceManager.AddOrUpdateExternalServiceData(sourceWallet);

                sourceExternalServiceId = sourceWallet.Id;
            }

            var recipe = new Recipe()
            {
                Name        = presetName,
                Description = "Generated from a preset",
                UserId      = _userManager.GetUserId(User),
                Enabled     = false
            };
            await _recipeManager.AddOrUpdateRecipe(recipe);

            var recipeTrigger = new RecipeTrigger()
            {
                ExternalServiceId = sourceExternalServiceId,
                TriggerId         = NBXplorerBalanceTrigger.Id,
                RecipeId          = recipe.Id,
                DataJson          = client.Serializer.ToString(new NBXplorerBalanceTriggerParameters()
                {
                    BalanceValue    = vm.SendEntireBalance ? 0 : vm.SendBalanceValue,
                    BalanceComparer = vm.SendEntireBalance
                        ? BalanceComparer.GreaterThan
                        : BalanceComparer.GreaterThanOrEqual,
                    BalanceMoneyUnit = vm.SendBalanceMoneyUnit
                })
            };

            await _recipeManager.AddOrUpdateRecipeTrigger(recipeTrigger);

            var recipeActionGroup = new RecipeActionGroup()
            {
                RecipeId = recipe.Id
            };
            await _recipeManager.AddRecipeActionGroup(recipeActionGroup);

            var recipeActionGroupIndex = 0;
            var ouputs = new List <SendTransactionData.TransactionOutput>();

            foreach (var paymentDestination in vm.PaymentDestinations)
            {
                var destinationExternalServiceId        = paymentDestination.SelectedDestinationWalletExternalServiceId;
                NBXplorerWalletExternalServiceData data = null;

                if (string.IsNullOrEmpty(destinationExternalServiceId))
                {
                    data = new NBXplorerWalletExternalServiceData()
                    {
                        CryptoCode         = vm.CryptoCode,
                        Address            = paymentDestination.DestinationAddress,
                        DerivationStrategy = paymentDestination.DerivationStrategy,
                    };

                    var wallet = new ExternalServiceData()
                    {
                        Name     = presetName + $"_Dest_Wallet_{data.DerivationStrategy}",
                        UserId   = _userManager.GetUserId(User),
                        Type     = NBXplorerWalletService.NBXplorerWalletServiceType,
                        DataJson = client.Serializer.ToString(data)
                    };
                    await _externalServiceManager.AddOrUpdateExternalServiceData(wallet);

                    destinationExternalServiceId = wallet.Id;
                }
                else
                {
                    var service = vm.Services.Items.Cast <ExternalServiceData>()
                                  .Single(serviceData => serviceData.Id == destinationExternalServiceId);
                    data = new NBXplorerWalletService(service, _nbXplorerPublicWalletProvider, _derivationSchemeParser,
                                                      _derivationStrategyFactoryProvider, _nbXplorerClientProvider).GetData();
                }

                var recipeAction = new RecipeAction()
                {
                    RecipeId            = recipe.Id,
                    RecipeActionGroupId = recipeActionGroup.Id,
                    ActionId            = new GenerateNextAddressDataActionHandler().ActionId,
                    ExternalServiceId   = destinationExternalServiceId,
                    Order    = recipeActionGroupIndex,
                    DataJson = JsonConvert.SerializeObject(new GenerateNextAddressData())
                };
                await _recipeManager.AddOrUpdateRecipeAction(recipeAction);

                ouputs.Add(new SendTransactionData.TransactionOutput()
                {
                    DestinationAddress     = "{{Action" + recipeActionGroupIndex + "}}",
                    Amount                 = "{{TriggerData.Balance * (" + paymentDestination.AmountPercentage + "/100)}}",
                    SubtractFeesFromOutput = paymentDestination.SubtractFeesFromOutput
                });

                recipeActionGroupIndex++;
            }

            var sweepRecipeAction = new RecipeAction()
            {
                RecipeId            = recipe.Id,
                RecipeActionGroupId = recipeActionGroup.Id,
                ActionId            = new SendTransactionDataActionHandler().ActionId,
                ExternalServiceId   = sourceExternalServiceId,
                Order    = recipeActionGroupIndex,
                DataJson = JsonConvert.SerializeObject(new SendTransactionData()
                {
                    Outputs = ouputs
                })
            };

            await _recipeManager.AddOrUpdateRecipeAction(sweepRecipeAction);

            return(RedirectToAction("EditRecipe", "Recipes", new
            {
                id = recipe.Id,
                statusMessage =
                    "Preset generated. Recipe is currently disabled for now. Please verify details are correct before enabling!"
            }));
        }
        private async Task MonitorClientForTriggers(ExplorerClient explorerClient, CancellationToken cancellationToken)
        {
            await explorerClient.WaitServerStartedAsync(cancellationToken);

            WebsocketNotificationSession notificationSession = null;

            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    var summary = _nbXplorerSummaryProvider.GetSummary(explorerClient.CryptoCode);
                    if (summary?.State != NBXplorerState.Ready)
                    {
                        await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);

                        continue;
                    }

                    notificationSession =
                        await explorerClient.CreateWebsocketNotificationSessionAsync(cancellationToken);

                    await notificationSession.ListenNewBlockAsync(cancellationToken);

                    await notificationSession.ListenAllTrackedSourceAsync(false, cancellationToken);

                    while (!cancellationToken.IsCancellationRequested)
                    {
                        var evt = await notificationSession.NextEventAsync(cancellationToken);

                        var factory =
                            _derivationStrategyFactoryProvider.GetDerivationStrategyFactory(
                                evt.CryptoCode);

                        switch (evt)
                        {
                        case NewBlockEvent newBlockEvent:
                            _ = _triggerDispatcher.DispatchTrigger(new NBXplorerNewBlockTrigger()
                            {
                                Data = new NBXplorerNewBlockTriggerData()
                                {
                                    CryptoCode = evt.CryptoCode,
                                    Event      = newBlockEvent
                                }
                            });
                            //we need to trigger transaction events for previous unconfirmed txs  so that they are checked again and trigger respective actions
                            var recipes = await _recipeManager.GetRecipes(new RecipesQuery()
                            {
                                Enabled   = true,
                                TriggerId = NBXplorerNewTransactionTrigger.Id
                            });

                            foreach (var recipe in recipes)
                            {
                                var triggerParameters =
                                    recipe.RecipeTrigger.Get <NBXplorerNewTransactionTriggerParameters>();
                                if (triggerParameters.Transactions == null || !triggerParameters.Transactions.Any())
                                {
                                    continue;
                                }

                                var tasks = triggerParameters.Transactions.Select(result =>
                                                                                  (result,
                                                                                   explorerClient.GetTransactionAsync(result.TransactionHash,
                                                                                                                      cancellationToken)));
                                await Task.WhenAll(tasks.Select(tuple => tuple.Item2));


                                foreach (var tx in tasks)
                                {
                                    if (tx.Item1.Confirmations != tx.Item2.Result.Confirmations)
                                    {
                                        var walletService = new NBXplorerWalletService(
                                            recipe.RecipeTrigger.ExternalService, _nbXplorerPublicWalletProvider,
                                            _derivationSchemeParser, _derivationStrategyFactoryProvider,
                                            _nbXplorerClientProvider);



                                        _ = _triggerDispatcher.DispatchTrigger(
                                            new NBXplorerNewTransactionTrigger(explorerClient)
                                        {
                                            Data = new NBXplorerNewTransactionTriggerData()
                                            {
                                                CryptoCode = evt.CryptoCode,
                                                Event      = new NewTransactionEvent()
                                                {
                                                    CryptoCode      = evt.CryptoCode,
                                                    BlockId         = newBlockEvent.Hash,
                                                    TransactionData = tx.Item2.Result,
                                                    TrackedSource   = await walletService.ConstructTrackedSource()
                                                }
                                            }
                                        });
                                    }
                                }
                            }

                            break;

                        case NewTransactionEvent newTransactionEvent:
                        {
                            _ = _triggerDispatcher.DispatchTrigger(
                                new NBXplorerNewTransactionTrigger(explorerClient)
                                {
                                    Data = new NBXplorerNewTransactionTriggerData()
                                    {
                                        CryptoCode = evt.CryptoCode,
                                        Event      = newTransactionEvent
                                    }
                                });
                            break;
                        }

                        case UnknownEvent unknownEvent:
                            _logger.LogWarning(
                                $"Received unknown message from NBXplorer ({unknownEvent.CryptoCode}), ID: {unknownEvent.EventId}");
                            break;
                        }
                    }
                }
                catch when(cancellationToken.IsCancellationRequested)
                {
                }