Commit 1f733aa0 authored by Jeremy Bokobza's avatar Jeremy Bokobza

Added wallet endpoints for balance, history, info, build transaction and send transaction

parent 76cff8d4
...@@ -21,7 +21,7 @@ namespace Breeze.Api.Tests ...@@ -21,7 +21,7 @@ namespace Breeze.Api.Tests
var controller = new WalletController(mockWalletCreate.Object); var controller = new WalletController(mockWalletCreate.Object);
// Act // Act
var result = controller.Create(new WalletCreationModel var result = controller.Create(new WalletCreationRequest
{ {
Name = "myName", Name = "myName",
FolderPath = "", FolderPath = "",
...@@ -52,7 +52,7 @@ namespace Breeze.Api.Tests ...@@ -52,7 +52,7 @@ namespace Breeze.Api.Tests
var controller = new WalletController(mockWalletWrapper.Object); var controller = new WalletController(mockWalletWrapper.Object);
// Act // Act
var result = controller.Recover(new WalletRecoveryModel var result = controller.Recover(new WalletRecoveryRequest
{ {
Name = "myName", Name = "myName",
FolderPath = "", FolderPath = "",
...@@ -87,7 +87,7 @@ namespace Breeze.Api.Tests ...@@ -87,7 +87,7 @@ namespace Breeze.Api.Tests
var controller = new WalletController(mockWalletWrapper.Object); var controller = new WalletController(mockWalletWrapper.Object);
// Act // Act
var result = controller.Load(new WalletLoadModel var result = controller.Load(new WalletLoadRequest
{ {
Name = "myName", Name = "myName",
FolderPath = "", FolderPath = "",
...@@ -113,7 +113,7 @@ namespace Breeze.Api.Tests ...@@ -113,7 +113,7 @@ namespace Breeze.Api.Tests
var controller = new WalletController(mockWalletWrapper.Object); var controller = new WalletController(mockWalletWrapper.Object);
// Act // Act
var result = controller.Load(new WalletLoadModel var result = controller.Load(new WalletLoadRequest
{ {
Name = "myName", Name = "myName",
FolderPath = "", FolderPath = "",
......
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Security; using System.Security;
using Breeze.Wallet.Errors;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Breeze.Wallet.Models; using Breeze.Wallet.Models;
using Breeze.Wallet.Wrappers; using Breeze.Wallet.Wrappers;
...@@ -25,14 +27,14 @@ namespace Breeze.Wallet.Controllers ...@@ -25,14 +27,14 @@ namespace Breeze.Wallet.Controllers
/// <param name="walletCreation">The object containing the parameters used to create the wallet.</param> /// <param name="walletCreation">The object containing the parameters used to create the wallet.</param>
/// <returns>A JSON object containing the mnemonic created for the new wallet.</returns> /// <returns>A JSON object containing the mnemonic created for the new wallet.</returns>
[HttpPost] [HttpPost]
public IActionResult Create([FromBody]WalletCreationModel walletCreation) public IActionResult Create([FromBody]WalletCreationRequest walletCreation)
{ {
// checks the request is valid // checks the request is valid
if (!this.ModelState.IsValid) if (!this.ModelState.IsValid)
{ {
var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage)); var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
return this.BadRequest(string.Join(Environment.NewLine, errors)); return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors));
} }
try try
{ {
...@@ -41,22 +43,20 @@ namespace Breeze.Wallet.Controllers ...@@ -41,22 +43,20 @@ namespace Breeze.Wallet.Controllers
} }
catch (NotSupportedException e) catch (NotSupportedException e)
{ {
Console.WriteLine(e); // indicates that this wallet already exists
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.Conflict, "This wallet already exists.", e.ToString());
// indicates that this wallet already exists
return this.StatusCode((int) HttpStatusCode.Conflict, "This wallet already exists.");
} }
} }
[HttpGet] [HttpGet]
public IActionResult Load([FromQuery]WalletLoadModel walletLoad) public IActionResult Load([FromQuery]WalletLoadRequest walletLoad)
{ {
// checks the request is valid // checks the request is valid
if (!this.ModelState.IsValid) if (!this.ModelState.IsValid)
{ {
var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage)); var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
return this.BadRequest(string.Join(Environment.NewLine, errors)); return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors));
} }
try try
{ {
...@@ -66,35 +66,29 @@ namespace Breeze.Wallet.Controllers ...@@ -66,35 +66,29 @@ namespace Breeze.Wallet.Controllers
} }
catch (FileNotFoundException e) catch (FileNotFoundException e)
{ {
Console.WriteLine(e); return ErrorHelpers.BuildErrorResponse(HttpStatusCode.Conflict, "This wallet already exists.", e.ToString());
}
// indicates that this wallet does not exist
return this.StatusCode((int)HttpStatusCode.NotFound, "Wallet not found.");
}
catch (SecurityException e) catch (SecurityException e)
{ {
Console.WriteLine(e); // indicates that the password is wrong
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.Forbidden, "Wrong password, please try again.", e.ToString());
// indicates that the password is wrong
return this.StatusCode((int)HttpStatusCode.Forbidden, "Wrong password, please try again.");
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(e); return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
return this.StatusCode((int)HttpStatusCode.BadRequest, e.Message); }
}
} }
[Route("recover")] [Route("recover")]
[HttpPost] [HttpPost]
public IActionResult Recover([FromBody]WalletRecoveryModel walletRecovery) public IActionResult Recover([FromBody]WalletRecoveryRequest walletRecovery)
{ {
// checks the request is valid // checks the request is valid
if (!this.ModelState.IsValid) if (!this.ModelState.IsValid)
{ {
var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage)); var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
return this.BadRequest(string.Join(Environment.NewLine, errors)); return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors));
} }
try try
{ {
...@@ -104,23 +98,133 @@ namespace Breeze.Wallet.Controllers ...@@ -104,23 +98,133 @@ namespace Breeze.Wallet.Controllers
} }
catch (FileNotFoundException e) catch (FileNotFoundException e)
{ {
Console.WriteLine(e); // indicates that this wallet does not exist
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.NotFound, "Wallet not found.", e.ToString());
// indicates that this wallet does not exist
return this.StatusCode((int)HttpStatusCode.NotFound, "Wallet not found.");
} }
catch (SecurityException e) catch (SecurityException e)
{ {
Console.WriteLine(e); // indicates that the password is wrong
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.Forbidden, "Wrong password, please try again.", e.ToString());
// indicates that the password is wrong }
return this.StatusCode((int)HttpStatusCode.Forbidden, "Wrong password, please try again.");
}
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(e); return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
return this.StatusCode((int)HttpStatusCode.BadRequest, e.Message); }
}
} }
}
[Route("info")]
[HttpGet]
public IActionResult GetInfo([FromQuery] WalletName model)
{
// checks the request is valid
if (!this.ModelState.IsValid)
{
var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors));
}
try
{
return this.Json(this.walletWrapper.GetInfo(model.Name));
}
catch (Exception e)
{
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
}
}
[Route("history")]
[HttpGet]
public IActionResult GetHistory([FromQuery] WalletName model)
{
// checks the request is valid
if (!this.ModelState.IsValid)
{
var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors));
}
try
{
return this.Json(this.walletWrapper.GetHistory(model.Name));
}
catch (Exception e)
{
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
}
}
[Route("balance")]
[HttpGet]
public IActionResult GetBalance([FromQuery] WalletName model)
{
// checks the request is valid
if (!this.ModelState.IsValid)
{
var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors));
}
try
{
return this.Json(this.walletWrapper.GetBalance(model.Name));
}
catch (Exception e)
{
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
}
}
[Route("build-transaction")]
[HttpPost]
public IActionResult BuildTransaction([FromQuery] BuildTransactionRequest request)
{
// checks the request is valid
if (!this.ModelState.IsValid)
{
var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors));
}
try
{
return this.Json(this.walletWrapper.BuildTransaction(request.Password, request.Address, request.Amount, request.FeeType, request.AllowUnconfirmed));
}
catch (Exception e)
{
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
}
}
[Route("send-transaction")]
[HttpPost]
public IActionResult SendTransaction([FromQuery] SendTransactionRequest request)
{
// checks the request is valid
if (!this.ModelState.IsValid)
{
var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors));
}
try
{
var result = this.walletWrapper.SendTransaction(request.Hex);
if (result)
{
return this.Ok();
}
return this.StatusCode((int)HttpStatusCode.BadRequest);
}
catch (Exception e)
{
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
}
}
}
} }
using System.Collections.Generic;
using System.Net;
namespace Breeze.Wallet.Errors
{
public static class ErrorHelpers
{
public static ErrorResult BuildErrorResponse(HttpStatusCode statusCode, string message, string description)
{
ErrorResponse errorResponse = new ErrorResponse
{
Errors = new List<ErrorModel>
{
new ErrorModel
{
Status = (int) statusCode,
Message = message,
Description = description
}
}
};
return new ErrorResult((int)statusCode, errorResponse);
}
}
}
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Breeze.Wallet.Errors
{
public class ErrorResponse
{
[JsonProperty(PropertyName = "errors")]
public List<ErrorModel> Errors { get; set; }
}
public class ErrorModel
{
[JsonProperty(PropertyName = "status")]
public int Status { get; set; }
[JsonProperty(PropertyName = "message")]
public string Message { get; set; }
[JsonProperty(PropertyName = "description")]
public string Description { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Breeze.Wallet.Errors;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Breeze.Wallet.Errors
{
public class ErrorResult : ObjectResult
{
public ErrorResult(int statusCode, ErrorResponse value) : base(value)
{
StatusCode = statusCode;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using Newtonsoft.Json;
namespace Breeze.Wallet.Models
{
public class WalletBalanceModel
{
[JsonProperty(PropertyName = "isSynced")]
public bool IsSynced { get; set; }
[JsonProperty(PropertyName = "confirmed")]
public Money Confirmed { get; set; }
[JsonProperty(PropertyName = "unconfirmed")]
public Money Unconfirmed { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using Newtonsoft.Json;
namespace Breeze.Wallet.Models
{
public class WalletBuildTransactionModel
{
[JsonProperty(PropertyName = "spendsUnconfirmed")]
public bool SpendsUnconfirmed { get; set; }
[JsonProperty(PropertyName = "fee")]
public Money Fee { get; set; }
[JsonProperty(PropertyName = "feePercentOfSent")]
public double FeePercentOfSent { get; set; }
[JsonProperty(PropertyName = "hex")]
public string Hex { get; set; }
[JsonProperty(PropertyName = "transaction")]
public Transaction Transaction { get; set; }
}
public class Transaction
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "isCoinbase")]
public bool IsCoinbase { get; set; }
[JsonProperty(PropertyName = "block")]
public string Block { get; set; }
[JsonProperty(PropertyName = "spentCoins")]
public IEnumerable<TransactionDetails> SpentCoins { get; set; }
[JsonProperty(PropertyName = "receivedCoins")]
public IEnumerable<TransactionDetails> ReceivedCoins { get; set; }
[JsonProperty(PropertyName = "firstSendDate")]
public DateTime FirstSeenDate { get; set; }
[JsonProperty(PropertyName = "fees")]
public Money Fees { get; set; }
}
public class TransactionDetails
{
[JsonProperty(PropertyName = "transactionId")]
public string TransactionId { get; set; }
[JsonProperty(PropertyName = "index")]
public int Index { get; set; }
[JsonProperty(PropertyName = "value")]
public int Value { get; set; }
[JsonProperty(PropertyName = "scriptPubKey")]
public string ScriptPubKey { get; set; }
[JsonProperty(PropertyName = "redeemScript")]
public string RedeemScript { get; set; }
}
}
...@@ -7,7 +7,7 @@ namespace Breeze.Wallet.Models ...@@ -7,7 +7,7 @@ namespace Breeze.Wallet.Models
/// <summary> /// <summary>
/// Object used to create a new wallet /// Object used to create a new wallet
/// </summary> /// </summary>
public class WalletCreationModel public class WalletCreationRequest
{ {
[Required(ErrorMessage = "A password is required.")] [Required(ErrorMessage = "A password is required.")]
public string Password { get; set; } public string Password { get; set; }
...@@ -21,10 +21,10 @@ namespace Breeze.Wallet.Models ...@@ -21,10 +21,10 @@ namespace Breeze.Wallet.Models
public string Name { get; set; } public string Name { get; set; }
} }
public class WalletLoadModel public class WalletLoadRequest
{ {
[Required(ErrorMessage = "A password is required.")] [Required(ErrorMessage = "A password is required.")]
public string Password { get; set; } public string Password { get; set; }
[Required(ErrorMessage = "The folder path is required.")] [Required(ErrorMessage = "The folder path is required.")]
public string FolderPath { get; set; } public string FolderPath { get; set; }
...@@ -33,7 +33,7 @@ namespace Breeze.Wallet.Models ...@@ -33,7 +33,7 @@ namespace Breeze.Wallet.Models
public string Name { get; set; } public string Name { get; set; }
} }
public class WalletRecoveryModel public class WalletRecoveryRequest
{ {
[Required(ErrorMessage = "A mnemonic is required.")] [Required(ErrorMessage = "A mnemonic is required.")]
public string Mnemonic { get; set; } public string Mnemonic { get; set; }
...@@ -48,5 +48,35 @@ namespace Breeze.Wallet.Models ...@@ -48,5 +48,35 @@ namespace Breeze.Wallet.Models
public string Name { get; set; } public string Name { get; set; }
public string Network { get; set; } public string Network { get; set; }
} }
public class WalletName
{
[Required(ErrorMessage = "The name of the wallet is missing.")]
public string Name { get; set; }
}
public class BuildTransactionRequest
{
[Required(ErrorMessage = "A password is required.")]
public string Password { get; set; }
[Required(ErrorMessage = "A destination address is required.")]
public string Address { get; set; }
[Required(ErrorMessage = "An amount is required.")]
public string Amount { get; set; }
[Required(ErrorMessage = "A fee type is required. It can be 'low', 'medium' or 'high'.")]
public string FeeType { get; set; }
public bool AllowUnconfirmed { get; set; }
}
public class SendTransactionRequest
{
[Required(ErrorMessage = "A transaction in hexadecimal format is required.")]
public string Hex { get; set; }
}
} }
using System.Collections.Generic;
using NBitcoin;
using Newtonsoft.Json;
namespace Breeze.Wallet.Models
{
public class WalletHistoryModel
{
[JsonProperty(PropertyName = "transactions")]
public List<TransactionItem> Transactions { get; set; }
}
public class TransactionItem
{
[JsonProperty(PropertyName = "txId")]
public string TransactionId { get; set; }
[JsonProperty(PropertyName = "amount")]
public Money Amount { get; set; }
[JsonProperty(PropertyName = "confirmed")]
public Money Confirmed { get; set; }
[JsonProperty(PropertyName = "timestamp")]
public string Timestamp { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Breeze.Wallet.Models
{
public class WalletInfoModel
{
[JsonProperty(PropertyName = "filePath")]
public string FilePath { get; set; }
[JsonProperty(PropertyName = "encryptedSeed")]
public string EncryptedSeed { get; set; }
[JsonProperty(PropertyName = "chainCode")]
public string ChainCode { get; set; }
[JsonProperty(PropertyName = "network")]
public string Network { get; set; }
[JsonProperty(PropertyName = "creationTime")]
public string CreationTime { get; set; }
[JsonProperty(PropertyName = "isDecrypted")]
public bool IsDecrypted { get; set; }
[JsonProperty(PropertyName = "uniqueId")]
public string UniqueId { get; set; }
}
}
...@@ -2,15 +2,19 @@ ...@@ -2,15 +2,19 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Breeze.Wallet namespace Breeze.Wallet.Models
{ {
public class WalletModel public class WalletModel
{ {
[JsonProperty(PropertyName = "network")]
public string Network { get; set; } public string Network { get; set; }
[JsonProperty(PropertyName = "fileName")]
public string FileName { get; set; } public string FileName { get; set; }
[JsonProperty(PropertyName = "addresses")]
public IEnumerable<string> Addresses { get; set; } public IEnumerable<string> Addresses { get; set; }
} }
} }
 
using Breeze.Wallet.Models;
using HBitcoin.Models;
using NBitcoin;
namespace Breeze.Wallet.Wrappers namespace Breeze.Wallet.Wrappers
{ {
/// <summary> /// <summary>
...@@ -6,10 +10,21 @@ namespace Breeze.Wallet.Wrappers ...@@ -6,10 +10,21 @@ namespace Breeze.Wallet.Wrappers
/// </summary> /// </summary>
public interface IWalletWrapper public interface IWalletWrapper
{ {
string Create(string password, string folderPath, string name, string network); string Create(string password, string folderPath, string name, string network);
WalletModel Load(string password, string folderPath, string name); WalletModel Load(string password, string folderPath, string name);
WalletModel Recover(string password, string folderPath, string name, string network, string mnemonic); WalletModel Recover(string password, string folderPath, string name, string network, string mnemonic);
WalletInfoModel GetInfo(string walletName);
WalletBalanceModel GetBalance(string walletName);
WalletHistoryModel GetHistory(string walletName);
WalletBuildTransactionModel BuildTransaction(string password, string address, Money amount, string feeType, bool allowUnconfirmed);
bool SendTransaction(string transactionHex);
} }
} }
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Breeze.Wallet.Models;
using HBitcoin.KeyManagement; using HBitcoin.KeyManagement;
using NBitcoin; using NBitcoin;
...@@ -79,5 +80,31 @@ namespace Breeze.Wallet.Wrappers ...@@ -79,5 +80,31 @@ namespace Breeze.Wallet.Wrappers
return Network.TestNet; return Network.TestNet;
} }
} }
public WalletInfoModel GetInfo(string name)
{
throw new System.NotImplementedException();
}
public WalletBalanceModel GetBalance(string walletName)
{
throw new System.NotImplementedException();
}
public WalletHistoryModel GetHistory(string walletName)
{
throw new System.NotImplementedException();
}
public WalletBuildTransactionModel BuildTransaction(string password, string address, Money amount, string feeType,
bool allowUnconfirmed)
{
throw new System.NotImplementedException();
}
public bool SendTransaction(string transactionHex)
{
throw new System.NotImplementedException();
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment