예제 #1
0
        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());
        }
예제 #4
0
 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.");
            }
        }
예제 #6
0
        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)));
        }
예제 #9
0
        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()
            });
        }
예제 #12
0
        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"));
            }
        }
예제 #13
0
        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());
            }
        }
예제 #14
0
 public ErrorResponseException(BlockchainErrorResponse error, ApiException inner) :
     base(error.GetSummaryMessage(), inner)
 {
     Error      = error;
     StatusCode = inner.StatusCode;
 }
예제 #15
0
        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
            }));
        }
예제 #16
0
        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)
            }));
        }
예제 #19
0
        /// <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);
        }
예제 #20
0
        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.");
            }
        }
예제 #21
0
        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)
            }));
        }