public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime appLifetime) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseLykkeMiddleware(ex => BlockchainErrorResponse.FromUnknownError(ex.Message)); app.UseMvc(); app.UseSwagger(c => { c.PreSerializeFilters.Add((swagger, httpReq) => swagger.Host = httpReq.Host.Value); }); app.UseSwaggerUI(x => { x.RoutePrefix = "swagger/ui"; x.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); }); app.UseStaticFiles(); appLifetime.ApplicationStarted.Register(() => StartApplication().GetAwaiter().GetResult()); appLifetime.ApplicationStopping.Register(() => StopApplication().GetAwaiter().GetResult()); appLifetime.ApplicationStopped.Register(CleanUp); }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime appLifetime) { try { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseLykkeMiddleware("Dynamic.Job", ex => BlockchainErrorResponse.FromUnknownError(ex.ToString()), logClientErrors: true); app.UseMvc(); app.UseSwagger(c => { c.PreSerializeFilters.Add((swagger, httpReq) => swagger.Host = httpReq.Host.Value); }); app.UseSwaggerUI(x => { x.RoutePrefix = "swagger/ui"; x.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); }); app.UseStaticFiles(); appLifetime.ApplicationStarted.Register(() => StartApplication().GetAwaiter().GetResult()); appLifetime.ApplicationStopping.Register(() => StopApplication().GetAwaiter().GetResult()); appLifetime.ApplicationStopped.Register(() => CleanUp().GetAwaiter().GetResult()); } catch (Exception ex) { Log?.WriteFatalErrorAsync(nameof(Startup), nameof(Configure), "", ex).GetAwaiter().GetResult(); throw; } }
public async Task <IActionResult> BroadcastTransaction([FromBody] BroadcastTransactionRequest request) { if (request == null) { throw new ValidationApiException("Unable deserialize request"); } if (_settings.OperationsToForceRebuild?.Contains(request.OperationId) == true) { return(BadRequest(BlockchainErrorResponse.FromKnownError(BlockchainErrorCode.BuildingShouldBeRepeated))); } try { await _broadcastService.BroadCastTransactionAsync(request.OperationId, request.SignedTransaction); } catch (BusinessException e) when(e.Code == ErrorCode.TransactionAlreadyBroadcasted) { return(Conflict()); } catch (BusinessException e) when(e.Code == ErrorCode.OperationNotFound) { return(NoContent()); } return(Ok()); }
public void Configure(IApplicationBuilder app) { app.UseLykkeConfiguration(options => { options.DefaultErrorHandler = ex => BlockchainErrorResponse.Create(ex.ToString()); options.SwaggerOptions = _swaggerOptions; }); }
public async Task <ActionResult> Broadcast( [FromBody] BroadcastTransactionRequest request) { var broadcastResult = await _transactionService.BroadcastTransactionAsync ( transactionId : request.OperationId, signedTxData : request.SignedTransaction ); if (broadcastResult is BroadcastTransactionResult.TransactionHash) { return(Ok()); } else if (broadcastResult is BroadcastTransactionResult.Error error) { switch (error.Type) { case BroadcastTransactionError.AmountIsTooSmall: return(BadRequest( BlockchainErrorResponse.FromKnownError( BlockchainErrorCode.AmountIsTooSmall))); case BroadcastTransactionError.BalanceIsNotEnough: return(BadRequest( BlockchainErrorResponse.FromKnownError( BlockchainErrorCode.NotEnoughBalance))); case BroadcastTransactionError.TransactionHasBeenBroadcasted: return(Conflict( BlockchainErrorResponse.FromUnknownError ($"Transaction with specified id [{request.OperationId}] has already been broadcasted."))); case BroadcastTransactionError.TransactionHasBeenDeleted: return(Conflict( BlockchainErrorResponse.FromUnknownError ($"Transaction with specified id [{request.OperationId}] has already been deleted."))); case BroadcastTransactionError.TransactionShouldBeRebuilt: return(BadRequest( BlockchainErrorResponse.FromKnownError( BlockchainErrorCode.BuildingShouldBeRepeated))); case BroadcastTransactionError.OperationHasNotBeenFound: return(NoContent()); default: throw new ArgumentOutOfRangeException(); } } else { throw new NotSupportedException( $"{nameof(_transactionService.BroadcastTransactionAsync)} returned unsupported result."); } }
private static BlockchainErrorResponse GetErrorResponse(ApiException ex) { BlockchainErrorResponse errorResponse; try { errorResponse = ex.GetContentAs <BlockchainErrorResponse>(); } catch (Exception) { errorResponse = null; } return(errorResponse ?? BlockchainErrorResponse.FromUnknownError("Blockchain API is not specify the error response")); }
public async Task <IActionResult> Broadcast([FromBody] BroadcastTransactionRequest request) { try { await _transactionBroadcaster.BroadcastAsync(request); } catch (TransactionBroadcastingException ex) { var errorResponse = BlockchainErrorResponse.CreateFromCode(ex.Error, ex.Message); return(BadRequest(errorResponse)); } return(Ok()); }
public async Task <ActionResult <RawObjectResponse> > GetRaw([FromRoute] TransactionId transactionId) { if (transactionId == null) { throw RequestValidationException.ShouldBeNotNull(nameof(transactionId)); } var raw = await _rawObjectsRepository.GetOrDefaultAsync(RawObjectType.Transaction, transactionId); if (raw == null) { return(NotFound(BlockchainErrorResponse.Create($"Raw transaction [{transactionId}] not found"))); } return(Ok(new RawObjectResponse(raw))); }
public async Task <ActionResult> Transfer(TestingTransferRequest request) { var caps = _api.GetCapabilities(); if (!caps.IsTestingTransfersSupported.HasValue || !caps.IsTestingTransfersSupported.Value) { return(StatusCode(501)); } var asset = await _assets.GetAsync(request.AssetId); if (asset == null) { return(BadRequest(BlockchainErrorResponse.Create("Unknown asset"))); } var amount = 0M; try { amount = Conversions.CoinsFromContract(request.Amount, asset.Accuracy); } catch (ConversionException) { return(BadRequest(BlockchainErrorResponse.Create("Invalid amount format"))); } if (amount < 0 || !_api.AddressIsValid(request.FromAddress) || !_api.AddressIsValid(request.ToAddress)) { return(BadRequest(BlockchainErrorResponse.Create("Invalid address(es) and/or negative amount"))); } var result = await _api.TestingTransfer( request.FromAddress, request.FromPrivateKey, request.ToAddress, asset, amount); return(Ok(result)); }
public async Task <ActionResult <BuildTransactionResponse> > BuildTransferCoins([FromBody] BuildTransferCoinsTransactionRequest request) { try { var response = await _transferCoinsTransactionsBuilder.BuildTransferCoinsAsync(request); if (response == null) { throw new InvalidOperationException("Not null response object expected"); } return(Ok(response)); } catch (TransactionBuildingException ex) { var errorResponse = BlockchainErrorResponse.CreateFromCode(ex.Error, ex.Message); return(BadRequest(errorResponse)); } }
public async Task <ActionResult <SignedTransactionResponse> > SignTransaction(SignTransactionRequest request, [FromServices] IBlockchainSignService signService) { if (request.PrivateKeys.Count == 0 || request.PrivateKeys.Any(k => !signService.ValidatePrivateKey(k))) { return(BadRequest(BlockchainErrorResponse.Create("Invalid private key(s)"))); } var tx = request.TransactionContext.Base64ToString(); if (tx == Constants.DUMMY_TX && request.PrivateKeys.Any(k => !signService.ValidateHotWalletPrivateKey(k))) { return(BadRequest(BlockchainErrorResponse.Create("Invalid private key(s)"))); } var result = ( hash : Constants.DUMMY_HASH, signedTransaction : Constants.DUMMY_TX ); if (tx != Constants.DUMMY_TX) { try { result = await signService.SignTransactionAsync(tx, request.PrivateKeys); } catch (ArgumentException ex) { return(BadRequest(BlockchainErrorResponse.Create(ex.ToAsyncString()))); } } return(new SignedTransactionResponse() { SignedTransaction = JsonConvert.SerializeObject(result).ToBase64() }); }
public async Task <ActionResult> Observe(string address) { if (!Validators.ValidateAzureKey(address) || !_api.AddressIsValid(address)) { return(BadRequest(BlockchainErrorResponse.Create("'address' must be valid blockchain address"))); } if (await _depositWallets.TryObserveAsync(address)) { _chaosKitty?.Meow($"{nameof(Observe)}_Data"); await _api.ObserveAddressAsync(address); _chaosKitty?.Meow($"{nameof(Observe)}_Blockchain"); return(Ok()); } else { return(Conflict($"Address {address} is already observed")); } }
public async Task <ActionResult> DeleteObservation(string address) { if (!Validators.ValidateAzureKey(address) || !_api.AddressIsValid(address)) { return(BadRequest(BlockchainErrorResponse.Create("'address' must be valid blockchain address"))); } if (await _depositWallets.TryDeleteObservationAsync(address)) { _chaosKitty?.Meow($"{nameof(DeleteObservation)}_Data"); await _api.DeleteAddressObservationAsync(address); _chaosKitty?.Meow($"{nameof(DeleteObservation)}_Blockchain"); return(Ok()); } else { return(NoContent()); } }
public ErrorResponseException(BlockchainErrorResponse error, ApiException inner) : base(error.GetSummaryMessage(), inner) { Error = error; StatusCode = inner.StatusCode; }
public async Task <IActionResult> Build([Required, FromBody] BuildSingleTransactionRequest request) { if (!ModelState.IsValid) { return(BadRequest(ModelState.ToErrorResponse())); } var fromAddress = _dynamicService.GetBitcoinAddress(request.FromAddress); if (fromAddress == null) { return(BadRequest(ErrorResponse.Create($"{nameof(request.FromAddress)} is not a valid"))); } var toAddress = _dynamicService.GetBitcoinAddress(request.ToAddress); if (toAddress == null) { return(BadRequest(ErrorResponse.Create($"{nameof(request.ToAddress)} is not a valid"))); } if (request.AssetId != Asset.Dynamic.Id) { return(BadRequest(ErrorResponse.Create($"{nameof(request.AssetId)} was not found"))); } var broadcast = await _dynamicService.GetBroadcastAsync(request.OperationId); if (broadcast != null) { return(new StatusCodeResult(StatusCodes.Status409Conflict)); } var build = await _buildRepository.GetAsync(request.OperationId); if (build != null) { return(Ok(new BuildTransactionResponse() { TransactionContext = build.TransactionContext })); } var amount = Conversions.CoinsFromContract(request.Amount, Asset.Dynamic.Accuracy); var fromAddressBalance = await _dynamicService.GetAddressBalance(request.FromAddress); var fee = _dynamicService.GetFee(); var requiredBalance = request.IncludeFee ? amount : amount + fee; if (amount < fee) { return(BadRequest(BlockchainErrorResponse.FromKnownError(BlockchainErrorCode.AmountIsTooSmall))); } if (requiredBalance > fromAddressBalance) { return(BadRequest(BlockchainErrorResponse.FromKnownError(BlockchainErrorCode.NotEnoughBalance))); } await _log.WriteInfoAsync(nameof(TransactionsController), nameof(Build), request.ToJson(), "Build transaction"); var transactionContext = await _dynamicService.BuildTransactionAsync(request.OperationId, fromAddress, toAddress, amount, request.IncludeFee); await _buildRepository.AddAsync(request.OperationId, transactionContext); return(Ok(new BuildTransactionResponse() { TransactionContext = transactionContext })); }
public async Task <ActionResult <PaginationResponse <WalletBalanceContract> > > Get(int take, string continuation) { if (take <= 0) { return(BadRequest(BlockchainErrorResponse.Create("'take' must be grater than zero"))); } if (!Validators.ValidateAzureContinuation(continuation)) { return(BadRequest(BlockchainErrorResponse.Create("'continuation' must be null or valid Azure continuation token"))); } IEnumerable <DepositWalletBalanceEntity> balances; if (_api.CanGetBalances) { // to be able to enroll fake transfers properly // we keep block number increased by one order of magnitude var lastConfirmedBlock = await _api.GetLastConfirmedBlockNumberAsync() * 10; var depositWallets = await _depositWallets.GetWalletsAsync(take, continuation); var addresses = depositWallets.items.Select(w => w.Address).ToArray(); continuation = depositWallets.continuation; balances = (await _api.GetBalancesAsync(addresses, async assetId => await _assets.GetAsync(assetId))) .Select(b => new DepositWalletBalanceEntity(b.Address, b.AssetId) { Amount = b.Amount, BlockNumber = lastConfirmedBlock }); } else { (balances, continuation) = await _depositWallets.GetBalanceAsync(take, continuation); } var result = new List <WalletBalanceContract>(); foreach (var balance in balances) { // blockchain may return unknown assets, // filter out such items var accuracy = (await _assets.GetAsync(balance.AssetId))?.Accuracy; if (accuracy == null) { continue; } result.Add(new WalletBalanceContract { Address = balance.Address, AssetId = balance.AssetId, Balance = Conversions.CoinsToContract(balance.Amount, accuracy.Value), Block = balance.BlockNumber }); } return(PaginationResponse.From(continuation, result)); }
public async Task <ActionResult <BuildTransactionResponse> > Build( [FromBody] BuildSingleTransactionRequest request) { var amount = BigInteger.Parse(request.Amount); var from = request.FromAddress.ToLowerInvariant(); var to = request.ToAddress.ToLowerInvariant(); var buildResult = await _transactionService.BuildTransactionAsync ( transactionId : request.OperationId, from : from, to : to, amount : amount, includeFee : request.IncludeFee ); if (buildResult is BuildTransactionResult.TransactionContext txContext) { return(new BuildTransactionResponse { TransactionContext = txContext.String }); } else if (buildResult is BuildTransactionResult.Error error) { switch (error.Type) { case BuildTransactionError.AmountIsTooSmall: return(BadRequest( BlockchainErrorResponse.FromKnownError( BlockchainErrorCode.AmountIsTooSmall))); case BuildTransactionError.BalanceIsNotEnough: return(BadRequest( BlockchainErrorResponse.FromKnownError( BlockchainErrorCode.NotEnoughBalance))); case BuildTransactionError.GasAmountIsTooHigh: return(BadRequest( BlockchainErrorResponse.FromKnownError( BlockchainErrorCode.AmountIsTooSmall))); // TODO: Replace with correct response, as soon, as it will be implemented in common part //return BadRequest( // BlockchainErrorResponse.FromUnknownError( // "Required gas amount is too high.")); case BuildTransactionError.TargetAddressBlacklistedOrInvalid: return(BadRequest( BlockchainErrorResponse.FromKnownError( BlockchainErrorCode.AmountIsTooSmall))); // TODO: Replace with correct response, as soon, as it will be implemented in common part //return BadRequest( // BlockchainErrorResponse.FromUnknownError( // "Target address is either blacklisted, or invalid.")); case BuildTransactionError.TransactionHasBeenBroadcasted: return(Conflict( BlockchainErrorResponse.FromUnknownError ($"Transaction with specified id [{request.OperationId}] has already been broadcasted."))); case BuildTransactionError.TransactionHasBeenDeleted: return(Conflict( BlockchainErrorResponse.FromUnknownError ($"Transaction for specified operation [{request.OperationId}] has already been deleted."))); default: throw new ArgumentOutOfRangeException(); } } else { throw new NotSupportedException( $"{nameof(_transactionService.BuildTransactionAsync)} returned unsupported result."); } }
public async Task <IActionResult> BuildSingle([FromBody] BuildSingleTransactionRequest request) { if (request == null) { throw new ValidationApiException("Unable deserialize request"); } var amountSatoshi = MoneyConversionHelper.SatoshiFromContract(request.Amount); if (amountSatoshi <= 0) { throw new ValidationApiException($"Amount can't be less or equal to zero: {amountSatoshi}"); } if (request.AssetId != Constants.Assets.Bitcoin.AssetId) { throw new ValidationApiException("Invalid assetId"); } if (request.OperationId == Guid.Empty) { throw new ValidationApiException("Invalid operation id (GUID)"); } if (await _operationEventRepository.ExistAsync(request.OperationId, OperationEventType.Broadcasted)) { return(Conflict()); } BuiltTransactionInfo tx; try { tx = await _operationService.GetOrBuildTransferTransactionAsync(request.OperationId, new List <OperationInput> { new OperationInput { Address = request.FromAddress, AddressContext = request.FromAddressContext?.DeserializeJson <AddressContextContract>()?.PubKey, Amount = amountSatoshi } }, new List <OperationOutput> { new OperationOutput { Address = request.ToAddress, Amount = amountSatoshi } }, OperationType.Single, request.AssetId, request.IncludeFee); } catch (NotEnoughFundsException) { return(BadRequest(BlockchainErrorResponse.FromKnownError(BlockchainErrorCode.NotEnoughBalance))); } catch (BusinessException e) when(e.Code == ErrorCode.NotEnoughFundsAvailable) { return(BadRequest(BlockchainErrorResponse.FromKnownError(BlockchainErrorCode.NotEnoughBalance))); } return(Ok(new BuildTransactionResponse { TransactionContext = tx.ToJson(_network) })); }
/// <summary> /// Configures a blockchain integration service application. /// </summary> public static IApplicationBuilder UseBlockchainIntegrationConfiguration( this IApplicationBuilder app, Action <BlockchainIntegrationApplicationOptions> configureOptions) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (configureOptions == null) { throw new ArgumentNullException(nameof(configureOptions)); } var options = new BlockchainIntegrationApplicationOptions(); configureOptions.Invoke(options); if (string.IsNullOrWhiteSpace(options.ServiceName)) { throw new InvalidOperationException($"{nameof(options)}.{nameof(options.ServiceName)} is required."); } app.UseLykkeConfiguration(lykkeSdkOptions => { lykkeSdkOptions.DisableValidationExceptionMiddleware(); lykkeSdkOptions.DefaultErrorHandler = ex => { switch (ex) { case OperationNotSupportedException _: return(BlockchainErrorResponse.CreateBrief(ex)); case RequestValidationException _: return(BlockchainErrorResponse.CreateBrief(ex)); default: return(BlockchainErrorResponse.Create(ex)); } }; lykkeSdkOptions.UnhandledExceptionHttpStatusCodeResolver = ex => { switch (ex) { case OperationNotSupportedException _: return(HttpStatusCode.NotImplemented); case RequestValidationException _: return(HttpStatusCode.BadRequest); default: return(HttpStatusCode.InternalServerError); } }; lykkeSdkOptions.SwaggerOptions = new LykkeSwaggerOptions { ApiTitle = $"{options.ServiceName} API", ApiVersion = "v1" }; }); return(app); }
public async Task <ActionResult <BuildTransactionResponse> > Build( [FromBody] BuildSingleTransactionRequest request) { var amount = BigInteger.Parse(request.Amount); var from = request.FromAddress.ToLowerInvariant(); var to = request.ToAddress.ToLowerInvariant(); var buildResult = await _transactionService.BuildTransactionAsync ( transactionId : request.OperationId, from : from, fromContext : request.FromAddressContext, to : to, transferAmount : amount, includeFee : request.IncludeFee ); if (buildResult is BuildTransactionResult.TransactionContext txContext) { return(new BuildTransactionResponse { TransactionContext = txContext.String }); } else if (buildResult is BuildTransactionResult.Error error) { switch (error.Type) { case BuildTransactionError.AmountIsTooSmall: return(BadRequest( BlockchainErrorResponse.FromKnownError( BlockchainErrorCode.AmountIsTooSmall))); case BuildTransactionError.BalanceIsNotEnough: return(BadRequest( BlockchainErrorResponse.FromKnownError( BlockchainErrorCode.NotEnoughBalance))); case BuildTransactionError.GasAmountIsInvalid: return(BadRequest( BlockchainErrorResponse.FromUnknownError ($"Gas amount for transaction [{request.OperationId}] can not be estimated."))); case BuildTransactionError.TargetAddressIsInvalid: // AmountIsTooSmall is the one only error code, that stops transaction execution // return BadRequest( // BlockchainErrorResponse.FromUnknownError // ($"Target address [{request.ToAddress}] either is invalid, or blacklisted.")); return(BadRequest( BlockchainErrorResponse.FromKnownError( BlockchainErrorCode.AmountIsTooSmall))); case BuildTransactionError.TransactionHasBeenBroadcasted: return(Conflict( BlockchainErrorResponse.FromUnknownError ($"Transaction with specified id [{request.OperationId}] has already been broadcasted."))); case BuildTransactionError.TransactionHasBeenDeleted: return(Conflict( BlockchainErrorResponse.FromUnknownError ($"Transaction for specified operation [{request.OperationId}] has already been deleted."))); default: throw new ArgumentOutOfRangeException(); } } else { throw new NotSupportedException( $"{nameof(_transactionService.BuildTransactionAsync)} returned unsupported result."); } }
public async Task <ActionResult> BuildSingle([FromBody] BuildSingleTransactionRequest request) { if (request == null) { throw new BusinessException("Unable deserialize request", ErrorCode.BadInputParameter); } var amountSatoshi = MoneyConversionHelper.SatoshiFromContract(request.Amount); if (amountSatoshi <= 0) { throw new BusinessException($"Amount can't be less or equal to zero: {amountSatoshi}", ErrorCode.BadInputParameter); } if (request.AssetId != (await _assetRepository.GetDefaultAsset()).AssetId) { throw new BusinessException("Invalid assetId", ErrorCode.BadInputParameter); } var toBitcoinAddress = _addressValidator.GetBitcoinAddress(request.ToAddress, _blockChainProvider.Network); if (toBitcoinAddress == null) { throw new BusinessException("Invalid ToAddress ", ErrorCode.BadInputParameter); } var fromBitcoinAddress = _addressValidator.GetBitcoinAddress(request.FromAddress, _blockChainProvider.Network); if (fromBitcoinAddress == null) { throw new BusinessException("Invalid FromAddress", ErrorCode.BadInputParameter); } if (request.OperationId == Guid.Empty) { throw new BusinessException("Invalid operation id (GUID)", ErrorCode.BadInputParameter); } if (await _operationEventRepository.Exist(request.OperationId, OperationEventType.Broadcasted)) { return(Conflict()); } BuildedTransactionInfo tx; try { tx = await _operationService.GetOrBuildTransferTransaction(request.OperationId, fromBitcoinAddress, toBitcoinAddress, request.AssetId, new Money(amountSatoshi), request.IncludeFee); } catch (NotEnoughFundsException) { return(BadRequest(BlockchainErrorResponse.FromKnownError(BlockchainErrorCode.NotEnoughBalance))); } catch (BusinessException e) when(e.Code == ErrorCode.NotEnoughFundsAvailable) { return(BadRequest(BlockchainErrorResponse.FromKnownError(BlockchainErrorCode.NotEnoughBalance))); } return(Ok(new BuildTransactionResponse { TransactionContext = tx.ToJson(_network) })); }