public async Task RegisterWalletAsync(DepositWalletKey key) { var client = _blockchainApiClientProvider.Get(key.BlockchainId); await _enrolledBalanceRepository.SetBalanceAsync(key, 0, 0); await client.StartBalanceObservationAsync(key.WalletAddress); }
public async Task <string> BuildTransactionAsync( Guid operationId, string blockchainId, string blockchainAssetId, string fromAddress, string fromAddressContext, string toAddress, string amount, bool includeFee ) { var client = _blockchainApiClientProvider.Get(blockchainId); var asset = await _assetService.GetBlockchainAsset(blockchainId, blockchainAssetId); var amountDecimal = ConverterExtensions.ConvertFromString(amount, asset.Accuracy, asset.Accuracy); var buildedTransaction = await client.BuildSingleTransactionAsync( operationId, fromAddress, fromAddressContext, toAddress, asset, amountDecimal, includeFee ); return(buildedTransaction.TransactionContext); }
public async Task <BlockchainAsset> GetBlockchainAsset(string blockchainId, string blockchainAssetId) { if (!_assets.TryGetValue(blockchainAssetId, out var asset)) { return(null); } //if (_dictionary.TryGetValue(asset.BlockchainId, out var value)) //{ // if (value.TryGetValue(blockchainAssetId, out var blockchainAsset)) // { // return blockchainAsset; // } //} var client = _blockchainApiClientProvider.Get(blockchainId); var assets = await client.GetAllAssetsAsync(100); if (!assets.TryGetValue(asset.BlockchainAssetId, out var blockchainAsset)) { return(null); } return(blockchainAsset); }
public DepositWalletsBalanceProcessingPeriodicalHandler( ILogFactory logFactory, TimeSpan period, int batchSize, string blockchainType, IBlockchainApiClientProvider blockchainApiClientProvider, ICqrsEngine cqrsEngine, IAssetsServiceWithCache assetsService, IEnrolledBalanceRepository enrolledBalanceRepository, IHotWalletsProvider hotWalletsProvider, ICashinRepository cashinRepository, IDepositWalletLockRepository depositWalletLockRepository, IChaosKitty chaosKitty) { _logFactory = logFactory; _batchSize = batchSize; _blockchainType = blockchainType; _blockchainApiClient = blockchainApiClientProvider.Get(blockchainType); _cqrsEngine = cqrsEngine; _assetsService = assetsService; _enrolledBalanceRepository = enrolledBalanceRepository; _hotWalletsProvider = hotWalletsProvider; _cashinRepository = cashinRepository; _depositWalletLockRepository = depositWalletLockRepository; _chaosKitty = chaosKitty; _timer = new TimerTrigger( $"{nameof(DepositWalletsBalanceProcessingPeriodicalHandler)} : {blockchainType}", period, _logFactory); _timer.Triggered += ProcessBalancesAsync; }
/// <exception cref="ArgumentValidationException"></exception> private Task <IBlockchainApiClient> ThrowOnNotSupportedBlockchainType(string blockchainType) { IBlockchainApiClient blockchainClient; try { blockchainClient = _blockchainApiClientProvider.Get(blockchainType); //throws } catch (ArgumentValidationException) { throw new ArgumentValidationException($"{blockchainType} is not a valid type", "blockchainType"); } return(Task.FromResult(blockchainClient)); }
public async Task<CommandHandlingResult> Handle(ClearBroadcastedTransactionCommand command, IEventPublisher publisher) { var apiClient = _apiClientProvider.Get(command.BlockchainType); await apiClient.ForgetBroadcastedTransactionsAsync(command.TransactionId); _chaosKitty.Meow(command.TransactionId); publisher.PublishEvent(new BroadcastedTransactionClearedEvent { OperationId = command.OperationId, TransactionId = command.TransactionId }); return CommandHandlingResult.Ok(); }
public async Task <CommandHandlingResult> Handle(WaitForTransactionEndingCommand command, IEventPublisher publisher) { var apiClient = _apiClientProvider.Get(command.BlockchainType); // TODO: Cache it var blockchainAsset = await apiClient.GetAssetAsync(command.BlockchainAssetId); BaseBroadcastedTransaction transaction; OperationOutput[] transactionOutputs = null; if (command.Outputs.Length > 1) { var manyOutputsTransaction = await apiClient.TryGetBroadcastedTransactionWithManyOutputsAsync ( command.TransactionId, blockchainAsset ); transaction = manyOutputsTransaction; if (manyOutputsTransaction != null) { transactionOutputs = manyOutputsTransaction.Outputs .Select(o => new OperationOutput { Address = o.ToAddress, Amount = o.Amount }) .ToArray(); } } else if (command.Outputs.Length == 1) { var singleTransaction = await apiClient.TryGetBroadcastedSingleTransactionAsync ( command.TransactionId, blockchainAsset ); transaction = singleTransaction; if (singleTransaction != null) { transactionOutputs = new[] { new OperationOutput { Address = command.Outputs.Single().Address, Amount = singleTransaction.Amount } }; } } else { throw new InvalidOperationException("There should be at least one output"); } if (transaction == null) { _log.Info("Blockchain API returned no transaction. Assuming, that it's already was cleared", command); // Transaction already has been forgotten, this means, // that process has been went further and no events should be generated here. return(CommandHandlingResult.Ok()); } if (transactionOutputs == null) { throw new InvalidOperationException("Transaction outputs should be not null here"); } switch (transaction.State) { case BroadcastedTransactionState.InProgress: return(CommandHandlingResult.Fail(_delayProvider.WaitForTransactionRetryDelay)); case BroadcastedTransactionState.Completed: publisher.PublishEvent(new TransactionExecutionCompletedEvent { OperationId = command.OperationId, TransactionId = command.TransactionId, TransactionNumber = command.TransactionNumber, TransactionHash = transaction.Hash, TransactionOutputs = transactionOutputs, TransactionFee = transaction.Fee, TransactionBlock = transaction.Block }); return(CommandHandlingResult.Ok()); case BroadcastedTransactionState.Failed: if (transaction.ErrorCode == BlockchainErrorCode.NotEnoughBalance || transaction.ErrorCode == BlockchainErrorCode.BuildingShouldBeRepeated) { publisher.PublishEvent(new TransactionExecutionRepeatRequestedEvent { OperationId = command.OperationId, TransactionId = command.TransactionId, TransactionNumber = command.TransactionNumber, ErrorCode = transaction.ErrorCode.Value.MapToTransactionExecutionResult(), Error = transaction.Error }); } else { publisher.PublishEvent(new TransactionExecutionFailedEvent { OperationId = command.OperationId, TransactionId = command.TransactionId, TransactionNumber = command.TransactionNumber, ErrorCode = transaction.ErrorCode?.MapToTransactionExecutionResult() ?? TransactionExecutionResult.UnknownError, Error = transaction.Error }); } return(CommandHandlingResult.Ok()); default: throw new ArgumentOutOfRangeException ( nameof(transaction.State), $"Transaction state [{transaction.State}] is not supported." ); } }
/// <summary> /// /// </summary> /// <param name="cashoutModel"></param> /// <returns> /// ValidationError - client error /// ArgumentValidationException - developer error /// </returns> public async Task <IReadOnlyCollection <ValidationError> > ValidateAsync(CashoutModel cashoutModel) { var errors = new List <ValidationError>(1); if (cashoutModel == null) { return(FieldNotValidResult("cashoutModel can't be null")); } if (string.IsNullOrEmpty(cashoutModel.AssetId)) { return(FieldNotValidResult("cashoutModel.AssetId can't be null or empty")); } Asset asset; try { asset = await _assetsService.TryGetAssetAsync(cashoutModel.AssetId); } catch (Exception) { throw new ArgumentValidationException($"Asset with Id-{cashoutModel.AssetId} does not exists", "assetId"); } if (asset.IsDisabled) { errors.Add(ValidationError.Create(ValidationErrorType.None, $"Asset {asset.Id} is disabled")); } if (asset == null) { throw new ArgumentValidationException($"Asset with Id-{cashoutModel.AssetId} does not exists", "assetId"); } var isAddressValid = true; IBlockchainApiClient blockchainClient = null; if (asset.Id != LykkeConstants.SolarAssetId) { if (string.IsNullOrEmpty(asset.BlockchainIntegrationLayerId)) { throw new ArgumentValidationException( $"Given asset Id-{cashoutModel.AssetId} is not a part of Blockchain Integration Layer", "assetId"); } blockchainClient = _blockchainApiClientProvider.Get(asset.BlockchainIntegrationLayerId); } if (string.IsNullOrEmpty(cashoutModel.DestinationAddress) || !cashoutModel.DestinationAddress.IsValidPartitionOrRowKey() || asset.Id != LykkeConstants.SolarAssetId && blockchainClient != null && !await blockchainClient.IsAddressValidAsync(cashoutModel.DestinationAddress) || asset.Id == LykkeConstants.SolarAssetId && !SolarCoinValidation.ValidateAddress(cashoutModel.DestinationAddress) ) { isAddressValid = false; errors.Add(ValidationError.Create(ValidationErrorType.AddressIsNotValid, "Address is not valid")); } if (isAddressValid) { if (asset.Id != LykkeConstants.SolarAssetId) { var isBlocked = await _blackListService.IsBlockedWithoutAddressValidationAsync ( asset.BlockchainIntegrationLayerId, cashoutModel.DestinationAddress ); if (isBlocked) { errors.Add(ValidationError.Create(ValidationErrorType.BlackListedAddress, "Address is in the black list")); } } if (cashoutModel.Volume.HasValue && Math.Abs(cashoutModel.Volume.Value) < (decimal)asset.CashoutMinimalAmount) { var minimalAmount = asset.CashoutMinimalAmount.GetFixedAsString(asset.Accuracy).TrimEnd('0'); errors.Add(ValidationError.Create(ValidationErrorType.LessThanMinCashout, $"Please enter an amount greater than {minimalAmount}")); } if (asset.Id != LykkeConstants.SolarAssetId) { var blockchainSettings = _blockchainSettingsProvider.Get(asset.BlockchainIntegrationLayerId); if (cashoutModel.DestinationAddress == blockchainSettings.HotWalletAddress) { errors.Add(ValidationError.Create(ValidationErrorType.HotwalletTargetProhibited, "Hot wallet as destitnation address prohibited")); } var isPublicExtensionRequired = _blockchainWalletsCacheService.IsPublicExtensionRequired(asset.BlockchainIntegrationLayerId); if (isPublicExtensionRequired) { var hotWalletParseResult = await _blockchainWalletsClient.ParseAddressAsync( asset.BlockchainIntegrationLayerId, blockchainSettings.HotWalletAddress); var destAddressParseResult = await _blockchainWalletsClient.ParseAddressAsync( asset.BlockchainIntegrationLayerId, cashoutModel.DestinationAddress); if (hotWalletParseResult.BaseAddress == destAddressParseResult.BaseAddress) { var existedClientIdAsDestination = await _blockchainWalletsClient.TryGetClientIdAsync( asset.BlockchainIntegrationLayerId, cashoutModel.DestinationAddress); if (existedClientIdAsDestination == null) { errors.Add(ValidationError.Create(ValidationErrorType.DepositAddressNotFound, $"Deposit address {cashoutModel.DestinationAddress} not found")); } } var forbiddenCharacterErrors = await ValidateForForbiddenCharsAsync( destAddressParseResult.BaseAddress, destAddressParseResult.AddressExtension, asset.BlockchainIntegrationLayerId); if (forbiddenCharacterErrors != null) { errors.AddRange(forbiddenCharacterErrors); } if (!string.IsNullOrEmpty(destAddressParseResult.BaseAddress)) { if (!cashoutModel.DestinationAddress.Contains(destAddressParseResult.BaseAddress)) { errors.Add(ValidationError.Create(ValidationErrorType.FieldIsNotValid, "Base Address should be part of destination address")); } // full address is already checked by integration, // we don't need to validate it again, // just ensure that base address is not black-listed var isBlockedBase = await _blackListService.IsBlockedWithoutAddressValidationAsync( asset.BlockchainIntegrationLayerId, destAddressParseResult.BaseAddress); if (isBlockedBase) { errors.Add(ValidationError.Create(ValidationErrorType.BlackListedAddress, "Base Address is in the black list")); } } } } if (cashoutModel.ClientId.HasValue) { var destinationClientId = await _blockchainWalletsClient.TryGetClientIdAsync ( asset.BlockchainIntegrationLayerId, cashoutModel.DestinationAddress ); if (destinationClientId.HasValue && destinationClientId == cashoutModel.ClientId.Value) { var error = ValidationError.Create ( ValidationErrorType.CashoutToSelfAddress, "Withdrawals to the deposit wallet owned by the customer himself prohibited" ); errors.Add(error); } } } return(errors); }