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)); }