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);
        }
示例#2
0
        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));
        }