private DepositWallet NormalizeWalletOrDefault(DepositWallet wallet)
        {
            if (string.IsNullOrWhiteSpace(wallet.Address) ||
                string.IsNullOrWhiteSpace(wallet.CryptoCurrency) ||
                wallet.UserId == Guid.Empty)
            {
                return(null);
            }

            var address = _addressNormalizer.NormalizeOrDefault(wallet.Address, wallet.CryptoCurrency);

            if (address == null)
            {
                _log.Warning
                (
                    "It is not a valid address, skipping",
                    context: new
                {
                    Address        = wallet.Address,
                    CryptoCurrency = wallet.CryptoCurrency
                }
                );
                return(null);
            }

            return(new DepositWallet(wallet.UserId, address, wallet.CryptoCurrency));
        }
        private async Task ProcessDepositWalletAsync(TransactionsReportBuilder reportBuilder, DepositWallet wallet)
        {
            try
            {
                foreach (var historyProvider in _depositsHistoryProviders)
                {
                    if (!historyProvider.CanProvideHistoryFor(wallet))
                    {
                        continue;
                    }

                    PaginatedList <Transaction> transactions = null;
                    var processedWalletTransactionsCount     = 0;

                    do
                    {
                        transactions = await Policy
                                       .Handle <Exception>(ex =>
                        {
                            _log.Warning
                            (
                                "Failed to get deposits history. Operation will be retried.",
                                context: new
                            {
                                Address         = wallet.Address,
                                CryptoCurrency  = wallet.CryptoCurrency,
                                HistoryProvider = historyProvider.GetType().Name
                            },
                                exception: ex
                            );
                            return(true);
                        })
                                       .WaitAndRetryForeverAsync(i => TimeSpan.FromSeconds(Math.Min(i, 5)))
                                       .ExecuteAsync(async() => await historyProvider.GetHistoryAsync(wallet, transactions?.Continuation));

                        foreach (var tx in transactions.Items)
                        {
                            var normalizedTransaction = NormalizeTransactionOrDefault(tx);
                            if (normalizedTransaction == null)
                            {
                                continue;
                            }

                            reportBuilder.AddTransaction(normalizedTransaction);

                            Interlocked.Increment(ref _exportedDepositsCount);
                            ++processedWalletTransactionsCount;

                            if (processedWalletTransactionsCount % 100 == 0)
                            {
                                _log.Info($"{processedWalletTransactionsCount} deposits processed so far of {wallet.CryptoCurrency}:{wallet.Address} wallet using {historyProvider.GetType().Name}");
                            }
                        }
                    } while (transactions.Continuation != null);
                }
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"Failed to process deposit wallet: {wallet.CryptoCurrency}:{wallet.Address}:{wallet.UserId}", ex);
            }
            finally
            {
                var processedWalletsCount = Interlocked.Increment(ref _processedWalletsCount);

                if (processedWalletsCount % 100 == 0)
                {
                    var completedPercent = processedWalletsCount * 100 / _totalDepositWalletsCount;
                    _log.Info($"{processedWalletsCount} wallets processed so far ({completedPercent}%). {_exportedDepositsCount} deposits exported so far.");
                }

                _concurrencySemaphore.Release();
            }
        }