public async Task <IActionResult> SweepFundsSpecifyInfo(SweepFundsViewModel viewModel, string command = "")
        {
            if (!ModelState.IsValid)
            {
                return(View(viewModel));
            }

            var wallet = new Wallet(viewModel.Seed, viewModel.Password, viewModel.KeyPath);

            if (viewModel.XPub != wallet.GetMasterPublicWallet().ExtPubKey.ToBytes().ToHex())
            {
                ModelState.AddModelError(nameof(viewModel.Seed), "Seed/Password do not match configured wallet");
            }

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

            if (viewModel.SweepRequests == null)
            {
                return(await ComputeSweepRequests(viewModel));
            }

            return(await CheckIfCanSweepNow(viewModel, command));
        }
        public async Task <IActionResult> SweepFundsSpecifyInfo(int chainId, string xpub)
        {
            var configuredEthereumLikePaymentMethods = StoreData
                                                       .GetSupportedPaymentMethods(_btcPayNetworkProvider)
                                                       .OfType <EthereumSupportedPaymentMethod>()
                                                       .Select(method => (Network: _btcPayNetworkProvider.GetNetwork <EthereumBTCPayNetwork>(method.CryptoCode),
                                                                          method))
                                                       .Where(method => method.Network.ChainId == chainId && method.method.XPub == xpub);


            if (!configuredEthereumLikePaymentMethods.Any())
            {
                TempData.SetStatusMessageModel(new StatusMessageModel()
                {
                    Message = $"Nothing configured to sweep", Severity = StatusMessageModel.StatusSeverity.Error
                });
                return(RedirectToAction("GetStoreEthereumLikePaymentMethods", new { storeId = StoreData.Id }));
            }

            var seedConfiguredMethod =
                configuredEthereumLikePaymentMethods.FirstOrDefault(tuple => !string.IsNullOrEmpty(tuple.method.Seed));

            var vm = new SweepFundsViewModel();

            vm.ChainId = chainId;
            vm.XPub    = xpub;
            if (seedConfiguredMethod.method != null)
            {
                vm.Seed     = seedConfiguredMethod.method.Seed;
                vm.Password = seedConfiguredMethod.method.Password;
                vm.KeyPath  = seedConfiguredMethod.method.KeyPath;
            }

            vm.KeyPath ??= configuredEthereumLikePaymentMethods.First().method.KeyPath;


            return(View("SweepFundsSpecifyInfo", vm));
        }
        public async Task <bool> DoSweepAction(SweepFundsViewModel viewModel, Web3 web3)
        {
            var somethingHappened = false;
            var grouped           = viewModel.SweepRequests
                                    .Select(request =>
            {
                var suff = request.Sufficient(viewModel.GasPrice.Value, out var diff);
                return(request, suff, diff);
            }).GroupBy(tuple => tuple.suff);

            var sufficient = grouped.First(tuples => tuples.Key);
            //do not sweep request for only native when not enough
            var insufficient = grouped.FirstOrDefault(tuples => !tuples.Key)?
                               .Where(tuple => tuple.request.Tokens.Any(item => item.Amount > 0 && item.Sweep));


            var ethForwardedTo = new Dictionary <string, ulong>();

            foreach (var tuple in sufficient)
            {
                var w       = new Wallet(viewModel.Seed, viewModel.Password, viewModel.KeyPath);
                var acc     = w.GetAccount((int)tuple.request.Index);
                var accWeb3 = new Web3(acc, web3.Client);

                var transferHandler = accWeb3.Eth.GetContractTransactionHandler <TransferFunction>();
                foreach (var tokenSweepRequest in tuple.request.Tokens.Where(item =>
                                                                             item.Sweep && string.IsNullOrEmpty(item.TransactionHash)))
                {
                    var network = _btcPayNetworkProvider.GetNetwork <ERC20BTCPayNetwork>(tokenSweepRequest.CryptoCode);
                    var receipt = await transferHandler.SendRequestAndWaitForReceiptAsync(network.SmartContractAddress,
                                                                                          new TransferFunction()
                    {
                        Value       = tokenSweepRequest.Amount,
                        To          = viewModel.DestinationAddress,
                        GasPrice    = viewModel.GasPrice.Value,
                        Gas         = tokenSweepRequest.GasCost,
                        FromAddress = acc.Address,
                    });

                    tokenSweepRequest.TransactionHash = receipt.TransactionHash;
                    if (tokenSweepRequest.TransactionHash != null)
                    {
                        var i = await _invoiceRepository.GetInvoice(tokenSweepRequest.InvoiceId);

                        var pD    = i.GetPayments(network.CryptoCode).Last(entity => entity.Accounted);
                        var ethpd = pD.GetCryptoPaymentData() as EthereumLikePaymentData;

                        ethpd.SweepingTransaction = tokenSweepRequest.TransactionHash;
                        pD.SetCryptoPaymentData(ethpd);
                        await _invoiceRepository.UpdatePayments(new List <PaymentEntity>() { pD });

                        somethingHappened = true;
                    }
                }

                if (tuple.diff > 0 && tuple.request.Native.Sweep)
                {
                    var anyInsufficientThatCanWorkWithExcessFromThisAccount =
                        insufficient?.Where(valueTuple =>
                    {
                        ulong requiredAmount = valueTuple.diff;

                        if (ethForwardedTo.ContainsKey(valueTuple.request.Address))
                        {
                            var contributedAmount = ethForwardedTo[valueTuple.request.Address];
                            if (requiredAmount > contributedAmount)
                            {
                                //still need more
                                return(true);
                            }
                            else
                            {
                                return(false);
                            }
                        }

                        if (requiredAmount < tuple.diff)
                        {
                            //this takes care of it enough
                            return(true);
                        }

                        return(false);
                    });

                    var etherTransferService = accWeb3.Eth.GetEtherTransferService();
                    TransactionReceipt tx    = null;
                    if (anyInsufficientThatCanWorkWithExcessFromThisAccount?.Any() is true)
                    {
                        var destination = anyInsufficientThatCanWorkWithExcessFromThisAccount.First();
                        var network     =
                            _btcPayNetworkProvider.GetNetwork <EthereumBTCPayNetwork>(tuple.request.Native.CryptoCode);
                        var netAmount = EthereumLikePaymentData.GetValue(network, tuple.diff);
                        tx = await etherTransferService.TransferEtherAndWaitForReceiptAsync(destination.request.Address,
                                                                                            netAmount,
                                                                                            Web3.Convert.FromWei((BigInteger)viewModel.GasPrice, UnitConversion.EthUnit.Gwei),
                                                                                            tuple.request.Native.GasCost);

                        if (ethForwardedTo.TryGetValue(destination.request.Address, out var existingAmount))
                        {
                            ethForwardedTo[destination.request.Address] = existingAmount + tuple.diff;
                        }
                        else
                        {
                            ethForwardedTo.Add(destination.request.Address, tuple.diff);
                        }
                    }
                    else
                    {
                        var destination = viewModel.DestinationAddress;
                        var network     =
                            _btcPayNetworkProvider.GetNetwork <EthereumBTCPayNetwork>(tuple.request.Native.CryptoCode);
                        var netAmount = EthereumLikePaymentData.GetValue(network, tuple.diff);
                        tx = await etherTransferService.TransferEtherAndWaitForReceiptAsync(destination,
                                                                                            netAmount,
                                                                                            Web3.Convert.FromWei((BigInteger)viewModel.GasPrice, UnitConversion.EthUnit.Gwei),
                                                                                            tuple.request.Native.GasCost);
                    }

                    if (tx != null)
                    {
                        tuple.request.Native.TransactionHash = tx.TransactionHash;
                        somethingHappened = true;
                    }

                    if (tx?.TransactionHash != null && !string.IsNullOrEmpty(tuple.request.Native.InvoiceId))
                    {
                        var i = await _invoiceRepository.GetInvoice(tuple.request.Native.InvoiceId);

                        var pD    = i.GetPayments(tuple.request.Native.CryptoCode).Last(entity => entity.Accounted);
                        var ethpd = pD.GetCryptoPaymentData() as EthereumLikePaymentData;

                        ethpd.SweepingTransaction = tuple.request.Native.TransactionHash;
                        pD.SetCryptoPaymentData(ethpd);
                        await _invoiceRepository.UpdatePayments(new List <PaymentEntity>() { pD });
                    }
                }
            }

            return(somethingHappened);
        }
        public async Task <IActionResult> CheckIfCanSweepNow(SweepFundsViewModel viewModel, string command,
                                                             bool leaveActiveInvoice = false)
        {
            var web3 = _ethereumService.GetWeb3(viewModel.ChainId);

            if (web3 == null)
            {
                TempData.SetStatusMessageModel(new StatusMessageModel()
                {
                    Message = $"Web3 not available", Severity = StatusMessageModel.StatusSeverity.Error
                });

                return(RedirectToAction("GetStoreEthereumLikePaymentMethods", new { storeId = StoreData.Id }));
            }

            viewModel.GasPrice ??= (ulong)(await web3.Eth.GasPrice.SendRequestAsync()).Value;
            viewModel.SweepRequests = viewModel.SweepRequests
                                      .Where(request =>
                                             leaveActiveInvoice ||
                                             string.IsNullOrEmpty(request.UnableToSweepBecauseOfActiveInvoiceWithNativeCurrency))
                                      .OrderByDescending(request => request.Sufficient(viewModel.GasPrice.Value, out var excess)
                                                         ).ToList();

            var networks = _btcPayNetworkProvider.GetAll().OfType <EthereumBTCPayNetwork>()
                           .Where(network => network.ChainId == viewModel.ChainId);
            var mainNetwork          = networks.SingleOrDefault(network => !(network is ERC20BTCPayNetwork));
            var etherTransferService = web3.Eth.GetEtherTransferService();
            var transferHandler      = web3.Eth.GetContractTransactionHandler <TransferFunction>();

            foreach (var sweepRequest in viewModel.SweepRequests)
            {
                sweepRequest.Native ??= new SweepRequestItem()
                {
                    Sweep = true, CryptoCode = mainNetwork.CryptoCode,
                };

                var amt = (await _ethereumService.GetBalance(mainNetwork, sweepRequest.Address)).Value;
                sweepRequest.Native.Amount  = amt;
                sweepRequest.Native.GasCost = amt == 0
                    ? 0
                    : (ulong)await etherTransferService.EstimateGasAsync(viewModel.DestinationAddress,
                                                                         EthereumLikePaymentData.GetValue(mainNetwork, amt));

                foreach (SweepRequestItem sweepRequestItem in sweepRequest.Tokens)
                {
                    var network = _btcPayNetworkProvider.GetNetwork <ERC20BTCPayNetwork>(sweepRequestItem.CryptoCode);

                    sweepRequestItem.Amount = (await _ethereumService.GetBalance(network, sweepRequest.Address)).Value;
                    if (sweepRequestItem.Amount == 0 && string.IsNullOrEmpty(sweepRequestItem.TransactionHash))
                    {
                        sweepRequestItem.TransactionHash = "external sweep detected";
                        var i = await _invoiceRepository.GetInvoice(sweepRequestItem.InvoiceId);

                        var pD    = i.GetPayments(network.CryptoCode).Last(entity => entity.Accounted);
                        var ethpd = pD.GetCryptoPaymentData() as EthereumLikePaymentData;

                        ethpd.SweepingTransaction = sweepRequestItem.TransactionHash;
                        pD.SetCryptoPaymentData(ethpd);
                        await _invoiceRepository.UpdatePayments(new List <PaymentEntity>() { pD });
                    }

                    var transfer = new TransferFunction()
                    {
                        To          = viewModel.DestinationAddress,
                        Value       = sweepRequestItem.Amount,
                        FromAddress = sweepRequest.Address
                    };
                    sweepRequestItem.GasCost =
                        (ulong)(await transferHandler.EstimateGasAsync(network.SmartContractAddress,
                                                                       transfer))
                        .Value;
                }

                sweepRequest.Tokens = sweepRequest.Tokens.Where(item => item.Amount > 0).ToList();
            }

            if (command == "sweep")
            {
                if (await DoSweepAction(viewModel, web3))
                {
                    return(await CheckIfCanSweepNow(viewModel, null));
                }
            }

            return(View("SweepFundsSpecifyInfo", viewModel));
        }
        private async Task <IActionResult> ComputeSweepRequests(SweepFundsViewModel viewModel)
        {
            var invoices = await _invoiceRepository.GetInvoices(new InvoiceQuery()
            {
                StoreId = new[] { StoreData.Id },
                Status  = new[] { InvoiceState.ToString(InvoiceStatusLegacy.Complete) },
            });

            var invoicesWithPayment = invoices.Select(entity => (
                                                          entity,
                                                          entity.GetSupportedPaymentMethod <EthereumSupportedPaymentMethod>(),
                                                          entity.GetPayments()
                                                          .Where(paymentEntity => paymentEntity.Accounted &&
                                                                 paymentEntity.Network is EthereumBTCPayNetwork ethN &&
                                                                 ethN.ChainId == viewModel.ChainId)
                                                          .Select(paymentEntity => (paymentEntity,
                                                                                    paymentEntity.GetCryptoPaymentData() as EthereumLikePaymentData))
                                                          .Where(tuple =>
                                                                 tuple.Item2 != null && string.IsNullOrEmpty(tuple.Item2.SweepingTransaction) &&
                                                                 tuple.Item2.XPub == viewModel.XPub)))
                                      .Where(tuple => tuple.Item3.Any());

            if (!invoicesWithPayment.Any())
            {
                TempData.SetStatusMessageModel(new StatusMessageModel()
                {
                    Message = $"Nothing found to sweep", Severity = StatusMessageModel.StatusSeverity.Error
                });

                return(RedirectToAction("GetStoreEthereumLikePaymentMethods", new { storeId = StoreData.Id }));
            }

            //need construct list address with ERC tokens and list address with ETH token
            var payments = invoicesWithPayment.SelectMany(tuple => tuple.Item3.Select(valueTuple =>
                                                                                      (InvoiceId: tuple.entity.Id, PaymentEntity: valueTuple.paymentEntity, PaymentData: valueTuple.Item2)));

            var groupedByAddress = payments.GroupBy(tuple => tuple.PaymentData.Address);

            var networks = _btcPayNetworkProvider.GetAll().OfType <EthereumBTCPayNetwork>()
                           .Where(network => network.ChainId == viewModel.ChainId);
            var mainNetwork = networks.SingleOrDefault(network => !(network is ERC20BTCPayNetwork));
            var pmi         = new PaymentMethodId(mainNetwork.CryptoCode, EthereumPaymentType.Instance);

            //need send ETH to addresses even erc, make sure they not monitored in invoices
            var ethInvoices =
                await _invoiceRepository.GetInvoicesFromAddresses(groupedByAddress
                                                                  .Select(tuples => $"{tuples.Key}#{pmi}").ToArray());

            var pendingInvoices = await _invoiceRepository.GetPendingInvoices();

            var pendingEthInvoices = ethInvoices.Where(entity => (pendingInvoices).Contains(entity.Id))
                                     .Select(entity => (entity, entity.GetPaymentMethod(pmi))).Select(tuple => (tuple.entity, tuple.Item2,
                                                                                                                tuple.Item2.GetPaymentMethodDetails() as EthereumLikeOnChainPaymentMethodDetails))
                                     .ToDictionary(tuple => tuple.Item3.DepositAddress);

            var requests = new List <SweepRequest>();

            foreach (IGrouping <string, (string InvoiceId, PaymentEntity PaymentEntity, EthereumLikePaymentData
                                         PaymentData)> grouping in groupedByAddress)
            {
                var request = new SweepRequest();
                request.Address = grouping.Key;
                request.Index   = grouping.First().PaymentData.AccountIndex;
                if (pendingEthInvoices.TryGetValue(grouping.Key, out var conflict))
                {
                    request.UnableToSweepBecauseOfActiveInvoiceWithNativeCurrency = conflict.entity.Id;
                }

                foreach ((string InvoiceId, PaymentEntity PaymentEntity, EthereumLikePaymentData PaymentData)valueTuple
                         in grouping)
                {
                    var network = valueTuple.PaymentEntity.Network as EthereumBTCPayNetwork;

                    var sweepRequestItem = new SweepRequestItem();
                    sweepRequestItem.CryptoCode = network.CryptoCode;
                    sweepRequestItem.InvoiceId  = valueTuple.InvoiceId;
                    sweepRequestItem.Sweep      =
                        string.IsNullOrEmpty(request.UnableToSweepBecauseOfActiveInvoiceWithNativeCurrency);
                    if (network is ERC20BTCPayNetwork erc20BTCPayNetwork)
                    {
                        request.Tokens.Add(sweepRequestItem);
                    }
                    else
                    {
                        request.Native = sweepRequestItem;
                    }
                }

                requests.Add(request);
            }

            viewModel.SweepRequests = requests;

            return(await CheckIfCanSweepNow(viewModel, "", false));
        }