Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
Breeze
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
3
Issues
3
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DeStream-public
Breeze
Commits
3e8b0a9f
Commit
3e8b0a9f
authored
May 09, 2017
by
Jeremy Bokobza
Browse files
Options
Browse Files
Download
Plain Diff
Merged from master
parents
1c4eaa2a
2279ba4a
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
275 additions
and
181 deletions
+275
-181
ApiSpecification.md
Breeze.Documentation/ApiSpecification.md
+12
-13
Wallet.postman_collection.json
...Api.Tests/Postman requests/Wallet.postman_collection.json
+9
-29
WalletController.cs
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
+10
-10
IWalletManager.cs
Breeze/src/Breeze.Wallet/IWalletManager.cs
+38
-13
CreateAccountModel.cs
Breeze/src/Breeze.Wallet/Models/CreateAccountModel.cs
+0
-36
CreateAddressModel.cs
Breeze/src/Breeze.Wallet/Models/CreateAddressModel.cs
+0
-28
RequestModels.cs
Breeze/src/Breeze.Wallet/Models/RequestModels.cs
+41
-0
Wallet.cs
Breeze/src/Breeze.Wallet/Wallet.cs
+46
-1
WalletManager.cs
Breeze/src/Breeze.Wallet/WalletManager.cs
+119
-51
No files found.
Breeze.Documentation/ApiSpecification.md
View file @
3e8b0a9f
...
@@ -167,12 +167,12 @@ Cannot check if the password is good or not. If the password is wrong it'll reco
...
@@ -167,12 +167,12 @@ 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
## POST /wallet/account - Gets an unused account from the wallet
This endpoint will get the first account containing no transaction or will create a new account.
### Parameters
### Parameters
```
```
{
{
"walletName": "myFirstWallet",
"walletName": "myFirstWallet",
"accountName": "account one",
"password": "123456",
"password": "123456",
"coinType": 105
"coinType": 105
}
}
...
@@ -182,21 +182,20 @@ Works as expected.
...
@@ -182,21 +182,20 @@ Works as expected.
"account one"
"account one"
```
```
##
POST /wallet/address - Adds an address to an account
##
GET /wallet/address - Gets an unused address
### Parameters
```
This endpoint will get the last address containing no transaction or will create a new address.
{
### Query parameters
"walletName": "myFirstWallet",
`walletName`
(required) - the name of the wallet in which this address is contained.
"accountName": "account one",
"coinType": 0
`coinType`
(required) - the type of coin for which to get the address, e.g 0 for bitcoin, 105 for stratis.
}
`
``
`
accountName`
(required) - the name of the account in which this address is contained.
### Responses
### Responses
```
```
"1HDypWxXWZC5KXK259EHMnrWaa2youy7Mj"
"1HDypWxXWZC5KXK259EHMnrWaa2youy7Mj"
```
```
## GET /wallet/receive/[account1/account2] - Displays unused receive addresses of the specified wallet account
## GET /wallet/receive/[account1/account2] - Displays unused receive addresses of the specified wallet account
### Responses
### Responses
```
```
...
...
Breeze/src/Breeze.Api.Tests/Postman requests/Wallet.postman_collection.json
View file @
3e8b0a9f
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
"variables"
:
[],
"variables"
:
[],
"info"
:
{
"info"
:
{
"name"
:
"Wallet"
,
"name"
:
"Wallet"
,
"_postman_id"
:
"5
eec0912-fcf0-50f5-05a2-0835fa13c670
"
,
"_postman_id"
:
"5
7013f2c-02dc-df32-41e9-6e4aaa14ad5e
"
,
"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"
},
},
...
@@ -81,7 +81,7 @@
...
@@ -81,7 +81,7 @@
],
],
"body"
:
{
"body"
:
{
"mode"
:
"raw"
,
"mode"
:
"raw"
,
"raw"
:
"{
\n\t\"
password
\"
:
\"
123456
\"
,
\n\t\"
network
\"
:
\"
Main
\"
,
\n\t\"
folderPath
\"
:
\"
Wallets
\"
,
\n\t\"
name
\"
:
\"
my
FirstWalletRecovered
\"
,
\n\t\"
mnemonic
\"
:
\"
elbow scale error joke labor page beyond curve indicate exit brass laundry
\"\n\t
\n
}"
"raw"
:
"{
\n\t\"
password
\"
:
\"
123456
\"
,
\n\t\"
network
\"
:
\"
Main
\"
,
\n\t\"
folderPath
\"
:
\"
Wallets
\"
,
\n\t\"
name
\"
:
\"
my
RecoveredWallet
\"
,
\n\t\"
mnemonic
\"
:
\"
elbow scale error joke labor page beyond curve indicate exit brass laundry
\"
,
\n\t\"
creationDate
\"
:
\"
2016-02-25 16:20:33
\"
\n
}"
},
},
"description"
:
""
"description"
:
""
},
},
...
@@ -206,29 +206,9 @@
...
@@ -206,29 +206,9 @@
"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"
,
"name"
:
"
Get unused account in
wallet"
,
"request"
:
{
"request"
:
{
"url"
:
"http://localhost:5000/api/v1/wallet/account"
,
"url"
:
"http://localhost:5000/api/v1/wallet/account"
,
"method"
:
"POST"
,
"method"
:
"POST"
,
...
@@ -241,17 +221,17 @@
...
@@ -241,17 +221,17 @@
],
],
"body"
:
{
"body"
:
{
"mode"
:
"raw"
,
"mode"
:
"raw"
,
"raw"
:
"{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
accountName
\"
:
\"
account one
\"
,
\n\t\"
password
\"
:
\"
123456
\"
,
\n\t\"
coinType
\"
: 105
\n
}"
"raw"
:
"{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
password
\"
:
\"
123456
\"
,
\n\t\"
coinType
\"
: 0
\n
}"
},
},
"description"
:
""
"description"
:
""
},
},
"response"
:
[]
"response"
:
[]
},
},
{
{
"name"
:
"
Create new address for
wallet"
,
"name"
:
"
Get unused address in
wallet"
,
"request"
:
{
"request"
:
{
"url"
:
"http://localhost:5000/api/v1/wallet/address"
,
"url"
:
"http://localhost:5000/api/v1/wallet/address
?walletName=wallet1&accountName=account 0&coinType=0
"
,
"method"
:
"
POS
T"
,
"method"
:
"
GE
T"
,
"header"
:
[
"header"
:
[
{
{
"key"
:
"Content-Type"
,
"key"
:
"Content-Type"
,
...
@@ -261,7 +241,7 @@
...
@@ -261,7 +241,7 @@
],
],
"body"
:
{
"body"
:
{
"mode"
:
"raw"
,
"mode"
:
"raw"
,
"raw"
:
"
{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
accountName
\"
:
\"
account one
\"
,
\n\t\"
coinType
\"
: 0
\n
}
"
"raw"
:
""
},
},
"description"
:
""
"description"
:
""
},
},
...
...
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
View file @
3e8b0a9f
...
@@ -359,7 +359,7 @@ namespace Breeze.Wallet.Controllers
...
@@ -359,7 +359,7 @@ namespace Breeze.Wallet.Controllers
/// <returns>An account name.</returns>
/// <returns>An account name.</returns>
[
Route
(
"account"
)]
[
Route
(
"account"
)]
[
HttpPost
]
[
HttpPost
]
public
IActionResult
CreateNewAccount
([
FromBody
]
Create
AccountModel
request
)
public
IActionResult
CreateNewAccount
([
FromBody
]
GetUnused
AccountModel
request
)
{
{
// checks the request is valid
// checks the request is valid
if
(!
this
.
ModelState
.
IsValid
)
if
(!
this
.
ModelState
.
IsValid
)
...
@@ -370,22 +370,22 @@ namespace Breeze.Wallet.Controllers
...
@@ -370,22 +370,22 @@ namespace Breeze.Wallet.Controllers
try
try
{
{
var
result
=
this
.
walletManager
.
CreateNewAccount
(
request
.
WalletName
,
request
.
CoinType
,
request
.
AccountNam
e
,
request
.
Password
);
var
result
=
this
.
walletManager
.
GetUnusedAccount
(
request
.
WalletName
,
request
.
CoinTyp
e
,
request
.
Password
);
return
this
.
Json
(
result
);
return
this
.
Json
(
result
.
Name
);
}
}
catch
(
Exception
e
)
catch
(
Exception
e
)
{
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
}
}
/// <summary>
/// <summary>
///
Creates a new address for a wallet
.
///
Gets an unused address
.
/// </summary>
/// </summary>
/// <returns>
An address in Base58 format
.</returns>
/// <returns>
The last created and unused address or creates a new address (in Base58 format)
.</returns>
[
Route
(
"address"
)]
[
Route
(
"address"
)]
[
Http
Pos
t
]
[
Http
Ge
t
]
public
IActionResult
CreateNewAddress
([
FromBody
]
Create
AddressModel
request
)
public
IActionResult
GetUnusedAddress
([
FromQuery
]
GetUnused
AddressModel
request
)
{
{
// checks the request is valid
// checks the request is valid
if
(!
this
.
ModelState
.
IsValid
)
if
(!
this
.
ModelState
.
IsValid
)
...
@@ -395,8 +395,8 @@ namespace Breeze.Wallet.Controllers
...
@@ -395,8 +395,8 @@ namespace Breeze.Wallet.Controllers
}
}
try
try
{
{
var
result
=
this
.
walletManager
.
CreateNew
Address
(
request
.
WalletName
,
request
.
CoinType
,
request
.
AccountName
);
var
result
=
this
.
walletManager
.
GetUnused
Address
(
request
.
WalletName
,
request
.
CoinType
,
request
.
AccountName
);
return
this
.
Json
(
result
);
return
this
.
Json
(
result
);
}
}
catch
(
Exception
e
)
catch
(
Exception
e
)
...
...
Breeze/src/Breeze.Wallet/IWalletManager.cs
View file @
3e8b0a9f
...
@@ -44,33 +44,58 @@ namespace Breeze.Wallet
...
@@ -44,33 +44,58 @@ namespace Breeze.Wallet
Wallet
RecoverWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
mnemonic
,
string
passphrase
=
null
,
DateTime
?
creationTime
=
null
);
Wallet
RecoverWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
mnemonic
,
string
passphrase
=
null
,
DateTime
?
creationTime
=
null
);
/// <summary>
/// <summary>
/// Delete
d
a wallet.
/// Delete
s
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>
/// <summary>
///
Creates a new account
.
///
Gets an account that contains no transactions
.
/// </summary>
/// </summary>
/// <param name="walletName">The name of the wallet in which this account will be created.</param>
/// <param name="walletName">The name of the wallet from which to get an account.</param>
/// <param name="coinType">the type of coin for which to create an account.</param>
/// <param name="coinType">The type of coin for which to get 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>
/// <param name="password">The password used to decrypt the private key.</param>
/// <remarks>
/// <remarks>
/// According to BIP44, an account at index (i) can only be created when the account
/// According to BIP44, an account at index (i) can only be created when the account
/// at index (i - 1) contains transactions.
/// at index (i - 1) contains transactions.
/// </remarks>
/// </remarks>
/// <returns>
The name of the new
account.</returns>
/// <returns>
An unused
account.</returns>
string
CreateNewAccount
(
string
walletName
,
CoinType
coinType
,
string
accountNam
e
,
string
password
);
HdAccount
GetUnusedAccount
(
string
walletName
,
CoinType
coinTyp
e
,
string
password
);
/// <summary>
/// <summary>
/// Creates the new address.
/// Gets an account that contains no transactions.
/// </summary>
/// <param name="wallet">The wallet from which to get an account.</param>
/// <param name="coinType">The type of coin for which to get an account.</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>An unused account.</returns>
HdAccount
GetUnusedAccount
(
Wallet
wallet
,
CoinType
coinType
,
string
password
);
/// <summary>
/// Creates a new account.
/// </summary>
/// </summary>
/// <param name="wallet
Name">The name of the wallet in which this address
will be created.</param>
/// <param name="wallet
">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="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>
/// <param name="password">The password used to decrypt the private key.</param>
/// <returns>The new address, in Base58 format.</returns>
/// <remarks>
string
CreateNewAddress
(
string
walletName
,
CoinType
coinType
,
string
accountName
);
/// According to BIP44, an account at index (i) can only be created when the account
/// at index (i - 1) contains transactions.
/// </remarks>
/// <returns>The new account.</returns>
HdAccount
CreateNewAccount
(
Wallet
wallet
,
CoinType
coinType
,
string
password
);
/// <summary>
/// Gets an address that contains no transaction.
/// </summary>
/// <param name="walletName">The name of the wallet in which this address is contained.</param>
/// <param name="coinType">The type of coin for which to get the address.</param>
/// <param name="accountName">The name of the account in which this address is contained.</param>
/// <returns>An unused address or a newly created address, in Base58 format.</returns>
string
GetUnusedAddress
(
string
walletName
,
CoinType
coinType
,
string
accountName
);
WalletGeneralInfoModel
GetGeneralInfo
(
string
walletName
);
WalletGeneralInfoModel
GetGeneralInfo
(
string
walletName
);
...
@@ -101,6 +126,6 @@ namespace Breeze.Wallet
...
@@ -101,6 +126,6 @@ namespace Breeze.Wallet
/// <param name="transaction">The transaction.</param>
/// <param name="transaction">The transaction.</param>
/// <param name="blockHeight">The height of the block this transaction came from. Null if it was not a transaction included in a block.</param>
/// <param name="blockHeight">The height of the block this transaction came from. Null if it was not a transaction included in a block.</param>
/// <param name="blockTime">The block time.</param>
/// <param name="blockTime">The block time.</param>
void
ProcessTransaction
(
CoinType
coinType
,
NBitcoin
.
Transaction
transaction
,
int
?
blockHeight
=
null
,
uint
?
blockTime
=
null
);
void
ProcessTransaction
(
CoinType
coinType
,
NBitcoin
.
Transaction
transaction
,
int
?
blockHeight
=
null
,
uint
?
blockTime
=
null
);
}
}
}
}
Breeze/src/Breeze.Wallet/Models/CreateAccountModel.cs
deleted
100644 → 0
View file @
1c4eaa2a
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
;
}
}
}
Breeze/src/Breeze.Wallet/Models/CreateAddressModel.cs
deleted
100644 → 0
View file @
1c4eaa2a
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
;
}
}
}
Breeze/src/Breeze.Wallet/Models/RequestModels.cs
View file @
3e8b0a9f
...
@@ -100,4 +100,45 @@ namespace Breeze.Wallet.Models
...
@@ -100,4 +100,45 @@ namespace Breeze.Wallet.Models
public
string
Hex
{
get
;
set
;
}
public
string
Hex
{
get
;
set
;
}
}
}
public
class
GetUnusedAddressModel
{
/// <summary>
/// The name of the wallet from which to get the address.
/// </summary>
[
Required
]
public
string
WalletName
{
get
;
set
;
}
/// <summary>
/// The type of coin this address is for.
/// </summary>
[
Required
]
public
CoinType
CoinType
{
get
;
set
;
}
/// <summary>
/// The name of the account for which to get the address.
/// </summary>
[
Required
]
public
string
AccountName
{
get
;
set
;
}
}
public
class
GetUnusedAccountModel
{
/// <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 password for this wallet.
/// </summary>
[
Required
]
public
string
Password
{
get
;
set
;
}
}
}
}
Breeze/src/Breeze.Wallet/Wallet.cs
View file @
3e8b0a9f
using
System
;
using
System
;
using
System.Collections.Generic
;
using
System.Collections.Generic
;
using
System.Linq
;
using
Breeze.Wallet.JsonConverters
;
using
Breeze.Wallet.JsonConverters
;
using
NBitcoin
;
using
NBitcoin
;
using
NBitcoin.JsonConverters
;
using
NBitcoin.JsonConverters
;
...
@@ -80,6 +81,23 @@ namespace Breeze.Wallet
...
@@ -80,6 +81,23 @@ namespace Breeze.Wallet
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"accounts"
)]
[
JsonProperty
(
PropertyName
=
"accounts"
)]
public
IEnumerable
<
HdAccount
>
Accounts
{
get
;
set
;
}
public
IEnumerable
<
HdAccount
>
Accounts
{
get
;
set
;
}
/// <summary>
/// Gets the first account that contains no transaction.
/// </summary>
/// <returns>An unused account</returns>
public
HdAccount
GetFirstUnusedAccount
()
{
var
unusedAccounts
=
this
.
Accounts
.
Where
(
acc
=>
!
acc
.
ExternalAddresses
.
Any
()
&&
!
acc
.
InternalAddresses
.
Any
()).
ToList
();
if
(!
unusedAccounts
.
Any
())
{
return
null
;
}
// gets the unused account with the lowest index
var
index
=
unusedAccounts
.
Min
(
a
=>
a
.
Index
);
return
unusedAccounts
.
Single
(
a
=>
a
.
Index
==
index
);
}
}
}
/// <summary>
/// <summary>
...
@@ -125,7 +143,6 @@ namespace Breeze.Wallet
...
@@ -125,7 +143,6 @@ namespace Breeze.Wallet
[
JsonProperty
(
PropertyName
=
"name"
)]
[
JsonProperty
(
PropertyName
=
"name"
)]
public
string
Name
{
get
;
set
;
}
public
string
Name
{
get
;
set
;
}
/// <summary>
/// <summary>
/// A path to the account as defined in BIP44.
/// A path to the account as defined in BIP44.
/// </summary>
/// </summary>
...
@@ -156,6 +173,34 @@ namespace Breeze.Wallet
...
@@ -156,6 +173,34 @@ namespace Breeze.Wallet
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"internalAddresses"
)]
[
JsonProperty
(
PropertyName
=
"internalAddresses"
)]
public
IEnumerable
<
HdAddress
>
InternalAddresses
{
get
;
set
;
}
public
IEnumerable
<
HdAddress
>
InternalAddresses
{
get
;
set
;
}
/// <summary>
/// Gets the type of coin this account is for.
/// </summary>
/// <returns>A <see cref="CoinType"/>.</returns>
public
CoinType
GetCoinType
()
{
string
[]
pathElements
=
this
.
HdPath
.
Split
(
'/'
);
int
coinType
=
int
.
Parse
(
pathElements
[
2
].
Replace
(
"'"
,
string
.
Empty
));
return
(
CoinType
)
coinType
;
}
/// <summary>
/// Gets the first receiving address that contains no transaction.
/// </summary>
/// <returns>An unused address</returns>
public
HdAddress
GetFirstUnusedExternalAddress
()
{
var
unusedAddresses
=
this
.
ExternalAddresses
.
Where
(
acc
=>
!
acc
.
Transactions
.
Any
()).
ToList
();
if
(!
unusedAddresses
.
Any
())
{
return
null
;
}
// gets the unused address with the lowest index
var
index
=
unusedAddresses
.
Min
(
a
=>
a
.
Index
);
return
unusedAddresses
.
Single
(
a
=>
a
.
Index
==
index
);
}
}
}
/// <summary>
/// <summary>
...
...
Breeze/src/Breeze.Wallet/WalletManager.cs
View file @
3e8b0a9f
...
@@ -18,10 +18,14 @@ namespace Breeze.Wallet
...
@@ -18,10 +18,14 @@ namespace Breeze.Wallet
{
{
public
List
<
Wallet
>
Wallets
{
get
;
}
public
List
<
Wallet
>
Wallets
{
get
;
}
public
HashSet
<
Script
>
PubKeys
{
get
;
}
public
HashSet
<
Script
>
PubKeys
{
get
;
set
;
}
public
HashSet
<
TransactionDetails
>
TrackedTransactions
{
get
;
}
public
HashSet
<
TransactionDetails
>
TrackedTransactions
{
get
;
}
private
const
int
UnusedAddressesBuffer
=
20
;
private
const
int
WalletRecoveryAccountsCreationCount
=
3
;
public
WalletManager
()
public
WalletManager
()
{
{
this
.
Wallets
=
new
List
<
Wallet
>();
this
.
Wallets
=
new
List
<
Wallet
>();
...
@@ -53,7 +57,6 @@ namespace Breeze.Wallet
...
@@ -53,7 +57,6 @@ 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
,
folderPath
,
name
,
WalletHelpers
.
GetNetwork
(
network
),
extendedKey
);
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
WalletHelpers
.
GetNetwork
(
network
),
extendedKey
);
this
.
Load
(
wallet
);
this
.
Load
(
wallet
);
...
@@ -84,15 +87,28 @@ namespace Breeze.Wallet
...
@@ -84,15 +87,28 @@ namespace Breeze.Wallet
// generate the root seed used to generate keys
// generate the root seed used to generate keys
ExtKey
extendedKey
=
(
new
Mnemonic
(
mnemonic
)).
DeriveExtKey
(
passphrase
);
ExtKey
extendedKey
=
(
new
Mnemonic
(
mnemonic
)).
DeriveExtKey
(
passphrase
);
Network
coinNetwork
=
WalletHelpers
.
GetNetwork
(
network
);
// create a wallet file
// create a wallet file
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
WalletHelpers
.
GetNetwork
(
network
)
,
extendedKey
,
creationTime
);
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
coinNetwork
,
extendedKey
,
creationTime
);
// generate multiple accounts and addresses from the get-go
for
(
int
i
=
0
;
i
<
WalletRecoveryAccountsCreationCount
;
i
++)
{
HdAccount
account
=
CreateNewAccount
(
wallet
,
CoinType
.
Bitcoin
,
password
);
this
.
CreateAddressesInAccount
(
account
,
coinNetwork
,
UnusedAddressesBuffer
);
this
.
CreateAddressesInAccount
(
account
,
coinNetwork
,
UnusedAddressesBuffer
,
true
);
}
// save the changes to the file and add addresses to be tracked
this
.
SaveToFile
(
wallet
);
this
.
PubKeys
=
this
.
LoadKeys
(
CoinType
.
Bitcoin
);
this
.
Load
(
wallet
);
this
.
Load
(
wallet
);
return
wallet
;
return
wallet
;
}
}
/// <inheritdoc />
/// <inheritdoc />
public
string
CreateNewAccount
(
string
walletName
,
CoinType
coinType
,
string
accountNam
e
,
string
password
)
public
HdAccount
GetUnusedAccount
(
string
walletName
,
CoinType
coinTyp
e
,
string
password
)
{
{
Wallet
wallet
=
this
.
Wallets
.
SingleOrDefault
(
w
=>
w
.
Name
==
walletName
);
Wallet
wallet
=
this
.
Wallets
.
SingleOrDefault
(
w
=>
w
.
Name
==
walletName
);
if
(
wallet
==
null
)
if
(
wallet
==
null
)
...
@@ -100,57 +116,73 @@ namespace Breeze.Wallet
...
@@ -100,57 +116,73 @@ namespace Breeze.Wallet
throw
new
Exception
(
$"No wallet with name
{
walletName
}
could be found."
);
throw
new
Exception
(
$"No wallet with name
{
walletName
}
could be found."
);
}
}
// get the accounts for this type of coin
return
this
.
GetUnusedAccount
(
wallet
,
coinType
,
password
);
var
accounts
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
.
ToList
();
}
int
newAccountIndex
=
0
;
// validate account creation
/// <inheritdoc />
if
(
accounts
.
Any
())
public
HdAccount
GetUnusedAccount
(
Wallet
wallet
,
CoinType
coinType
,
string
password
)
{
// get the accounts root for this type of coin
var
accountsRoot
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
);
// check if an unused account exists
if
(
accountsRoot
.
Accounts
.
Any
())
{
{
// check account with same name doesn't already exists
// gets an unused account
if
(
accounts
.
Any
(
a
=>
a
.
Name
==
accountName
))
var
firstUnusedAccount
=
accountsRoot
.
GetFirstUnusedAccount
();
if
(
firstUnusedAccount
!=
null
)
{
{
throw
new
Exception
(
$"Account with name '
{
accountName
}
' already exists in '
{
walletName
}
'."
)
;
return
firstUnusedAccount
;
}
}
}
// check account at index i - 1 contains transactions.
// all accounts contain transactions, create a new one
int
lastAccountIndex
=
accounts
.
Max
(
a
=>
a
.
Index
);
var
newAccount
=
this
.
CreateNewAccount
(
wallet
,
coinType
,
password
);
HdAccount
previousAccount
=
accounts
.
Single
(
a
=>
a
.
Index
==
lastAccountIndex
);
if
(!
previousAccount
.
ExternalAddresses
.
Any
(
addresses
=>
addresses
.
Transactions
.
Any
())
&&
!
previousAccount
.
InternalAddresses
.
Any
(
addresses
=>
addresses
.
Transactions
.
Any
()))
// save the changes to the file
{
this
.
SaveToFile
(
wallet
);
throw
new
Exception
(
$"Cannot create new account '
{
accountName
}
' in '
{
walletName
}
' if the previous account '
{
previousAccount
.
Name
}
' has not been used."
)
;
return
newAccount
;
}
}
newAccountIndex
=
lastAccountIndex
+
1
;
/// <inheritdoc />
public
HdAccount
CreateNewAccount
(
Wallet
wallet
,
CoinType
coinType
,
string
password
)
{
// get the accounts for this type of coin
var
accounts
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
.
ToList
();
int
newAccountIndex
=
0
;
if
(
accounts
.
Any
())
{
newAccountIndex
=
accounts
.
Max
(
a
=>
a
.
Index
)
+
1
;
}
}
// get the extended pub key used to generate addresses for this account
// get the extended pub key used to generate addresses for this account
var
privateKey
=
Key
.
Parse
(
wallet
.
EncryptedSeed
,
password
,
wallet
.
Network
);
var
privateKey
=
Key
.
Parse
(
wallet
.
EncryptedSeed
,
password
,
wallet
.
Network
);
var
seedExtKey
=
new
ExtKey
(
privateKey
,
wallet
.
ChainCode
);
var
seedExtKey
=
new
ExtKey
(
privateKey
,
wallet
.
ChainCode
);
var
accountHdPath
=
$"m/44'/
{(
int
)
coinType
}
'/
{
newAccountIndex
}
'"
;
var
accountHdPath
=
$"m/44'/
{(
int
)
coinType
}
'/
{
newAccountIndex
}
'"
;
KeyPath
keyPath
=
new
KeyPath
(
accountHdPath
);
KeyPath
keyPath
=
new
KeyPath
(
accountHdPath
);
ExtKey
accountExtKey
=
seedExtKey
.
Derive
(
keyPath
);
ExtKey
accountExtKey
=
seedExtKey
.
Derive
(
keyPath
);
ExtPubKey
accountExtPubKey
=
accountExtKey
.
Neuter
();
ExtPubKey
accountExtPubKey
=
accountExtKey
.
Neuter
();
accounts
.
Add
(
new
HdAccount
var
newAccount
=
new
HdAccount
{
{
Index
=
newAccountIndex
,
Index
=
newAccountIndex
,
ExtendedPubKey
=
accountExtPubKey
.
ToString
(
wallet
.
Network
),
ExtendedPubKey
=
accountExtPubKey
.
ToString
(
wallet
.
Network
),
ExternalAddresses
=
new
List
<
HdAddress
>(),
ExternalAddresses
=
new
List
<
HdAddress
>(),
InternalAddresses
=
new
List
<
HdAddress
>(),
InternalAddresses
=
new
List
<
HdAddress
>(),
Name
=
accountName
,
Name
=
$"account
{
newAccountIndex
}
"
,
HdPath
=
accountHdPath
,
HdPath
=
accountHdPath
,
CreationTime
=
DateTimeOffset
.
Now
CreationTime
=
DateTimeOffset
.
Now
}
)
;
};
accounts
.
Add
(
newAccount
);
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
=
accounts
;
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
=
accounts
;
this
.
SaveToFile
(
wallet
);
return
accountName
;
return
newAccount
;
}
}
/// <inheritdoc />
/// <inheritdoc />
public
string
CreateNew
Address
(
string
walletName
,
CoinType
coinType
,
string
accountName
)
public
string
GetUnused
Address
(
string
walletName
,
CoinType
coinType
,
string
accountName
)
{
{
Wallet
wallet
=
this
.
Wallets
.
SingleOrDefault
(
w
=>
w
.
Name
==
walletName
);
Wallet
wallet
=
this
.
Wallets
.
SingleOrDefault
(
w
=>
w
.
Name
==
walletName
);
if
(
wallet
==
null
)
if
(
wallet
==
null
)
...
@@ -165,42 +197,78 @@ namespace Breeze.Wallet
...
@@ -165,42 +197,78 @@ namespace Breeze.Wallet
throw
new
Exception
(
$"No account with name
{
accountName
}
could be found."
);
throw
new
Exception
(
$"No account with name
{
accountName
}
could be found."
);
}
}
int
newAddressIndex
=
0
;
// validate address creation
// validate address creation
if
(
account
.
ExternalAddresses
.
Any
())
if
(
account
.
ExternalAddresses
.
Any
())
{
{
// check last created address contains transactions.
// check last created address contains transactions.
int
lastAddressIndex
=
account
.
ExternalAddresses
.
Max
(
a
=>
a
.
Index
);
var
firstUnusedExternalAddress
=
account
.
GetFirstUnusedExternalAddress
();
var
lastAddress
=
account
.
ExternalAddresses
.
SingleOrDefault
(
a
=>
a
.
Index
==
lastAddressIndex
);
if
(
firstUnusedExternalAddress
!=
null
)
if
(
lastAddress
!=
null
&&
!
lastAddress
.
Transactions
.
Any
())
{
{
throw
new
Exception
(
$"Cannot create new address in account '
{
accountName
}
' if the previous address '
{
lastAddress
.
Address
}
' has not been used."
)
;
return
firstUnusedExternalAddress
.
Address
;
}
}
newAddressIndex
=
lastAddressIndex
+
1
;
}
}
// generate new receiving address
// creates an address
BitcoinPubKeyAddress
address
=
this
.
GenerateAddress
(
account
.
ExtendedPubKey
,
newAddressIndex
,
false
,
wallet
.
Network
);
this
.
CreateAddressesInAccount
(
account
,
wallet
.
Network
,
1
);
// add address details
account
.
ExternalAddresses
=
account
.
ExternalAddresses
.
Concat
(
new
[]
{
new
HdAddress
{
Index
=
newAddressIndex
,
HdPath
=
CreateBip44Path
(
coinType
,
account
.
Index
,
newAddressIndex
,
false
),
ScriptPubKey
=
address
.
ScriptPubKey
,
Address
=
address
.
ToString
(),
Transactions
=
new
List
<
TransactionData
>(),
CreationTime
=
DateTimeOffset
.
Now
}});
// persists the address to the wallet file
// persists the address to the wallet file
this
.
SaveToFile
(
wallet
);
this
.
SaveToFile
(
wallet
);
// adds the address to the list of tracked addresses
// adds the address to the list of tracked addresses
this
.
PubKeys
.
Add
(
address
.
ScriptPubKey
);
this
.
PubKeys
=
this
.
LoadKeys
(
coinType
);
return
address
.
ToString
();
return
account
.
GetFirstUnusedExternalAddress
().
Address
;
}
/// <summary>
/// Creates a number of addresses in the provided account.
/// </summary>
/// <param name="account">The account.</param>
/// <param name="network">The network.</param>
/// <param name="addressesQuantity">The number of addresses to create.</param>
/// <param name="isChange">Whether the addresses added are change (internal) addresses or receiving (external) addresses.</param>
/// <returns>A list of addresses in Base58.</returns>
private
List
<
string
>
CreateAddressesInAccount
(
HdAccount
account
,
Network
network
,
int
addressesQuantity
,
bool
isChange
=
false
)
{
List
<
string
>
addressesCreated
=
new
List
<
string
>();
var
addresses
=
isChange
?
account
.
InternalAddresses
:
account
.
ExternalAddresses
;
// gets the index of the last address with transactions
int
firstNewAddressIndex
=
0
;
if
(
addresses
.
Any
())
{
firstNewAddressIndex
=
addresses
.
Max
(
add
=>
add
.
Index
)
+
1
;
}
for
(
int
i
=
firstNewAddressIndex
;
i
<
firstNewAddressIndex
+
addressesQuantity
;
i
++)
{
// generate new receiving address
BitcoinPubKeyAddress
address
=
this
.
GenerateAddress
(
account
.
ExtendedPubKey
,
i
,
false
,
network
);
// add address details
addresses
=
addresses
.
Concat
(
new
[]
{
new
HdAddress
{
Index
=
i
,
HdPath
=
CreateBip44Path
(
account
.
GetCoinType
(),
account
.
Index
,
i
,
isChange
),
ScriptPubKey
=
address
.
ScriptPubKey
,
Address
=
address
.
ToString
(),
Transactions
=
new
List
<
TransactionData
>(),
CreationTime
=
DateTimeOffset
.
Now
}});
addressesCreated
.
Add
(
address
.
ToString
());
}
if
(
isChange
)
{
account
.
InternalAddresses
=
addresses
;
}
else
{
account
.
ExternalAddresses
=
addresses
;
}
return
addressesCreated
;
}
}
public
WalletGeneralInfoModel
GetGeneralInfo
(
string
name
)
public
WalletGeneralInfoModel
GetGeneralInfo
(
string
name
)
...
@@ -485,7 +553,7 @@ namespace Breeze.Wallet
...
@@ -485,7 +553,7 @@ namespace Breeze.Wallet
SelectMany
(
a
=>
a
.
ExternalAddresses
).
SelectMany
(
a
=>
a
.
ExternalAddresses
).
Select
(
s
=>
s
.
ScriptPubKey
));
Select
(
s
=>
s
.
ScriptPubKey
));
// uncomment the following for testing on a random address
// uncomment the following for testing on a random address
//
Select(t => (new BitcoinPubKeyAddress(t.Address, Network.Main)).ScriptPubKey));
//
Select(t => (new BitcoinPubKeyAddress(t.Address, Network.Main)).ScriptPubKey));
}
}
/// <summary>
/// <summary>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment