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
Show 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
## DELETE /wallet - Deletes the wallet
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
```
{
"walletName": "myFirstWallet",
"accountName": "account one",
"password": "123456",
"coinType": 105
}
...
...
@@ -182,21 +182,20 @@ Works as expected.
"account one"
```
##
POST /wallet/address - Adds an address to an account
### Parameters
```
{
"walletName": "myFirstWallet",
"accountName": "account one",
"coinType": 0
}
`
``
##
GET /wallet/address - Gets an unused address
This endpoint will get the last address containing no transaction or will create a new address.
### Query parameters
`walletName`
(required) - the name of the wallet in which this address is contained.
`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
```
"1HDypWxXWZC5KXK259EHMnrWaa2youy7Mj"
```
## GET /wallet/receive/[account1/account2] - Displays unused receive addresses of the specified wallet account
### Responses
```
...
...
Breeze/src/Breeze.Api.Tests/Postman requests/Wallet.postman_collection.json
View file @
3e8b0a9f
...
...
@@ -2,7 +2,7 @@
"variables"
:
[],
"info"
:
{
"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"
,
"schema"
:
"https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
},
...
...
@@ -81,7 +81,7 @@
],
"body"
:
{
"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"
:
""
},
...
...
@@ -208,7 +208,7 @@
"response"
:
[]
},
{
"name"
:
"
New account for non-existant wallet fails
"
,
"name"
:
"
Get unused account in wallet
"
,
"request"
:
{
"url"
:
"http://localhost:5000/api/v1/wallet/account"
,
"method"
:
"POST"
,
...
...
@@ -221,37 +221,17 @@
],
"body"
:
{
"mode"
:
"raw"
,
"raw"
:
"{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
accountName
\"
:
\"
account one
\"
\n
}"
"raw"
:
"{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
password
\"
:
\"
123456
\"
,
\n\t\"
coinType
\"
: 0
\n
}"
},
"description"
:
""
},
"response"
:
[]
},
{
"name"
:
"
Create new account for
wallet"
,
"name"
:
"
Get unused address in
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"
,
"url"
:
"http://localhost:5000/api/v1/wallet/address?walletName=wallet1&accountName=account 0&coinType=0"
,
"method"
:
"GET"
,
"header"
:
[
{
"key"
:
"Content-Type"
,
...
...
@@ -261,7 +241,7 @@
],
"body"
:
{
"mode"
:
"raw"
,
"raw"
:
"
{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
accountName
\"
:
\"
account one
\"
,
\n\t\"
coinType
\"
: 0
\n
}
"
"raw"
:
""
},
"description"
:
""
},
...
...
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
View file @
3e8b0a9f
...
...
@@ -359,7 +359,7 @@ namespace Breeze.Wallet.Controllers
/// <returns>An account name.</returns>
[
Route
(
"account"
)]
[
HttpPost
]
public
IActionResult
CreateNewAccount
([
FromBody
]
Create
AccountModel
request
)
public
IActionResult
CreateNewAccount
([
FromBody
]
GetUnused
AccountModel
request
)
{
// checks the request is valid
if
(!
this
.
ModelState
.
IsValid
)
...
...
@@ -370,8 +370,8 @@ namespace Breeze.Wallet.Controllers
try
{
var
result
=
this
.
walletManager
.
CreateNewAccount
(
request
.
WalletName
,
request
.
CoinType
,
request
.
AccountNam
e
,
request
.
Password
);
return
this
.
Json
(
result
);
var
result
=
this
.
walletManager
.
GetUnusedAccount
(
request
.
WalletName
,
request
.
CoinTyp
e
,
request
.
Password
);
return
this
.
Json
(
result
.
Name
);
}
catch
(
Exception
e
)
{
...
...
@@ -380,12 +380,12 @@ namespace Breeze.Wallet.Controllers
}
/// <summary>
///
Creates a new address for a wallet
.
///
Gets an unused address
.
/// </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"
)]
[
Http
Pos
t
]
public
IActionResult
CreateNewAddress
([
FromBody
]
Create
AddressModel
request
)
[
Http
Ge
t
]
public
IActionResult
GetUnusedAddress
([
FromQuery
]
GetUnused
AddressModel
request
)
{
// checks the request is valid
if
(!
this
.
ModelState
.
IsValid
)
...
...
@@ -396,7 +396,7 @@ namespace Breeze.Wallet.Controllers
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
);
}
catch
(
Exception
e
)
...
...
Breeze/src/Breeze.Wallet/IWalletManager.cs
View file @
3e8b0a9f
...
...
@@ -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
);
/// <summary>
/// Delete
d
a wallet.
/// Delete
s
a wallet.
/// </summary>
/// <param name="walletFilePath">The location of the wallet file.</param>
void
DeleteWallet
(
string
walletFilePath
);
/// <summary>
/// Creates a new account.
/// Gets an account that contains no transactions.
/// </summary>
/// <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 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
(
string
walletName
,
CoinType
coinType
,
string
password
);
/// <summary>
/// Gets an account that contains no transactions.
/// </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="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>
The name of the new
account.</returns>
string
CreateNewAccount
(
string
walletName
,
CoinType
coinType
,
string
accountNam
e
,
string
password
);
/// <returns>
An unused
account.</returns>
HdAccount
GetUnusedAccount
(
Wallet
wallet
,
CoinType
coinTyp
e
,
string
password
);
/// <summary>
/// Creates
the new address
.
/// Creates
a new account
.
/// </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="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
);
/// <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 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
);
...
...
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
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.Collections.Generic
;
using
System.Linq
;
using
Breeze.Wallet.JsonConverters
;
using
NBitcoin
;
using
NBitcoin.JsonConverters
;
...
...
@@ -80,6 +81,23 @@ namespace Breeze.Wallet
/// </summary>
[
JsonProperty
(
PropertyName
=
"accounts"
)]
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>
...
...
@@ -125,7 +143,6 @@ namespace Breeze.Wallet
[
JsonProperty
(
PropertyName
=
"name"
)]
public
string
Name
{
get
;
set
;
}
/// <summary>
/// A path to the account as defined in BIP44.
/// </summary>
...
...
@@ -156,6 +173,34 @@ namespace Breeze.Wallet
/// </summary>
[
JsonProperty
(
PropertyName
=
"internalAddresses"
)]
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>
...
...
Breeze/src/Breeze.Wallet/WalletManager.cs
View file @
3e8b0a9f
...
...
@@ -18,10 +18,14 @@ namespace Breeze.Wallet
{
public
List
<
Wallet
>
Wallets
{
get
;
}
public
HashSet
<
Script
>
PubKeys
{
get
;
}
public
HashSet
<
Script
>
PubKeys
{
get
;
set
;
}
public
HashSet
<
TransactionDetails
>
TrackedTransactions
{
get
;
}
private
const
int
UnusedAddressesBuffer
=
20
;
private
const
int
WalletRecoveryAccountsCreationCount
=
3
;
public
WalletManager
()
{
this
.
Wallets
=
new
List
<
Wallet
>();
...
...
@@ -53,7 +57,6 @@ namespace Breeze.Wallet
ExtKey
extendedKey
=
mnemonic
.
DeriveExtKey
(
passphrase
);
// create a wallet file
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
WalletHelpers
.
GetNetwork
(
network
),
extendedKey
);
this
.
Load
(
wallet
);
...
...
@@ -84,15 +87,28 @@ namespace Breeze.Wallet
// generate the root seed used to generate keys
ExtKey
extendedKey
=
(
new
Mnemonic
(
mnemonic
)).
DeriveExtKey
(
passphrase
);
Network
coinNetwork
=
WalletHelpers
.
GetNetwork
(
network
);
// 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
);
return
wallet
;
}
/// <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
);
if
(
wallet
==
null
)
...
...
@@ -100,57 +116,73 @@ namespace Breeze.Wallet
throw
new
Exception
(
$"No wallet with name
{
walletName
}
could be found."
);
}
// get the accounts for this type of coin
var
accounts
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
.
ToList
();
int
newAccountIndex
=
0
;
return
this
.
GetUnusedAccount
(
wallet
,
coinType
,
password
);
}
// validate account creation
if
(
accounts
.
Any
()
)
/// <inheritdoc />
public
HdAccount
GetUnusedAccount
(
Wallet
wallet
,
CoinType
coinType
,
string
password
)
{
// check account with same name doesn't already exists
if
(
accounts
.
Any
(
a
=>
a
.
Name
==
accountName
))
// 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
())
{
// gets an unused account
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.
int
lastAccountIndex
=
accounts
.
Max
(
a
=>
a
.
Index
);
HdAccount
previousAccount
=
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."
)
;
// all accounts contain transactions, create a new one
var
newAccount
=
this
.
CreateNewAccount
(
wallet
,
coinType
,
password
);
// save the changes to the file
this
.
SaveToFile
(
wallet
);
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
var
privateKey
=
Key
.
Parse
(
wallet
.
EncryptedSeed
,
password
,
wallet
.
Network
);
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
);
ExtKey
accountExtKey
=
seedExtKey
.
Derive
(
keyPath
);
ExtPubKey
accountExtPubKey
=
accountExtKey
.
Neuter
();
accounts
.
Add
(
new
HdAccount
var
newAccount
=
new
HdAccount
{
Index
=
newAccountIndex
,
ExtendedPubKey
=
accountExtPubKey
.
ToString
(
wallet
.
Network
),
ExternalAddresses
=
new
List
<
HdAddress
>(),
InternalAddresses
=
new
List
<
HdAddress
>(),
Name
=
accountName
,
Name
=
$"account
{
newAccountIndex
}
"
,
HdPath
=
accountHdPath
,
CreationTime
=
DateTimeOffset
.
Now
}
)
;
};
accounts
.
Add
(
newAccount
);
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
=
accounts
;
this
.
SaveToFile
(
wallet
);
return
accountName
;
return
newAccount
;
}
/// <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
);
if
(
wallet
==
null
)
...
...
@@ -165,42 +197,78 @@ namespace Breeze.Wallet
throw
new
Exception
(
$"No account with name
{
accountName
}
could be found."
);
}
int
newAddressIndex
=
0
;
// validate address creation
if
(
account
.
ExternalAddresses
.
Any
())
{
// check last created address contains transactions.
int
lastAddressIndex
=
account
.
ExternalAddresses
.
Max
(
a
=>
a
.
Index
);
var
lastAddress
=
account
.
ExternalAddresses
.
SingleOrDefault
(
a
=>
a
.
Index
==
lastAddressIndex
);
if
(
lastAddress
!=
null
&&
!
lastAddress
.
Transactions
.
Any
())
var
firstUnusedExternalAddress
=
account
.
GetFirstUnusedExternalAddress
();
if
(
firstUnusedExternalAddress
!=
null
)
{
throw
new
Exception
(
$"Cannot create new address in account '
{
accountName
}
' if the previous address '
{
lastAddress
.
Address
}
' has not been used."
);
return
firstUnusedExternalAddress
.
Address
;
}
}
// creates an address
this
.
CreateAddressesInAccount
(
account
,
wallet
.
Network
,
1
);
// persists the address to the wallet file
this
.
SaveToFile
(
wallet
);
// adds the address to the list of tracked addresses
this
.
PubKeys
=
this
.
LoadKeys
(
coinType
);
return
account
.
GetFirstUnusedExternalAddress
().
Address
;
}
newAddressIndex
=
lastAddressIndex
+
1
;
/// <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
,
newAddressIndex
,
false
,
wallet
.
N
etwork
);
BitcoinPubKeyAddress
address
=
this
.
GenerateAddress
(
account
.
ExtendedPubKey
,
i
,
false
,
n
etwork
);
// add address details
account
.
ExternalAddresses
=
account
.
ExternalA
ddresses
.
Concat
(
new
[]
{
new
HdAddress
addresses
=
a
ddresses
.
Concat
(
new
[]
{
new
HdAddress
{
Index
=
newAddressIndex
,
HdPath
=
CreateBip44Path
(
coinType
,
account
.
Index
,
newAddressIndex
,
fals
e
),
Index
=
i
,
HdPath
=
CreateBip44Path
(
account
.
GetCoinType
(),
account
.
Index
,
i
,
isChang
e
),
ScriptPubKey
=
address
.
ScriptPubKey
,
Address
=
address
.
ToString
(),
Transactions
=
new
List
<
TransactionData
>(),
CreationTime
=
DateTimeOffset
.
Now
}});
// persists the address to the wallet file
this
.
SaveToFile
(
wallet
);
addressesCreated
.
Add
(
address
.
ToString
());
}
// adds the address to the list of tracked addresses
this
.
PubKeys
.
Add
(
address
.
ScriptPubKey
);
return
address
.
ToString
();
if
(
isChange
)
{
account
.
InternalAddresses
=
addresses
;
}
else
{
account
.
ExternalAddresses
=
addresses
;
}
return
addressesCreated
;
}
public
WalletGeneralInfoModel
GetGeneralInfo
(
string
name
)
...
...
@@ -485,7 +553,7 @@ namespace Breeze.Wallet
SelectMany
(
a
=>
a
.
ExternalAddresses
).
Select
(
s
=>
s
.
ScriptPubKey
));
// 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>
...
...
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