Exemple #1
0
        private async Task PublishSuccess(WithdrawalEntity withdrawal)
        {
            var retriesCount = withdrawal.RetriesCount;
            var lastError    = withdrawal.LastError;
            var state        = withdrawal.WorkflowState;

            try
            {
                withdrawal.RetriesCount  = 0;
                withdrawal.LastError     = null;
                withdrawal.WorkflowState = WithdrawalWorkflowState.OK;

                await _withdrawalPublisher.PublishAsync(new Withdrawal(withdrawal));

                _logger.LogInformation(
                    "Withdrawal with Operation ID {operationId} is changed to status {status}. Operation: {operationJson}",
                    withdrawal.TransactionId, withdrawal.Status, JsonConvert.SerializeObject(withdrawal));
            }
            catch (Exception e)
            {
                withdrawal.RetriesCount  = retriesCount;
                withdrawal.LastError     = lastError;
                withdrawal.WorkflowState = state;
                throw;
            }
        }
Exemple #2
0
        public async Task RefundWithdrawalFeeInMeAsync(WithdrawalEntity withdrawalEntity)
        {
            withdrawalEntity.FeeRefundTransactionId =
                OperationIdGenerator.GenerateOperationId("fee_refund", withdrawalEntity.TransactionId);
            var fees = _assetFeesClient.GetAssetFees(withdrawalEntity.BrokerId, withdrawalEntity.AssetSymbol,
                                                     OperationType.Withdrawal);

            var changeBalanceResult = await _changeBalanceService.RefundBlockchainWithdrawalAsync(new RefundBlockchainWithdrawalGrpcRequest()
            {
                TransactionId = withdrawalEntity.FeeRefundTransactionId,
                ClientId      = fees.AccountId,
                WalletId      = withdrawalEntity.WalletId,
                Amount        = withdrawalEntity.FeeAmount,
                AssetSymbol   = withdrawalEntity.FeeAssetSymbol,
                Comment       = $"Fee refund for transaction: {withdrawalEntity.TransactionId}",
                BrokerId      = withdrawalEntity.BrokerId,
                Integration   = withdrawalEntity.Integration,
                Txid          = $"Internal fee refund {withdrawalEntity.Id}"
            });

            if (changeBalanceResult.ErrorCode == ChangeBalanceGrpcResponse.ErrorCodeEnum.LowBalance ||
                changeBalanceResult.ErrorCode == ChangeBalanceGrpcResponse.ErrorCodeEnum.WalletDoNotFound)
            {
                withdrawalEntity.MeErrorCode = GetErrorCode(BitgoErrorType.ErrorCode.LowBalance);
                throw new Exception($"Cannot execute fee refund in ME: changeBalanceResult.ErrorMessage");
            }

            if (changeBalanceResult.ErrorCode != ChangeBalanceGrpcResponse.ErrorCodeEnum.Ok &&
                changeBalanceResult.ErrorCode != ChangeBalanceGrpcResponse.ErrorCodeEnum.Duplicate)
            {
                withdrawalEntity.MeErrorCode = GetErrorCode(BitgoErrorType.ErrorCode.InternalError);
                throw new Exception($"Cannot execute fee refund in ME: changeBalanceResult.ErrorMessage");
            }
        }
Exemple #3
0
        public async Task ExecuteInternalWithdrawalInMeAsync(WithdrawalEntity withdrawalEntity)
        {
            var request = new CryptoWithdrawalRequest
            {
                Amount      = withdrawalEntity.Amount,
                AssetSymbol = withdrawalEntity.AssetSymbol,
                BrokerId    = withdrawalEntity.BrokerId,
                ClientId    = withdrawalEntity.ClientId,
                WalletId    = withdrawalEntity.WalletId,
                RequestId   = withdrawalEntity.TransactionId,
                ToAddress   = withdrawalEntity.ToAddress
            };

            var executeResult = await SendTransferAsync(withdrawalEntity, request);

            if (executeResult != null)
            {
                withdrawalEntity.MeErrorCode = GetErrorCode(executeResult.Error.Code);
                throw new Exception(
                          $"[CryptoWithdrawalRequest] Cannot execute withdrawal in ME: {JsonConvert.SerializeObject(executeResult)}");
            }

            withdrawalEntity.Status           = WithdrawalStatus.Success;
            withdrawalEntity.Txid             = $"Internal Transfer {withdrawalEntity.Id}";
            withdrawalEntity.MatchingEngineId = withdrawalEntity.TransactionId;
        }
Exemple #4
0
        private async Task HandleError(WithdrawalEntity withdrawal, Exception ex, bool retrying = true)
        {
            ex.FailActivity();

            withdrawal.WorkflowState = WithdrawalWorkflowState.Retrying;

            withdrawal.LastError = ex.Message.Length > 2048 ? ex.Message.Substring(0, 2048) : ex.Message;
            withdrawal.RetriesCount++;
            if (withdrawal.RetriesCount >= Program.Settings.WithdrawalsRetriesLimit || !retrying)
            {
                withdrawal.WorkflowState = WithdrawalWorkflowState.Failed;
            }

            _logger.LogError(ex,
                             "Withdrawal with Operation ID {operationId} changed workflow state to {status}. Operation: {operationJson}",
                             withdrawal.TransactionId, withdrawal.WorkflowState, JsonConvert.SerializeObject(withdrawal));
            try
            {
                await _withdrawalPublisher.PublishAsync(new Withdrawal(withdrawal));
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Can not publish error operation status {operationJson}",
                                 JsonConvert.SerializeObject(withdrawal));
            }
        }
Exemple #5
0
        public WithdrawalEntity GetOne(int userId)
        {
            try
            {
                using (Database db = new Database(GlobalObjects.CONNECTION_STRING))
                {
                    db.Open();
                    string    sql;
                    int       ret    = 0;
                    DataTable oTable = new DataTable();
                    sql = "GetWithdrawal";
                    db.ExecuteCommandReader(sql,
                                            new string[] { "@id" },
                                            new DbType[] { DbType.Int32 },
                                            new object[] { userId },
                                            out ret, ref oTable, CommandTypeEnum.StoredProcedure);

                    WithdrawalEntity user = new WithdrawalEntity();
                    if (oTable.Rows.Count > 0)
                    {
                        DataRow oRow = oTable.Rows[0];
                        user = SetData(oRow);
                    }

                    return(user);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
Exemple #6
0
 public Task RetryWithdrawalAsync(WithdrawalEntity withdrawalEntity)
 {
     withdrawalEntity.WorkflowState = WithdrawalWorkflowState.Retrying;
     _logger.LogInformation("Manual retry withdrawal with Operation Id {operationId} and status {status}",
                            withdrawalEntity.TransactionId, withdrawalEntity.Status);
     return(Task.CompletedTask);
 }
Exemple #7
0
        private async Task PublishSuccess(WithdrawalEntity withdrawal)
        {
            withdrawal.RetriesCount  = 0;
            withdrawal.LastError     = null;
            withdrawal.WorkflowState = WithdrawalWorkflowState.OK;

            await Publish(withdrawal);
        }
        private void Update()
        {
            newEntity = new WithdrawalEntity();

            newEntity.ID    = id;
            newEntity.Notes = this.txtNotes.Text.Trim();
            newEntity.Date  = Convert.ToDateTime(txtDate.Text);
            newService.Save(ActionType.Update, newEntity);

            Response.Redirect("ManageExpenses.aspx");
        }
        private void Create()
        {
            newEntity = new WithdrawalEntity();

            newEntity.Amount = Convert.ToDecimal(this.txtAmount.Text.Trim());
            newEntity.Date   = Convert.ToDateTime(this.txtDate.Text.Trim());
            newEntity.Userid = GlobalObjects.User.ID;
            newEntity.Notes  = this.txtNotes.Text.Trim();
            newEntity.Status = 1;

            newService.Save(ActionType.Create, newEntity, 1);

            Response.Redirect("ManageExpenses.aspx");
        }
        private void Create()
        {
            newEntity = new WithdrawalEntity();

            newEntity.Amount = Convert.ToDecimal(this.txtAmount.Text.Trim());
            newEntity.Date   = Convert.ToDateTime(this.txtDate.Text.Trim());
            newEntity.Userid = Convert.ToInt32(ddlInvestor.SelectedValue);
            newEntity.Notes  = this.txtNotes.Text.Trim();
            newEntity.Status = 1;

            newService.Save(ActionType.Create, newEntity, 0);

            Response.Redirect("ManageWithdrawals.aspx");
        }
Exemple #11
0
        private WithdrawalEntity SetData(DataRow oRow)
        {
            try
            {
                WithdrawalEntity ent = new WithdrawalEntity();
                ent.ID     = Convert.ToInt32(oRow["id"]);
                ent.Amount = Convert.ToDecimal(oRow["amount"]);
                ent.Userid = Convert.ToInt32(oRow["user_id"]);
                ent.Date   = Convert.ToDateTime(oRow["date"]);
                ent.Notes  = oRow["notes"].ToString();
                ent.Status = Convert.ToInt32(oRow["status"]);

                return(ent);
            }
            catch (Exception ex) { throw ex; }
        }
        private void PopulateFields(int id)
        {
            newEntity = new WithdrawalEntity();
            newEntity = newService.GetOne(id);


            this.txtDate.Text   = newEntity.Date.ToString("yyyy-MM-dd");
            this.txtNotes.Text  = newEntity.Notes;
            this.txtAmount.Text = newEntity.Amount.ToString();



            //ddlInvestor.Enabled = false;
            //txtDate.Enabled = false;
            //this.txtAmount.Enabled = false;
        }
Exemple #13
0
        public async Task ExecuteFireblocksWithdrawalInBlockchainAsync(WithdrawalEntity withdrawalEntity,
                                                                       Domain.Models.Settings.FireblocksWithdrawalMode mode)
        {
            var amount = (withdrawalEntity.AssetSymbol == withdrawalEntity.FeeAssetSymbol)
                ? withdrawalEntity.Amount - withdrawalEntity.FeeAmount
                : withdrawalEntity.Amount;

            var request = new Fireblocks.Signer.Grpc.Models.Transactions.CreateTransactionRequest
            {
                Amount                = amount,
                ToAddress             = withdrawalEntity.ToAddress,
                AssetNetwork          = withdrawalEntity.Blockchain,
                AssetSymbol           = withdrawalEntity.AssetSymbol,
                ExternalTransactionId = "fire_tx_" + withdrawalEntity.Id + "_" + withdrawalEntity.RetriesCount,
                Tag              = withdrawalEntity.ToTag,
                AmountWithFee    = withdrawalEntity.Amount,
                ClientId         = withdrawalEntity.ClientId,
                IssuedAtUnixTime = withdrawalEntity.SignatureIssuedAt,
                Signature        = withdrawalEntity.Signature,
            };

            if ((mode & Domain.Models.Settings.FireblocksWithdrawalMode.Automatic) == Domain.Models.Settings.FireblocksWithdrawalMode.Automatic)
            {
                request.AddToActivityAsJsonTag("transfer-request");

                var transferResult = await _transactionService.CreateTransactionAsync(request);

                _logger.LogDebug("[CryptoWithdrawalRequest] Withdrawal in Fireblocks ({operationIdText}): {jsonText}",
                                 withdrawalEntity.TransactionId, JsonConvert.SerializeObject(transferResult));

                transferResult.AddToActivityAsJsonTag("transfer-result");

                if (transferResult.Error != null)
                {
                    throw new Exception(
                              $"[CryptoWithdrawalRequest] Cannot execute withdrawal in Fireblocks ({withdrawalEntity.TransactionId}): {JsonConvert.SerializeObject(transferResult)}");
                }

                withdrawalEntity.Status = WithdrawalStatus.BlockchainProcessing;
            }
            else
            {
                //NOT AN ERROR!
                _logger.LogError("[CryptoWithdrawalRequest] Withdrawal in Fireblocks awaits manual processing! This is not an actual error: {jsonText}",
                                 withdrawalEntity.TransactionId, JsonConvert.SerializeObject(withdrawalEntity));
            }
        }
Exemple #14
0
        public async Task RefundWithdrawalInMeAsync(WithdrawalEntity withdrawalEntity)
        {
            withdrawalEntity.RefundTransactionId =
                OperationIdGenerator.GenerateOperationId("refund", withdrawalEntity.TransactionId);

            var changeBalanceResult = await _changeBalanceService.RefundBlockchainWithdrawalAsync(
                new RefundBlockchainWithdrawalGrpcRequest(
                    withdrawalEntity.RefundTransactionId,
                    withdrawalEntity.ClientId,
                    withdrawalEntity.WalletId,
                    withdrawalEntity.Amount,
                    withdrawalEntity.AssetSymbol,
                    $"Refund for transaction: {withdrawalEntity.TransactionId}",
                    withdrawalEntity.BrokerId,
                    withdrawalEntity.Integration,
                    $"Internal refund {withdrawalEntity.Id}"
                    ));

            if (changeBalanceResult.ErrorCode == ChangeBalanceGrpcResponse.ErrorCodeEnum.LowBalance ||
                changeBalanceResult.ErrorCode == ChangeBalanceGrpcResponse.ErrorCodeEnum.WalletDoNotFound)
            {
                withdrawalEntity.MeErrorCode = GetErrorCode(BitgoErrorType.ErrorCode.LowBalance);
                throw new Exception($"Cannot execute deposit refund in ME: changeBalanceResult.ErrorMessage");
            }

            if (changeBalanceResult.ErrorCode != ChangeBalanceGrpcResponse.ErrorCodeEnum.Ok &&
                changeBalanceResult.ErrorCode != ChangeBalanceGrpcResponse.ErrorCodeEnum.Duplicate)
            {
                withdrawalEntity.MeErrorCode = GetErrorCode(BitgoErrorType.ErrorCode.InternalError);
                throw new Exception($"Cannot execute deposit refund in ME: changeBalanceResult.ErrorMessage");
            }

            if (withdrawalEntity.AssetSymbol != withdrawalEntity.FeeAssetSymbol)
            {
                await RefundWithdrawalFeeInMeAsync(withdrawalEntity);
            }

            withdrawalEntity.Status = WithdrawalStatus.Cancelled;
        }
 public async Task UpdateAsync(WithdrawalEntity entity)
 {
     await UpdateAsync(new List <WithdrawalEntity> {
         entity
     });
 }
Exemple #16
0
        public void Save(ActionType type, WithdrawalEntity ent, int remType = 0)
        {
            try
            {
                using (Database db = new Database(GlobalObjects.CONNECTION_STRING))
                {
                    db.Open();
                    int      ret = 0;
                    int      typ = (int)type;
                    string   sql = "SaveWithdrawals";
                    string[] asParams;
                    DbType[] atParamTypes;
                    object[] aoValues;


                    asParams = new string[] { "@actiontype",
                                              "@id",
                                              "@userid",
                                              "@amount",
                                              "@date",
                                              "@notes",
                                              "@remType",
                                              "@status",
                                              "@createdby",
                                              "@createddate",
                                              "@updatedby",
                                              "@updateddate" };

                    atParamTypes = new DbType[] {
                        DbType.Int16,
                        DbType.Int32,
                        DbType.Int32,
                        DbType.Decimal,
                        DbType.Date,
                        DbType.String,
                        DbType.Int16,
                        DbType.Int32,
                        DbType.String,
                        DbType.DateTime,
                        DbType.String,
                        DbType.DateTime
                    };

                    aoValues = new object[] {
                        typ,
                        ent.ID,
                        ent.Userid,
                        ent.Amount,
                        ent.Date,
                        ent.Notes,
                        remType,
                        ent.Status,
                        appUsr.UserName,
                        DateTime.Now,
                        appUsr.UserName,
                        DateTime.Now
                    };

                    db.ExecuteCommandNonQuery(sql, asParams, atParamTypes, aoValues, out ret, CommandTypeEnum.StoredProcedure);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
Exemple #17
0
        public async Task ExecuteCircleWithdrawalInBlockchainAsync(WithdrawalEntity withdrawalEntity)
        {
            var circleAsset =
                _circleAssetMapper.AssetToCircleTokenAsset(withdrawalEntity.BrokerId, withdrawalEntity.AssetSymbol);

            if (circleAsset == null)
            {
                throw new Exception(
                          $"[CryptoWithdrawalRequest] Cannot found circle coin association for asset {withdrawalEntity.AssetSymbol}, broker {withdrawalEntity.BrokerId}");
            }

            var circleBlockchain =
                _circleBlockchainMapper.BlockchainToCircleBlockchain(withdrawalEntity.BrokerId,
                                                                     withdrawalEntity.Blockchain);

            if (circleBlockchain == null)
            {
                throw new Exception(
                          $"[CryptoWithdrawalRequest] Cannot found circle blockchain association for blockchain {withdrawalEntity.Blockchain}, broker {withdrawalEntity.BrokerId}");
            }

            var amount = (withdrawalEntity.AssetSymbol == withdrawalEntity.FeeAssetSymbol)
                ? withdrawalEntity.Amount - withdrawalEntity.FeeAmount
                : withdrawalEntity.Amount;

            var address    = withdrawalEntity.ToAddress;
            var addressTag = withdrawalEntity.ToTag;

            //var tagSeparator = circleBlockchain.TagSeparator;
            //if (string.IsNullOrEmpty(tagSeparator))
            //{
            //    address = withdrawalEntity.ToAddress;
            //    addressTag = null;
            //}
            //else
            //{
            //    address = withdrawalEntity.ToAddress.Split(tagSeparator)[0];
            //    addressTag = withdrawalEntity.ToAddress.Split(tagSeparator)[1];
            //}


            var addTransferRequest = new AddTransferRequest
            {
                BrokerId       = withdrawalEntity.BrokerId,
                ClientId       = withdrawalEntity.ClientId,
                IdempotencyKey = withdrawalEntity.TransactionId,
                SourceId       = circleAsset.CircleWalletId,
                Amount         = (double)amount,
                Currency       = circleAsset.CircleAsset,
                DstAddress     = address,
                DstAddressTag  = addressTag,
                DstChain       = circleBlockchain.CircleBlockchain
            };

            addTransferRequest.AddToActivityAsJsonTag("transfer-request");

            var transferResult = await _circlePaymentsService.AddCircleTransfer(addTransferRequest);

            _logger.LogInformation("[CryptoWithdrawalRequest] Withdrawal in Circle ({operationIdText}): {jsonText}",
                                   withdrawalEntity.TransactionId, JsonConvert.SerializeObject(transferResult));

            transferResult.AddToActivityAsJsonTag("transfer-result");

            if (!transferResult.IsSuccess)
            {
                throw new Exception(
                          $"[CryptoWithdrawalRequest] Cannot execute withdrawal in Circle ({withdrawalEntity.TransactionId}): {JsonConvert.SerializeObject(transferResult)}");
            }

            var txid = transferResult.Data?.TransactionHash;

            withdrawalEntity.Status           = WithdrawalStatus.BlockchainProcessing;
            withdrawalEntity.Txid             = txid;
            withdrawalEntity.ExternalSystemId = transferResult.Data?.Id;
        }
        public async Task <int> InsertAsync(WithdrawalEntity entity)
        {
            var result = await Withdrawals.Upsert(entity).On(e => e.TransactionId).NoUpdate().RunAsync();

            return(result);
        }
Exemple #19
0
        public async Task <CryptoWithdrawalResponse> CryptoWithdrawalAsync(CryptoWithdrawalRequest request)
        {
            _logger.LogDebug("Receive CryptoWithdrawalRequest: {jsonText}", JsonConvert.SerializeObject(request));
            request.WalletId.AddToActivityAsTag("walletId");
            request.ClientId.AddToActivityAsTag("clientId");
            request.BrokerId.AddToActivityAsTag("brokerId");
            request.ToAddress.AddToActivityAsTag("blockchain-address");

            try
            {
                var    feeAmount         = 0m;
                string feeAsset          = null;
                string comment           = null;
                string integration       = null;
                string destinationWallet = null;
                string destinationClient = null;

                var requestId     = request.RequestId ?? Guid.NewGuid().ToString("N");
                var transactionId = OperationIdGenerator.GenerateOperationId(requestId, request.WalletId);

                var addressInfoResponse = await _walletService.GetUserByAddressAsync(new Blockchain.Wallets.Grpc.Models.UserWallets.GetUserByAddressRequest
                {
                    Addresses = new Blockchain.Wallets.Grpc.Models.UserWallets.GetUserByAddressRequest.AddressAndTag[]
                    {
                        new Blockchain.Wallets.Grpc.Models.UserWallets.GetUserByAddressRequest.AddressAndTag
                        {
                            Address = request.ToAddress,
                            Tag     = string.IsNullOrEmpty(request.ToTag) ? null : request.ToTag,
                        }
                    },
                });

                var assetIdentity = new AssetIdentity
                {
                    BrokerId = request.BrokerId,
                    Symbol   = request.AssetSymbol
                };

                var paymentSettings = _assetPaymentSettingsClient.GetAssetById(assetIdentity);

                var isInternal = addressInfoResponse.Users != null && addressInfoResponse.Users.Any();
                if (isInternal)
                {
                    var existingAddress  = addressInfoResponse.Users.First();
                    var clientIdResponse = await _clientWalletService.SearchClientsAsync(new SearchWalletsRequest()
                    {
                        SearchText = existingAddress.WalletId,
                        Take       = 1
                    });

                    destinationClient = clientIdResponse.Clients.FirstOrDefault()?.ClientId;

                    destinationWallet = existingAddress.WalletId;
                    comment           = $"Internal withdrawal [{request.AssetSymbol}:{request.Amount}:{request.WalletId}]";
                    integration       = "Internal";

                    if (destinationClient == request.ClientId)
                    {
                        return(new CryptoWithdrawalResponse()
                        {
                            Error = new BitgoErrorType()
                            {
                                Code = BitgoErrorType.ErrorCode.AddressIsNotValid,
                                Message = "Cannot send fund to yourself"
                            }
                        });
                    }
                }
                else
                {
                    if (paymentSettings?.Fireblocks?.IsEnabledWithdrawal == true)
                    {
                        comment     = $"Fireblocks withdrawal [{request.AssetSymbol}:{request.Amount}:{request.WalletId}]";
                        integration = "Fireblocks";
                    }
                    else if (paymentSettings?.Circle?.IsEnabledBlockchainWithdrawal == true)
                    {
                        var isGuidValid = Guid.TryParse(request.RequestId, out _);
                        if (!isGuidValid)
                        {
                            return(new CryptoWithdrawalResponse()
                            {
                                Error = new BitgoErrorType()
                                {
                                    Message =
                                        $"RequestId should be Guid {request.RequestId}",
                                    Code = BitgoErrorType.ErrorCode.InvalidRequestId
                                }
                            });
                        }

                        var circleAsset = _circleAssetMapper.AssetToCircleTokenAsset(request.BrokerId, request.AssetSymbol);

                        if (circleAsset == null)
                        {
                            _logger.LogInformation(
                                $"[CryptoWithdrawalRequest] Cannot found circle coin association for asset {request.AssetSymbol}, broker {request.BrokerId}");

                            return(new CryptoWithdrawalResponse()
                            {
                                Error = new BitgoErrorType()
                                {
                                    Message =
                                        $"Cannot found circle coin association for asset {request.AssetSymbol}, broker {request.BrokerId}",
                                    Code = BitgoErrorType.ErrorCode.AssetIsNotFoundInCircle
                                }
                            });
                        }

                        var circleBlockchain =
                            _circleBlockchainMapper.BlockchainToCircleBlockchain(request.BrokerId, request.Blockchain);

                        if (circleBlockchain == null)
                        {
                            _logger.LogInformation(
                                $"[CryptoWithdrawalRequest] Cannot found circle blockchain association for blockchain {request.Blockchain}, broker {request.BrokerId}");

                            return(new CryptoWithdrawalResponse()
                            {
                                Error = new BitgoErrorType()
                                {
                                    Message =
                                        $"Cannot found circle blockchain association for blockchain {request.Blockchain}, broker {request.BrokerId}",
                                    Code = BitgoErrorType.ErrorCode.BlockchainIsNotFoundInCircle
                                }
                            });
                        }

                        comment       = $"Circle withdrawal [{request.AssetSymbol}:{request.Amount}:{request.WalletId}]";
                        integration   = "Circle";
                        transactionId = requestId;
                    }

                    var fees = _assetFeesClient.GetAssetFees(request.BrokerId, request.AssetSymbol,
                                                             OperationType.Withdrawal);

                    if (fees != null)
                    {
                        feeAsset = fees.AssetId;
                        if (feeAsset == request.AssetSymbol)
                        {
                            feeAmount = fees.FeeSizeType == FeeSizeType.Absolute
                                ? (decimal)fees.FeeSize
                                : (decimal)fees.FeeSize / 100 * request.Amount;
                        }
                        else
                        {
                            feeAmount = (decimal)fees.FeeSize;
                        }
                    }
                }


                await using var ctx = DatabaseContext.Create(_dbContextOptionsBuilder);
                var withdrawalEntity = new WithdrawalEntity()
                {
                    BrokerId            = request.BrokerId,
                    ClientId            = request.ClientId,
                    WalletId            = request.WalletId,
                    TransactionId       = transactionId,
                    Amount              = request.Amount,
                    AssetSymbol         = request.AssetSymbol,
                    Comment             = comment,
                    Integration         = integration,
                    Status              = WithdrawalStatus.New,
                    EventDate           = DateTime.UtcNow,
                    ToAddress           = request.ToAddress,
                    ToTag               = request.ToTag,
                    ClientIp            = request.ClientIp,
                    ClientLang          = request.ClientLang,
                    FeeAmount           = feeAmount,
                    FeeAssetSymbol      = feeAsset,
                    IsInternal          = isInternal,
                    DestinationWalletId = destinationWallet,
                    DestinationClientId = destinationClient,
                    Blockchain          = request.Blockchain,
                    Signature           = request.Signature,
                    SignatureIssuedAt   = request.SignatureIssuedAtUnixTime,
                };
                try
                {
                    await ctx.AddAsync(withdrawalEntity);

                    await ctx.SaveChangesAsync();
                }
                catch (Exception)
                {
                    var existingWithdrawal =
                        await ctx.Withdrawals.Where(t => t.TransactionId == transactionId).FirstAsync();

                    return(new CryptoWithdrawalResponse()
                    {
                        OperationId = existingWithdrawal.Id.ToString()
                    });
                }

                return(new CryptoWithdrawalResponse
                {
                    OperationId = withdrawalEntity.Id.ToString()
                });
            }
            catch (Exception ex)
            {
                ex.FailActivity();
                _logger.LogError(ex, "Cannot handle CryptoWithdrawalRequest {jsonText}",
                                 JsonConvert.SerializeObject(request));
                return(new CryptoWithdrawalResponse()
                {
                    Error = new BitgoErrorType()
                    {
                        Code = BitgoErrorType.ErrorCode.InternalError,
                        Message = ex.Message
                    }
                });
            }
        }