Commit b0838544 authored by Jeremy Bokobza's avatar Jeremy Bokobza Committed by GitHub

Merge pull request #34 from bokobza/feature/walletmanager

Creates accounts and create addresses
parents f24e1da0 a48af926
...@@ -61,6 +61,8 @@ POST /wallet/create - Creates the wallet ...@@ -61,6 +61,8 @@ POST /wallet/create - Creates the wallet
POST /wallet/load - Loads the wallet and starts syncing POST /wallet/load - Loads the wallet and starts syncing
POST /wallet/recover - Recovers the wallet POST /wallet/recover - Recovers the wallet
DELETE /wallet - Deletes the wallet DELETE /wallet - Deletes the wallet
POST /wallet/account
POST /wallet/address
``` ```
## Syncing ## Syncing
...@@ -165,6 +167,35 @@ Cannot check if the password is good or not. If the password is wrong it'll reco ...@@ -165,6 +167,35 @@ Cannot check if the password is good or not. If the password is wrong it'll reco
## DELETE /wallet - Deletes the wallet ## DELETE /wallet - Deletes the wallet
Works as expected. Works as expected.
## POST /wallet/account - Adds an account to the wallet
### Parameters
```
{
"walletName": "myFirstWallet",
"accountName": "account one",
"password": "123456",
"coinType": 105
}
```
### Responses
```
"account one"
```
## POST /wallet/address - Adds an address to an account
### Parameters
```
{
"walletName": "myFirstWallet",
"accountName": "account one",
"coinType": 0
}
```
### Responses
```
"1HDypWxXWZC5KXK259EHMnrWaa2youy7Mj"
```
## GET /wallet/mempool/?allow=[true/false] - Allows or disallows mempool syncing ## GET /wallet/mempool/?allow=[true/false] - Allows or disallows mempool syncing
Works as expected. Works as expected.
......
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)).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, 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);
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"variables": [], "variables": [],
"info": { "info": {
"name": "Wallet", "name": "Wallet",
"_postman_id": "25ac0bc5-23f5-e00d-932f-a8fde7787e7a", "_postman_id": "5eec0912-fcf0-50f5-05a2-0835fa13c670",
"description": "Requests relating to operations on the wallet", "description": "Requests relating to operations on the wallet",
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
}, },
...@@ -206,6 +206,66 @@ ...@@ -206,6 +206,66 @@
"description": "Gets all the wallets files stored in the default folder" "description": "Gets all the wallets files stored in the default folder"
}, },
"response": [] "response": []
},
{
"name": "New account for non-existant wallet fails",
"request": {
"url": "http://localhost:5000/api/v1/wallet/account",
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"description": ""
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"walletName\": \"myFirstWallet\",\n\t\"accountName\": \"account one\"\n}"
},
"description": ""
},
"response": []
},
{
"name": "Create new account for wallet",
"request": {
"url": "http://localhost:5000/api/v1/wallet/account",
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"description": ""
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"walletName\": \"myFirstWallet\",\n\t\"accountName\": \"account one\",\n\t\"password\": \"123456\",\n\t\"coinType\": 105\n}"
},
"description": ""
},
"response": []
},
{
"name": "Create new address for wallet",
"request": {
"url": "http://localhost:5000/api/v1/wallet/address",
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"description": ""
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"walletName\": \"myFirstWallet\",\n\t\"accountName\": \"account one\",\n\t\"coinType\": 0\n}"
},
"description": ""
},
"response": []
} }
] ]
} }
\ No newline at end of file
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ConcurrentHashSet" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="1.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="1.0.3" />
<PackageReference Include="Stratis.Bitcoin" Version="1.0.1.5-alpha" /> <PackageReference Include="Stratis.Bitcoin" Version="1.0.1.5-alpha" />
</ItemGroup> </ItemGroup>
......
...@@ -8,6 +8,7 @@ using Breeze.Wallet.Errors; ...@@ -8,6 +8,7 @@ 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;
using NBitcoin;
namespace Breeze.Wallet.Controllers namespace Breeze.Wallet.Controllers
{ {
...@@ -17,11 +18,11 @@ namespace Breeze.Wallet.Controllers ...@@ -17,11 +18,11 @@ namespace Breeze.Wallet.Controllers
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
public class WalletController : Controller public class WalletController : Controller
{ {
private readonly IWalletWrapper walletWrapper; private readonly IWalletManager walletManager;
public WalletController(IWalletWrapper walletWrapper) public WalletController(IWalletManager walletManager)
{ {
this.walletWrapper = walletWrapper; this.walletManager = walletManager;
} }
/// <summary> /// <summary>
...@@ -44,9 +45,9 @@ namespace Breeze.Wallet.Controllers ...@@ -44,9 +45,9 @@ namespace Breeze.Wallet.Controllers
{ {
// get the wallet folder // get the wallet folder
DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath); DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath);
Mnemonic mnemonic = this.walletManager.CreateWallet(request.Password, walletFolder.FullName, request.Name, request.Network);
var mnemonic = this.walletWrapper.Create(request.Password, walletFolder.FullName, request.Name, request.Network); return this.Json(mnemonic.ToString());
return this.Json(mnemonic);
} }
catch (InvalidOperationException e) catch (InvalidOperationException e)
{ {
...@@ -76,9 +77,13 @@ namespace Breeze.Wallet.Controllers ...@@ -76,9 +77,13 @@ namespace Breeze.Wallet.Controllers
// get the wallet folder // get the wallet folder
DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath); DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath);
var wallet = this.walletWrapper.Load(request.Password, walletFolder.FullName, request.Name); Wallet wallet = this.walletManager.LoadWallet(request.Password, walletFolder.FullName, request.Name);
return this.Json(wallet); return this.Json(new WalletModel
{
Network = wallet.Network.Name,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = wallet.WalletFilePath
});
} }
catch (FileNotFoundException e) catch (FileNotFoundException e)
{ {
...@@ -116,8 +121,13 @@ namespace Breeze.Wallet.Controllers ...@@ -116,8 +121,13 @@ namespace Breeze.Wallet.Controllers
// get the wallet folder // get the wallet folder
DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath); DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath);
var wallet = this.walletWrapper.Recover(request.Password, walletFolder.FullName, request.Name, request.Network, request.Mnemonic); Wallet wallet = this.walletManager.RecoverWallet(request.Password, walletFolder.FullName, request.Name, request.Network, request.Mnemonic);
return this.Json(wallet); return this.Json(new WalletModel
{
Network = wallet.Network.Name,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = wallet.WalletFilePath
});
} }
catch (InvalidOperationException e) catch (InvalidOperationException e)
{ {
...@@ -153,7 +163,7 @@ namespace Breeze.Wallet.Controllers ...@@ -153,7 +163,7 @@ namespace Breeze.Wallet.Controllers
try try
{ {
return this.Json(this.walletWrapper.GetGeneralInfo(model.Name)); return this.Json(this.walletManager.GetGeneralInfo(model.Name));
} }
catch (Exception e) catch (Exception e)
...@@ -180,7 +190,7 @@ namespace Breeze.Wallet.Controllers ...@@ -180,7 +190,7 @@ namespace Breeze.Wallet.Controllers
try try
{ {
return this.Json(this.walletWrapper.GetHistory(model.Name)); return this.Json(this.walletManager.GetHistory(model.Name));
} }
catch (Exception e) catch (Exception e)
...@@ -207,7 +217,7 @@ namespace Breeze.Wallet.Controllers ...@@ -207,7 +217,7 @@ namespace Breeze.Wallet.Controllers
try try
{ {
return this.Json(this.walletWrapper.GetBalance(model.Name)); return this.Json(this.walletManager.GetBalance(model.Name));
} }
catch (Exception e) catch (Exception e)
...@@ -234,7 +244,7 @@ namespace Breeze.Wallet.Controllers ...@@ -234,7 +244,7 @@ namespace Breeze.Wallet.Controllers
try try
{ {
return this.Json(this.walletWrapper.BuildTransaction(request.Password, request.Address, request.Amount, request.FeeType, request.AllowUnconfirmed)); return this.Json(this.walletManager.BuildTransaction(request.Password, request.Address, request.Amount, request.FeeType, request.AllowUnconfirmed));
} }
catch (Exception e) catch (Exception e)
...@@ -261,7 +271,7 @@ namespace Breeze.Wallet.Controllers ...@@ -261,7 +271,7 @@ namespace Breeze.Wallet.Controllers
try try
{ {
var result = this.walletWrapper.SendTransaction(request.Hex); var result = this.walletManager.SendTransaction(request.Hex);
if (result) if (result)
{ {
return this.Ok(); return this.Ok();
...@@ -301,6 +311,58 @@ namespace Breeze.Wallet.Controllers ...@@ -301,6 +311,58 @@ 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.CoinType, request.AccountName, request.Password);
return this.Json(result);
}
catch (Exception e)
{
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
}
}
/// <summary>
/// Creates a new address for a wallet.
/// </summary>
/// <returns>An address in Base58 format.</returns>
[Route("address")]
[HttpPost]
public IActionResult CreateNewAddress([FromBody]CreateAddressModel 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.CreateNewAddress(request.WalletName, request.CoinType, 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>
......
using System; using System;
using Breeze.Wallet.Models;
using NBitcoin; using NBitcoin;
namespace Breeze.Wallet namespace Breeze.Wallet
...@@ -12,36 +13,72 @@ namespace Breeze.Wallet ...@@ -12,36 +13,72 @@ namespace Breeze.Wallet
/// Creates a wallet and persist it as a file on the local system. /// Creates a wallet and persist it as a file on the local system.
/// </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="passphrase">The passphrase used in the seed.</param> /// <param name="folderPath">The folder where the wallet will be saved.</param>
/// <param name="walletFilePath">The path where the wallet file will be created.</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>
/// <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 walletFilePath, Network network, string passphrase = null); Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null);
/// <summary> /// <summary>
/// Loads a wallet from a file. /// Loads a wallet from a file.
/// </summary> /// </summary>
/// <param name="password">The password used to encrypt sensitive info.</param> /// <param name="password">The user's password.</param>
/// <param name="walletFilePath">The location of the wallet file.</param> /// <param name="folderPath">The folder where the wallet will be loaded.</param>
/// <param name="name">The name of the wallet.</param>
/// <returns>The wallet.</returns> /// <returns>The wallet.</returns>
Wallet LoadWallet(string password, string walletFilePath); Wallet LoadWallet(string password, string folderPath, string name);
/// <summary> /// <summary>
/// Recovers a wallet. /// Recovers a wallet.
/// </summary> /// </summary>
/// <param name="mnemonic">A mnemonic defining the wallet's seed used to generate addresses.</param> /// <param name="password">The user's password.</param>
/// <param name="password">The password used to encrypt sensitive info.</param> /// <param name="folderPath">The folder where the wallet will be loaded.</param>
/// <param name="walletFilePath">The location of the wallet file.</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 in which to creae this 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="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(Mnemonic mnemonic, string password, string walletFilePath, Network network, string passphrase = null, DateTimeOffset? creationTime = null); Wallet RecoverWallet(string password, string folderPath, string name, string network, string mnemonic, string passphrase = null, DateTimeOffset? creationTime = null);
/// <summary> /// <summary>
/// Deleted a wallet. /// Deleted a wallet.
/// </summary> /// </summary>
/// <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="coinType">the type of coin for which to create an account.</param>
/// <param name="accountName">The name by which this account will be identified.</param>
/// <param name="password">The password used to decrypt the private key.</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, CoinType coinType, string accountName, string password);
/// <summary>
/// Creates the new address.
/// </summary>
/// <param name="walletName">The name of the wallet in which this address will be created.</param>
/// <param name="coinType">the type of coin for which to create an account.</param>
/// <param name="accountName">The name of the account in which this address will be created.</param>
/// <returns>The new address, in Base58 format.</returns>
string CreateNewAddress(string walletName, CoinType coinType, string accountName);
WalletGeneralInfoModel GetGeneralInfo(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;
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 type of coin this account contains.
/// </summary>
[Required]
public CoinType CoinType { get; set; }
/// <summary>
/// The name of the account.
/// </summary>
[Required]
public string AccountName { get; set; }
/// <summary>
/// The password for this wallet.
/// </summary>
[Required]
public string Password { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace Breeze.Wallet.Models
{
public class CreateAddressModel
{
/// <summary>
/// The name of the wallet in which to create the address.
/// </summary>
[Required]
public string WalletName { get; set; }
/// <summary>
/// The type of coin this account contains.
/// </summary>
[Required]
public CoinType CoinType { get; set; }
/// <summary>
/// The name of the account in which to create the address.
/// </summary>
[Required]
public string AccountName { get; set; }
}
}
//from https://github.com/brianchance/MonoTouchMVVMCrossValidationTester/blob/master/Validation.Core/ConcurrentObservableDictionary.cs
//modified
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace System.Collections.ObjectModel
{
public class ConcurrentObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
private const string CountString = "Count";
private const string IndexerName = "Item[]";
private const string KeysName = "Keys";
private const string ValuesName = "Values";
private readonly object Lock = new object();
protected ConcurrentDictionary<TKey, TValue> ConcurrentDictionary { get; private set; }
#region Constructors
public ConcurrentObservableDictionary()
{
ConcurrentDictionary = new ConcurrentDictionary<TKey, TValue>();
}
public ConcurrentObservableDictionary(ConcurrentDictionary<TKey, TValue> dictionary)
{
ConcurrentDictionary = new ConcurrentDictionary<TKey, TValue>(dictionary);
}
public ConcurrentObservableDictionary(IEqualityComparer<TKey> comparer)
{
ConcurrentDictionary = new ConcurrentDictionary<TKey, TValue>(comparer);
}
public ConcurrentObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
ConcurrentDictionary = new ConcurrentDictionary<TKey, TValue>(dictionary, comparer);
}
#endregion
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value) => Insert(key, value, true);
public bool ContainsKey(TKey key) => ConcurrentDictionary.ContainsKey(key);
public ICollection<TKey> Keys => ConcurrentDictionary.Keys;
public bool Remove(TKey key) => Remove(key, suppressNotifications: false);
private bool Remove(TKey key, bool suppressNotifications)
{
lock(Lock)
{
TValue value;
var ret = ConcurrentDictionary.TryRemove(key, out value);
if(ret && !suppressNotifications) OnCollectionChanged();
return ret;
}
}
public bool TryGetValue(TKey key, out TValue value) => ConcurrentDictionary.TryGetValue(key, out value);
public ICollection<TValue> Values => ConcurrentDictionary.Values;
public TValue this[TKey key]
{
get
{
TValue value;
return TryGetValue(key, out value) ? value : default(TValue);
}
set
{
Insert(key, value, false);
}
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
public void Add(KeyValuePair<TKey, TValue> item) => Insert(item.Key, item.Value, true);
public void Clear()
{
lock(Lock)
{
if (ConcurrentDictionary.Count > 0)
{
ConcurrentDictionary.Clear();
OnCollectionChanged();
}
}
}
public bool Contains(KeyValuePair<TKey, TValue> item) => ConcurrentDictionary.Contains(item);
/// <summary>
/// NotImplementedException
/// </summary>
/// <param name="array"></param>
/// <param name="arrayIndex"></param>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
/// <summary>
/// NotImplementedException
/// </summary>
public bool IsReadOnly
{
get { throw new NotImplementedException(); }
}
public int Count => ConcurrentDictionary.Count;
public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key);
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => ConcurrentDictionary.GetEnumerator();
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)ConcurrentDictionary).GetEnumerator();
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void AddOrReplace(TKey key, TValue value)
{
if (ContainsKey(key))
{
Remove(key, suppressNotifications: true);
Add(key, value);
}
else
{
Add(key, value);
}
}
/// <summary>
/// NotImplementedException
/// </summary>
/// <param name="items"></param>
public void AddRange(IDictionary<TKey, TValue> items)
{
throw new NotImplementedException();
}
private void Insert(TKey key, TValue value, bool add)
{
lock(Lock)
{
if (key == null) throw new ArgumentNullException(nameof(key));
TValue item;
if (ConcurrentDictionary.TryGetValue(key, out item))
{
if (add) throw new ArgumentException("An item with the same key has already been added.");
if (Equals(item, value)) return;
ConcurrentDictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item));
OnPropertyChanged(key.ToString());
}
else
{
ConcurrentDictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value));
OnPropertyChanged(key.ToString());
}
}
}
private void OnPropertyChanged()
{
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnPropertyChanged(KeysName);
OnPropertyChanged(ValuesName);
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnCollectionChanged()
{
OnPropertyChanged();
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem)
{
OnPropertyChanged();
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, changedItem, 0));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem, 0));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems)
{
OnPropertyChanged();
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, newItems, 0));
}
}
}
\ No newline at end of file
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Linq;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.Specialized;
using ConcurrentCollections;
namespace System.Collections.ObjectModel
{
public class ConcurrentObservableHashSet<T> : INotifyCollectionChanged, IReadOnlyCollection<T>
{
protected ConcurrentHashSet<T> ConcurrentHashSet { get; }
private readonly object Lock = new object();
public ConcurrentObservableHashSet()
{
ConcurrentHashSet = new ConcurrentHashSet<T>();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void OnCollectionChanged()
{
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<T> GetEnumerator() => ConcurrentHashSet.GetEnumerator();
public bool TryAdd(T item)
{
lock(Lock)
{
if(ConcurrentHashSet.Add(item))
{
OnCollectionChanged();
return true;
}
return false;
}
}
public void Clear()
{
lock(Lock)
{
if(ConcurrentHashSet.Count > 0)
{
ConcurrentHashSet.Clear();
OnCollectionChanged();
}
}
}
public bool Contains(T item) => ConcurrentHashSet.Contains(item);
public bool TryRemove(T item)
{
lock(Lock)
{
if(ConcurrentHashSet.TryRemove(item))
{
OnCollectionChanged();
return true;
}
return false;
}
}
public int Count => ConcurrentHashSet.Count;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace HBitcoin.Models
{
public struct Height : IEquatable<Height>, IEquatable<int>, IComparable<Height>, IComparable<int>
{
public HeightType Type { get; }
private readonly int _value;
public int Value
{
get
{
if (Type == HeightType.Chain)
return _value;
if (Type == HeightType.MemPool)
return int.MaxValue - 1;
//if(Type == HeightType.Unknown)
return int.MaxValue;
}
}
public static Height MemPool => new Height(HeightType.MemPool);
public static Height Unknown => new Height(HeightType.Unknown);
public Height(int height)
{
if(height < 0) throw new ArgumentException($"{nameof(height)} : {height} cannot be < 0");
if(height == int.MaxValue) Type = HeightType.Unknown;
else if(height == int.MaxValue - 1) Type = HeightType.MemPool;
else Type = HeightType.Chain;
_value = height;
}
public Height(string heightOrHeightType)
{
var trimmed = heightOrHeightType.Trim();
if (trimmed == HeightType.MemPool.ToString())
this = MemPool;
else if (trimmed == HeightType.Unknown.ToString())
this = Unknown;
else this = new Height(int.Parse(trimmed));
}
public Height(HeightType type)
{
if(type == HeightType.Chain) throw new NotSupportedException($"For {type} height must be specified");
Type = type;
if (Type == HeightType.MemPool)
_value = int.MaxValue - 1;
else _value = int.MaxValue; // HeightType.Unknown
}
public override string ToString()
{
if(Type == HeightType.Chain) return Value.ToString();
else return Type.ToString();
}
#region EqualityAndComparison
public override bool Equals(object obj) => obj is Height && this == (Height) obj;
public bool Equals(Height other) => this == other;
public override int GetHashCode() => Value.GetHashCode();
public static bool operator ==(Height x, Height y) => x.Value == y.Value;
public static bool operator !=(Height x, Height y) => !(x == y);
public bool Equals(int other) => Value == other;
public static bool operator ==(int x, Height y) => x == y.Value;
public static bool operator ==(Height x, int y) => x.Value == y;
public static bool operator !=(int x, Height y) => !(x == y);
public static bool operator !=(Height x, int y) => !(x == y);
public int CompareTo(Height other) => Value.CompareTo(other.Value);
public int CompareTo(int other)
{
if (Value > other) return -1;
if (Value == other) return 0;
return 1;
}
public static bool operator >(Height x, Height y) => x.Value > y.Value;
public static bool operator <(Height x, Height y) => x.Value < y.Value;
public static bool operator >=(Height x, Height y) => x.Value >= y.Value;
public static bool operator <=(Height x, Height y) => x.Value <= y.Value;
public static bool operator >(int x, Height y) => x > y.Value;
public static bool operator >(Height x, int y) => x.Value > y;
public static bool operator <(int x, Height y) => x < y.Value;
public static bool operator <(Height x, int y) => x.Value < y;
public static bool operator >=(int x, Height y) => x >= y.Value;
public static bool operator <=(int x, Height y) => x <= y.Value;
public static bool operator >=(Height x, int y) => x.Value >= y;
public static bool operator <=(Height x, int y) => x.Value <= y;
#endregion
}
public enum HeightType
{
Chain,
MemPool,
Unknown
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using ConcurrentCollections;
using HBitcoin.Models;
using NBitcoin;
namespace HBitcoin.Models
{
public class SmartMerkleBlock : IEquatable<SmartMerkleBlock>, IComparable<SmartMerkleBlock>
{
#region Members
public Height Height { get; }
public MerkleBlock MerkleBlock { get; }
public IEnumerable<uint256> GetMatchedTransactions() => MerkleBlock.PartialMerkleTree.GetMatchedTransactions();
public uint TransactionCount => MerkleBlock.PartialMerkleTree.TransactionCount;
#endregion
#region Constructors
public SmartMerkleBlock()
{
}
public SmartMerkleBlock(Height height, Block block, params uint256[] interestedTransactionIds)
{
Height = height;
MerkleBlock = interestedTransactionIds == null || interestedTransactionIds.Length == 0 ? block.Filter() : block.Filter(interestedTransactionIds);
}
public SmartMerkleBlock(int height, Block block, params uint256[] interestedTransactionIds)
{
Height = new Height(height);
MerkleBlock = interestedTransactionIds == null || interestedTransactionIds.Length == 0 ? block.Filter() : block.Filter(interestedTransactionIds);
}
public SmartMerkleBlock(Height height, MerkleBlock merkleBlock)
{
Height = height;
MerkleBlock = merkleBlock;
}
#endregion
#region Formatting
public static byte[] ToBytes(SmartMerkleBlock smartMerkleBlock) =>
BitConverter.GetBytes(smartMerkleBlock.Height.Value) // 4bytes
.Concat(smartMerkleBlock.MerkleBlock.ToBytes())
.ToArray();
public byte[] ToBytes() => ToBytes(this);
public static SmartMerkleBlock FromBytes(byte[] bytes)
{
var heightBytes = bytes.Take(4).ToArray();
var merkleBlockBytes = bytes.Skip(4).ToArray();
var height = new Height(BitConverter.ToInt32(heightBytes, startIndex: 0));
// Bypass NBitcoin bug
var merkleBlock = new MerkleBlock();
if(!merkleBlock.ToBytes().SequenceEqual(merkleBlockBytes)) // if not default MerkleBlock
{
merkleBlock.FromBytes(merkleBlockBytes);
}
return new SmartMerkleBlock(height, merkleBlock);
}
#endregion
#region EqualityAndComparison
public override bool Equals(object obj) => obj is SmartMerkleBlock && this == (SmartMerkleBlock)obj;
public bool Equals(SmartMerkleBlock other) => this == other;
public override int GetHashCode()
{
var hash = Height.GetHashCode();
hash = hash ^ MerkleBlock.Header.GetHash().GetHashCode();
hash = hash ^ MerkleBlock.Header.HashPrevBlock.GetHashCode();
hash = hash ^ MerkleBlock.Header.HashMerkleRoot.GetHashCode();
foreach(uint256 txhash in GetMatchedTransactions())
hash = hash ^ txhash.GetHashCode();
return hash;
}
public static bool operator ==(SmartMerkleBlock x, SmartMerkleBlock y)
{
if (x.Height != y.Height)
return false;
if(x.MerkleBlock.Header.GetHash() != y.MerkleBlock.Header.GetHash())
return false;
if (x.MerkleBlock.Header.HashPrevBlock != y.MerkleBlock.Header.HashPrevBlock)
return false;
if (x.MerkleBlock.Header.HashMerkleRoot != y.MerkleBlock.Header.HashMerkleRoot)
return false;
if (x.TransactionCount != y.TransactionCount)
return false;
if(x.TransactionCount == 0) return true;
if (!x.GetMatchedTransactions().SequenceEqual(y.GetMatchedTransactions()))
return false;
return true;
}
public static bool operator !=(SmartMerkleBlock x, SmartMerkleBlock y) => !(x == y);
public int CompareTo(SmartMerkleBlock other) => Height.CompareTo(other.Height);
public static bool operator >(SmartMerkleBlock x, SmartMerkleBlock y) => x.Height > y.Height;
public static bool operator <(SmartMerkleBlock x, SmartMerkleBlock y) => x.Height < y.Height;
public static bool operator >=(SmartMerkleBlock x, SmartMerkleBlock y) => x.Height >= y.Height;
public static bool operator <=(SmartMerkleBlock x, SmartMerkleBlock y) => x.Height <= y.Height;
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using HBitcoin.Models;
using NBitcoin;
namespace HBitcoin.Models
{
public class SmartTransaction: IEquatable<SmartTransaction>
{
#region Members
public Height Height { get; }
public Transaction Transaction { get; }
public bool Confirmed => Height.Type == HeightType.Chain;
public uint256 GetHash() => Transaction.GetHash();
#endregion
#region Constructors
public SmartTransaction()
{
}
public SmartTransaction(Transaction transaction, Height height)
{
Height = height;
Transaction = transaction;
}
#endregion
#region Equality
public bool Equals(SmartTransaction other) => GetHash().Equals(other.GetHash());
public bool Equals(Transaction other) => GetHash().Equals(other.GetHash());
public override bool Equals(object obj)
{
bool rc = false;
if (obj is SmartTransaction)
{
var transaction = (SmartTransaction)obj;
rc = GetHash().Equals(transaction.GetHash());
}
else if (obj is Transaction)
{
var transaction = (Transaction)obj;
rc = GetHash().Equals(transaction.GetHash());
}
return rc;
}
public override int GetHashCode()
{
return GetHash().GetHashCode();
}
public static bool operator !=(SmartTransaction tx1, SmartTransaction tx2)
{
return !(tx1 == tx2);
}
public static bool operator ==(SmartTransaction tx1, SmartTransaction tx2)
{
bool rc;
if(ReferenceEquals(tx1, tx2)) rc = true;
else if((object) tx1 == null || (object) tx2 == null)
{
rc = false;
}
else
{
rc = tx1.GetHash().Equals(tx2.GetHash());
}
return rc;
}
public static bool operator ==(Transaction tx1, SmartTransaction tx2)
{
bool rc;
if ((object)tx1 == null || (object)tx2 == null)
{
rc = false;
}
else
{
rc = tx1.GetHash().Equals(tx2.GetHash());
}
return rc;
}
public static bool operator !=(Transaction tx1, SmartTransaction tx2)
{
return !(tx1 == tx2);
}
public static bool operator ==(SmartTransaction tx1, Transaction tx2)
{
bool rc;
if ((object)tx1 == null || (object)tx2 == null)
{
rc = false;
}
else
{
rc = tx1.GetHash().Equals(tx2.GetHash());
}
return rc;
}
public static bool operator !=(SmartTransaction tx1, Transaction tx2)
{
return !(tx1 == tx2);
}
#endregion
}
}
This diff is collapsed.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using HBitcoin.Models;
using NBitcoin;
namespace HBitcoin.FullBlockSpv
{
public class UnprocessedBlockBuffer
{
public const int Capacity = 50;
private readonly ConcurrentObservableDictionary<Height, Block> _blocks = new ConcurrentObservableDictionary<Height, Block>();
public event EventHandler HaveBlocks;
private void OnHaveBlocks() => HaveBlocks?.Invoke(this, EventArgs.Empty);
/// <summary>
///
/// </summary>
/// <param name="height"></param>
/// <param name="block"></param>
/// <returns>false if we have more than UnprocessedBlockBuffer.Capacity blocks in memory already</returns>
public bool TryAddOrReplace(Height height, Block block)
{
if (_blocks.Count > Capacity) return false;
_blocks.AddOrReplace(height, block);
if (_blocks.Count == 1) OnHaveBlocks();
return true;
}
public bool Full => _blocks.Count == Capacity;
public Height BestHeight => _blocks.Count == 0 ? Height.Unknown : _blocks.Keys.Max();
/// <summary>
///
/// </summary>
/// <returns>false if empty</returns>
public bool TryGetAndRemoveOldest(out Height height, out Block block)
{
height = Height.Unknown;
block = default(Block);
if(_blocks.Count == 0) return false;
height = _blocks.Keys.Min();
block = _blocks[height];
_blocks.Remove(height);
return true;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace HBitcoin
{
internal static class Util
{
internal static byte[][] Separate(byte[] source, byte[] separator)
{
var Parts = new List<byte[]>();
var Index = 0;
byte[] Part;
for (var I = 0; I < source.Length; ++I)
{
if (Equals(source, separator, I))
{
Part = new byte[I - Index];
Array.Copy(source, Index, Part, 0, Part.Length);
Parts.Add(Part);
Index = I + separator.Length;
I += separator.Length - 1;
}
}
Part = new byte[source.Length - Index];
Array.Copy(source, Index, Part, 0, Part.Length);
Parts.Add(Part);
return Parts.ToArray();
}
private static bool Equals(byte[] source, byte[] separator, int index)
{
for (int i = 0; i < separator.Length; ++i)
if (index + i >= source.Length || source[index + i] != separator[i])
return false;
return true;
}
/// <summary>
/// Splits an array into several smaller arrays.
/// </summary>
/// <typeparam name="T">The type of the array.</typeparam>
/// <param name="array">The array to split.</param>
/// <param name="size">The size of the smaller arrays.</param>
/// <returns>An array containing smaller arrays.</returns>
public static IEnumerable<IEnumerable<T>> Split<T>(T[] array, int size)
{
for (var i = 0; i < (float)array.Length / size; i++)
{
yield return array.Skip(i * size).Take(size);
}
}
}
}
using System; using System;
using System.Collections.Generic;
using Breeze.Wallet.JsonConverters;
using NBitcoin; using NBitcoin;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace Breeze.Wallet namespace Breeze.Wallet
{ {
...@@ -8,29 +12,210 @@ namespace Breeze.Wallet ...@@ -8,29 +12,210 @@ 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>
/// The seed for this wallet, password encrypted.
/// </summary>
[JsonProperty(PropertyName = "encryptedSeed")]
public string EncryptedSeed { get; set; }
/// <summary> /// <summary>
/// The chain code. /// The chain code.
/// </summary> /// </summary>
[JsonProperty(PropertyName = "chainCode")]
[JsonConverter(typeof(ByteArrayConverter))]
public byte[] ChainCode { get; set; } public byte[] ChainCode { get; set; }
/// <summary> /// <summary>
/// The network this wallet is for. /// The network this wallet is for.
/// </summary> /// </summary>
[JsonProperty(PropertyName = "network")]
[JsonConverter(typeof(NetworkConverter))]
public Network Network { get; set; } public Network Network { get; set; }
/// <summary> /// <summary>
/// The time this wallet was created. /// The time this wallet was created.
/// </summary> /// </summary>
[JsonProperty(PropertyName = "creationTime")]
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset CreationTime { get; set; } public DateTimeOffset CreationTime { get; set; }
/// <summary> /// <summary>
/// The location of the wallet file on the local system. /// The location of the wallet file on the local system.
/// </summary> /// </summary>
[JsonProperty(PropertyName = "walletFilePath")]
public string WalletFilePath { get; set; } public string WalletFilePath { get; set; }
/// <summary> /// <summary>
/// The key used to generate keys. /// The root of the accounts tree.
/// </summary>
[JsonProperty(PropertyName = "accountsRoot")]
public IEnumerable<AccountRoot> AccountsRoot { get; set; }
}
/// <summary>
/// The root for the accounts for any type of coins.
/// </summary>
public class AccountRoot
{
/// <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 name of this account.
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// An extended pub key used to generate addresses.
/// </summary>
[JsonProperty(PropertyName = "extPubKey")]
public string ExtendedPubKey { 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>
/// The index of the address.
/// </summary>
[JsonProperty(PropertyName = "index")]
public int Index { get; set; }
/// <summary>
/// Gets or sets the creation time.
/// </summary> /// </summary>
public ExtKey ExtendedKey { get; set; } [JsonProperty(PropertyName = "creationTime")]
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset CreationTime { get; set; }
/// <summary>
/// The script pub key for this address.
/// </summary>
[JsonProperty(PropertyName = "scriptPubKey")]
[JsonConverter(typeof(ScriptJsonConverter))]
public Script ScriptPubKey { get; set; }
/// <summary>
/// The Base58 representation of this address.
/// </summary>
[JsonProperty(PropertyName = "address")]
public string 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
...@@ -41,8 +41,7 @@ namespace Breeze.Wallet ...@@ -41,8 +41,7 @@ namespace Breeze.Wallet
.AddFeature<WalletFeature>() .AddFeature<WalletFeature>()
.FeatureServices(services => .FeatureServices(services =>
{ {
services.AddTransient<IWalletWrapper, WalletWrapper>(); services.AddSingleton<ITrackerWrapper, TrackerWrapper>();
services.AddTransient<ITrackerWrapper, TrackerWrapper>();
services.AddSingleton<IWalletManager, WalletManager>(); services.AddSingleton<IWalletManager, WalletManager>();
services.AddSingleton<WalletController>(); services.AddSingleton<WalletController>();
}); });
......
using System;
using NBitcoin;
using Newtonsoft.Json;
using Breeze.Wallet.JsonConverters;
namespace Breeze.Wallet
{
/// <summary>
/// An object representing a wallet on a local file system.
/// </summary>
public class WalletFile
{
/// <summary>
/// The seed for this wallet, password encrypted.
/// </summary>
[JsonProperty(PropertyName = "encryptedSeed")]
public string EncryptedSeed { get; set; }
/// <summary>
/// The chain code.
/// </summary>
[JsonProperty(PropertyName = "chainCode")]
[JsonConverter(typeof(ByteArrayConverter))]
public byte[] ChainCode { get; set; }
/// <summary>
/// The network this wallet is for.
/// </summary>
[JsonProperty(PropertyName = "network")]
[JsonConverter(typeof(NetworkConverter))]
public Network Network { get; set; }
/// <summary>
/// The time this wallet was created.
/// </summary>
[JsonProperty(PropertyName = "creationTime")]
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset CreationTime { get; set; }
}
}
This diff is collapsed.
using Breeze.Wallet.Models;
using NBitcoin;
namespace Breeze.Wallet.Wrappers
{
/// <summary>
/// An interface enabling wallet operations.
/// </summary>
public interface IWalletWrapper
{
string Create(string password, string folderPath, string name, string network);
WalletModel Load(string password, string folderPath, string name);
WalletModel Recover(string password, string folderPath, string name, string network, string mnemonic);
WalletGeneralInfoModel GetGeneralInfo(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;
using System.IO;
using System.Linq;
using Breeze.Wallet.Helpers;
using Breeze.Wallet.Models;
using NBitcoin;
namespace Breeze.Wallet.Wrappers
{
/// <summary>
/// An implementation of the <see cref="IWalletWrapper"/> interface.
/// </summary>
public class WalletWrapper : IWalletWrapper
{
private readonly IWalletManager walletManager;
public WalletWrapper(IWalletManager walletManager)
{
this.walletManager = walletManager;
}
/// <summary>
/// Creates a wallet on the local device.
/// </summary>
/// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the wallet will be saved.</param>
/// <param name="name">The name of the wallet.</param>
/// <param name="network">The network for which to create a wallet.</param>
/// <returns>A mnemonic allowing recovery of the wallet.</returns>
public string Create(string password, string folderPath, string name, string network)
{
Mnemonic mnemonic = this.walletManager.CreateWallet(password, Path.Combine(folderPath, $"{name}.json"), WalletHelpers.GetNetwork(network), password);
return mnemonic.ToString();
}
/// <summary>
/// Loads a wallet from the local device.
/// </summary>
/// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the wallet will be loaded.</param>
/// <param name="name">The name of the wallet.</param>
/// <returns>The wallet loaded from the local device</returns>
public WalletModel Load(string password, string folderPath, string name)
{
Wallet wallet = this.walletManager.LoadWallet(password, Path.Combine(folderPath, $"{name}.json"));
//TODO review here which data should be returned
return new WalletModel
{
Network = wallet.Network.Name,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = wallet.WalletFilePath
};
}
/// <summary>
/// Recovers a wallet from the local device.
/// </summary>
/// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the wallet will be loaded.</param>
/// <param name="name">The name of the 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>
/// <returns></returns>
public WalletModel Recover(string password, string folderPath, string name, string network, string mnemonic)
{
Wallet wallet = this.walletManager.RecoverWallet(new Mnemonic(mnemonic), password, Path.Combine(folderPath, $"{name}.json"), WalletHelpers.GetNetwork(network), password);
//TODO review here which data should be returned
return new WalletModel
{
Network = wallet.Network.Name,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = wallet.WalletFilePath
};
}
public WalletGeneralInfoModel GetGeneralInfo(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