public async Task HandledDepositAsync(SignalBitGoTransfer transferId)
        {
            transferId.AddToActivityAsJsonTag("bitgo signal");

            _logger.LogInformation("Request to handle transfer from BitGo: {transferJson}",
                                   JsonConvert.SerializeObject(transferId));

            var(brokerId, assetSymbol) = _assetMapper.BitgoCoinToAsset(transferId.Coin, transferId.WalletId);

            if (string.IsNullOrEmpty(brokerId) || string.IsNullOrEmpty(assetSymbol))
            {
                _logger.LogWarning("Cannot handle BitGo deposit, asset do not found {transferJson}",
                                   JsonConvert.SerializeObject(transferId));
                return;
            }

            var coin = _bitgoCoinReader.Get(BitgoCoinEntity.GeneratePartitionKey(),
                                            BitgoCoinEntity.GenerateRowKey(transferId.Coin));

            var transferResp =
                await _bitgoClient.Get(coin.IsMainNet)
                .GetTransferAsync(transferId.Coin, transferId.WalletId, transferId.TransferId);

            var transfer = transferResp.Data;

            if (transfer == null)
            {
                _logger.LogWarning("Cannot handle BitGo deposit, transfer do not found {transferJson}",
                                   JsonConvert.SerializeObject(transferId));
                Activity.Current?.SetStatus(Status.Error);
                return;
            }

            transfer.AddToActivityAsJsonTag("bitgo-transfer");

            _logger.LogInformation("Transfer from BitGo: {transferJson}", JsonConvert.SerializeObject(transfer));

            var requirement = _assetMapper.GetRequiredConfirmations(transfer.Coin);

            if (transfer.Confirmations < requirement)
            {
                _logger.LogError(
                    $"Transaction do not has enough conformations. Transaction has: {transfer.Confirmations}, requirement: {requirement}");
                Activity.Current?.SetStatus(Status.Error);
                throw new Exception(
                          $"Transaction do not has enough conformations. Transaction has: {transfer.Confirmations}, requirement: {requirement}");
            }

            if (!_assetMapper.IsWalletEnabled(transfer.Coin, transfer.WalletId))
            {
                _logger.LogError(
                    "Transfer {transferIdString} from BitGo is skipped, Wallet do not include in enabled wallet list",
                    transfer.TransferId);
                Activity.Current?.SetStatus(Status.Error);
                return;
            }

            foreach (var entryGroup in transfer.Entries
                     .Where(e => e.Value > 0 && !string.IsNullOrEmpty(e.Label) && e.WalletId == transferId.WalletId &&
                            (e.Token == null || e.Token == transferId.Coin))
                     .GroupBy(e => e.Label))
            {
                var label  = entryGroup.Key;
                var wallet = _walletMapper.BitgoLabelToWallet(label);
                if (wallet == null)
                {
                    _logger.LogWarning("Cannot found wallet for transfer entry with label={label} address={address}",
                                       label, entryGroup.First().Address);
                    continue;
                }

                wallet.WalletId.AddToActivityAsTag("walletId");
                wallet.ClientId.AddToActivityAsTag("clientId");

                var bitgoAmount = entryGroup.Sum(e => e.Value);
                var meAmount    = _assetMapper.ConvertAmountFromBitgo(transferId.Coin, bitgoAmount);

                var accuracy = _assetsDictionary.GetAssetById(new AssetIdentity()
                {
                    Symbol   = assetSymbol,
                    BrokerId = wallet.BrokerId
                }).Accuracy;

                var roundedAmount = Math.Round(meAmount, accuracy, MidpointRounding.ToNegativeInfinity);

                try
                {
                    await using var ctx = DatabaseContext.Create(_dbContextOptionsBuilder);
                    await ctx.InsertAsync(new DepositEntity
                    {
                        BrokerId      = wallet.BrokerId,
                        WalletId      = wallet.WalletId,
                        ClientId      = wallet.ClientId,
                        TransactionId = $"{transfer.TransferId}:{wallet.WalletId}",
                        Amount        = (decimal)roundedAmount,
                        AssetSymbol   = assetSymbol,
                        Comment       =
                            $"Bitgo transfer [{transferId.Coin}:{transferId.WalletId}] entry label='{label}', count entry={entryGroup.Count()}",
                        Integration    = "BitGo",
                        Txid           = transfer.TxId,
                        Status         = DepositStatus.New,
                        EventDate      = DateTime.UtcNow,
                        UpdateDate     = DateTime.UtcNow,
                        FeeAmount      = 0,
                        FeeAssetSymbol = assetSymbol,
                        CardLast4      = string.Empty
                    });
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
            }

            _logger.LogInformation("Deposit request from BitGo {transferIdString} is handled", transfer.TransferId);
        }
        public async Task <SendTransactionResponse> SignAndSendTransactionAsync(SendTransactionRequest request)
        {
            _logger.LogInformation("Transfer Request: {jsonText}", JsonConvert.SerializeObject(request));

            try
            {
                var client = _bitGoClientService.GetByUser(request.BrokerId, BitGoUserNoSqlEntity.TechSignerId, request.BitgoCoin);
                if (client == null)
                {
                    throw new Exception($"Tech account is not configured, id = {BitGoUserNoSqlEntity.TechSignerId}, coin = {request.BitgoCoin}");
                }

                var wallet = _myNoSqlServerWalletDataReader.Get(
                    BitGoWalletNoSqlEntity.GeneratePartitionKey(request.BrokerId),
                    BitGoWalletNoSqlEntity.GenerateRowKey(request.BitgoWalletId));

                if (string.IsNullOrEmpty(wallet?.Wallet?.ApiKey))
                {
                    _logger.LogError("Cannot find pass phase for wallet {bitgoWalletIdText}", request.BitgoWalletId);
                    throw new Exception($"Cannot find pass phase for wallet {request.BitgoWalletId}");
                }

                var walletPass = _encryptionService.Decrypt(wallet.Wallet.ApiKey);

                var result = await client.SendCoinsAsync(request.BitgoCoin, request.BitgoWalletId,
                                                         walletPass, request.SequenceId,
                                                         request.Amount, request.Address);

                if (!result.Success)
                {
                    switch (result.Error.Code)
                    {
                    case "DuplicateSequenceIdError":
                    {
                        var transaction = await client.GetTransferBySequenceIdAsync(request.BitgoCoin,
                                                                                    request.BitgoWalletId, request.SequenceId);

                        if (!transaction.Success || transaction.Data == null)
                        {
                            _logger.LogError("Transfer is Duplicate, but cannot found transaction: {jsonText}",
                                             JsonConvert.SerializeObject(transaction.Error));

                            return(new SendTransactionResponse()
                                {
                                    Error = result.Error
                                });
                        }

                        _logger.LogInformation("Transfer is Duplicate, Result: {jsonText}",
                                               JsonConvert.SerializeObject(transaction.Data));

                        return(new SendTransactionResponse()
                            {
                                DuplicateTransaction = transaction.Data
                            });
                    }

                    case "needs unlock":
                        await _sessionPublisher.PublishAsync(new SignalBitGoSessionStateUpdate()
                        {
                            State = BitGoSessionState.Locked
                        });

                        return(new SendTransactionResponse()
                        {
                            Error =
                            {
                                Code         = "needs unlock",
                                ErrorMessage = "Session is locked"
                            }
                        });
                    }
                }

                if (!result.Success)
                {
                    _logger.LogError("Transfer Result: {jsonText}", JsonConvert.SerializeObject(result.Error));
                    return(new SendTransactionResponse()
                    {
                        Error = result.Error
                    });
                }

                await AddVolumeToApiKey(request.BrokerId, _encryptionService.GetSha256Hash(wallet.Wallet.ApiKey),
                                        request.BitgoCoin,
                                        _assetMapper.ConvertAmountFromBitgo(request.BitgoCoin, long.Parse(request.Amount)));

                _logger.LogInformation("Transfer Result: {jsonText}", JsonConvert.SerializeObject(result.Data));
                return(new SendTransactionResponse()
                {
                    Result = result.Data
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Transfer Request ERROR: {jsonText}. Error: {message}",
                                 JsonConvert.SerializeObject(request), ex.Message);
                throw;
            }
        }
        public async Task <GetBitGoWalletLimitsResponse> GetSpendingLimitsAsync(GetBitGoWalletLimitsRequest request)
        {
            try
            {
                var(coin, wallet) = _assetMapper.AssetToBitgoCoinAndWallet(request.BrokerId, request.AssetId);

                var client = _bitGoClientService.GetByUser(request.BrokerId, BitGoUserNoSqlEntity.TechSignerId, coin);
                if (client == null)
                {
                    return(new GetBitGoWalletLimitsResponse
                    {
                        Success = false,
                        Error = $"Tech account is not configured, id = {BitGoUserNoSqlEntity.TechSignerId}, coin = {coin}"
                    });
                }

                var result = await client.GetSpendingLimitsForWalletAsync(coin, wallet);

                if (!result.Success)
                {
                    _logger.LogInformation(
                        $"Unable to get spending limits: {JsonConvert.SerializeObject(result.Error)}");
                    return(new GetBitGoWalletLimitsResponse
                    {
                        Success = false,
                        Error = result.Error.Message
                    });
                }

                var limit = new SpendingLimit
                {
                    AssetId   = request.AssetId,
                    BitgoCoin = coin
                };

                foreach (var spendingLimit in result.Data.Limits)
                {
                    if (spendingLimit.Coin == coin)
                    {
                        switch (spendingLimit.TimeWindow)
                        {
                        case "0":
                            limit.TransactionLimit =
                                _assetMapper.ConvertAmountFromBitgo(coin, decimal.Parse(spendingLimit.LimitAmountString));
                            break;

                        case "3600":
                            limit.HourlyLimit =
                                _assetMapper.ConvertAmountFromBitgo(coin, decimal.Parse(spendingLimit.LimitAmountString));
                            limit.HourlySpent =
                                _assetMapper.ConvertAmountFromBitgo(coin, decimal.Parse(spendingLimit.AmountSpentString));
                            break;

                        case "86400":
                            limit.DailyLimit =
                                _assetMapper.ConvertAmountFromBitgo(coin, decimal.Parse(spendingLimit.LimitAmountString));
                            limit.DailySpent =
                                _assetMapper.ConvertAmountFromBitgo(coin, decimal.Parse(spendingLimit.AmountSpentString));
                            break;
                        }
                    }
                }

                return(new GetBitGoWalletLimitsResponse
                {
                    Success = true,
                    Limit = limit
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "GetSpendingLimitsAsync request error: {message}", ex.Message);
                throw;
            }
        }