public async Task HandleAsync(
            string sourceAddress,
            string targetAddress,
            Money18 amount,
            string transactionHash,
            DateTime observedAt)
        {
            #region Validation

            if (string.IsNullOrEmpty(sourceAddress))
            {
                _log.Warning("Source address is empty");
                return;
            }

            if (string.IsNullOrEmpty(targetAddress))
            {
                _log.Warning("Target address is empty");
                return;
            }

            if (sourceAddress.Equals(EmptyWalletAddress) || targetAddress.Equals(EmptyWalletAddress))
            {
                return;
            }

            if (amount <= 0)
            {
                _log.Warning("Amount is less or equal 0");
                return;
            }

            if (string.IsNullOrEmpty(transactionHash))
            {
                _log.Warning("Transaction hash is empty");
                return;
            }

            #endregion

            #region Find operation

            var operation = await _operationsFetcher.GetByHashAsync(transactionHash);

            if (operation == null)
            {
                var transactionState = await _executorClient.TransactionsApi.GetTransactionStateAsync(transactionHash);

                if (transactionState.Error != GetTransactionStateError.None)
                {
                    _log.Error(message: "Already processed operation was not found by hash",
                               context: new { transactionHash, sourceAddress, targetAddress });
                    return;
                }

                if (!transactionState.OperationId.HasValue)
                {
                    _log.Warning("Operation id is empty",
                                 context: new { transactionHash, sourceAddress, targetAddress });
                    return;
                }

                operation = await _operationsFetcher.GetByIdAsync(transactionState.OperationId.Value);

                if (operation == null)
                {
                    _log.Error(message: "Already processed operation was not found by id",
                               context: new
                    {
                        id = transactionState.OperationId.Value, transactionHash, sourceAddress, targetAddress
                    });
                    return;
                }
            }

            #endregion

            var tasksForBalanceUpdate = new List <Task>();

            var sourceWalletOwner = await _walletOwnersRepository.GetByWalletAddressAsync(sourceAddress);

            if (sourceWalletOwner == null)
            {
                _log.Info("Transfer with unknown source wallet owner", context: sourceAddress);
            }
            else
            {
                tasksForBalanceUpdate.Add(_balanceService.ForceBalanceUpdateAsync(sourceWalletOwner.OwnerId, operation.Type, operation.Id));
            }

            var targetWalletOwner = await _walletOwnersRepository.GetByWalletAddressAsync(targetAddress);

            if (targetWalletOwner == null)
            {
                _log.Info("Transfer with unknown target wallet owner", context: targetAddress);
            }
            else
            {
                tasksForBalanceUpdate.Add(_balanceService.ForceBalanceUpdateAsync(targetWalletOwner.OwnerId, operation.Type, operation.Id));
            }

            await Task.WhenAll(tasksForBalanceUpdate);

            var context = JsonConvert.DeserializeObject <TokensTransferContext>(operation.ContextJson);

            //If there is not customer behind one of the wallets then this is not P2P transfer
            if (sourceWalletOwner != null && targetWalletOwner != null)
            {
                await _p2PTransferPublisher.PublishAsync(new P2PTransferDetectedEvent
                {
                    TransactionHash    = transactionHash,
                    SenderCustomerId   = sourceWalletOwner.OwnerId,
                    ReceiverCustomerId = targetWalletOwner.OwnerId,
                    Amount             = amount,
                    Timestamp          = observedAt,
                    RequestId          = context.RequestId
                });
            }

            await _transferDetectedPublisher.PublishAsync(new TransferDetectedEvent
            {
                TransactionHash    = transactionHash,
                SenderCustomerId   = sourceWalletOwner?.OwnerId,
                ReceiverCustomerId = targetWalletOwner?.OwnerId,
                Amount             = amount,
                Timestamp          = observedAt,
                RequestId          = context.RequestId
            });
        }
        public async Task HandleAsync(string transactionHash, Money18 amount, string walletAddress, DateTime observedAt)
        {
            if (string.IsNullOrEmpty(transactionHash))
            {
                _log.Warning("Mint event with empty hash received");
                return;
            }

            if (amount <= 0)
            {
                _log.Warning("Invalid amount for handling MintEvent",
                             context: new { amount, hash = transactionHash });
                return;
            }

            var walletOwner = await _walletOwnersRepository.GetByWalletAddressAsync(walletAddress);

            if (walletOwner == null)
            {
                _log.Error(message: "Mint event for wallet address which does not exist", context: walletAddress);
                return;
            }

            var operation = await _operationsFetcher.GetByHashAsync(transactionHash);

            if (operation == null)
            {
                var transactionState = await _executorClient.TransactionsApi.GetTransactionStateAsync(transactionHash);

                if (transactionState.Error != GetTransactionStateError.None)
                {
                    _log.Error(message: "Already processed operation was not found by hash",
                               context: new { hash = transactionHash, walletOwner, walletAddress });
                    return;
                }

                if (!transactionState.OperationId.HasValue)
                {
                    _log.Warning("Operation id is empty", context: new { hash = transactionHash, amount, walletAddress });
                    return;
                }

                operation = await _operationsFetcher.GetByIdAsync(transactionState.OperationId.Value);

                if (operation == null)
                {
                    _log.Error(message: "Already processed operation was not found by id",
                               context: new { id = transactionState.OperationId.Value, transactionHash, walletOwner, walletAddress });
                    return;
                }
            }

            await _balanceService.ForceBalanceUpdateAsync(walletOwner.OwnerId, operation.Type, operation.Id);

            var bonusRewardContext = JsonConvert.DeserializeObject <CustomerBonusRewardContext>(operation.ContextJson);

            await _bonusRewardDetectedPublisher.PublishAsync(new BonusRewardDetectedEvent
            {
                Amount      = amount,
                Timestamp   = observedAt,
                CustomerId  = walletOwner.OwnerId,
                RequestId   = bonusRewardContext.RequestId,
                BonusReason = bonusRewardContext.BonusReason,
                CampaignId  = bonusRewardContext.CampaignId,
                ConditionId = bonusRewardContext.ConditionId
            });
        }