Example #1
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");
            }
        }
Example #2
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;
        }
Example #3
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
                    }
                });
            }
        }