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