Beispiel #1
0
        public async Task <CommandHandlingResult> Handle(StartCashoutCommand command, IEventPublisher eventPublisher)
        {
            var asset = await _assetsService.AssetGetAsync(command.AssetId);

            var amount = EthServiceHelpers.ConvertToContract(command.Amount, asset.MultiplierPower, asset.Accuracy);

            try
            {
                if (asset.Type == AssetType.Erc20Token)
                {
                    var token = await _assetsService.Erc20TokenGetBySpecificationAsync(new Erc20TokenSpecification(new List <string>()
                    {
                        asset.Id
                    }));

                    var tokenAddress = token?.Items?.FirstOrDefault()?.Address;

                    if (string.IsNullOrEmpty(tokenAddress))
                    {
                        _logger.WriteWarning(nameof(CashoutCommandHandler), nameof(Handle), $"Can't perform cashout on empty token, {command.Id}");
                        return(CommandHandlingResult.Ok());
                    }

                    await _hotWalletService.EnqueueCashoutAsync(new Service.EthereumCore.Core.Repositories.HotWalletOperation()
                    {
                        Amount       = amount,
                        OperationId  = command.Id.ToString(),
                        FromAddress  = command.FromAddress,
                        ToAddress    = command.ToAddress,
                        TokenAddress = tokenAddress
                    });
                }
                else
                {
                    await _pendingOperationService.CashOut(command.Id, asset.AssetAddress,
                                                           _addressUtil.ConvertToChecksumAddress(command.FromAddress), _addressUtil.ConvertToChecksumAddress(command.ToAddress), amount, string.Empty);
                }
            }
            catch (ClientSideException ex) when(ex.ExceptionType == ExceptionType.EntityAlreadyExists || ex.ExceptionType == ExceptionType.OperationWithIdAlreadyExists)
            {
                _logger.WriteWarning(nameof(CashoutCommandHandler), nameof(Handle), $"Operation already exists, {command.Id}", ex);
            }

            eventPublisher.PublishEvent(new CashoutCompletedEvent {
                OperationId = command.Id
            });

            return(CommandHandlingResult.Ok());
        }
Beispiel #2
0
        public async Task <CommandHandlingResult> Handle(StartCashoutCommand command, IEventPublisher publisher)
        {
            var asset = await _assetsService.TryGetAssetAsync(command.AssetId);

            if (asset == null)
            {
                throw new InvalidOperationException("Asset not found");
            }

            if (string.IsNullOrWhiteSpace(asset.BlockchainIntegrationLayerId))
            {
                throw new InvalidOperationException("BlockchainIntegrationLayerId of the asset is not configured");
            }

            if (string.IsNullOrWhiteSpace(asset.BlockchainIntegrationLayerAssetId))
            {
                throw new InvalidOperationException("BlockchainIntegrationLayerAssetId of the asset is not configured");
            }

            var blockchainConfiguration = _blockchainConfigurationProvider.GetConfiguration(asset.BlockchainIntegrationLayerId);

            if (blockchainConfiguration.AreCashoutsDisabled)
            {
                _log.Warning(
                    $"Cashouts for {asset.BlockchainIntegrationLayerId} are disabled",
                    context: command);

                await SendToOpsGenieAsync($"Cashouts for {asset.BlockchainIntegrationLayerId} are disabled",
                                          $"Cashouts for {asset.BlockchainIntegrationLayerId} are disabled, operation Id = {command.OperationId}",
                                          command.ToJson());

                return(CommandHandlingResult.Fail(TimeSpan.FromMinutes(10)));
            }

            publisher.PublishEvent(new ValidationStartedEvent
            {
                OperationId       = command.OperationId,
                ClientId          = command.ClientId,
                AssetId           = asset.Id,
                BlockchainType    = asset.BlockchainIntegrationLayerId,
                BlockchainAssetId = asset.BlockchainIntegrationLayerAssetId,
                HotWalletAddress  = blockchainConfiguration.HotWalletAddress,
                ToAddress         = command.ToAddress,
                Amount            = command.Amount
            });

            return(CommandHandlingResult.Ok());
        }
        public async Task <CommandHandlingResult> Handle(StartCashoutCommand command,
                                                         IEventPublisher eventPublisher)
        {
            var address = command.Address.Trim('\n', ' ', '\t');

            try
            {
                var transactionId = await _builder.AddTransactionId(command.Id, $"Cashout: {command.ToJson()}");

                if (OpenAssetsHelper.IsBitcoin(command.AssetId))
                {
                    await _cashoutRequestRepository.CreateCashoutRequest(transactionId, command.Amount, address);
                }
                else
                {
                    var assetSetting = await _assetSettingCache.GetItemAsync(command.AssetId);

                    var hotWallet = !string.IsNullOrEmpty(assetSetting.ChangeWallet)
                        ? assetSetting.ChangeWallet
                        : assetSetting.HotWallet;

                    await _transactionQueueWriter.AddCommand(transactionId, TransactionCommandType.Transfer, new TransferCommand
                    {
                        Amount             = command.Amount,
                        SourceAddress      = hotWallet,
                        Asset              = command.AssetId,
                        DestinationAddress = command.Address
                    }.ToJson());
                }
            }
            catch (BackendException ex) when(ex.Code == ErrorCode.DuplicateTransactionId)
            {
                _logger.WriteWarning(nameof(CashinCommandHandler), nameof(Handle), $"Duplicated id: {command.Id}");
            }

            return(CommandHandlingResult.Ok());
        }
Beispiel #4
0
        public async Task <CommandHandlingResult> Handle(StartCashoutCommand command, IEventPublisher eventPublisher)
        {
            var operationId = command.OperationId.ToString();

            _log.Info("Got cashout command", context: new { operationId, command = command.ToJson() });

            var  clientId  = command.ClientId.ToString();
            long?accountId = null;

            string walletId;

            if (command.WalletId.HasValue)
            {
                walletId = command.WalletId.Value == Guid.Empty
                    ? clientId
                    : command.WalletId.Value.ToString();
            }
            else
            {
                walletId = clientId;
            }

            var accountSearchResponse = await _siriusApiClient.Accounts.SearchAsync(new AccountSearchRequest
            {
                BrokerAccountId = _brokerAccountId,
                UserNativeId    = clientId,
                ReferenceId     = walletId
            });

            if (accountSearchResponse.ResultCase == AccountSearchResponse.ResultOneofCase.Error)
            {
                var message = "Error fetching Sirius Account";
                _log.Warning(nameof(CashoutCommandHandler),
                             message,
                             context: new
                {
                    error = accountSearchResponse.Error,
                    walletId,
                    clientId,
                    operationId
                });
                throw new Exception(message);
            }

            if (!accountSearchResponse.Body.Items.Any())
            {
                var accountRequestId = $"{_brokerAccountId}_{walletId}_account";
                var userRequestId    = $"{clientId}_user";

                var userCreateResponse = await _siriusApiClient.Users.CreateAsync(new CreateUserRequest
                {
                    RequestId = userRequestId,
                    NativeId  = clientId
                });

                if (userCreateResponse.BodyCase == CreateUserResponse.BodyOneofCase.Error)
                {
                    var message = "Error creating User in Sirius";
                    _log.Warning(nameof(CashoutCommandHandler),
                                 message,
                                 context: new
                    {
                        error = userCreateResponse.Error,
                        clientId,
                        requestId = userRequestId,
                        operationId
                    });
                    throw new Exception(message);
                }

                var createResponse = await _siriusApiClient.Accounts.CreateAsync(new AccountCreateRequest
                {
                    RequestId       = accountRequestId,
                    BrokerAccountId = _brokerAccountId,
                    UserId          = userCreateResponse.User.Id,
                    ReferenceId     = walletId
                });

                if (createResponse.ResultCase == AccountCreateResponse.ResultOneofCase.Error)
                {
                    var message = "Error creating user in Sirius";
                    _log.Warning(nameof(CashoutCommandHandler),
                                 message,
                                 context: new
                    {
                        error = createResponse.Error,
                        clientId,
                        requestId = accountRequestId,
                        operationId
                    });
                    throw new Exception(message);
                }

                accountId = createResponse.Body.Account.Id;
            }
            else
            {
                accountId = accountSearchResponse.Body.Items.FirstOrDefault()?.Id;
            }

            var whitelistingRequestId = $"lykke:trading_wallet:{clientId}";

            var whitelistItemCreateResponse = await _siriusApiClient.WhitelistItems.CreateAsync(new WhitelistItemCreateRequest
            {
                Name  = "Trading Wallet Whitelist",
                Scope = new WhitelistItemScope
                {
                    BrokerAccountId = _brokerAccountId,
                    AccountId       = accountId,
                    UserNativeId    = clientId
                },
                Details = new WhitelistItemDetails
                {
                    TransactionType = WhitelistTransactionType.Any,
                    TagType         = new  NullableWhitelistItemTagType
                    {
                        Null = NullValue.NullValue
                    }
                },
                Lifespan = new WhitelistItemLifespan
                {
                    StartsAt = Timestamp.FromDateTime(DateTime.UtcNow)
                },
                RequestId = whitelistingRequestId
            });

            if (whitelistItemCreateResponse.BodyCase == WhitelistItemCreateResponse.BodyOneofCase.Error)
            {
                _log.Warning(nameof(CashoutCommandHandler), "Error creating Whitelist item",
                             context: new
                {
                    error = whitelistItemCreateResponse.Error,
                    clientId,
                    requestId = whitelistingRequestId,
                    operationId
                });

                throw new Exception("Error creating Whitelist item");
            }

            var tag = !string.IsNullOrWhiteSpace(command.Tag) ? command.Tag : string.Empty;

            WithdrawalDestinationTagType?tagType =
                !string.IsNullOrWhiteSpace(command.Tag)
                    ? (long.TryParse(command.Tag, out _)
                        ? WithdrawalDestinationTagType.Number
                        : WithdrawalDestinationTagType.Text)
                    : null;

            var document = new WithdrawalDocument
            {
                BrokerAccountId       = _brokerAccountId,
                WithdrawalReferenceId = command.OperationId.ToString(),
                AssetId            = command.SiriusAssetId,
                Amount             = command.Amount,
                DestinationDetails = new WithdrawalDestinationDetails
                {
                    Address = command.Address,
                    Tag     = tag,
                    TagType = tagType
                },
                AccountReferenceId = walletId
            }.ToJson();

            var signatureBytes = _encryptionService.GenerateSignature(Encoding.UTF8.GetBytes(document), _privateKeyService.GetPrivateKey());
            var signature      = Convert.ToBase64String(signatureBytes);

            var result = await _siriusApiClient.Withdrawals.ExecuteAsync(new WithdrawalExecuteRequest
            {
                RequestId = $"{_brokerAccountId}_{command.OperationId}",
                Document  = document,
                Signature = signature
            });


            if (result.ResultCase == WithdrawalExecuteResponse.ResultOneofCase.Error)
            {
                switch (result.Error.ErrorCode)
                {
                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.IsNotAuthorized:
                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.Unknown:
                    LogError(operationId, result.Error);
                    return(CommandHandlingResult.Fail(TimeSpan.FromSeconds(10)));

                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.InvalidParameters:
                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.NotFound:
                    LogError(operationId, result.Error);
                    return(CommandHandlingResult.Ok());    // abort

                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.NotEnoughBalance:
                    LogWarning(operationId, result.Error);
                    return(CommandHandlingResult.Fail(TimeSpan.FromSeconds(_notEnoughBalanceRetryDelayInSeconds)));

                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.InvalidAddress:
                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.AmountIsTooLow:
                    LogWarning(operationId, result.Error);
                    return(CommandHandlingResult.Ok());    // abort
                }
            }

            _log.Info("Cashout sent to Sirius", context: new { operationId, withdrawal = result.Body.Withdrawal.ToJson() });

            return(CommandHandlingResult.Ok());
        }