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); }
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken) { if (evt is CatchUp) { DateTimeOffset start = DateTimeOffset.Now; await UpdateAnyPendingEthLikePaymentAndAddressWatchList(cancellationToken); TimeSpan diff = start - DateTimeOffset.Now; if (diff.TotalSeconds < 5) { _ = Task.Delay(TimeSpan.FromSeconds(5 - diff.TotalSeconds), cancellationToken).ContinueWith(task => { _eventAggregator.Publish(new CatchUp()); return(Task.CompletedTask); }, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current); } } if (evt is EthereumAddressBalanceFetched response) { if (response.ChainId != ChainId) { return; } var network = Networks.SingleOrDefault(payNetwork => payNetwork.CryptoCode.Equals(response.CryptoCode, StringComparison.InvariantCultureIgnoreCase)); if (network is null) { return; } var invoice = response.InvoiceEntity; if (invoice is null) { return; } var existingPayment = response.MatchedExistingPayment; if (existingPayment is null && response.Amount > 0) { //new payment var paymentData = new EthereumLikePaymentData() { Address = response.Address, CryptoCode = response.CryptoCode, Amount = response.Amount, Network = network, BlockNumber = response.BlockParameter.ParameterType == BlockParameter.BlockParameterType.blockNumber ? (long?)response.BlockParameter.BlockNumber.Value : (long?)null, ConfirmationCount = 0, AccountIndex = response.PaymentMethodDetails.Index, XPub = response.PaymentMethodDetails.XPub }; var payment = await _invoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, paymentData, network, true); if (payment != null) { ReceivedPayment(invoice, payment); } } else if (existingPayment != null) { var cd = (EthereumLikePaymentData)existingPayment.GetCryptoPaymentData(); //existing payment amount was changed. Set to unaccounted and register as a new payment. if (response.Amount == 0 || response.Amount != cd.Amount) { existingPayment.Accounted = false; await _invoiceRepository.UpdatePayments(new List <PaymentEntity>() { existingPayment }); if (response.Amount > 0) { var paymentData = new EthereumLikePaymentData() { Address = response.Address, CryptoCode = response.CryptoCode, Amount = response.Amount, Network = network, BlockNumber = response.BlockParameter.ParameterType == BlockParameter.BlockParameterType.blockNumber ? (long?)response.BlockParameter.BlockNumber.Value : null, ConfirmationCount = response.BlockParameter.ParameterType == BlockParameter.BlockParameterType.blockNumber ? 1 : 0, AccountIndex = cd.AccountIndex, XPub = cd.XPub }; var payment = await _invoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, paymentData, network, true); if (payment != null) { ReceivedPayment(invoice, payment); } } } else if (response.Amount == cd.Amount) { //transition from pending to 1 confirmed if (cd.BlockNumber is null && response.BlockParameter.ParameterType == BlockParameter.BlockParameterType.blockNumber) { cd.ConfirmationCount = 1; cd.BlockNumber = (long?)response.BlockParameter.BlockNumber.Value; existingPayment.SetCryptoPaymentData(cd); await _invoiceRepository.UpdatePayments(new List <PaymentEntity>() { existingPayment }); _eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(invoice.Id)); } //increment confirm count else if (response.BlockParameter.ParameterType == BlockParameter.BlockParameterType.blockNumber) { if (response.BlockParameter.BlockNumber.Value > cd.BlockNumber.Value) { cd.ConfirmationCount = (long)(response.BlockParameter.BlockNumber.Value - cd.BlockNumber.Value); } else { cd.BlockNumber = (long?)response.BlockParameter.BlockNumber.Value; cd.ConfirmationCount = 1; } existingPayment.SetCryptoPaymentData(cd); await _invoiceRepository.UpdatePayments(new List <PaymentEntity>() { existingPayment }); _eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(invoice.Id)); } } }
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)); }