Commit c5fa0532 authored by Jeremy Bokobza's avatar Jeremy Bokobza

Added creation of accounts for a wallet

parent bbec71c8
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Xunit; using Xunit;
using Moq; using Moq;
using Breeze.Wallet.Wrappers;
using Breeze.Wallet; using Breeze.Wallet;
using Breeze.Wallet.Controllers; using Breeze.Wallet.Controllers;
using Breeze.Wallet.Errors; using Breeze.Wallet.Errors;
using Breeze.Wallet.Helpers;
using Breeze.Wallet.Models; using Breeze.Wallet.Models;
using NBitcoin;
namespace Breeze.Api.Tests namespace Breeze.Api.Tests
{ {
...@@ -16,8 +18,9 @@ namespace Breeze.Api.Tests ...@@ -16,8 +18,9 @@ namespace Breeze.Api.Tests
[Fact] [Fact]
public void CreateWalletSuccessfullyReturnsMnemonic() public void CreateWalletSuccessfullyReturnsMnemonic()
{ {
var mockWalletCreate = new Mock<IWalletWrapper>(); Mnemonic mnemonic = new Mnemonic(Wordlist.English, WordCount.Twelve);
mockWalletCreate.Setup(wallet => wallet.Create(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns("mnemonic"); var mockWalletCreate = new Mock<IWalletManager>();
mockWalletCreate.Setup(wallet => wallet.CreateWallet(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), null, CoinType.Bitcoin)).Returns(mnemonic);
var controller = new WalletController(mockWalletCreate.Object); var controller = new WalletController(mockWalletCreate.Object);
...@@ -33,32 +36,31 @@ namespace Breeze.Api.Tests ...@@ -33,32 +36,31 @@ namespace Breeze.Api.Tests
// Assert // Assert
mockWalletCreate.VerifyAll(); mockWalletCreate.VerifyAll();
var viewResult = Assert.IsType<JsonResult>(result); var viewResult = Assert.IsType<JsonResult>(result);
Assert.Equal("mnemonic", viewResult.Value); Assert.Equal(mnemonic.ToString(), viewResult.Value);
Assert.NotNull(result); Assert.NotNull(result);
} }
[Fact] [Fact]
public void LoadWalletSuccessfullyReturnsWalletModel() public void LoadWalletSuccessfullyReturnsWalletModel()
{ {
WalletModel walletModel = new WalletModel Wallet.Wallet wallet = new Wallet.Wallet
{ {
FileName = "myWallet", Name = "myWallet",
Network = "MainNet", Network = WalletHelpers.GetNetwork("mainnet")
Addresses = new List<string> { "address1", "address2", "address3", "address4", "address5" }
}; };
var mockWalletWrapper = new Mock<IWalletWrapper>();
mockWalletWrapper.Setup(wallet => wallet.Recover(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(walletModel); var mockWalletWrapper = new Mock<IWalletManager>();
mockWalletWrapper.Setup(w => w.RecoverWallet(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), null, CoinType.Bitcoin, null)).Returns(wallet);
var controller = new WalletController(mockWalletWrapper.Object); var controller = new WalletController(mockWalletWrapper.Object);
// Act // Act
var result = controller.Recover(new WalletRecoveryRequest var result = controller.Recover(new WalletRecoveryRequest
{ {
Name = "myName", Name = "myWallet",
FolderPath = "", FolderPath = "",
Password = "", Password = "",
Network = "", Network = "MainNet",
Mnemonic = "mnemonic" Mnemonic = "mnemonic"
}); });
...@@ -69,28 +71,26 @@ namespace Breeze.Api.Tests ...@@ -69,28 +71,26 @@ namespace Breeze.Api.Tests
Assert.IsType<WalletModel>(viewResult.Value); Assert.IsType<WalletModel>(viewResult.Value);
var model = viewResult.Value as WalletModel; var model = viewResult.Value as WalletModel;
Assert.Equal("myWallet", model.FileName); Assert.Equal("Main", model.Network);
} }
[Fact] [Fact]
public void RecoverWalletSuccessfullyReturnsWalletModel() public void RecoverWalletSuccessfullyReturnsWalletModel()
{ {
WalletModel walletModel = new WalletModel Wallet.Wallet wallet = new Wallet.Wallet
{ {
FileName = "myWallet", Name = "myWallet",
Network = "MainNet", Network = WalletHelpers.GetNetwork("mainnet")
Addresses = new List<string> { "address1", "address2", "address3", "address4", "address5" }
}; };
var mockWalletWrapper = new Mock<IWalletWrapper>(); var mockWalletWrapper = new Mock<IWalletManager>();
mockWalletWrapper.Setup(wallet => wallet.Load(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(walletModel); mockWalletWrapper.Setup(w => w.LoadWallet(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(wallet);
var controller = new WalletController(mockWalletWrapper.Object); var controller = new WalletController(mockWalletWrapper.Object);
// Act // Act
var result = controller.Load(new WalletLoadRequest var result = controller.Load(new WalletLoadRequest
{ {
Name = "myName", Name = "myWallet",
FolderPath = "", FolderPath = "",
Password = "" Password = ""
}); });
...@@ -102,14 +102,14 @@ namespace Breeze.Api.Tests ...@@ -102,14 +102,14 @@ namespace Breeze.Api.Tests
Assert.IsType<WalletModel>(viewResult.Value); Assert.IsType<WalletModel>(viewResult.Value);
var model = viewResult.Value as WalletModel; var model = viewResult.Value as WalletModel;
Assert.Equal("myWallet", model.FileName); Assert.Equal("Main", model.Network);
} }
[Fact] [Fact]
public void FileNotFoundExceptionandReturns404() public void FileNotFoundExceptionandReturns404()
{ {
var mockWalletWrapper = new Mock<IWalletWrapper>(); var mockWalletWrapper = new Mock<IWalletManager>();
mockWalletWrapper.Setup(wallet => wallet.Load(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Throws<FileNotFoundException>(); mockWalletWrapper.Setup(wallet => wallet.LoadWallet(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Throws<FileNotFoundException>();
var controller = new WalletController(mockWalletWrapper.Object); var controller = new WalletController(mockWalletWrapper.Object);
......
...@@ -311,6 +311,32 @@ namespace Breeze.Wallet.Controllers ...@@ -311,6 +311,32 @@ namespace Breeze.Wallet.Controllers
} }
} }
/// <summary>
/// Creates a new account for a wallet.
/// </summary>
/// <returns>An account name.</returns>
[Route("account")]
[HttpPost]
public IActionResult CreateNewAccount([FromBody]CreateAccountModel 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.walletManager.CreateNewAccount(request.WalletName, request.AccountName);
return this.Json(result);
}
catch (Exception e)
{
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
}
}
/// <summary> /// <summary>
/// Gets a folder. /// Gets a folder.
/// </summary> /// </summary>
......
...@@ -17,8 +17,9 @@ namespace Breeze.Wallet ...@@ -17,8 +17,9 @@ namespace Breeze.Wallet
/// <param name="name">The name of the wallet.</param> /// <param name="name">The name of the wallet.</param>
/// <param name="network">The network this wallet is for.</param> /// <param name="network">The network this wallet is for.</param>
/// <param name="passphrase">The passphrase used in the seed.</param> /// <param name="passphrase">The passphrase used in the seed.</param>
/// <param name="coinType">The type of coin this wallet will contain.</param>
/// <returns>A mnemonic defining the wallet's seed used to generate addresses.</returns> /// <returns>A mnemonic defining the wallet's seed used to generate addresses.</returns>
Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null); Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null, CoinType coinType = CoinType.Bitcoin);
/// <summary> /// <summary>
/// Loads a wallet from a file. /// Loads a wallet from a file.
...@@ -38,9 +39,10 @@ namespace Breeze.Wallet ...@@ -38,9 +39,10 @@ namespace Breeze.Wallet
/// <param name="network">The network in which to creae this wallet</param> /// <param name="network">The network in which to creae this wallet</param>
/// <param name="mnemonic">The user's mnemonic for the wallet.</param> /// <param name="mnemonic">The user's mnemonic for the wallet.</param>
/// <param name="passphrase">The passphrase used in the seed.</param> /// <param name="passphrase">The passphrase used in the seed.</param>
/// <param name="coinType">The type of coin this wallet will contain.</param>
/// <param name="creationTime">The time this wallet was created.</param> /// <param name="creationTime">The time this wallet was created.</param>
/// <returns>The recovered wallet.</returns> /// <returns>The recovered wallet.</returns>
Wallet RecoverWallet(string password, string folderPath, string name, string network, string mnemonic, string passphrase = null, DateTimeOffset? creationTime = null); Wallet RecoverWallet(string password, string folderPath, string name, string network, string mnemonic, string passphrase = null, CoinType coinType = CoinType.Bitcoin, DateTimeOffset? creationTime = null);
/// <summary> /// <summary>
/// Deleted a wallet. /// Deleted a wallet.
...@@ -48,6 +50,18 @@ namespace Breeze.Wallet ...@@ -48,6 +50,18 @@ namespace Breeze.Wallet
/// <param name="walletFilePath">The location of the wallet file.</param> /// <param name="walletFilePath">The location of the wallet file.</param>
void DeleteWallet(string walletFilePath); void DeleteWallet(string walletFilePath);
/// <summary>
/// Creates a new account.
/// </summary>
/// <param name="walletName">The name of the wallet in which this account will be created.</param>
/// <param name="accountName">The name by which this account will be identified.</param>
/// <remarks>
/// According to BIP44, an account at index (i) can only be created when the account
/// at index (i - 1) contains transactions.
/// </remarks>
/// <returns>The name of the new account.</returns>
string CreateNewAccount(string walletName, string accountName);
WalletGeneralInfoModel GetGeneralInfo(string walletName); WalletGeneralInfoModel GetGeneralInfo(string walletName);
WalletBalanceModel GetBalance(string walletName); WalletBalanceModel GetBalance(string walletName);
......
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace Breeze.Wallet.Models
{
public class CreateAccountModel
{
/// <summary>
/// The name of the wallet in which to create the account.
/// </summary>
[Required]
public string WalletName { get; set; }
/// <summary>
/// The name of the account.
/// </summary>
[Required]
public string AccountName { get; set; }
}
}
using System; using System;
using System.Collections.Generic;
using Breeze.Wallet.JsonConverters; using Breeze.Wallet.JsonConverters;
using NBitcoin; using NBitcoin;
using Newtonsoft.Json; using Newtonsoft.Json;
...@@ -10,6 +11,11 @@ namespace Breeze.Wallet ...@@ -10,6 +11,11 @@ namespace Breeze.Wallet
/// </summary> /// </summary>
public class Wallet public class Wallet
{ {
/// <summary>
/// The name of this wallet.
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary> /// <summary>
/// The seed for this wallet, password encrypted. /// The seed for this wallet, password encrypted.
...@@ -41,13 +47,149 @@ namespace Breeze.Wallet ...@@ -41,13 +47,149 @@ namespace Breeze.Wallet
/// <summary> /// <summary>
/// The location of the wallet file on the local system. /// The location of the wallet file on the local system.
/// </summary> /// </summary>
[JsonIgnore] [JsonProperty(PropertyName = "walletFilePath")]
public string WalletFilePath { get; set; } public string WalletFilePath { get; set; }
/// <summary> /// <summary>
/// The hierarchy of the wallet's accounts and addresses. /// The type of coin, Bitcoin or Stratis.
/// </summary>
[JsonProperty(PropertyName = "coinType")]
public CoinType CoinType { get; set; }
/// <summary>
/// The accounts used in the wallet.
/// </summary>
[JsonProperty(PropertyName = "accounts")]
public IEnumerable<HdAccount> Accounts { get; set; }
}
/// <summary>
/// The type of coin, as specified in BIP44.
/// </summary>
public enum CoinType
{
Bitcoin = 0,
Stratis = 105
}
/// <summary>
/// An Hd account's details.
/// </summary>
public class HdAccount
{
/// <summary>
/// The index of the account.
/// </summary>
/// <remarks>
/// According to BIP44, an account at index (i) can only be created when the account
/// at index (i - 1) contains transactions.
/// </remarks>
[JsonProperty(PropertyName = "index")]
public int Index { get; set; }
/// <summary>
/// The name of this account.
/// </summary> /// </summary>
[JsonProperty(PropertyName = "hierarchy")] [JsonProperty(PropertyName = "name")]
public WalletHierarchy Hierarchy { get; set; } public string Name { get; set; }
/// <summary>
/// Gets or sets the creation time.
/// </summary>
[JsonProperty(PropertyName = "creationTime")]
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset CreationTime { get; set; }
/// <summary>
/// The list of external addresses, typically used for receiving money.
/// </summary>
[JsonProperty(PropertyName = "externalAddresses")]
public IEnumerable<HdAddress> ExternalAddresses { get; set; }
/// <summary>
/// The list of internal addresses, typically used to receive change.
/// </summary>
[JsonProperty(PropertyName = "internalAddresses")]
public IEnumerable<HdAddress> InternalAddresses { get; set; }
}
/// <summary>
/// An Hd address.
/// </summary>
public class HdAddress
{
/// <summary>
/// Gets or sets the creation time.
/// </summary>
[JsonProperty(PropertyName = "creationTime")]
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset CreationTime { get; set; }
/// <summary>
/// The script pub key for this address.
/// </summary>
[JsonProperty(PropertyName = "scriptPubKey")]
public Script ScriptPubKey { get; set; }
/// <summary>
/// The Base58 representation of this address.
/// </summary>
[JsonProperty(PropertyName = "address")]
public BitcoinAddress Address { get; set; }
/// <summary>
/// A path to the address as defined in BIP44.
/// </summary>
[JsonProperty(PropertyName = "hdPath")]
public string HdPath { get; set; }
/// <summary>
/// A list detailing which blocks have been scanned for this address.
/// </summary>
[JsonIgnore]
public SortedList<int, int> BlocksScanned { get; set; }
/// <summary>
/// A list of transactions involving this address.
/// </summary>
[JsonProperty(PropertyName = "transactions")]
public IEnumerable<TransactionData> Transactions { get; set; }
}
/// <summary>
/// An object containing transaction data.
/// </summary>
public class TransactionData
{
/// <summary>
/// Transaction id.
/// </summary>
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
/// <summary>
/// The transaction amount.
/// </summary>
[JsonProperty(PropertyName = "amount")]
public Money Amount { get; set; }
/// <summary>
/// The height of the block including this transaction.
/// </summary>
[JsonProperty(PropertyName = "blockHeight")]
public int BlockHeight { get; set; }
/// <summary>
/// Whether this transaction has been confirmed or not.
/// </summary>
[JsonProperty(PropertyName = "confirmed")]
public bool Confirmed { get; set; }
/// <summary>
/// Gets or sets the creation time.
/// </summary>
[JsonProperty(PropertyName = "creationTime")]
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset CreationTime { get; set; }
} }
} }
\ No newline at end of file
using System;
using System.Collections.Generic;
using Breeze.Wallet.JsonConverters;
using NBitcoin;
using Newtonsoft.Json;
namespace Breeze.Wallet
{
/// <summary>
/// Represents the root of the user's wallet's addresses and transactions.
/// </summary>
public class WalletHierarchy
{
/// <summary>
/// The type of coin, Bitcoin or Stratis.
/// </summary>
[JsonProperty(PropertyName = "coinType")]
public CoinType CoinType { get; set; }
/// <summary>
/// The accounts used in the wallet.
/// </summary>
[JsonProperty(PropertyName = "accounts")]
public IEnumerable<HdAccount> Accounts { get; set; }
}
/// <summary>
/// The type of coin, as specified in BIP44.
/// </summary>
public enum CoinType
{
Bitcoin = 0,
Stratis = 105
}
/// <summary>
/// An Hd account's details.
/// </summary>
public class HdAccount
{
/// <summary>
/// The index of the account.
/// </summary>
/// <remarks>
/// According to BIP44, an account at index (i) can only be created when the account
/// at index (i - 1) contains transactions.
/// </remarks>
[JsonProperty(PropertyName = "index")]
public int Index { get; set; }
/// <summary>
/// The list of external addresses, typically used for receiving money.
/// </summary>
[JsonProperty(PropertyName = "externalAddresses")]
public IEnumerable<HdAddress> ExternalAddresses { get; set; }
/// <summary>
/// The list of internal addresses, typically used to receive change.
/// </summary>
[JsonProperty(PropertyName = "internalAddresses")]
public IEnumerable<HdAddress> InternalAddresses { get; set; }
}
/// <summary>
/// An Hd address.
/// </summary>
public class HdAddress
{
/// <summary>
/// Gets or sets the creation time.
/// </summary>
[JsonProperty(PropertyName = "creationTime")]
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset CreationTime { get; set; }
/// <summary>
/// The script pub key for this address.
/// </summary>
[JsonProperty(PropertyName = "scriptPubKey")]
public Script ScriptPubKey { get; set; }
/// <summary>
/// The Base58 representation of this address.
/// </summary>
[JsonProperty(PropertyName = "address")]
public BitcoinAddress Address { get; set; }
/// <summary>
/// A path to the address as defined in BIP44.
/// </summary>
[JsonProperty(PropertyName = "hdPath")]
public string HdPath { get; set; }
/// <summary>
/// A list detailing which blocks have been scanned for this address.
/// </summary>
[JsonIgnore]
public SortedList<int, int> BlocksScanned { get; set; }
/// <summary>
/// A list of transactions involving this address.
/// </summary>
[JsonProperty(PropertyName = "transactions")]
public IEnumerable<TransactionData> Transactions { get; set; }
}
/// <summary>
/// An object containing transaction data.
/// </summary>
public class TransactionData
{
/// <summary>
/// Transaction id.
/// </summary>
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
/// <summary>
/// The transaction amount.
/// </summary>
[JsonProperty(PropertyName = "amount")]
public Money Amount { get; set; }
/// <summary>
/// The height of the block including this transaction.
/// </summary>
[JsonProperty(PropertyName = "blockHeight")]
public int BlockHeight { get; set; }
/// <summary>
/// Whether this transaction has been confirmed or not.
/// </summary>
[JsonProperty(PropertyName = "confirmed")]
public bool Confirmed { get; set; }
/// <summary>
/// Gets or sets the creation time.
/// </summary>
[JsonProperty(PropertyName = "creationTime")]
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset CreationTime { get; set; }
}
}
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using Breeze.Wallet.Helpers; using Breeze.Wallet.Helpers;
using Breeze.Wallet.Models; using Breeze.Wallet.Models;
using NBitcoin; using NBitcoin;
...@@ -12,11 +14,16 @@ namespace Breeze.Wallet ...@@ -12,11 +14,16 @@ namespace Breeze.Wallet
/// </summary> /// </summary>
public class WalletManager : IWalletManager public class WalletManager : IWalletManager
{ {
/// <inheritdoc /> public List<Wallet> Wallets { get; }
public Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null)
public WalletManager()
{ {
string walletFilePath = Path.Combine(folderPath, $"{name}.json"); this.Wallets = new List<Wallet>();
}
/// <inheritdoc />
public Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null, CoinType coinType = CoinType.Bitcoin)
{
// for now the passphrase is set to be the password by default. // for now the passphrase is set to be the password by default.
if (passphrase == null) if (passphrase == null)
{ {
...@@ -29,7 +36,9 @@ namespace Breeze.Wallet ...@@ -29,7 +36,9 @@ namespace Breeze.Wallet
ExtKey extendedKey = mnemonic.DeriveExtKey(passphrase); ExtKey extendedKey = mnemonic.DeriveExtKey(passphrase);
// create a wallet file // create a wallet file
Wallet wallet = this.GenerateWalletFile(password, walletFilePath, WalletHelpers.GetNetwork(network), extendedKey); Wallet wallet = this.GenerateWalletFile(password, folderPath, name, WalletHelpers.GetNetwork(network), extendedKey, coinType);
this.Load(wallet);
return mnemonic; return mnemonic;
} }
...@@ -43,11 +52,13 @@ namespace Breeze.Wallet ...@@ -43,11 +52,13 @@ namespace Breeze.Wallet
// load the file from the local system // load the file from the local system
Wallet wallet = JsonConvert.DeserializeObject<Wallet>(File.ReadAllText(walletFilePath)); Wallet wallet = JsonConvert.DeserializeObject<Wallet>(File.ReadAllText(walletFilePath));
this.Load(wallet);
return wallet; return wallet;
} }
/// <inheritdoc /> /// <inheritdoc />
public Wallet RecoverWallet(string password, string folderPath, string name, string network, string mnemonic, string passphrase = null, DateTimeOffset? creationTime = null) public Wallet RecoverWallet(string password, string folderPath, string name, string network, string mnemonic, string passphrase = null, CoinType coinType = CoinType.Bitcoin, DateTimeOffset? creationTime = null)
{ {
// for now the passphrase is set to be the password by default. // for now the passphrase is set to be the password by default.
if (passphrase == null) if (passphrase == null)
...@@ -59,9 +70,54 @@ namespace Breeze.Wallet ...@@ -59,9 +70,54 @@ namespace Breeze.Wallet
ExtKey extendedKey = (new Mnemonic(mnemonic)).DeriveExtKey(passphrase); ExtKey extendedKey = (new Mnemonic(mnemonic)).DeriveExtKey(passphrase);
// create a wallet file // create a wallet file
Wallet wallet = this.GenerateWalletFile(password, Path.Combine(folderPath, $"{name}.json"), WalletHelpers.GetNetwork(network), extendedKey, creationTime); Wallet wallet = this.GenerateWalletFile(password, folderPath, name, WalletHelpers.GetNetwork(network), extendedKey, coinType, creationTime);
this.Load(wallet);
return wallet; return wallet;
} }
/// <inheritdoc />
public string CreateNewAccount(string walletName, string accountName)
{
Wallet wallet = this.Wallets.SingleOrDefault(w => w.Name == walletName);
if (wallet == null)
{
throw new Exception($"No wallet with name {walletName} could be found.");
}
var lastAccountIndex = 0;
// validate account creation
if (wallet.Accounts.Any())
{
// check account with same name doesn't already exists
if (wallet.Accounts.Any(a => a.Name == accountName))
{
throw new Exception($"Account with name '{accountName}' already exists in '{walletName}'.");
}
// check account at index i - 1 contains transactions.
lastAccountIndex = wallet.Accounts.Max(a => a.Index);
HdAccount previousAccount = wallet.Accounts.Single(a => a.Index == lastAccountIndex);
if (!previousAccount.ExternalAddresses.Any(addresses => addresses.Transactions.Any()) && !previousAccount.InternalAddresses.Any(addresses => addresses.Transactions.Any()))
{
throw new Exception($"Cannot create new account '{accountName}' in '{walletName}' if the previous account '{previousAccount.Name}' has not been used.");
}
}
wallet.Accounts = wallet.Accounts.Concat(new[] {new HdAccount
{
Index = lastAccountIndex + 1,
ExternalAddresses = new List<HdAddress>(),
InternalAddresses = new List<HdAddress>(),
Name = accountName,
CreationTime = DateTimeOffset.Now
}});
this.SaveToFile(wallet);
return accountName;
}
public WalletGeneralInfoModel GetGeneralInfo(string name) public WalletGeneralInfoModel GetGeneralInfo(string name)
{ {
...@@ -105,23 +161,31 @@ namespace Breeze.Wallet ...@@ -105,23 +161,31 @@ namespace Breeze.Wallet
/// Generates the wallet file. /// Generates the wallet file.
/// </summary> /// </summary>
/// <param name="password">The password used to encrypt sensitive info.</param> /// <param name="password">The password used to encrypt sensitive info.</param>
/// <param name="walletFilePath">The location of the wallet file.</param> /// <param name="folderPath">The folder where the wallet will be generated.</param>
/// <param name="name">The name of the wallet.</param>
/// <param name="network">The network this wallet is for.</param> /// <param name="network">The network this wallet is for.</param>
/// <param name="extendedKey">The root key used to generate keys.</param> /// <param name="extendedKey">The root key used to generate keys.</param>
/// <param name="coinType">The type of coin for which this wallet is created.</param>
/// <param name="creationTime">The time this wallet was created.</param> /// <param name="creationTime">The time this wallet was created.</param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="System.NotSupportedException"></exception> /// <exception cref="System.NotSupportedException"></exception>
private Wallet GenerateWalletFile(string password, string walletFilePath, Network network, ExtKey extendedKey, DateTimeOffset? creationTime = null) private Wallet GenerateWalletFile(string password, string folderPath, string name, Network network, ExtKey extendedKey, CoinType coinType = CoinType.Bitcoin, DateTimeOffset? creationTime = null)
{ {
string walletFilePath = Path.Combine(folderPath, $"{name}.json");
if (File.Exists(walletFilePath)) if (File.Exists(walletFilePath))
throw new InvalidOperationException($"Wallet already exists at {walletFilePath}"); throw new InvalidOperationException($"Wallet already exists at {walletFilePath}");
Wallet walletFile = new Wallet Wallet walletFile = new Wallet
{ {
Name = name,
EncryptedSeed = extendedKey.PrivateKey.GetEncryptedBitcoinSecret(password, network).ToWif(), EncryptedSeed = extendedKey.PrivateKey.GetEncryptedBitcoinSecret(password, network).ToWif(),
ChainCode = extendedKey.ChainCode, ChainCode = extendedKey.ChainCode,
CreationTime = creationTime ?? DateTimeOffset.Now, CreationTime = creationTime ?? DateTimeOffset.Now,
Network = network Network = network,
Accounts = new List<HdAccount>(),
CoinType = coinType,
WalletFilePath = walletFilePath
}; };
// create a folder if none exists and persist the file // create a folder if none exists and persist the file
...@@ -130,5 +194,26 @@ namespace Breeze.Wallet ...@@ -130,5 +194,26 @@ namespace Breeze.Wallet
return walletFile; return walletFile;
} }
/// <summary>
/// Saves the wallet into the file system.
/// </summary>
/// <param name="wallet">The wallet to save.</param>
private void SaveToFile(Wallet wallet)
{
File.WriteAllText(wallet.WalletFilePath, JsonConvert.SerializeObject(wallet, Formatting.Indented));
}
/// <summary>
/// Loads the wallet to be used by the manager.
/// </summary>
/// <param name="wallet">The wallet to load.</param>
private void Load(Wallet wallet)
{
if (this.Wallets.All(w => w.Name != wallet.Name))
{
this.Wallets.Add(wallet);
}
}
} }
} }
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