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
3a244ac5
Commit
3a244ac5
authored
Jun 02, 2017
by
Jeremy Bokobza
Committed by
GitHub
Jun 02, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #86 from dangershony/wallet-to-fullnode
Moving the wallet feature to the full node
parents
3435ae71
8036646f
Hide whitespace changes
Inline
Side-by-side
Showing
38 changed files
with
218 additions
and
3081 deletions
+218
-3081
.gitignore
.gitignore
+1
-0
Breeze.sln
Breeze/Breeze.sln
+19
-15
Breeze.Api.Tests.csproj
Breeze/src/Breeze.Api.Tests/Breeze.Api.Tests.csproj
+1
-0
ControllersTests.cs
Breeze/src/Breeze.Api.Tests/ControllersTests.cs
+0
-126
WalletHelpersTest.cs
Breeze/src/Breeze.Api.Tests/WalletHelpersTest.cs
+0
-58
Breeze.Api.csproj
Breeze/src/Breeze.Api/Breeze.Api.csproj
+4
-1
Breeze.Common.csproj
Breeze/src/Breeze.Common/Breeze.Common.csproj
+0
-14
ErrorHelpers.cs
Breeze/src/Breeze.Common/JsonErrors/ErrorHelpers.cs
+0
-26
ErrorResponse.cs
Breeze/src/Breeze.Common/JsonErrors/ErrorResponse.cs
+0
-23
ErrorResult.cs
Breeze/src/Breeze.Common/JsonErrors/ErrorResult.cs
+0
-18
Breeze.Daemon.csproj
Breeze/src/Breeze.Daemon/Breeze.Daemon.csproj
+1
-1
Program.cs
Breeze/src/Breeze.Daemon/Program.cs
+128
-27
Breeze.TumbleBit.Client.csproj
...rc/Breeze.TumbleBit.Client/Breeze.TumbleBit.Client.csproj
+2
-2
TumbleBitController.cs
...reeze.TumbleBit.Client/Controllers/TumbleBitController.cs
+1
-1
Breeze.Wallet.csproj
Breeze/src/Breeze.Wallet/Breeze.Wallet.csproj
+1
-2
ChainExtensions.cs
Breeze/src/Breeze.Wallet/ChainExtensions.cs
+0
-53
WalletController.cs
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
+0
-472
WalletHelpers.cs
Breeze/src/Breeze.Wallet/Helpers/WalletHelpers.cs
+0
-37
ITracker.cs
Breeze/src/Breeze.Wallet/ITracker.cs
+0
-35
IWalletManager.cs
Breeze/src/Breeze.Wallet/IWalletManager.cs
+0
-191
ByteArrayConverter.cs
...ze/src/Breeze.Wallet/JsonConverters/ByteArrayConverter.cs
+0
-32
DateTimeOffsetConverter.cs
...c/Breeze.Wallet/JsonConverters/DateTimeOffsetConverter.cs
+0
-30
NetworkConverter.cs
Breeze/src/Breeze.Wallet/JsonConverters/NetworkConverter.cs
+0
-32
LightWalletFeature.cs
Breeze/src/Breeze.Wallet/LightWalletFeature.cs
+52
-0
LightWalletSyncManager.cs
Breeze/src/Breeze.Wallet/LightWalletSyncManager.cs
+8
-24
RequestModels.cs
Breeze/src/Breeze.Wallet/Models/RequestModels.cs
+0
-161
WalletBalanceModel.cs
Breeze/src/Breeze.Wallet/Models/WalletBalanceModel.cs
+0
-33
WalletBuildTransactionModel.cs
...e/src/Breeze.Wallet/Models/WalletBuildTransactionModel.cs
+0
-21
WalletFileModel.cs
Breeze/src/Breeze.Wallet/Models/WalletFileModel.cs
+0
-16
WalletGeneralInfoModel.cs
Breeze/src/Breeze.Wallet/Models/WalletGeneralInfoModel.cs
+0
-45
WalletHistoryModel.cs
Breeze/src/Breeze.Wallet/Models/WalletHistoryModel.cs
+0
-78
WalletModel.cs
Breeze/src/Breeze.Wallet/Models/WalletModel.cs
+0
-20
BlockObserver.cs
Breeze/src/Breeze.Wallet/Notifications/BlockObserver.cs
+0
-32
BlockSubscriber.cs
Breeze/src/Breeze.Wallet/Notifications/BlockSubscriber.cs
+0
-30
TransactionObserver.cs
...ze/src/Breeze.Wallet/Notifications/TransactionObserver.cs
+0
-27
TransactionSubscriber.cs
.../src/Breeze.Wallet/Notifications/TransactionSubscriber.cs
+0
-30
Wallet.cs
Breeze/src/Breeze.Wallet/Wallet.cs
+0
-502
WalletManager.cs
Breeze/src/Breeze.Wallet/WalletManager.cs
+0
-866
No files found.
.gitignore
View file @
3a244ac5
...
...
@@ -297,3 +297,4 @@ Thumbs.db
project.lock.json
/Breeze/src/Breeze.Daemon/Wallets
/Breeze/src/Breeze.Daemon/Logs
/Breeze.UI/.vscode/.BROWSE.VC.DB-wal
Breeze/Breeze.sln
View file @
3a244ac5
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.
6
VisualStudioVersion = 15.0.26430.
12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{807563C4-7259-434D-B604-A14C3DCF8E30}"
EndProject
...
...
@@ -17,16 +17,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Breeze.Api.Tests", "src\Bre
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Breeze.Wallet", "src\Breeze.Wallet\Breeze.Wallet.csproj", "{D16CD478-9D1E-4C69-91AD-43539E94A215}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Breeze.Daemon", "src\Breeze.Daemon\Breeze.Daemon.csproj", "{1B598E33-667F-496D-BC0D-88276E8E7632}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Breeze.TumbleBit.Client", "src\Breeze.TumbleBit.Client\Breeze.TumbleBit.Client.csproj", "{2490DD1A-6C14-47F2-A9C6-56761A52E2D9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Breeze.Common", "src\Breeze.Common\Breeze.Common.csproj", "{C726817D-9E2F-4DDC-90A4-B6895CF5309B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TumbleBit", "TumbleBit", "{1B724678-2B73-483E-B981-3A6733C2194E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NTumbleBit", "src\NTumbleBit\NTumbleBit.csproj", "{29E411B1-5687-43EE-A71B-6CCEC2289129}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.Bitcoin", "..\..\StratisBitcoinFullNode\Stratis.Bitcoin\Stratis.Bitcoin.csproj", "{6702A093-B42C-44DA-86BB-CD8F87E9A665}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.Bitcoin.Common", "..\..\StratisBitcoinFullNode\Stratis.Bitcoin.Common\Stratis.Bitcoin.Common.csproj", "{4F9F7CF7-326C-4FC0-9EFB-209536A42030}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Breeze.Daemon", "src\Breeze.Daemon\Breeze.Daemon.csproj", "{AAF6163B-1BE2-48CE-9F9F-577C6D7AAB8D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
...
...
@@ -45,22 +47,26 @@ Global
{D16CD478-9D1E-4C69-91AD-43539E94A215}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D16CD478-9D1E-4C69-91AD-43539E94A215}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D16CD478-9D1E-4C69-91AD-43539E94A215}.Release|Any CPU.Build.0 = Release|Any CPU
{1B598E33-667F-496D-BC0D-88276E8E7632}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B598E33-667F-496D-BC0D-88276E8E7632}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B598E33-667F-496D-BC0D-88276E8E7632}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B598E33-667F-496D-BC0D-88276E8E7632}.Release|Any CPU.Build.0 = Release|Any CPU
{2490DD1A-6C14-47F2-A9C6-56761A52E2D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2490DD1A-6C14-47F2-A9C6-56761A52E2D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2490DD1A-6C14-47F2-A9C6-56761A52E2D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2490DD1A-6C14-47F2-A9C6-56761A52E2D9}.Release|Any CPU.Build.0 = Release|Any CPU
{C726817D-9E2F-4DDC-90A4-B6895CF5309B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C726817D-9E2F-4DDC-90A4-B6895CF5309B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C726817D-9E2F-4DDC-90A4-B6895CF5309B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C726817D-9E2F-4DDC-90A4-B6895CF5309B}.Release|Any CPU.Build.0 = Release|Any CPU
{29E411B1-5687-43EE-A71B-6CCEC2289129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29E411B1-5687-43EE-A71B-6CCEC2289129}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29E411B1-5687-43EE-A71B-6CCEC2289129}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29E411B1-5687-43EE-A71B-6CCEC2289129}.Release|Any CPU.Build.0 = Release|Any CPU
{6702A093-B42C-44DA-86BB-CD8F87E9A665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6702A093-B42C-44DA-86BB-CD8F87E9A665}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6702A093-B42C-44DA-86BB-CD8F87E9A665}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6702A093-B42C-44DA-86BB-CD8F87E9A665}.Release|Any CPU.Build.0 = Release|Any CPU
{4F9F7CF7-326C-4FC0-9EFB-209536A42030}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4F9F7CF7-326C-4FC0-9EFB-209536A42030}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F9F7CF7-326C-4FC0-9EFB-209536A42030}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F9F7CF7-326C-4FC0-9EFB-209536A42030}.Release|Any CPU.Build.0 = Release|Any CPU
{AAF6163B-1BE2-48CE-9F9F-577C6D7AAB8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAF6163B-1BE2-48CE-9F9F-577C6D7AAB8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAF6163B-1BE2-48CE-9F9F-577C6D7AAB8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAF6163B-1BE2-48CE-9F9F-577C6D7AAB8D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
...
...
@@ -69,9 +75,7 @@ Global
{E7B3E9EB-34E8-4B10-B296-4D5270E314A4} = {807563C4-7259-434D-B604-A14C3DCF8E30}
{BD5174B4-DCE8-4594-9A16-B83E56767770} = {807563C4-7259-434D-B604-A14C3DCF8E30}
{D16CD478-9D1E-4C69-91AD-43539E94A215} = {807563C4-7259-434D-B604-A14C3DCF8E30}
{1B598E33-667F-496D-BC0D-88276E8E7632} = {807563C4-7259-434D-B604-A14C3DCF8E30}
{2490DD1A-6C14-47F2-A9C6-56761A52E2D9} = {807563C4-7259-434D-B604-A14C3DCF8E30}
{C726817D-9E2F-4DDC-90A4-B6895CF5309B} = {807563C4-7259-434D-B604-A14C3DCF8E30}
{29E411B1-5687-43EE-A71B-6CCEC2289129} = {1B724678-2B73-483E-B981-3A6733C2194E}
EndGlobalSection
EndGlobal
Breeze/src/Breeze.Api.Tests/Breeze.Api.Tests.csproj
View file @
3a244ac5
...
...
@@ -10,6 +10,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\StratisBitcoinFullNode\Stratis.Bitcoin\Stratis.Bitcoin.csproj" />
<ProjectReference Include="..\Breeze.Api\Breeze.Api.csproj" />
<ProjectReference Include="..\Breeze.Wallet\Breeze.Wallet.csproj" />
</ItemGroup>
...
...
Breeze/src/Breeze.Api.Tests/ControllersTests.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
Microsoft.AspNetCore.Mvc
;
using
Xunit
;
using
Moq
;
using
Breeze.Wallet
;
using
Breeze.Wallet.Controllers
;
using
Breeze.Common.JsonErrors
;
using
Breeze.Wallet.Helpers
;
using
Breeze.Wallet.Models
;
using
NBitcoin
;
using
NBitcoin.Protocol
;
using
Stratis.Bitcoin.Configuration
;
using
Stratis.Bitcoin.Connection
;
namespace
Breeze.Api.Tests
{
public
class
ControllersTests
{
[
Fact
]
public
void
CreateWalletSuccessfullyReturnsMnemonic
()
{
Mnemonic
mnemonic
=
new
Mnemonic
(
Wordlist
.
English
,
WordCount
.
Twelve
);
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
,
new
Mock
<
ITracker
>().
Object
,
It
.
IsAny
<
ConnectionManager
>(),
Network
.
Main
,
new
Mock
<
ConcurrentChain
>().
Object
);
// Act
var
result
=
controller
.
Create
(
new
WalletCreationRequest
{
Name
=
"myName"
,
FolderPath
=
""
,
Password
=
""
,
Network
=
""
});
// Assert
mockWalletCreate
.
VerifyAll
();
var
viewResult
=
Assert
.
IsType
<
JsonResult
>(
result
);
Assert
.
Equal
(
mnemonic
.
ToString
(),
viewResult
.
Value
);
Assert
.
NotNull
(
result
);
}
[
Fact
]
public
void
LoadWalletSuccessfullyReturnsWalletModel
()
{
Wallet
.
Wallet
wallet
=
new
Wallet
.
Wallet
{
Name
=
"myWallet"
,
Network
=
WalletHelpers
.
GetNetwork
(
"mainnet"
)
};
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
>(),
It
.
IsAny
<
DateTime
>(),
null
)).
Returns
(
wallet
);
var
controller
=
new
WalletController
(
mockWalletWrapper
.
Object
,
new
Mock
<
ITracker
>().
Object
,
It
.
IsAny
<
ConnectionManager
>(),
Network
.
Main
,
new
Mock
<
ConcurrentChain
>().
Object
);
// Act
var
result
=
controller
.
Recover
(
new
WalletRecoveryRequest
{
Name
=
"myWallet"
,
FolderPath
=
""
,
Password
=
""
,
Network
=
"MainNet"
,
Mnemonic
=
"mnemonic"
});
// Assert
mockWalletWrapper
.
VerifyAll
();
var
viewResult
=
Assert
.
IsType
<
OkResult
>(
result
);
Assert
.
Equal
(
200
,
viewResult
.
StatusCode
);
}
[
Fact
]
public
void
RecoverWalletSuccessfullyReturnsWalletModel
()
{
Wallet
.
Wallet
wallet
=
new
Wallet
.
Wallet
{
Name
=
"myWallet"
,
Network
=
WalletHelpers
.
GetNetwork
(
"mainnet"
)
};
var
mockWalletWrapper
=
new
Mock
<
IWalletManager
>();
mockWalletWrapper
.
Setup
(
w
=>
w
.
LoadWallet
(
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>())).
Returns
(
wallet
);
var
controller
=
new
WalletController
(
mockWalletWrapper
.
Object
,
new
Mock
<
ITracker
>().
Object
,
It
.
IsAny
<
ConnectionManager
>(),
Network
.
Main
,
new
Mock
<
ConcurrentChain
>().
Object
);
// Act
var
result
=
controller
.
Load
(
new
WalletLoadRequest
{
Name
=
"myWallet"
,
FolderPath
=
""
,
Password
=
""
});
// Assert
mockWalletWrapper
.
VerifyAll
();
var
viewResult
=
Assert
.
IsType
<
OkResult
>(
result
);
Assert
.
Equal
(
200
,
viewResult
.
StatusCode
);
}
[
Fact
]
public
void
FileNotFoundExceptionandReturns404
()
{
var
mockWalletWrapper
=
new
Mock
<
IWalletManager
>();
mockWalletWrapper
.
Setup
(
wallet
=>
wallet
.
LoadWallet
(
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>())).
Throws
<
FileNotFoundException
>();
var
controller
=
new
WalletController
(
mockWalletWrapper
.
Object
,
new
Mock
<
ITracker
>().
Object
,
It
.
IsAny
<
ConnectionManager
>(),
Network
.
Main
,
new
Mock
<
ConcurrentChain
>().
Object
);
// Act
var
result
=
controller
.
Load
(
new
WalletLoadRequest
{
Name
=
"myName"
,
FolderPath
=
""
,
Password
=
""
});
// Assert
mockWalletWrapper
.
VerifyAll
();
var
viewResult
=
Assert
.
IsType
<
ErrorResult
>(
result
);
Assert
.
NotNull
(
viewResult
);
Assert
.
Equal
(
404
,
viewResult
.
StatusCode
);
}
}
}
Breeze/src/Breeze.Api.Tests/WalletHelpersTest.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.Text
;
using
Breeze.Wallet.Helpers
;
using
NBitcoin
;
using
Xunit
;
namespace
Breeze.Api.Tests
{
public
class
WalletHelpersTest
{
[
Fact
]
public
void
GetMainNetworkRetuirnsNetworkMain
()
{
Network
network
=
WalletHelpers
.
GetNetwork
(
"main"
);
Assert
.
Equal
(
Network
.
Main
,
network
);
}
[
Fact
]
public
void
GetMainNetNetworkRetuirnsNetworkMain
()
{
Network
network
=
WalletHelpers
.
GetNetwork
(
"mainnet"
);
Assert
.
Equal
(
Network
.
Main
,
network
);
}
[
Fact
]
public
void
GetTestNetworkRetuirnsNetworkTest
()
{
Network
network
=
WalletHelpers
.
GetNetwork
(
"test"
);
Assert
.
Equal
(
Network
.
TestNet
,
network
);
}
[
Fact
]
public
void
GetTestNetNetworkRetuirnsNetworkTest
()
{
Network
network
=
WalletHelpers
.
GetNetwork
(
"testnet"
);
Assert
.
Equal
(
Network
.
TestNet
,
network
);
}
[
Fact
]
public
void
GetNetworkIsCaseInsensitive
()
{
Network
testNetwork
=
WalletHelpers
.
GetNetwork
(
"Test"
);
Assert
.
Equal
(
Network
.
TestNet
,
testNetwork
);
Network
mainNetwork
=
WalletHelpers
.
GetNetwork
(
"MainNet"
);
Assert
.
Equal
(
Network
.
Main
,
mainNetwork
);
}
[
Fact
]
public
void
WrongNetworkThrowsArgumentException
()
{
var
exception
=
Record
.
Exception
(()
=>
WalletHelpers
.
GetNetwork
(
"myNetwork"
));
Assert
.
NotNull
(
exception
);
Assert
.
IsType
<
ArgumentException
>(
exception
);
}
}
}
Breeze/src/Breeze.Api/Breeze.Api.csproj
View file @
3a244ac5
...
...
@@ -29,10 +29,13 @@
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" />
<PackageReference Include="Stratis.Bitcoin" Version="1.0.1.8-alpha" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
<PackageReference Include="System.Reactive" Version="3.1.1" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\StratisBitcoinFullNode\Stratis.Bitcoin\Stratis.Bitcoin.csproj" />
</ItemGroup>
</Project>
Breeze/src/Breeze.Common/Breeze.Common.csproj
deleted
100644 → 0
View file @
3435ae71
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="1.1.3" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
</ItemGroup>
</Project>
\ No newline at end of file
Breeze/src/Breeze.Common/JsonErrors/ErrorHelpers.cs
deleted
100644 → 0
View file @
3435ae71
using
System.Collections.Generic
;
using
System.Net
;
namespace
Breeze.Common.JsonErrors
{
public
static
class
ErrorHelpers
{
public
static
ErrorResult
BuildErrorResponse
(
HttpStatusCode
statusCode
,
string
message
,
string
description
)
{
ErrorResponse
errorResponse
=
new
ErrorResponse
{
Errors
=
new
List
<
ErrorModel
>
{
new
ErrorModel
{
Status
=
(
int
)
statusCode
,
Message
=
message
,
Description
=
description
}
}
};
return
new
ErrorResult
((
int
)
statusCode
,
errorResponse
);
}
}
}
Breeze/src/Breeze.Common/JsonErrors/ErrorResponse.cs
deleted
100644 → 0
View file @
3435ae71
using
System.Collections.Generic
;
using
Newtonsoft.Json
;
namespace
Breeze.Common.JsonErrors
{
public
class
ErrorResponse
{
[
JsonProperty
(
PropertyName
=
"errors"
)]
public
List
<
ErrorModel
>
Errors
{
get
;
set
;
}
}
public
class
ErrorModel
{
[
JsonProperty
(
PropertyName
=
"status"
)]
public
int
Status
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"message"
)]
public
string
Message
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"description"
)]
public
string
Description
{
get
;
set
;
}
}
}
Breeze/src/Breeze.Common/JsonErrors/ErrorResult.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Threading.Tasks
;
using
Breeze.Common.JsonErrors
;
using
Microsoft.AspNetCore.Http
;
using
Microsoft.AspNetCore.Mvc
;
namespace
Breeze.Common.JsonErrors
{
public
class
ErrorResult
:
ObjectResult
{
public
ErrorResult
(
int
statusCode
,
ErrorResponse
value
)
:
base
(
value
)
{
StatusCode
=
statusCode
;
}
}
}
Breeze/src/Breeze.Daemon/Breeze.Daemon.csproj
View file @
3a244ac5
...
...
@@ -13,6 +13,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\StratisBitcoinFullNode\Stratis.Bitcoin\Stratis.Bitcoin.csproj" />
<ProjectReference Include="..\Breeze.Api\Breeze.Api.csproj" />
<ProjectReference Include="..\Breeze.TumbleBit.Client\Breeze.TumbleBit.Client.csproj" />
<ProjectReference Include="..\Breeze.Wallet\Breeze.Wallet.csproj" />
...
...
@@ -21,7 +22,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.2" />
<PackageReference Include="Stratis.Bitcoin" Version="1.0.1.8-alpha" />
</ItemGroup>
</Project>
Breeze/src/Breeze.Daemon/Program.cs
View file @
3a244ac5
...
...
@@ -9,7 +9,15 @@ using Stratis.Bitcoin.Builder;
using
Stratis.Bitcoin.Configuration
;
using
Stratis.Bitcoin.Logging
;
using
Breeze.Wallet
;
using
NBitcoin
;
using
NBitcoin.Protocol
;
using
Stratis.Bitcoin.BlockStore
;
using
Stratis.Bitcoin.Consensus
;
using
Stratis.Bitcoin.MemoryPool
;
using
Stratis.Bitcoin.Miner
;
using
Stratis.Bitcoin.Notifications
;
using
Stratis.Bitcoin.Utilities
;
using
Stratis.Bitcoin.Wallet
;
namespace
Breeze.Daemon
{
...
...
@@ -17,32 +25,125 @@ namespace Breeze.Daemon
{
public
static
void
Main
(
string
[]
args
)
{
// configure Full Node
Logs
.
Configure
(
new
LoggerFactory
().
AddConsole
(
LogLevel
.
Trace
,
false
));
NodeSettings
nodeSettings
=
NodeSettings
.
FromArguments
(
args
);
var
fullNodeBuilder
=
new
FullNodeBuilder
()
.
UseNodeSettings
(
nodeSettings
)
.
UseWallet
()
.
UseBlockNotification
()
.
UseTransactionNotification
()
.
UseApi
();
// add the tumbler's settings
var
tumblerAddress
=
args
.
SingleOrDefault
(
arg
=>
arg
.
StartsWith
(
"-tumbler-uri="
));
if
(!
string
.
IsNullOrEmpty
(
tumblerAddress
))
{
tumblerAddress
=
tumblerAddress
.
Replace
(
"-tumbler-uri="
,
string
.
Empty
);
fullNodeBuilder
.
UseTumbleBit
(
new
Uri
(
tumblerAddress
));
}
var
node
=
(
FullNode
)
fullNodeBuilder
.
Build
();
// start Full Node - this will also start the API
node
.
Start
();
Console
.
WriteLine
(
"Press any key to stop"
);
Console
.
ReadLine
();
node
.
Dispose
();
IFullNodeBuilder
fullNodeBuilder
=
null
;
if
(
args
.
Contains
(
"stratis"
))
{
// configure Full Node
Logs
.
Configure
(
Logs
.
GetLoggerFactory
(
args
));
if
(
NodeSettings
.
PrintHelp
(
args
,
Network
.
StratisMain
))
return
;
var
network
=
args
.
Contains
(
"-testnet"
)
?
InitStratisTest
()
:
Network
.
StratisMain
;
var
nodeSettings
=
NodeSettings
.
FromArguments
(
args
,
"stratis"
,
network
,
ProtocolVersion
.
ALT_PROTOCOL_VERSION
);
if
(
args
.
Contains
(
"light"
))
{
fullNodeBuilder
=
new
FullNodeBuilder
()
.
UseNodeSettings
(
nodeSettings
)
.
UseLightWallet
()
.
UseBlockNotification
()
.
UseTransactionNotification
()
.
UseApi
();
}
else
{
fullNodeBuilder
=
new
FullNodeBuilder
()
.
UseNodeSettings
(
nodeSettings
)
.
UseStratisConsensus
()
.
UseBlockStore
()
.
UseMempool
()
.
UseWallet
()
.
AddPowPosMining
()
.
UseApi
();
}
}
else
{
NodeSettings
nodeSettings
=
NodeSettings
.
FromArguments
(
args
);
if
(
args
.
Contains
(
"light"
))
{
fullNodeBuilder
=
new
FullNodeBuilder
()
.
UseNodeSettings
(
nodeSettings
)
.
UseLightWallet
()
.
UseBlockNotification
()
.
UseTransactionNotification
()
.
UseApi
();
}
else
{
fullNodeBuilder
=
new
FullNodeBuilder
()
.
UseNodeSettings
(
nodeSettings
)
.
UseConsensus
()
.
UseBlockStore
()
.
UseMempool
()
.
UseWallet
()
.
UseApi
();
}
}
// add the tumbler's settings
var
tumblerAddress
=
args
.
SingleOrDefault
(
arg
=>
arg
.
StartsWith
(
"-tumbler-uri="
));
if
(!
string
.
IsNullOrEmpty
(
tumblerAddress
))
{
tumblerAddress
=
tumblerAddress
.
Replace
(
"-tumbler-uri="
,
string
.
Empty
);
fullNodeBuilder
.
UseTumbleBit
(
new
Uri
(
tumblerAddress
));
}
var
node
=
fullNodeBuilder
.
Build
();
//start Full Node - this will also start the API
node
.
Run
();
}
}
private
static
Network
InitStratisTest
()
{
Block
.
BlockSignature
=
true
;
Transaction
.
TimeStamp
=
true
;
var
consensus
=
Network
.
StratisMain
.
Consensus
.
Clone
();
consensus
.
PowLimit
=
new
Target
(
uint256
.
Parse
(
"0000ffff00000000000000000000000000000000000000000000000000000000"
));
// The message start string is designed to be unlikely to occur in normal data.
// The characters are rarely used upper ASCII, not valid as UTF-8, and produce
// a large 4-byte int at any alignment.
var
pchMessageStart
=
new
byte
[
4
];
pchMessageStart
[
0
]
=
0x71
;
pchMessageStart
[
1
]
=
0x31
;
pchMessageStart
[
2
]
=
0x21
;
pchMessageStart
[
3
]
=
0x11
;
var
magic
=
BitConverter
.
ToUInt32
(
pchMessageStart
,
0
);
//0x5223570;
var
genesis
=
Network
.
StratisMain
.
GetGenesis
().
Clone
();
genesis
.
Header
.
Time
=
1493909211
;
genesis
.
Header
.
Nonce
=
2433759
;
genesis
.
Header
.
Bits
=
consensus
.
PowLimit
;
consensus
.
HashGenesisBlock
=
genesis
.
GetHash
();
Guard
.
Assert
(
consensus
.
HashGenesisBlock
==
uint256
.
Parse
(
"0x00000e246d7b73b88c9ab55f2e5e94d9e22d471def3df5ea448f5576b1d156b9"
));
var
builder
=
new
NetworkBuilder
()
.
SetName
(
"StratisTest"
)
.
SetConsensus
(
consensus
)
.
SetMagic
(
magic
)
.
SetGenesis
(
genesis
)
.
SetPort
(
26178
)
.
SetRPCPort
(
26174
)
.
SetBase58Bytes
(
Base58Type
.
PUBKEY_ADDRESS
,
new
byte
[]
{
(
65
)
})
.
SetBase58Bytes
(
Base58Type
.
SCRIPT_ADDRESS
,
new
byte
[]
{
(
196
)
})
.
SetBase58Bytes
(
Base58Type
.
SECRET_KEY
,
new
byte
[]
{
(
65
+
128
)
})
.
SetBase58Bytes
(
Base58Type
.
ENCRYPTED_SECRET_KEY_NO_EC
,
new
byte
[]
{
0x01
,
0x42
})
.
SetBase58Bytes
(
Base58Type
.
ENCRYPTED_SECRET_KEY_EC
,
new
byte
[]
{
0x01
,
0x43
})
.
SetBase58Bytes
(
Base58Type
.
EXT_PUBLIC_KEY
,
new
byte
[]
{
(
0x04
),
(
0x88
),
(
0xB2
),
(
0x1E
)
})
.
SetBase58Bytes
(
Base58Type
.
EXT_SECRET_KEY
,
new
byte
[]
{
(
0x04
),
(
0x88
),
(
0xAD
),
(
0xE4
)
})
.
AddDNSSeeds
(
new
[]
{
new
DNSSeedData
(
"stratisplatform.com"
,
"testnode1.stratisplatform.com"
),
});
return
builder
.
BuildAndRegister
();
}
}
}
Breeze/src/Breeze.TumbleBit.Client/Breeze.TumbleBit.Client.csproj
View file @
3a244ac5
...
...
@@ -21,11 +21,11 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="1.0.3" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="1.0.1" />
<PackageReference Include="Stratis.Bitcoin" Version="1.0.1.8-alpha" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Breeze.Common\Breeze.Common.csproj" />
<ProjectReference Include="..\..\..\..\StratisBitcoinFullNode\Stratis.Bitcoin.Common\Stratis.Bitcoin.Common.csproj" />
<ProjectReference Include="..\..\..\..\StratisBitcoinFullNode\Stratis.Bitcoin\Stratis.Bitcoin.csproj" />
<ProjectReference Include="..\NTumbleBit\NTumbleBit.csproj" />
</ItemGroup>
...
...
Breeze/src/Breeze.TumbleBit.Client/Controllers/TumbleBitController.cs
View file @
3a244ac5
...
...
@@ -2,9 +2,9 @@
using
System.Linq
;
using
System.Net
;
using
System.Threading.Tasks
;
using
Breeze.Common.JsonErrors
;
using
Microsoft.AspNetCore.Mvc
;
using
Breeze.TumbleBit.Client
;
using
Stratis.Bitcoin.Common.JsonErrors
;
namespace
Breeze.TumbleBit.Controllers
{
...
...
Breeze/src/Breeze.Wallet/Breeze.Wallet.csproj
View file @
3a244ac5
...
...
@@ -20,11 +20,10 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="1.0.3" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="1.0.1" />
<PackageReference Include="System.ValueTuple" Version="4.3.1" />
<PackageReference Include="Stratis.Bitcoin" Version="1.0.1.8-alpha" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\
Breeze.Common\Breeze.Commo
n.csproj" />
<ProjectReference Include="..\
..\..\..\StratisBitcoinFullNode\Stratis.Bitcoin\Stratis.Bitcoi
n.csproj" />
</ItemGroup>
</Project>
Breeze/src/Breeze.Wallet/ChainExtensions.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.Text
;
using
NBitcoin
;
namespace
Breeze.Wallet
{
public
static
class
ChainExtensions
{
/// <summary>
/// Determines whether the chain is downloaded and up to date.
/// </summary>
/// <param name="chain">The chain.</param>
public
static
bool
IsDownloaded
(
this
ConcurrentChain
chain
)
{
return
chain
.
Tip
.
Header
.
BlockTime
.
ToUnixTimeSeconds
()
>
(
DateTimeOffset
.
Now
.
ToUnixTimeSeconds
()
-
TimeSpan
.
FromHours
(
1
).
TotalSeconds
);
}
/// <summary>
/// Gets the height of the first block created after this date.
/// </summary>
/// <param name="chain">The chain of blocks.</param>
/// <param name="date">The date.</param>
/// <returns>The height of the first block created after the date.</returns>
public
static
int
GetHeightAtTime
(
this
ConcurrentChain
chain
,
DateTime
date
)
{
int
blockSyncStart
=
0
;
int
upperLimit
=
chain
.
Tip
.
Height
;
int
lowerLimit
=
0
;
bool
found
=
false
;
while
(!
found
)
{
int
check
=
lowerLimit
+
(
upperLimit
-
lowerLimit
)
/
2
;
if
(
chain
.
GetBlock
(
check
).
Header
.
BlockTime
>=
date
)
{
upperLimit
=
check
;
}
else
if
(
chain
.
GetBlock
(
check
).
Header
.
BlockTime
<
date
)
{
lowerLimit
=
check
;
}
if
(
upperLimit
-
lowerLimit
<=
1
)
{
blockSyncStart
=
upperLimit
;
found
=
true
;
}
}
return
blockSyncStart
;
}
}
}
\ No newline at end of file
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Net
;
using
System.Security
;
using
Breeze.Common.JsonErrors
;
using
Microsoft.AspNetCore.Mvc
;
using
Breeze.Wallet.Models
;
using
NBitcoin
;
using
Stratis.Bitcoin.Connection
;
namespace
Breeze.Wallet.Controllers
{
/// <summary>
/// Controller providing operations on a wallet.
/// </summary>
[
Route
(
"api/v{version:apiVersion}/[controller]"
)]
public
class
WalletController
:
Controller
{
private
readonly
IWalletManager
walletManager
;
private
readonly
ITracker
tracker
;
private
readonly
CoinType
coinType
;
private
readonly
Network
network
;
private
readonly
ConnectionManager
connectionManager
;
private
readonly
ConcurrentChain
chain
;
public
WalletController
(
IWalletManager
walletManager
,
ITracker
tracker
,
ConnectionManager
connectionManager
,
Network
network
,
ConcurrentChain
chain
)
{
this
.
walletManager
=
walletManager
;
this
.
tracker
=
tracker
;
this
.
connectionManager
=
connectionManager
;
this
.
network
=
network
;
this
.
coinType
=
(
CoinType
)
network
.
Consensus
.
CoinType
;
this
.
chain
=
chain
;
}
/// <summary>
/// Creates a new wallet on the local machine.
/// </summary>
/// <param name="request">The object containing the parameters used to create the wallet.</param>
/// <returns>A JSON object containing the mnemonic created for the new wallet.</returns>
[
Route
(
"create"
)]
[
HttpPost
]
public
IActionResult
Create
([
FromBody
]
WalletCreationRequest
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
{
// get the wallet folder
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
Mnemonic
mnemonic
=
this
.
walletManager
.
CreateWallet
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
,
request
.
Network
);
return
this
.
Json
(
mnemonic
.
ToString
());
}
catch
(
InvalidOperationException
e
)
{
// indicates that this wallet already exists
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
Conflict
,
"This wallet already exists."
,
e
.
ToString
());
}
}
/// <summary>
/// Loads a wallet previously created by the user.
/// </summary>
/// <param name="request">The name of the wallet to load.</param>
/// <returns></returns>
[
Route
(
"load"
)]
[
HttpPost
]
public
IActionResult
Load
([
FromBody
]
WalletLoadRequest
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
{
// get the wallet folder
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
Wallet
wallet
=
this
.
walletManager
.
LoadWallet
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
);
return
this
.
Ok
();
}
catch
(
FileNotFoundException
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
NotFound
,
"This wallet was not found at the specified location."
,
e
.
ToString
());
}
catch
(
SecurityException
e
)
{
// indicates that the password is wrong
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
Forbidden
,
"Wrong password, please try again."
,
e
.
ToString
());
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Recovers a wallet.
/// </summary>
/// <param name="request">The object containing the parameters used to recover a wallet.</param>
/// <returns></returns>
[
Route
(
"recover"
)]
[
HttpPost
]
public
IActionResult
Recover
([
FromBody
]
WalletRecoveryRequest
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
{
// get the wallet folder
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
Wallet
wallet
=
this
.
walletManager
.
RecoverWallet
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
,
request
.
Network
,
request
.
Mnemonic
,
request
.
CreationDate
,
null
);
// start syncing the wallet from the creation date
this
.
tracker
.
SyncFrom
(
request
.
CreationDate
);
return
this
.
Ok
();
}
catch
(
InvalidOperationException
e
)
{
// indicates that this wallet already exists
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
Conflict
,
"This wallet already exists."
,
e
.
ToString
());
}
catch
(
FileNotFoundException
e
)
{
// indicates that this wallet does not exist
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
NotFound
,
"Wallet not found."
,
e
.
ToString
());
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Get some general info about a wallet.
/// </summary>
/// <param name="request">The name of the wallet.</param>
/// <returns></returns>
[
Route
(
"general-info"
)]
[
HttpGet
]
public
IActionResult
GetGeneralInfo
([
FromQuery
]
WalletName
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
{
Wallet
wallet
=
this
.
walletManager
.
GetWallet
(
request
.
Name
);
var
model
=
new
WalletGeneralInfoModel
{
Network
=
wallet
.
Network
,
WalletFilePath
=
wallet
.
WalletFilePath
,
CreationTime
=
wallet
.
CreationTime
,
LastBlockSyncedHeight
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
this
.
coinType
).
LastBlockSyncedHeight
,
ConnectedNodes
=
this
.
connectionManager
.
ConnectedNodes
.
Count
(),
ChainTip
=
this
.
chain
.
Tip
.
Height
,
IsDecrypted
=
true
};
return
this
.
Json
(
model
);
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Retrieves the history of a wallet.
/// </summary>
/// <param name="request">The request parameters.</param>
/// <returns></returns>
[
Route
(
"history"
)]
[
HttpGet
]
public
IActionResult
GetHistory
([
FromQuery
]
WalletHistoryRequest
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
{
WalletHistoryModel
model
=
new
WalletHistoryModel
{
TransactionsHistory
=
new
List
<
TransactionItemModel
>()
};
// get transactions contained in the wallet
var
addresses
=
this
.
walletManager
.
GetHistoryByCoinType
(
request
.
WalletName
,
request
.
CoinType
);
foreach
(
var
address
in
addresses
.
Where
(
a
=>
!
a
.
IsChangeAddress
()))
{
foreach
(
var
transaction
in
address
.
Transactions
)
{
TransactionItemModel
item
=
new
TransactionItemModel
();
if
(
transaction
.
Amount
>
Money
.
Zero
)
{
item
.
Type
=
TransactionItemType
.
Received
;
item
.
ToAddress
=
address
.
Address
;
item
.
Amount
=
transaction
.
Amount
;
}
else
{
item
.
Type
=
TransactionItemType
.
Send
;
item
.
Amount
=
Money
.
Zero
;
if
(
transaction
.
Payments
!=
null
)
{
item
.
Payments
=
new
List
<
PaymentDetailModel
>();
foreach
(
var
payment
in
transaction
.
Payments
)
{
item
.
Payments
.
Add
(
new
PaymentDetailModel
{
DestinationAddress
=
payment
.
DestinationAddress
,
Amount
=
payment
.
Amount
});
item
.
Amount
+=
payment
.
Amount
;
}
}
var
changeAddress
=
addresses
.
Single
(
a
=>
a
.
IsChangeAddress
()
&&
a
.
Transactions
.
Any
(
t
=>
t
.
Id
==
transaction
.
Id
));
item
.
Fee
=
transaction
.
Amount
.
Abs
()
-
item
.
Amount
-
changeAddress
.
Transactions
.
First
(
t
=>
t
.
Id
==
transaction
.
Id
).
Amount
;
}
item
.
Id
=
transaction
.
Id
;
item
.
Timestamp
=
transaction
.
CreationTime
;
item
.
ConfirmedInBlock
=
transaction
.
BlockHeight
;
model
.
TransactionsHistory
.
Add
(
item
);
}
}
model
.
TransactionsHistory
=
model
.
TransactionsHistory
.
OrderByDescending
(
t
=>
t
.
Timestamp
).
ToList
();
return
this
.
Json
(
model
);
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Gets the balance of a wallet.
/// </summary>
/// <param name="request">The request parameters.</param>
/// <returns></returns>
[
Route
(
"balance"
)]
[
HttpGet
]
public
IActionResult
GetBalance
([
FromQuery
]
WalletBalanceRequest
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
{
WalletBalanceModel
model
=
new
WalletBalanceModel
{
AccountsBalances
=
new
List
<
AccountBalance
>()
};
var
accounts
=
this
.
walletManager
.
GetAccountsByCoinType
(
request
.
WalletName
,
request
.
CoinType
).
ToList
();
foreach
(
var
account
in
accounts
)
{
var
allTransactions
=
account
.
ExternalAddresses
.
SelectMany
(
a
=>
a
.
Transactions
)
.
Concat
(
account
.
InternalAddresses
.
SelectMany
(
i
=>
i
.
Transactions
)).
ToList
();
AccountBalance
balance
=
new
AccountBalance
{
CoinType
=
request
.
CoinType
,
Name
=
account
.
Name
,
HdPath
=
account
.
HdPath
,
AmountConfirmed
=
allTransactions
.
Where
(
t
=>
t
.
IsConfirmed
()).
Sum
(
t
=>
t
.
Amount
),
AmountUnconfirmed
=
allTransactions
.
Where
(
t
=>
!
t
.
IsConfirmed
()).
Sum
(
t
=>
t
.
Amount
)
};
model
.
AccountsBalances
.
Add
(
balance
);
}
return
this
.
Json
(
model
);
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Builds a transaction.
/// </summary>
/// <param name="request">The transaction parameters.</param>
/// <returns>All the details of the transaction, including the hex used to execute it.</returns>
[
Route
(
"build-transaction"
)]
[
HttpPost
]
public
IActionResult
BuildTransaction
([
FromBody
]
BuildTransactionRequest
request
)
{
// checks the request is valid
if
(!
this
.
ModelState
.
IsValid
)
{
var
errors
=
this
.
ModelState
.
Values
.
SelectMany
(
e
=>
e
.
Errors
.
Select
(
m
=>
m
.
ErrorMessage
));
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
"Formatting error"
,
string
.
Join
(
Environment
.
NewLine
,
errors
));
}
try
{
var
transactionResult
=
this
.
walletManager
.
BuildTransaction
(
request
.
WalletName
,
request
.
AccountName
,
request
.
CoinType
,
request
.
Password
,
request
.
DestinationAddress
,
request
.
Amount
,
request
.
FeeType
,
request
.
AllowUnconfirmed
);
var
model
=
new
WalletBuildTransactionModel
{
Hex
=
transactionResult
.
hex
,
Fee
=
transactionResult
.
fee
,
TransactionId
=
transactionResult
.
transactionId
};
return
this
.
Json
(
model
);
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Sends a transaction.
/// </summary>
/// <param name="request">The hex representing the transaction.</param>
/// <returns></returns>
[
Route
(
"send-transaction"
)]
[
HttpPost
]
public
IActionResult
SendTransaction
([
FromBody
]
SendTransactionRequest
request
)
{
// checks the request is valid
if
(!
this
.
ModelState
.
IsValid
)
{
var
errors
=
this
.
ModelState
.
Values
.
SelectMany
(
e
=>
e
.
Errors
.
Select
(
m
=>
m
.
ErrorMessage
));
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
"Formatting error"
,
string
.
Join
(
Environment
.
NewLine
,
errors
));
}
try
{
var
result
=
this
.
walletManager
.
SendTransaction
(
request
.
Hex
);
if
(
result
)
{
return
this
.
Ok
();
}
return
this
.
StatusCode
((
int
)
HttpStatusCode
.
BadRequest
);
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Lists all the wallet files found under the default folder.
/// </summary>
/// <returns>A list of the wallets files found.</returns>
[
Route
(
"files"
)]
[
HttpGet
]
public
IActionResult
ListWalletsFiles
()
{
try
{
DirectoryInfo
walletsFolder
=
GetWalletFolder
();
WalletFileModel
model
=
new
WalletFileModel
{
WalletsPath
=
walletsFolder
.
FullName
,
WalletsFiles
=
Directory
.
EnumerateFiles
(
walletsFolder
.
FullName
,
"*.json"
,
SearchOption
.
TopDirectoryOnly
).
Select
(
p
=>
Path
.
GetFileName
(
p
))
};
return
this
.
Json
(
model
);
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Creates a new account for a wallet.
/// </summary>
/// <returns>An account name.</returns>
[
Route
(
"account"
)]
[
HttpPost
]
public
IActionResult
CreateNewAccount
([
FromBody
]
GetUnusedAccountModel
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
.
GetUnusedAccount
(
request
.
WalletName
,
request
.
CoinType
,
request
.
Password
);
return
this
.
Json
(
result
.
Name
);
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Gets an unused address.
/// </summary>
/// <returns>The last created and unused address or creates a new address (in Base58 format).</returns>
[
Route
(
"address"
)]
[
HttpGet
]
public
IActionResult
GetUnusedAddress
([
FromQuery
]
GetUnusedAddressModel
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
.
GetUnusedAddress
(
request
.
WalletName
,
request
.
CoinType
,
request
.
AccountName
);
return
this
.
Json
(
result
);
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Gets a folder.
/// </summary>
/// <returns>The path folder of the folder.</returns>
/// <remarks>The folder is created if it doesn't exist.</remarks>
private
static
DirectoryInfo
GetWalletFolder
(
string
folderPath
=
null
)
{
if
(
string
.
IsNullOrEmpty
(
folderPath
))
{
folderPath
=
WalletManager
.
GetDefaultWalletFolderPath
();
}
return
Directory
.
CreateDirectory
(
folderPath
);
}
}
}
Breeze/src/Breeze.Wallet/Helpers/WalletHelpers.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.Text
;
using
NBitcoin
;
using
Stratis.Bitcoin.Utilities
;
namespace
Breeze.Wallet.Helpers
{
/// <summary>
/// Contains a collection of helpers methods.
/// </summary>
public
static
class
WalletHelpers
{
/// <summary>
/// Get the network on which to operate.
/// </summary>
/// <param name="network">The network</param>
/// <returns>A <see cref="Network"/> object.</returns>
public
static
Network
GetNetwork
(
string
network
)
{
Guard
.
NotEmpty
(
network
,
nameof
(
network
));
switch
(
network
.
ToLowerInvariant
())
{
case
"main"
:
case
"mainnet"
:
return
Network
.
Main
;
case
"test"
:
case
"testnet"
:
return
Network
.
TestNet
;
default
:
throw
new
ArgumentException
(
$"Network '
{
network
}
' is not a valid network."
);
}
}
}
}
Breeze/src/Breeze.Wallet/ITracker.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Threading.Tasks
;
using
NBitcoin
;
namespace
Breeze.Wallet
{
public
interface
ITracker
{
/// <summary>
/// Initializes the tracker.
/// </summary>
/// <returns></returns>
Task
Initialize
();
/// <summary>
/// Waits for the chain to download.
/// </summary>
/// <returns></returns>
Task
WaitForChainDownloadAsync
();
/// <summary>
/// Synchronize the wallet starting from the date passed as a parameter.
/// </summary>
/// <param name="date">The date from which to start the sync process.</param>
/// <returns>The height of the block sync will start from</returns>
void
SyncFrom
(
DateTime
date
);
/// <summary>
/// Synchronize the wallet starting from the height passed as a parameter.
/// </summary>
/// <param name="height">The height from which to start the sync process.</param>
/// <returns>The height of the block sync will start from</returns>
void
SyncFrom
(
int
height
);
}
}
Breeze/src/Breeze.Wallet/IWalletManager.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
Breeze.Wallet.Models
;
using
NBitcoin
;
namespace
Breeze.Wallet
{
/// <summary>
/// Interface for a manager providing operations on wallets.
/// </summary>
public
interface
IWalletManager
:
IDisposable
{
/// <summary>
/// Creates a wallet and persist it as a file on the local system.
/// </summary>
/// <param name="password">The password used to encrypt sensitive info.</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 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>
Mnemonic
CreateWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
passphrase
=
null
);
/// <summary>
/// Loads a wallet from a file.
/// </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.</returns>
Wallet
LoadWallet
(
string
password
,
string
folderPath
,
string
name
);
/// <summary>
/// Recovers a wallet.
/// </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>
/// <param name="passphrase">The passphrase used in the seed.</param>
/// <param name="creationTime">The date and time this wallet was created.</param>
/// <returns>The recovered wallet.</returns>
Wallet
RecoverWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
mnemonic
,
DateTime
creationTime
,
string
passphrase
=
null
);
/// <summary>
/// Deletes a wallet.
/// </summary>
/// <param name="walletFilePath">The location of the wallet file.</param>
void
DeleteWallet
(
string
walletFilePath
);
/// <summary>
/// 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="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>
/// <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="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
);
/// <summary>
/// Gets a collection of addresses containing transactions for this coin.
/// </summary>
/// <param name="walletName">The name of the wallet to get history from.</param>
/// <param name="coinType">Type of the coin.</param>
/// <returns></returns>
IEnumerable
<
HdAddress
>
GetHistoryByCoinType
(
string
walletName
,
CoinType
coinType
);
/// <summary>
/// Gets a collection of addresses containing transactions for this coin.
/// </summary>
/// <param name="wallet">The wallet to get history from.</param>
/// <param name="coinType">Type of the coin.</param>
/// <returns></returns>
IEnumerable
<
HdAddress
>
GetHistoryByCoinType
(
Wallet
wallet
,
CoinType
coinType
);
/// <summary>
/// Gets some general information about a wallet.
/// </summary>
/// <param name="walletName">The name of the wallet.</param>
/// <returns></returns>
Wallet
GetWallet
(
string
walletName
);
/// <summary>
/// Gets a list of accounts filtered by coin type.
/// </summary>
/// <param name="walletName">The name of the wallet to look into.</param>
/// <param name="coinType">The type of coin to filter by.</param>
/// <returns></returns>
IEnumerable
<
HdAccount
>
GetAccountsByCoinType
(
string
walletName
,
CoinType
coinType
);
/// <summary>
/// Builds a transaction to be sent to the network.
/// </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>
/// <param name="password">The password used to decrypt the private key.</param>
/// <param name="destinationAddress">The destination address to send the funds to.</param>
/// <param name="amount">The amount of funds to be sent.</param>
/// <param name="feeType">The type of fee to be included.</param>
/// <param name="allowUnconfirmed">Whether or not we allow this transaction to rely on unconfirmed outputs.</param>
/// <returns></returns>
(
string
hex
,
uint256
transactionId
,
Money
fee
)
BuildTransaction
(
string
walletName
,
string
accountName
,
CoinType
coinType
,
string
password
,
string
destinationAddress
,
Money
amount
,
string
feeType
,
bool
allowUnconfirmed
);
/// <summary>
/// Sends a transaction to the network.
/// </summary>
/// <param name="transactionHex">The hex of the transaction.</param>
/// <returns></returns>
bool
SendTransaction
(
string
transactionHex
);
/// <summary>
/// Processes a block received from the network.
/// </summary>
/// <param name="height">The height of the block in the blockchain.</param>
/// <param name="block">The block.</param>
void
ProcessBlock
(
int
height
,
Block
block
);
/// <summary>
/// Processes a transaction received from the network.
/// </summary>
/// <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="block">The block in which this transaction was included.</param>
void
ProcessTransaction
(
Transaction
transaction
,
int
?
blockHeight
=
null
,
Block
block
=
null
);
/// <summary>
/// Saves the wallet into the file system.
/// </summary>
/// <param name="wallet">The wallet to save.</param>
void
SaveToFile
(
Wallet
wallet
);
/// <summary>
/// Saves all the loaded wallets into the file system.
/// </summary>
void
SaveToFile
();
/// <summary>
/// Updates the wallet with the height of the last block synced.
/// </summary>
/// <param name="wallet">The wallet to update.</param>
/// <param name="height">The height of the last block synced.</param>
void
UpdateLastBlockSyncedHeight
(
Wallet
wallet
,
int
height
);
/// <summary>
/// Updates all the loaded wallets with the height of the last block synced.
/// </summary>
/// <param name="height">The height of the last block synced.</param>
void
UpdateLastBlockSyncedHeight
(
int
height
);
}
}
\ No newline at end of file
Breeze/src/Breeze.Wallet/JsonConverters/ByteArrayConverter.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.Text
;
using
Newtonsoft.Json
;
namespace
Breeze.Wallet.JsonConverters
{
/// <summary>
/// Converter used to convert <see cref="byte"/> arrays to and from JSON.
/// </summary>
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
public
class
ByteArrayConverter
:
JsonConverter
{
/// <inheritdoc />
public
override
bool
CanConvert
(
Type
objectType
)
{
return
objectType
==
typeof
(
byte
[]);
}
/// <inheritdoc />
public
override
object
ReadJson
(
JsonReader
reader
,
Type
objectType
,
object
existingValue
,
JsonSerializer
serializer
)
{
return
Convert
.
FromBase64String
((
string
)
reader
.
Value
);
}
/// <inheritdoc />
public
override
void
WriteJson
(
JsonWriter
writer
,
object
value
,
JsonSerializer
serializer
)
{
writer
.
WriteValue
(
Convert
.
ToBase64String
((
byte
[])
value
));
}
}
}
Breeze/src/Breeze.Wallet/JsonConverters/DateTimeOffsetConverter.cs
deleted
100644 → 0
View file @
3435ae71
using
Newtonsoft.Json
;
using
System
;
namespace
Breeze.Wallet.JsonConverters
{
/// <summary>
/// Converter used to convert <see cref="DateTimeOffset"/> to and from Unix time.
/// </summary>
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
public
class
DateTimeOffsetConverter
:
JsonConverter
{
/// <inheritdoc />
public
override
bool
CanConvert
(
Type
objectType
)
{
return
objectType
==
typeof
(
DateTimeOffset
);
}
/// <inheritdoc />
public
override
object
ReadJson
(
JsonReader
reader
,
Type
objectType
,
object
existingValue
,
JsonSerializer
serializer
)
{
return
DateTimeOffset
.
FromUnixTimeSeconds
(
long
.
Parse
((
string
)
reader
.
Value
));
}
/// <inheritdoc />
public
override
void
WriteJson
(
JsonWriter
writer
,
object
value
,
JsonSerializer
serializer
)
{
writer
.
WriteValue
(((
DateTimeOffset
)
value
).
ToUnixTimeSeconds
().
ToString
());
}
}
}
Breeze/src/Breeze.Wallet/JsonConverters/NetworkConverter.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
Breeze.Wallet.Helpers
;
using
NBitcoin
;
using
Newtonsoft.Json
;
namespace
Breeze.Wallet.JsonConverters
{
/// <summary>
/// Converter used to convert <see cref="Network"/> to and from JSON.
/// </summary>
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
public
class
NetworkConverter
:
JsonConverter
{
/// <inheritdoc />
public
override
bool
CanConvert
(
Type
objectType
)
{
return
objectType
==
typeof
(
Network
);
}
/// <inheritdoc />
public
override
object
ReadJson
(
JsonReader
reader
,
Type
objectType
,
object
existingValue
,
JsonSerializer
serializer
)
{
return
WalletHelpers
.
GetNetwork
((
string
)
reader
.
Value
);
}
/// <inheritdoc />
public
override
void
WriteJson
(
JsonWriter
writer
,
object
value
,
JsonSerializer
serializer
)
{
writer
.
WriteValue
(((
Network
)
value
).
ToString
());
}
}
}
Breeze/src/Breeze.Wallet/WalletFeature.cs
→
Breeze/src/Breeze.Wallet/
Light
WalletFeature.cs
View file @
3a244ac5
using
Stratis.Bitcoin.Builder.Feature
;
using
Breeze.Wallet.Controllers
;
using
Microsoft.Extensions.DependencyInjection
;
using
Stratis.Bitcoin.Builder
;
using
Stratis.Bitcoin.Logging
;
using
Microsoft.Extensions.Logging
;
using
Serilog
;
using
Stratis.Bitcoin.Wallet
;
using
Stratis.Bitcoin.Wallet.Controllers
;
namespace
Breeze.Wallet
{
public
class
WalletFeature
:
FullNodeFeature
public
class
Light
WalletFeature
:
FullNodeFeature
{
private
readonly
ITracker
tracker
;
private
readonly
IWalletManager
walletManager
;
private
readonly
LightWalletSyncManager
lightWalletSyncManager
;
public
WalletFeature
(
ITracker
tracker
,
IWalletManager
walletManager
)
{
this
.
tracker
=
tracker
;
this
.
walletManager
=
walletManager
;
}
public
LightWalletFeature
(
LightWalletSyncManager
lightWalletSyncManager
)
{
this
.
lightWalletSyncManager
=
lightWalletSyncManager
;
}
public
override
void
Start
()
{
this
.
track
er
.
Initialize
();
this
.
lightWalletSyncManag
er
.
Initialize
();
}
public
override
void
Stop
()
{
this
.
walletManager
.
Dispose
();
base
.
Stop
();
}
}
public
static
class
WalletFeatureExtension
{
public
static
IFullNodeBuilder
UseWallet
(
this
IFullNodeBuilder
fullNodeBuilder
)
public
static
class
Light
WalletFeatureExtension
{
public
static
IFullNodeBuilder
Use
Light
Wallet
(
this
IFullNodeBuilder
fullNodeBuilder
)
{
fullNodeBuilder
.
ConfigureFeature
(
features
=>
{
features
.
AddFeature
<
WalletFeature
>()
.
AddFeature
<
Light
WalletFeature
>()
.
FeatureServices
(
services
=>
{
var
loggerFactory
=
Logs
.
LoggerFactory
;
loggerFactory
.
AddFile
(
"Logs/Breeze-{Date}.json"
,
isJson
:
true
,
minimumLevel
:
LogLevel
.
Debug
,
fileSizeLimitBytes
:
10000000
);
services
.
AddSingleton
<
IWalletSyncManager
,
LightWalletSyncManager
>();
services
.
AddSingleton
<
IWalletManager
,
WalletManager
>();
services
.
AddSingleton
<
WalletController
>();
services
.
AddSingleton
<
ITracker
,
Tracker
>();
services
.
AddSingleton
<
ILoggerFactory
>(
loggerFactory
);
services
.
AddSingleton
<
IWalletManager
,
WalletManager
>();
services
.
AddSingleton
<
WalletController
>();
});
});
});
return
fullNodeBuilder
;
...
...
Breeze/src/Breeze.Wallet/
Track
er.cs
→
Breeze/src/Breeze.Wallet/
LightWalletSyncManag
er.cs
View file @
3a244ac5
using
System
;
using
System.Collections.Generic
;
using
System.Collections.ObjectModel
;
using
System.Linq
;
using
System.Reactive.Linq
;
using
System.Text
;
using
System.Threading
;
using
System.Threading.Tasks
;
using
Breeze.Wallet.Notifications
;
using
Microsoft.Extensions.Logging
;
using
NBitcoin
;
using
Stratis.Bitcoin
;
using
Stratis.Bitcoin.Notifications
;
using
Stratis.Bitcoin.Utilities
;
using
Stratis.Bitcoin.Wallet
;
namespace
Breeze.Wallet
{
public
class
Tracker
:
ITrack
er
public
class
LightWalletSyncManager
:
WalletSyncManag
er
{
private
readonly
WalletManager
walletManager
;
private
readonly
ConcurrentChain
chain
;
private
readonly
Signals
signals
;
private
readonly
BlockNotification
blockNotification
;
private
readonly
CoinType
coinType
;
private
readonly
ILogger
logger
;
public
Tracker
(
ILoggerFactory
loggerFactory
,
IWalletManager
walletManager
,
ConcurrentChain
chain
,
Signals
signals
,
BlockNotification
blockNotification
,
Network
network
)
public
LightWalletSyncManager
(
ILoggerFactory
loggerFactory
,
IWalletManager
walletManager
,
ConcurrentChain
chain
,
Network
network
,
BlockNotification
blockNotification
):
base
(
loggerFactory
,
walletManager
,
chain
,
network
)
{
this
.
walletManager
=
walletManager
as
WalletManager
;
this
.
chain
=
chain
;
this
.
signals
=
signals
;
this
.
blockNotification
=
blockNotification
;
this
.
coinType
=
(
CoinType
)
network
.
Consensus
.
CoinType
;
this
.
logger
=
loggerFactory
.
CreateLogger
(
this
.
GetType
().
FullName
);
}
/// <inheritdoc />
public
async
Task
Initialize
()
public
override
void
Initialize
()
{
// get the chain headers. This needs to be up-to-date before we really do anything
await
this
.
WaitForChainDownloadAsync
();
// subscribe to receiving blocks and transactions
BlockSubscriber
sub
=
new
BlockSubscriber
(
this
.
signals
.
Blocks
,
new
BlockObserver
(
this
.
chain
,
this
.
walletManager
));
sub
.
Subscribe
();
TransactionSubscriber
txSub
=
new
TransactionSubscriber
(
this
.
signals
.
Transactions
,
new
TransactionObserver
(
this
.
walletManager
));
txSub
.
Subscribe
();
this
.
WaitForChainDownloadAsync
().
GetAwaiter
().
GetResult
();
// start syncing blocks
var
bestHeightForSyncing
=
this
.
FindBestHeightForSyncing
();
this
.
SyncFrom
(
bestHeightForSyncing
);
this
.
logger
.
LogInformation
(
$"Tracker initialized. Syncing from
{
bestHeightForSyncing
}
."
);
}
private
int
FindBestHeightForSyncing
()
...
...
@@ -91,7 +75,7 @@ namespace Breeze.Wallet
}
/// <inheritdoc />
public
void
SyncFrom
(
DateTime
date
)
public
override
void
SyncFrom
(
DateTime
date
)
{
int
blockSyncStart
=
this
.
chain
.
GetHeightAtTime
(
date
);
...
...
@@ -100,7 +84,7 @@ namespace Breeze.Wallet
}
/// <inheritdoc />
public
void
SyncFrom
(
int
height
)
public
override
void
SyncFrom
(
int
height
)
{
this
.
blockNotification
.
SyncFrom
(
this
.
chain
.
GetBlock
(
height
).
HashBlock
);
}
...
...
Breeze/src/Breeze.Wallet/Models/RequestModels.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections
;
using
System.Collections.Generic
;
using
System.ComponentModel.DataAnnotations
;
using
Newtonsoft.Json
;
using
Newtonsoft.Json.Converters
;
namespace
Breeze.Wallet.Models
{
public
class
RequestModel
{
public
override
string
ToString
()
{
return
JsonConvert
.
SerializeObject
(
this
,
Formatting
.
Indented
);
}
}
/// <summary>
/// Object used to create a new wallet
/// </summary>
public
class
WalletCreationRequest
:
RequestModel
{
[
Required
(
ErrorMessage
=
"A password is required."
)]
public
string
Password
{
get
;
set
;
}
public
string
Network
{
get
;
set
;
}
public
string
FolderPath
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"The name of the wallet to create is missing."
)]
public
string
Name
{
get
;
set
;
}
}
public
class
WalletLoadRequest
:
RequestModel
{
[
Required
(
ErrorMessage
=
"A password is required."
)]
public
string
Password
{
get
;
set
;
}
public
string
FolderPath
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"The name of the wallet is missing."
)]
public
string
Name
{
get
;
set
;
}
}
public
class
WalletRecoveryRequest
:
RequestModel
{
[
Required
(
ErrorMessage
=
"A mnemonic is required."
)]
public
string
Mnemonic
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"A password is required."
)]
public
string
Password
{
get
;
set
;
}
public
string
FolderPath
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"The name of the wallet is missing."
)]
public
string
Name
{
get
;
set
;
}
public
string
Network
{
get
;
set
;
}
[
JsonConverter
(
typeof
(
IsoDateTimeConverter
))]
public
DateTime
CreationDate
{
get
;
set
;
}
}
public
class
WalletHistoryRequest
:
RequestModel
{
[
Required
(
ErrorMessage
=
"The name of the wallet is missing."
)]
public
string
WalletName
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"The type of coin for which history is requested is missing."
)]
public
CoinType
CoinType
{
get
;
set
;
}
}
public
class
WalletBalanceRequest
:
RequestModel
{
[
Required
(
ErrorMessage
=
"The name of the wallet is missing."
)]
public
string
WalletName
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"The type of coin for which history is requested is missing."
)]
public
CoinType
CoinType
{
get
;
set
;
}
}
public
class
WalletName
:
RequestModel
{
[
Required
(
ErrorMessage
=
"The name of the wallet is missing."
)]
public
string
Name
{
get
;
set
;
}
}
public
class
BuildTransactionRequest
:
RequestModel
{
[
Required
(
ErrorMessage
=
"The name of the wallet is missing."
)]
public
string
WalletName
{
get
;
set
;
}
[
Required
]
public
CoinType
CoinType
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"The name of the account is missing."
)]
public
string
AccountName
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"A password is required."
)]
public
string
Password
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"A destination address is required."
)]
public
string
DestinationAddress
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"An amount is required."
)]
public
string
Amount
{
get
;
set
;
}
[
Required
(
ErrorMessage
=
"A fee type is required. It can be 'low', 'medium' or 'high'."
)]
public
string
FeeType
{
get
;
set
;
}
public
bool
AllowUnconfirmed
{
get
;
set
;
}
}
public
class
SendTransactionRequest
:
RequestModel
{
[
Required
(
ErrorMessage
=
"A transaction in hexadecimal format is required."
)]
public
string
Hex
{
get
;
set
;
}
}
public
class
GetUnusedAddressModel
:
RequestModel
{
/// <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
:
RequestModel
{
/// <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/Models/WalletBalanceModel.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Threading.Tasks
;
using
NBitcoin
;
using
Newtonsoft.Json
;
namespace
Breeze.Wallet.Models
{
public
class
WalletBalanceModel
{
[
JsonProperty
(
PropertyName
=
"balances"
)]
public
List
<
AccountBalance
>
AccountsBalances
{
get
;
set
;
}
}
public
class
AccountBalance
{
[
JsonProperty
(
PropertyName
=
"accountName"
)]
public
string
Name
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"accountHdPath"
)]
public
string
HdPath
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"coinType"
)]
public
CoinType
CoinType
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"amountConfirmed"
)]
public
Money
AmountConfirmed
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"amountUnconfirmed"
)]
public
Money
AmountUnconfirmed
{
get
;
set
;
}
}
}
Breeze/src/Breeze.Wallet/Models/WalletBuildTransactionModel.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Threading.Tasks
;
using
NBitcoin
;
using
Newtonsoft.Json
;
namespace
Breeze.Wallet.Models
{
public
class
WalletBuildTransactionModel
{
[
JsonProperty
(
PropertyName
=
"fee"
)]
public
Money
Fee
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"hex"
)]
public
string
Hex
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"transactionId"
)]
public
uint256
TransactionId
{
get
;
set
;
}
}
}
Breeze/src/Breeze.Wallet/Models/WalletFileModel.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.Text
;
using
Newtonsoft.Json
;
namespace
Breeze.Wallet.Models
{
public
class
WalletFileModel
{
[
JsonProperty
(
PropertyName
=
"walletsPath"
)]
public
string
WalletsPath
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"walletsFiles"
)]
public
IEnumerable
<
string
>
WalletsFiles
{
get
;
set
;
}
}
}
Breeze/src/Breeze.Wallet/Models/WalletGeneralInfoModel.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
Breeze.Wallet.JsonConverters
;
using
NBitcoin
;
using
Newtonsoft.Json
;
namespace
Breeze.Wallet.Models
{
public
class
WalletGeneralInfoModel
{
[
JsonProperty
(
PropertyName
=
"walletFilePath"
)]
public
string
WalletFilePath
{
get
;
set
;
}
[
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
;
}
[
JsonProperty
(
PropertyName
=
"isDecrypted"
)]
public
bool
IsDecrypted
{
get
;
set
;
}
/// <summary>
/// The height of the last block that was synced.
/// </summary>
[
JsonProperty
(
PropertyName
=
"lastBlockSyncedHeight"
)]
public
int
?
LastBlockSyncedHeight
{
get
;
set
;
}
/// <summary>
/// The total number of blocks.
/// </summary>
[
JsonProperty
(
PropertyName
=
"chainTip"
)]
public
int
?
ChainTip
{
get
;
set
;
}
/// <summary>
/// The total number of nodes that we're connected to.
/// </summary>
[
JsonProperty
(
PropertyName
=
"connectedNodes"
)]
public
int
ConnectedNodes
{
get
;
set
;
}
}
}
Breeze/src/Breeze.Wallet/Models/WalletHistoryModel.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
Breeze.Wallet.JsonConverters
;
using
NBitcoin
;
using
NBitcoin.JsonConverters
;
using
Newtonsoft.Json
;
using
Newtonsoft.Json.Converters
;
using
Newtonsoft.Json.Serialization
;
namespace
Breeze.Wallet.Models
{
public
class
WalletHistoryModel
{
[
JsonProperty
(
PropertyName
=
"transactionsHistory"
)]
public
List
<
TransactionItemModel
>
TransactionsHistory
{
get
;
set
;
}
}
public
class
TransactionItemModel
{
[
JsonProperty
(
PropertyName
=
"type"
)]
[
JsonConverter
(
typeof
(
StringEnumConverter
),
true
)]
public
TransactionItemType
Type
{
get
;
set
;
}
/// <summary>
/// The Base58 representation of this address.
/// </summary>
[
JsonProperty
(
PropertyName
=
"toAddress"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
public
string
ToAddress
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"id"
)]
[
JsonConverter
(
typeof
(
UInt256JsonConverter
))]
public
uint256
Id
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"amount"
)]
public
Money
Amount
{
get
;
set
;
}
/// <summary>
/// A list of payments made out in this transaction.
/// </summary>
[
JsonProperty
(
PropertyName
=
"payments"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
public
ICollection
<
PaymentDetailModel
>
Payments
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"fee"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
public
Money
Fee
{
get
;
set
;
}
/// <summary>
/// The height of the block in which this transaction was confirmed.
/// </summary>
[
JsonProperty
(
PropertyName
=
"confirmedInBlock"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
public
int
?
ConfirmedInBlock
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"timestamp"
)]
[
JsonConverter
(
typeof
(
DateTimeOffsetConverter
))]
public
DateTimeOffset
Timestamp
{
get
;
set
;
}
}
public
class
PaymentDetailModel
{
/// <summary>
/// The Base58 representation of the destination address.
/// </summary>
[
JsonProperty
(
PropertyName
=
"destinationAddress"
)]
public
string
DestinationAddress
{
get
;
set
;
}
/// <summary>
/// The transaction amount.
/// </summary>
[
JsonProperty
(
PropertyName
=
"amount"
)]
[
JsonConverter
(
typeof
(
MoneyJsonConverter
))]
public
Money
Amount
{
get
;
set
;
}
}
public
enum
TransactionItemType
{
Received
,
Send
}
}
Breeze/src/Breeze.Wallet/Models/WalletModel.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Threading.Tasks
;
using
Newtonsoft.Json
;
namespace
Breeze.Wallet.Models
{
public
class
WalletModel
{
[
JsonProperty
(
PropertyName
=
"network"
)]
public
string
Network
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"fileName"
)]
public
string
FileName
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"addresses"
)]
public
IEnumerable
<
string
>
Addresses
{
get
;
set
;
}
}
}
Breeze/src/Breeze.Wallet/Notifications/BlockObserver.cs
deleted
100644 → 0
View file @
3435ae71
using
NBitcoin
;
using
Stratis.Bitcoin
;
namespace
Breeze.Wallet.Notifications
{
/// <summary>
/// Observer that receives notifications about the arrival of new <see cref="Block"/>s.
/// </summary>
public
class
BlockObserver
:
SignalObserver
<
Block
>
{
private
readonly
ConcurrentChain
chain
;
private
readonly
IWalletManager
walletManager
;
public
BlockObserver
(
ConcurrentChain
chain
,
IWalletManager
walletManager
)
{
this
.
chain
=
chain
;
this
.
walletManager
=
walletManager
;
}
/// <summary>
/// Manages what happens when a new block is received.
/// </summary>
/// <param name="block">The new block</param>
protected
override
void
OnNextCore
(
Block
block
)
{
var
hash
=
block
.
Header
.
GetHash
();
var
height
=
this
.
chain
.
GetBlock
(
hash
).
Height
;
this
.
walletManager
.
ProcessBlock
(
height
,
block
);
}
}
}
Breeze/src/Breeze.Wallet/Notifications/BlockSubscriber.cs
deleted
100644 → 0
View file @
3435ae71
using
Stratis.Bitcoin
;
using
System
;
using
NBitcoin
;
namespace
Breeze.Wallet.Notifications
{
/// <summary>
/// Manages the subscription of the block observer to the block signaler.
/// </summary>
public
class
BlockSubscriber
{
private
readonly
ISignaler
<
Block
>
signaler
;
private
readonly
BlockObserver
observer
;
public
BlockSubscriber
(
ISignaler
<
Block
>
signaler
,
BlockObserver
observer
)
{
this
.
signaler
=
signaler
;
this
.
observer
=
observer
;
}
/// <summary>
/// Subscribes the block observer to the block signaler.
/// </summary>
/// <returns>An <see cref="IDisposable"/></returns>
public
IDisposable
Subscribe
()
{
return
this
.
signaler
.
Subscribe
(
this
.
observer
);
}
}
}
Breeze/src/Breeze.Wallet/Notifications/TransactionObserver.cs
deleted
100644 → 0
View file @
3435ae71
using
NBitcoin
;
using
Stratis.Bitcoin
;
namespace
Breeze.Wallet.Notifications
{
/// <summary>
/// Observer that receives notifications about the arrival of new <see cref="Transaction"/>s.
/// </summary>
public
class
TransactionObserver
:
SignalObserver
<
Transaction
>
{
private
readonly
IWalletManager
walletManager
;
public
TransactionObserver
(
IWalletManager
walletManager
)
{
this
.
walletManager
=
walletManager
;
}
/// <summary>
/// Manages what happens when a new transaction is received.
/// </summary>
/// <param name="transaction">The new transaction</param>
protected
override
void
OnNextCore
(
Transaction
transaction
)
{
this
.
walletManager
.
ProcessTransaction
(
transaction
);
}
}
}
Breeze/src/Breeze.Wallet/Notifications/TransactionSubscriber.cs
deleted
100644 → 0
View file @
3435ae71
using
Stratis.Bitcoin
;
using
System
;
using
NBitcoin
;
namespace
Breeze.Wallet.Notifications
{
/// <summary>
/// Manages the subscription of the transaction observer to the transaction signaler.
/// </summary>
public
class
TransactionSubscriber
{
private
readonly
ISignaler
<
Transaction
>
signaler
;
private
readonly
TransactionObserver
observer
;
public
TransactionSubscriber
(
ISignaler
<
Transaction
>
signaler
,
TransactionObserver
observer
)
{
this
.
signaler
=
signaler
;
this
.
observer
=
observer
;
}
/// <summary>
/// Subscribes the transaction observer to the transaction signaler.
/// </summary>
/// <returns>An <see cref="IDisposable"/></returns>
public
IDisposable
Subscribe
()
{
return
this
.
signaler
.
Subscribe
(
this
.
observer
);
}
}
}
Breeze/src/Breeze.Wallet/Wallet.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
Breeze.Wallet.JsonConverters
;
using
NBitcoin
;
using
NBitcoin.JsonConverters
;
using
Newtonsoft.Json
;
namespace
Breeze.Wallet
{
/// <summary>
/// A wallet
/// </summary>
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>
/// 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
;
}
/// <summary>
/// The location of the wallet file on the local system.
/// </summary>
[
JsonProperty
(
PropertyName
=
"walletFilePath"
)]
public
string
WalletFilePath
{
get
;
set
;
}
/// <summary>
/// The root of the accounts tree.
/// </summary>
[
JsonProperty
(
PropertyName
=
"accountsRoot"
)]
public
IEnumerable
<
AccountRoot
>
AccountsRoot
{
get
;
set
;
}
/// <summary>
/// Gets the type of the accounts by coin.
/// </summary>
/// <param name="coinType">Type of the coin.</param>
/// <returns></returns>
public
IEnumerable
<
HdAccount
>
GetAccountsByCoinType
(
CoinType
coinType
)
{
return
this
.
AccountsRoot
.
Where
(
a
=>
a
.
CoinType
==
coinType
).
SelectMany
(
a
=>
a
.
Accounts
);
}
/// <summary>
/// Gets all the transactions by coin type.
/// </summary>
/// <param name="coinType">Type of the coin.</param>
/// <returns></returns>
public
IEnumerable
<
TransactionData
>
GetAllTransactionsByCoinType
(
CoinType
coinType
)
{
List
<
TransactionData
>
result
=
new
List
<
TransactionData
>();
var
accounts
=
this
.
GetAccountsByCoinType
(
coinType
).
ToList
();
foreach
(
var
address
in
accounts
.
SelectMany
(
a
=>
a
.
ExternalAddresses
).
Concat
(
accounts
.
SelectMany
(
a
=>
a
.
InternalAddresses
)))
{
result
.
AddRange
(
address
.
Transactions
);
}
return
result
;
}
/// <summary>
/// Gets all the pub keys conatined in this wallet.
/// </summary>
/// <param name="coinType">Type of the coin.</param>
/// <returns></returns>
public
IEnumerable
<
Script
>
GetAllPubKeysByCoinType
(
CoinType
coinType
)
{
var
accounts
=
this
.
GetAccountsByCoinType
(
coinType
).
ToList
();
foreach
(
var
address
in
accounts
.
SelectMany
(
a
=>
a
.
ExternalAddresses
).
Concat
(
accounts
.
SelectMany
(
a
=>
a
.
InternalAddresses
)))
{
yield
return
address
.
ScriptPubKey
;
}
}
}
/// <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 height of the last block that was synced.
/// </summary>
[
JsonProperty
(
PropertyName
=
"lastBlockSyncedHeight"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
public
int
?
LastBlockSyncedHeight
{
get
;
set
;
}
/// <summary>
/// The accounts used in the wallet.
/// </summary>
[
JsonProperty
(
PropertyName
=
"accounts"
)]
public
ICollection
<
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>
/// Gets the account matching the name passed as a parameter.
/// </summary>
/// <param name="accountName">The name of the account to get.</param>
/// <returns></returns>
/// <exception cref="System.Exception"></exception>
public
HdAccount
GetAccountByName
(
string
accountName
)
{
// get the account
HdAccount
account
=
this
.
Accounts
.
SingleOrDefault
(
a
=>
a
.
Name
==
accountName
);
if
(
account
==
null
)
{
throw
new
Exception
(
$"No account with name
{
accountName
}
could be found."
);
}
return
account
;
}
}
/// <summary>
/// The type of coin, as specified in BIP44.
/// </summary>
/// <remarks>For more, see https://github.com/satoshilabs/slips/blob/master/slip-0044.md</remarks>
public
enum
CoinType
{
/// <summary>
/// Bitcoin
/// </summary>
Bitcoin
=
0
,
/// <summary>
/// Testnet (all coins)
/// </summary>
Testnet
=
1
,
/// <summary>
/// Stratis
/// </summary>
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>
/// A path to the account as defined in BIP44.
/// </summary>
[
JsonProperty
(
PropertyName
=
"hdPath"
)]
public
string
HdPath
{
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
ICollection
<
HdAddress
>
ExternalAddresses
{
get
;
set
;
}
/// <summary>
/// The list of internal addresses, typically used to receive change.
/// </summary>
[
JsonProperty
(
PropertyName
=
"internalAddresses"
)]
public
ICollection
<
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
GetFirstUnusedReceivingAddress
()
{
return
this
.
GetFirstUnusedAddress
(
false
);
}
/// <summary>
/// Gets the first change address that contains no transaction.
/// </summary>
/// <returns>An unused address</returns>
public
HdAddress
GetFirstUnusedChangeAddress
()
{
return
this
.
GetFirstUnusedAddress
(
true
);
}
/// <summary>
/// Gets the first receiving address that contains no transaction.
/// </summary>
/// <returns>An unused address</returns>
private
HdAddress
GetFirstUnusedAddress
(
bool
isChange
)
{
IEnumerable
<
HdAddress
>
addresses
=
isChange
?
this
.
InternalAddresses
:
this
.
ExternalAddresses
;
var
unusedAddresses
=
addresses
.
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>
/// Gets the last address that contains transactions.
/// </summary>
/// <param name="isChange">Whether the address is a change (internal) address or receiving (external) address.</param>
/// <returns></returns>
public
HdAddress
GetLastUsedAddress
(
bool
isChange
)
{
IEnumerable
<
HdAddress
>
addresses
=
isChange
?
this
.
InternalAddresses
:
this
.
ExternalAddresses
;
var
usedAddresses
=
addresses
.
Where
(
acc
=>
acc
.
Transactions
.
Any
()).
ToList
();
if
(!
usedAddresses
.
Any
())
{
return
null
;
}
// gets the used address with the highest index
var
index
=
usedAddresses
.
Max
(
a
=>
a
.
Index
);
return
usedAddresses
.
Single
(
a
=>
a
.
Index
==
index
);
}
/// <summary>
/// Gets a collection of transactions by id.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns></returns>
public
IEnumerable
<
TransactionData
>
GetTransactionsById
(
uint256
id
)
{
var
addresses
=
this
.
ExternalAddresses
.
Concat
(
this
.
InternalAddresses
);
return
addresses
.
SelectMany
(
a
=>
a
.
Transactions
.
Where
(
t
=>
t
.
Id
==
id
));
}
/// <summary>
/// Gets a collection of transactions with spendable outputs.
/// </summary>
/// <returns></returns>
public
IEnumerable
<
TransactionData
>
GetSpendableTransactions
()
{
var
addresses
=
this
.
ExternalAddresses
.
Concat
(
this
.
InternalAddresses
);
return
addresses
.
SelectMany
(
a
=>
a
.
Transactions
.
Where
(
t
=>
t
.
SpentInTransaction
==
null
&&
t
.
Amount
>
Money
.
Zero
));
}
/// <summary>
/// Finds the addresses in which a transaction is contained.
/// </summary>
/// <remarks>
/// Returns a collection because a transaction can be contained in a change address as well as in a receive address (as a spend).
/// </remarks>
/// <param name="predicate">A predicate by which to filter the transactions.</param>
/// <returns></returns>
public
IEnumerable
<
HdAddress
>
FindAddressesForTransaction
(
Func
<
TransactionData
,
bool
>
predicate
)
{
var
addresses
=
this
.
ExternalAddresses
.
Concat
(
this
.
InternalAddresses
);
return
addresses
.
Where
(
a
=>
a
.
Transactions
.
Any
(
predicate
));
}
}
/// <summary>
/// An Hd address.
/// </summary>
public
class
HdAddress
{
/// <summary>
/// The index of the address.
/// </summary>
[
JsonProperty
(
PropertyName
=
"index"
)]
public
int
Index
{
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
ICollection
<
TransactionData
>
Transactions
{
get
;
set
;
}
/// <summary>
/// Determines whether this is a change address or a receive address.
/// </summary>
/// <returns>
/// <c>true</c> if it is a change address; otherwise, <c>false</c>.
/// </returns>
public
bool
IsChangeAddress
()
{
return
int
.
Parse
(
this
.
HdPath
.
Split
(
'/'
)[
4
])
==
1
;
}
}
/// <summary>
/// An object containing transaction data.
/// </summary>
public
class
TransactionData
{
/// <summary>
/// Transaction id.
/// </summary>
[
JsonProperty
(
PropertyName
=
"id"
)]
[
JsonConverter
(
typeof
(
UInt256JsonConverter
))]
public
uint256
Id
{
get
;
set
;
}
/// <summary>
/// The id of the transaction in which the output referenced in this transaction is spent.
/// </summary>
[
JsonProperty
(
PropertyName
=
"spentIn"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
[
JsonConverter
(
typeof
(
UInt256JsonConverter
))]
public
uint256
SpentInTransaction
{
get
;
set
;
}
/// <summary>
/// The transaction amount.
/// </summary>
[
JsonProperty
(
PropertyName
=
"amount"
)]
[
JsonConverter
(
typeof
(
MoneyJsonConverter
))]
public
Money
Amount
{
get
;
set
;
}
/// <summary>
/// A list of payments made out in this transaction.
/// </summary>
[
JsonProperty
(
PropertyName
=
"payments"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
public
ICollection
<
PaymentDetails
>
Payments
{
get
;
set
;
}
/// <summary>
/// The index of this scriptPubKey in the transaction it is contained.
/// </summary>
[
JsonProperty
(
PropertyName
=
"index"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
public
int
?
Index
{
get
;
set
;
}
/// <summary>
/// The height of the block including this transaction.
/// </summary>
[
JsonProperty
(
PropertyName
=
"blockHeight"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
public
int
?
BlockHeight
{
get
;
set
;
}
/// <summary>
/// Gets or sets the creation time.
/// </summary>
[
JsonProperty
(
PropertyName
=
"creationTime"
)]
[
JsonConverter
(
typeof
(
DateTimeOffsetConverter
))]
public
DateTimeOffset
CreationTime
{
get
;
set
;
}
/// <summary>
/// Gets or sets the Merkle proof for this transaction.
/// </summary>
[
JsonProperty
(
PropertyName
=
"merkleProof"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
public
MerkleProof
MerkleProof
{
get
;
set
;
}
/// <summary>
/// Determines whether this transaction is confirmed.
/// </summary>
public
bool
IsConfirmed
()
{
return
this
.
BlockHeight
!=
null
;
}
}
/// <summary>
/// An object representing a payment.
/// </summary>
public
class
PaymentDetails
{
/// <summary>
/// The script pub key of the destination address.
/// </summary>
[
JsonProperty
(
PropertyName
=
"destinationScriptPubKey"
)]
[
JsonConverter
(
typeof
(
ScriptJsonConverter
))]
public
Script
DestinationScriptPubKey
{
get
;
set
;
}
/// <summary>
/// The Base58 representation of the destination address.
/// </summary>
[
JsonProperty
(
PropertyName
=
"destinationAddress"
)]
public
string
DestinationAddress
{
get
;
set
;
}
/// <summary>
/// The transaction amount.
/// </summary>
[
JsonProperty
(
PropertyName
=
"amount"
)]
[
JsonConverter
(
typeof
(
MoneyJsonConverter
))]
public
Money
Amount
{
get
;
set
;
}
}
/// <summary>
/// An object representing a Merkle proof
/// </summary>
public
class
MerkleProof
{
/// <summary>
/// Gets or sets the merkle root.
/// </summary>
[
JsonProperty
(
PropertyName
=
"merkleRoot"
)]
[
JsonConverter
(
typeof
(
UInt256JsonConverter
))]
public
uint256
MerkleRoot
{
get
;
set
;
}
/// <summary>
/// Gets or sets the merkle path.
/// </summary>
[
JsonProperty
(
PropertyName
=
"merklePath"
,
ItemConverterType
=
typeof
(
UInt256JsonConverter
))]
public
ICollection
<
uint256
>
MerklePath
{
get
;
set
;
}
}
}
\ No newline at end of file
Breeze/src/Breeze.Wallet/WalletManager.cs
deleted
100644 → 0
View file @
3435ae71
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Runtime.InteropServices
;
using
Breeze.Wallet.Helpers
;
using
Breeze.Wallet.Models
;
using
Microsoft.Extensions.Logging
;
using
NBitcoin
;
using
NBitcoin.DataEncoders
;
using
NBitcoin.Protocol
;
using
Newtonsoft.Json
;
using
Stratis.Bitcoin.Connection
;
using
Transaction
=
NBitcoin
.
Transaction
;
namespace
Breeze.Wallet
{
/// <summary>
/// A manager providing operations on wallets.
/// </summary>
public
class
WalletManager
:
IWalletManager
{
public
List
<
Wallet
>
Wallets
{
get
;
}
private
const
int
UnusedAddressesBuffer
=
20
;
private
const
int
WalletRecoveryAccountsCount
=
3
;
private
const
int
WalletCreationAccountsCount
=
2
;
private
readonly
CoinType
coinType
;
private
readonly
Network
network
;
private
readonly
ConnectionManager
connectionManager
;
private
readonly
ConcurrentChain
chain
;
private
Dictionary
<
Script
,
HdAddress
>
keysLookup
;
private
readonly
ILogger
logger
;
/// <summary>
/// Occurs when a transaction is found.
/// </summary>
public
event
EventHandler
<
TransactionFoundEventArgs
>
TransactionFound
;
public
WalletManager
(
ILoggerFactory
loggerFactory
,
ConnectionManager
connectionManager
,
Network
network
,
ConcurrentChain
chain
)
{
this
.
logger
=
loggerFactory
.
CreateLogger
(
this
.
GetType
().
FullName
);
this
.
Wallets
=
new
List
<
Wallet
>();
// find wallets and load them in memory
foreach
(
var
path
in
this
.
GetWalletFilesPaths
())
{
this
.
Load
(
this
.
DeserializeWallet
(
path
));
}
this
.
connectionManager
=
connectionManager
;
this
.
network
=
network
;
this
.
coinType
=
(
CoinType
)
network
.
Consensus
.
CoinType
;
this
.
chain
=
chain
;
// load data in memory for faster lookups
this
.
LoadKeysLookup
();
// register events
this
.
TransactionFound
+=
this
.
OnTransactionFound
;
}
/// <inheritdoc />
public
Mnemonic
CreateWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
passphrase
=
null
)
{
// for now the passphrase is set to be the password by default.
if
(
passphrase
==
null
)
{
passphrase
=
password
;
}
// generate the root seed used to generate keys from a mnemonic picked at random
// and a passphrase optionally provided by the user
Mnemonic
mnemonic
=
new
Mnemonic
(
Wordlist
.
English
,
WordCount
.
Twelve
);
ExtKey
extendedKey
=
mnemonic
.
DeriveExtKey
(
passphrase
);
Network
coinNetwork
=
WalletHelpers
.
GetNetwork
(
network
);
// create a wallet file
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
coinNetwork
,
extendedKey
);
// generate multiple accounts and addresses from the get-go
for
(
int
i
=
0
;
i
<
WalletCreationAccountsCount
;
i
++)
{
HdAccount
account
=
CreateNewAccount
(
wallet
,
this
.
coinType
,
password
);
this
.
CreateAddressesInAccount
(
account
,
coinNetwork
,
UnusedAddressesBuffer
);
this
.
CreateAddressesInAccount
(
account
,
coinNetwork
,
UnusedAddressesBuffer
,
true
);
}
// update the height of the we start syncing from
this
.
UpdateLastBlockSyncedHeight
(
wallet
,
this
.
chain
.
Tip
.
Height
);
// save the changes to the file and add addresses to be tracked
this
.
SaveToFile
(
wallet
);
this
.
Load
(
wallet
);
this
.
LoadKeysLookup
();
return
mnemonic
;
}
/// <inheritdoc />
public
Wallet
LoadWallet
(
string
password
,
string
folderPath
,
string
name
)
{
string
walletFilePath
=
Path
.
Combine
(
folderPath
,
$"
{
name
}
.json"
);
// load the file from the local system
Wallet
wallet
=
this
.
DeserializeWallet
(
walletFilePath
);
this
.
Load
(
wallet
);
return
wallet
;
}
/// <inheritdoc />
public
Wallet
RecoverWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
mnemonic
,
DateTime
creationTime
,
string
passphrase
=
null
)
{
// for now the passphrase is set to be the password by default.
if
(
passphrase
==
null
)
{
passphrase
=
password
;
}
// 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
,
coinNetwork
,
extendedKey
,
creationTime
);
// generate multiple accounts and addresses from the get-go
for
(
int
i
=
0
;
i
<
WalletRecoveryAccountsCount
;
i
++)
{
HdAccount
account
=
CreateNewAccount
(
wallet
,
this
.
coinType
,
password
);
this
.
CreateAddressesInAccount
(
account
,
coinNetwork
,
UnusedAddressesBuffer
);
this
.
CreateAddressesInAccount
(
account
,
coinNetwork
,
UnusedAddressesBuffer
,
true
);
}
int
blockSyncStart
=
this
.
chain
.
GetHeightAtTime
(
creationTime
);
this
.
UpdateLastBlockSyncedHeight
(
wallet
,
blockSyncStart
);
// save the changes to the file and add addresses to be tracked
this
.
SaveToFile
(
wallet
);
this
.
Load
(
wallet
);
this
.
LoadKeysLookup
();
return
wallet
;
}
/// <inheritdoc />
public
HdAccount
GetUnusedAccount
(
string
walletName
,
CoinType
coinType
,
string
password
)
{
Wallet
wallet
=
this
.
GetWalletByName
(
walletName
);
return
this
.
GetUnusedAccount
(
wallet
,
coinType
,
password
);
}
/// <inheritdoc />
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
())
{
// gets an unused account
var
firstUnusedAccount
=
accountsRoot
.
GetFirstUnusedAccount
();
if
(
firstUnusedAccount
!=
null
)
{
return
firstUnusedAccount
;
}
}
// 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
;
}
/// <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
}
'"
;
KeyPath
keyPath
=
new
KeyPath
(
accountHdPath
);
ExtKey
accountExtKey
=
seedExtKey
.
Derive
(
keyPath
);
ExtPubKey
accountExtPubKey
=
accountExtKey
.
Neuter
();
var
newAccount
=
new
HdAccount
{
Index
=
newAccountIndex
,
ExtendedPubKey
=
accountExtPubKey
.
ToString
(
wallet
.
Network
),
ExternalAddresses
=
new
List
<
HdAddress
>(),
InternalAddresses
=
new
List
<
HdAddress
>(),
Name
=
$"account
{
newAccountIndex
}
"
,
HdPath
=
accountHdPath
,
CreationTime
=
DateTimeOffset
.
Now
};
accounts
.
Add
(
newAccount
);
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
=
accounts
;
return
newAccount
;
}
/// <inheritdoc />
public
string
GetUnusedAddress
(
string
walletName
,
CoinType
coinType
,
string
accountName
)
{
Wallet
wallet
=
this
.
GetWalletByName
(
walletName
);
// get the account
HdAccount
account
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
GetAccountByName
(
accountName
);
// validate address creation
if
(
account
.
ExternalAddresses
.
Any
())
{
// check last created address contains transactions.
var
firstUnusedExternalAddress
=
account
.
GetFirstUnusedReceivingAddress
();
if
(
firstUnusedExternalAddress
!=
null
)
{
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
.
LoadKeysLookup
();
return
account
.
GetFirstUnusedReceivingAddress
().
Address
;
}
/// <inheritdoc />
public
IEnumerable
<
HdAddress
>
GetHistoryByCoinType
(
string
walletName
,
CoinType
coinType
)
{
Wallet
wallet
=
this
.
GetWalletByName
(
walletName
);
return
this
.
GetHistoryByCoinType
(
wallet
,
coinType
);
}
/// <inheritdoc />
public
IEnumerable
<
HdAddress
>
GetHistoryByCoinType
(
Wallet
wallet
,
CoinType
coinType
)
{
var
accounts
=
wallet
.
GetAccountsByCoinType
(
coinType
).
ToList
();
foreach
(
var
address
in
accounts
.
SelectMany
(
a
=>
a
.
ExternalAddresses
).
Concat
(
accounts
.
SelectMany
(
a
=>
a
.
InternalAddresses
)))
{
if
(
address
.
Transactions
.
Any
())
{
yield
return
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
,
isChange
,
network
);
// add address details
addresses
.
Add
(
new
HdAddress
{
Index
=
i
,
HdPath
=
CreateBip44Path
(
account
.
GetCoinType
(),
account
.
Index
,
i
,
isChange
),
ScriptPubKey
=
address
.
ScriptPubKey
,
Address
=
address
.
ToString
(),
Transactions
=
new
List
<
TransactionData
>()
});
addressesCreated
.
Add
(
address
.
ToString
());
}
if
(
isChange
)
{
account
.
InternalAddresses
=
addresses
;
}
else
{
account
.
ExternalAddresses
=
addresses
;
}
return
addressesCreated
;
}
/// <inheritdoc />
public
Wallet
GetWallet
(
string
walletName
)
{
Wallet
wallet
=
this
.
GetWalletByName
(
walletName
);
return
wallet
;
}
/// <inheritdoc />
public
IEnumerable
<
HdAccount
>
GetAccountsByCoinType
(
string
walletName
,
CoinType
coinType
)
{
Wallet
wallet
=
this
.
GetWalletByName
(
walletName
);
return
wallet
.
GetAccountsByCoinType
(
coinType
);
}
/// <inheritdoc />
public
(
string
hex
,
uint256
transactionId
,
Money
fee
)
BuildTransaction
(
string
walletName
,
string
accountName
,
CoinType
coinType
,
string
password
,
string
destinationAddress
,
Money
amount
,
string
feeType
,
bool
allowUnconfirmed
)
{
if
(
amount
==
Money
.
Zero
)
{
throw
new
Exception
(
$"Cannot send transaction with 0
{
this
.
coinType
}
"
);
}
// get the wallet and the account
Wallet
wallet
=
this
.
GetWalletByName
(
walletName
);
HdAccount
account
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
GetAccountByName
(
accountName
);
// get a list of transactions outputs that have not been spent
IEnumerable
<
TransactionData
>
spendableTransactions
=
account
.
GetSpendableTransactions
();
// get total spendable balance in the account.
var
balance
=
spendableTransactions
.
Sum
(
t
=>
t
.
Amount
);
// make sure we have enough funds
if
(
balance
<
amount
)
{
throw
new
Exception
(
"Not enough funds."
);
}
// calculate which addresses needs to be used as well as the fee to be charged
var
calculationResult
=
this
.
CalculateFees
(
spendableTransactions
,
amount
);
// get extended private key
var
privateKey
=
Key
.
Parse
(
wallet
.
EncryptedSeed
,
password
,
wallet
.
Network
);
var
seedExtKey
=
new
ExtKey
(
privateKey
,
wallet
.
ChainCode
);
var
signingKeys
=
new
HashSet
<
ISecret
>();
var
coins
=
new
List
<
Coin
>();
foreach
(
var
transactionToUse
in
calculationResult
.
transactionsToUse
)
{
var
address
=
account
.
FindAddressesForTransaction
(
t
=>
t
.
Id
==
transactionToUse
.
Id
&&
t
.
Amount
>
0
).
Single
();
ExtKey
addressExtKey
=
seedExtKey
.
Derive
(
new
KeyPath
(
address
.
HdPath
));
BitcoinExtKey
addressPrivateKey
=
addressExtKey
.
GetWif
(
wallet
.
Network
);
signingKeys
.
Add
(
addressPrivateKey
);
coins
.
Add
(
new
Coin
(
transactionToUse
.
Id
,
(
uint
)
transactionToUse
.
Index
,
transactionToUse
.
Amount
,
address
.
ScriptPubKey
));
}
// get address to send the change to
var
changeAddress
=
account
.
GetFirstUnusedChangeAddress
();
// get script destination address
Script
destinationScript
=
PayToPubkeyHashTemplate
.
Instance
.
GenerateScriptPubKey
(
new
BitcoinPubKeyAddress
(
destinationAddress
,
wallet
.
Network
));
// build transaction
var
builder
=
new
TransactionBuilder
();
Transaction
tx
=
builder
.
AddCoins
(
coins
)
.
AddKeys
(
signingKeys
.
ToArray
())
.
Send
(
destinationScript
,
amount
)
.
SetChange
(
changeAddress
.
ScriptPubKey
)
.
SendFees
(
calculationResult
.
fee
)
.
BuildTransaction
(
true
);
if
(!
builder
.
Verify
(
tx
))
{
throw
new
Exception
(
"Could not build transaction, please make sure you entered the correct data."
);
}
return
(
tx
.
ToHex
(),
tx
.
GetHash
(),
calculationResult
.
fee
);
}
/// <summary>
/// Calculates which outputs are to be used in the transaction, as well as the fees that will be charged.
/// </summary>
/// <param name="spendableTransactions">The transactions with unspent funds.</param>
/// <param name="amount">The amount to be sent.</param>
/// <returns>The collection of transactions to be used and the fee to be charged</returns>
private
(
List
<
TransactionData
>
transactionsToUse
,
Money
fee
)
CalculateFees
(
IEnumerable
<
TransactionData
>
spendableTransactions
,
Money
amount
)
{
// TODO make this a bit smarter!
List
<
TransactionData
>
transactionsToUse
=
new
List
<
TransactionData
>();
foreach
(
var
transaction
in
spendableTransactions
)
{
transactionsToUse
.
Add
(
transaction
);
if
(
transactionsToUse
.
Sum
(
t
=>
t
.
Amount
)
>=
amount
)
{
break
;
}
}
Money
fee
=
new
Money
(
new
decimal
(
0.001
),
MoneyUnit
.
BTC
);
return
(
transactionsToUse
,
fee
);
}
/// <inheritdoc />
public
bool
SendTransaction
(
string
transactionHex
)
{
// TODO move this to a behavior on the full node
// parse transaction
Transaction
transaction
=
Transaction
.
Parse
(
transactionHex
);
TxPayload
payload
=
new
TxPayload
(
transaction
);
foreach
(
var
node
in
this
.
connectionManager
.
ConnectedNodes
)
{
node
.
SendMessage
(
payload
);
}
return
true
;
}
/// <inheritdoc />
public
void
ProcessBlock
(
int
height
,
Block
block
)
{
this
.
logger
.
LogDebug
(
$"block notification - height:
{
height
}
, hash:
{
block
.
Header
.
GetHash
()}
, coin:
{
this
.
coinType
}
"
);
foreach
(
Transaction
transaction
in
block
.
Transactions
)
{
this
.
ProcessTransaction
(
transaction
,
height
,
block
);
}
// update the wallets with the last processed block height
this
.
UpdateLastBlockSyncedHeight
(
height
);
}
/// <inheritdoc />
public
void
ProcessTransaction
(
Transaction
transaction
,
int
?
blockHeight
=
null
,
Block
block
=
null
)
{
this
.
logger
.
LogDebug
(
$"transaction received - hash:
{
transaction
.
GetHash
()}
, coin:
{
this
.
coinType
}
"
);
// check the outputs
foreach
(
var
pubKey
in
this
.
keysLookup
.
Keys
)
{
// check if the outputs contain one of our addresses
var
utxo
=
transaction
.
Outputs
.
SingleOrDefault
(
o
=>
pubKey
==
o
.
ScriptPubKey
);
if
(
utxo
!=
null
)
{
AddTransactionToWallet
(
transaction
.
GetHash
(),
transaction
.
Time
,
transaction
.
Outputs
.
IndexOf
(
utxo
),
utxo
.
Value
,
pubKey
,
blockHeight
,
block
);
}
}
// check the inputs - include those that have a reference to a transaction containing one of our scripts and the same index
foreach
(
TxIn
input
in
transaction
.
Inputs
.
Where
(
txIn
=>
this
.
keysLookup
.
Values
.
SelectMany
(
v
=>
v
.
Transactions
).
Any
(
trackedTx
=>
trackedTx
.
Id
==
txIn
.
PrevOut
.
Hash
&&
trackedTx
.
Index
==
txIn
.
PrevOut
.
N
)))
{
TransactionData
tTx
=
this
.
keysLookup
.
Values
.
SelectMany
(
v
=>
v
.
Transactions
).
Single
(
trackedTx
=>
trackedTx
.
Id
==
input
.
PrevOut
.
Hash
&&
trackedTx
.
Index
==
input
.
PrevOut
.
N
);
// find the script this input references
var
keyToSpend
=
this
.
keysLookup
.
Single
(
v
=>
v
.
Value
.
Transactions
.
Contains
(
tTx
)).
Key
;
// get the details of the outputs paid out.
// We first include the keys we don't hold and then we include the keys we do hold but that are for receiving addresses (which would mean the user paid itself).
IEnumerable
<
TxOut
>
paidoutto
=
transaction
.
Outputs
.
Where
(
o
=>
!
this
.
keysLookup
.
Keys
.
Contains
(
o
.
ScriptPubKey
)
||
(
this
.
keysLookup
.
ContainsKey
(
o
.
ScriptPubKey
)
&&
!
this
.
keysLookup
[
o
.
ScriptPubKey
].
IsChangeAddress
()));
AddTransactionToWallet
(
transaction
.
GetHash
(),
transaction
.
Time
,
null
,
-
tTx
.
Amount
,
keyToSpend
,
blockHeight
,
block
,
tTx
.
Id
,
tTx
.
Index
,
paidoutto
);
}
}
/// <summary>
/// Adds the transaction to the wallet.
/// </summary>
/// <param name="transactionHash">The transaction hash.</param>
/// <param name="time">The time.</param>
/// <param name="index">The index.</param>
/// <param name="amount">The amount.</param>
/// <param name="script">The script.</param>
/// <param name="blockHeight">Height of the block.</param>
/// <param name="block">The block containing the transaction to add.</param>
/// <param name="spendingTransactionId">The id of the transaction containing the output being spent, if this is a spending transaction.</param>
/// <param name="spendingTransactionIndex">The index of the output in the transaction being referenced, if this is a spending transaction.</param>
private
void
AddTransactionToWallet
(
uint256
transactionHash
,
uint
time
,
int
?
index
,
Money
amount
,
Script
script
,
int
?
blockHeight
=
null
,
Block
block
=
null
,
uint256
spendingTransactionId
=
null
,
int
?
spendingTransactionIndex
=
null
,
IEnumerable
<
TxOut
>
paidToOutputs
=
null
)
{
// get the collection of transactions to add to.
this
.
keysLookup
.
TryGetValue
(
script
,
out
HdAddress
address
);
var
isSpendingTransaction
=
paidToOutputs
!=
null
&&
paidToOutputs
.
Any
();
var
trans
=
address
.
Transactions
;
// if it's the first time we see this transaction
if
(
trans
.
All
(
t
=>
t
.
Id
!=
transactionHash
))
{
var
newTransaction
=
new
TransactionData
{
Amount
=
amount
,
BlockHeight
=
blockHeight
,
Id
=
transactionHash
,
CreationTime
=
DateTimeOffset
.
FromUnixTimeSeconds
(
block
?.
Header
.
Time
??
time
),
Index
=
index
};
// add the Merkle proof to the (non-spending) transaction
if
(
block
!=
null
&&
!
isSpendingTransaction
)
{
newTransaction
.
MerkleProof
=
this
.
CreateMerkleProof
(
block
,
transactionHash
);
}
// if this is a spending transaction, keep a record of the payments made out to other scripts.
if
(
isSpendingTransaction
)
{
List
<
PaymentDetails
>
payments
=
new
List
<
PaymentDetails
>();
foreach
(
var
paidToOutput
in
paidToOutputs
)
{
payments
.
Add
(
new
PaymentDetails
{
DestinationScriptPubKey
=
paidToOutput
.
ScriptPubKey
,
DestinationAddress
=
paidToOutput
.
ScriptPubKey
.
GetDestinationAddress
(
this
.
network
).
ToString
(),
Amount
=
paidToOutput
.
Value
});
}
newTransaction
.
Payments
=
payments
;
// mark the transaction spent by this transaction as such
var
transactions
=
this
.
keysLookup
.
Values
.
SelectMany
(
v
=>
v
.
Transactions
).
Where
(
t
=>
t
.
Id
==
spendingTransactionId
);
if
(
transactions
.
Any
())
{
var
spentTransaction
=
transactions
.
Single
(
t
=>
t
.
Index
==
spendingTransactionIndex
);
spentTransaction
.
SpentInTransaction
=
transactionHash
;
spentTransaction
.
MerkleProof
=
null
;
}
}
trans
.
Add
(
newTransaction
);
}
else
if
(
trans
.
Any
(
t
=>
t
.
Id
==
transactionHash
))
// if this is an unconfirmed transaction now received in a block
{
var
foundTransaction
=
trans
.
Single
(
t
=>
t
.
Id
==
transactionHash
);
// update the block height
if
(
foundTransaction
.
BlockHeight
==
null
&&
blockHeight
!=
null
)
{
foundTransaction
.
BlockHeight
=
blockHeight
;
}
// update the block time
if
(
block
!=
null
)
{
foundTransaction
.
CreationTime
=
DateTimeOffset
.
FromUnixTimeSeconds
(
block
.
Header
.
Time
);
}
// add the Merkle proof now that the transaction is confirmed in a block
if
(!
isSpendingTransaction
&&
foundTransaction
.
MerkleProof
==
null
)
{
foundTransaction
.
MerkleProof
=
this
.
CreateMerkleProof
(
block
,
transactionHash
);
}
}
// notify a transaction has been found
this
.
TransactionFound
?.
Invoke
(
this
,
new
TransactionFoundEventArgs
(
script
,
transactionHash
));
}
private
MerkleProof
CreateMerkleProof
(
Block
block
,
uint256
transactionHash
)
{
MerkleBlock
merkleBlock
=
new
MerkleBlock
(
block
,
new
[]
{
transactionHash
});
return
new
MerkleProof
{
MerkleRoot
=
block
.
Header
.
HashMerkleRoot
,
MerklePath
=
merkleBlock
.
PartialMerkleTree
.
Hashes
};
}
private
void
OnTransactionFound
(
object
sender
,
TransactionFoundEventArgs
a
)
{
foreach
(
Wallet
wallet
in
this
.
Wallets
)
{
foreach
(
var
account
in
wallet
.
GetAccountsByCoinType
(
this
.
coinType
))
{
bool
isChange
;
if
(
account
.
ExternalAddresses
.
Any
(
address
=>
address
.
ScriptPubKey
==
a
.
Script
))
{
isChange
=
false
;
}
else
if
(
account
.
InternalAddresses
.
Any
(
address
=>
address
.
ScriptPubKey
==
a
.
Script
))
{
isChange
=
true
;
}
else
{
continue
;
}
// calculate how many accounts to add to keep a buffer of 20 unused addresses
int
lastUsedAddressIndex
=
account
.
GetLastUsedAddress
(
isChange
).
Index
;
int
addressesCount
=
isChange
?
account
.
InternalAddresses
.
Count
()
:
account
.
ExternalAddresses
.
Count
();
int
emptyAddressesCount
=
addressesCount
-
lastUsedAddressIndex
-
1
;
int
accountsToAdd
=
UnusedAddressesBuffer
-
emptyAddressesCount
;
this
.
CreateAddressesInAccount
(
account
,
wallet
.
Network
,
accountsToAdd
,
isChange
);
// persists the address to the wallet file
this
.
SaveToFile
(
wallet
);
}
}
this
.
LoadKeysLookup
();
}
/// <inheritdoc />
public
void
DeleteWallet
(
string
walletFilePath
)
{
File
.
Delete
(
walletFilePath
);
}
/// <inheritdoc />
public
void
SaveToFile
(
Wallet
wallet
)
{
File
.
WriteAllText
(
wallet
.
WalletFilePath
,
JsonConvert
.
SerializeObject
(
wallet
,
Formatting
.
Indented
));
}
/// <inheritdoc />
public
void
UpdateLastBlockSyncedHeight
(
int
height
)
{
// update the wallets with the last processed block height
foreach
(
var
wallet
in
this
.
Wallets
)
{
this
.
UpdateLastBlockSyncedHeight
(
wallet
,
height
);
}
}
/// <inheritdoc />
public
void
UpdateLastBlockSyncedHeight
(
Wallet
wallet
,
int
height
)
{
// update the wallets with the last processed block height
foreach
(
var
accountRoot
in
wallet
.
AccountsRoot
.
Where
(
a
=>
a
.
CoinType
==
this
.
coinType
))
{
accountRoot
.
LastBlockSyncedHeight
=
height
;
}
}
/// <inheritdoc />
public
void
SaveToFile
()
{
foreach
(
var
wallet
in
this
.
Wallets
)
{
this
.
SaveToFile
(
wallet
);
}
}
/// <inheritdoc />
public
void
Dispose
()
{
// safely persist the wallets to the file system before disposing
foreach
(
var
wallet
in
this
.
Wallets
)
{
this
.
SaveToFile
(
wallet
);
}
}
/// <summary>
/// Generates the wallet file.
/// </summary>
/// <param name="password">The password used to encrypt sensitive info.</param>
/// <param name="folderPath">The folder where the wallet will be generated.</param>
/// <param name="name">The name of the wallet.</param>
/// <param name="network">The network this wallet is for.</param>
/// <param name="extendedKey">The root key used to generate keys.</param>
/// <param name="creationTime">The time this wallet was created.</param>
/// <returns></returns>
/// <exception cref="System.NotSupportedException"></exception>
private
Wallet
GenerateWalletFile
(
string
password
,
string
folderPath
,
string
name
,
Network
network
,
ExtKey
extendedKey
,
DateTimeOffset
?
creationTime
=
null
)
{
string
walletFilePath
=
Path
.
Combine
(
folderPath
,
$"
{
name
}
.json"
);
if
(
File
.
Exists
(
walletFilePath
))
throw
new
InvalidOperationException
(
$"Wallet already exists at
{
walletFilePath
}
"
);
Wallet
walletFile
=
new
Wallet
{
Name
=
name
,
EncryptedSeed
=
extendedKey
.
PrivateKey
.
GetEncryptedBitcoinSecret
(
password
,
network
).
ToWif
(),
ChainCode
=
extendedKey
.
ChainCode
,
CreationTime
=
creationTime
??
DateTimeOffset
.
Now
,
Network
=
network
,
AccountsRoot
=
new
List
<
AccountRoot
>
{
new
AccountRoot
{
Accounts
=
new
List
<
HdAccount
>(),
CoinType
=
CoinType
.
Bitcoin
},
new
AccountRoot
{
Accounts
=
new
List
<
HdAccount
>(),
CoinType
=
CoinType
.
Testnet
},
new
AccountRoot
{
Accounts
=
new
List
<
HdAccount
>(),
CoinType
=
CoinType
.
Stratis
}
},
WalletFilePath
=
walletFilePath
,
};
// create a folder if none exists and persist the file
Directory
.
CreateDirectory
(
Path
.
GetDirectoryName
(
Path
.
GetFullPath
(
walletFilePath
)));
File
.
WriteAllText
(
walletFilePath
,
JsonConvert
.
SerializeObject
(
walletFile
,
Formatting
.
Indented
));
return
walletFile
;
}
/// <summary>
/// Gets the wallet located at the specified path.
/// </summary>
/// <param name="walletFilePath">The wallet file path.</param>
/// <returns></returns>
/// <exception cref="System.IO.FileNotFoundException"></exception>
private
Wallet
DeserializeWallet
(
string
walletFilePath
)
{
if
(!
File
.
Exists
(
walletFilePath
))
throw
new
FileNotFoundException
(
$"No wallet file found at
{
walletFilePath
}
"
);
// load the file from the local system
return
JsonConvert
.
DeserializeObject
<
Wallet
>(
File
.
ReadAllText
(
walletFilePath
));
}
/// <summary>
/// Loads the wallet to be used by the manager.
/// </summary>
/// <param name="wallet">The wallet to load.</param>
private
void
Load
(
Wallet
wallet
)
{
if
(
this
.
Wallets
.
Any
(
w
=>
w
.
Name
==
wallet
.
Name
))
{
return
;
}
this
.
Wallets
.
Add
(
wallet
);
}
private
BitcoinPubKeyAddress
GenerateAddress
(
string
accountExtPubKey
,
int
index
,
bool
isChange
,
Network
network
)
{
int
change
=
isChange
?
1
:
0
;
KeyPath
keyPath
=
new
KeyPath
(
$"
{
change
}
/
{
index
}
"
);
ExtPubKey
extPubKey
=
ExtPubKey
.
Parse
(
accountExtPubKey
).
Derive
(
keyPath
);
return
extPubKey
.
PubKey
.
GetAddress
(
network
);
}
private
IEnumerable
<
string
>
GetWalletFilesPaths
()
{
// TODO look in user-chosen folder as well.
// maybe the api can maintain a list of wallet paths it knows about
var
defaultFolderPath
=
GetDefaultWalletFolderPath
();
// create the directory if it doesn't exist
Directory
.
CreateDirectory
(
defaultFolderPath
);
return
Directory
.
EnumerateFiles
(
defaultFolderPath
,
"*.json"
,
SearchOption
.
TopDirectoryOnly
);
}
/// <summary>
/// Creates the bip44 path.
/// </summary>
/// <param name="coinType">Type of the coin.</param>
/// <param name="accountIndex">Index of the account.</param>
/// <param name="addressIndex">Index of the address.</param>
/// <param name="isChange">if set to <c>true</c> [is change].</param>
/// <returns></returns>
public
static
string
CreateBip44Path
(
CoinType
coinType
,
int
accountIndex
,
int
addressIndex
,
bool
isChange
=
false
)
{
//// populate the items according to the BIP44 path
//// [m/purpose'/coin_type'/account'/change/address_index]
int
change
=
isChange
?
1
:
0
;
return
$"m/44'/
{(
int
)
coinType
}
'/
{
accountIndex
}
'/
{
change
}
/
{
addressIndex
}
"
;
}
/// <summary>
/// Gets the path of the default folder in which the wallets will be stored.
/// </summary>
/// <returns>The folder path for Windows, Linux or OSX systems.</returns>
public
static
string
GetDefaultWalletFolderPath
()
{
if
(
RuntimeInformation
.
IsOSPlatform
(
OSPlatform
.
Windows
))
{
return
$@"
{
Environment
.
GetEnvironmentVariable
(
"AppData"
)}
\Breeze"
;
}
return
$"
{
Environment
.
GetEnvironmentVariable
(
"HOME"
)}
/.breeze"
;
}
/// <summary>
/// Loads the keys and transactions we're tracking in memory for faster lookups.
/// </summary>
/// <returns></returns>
private
void
LoadKeysLookup
()
{
this
.
keysLookup
=
new
Dictionary
<
Script
,
HdAddress
>();
foreach
(
var
wallet
in
this
.
Wallets
)
{
var
accounts
=
wallet
.
GetAccountsByCoinType
(
this
.
coinType
);
foreach
(
var
account
in
accounts
)
{
var
addresses
=
account
.
ExternalAddresses
.
Concat
(
account
.
InternalAddresses
);
foreach
(
var
address
in
addresses
)
{
this
.
keysLookup
.
Add
(
address
.
ScriptPubKey
,
address
);
}
}
}
}
/// <summary>
/// Gets a wallet given its name.
/// </summary>
/// <param name="walletName">The name of the wallet to get.</param>
/// <returns>A wallet or null if it doesn't exist</returns>
private
Wallet
GetWalletByName
(
string
walletName
)
{
Wallet
wallet
=
this
.
Wallets
.
SingleOrDefault
(
w
=>
w
.
Name
==
walletName
);
if
(
wallet
==
null
)
{
throw
new
Exception
(
$"No wallet with name
{
walletName
}
could be found."
);
}
return
wallet
;
}
}
public
class
TransactionDetails
{
public
uint256
Hash
{
get
;
set
;
}
public
int
?
Index
{
get
;
set
;
}
public
Money
Amount
{
get
;
internal
set
;
}
}
public
class
TransactionFoundEventArgs
:
EventArgs
{
public
Script
Script
{
get
;
set
;
}
public
uint256
TransactionHash
{
get
;
set
;
}
public
TransactionFoundEventArgs
(
Script
script
,
uint256
transactionHash
)
{
this
.
Script
=
script
;
this
.
TransactionHash
=
transactionHash
;
}
}
}
\ No newline at end of file
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