public override Task <CashInOutOperationResponse> CashInOut(CashInOutOperation request,
                                                                    ServerCallContext context)
        {
            var isWithdrawal = double.Parse(request.Volume) < 0;

            using var activity = MyTelemetry.StartActivity(isWithdrawal ? "Withdrawal" : "Deposit");

            activity?.AddTag("operationId", request.Id)
            .AddTag("brokerId", request.BrokerId)
            .AddTag("accountId", request.AccountId)
            .AddTag("walletId", request.WalletId)
            .AddTag("assetId", request.AssetId)
            .AddTag("volume", request.Volume);

            if (isWithdrawal && request.Fees.Count == 0)
            {
                var fees = _assetFeesClient.GetAssetFees(request.BrokerId, request.AssetId,
                                                         OperationType.Withdrawal);
                if (fees != null)
                {
                    request.Fees.Add(FeesConverter.ConvertAssetFee(fees));
                }
            }

            return(_cashServiceClient.CashInOutAsync(request, cancellationToken: context.CancellationToken)
                   .ResponseAsync);
        }
Пример #2
0
        public async Task <NullableValue <AssetFees> > GetAssetFees(GetAssetFeesRequest request)
        {
            try
            {
                var fees = _client.GetAssetFees(request.BrokerId, request.GroupId, request.AssetId, request.OperationType);

                return(fees == null ? null : new NullableValue <AssetFees>(AssetFees.Create(fees)));
            }
            catch (Exception ex)
            {
                if (ex.Message == "Unknown HTTP result CodeNotFound. Message: Row not found")
                {
                    return(new NullableValue <AssetFees>());
                }
                throw;
            }
        }
Пример #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
                    }
                });
            }
        }