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; } }
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"); } }
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; }
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)); } }
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; } }
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); }
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"); }
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; }
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)); } }
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 }); }
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; } }
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); }
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 } }); } }