public async Task EnqueueWithdrawal(TransactionRequest request)
        {
            var transfer = new EthereumTransfer()
            {
                AmountConverted    = request.Amount,
                AssetId            = request.AssetId,
                DestinationAddress = request.DestinationAddress.ToLower(),
                TransferId         = request.TransferId,
                TransferType       = TransferType.Withdrawal,
                Status             = EthereumTransferStatus.New,
                AccountId          = request.AccountId
            };

            await _databaseHelper.AddTransferNoDuplicates(transfer);

            if (transfer.Id == 0)
            {
                LogError("Withdrawal with transferId = {@TransferId} already exists in database and cannot be re-scheduled", request.TransferId);
            }
            else
            {
                if (!_transfersToSend.TryAdd(transfer.Id, transfer))
                {
                    LogError("Failed to enqueue transfer request for withdrawal: {@TransferForWithdrawal}", transfer);
                }
                else
                {
                    LogInfo("Enqueued transfer request for withdrawal: {@TransferForWithdrawal}", transfer);
                }
            }
        }
        private void InvokeBackofficeCallback(EthereumTransfer transfer, CancellationToken cancellationToken)
        {
            TransferResult transferResult = null;

            do
            {
                try
                {
                    var backofficeRequest = _backofficeTransferHelper.CreateBackofficeTransferRequest(transfer, _options.ConfirmationsRequired);

                    transferResult = TransferCallback.Invoke(backofficeRequest);

                    if (transferResult.HasException)
                    {
                        throw transferResult.Exception;
                    }

                    LogInfo("Withdrawal transfer {@TransferId} with assetId = {@AssetId} and amountEth = {@Amount} to {@ToAddress} failed to be sent",
                            backofficeRequest.TransferId,
                            backofficeRequest.AssetId, backofficeRequest.Amount, backofficeRequest.TargetAccount);
                }
                catch (Exception ex)
                {
                    LogError(ex, "TransferCallback.Invoke failed");
                }
            } while (transferResult == null && !cancellationToken.WaitHandle.WaitOne(InvokeTransferCallbackRetryIntervalMs));
        }
        protected override async Task OnDoWork(ILogger logger, CancellationToken cancellationToken)
        {
            EthereumTransfer currentTransfer = null;

            do
            {
                foreach (var transferPair in _transfersToSend)
                {
                    currentTransfer = transferPair.Value;

                    if (currentTransfer.Status == EthereumTransferStatus.SendingWithdrawal)
                    {
                        if (!string.IsNullOrWhiteSpace(currentTransfer.TransactionHash))
                        {
                            LogInfo("Found SendingWithdrawal transferId = {@TransferId} with known txHashId = {@TxHashId}. Marking it as succeed even if it is not exist",
                                    currentTransfer.TransferId, currentTransfer.TransactionHash);

                            currentTransfer.Status = EthereumTransferStatus.SentWithdrawal;
                            await _databaseHelper.UpdateTransfer(transferPair.Key, currentTransfer.Status);

                            _transfersToSend.TryRemove(transferPair.Key, out _);
                            continue;
                        }
                        ;
                    }

                    if (!string.IsNullOrWhiteSpace(currentTransfer.TransactionHash))
                    {
                        LogError("Attempted to process withdrawal transfer that is already processed: {@Transfer}", currentTransfer);
                        _transfersToSend.TryRemove(transferPair.Key, out _);
                        continue;
                    }

                    await _databaseHelper.UpdateTransfer(transferPair.Key, EthereumTransferStatus.SendingWithdrawal); //to prevent withdrawal duplicating on database failure

                    currentTransfer.TransactionHash = await _hotWalletSender.SendTransferAsync(currentTransfer, cancellationToken, true, true);

                    if (!string.IsNullOrWhiteSpace(currentTransfer.TransactionHash)) //ok
                    {
                        currentTransfer.Status = EthereumTransferStatus.SentWithdrawal;
                        await _databaseHelper.UpdateTransfer(transferPair.Key, currentTransfer.Status, transactionHash : currentTransfer.TransactionHash);

                        _transfersToSend.TryRemove(transferPair.Key, out _);
                    }
                    else if (!cancellationToken.IsCancellationRequested) //not sent
                    {
                        await DequeueSendTransferAsync(currentTransfer, EthereumTransferStatus.Failed);

                        LogError("Failed withdrawal transfer: {@Transfer}", currentTransfer);

                        InvokeBackofficeCallback(currentTransfer, cancellationToken);
                    }
                }
            } while (!cancellationToken.WaitHandle.WaitOne(NewWithdrawalsWaitIntervalMs));
        }
        private async Task DequeueSendTransferAsync(EthereumTransfer transfer, EthereumTransferStatus newStatus, string errorText = null)
        {
            transfer.Status = newStatus;

            if (!string.IsNullOrWhiteSpace(errorText))
            {
                if (transfer.ErrorText == null || transfer.ErrorText.Length < 4000)
                {
                    transfer.ErrorText = (transfer.ErrorText ?? string.Empty) + errorText;
                    await _databaseHelper.UpdateTransfer(transfer.Id, transfer.Status, errorText : transfer.ErrorText);
                }
            }
            else
            {
                await _databaseHelper.UpdateTransfer(transfer.Id, transfer.Status);
            }

            _transfersToSend.TryRemove(transfer.Id, out _);
        }