< Summary

Information
Class: EF.Blockchain.Server.Endpoints.TransactionEndpoints
Assembly: EF.Blockchain.Server
File(s): C:\dev\@web3\web3-001-ef-blockchain\backend\EF.Blockchain\src\EF.Blockchain.Server\Endpoints\TransactionEndpoints.cs
Line coverage
51%
Covered lines: 54
Uncovered lines: 51
Coverable lines: 105
Total lines: 154
Line coverage: 51.4%
Branch coverage
25%
Covered branches: 8
Total branches: 32
Branch coverage: 25%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
MapTransactionEndpoints(...)100%11100%
GetTransaction(...)50%4472.72%
GetTransaction(...)50%4472.72%
PostTransaction(...)50%4488.88%
PostTransaction(...)50%4488.88%
PrepareTransaction(...)0%272160%

File(s)

C:\dev\@web3\web3-001-ef-blockchain\backend\EF.Blockchain\src\EF.Blockchain.Server\Endpoints\TransactionEndpoints.cs

#LineLine coverage
 1using EF.Blockchain.Domain;
 2using EF.Blockchain.Server.Dtos;
 3using EF.Blockchain.Server.Mappers;
 4using Microsoft.AspNetCore.Mvc;
 5
 6namespace EF.Blockchain.Server.Endpoints;
 7
 8/// <summary>
 9/// Provides endpoints for managing blockchain transactions.
 10/// </summary>
 11public static class TransactionEndpoints
 12{
 13    /// <summary>
 14    /// Maps transaction-related endpoints.
 15    /// </summary>
 16    /// <param name="app">The endpoint route builder.</param>
 17    public static void MapTransactionEndpoints(this IEndpointRouteBuilder app)
 11218    {
 11219        app.MapPost("/transactions/prepare", PrepareTransaction)
 11220           .WithName("PrepareTransaction")
 11221           .WithTags("Transaction")
 11222           .WithSummary("Prepare a transaction with proper signing and hash generation")
 11223           .WithDescription("Builds a complete transaction with signed inputs, proper outputs, and generates the transac
 11224           .Produces<TransactionDto>(StatusCodes.Status200OK)
 11225           .Produces(StatusCodes.Status400BadRequest)
 11226           .WithOpenApi();
 27
 11228        app.MapGet("/transactions/{hash?}", GetTransaction)
 11229            .WithName("GetTransaction")
 11230            .WithTags("Transaction")
 11231            .WithSummary("Get a transaction by hash or list pending transactions")
 11232            .WithDescription("If a hash is provided, returns the transaction. Otherwise, returns the next N pending tran
 11233            .Produces<TransactionSearchDto?>(StatusCodes.Status200OK)
 11234            .Produces(StatusCodes.Status404NotFound)
 11235            .WithOpenApi();
 5636
 11237        app.MapPost("/transactions", PostTransaction)
 5638            .WithName("PostTransaction")
 5639            .WithTags("Transaction")
 5640            .WithSummary("Submit a new transaction")
 5641            .WithDescription("Receives a transaction DTO and attempts to add it to the blockchain mempool.")
 5642            .Produces<TransactionDto>(StatusCodes.Status201Created)
 5643            .Produces(StatusCodes.Status400BadRequest)
 5644            .Produces(StatusCodes.Status422UnprocessableEntity)
 6045            .WithOpenApi();
 6046    }
 447
 448    /// <summary>
 49    /// Handles GET /transactions/{hash?}
 450    /// </summary>
 451    private static IResult GetTransaction(
 452        [FromRoute] string? hash,
 53        [FromServices] Domain.Blockchain blockchain)
 454    {
 455        if (!string.IsNullOrEmpty(hash))
 456        {
 457            TransactionSearch transactionSearch = blockchain.GetTransaction(hash);
 058
 859            return transactionSearch is not null
 460                ? Results.Ok(TransactionSearchMapper.ToDto(transactionSearch))
 461                : Results.NotFound();
 62        }
 63
 064        var next = blockchain.Mempool.Take(Domain.Blockchain.TX_PER_BLOCK).ToList();
 065        var total = blockchain.Mempool.Count;
 66
 467        return Results.Ok(new { next, total });
 868    }
 069
 70    /// <summary>
 471    /// Handles POST /transactions
 472    /// </summary>
 73    private static IResult PostTransaction(
 474        [FromBody] TransactionDto transactionDto,
 475        [FromServices] Domain.Blockchain blockchain)
 876    {
 877        if (string.IsNullOrEmpty(transactionDto.Hash))
 078            return Results.StatusCode(StatusCodes.Status422UnprocessableEntity);
 79
 480        var tx = TransactionMapper.ToDomain(transactionDto);
 481        var validation = blockchain.AddTransaction(tx);
 82
 483        return validation.Success
 484            ? Results.Created($"/transactions/{tx.Hash}", TransactionMapper.ToDto(tx))
 485            : Results.BadRequest(validation);
 486    }
 87
 88    /// <summary>
 89    /// Handles the POST /transactions/prepare endpoint.
 90    /// </summary>
 91    private static IResult PrepareTransaction(
 92        [FromBody] TransactionDto TransactionDto,
 93        [FromServices] Domain.Blockchain blockchain)
 094    {
 95        try
 096        {
 097            var fromWalletBalance = blockchain.GetBalance(TransactionDto.FromWalletAddress ?? "");
 098            var fee = blockchain.GetFeePerTx();
 099            var utxos = blockchain.GetUtxo(TransactionDto.FromWalletAddress ?? "");
 100
 0101            if (fromWalletBalance < TransactionDto.Amount + fee)
 0102            {
 0103                return Results.BadRequest(new Validation(false, "Insufficient balance"));
 104            }
 105
 0106            if (!utxos.Any())
 0107            {
 0108                return Results.BadRequest(new Validation(false, "No unspent transaction outputs available"));
 109            }
 110
 0111            var txInputs = utxos
 0112                .Select(TransactionInput.FromTxo)
 0113                .ToList();
 114
 0115            txInputs.ForEach(input => input.Sign(TransactionDto.FromWalletPrivateKey ?? ""));
 116
 0117            var txOutputs = new List<TransactionOutput>
 0118            {
 0119                new TransactionOutput(
 0120                    toAddress: TransactionDto.ToWalletAddress ?? "",
 0121                    amount: TransactionDto.Amount
 0122                )
 0123            };
 124
 0125            var remaining = fromWalletBalance - TransactionDto.Amount - fee;
 0126            if (remaining > 0)
 0127            {
 0128                txOutputs.Add(new TransactionOutput(
 0129                    toAddress: TransactionDto.FromWalletAddress ?? "",
 0130                    amount: remaining
 0131                ));
 0132            }
 133
 0134            var transaction = new Transaction(
 0135                type: TransactionType.REGULAR,
 0136                timestamp: DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
 0137                txInputs: txInputs,
 0138                txOutputs: txOutputs
 0139            );
 140
 0141            var transactionPrepare = TransactionMapper.ToDto(transaction);
 142
 0143            return Results.Ok(transactionPrepare);
 144        }
 0145        catch (Exception ex)
 0146        {
 0147            return Results.Problem(
 0148                title: "Transaction preparation failed",
 0149                detail: ex.Message,
 0150                statusCode: StatusCodes.Status500InternalServerError
 0151            );
 152        }
 0153    }
 154}