| | 1 | | using System.Threading.Tasks; |
| | 2 | | using EF.Blockchain.Domain; |
| | 3 | | using EF.Blockchain.Server.Dtos; |
| | 4 | | using EF.Blockchain.Server.Mappers; |
| | 5 | | using Microsoft.AspNetCore.Mvc; |
| | 6 | |
|
| | 7 | | namespace EF.Blockchain.Server.Endpoints; |
| | 8 | |
|
| | 9 | | /// <summary> |
| | 10 | | /// Provides endpoints to query wallet information. |
| | 11 | | /// </summary> |
| | 12 | | public static class WalletEndpoints |
| | 13 | | { |
| | 14 | | /// <summary> |
| | 15 | | /// Maps wallet-related endpoints. |
| | 16 | | /// </summary> |
| 56 | 17 | | /// <param name="app">The endpoint route builder.</param> |
| 56 | 18 | | public static void MapWalletEndpoints(this IEndpointRouteBuilder app) |
| 112 | 19 | | { |
| 112 | 20 | | app.MapGet("/wallets/{walletAddress}", GetWalletInfo) |
| 112 | 21 | | .WithName("GetWalletInfo") |
| 112 | 22 | | .WithTags("Wallet") |
| 112 | 23 | | .WithSummary("Get wallet balance, fee, and UTXOs") |
| 112 | 24 | | .WithDescription("Returns balance, transaction fee, and unspent transaction outputs (UTXOs) for a wallet.") |
| 112 | 25 | | .Produces<WalletDto>(StatusCodes.Status200OK) |
| 56 | 26 | | .WithOpenApi(); |
| | 27 | |
|
| 56 | 28 | | app.MapPost("/wallets", CreateWallet) |
| 56 | 29 | | .WithName("CreateWallet") |
| 56 | 30 | | .WithTags("Wallet") |
| 56 | 31 | | .WithSummary("Create a new wallet") |
| 56 | 32 | | .WithDescription("Generates a new wallet with public key, private key, and mnemonic phrase.") |
| 56 | 33 | | .Produces<WalletDto>(StatusCodes.Status201Created) |
| 56 | 34 | | .WithOpenApi(); |
| | 35 | |
|
| 60 | 36 | | app.MapPost("/wallets/recover", RecoverWallet) |
| 60 | 37 | | .WithName("RecoverWallet") |
| 60 | 38 | | .WithTags("Wallet") |
| 56 | 39 | | .WithSummary("Recover an existing wallet") |
| 60 | 40 | | .WithDescription("Recovers a wallet using private key or mnemonic phrase.") |
| 56 | 41 | | .Produces<WalletDto>(StatusCodes.Status200OK) |
| 60 | 42 | | .Produces(StatusCodes.Status400BadRequest) |
| 60 | 43 | | .WithOpenApi(); |
| 56 | 44 | | } |
| | 45 | |
|
| | 46 | | /// <summary> |
| | 47 | | /// Handles the GET /wallets/{walletAddress} endpoint. |
| | 48 | | /// </summary> |
| | 49 | | /// <param name="walletAddress">Wallet public address.</param> |
| | 50 | | /// <param name="blockchain">Injected blockchain instance.</param> |
| | 51 | | /// <returns>Wallet details including balance, fee, and UTXOs.</returns> |
| | 52 | | private static WalletDto GetWalletInfo( |
| | 53 | | [FromRoute] string walletAddress, |
| | 54 | | [FromServices] Domain.Blockchain blockchain) |
| 4 | 55 | | { |
| 4 | 56 | | int balance = blockchain.GetBalance(walletAddress); |
| 4 | 57 | | int fee = blockchain.GetFeePerTx(); |
| | 58 | |
|
| 4 | 59 | | var utxo = blockchain.GetUtxo(walletAddress); |
| | 60 | |
|
| 4 | 61 | | return WalletMapper.ToDto(balance, fee, utxo); |
| 4 | 62 | | } |
| | 63 | |
|
| | 64 | | /// <summary> |
| | 65 | | /// Handles the POST /wallets endpoint. |
| | 66 | | /// </summary> |
| | 67 | | private static async Task<IResult> CreateWallet([FromBody] WalletDto walletDto, |
| | 68 | | IConfiguration config, |
| | 69 | | [FromServices] Domain.Blockchain blockchain) |
| 0 | 70 | | { |
| | 71 | | try |
| 0 | 72 | | { |
| 0 | 73 | | var newWallet = new Wallet(); |
| 0 | 74 | | var amountFaucet = 10; |
| | 75 | |
|
| 0 | 76 | | var miner = config["Blockchain:MinerWallet:PrivateKey"] |
| 0 | 77 | | ?? Environment.GetEnvironmentVariable("BLOCKCHAIN_MINER") |
| 0 | 78 | | ?? "default-miner"; |
| | 79 | |
|
| 0 | 80 | | var minerWallet = new Wallet(miner); |
| | 81 | |
|
| 0 | 82 | | var fromWalletBalance = blockchain.GetBalance(minerWallet.PublicKey ?? ""); |
| 0 | 83 | | var fee = blockchain.GetFeePerTx(); |
| 0 | 84 | | var utxos = blockchain.GetUtxo(minerWallet.PublicKey ?? ""); |
| | 85 | |
|
| 0 | 86 | | var txInputs = utxos |
| 0 | 87 | | .Select(TransactionInput.FromTxo) |
| 0 | 88 | | .ToList(); |
| | 89 | |
|
| 0 | 90 | | txInputs.ForEach(input => input.Sign(minerWallet.PrivateKey ?? "")); |
| | 91 | |
|
| 0 | 92 | | var txOutputs = new List<TransactionOutput> |
| 0 | 93 | | { |
| 0 | 94 | | new TransactionOutput( |
| 0 | 95 | | toAddress: newWallet.PublicKey ?? "", |
| 0 | 96 | | amount: amountFaucet |
| 0 | 97 | | ) |
| 0 | 98 | | }; |
| | 99 | |
|
| 0 | 100 | | var remaining = fromWalletBalance - amountFaucet - fee; |
| 0 | 101 | | if (remaining > 0) |
| 0 | 102 | | { |
| 0 | 103 | | txOutputs.Add(new TransactionOutput( |
| 0 | 104 | | toAddress: minerWallet.PublicKey ?? "", |
| 0 | 105 | | amount: remaining |
| 0 | 106 | | )); |
| 0 | 107 | | } |
| | 108 | |
|
| 0 | 109 | | var faucetTransaction = new Transaction( |
| 0 | 110 | | type: TransactionType.REGULAR, |
| 0 | 111 | | timestamp: DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), |
| 0 | 112 | | txInputs: txInputs, |
| 0 | 113 | | txOutputs: txOutputs |
| 0 | 114 | | ); |
| | 115 | |
|
| 0 | 116 | | blockchain.AddTransaction(faucetTransaction); |
| | 117 | |
|
| 0 | 118 | | await Task.Delay(2000); |
| | 119 | |
|
| 0 | 120 | | walletDto.PublicKey = newWallet.PublicKey; |
| 0 | 121 | | walletDto.PrivateKey = newWallet.PrivateKey; |
| 0 | 122 | | walletDto.Balance = amountFaucet; |
| | 123 | |
|
| 0 | 124 | | return Results.Created($"/wallets/{walletDto.PublicKey}", walletDto); |
| | 125 | | } |
| 0 | 126 | | catch (Exception ex) |
| 0 | 127 | | { |
| 0 | 128 | | return Results.Problem( |
| 0 | 129 | | title: "Wallet Creation Failed", |
| 0 | 130 | | detail: ex.Message, |
| 0 | 131 | | statusCode: StatusCodes.Status500InternalServerError |
| 0 | 132 | | ); |
| | 133 | | } |
| 0 | 134 | | } |
| | 135 | |
|
| | 136 | | /// <summary> |
| | 137 | | /// Handles the POST /wallets/recover endpoint. |
| | 138 | | /// </summary> |
| | 139 | | /// <param name="walletDto">The wallet recovery request.</param> |
| | 140 | | /// <param name="blockchain">Injected blockchain instance.</param> |
| | 141 | | /// <returns>The recovered wallet information.</returns> |
| | 142 | | private static IResult RecoverWallet( |
| | 143 | | [FromBody] WalletDto walletDto, |
| | 144 | | [FromServices] Domain.Blockchain blockchain) |
| 0 | 145 | | { |
| | 146 | | try |
| 0 | 147 | | { |
| 0 | 148 | | Domain.Wallet recoveredWallet = new Domain.Wallet(walletDto.PrivateKey); |
| | 149 | |
|
| 0 | 150 | | int balance = blockchain.GetBalance(recoveredWallet.PublicKey); |
| | 151 | |
|
| 0 | 152 | | walletDto.PublicKey = recoveredWallet.PublicKey; |
| 0 | 153 | | walletDto.PrivateKey = recoveredWallet.PrivateKey; |
| 0 | 154 | | walletDto.Balance = balance; |
| | 155 | |
|
| 0 | 156 | | return Results.Ok(walletDto); |
| | 157 | | } |
| 0 | 158 | | catch (Exception ex) |
| 0 | 159 | | { |
| 0 | 160 | | return Results.Problem( |
| 0 | 161 | | title: "Wallet Recovery Failed", |
| 0 | 162 | | detail: ex.Message, |
| 0 | 163 | | statusCode: StatusCodes.Status500InternalServerError |
| 0 | 164 | | ); |
| | 165 | | } |
| 0 | 166 | | } |
| | 167 | | } |