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
8e6ab15d
Commit
8e6ab15d
authored
May 31, 2017
by
Dan Gershony
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Moving the wallet feature to the full node
parent
3435ae71
Show whitespace changes
Inline
Side-by-side
Showing
38 changed files
with
98 additions
and
3055 deletions
+98
-3055
Breeze.sln
Breeze/Breeze.sln
+20
-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
LoggingActionFilter.cs
Breeze/src/Breeze.Api/LoggingActionFilter.cs
+1
-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
+8
-10
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
+51
-0
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
TrackNotifier.cs
Breeze/src/Breeze.Wallet/TrackNotifier.cs
+8
-14
Wallet.cs
Breeze/src/Breeze.Wallet/Wallet.cs
+0
-502
WalletManager.cs
Breeze/src/Breeze.Wallet/WalletManager.cs
+0
-866
No files found.
Breeze/Breeze.sln
View file @
8e6ab15d
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", "{3F0937A2-3182-42D9-866F-3DEDEE28EC5A}"
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
{3F0937A2-3182-42D9-866F-3DEDEE28EC5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F0937A2-3182-42D9-866F-3DEDEE28EC5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F0937A2-3182-42D9-866F-3DEDEE28EC5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F0937A2-3182-42D9-866F-3DEDEE28EC5A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
...
...
@@ -69,9 +75,8 @@ 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}
{3F0937A2-3182-42D9-866F-3DEDEE28EC5A} = {807563C4-7259-434D-B604-A14C3DCF8E30}
EndGlobalSection
EndGlobal
Breeze/src/Breeze.Api.Tests/Breeze.Api.Tests.csproj
View file @
8e6ab15d
...
...
@@ -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 @
8e6ab15d
...
...
@@ -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.Api/LoggingActionFilter.cs
View file @
8e6ab15d
...
...
@@ -33,7 +33,7 @@ namespace Breeze.Api
body
=
string
.
Join
(
Environment
.
NewLine
,
arguments
.
Values
);
}
this
.
logger
.
Log
Debug
(
$"Received
{
request
.
Method
}
{
request
.
GetDisplayUrl
()}
. Body: '
{
body
}
'"
);
this
.
logger
.
Log
Information
(
$"Received
{
request
.
Method
}
{
request
.
GetDisplayUrl
()}
. Body: '
{
body
}
'"
);
await
next
();
}
}
...
...
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 @
8e6ab15d
...
...
@@ -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 @
8e6ab15d
...
...
@@ -10,6 +10,7 @@ using Stratis.Bitcoin.Configuration;
using
Stratis.Bitcoin.Logging
;
using
Breeze.Wallet
;
using
Stratis.Bitcoin.Notifications
;
using
Stratis.Bitcoin.Utilities
;
namespace
Breeze.Daemon
{
...
...
@@ -18,12 +19,12 @@ namespace Breeze.Daemon
public
static
void
Main
(
string
[]
args
)
{
// configure Full Node
Logs
.
Configure
(
new
LoggerFactory
().
AddConsole
(
LogLevel
.
Trace
,
false
));
Logs
.
Configure
(
Logs
.
GetLoggerFactory
(
args
));
NodeSettings
nodeSettings
=
NodeSettings
.
FromArguments
(
args
);
var
fullNodeBuilder
=
new
FullNodeBuilder
()
.
UseNodeSettings
(
nodeSettings
)
.
UseWallet
()
.
Use
Light
Wallet
()
.
UseBlockNotification
()
.
UseTransactionNotification
()
.
UseApi
();
...
...
@@ -36,13 +37,10 @@ namespace Breeze.Daemon
fullNodeBuilder
.
UseTumbleBit
(
new
Uri
(
tumblerAddress
));
}
var
node
=
(
FullNode
)
fullNodeBuilder
.
Build
();
var
node
=
fullNodeBuilder
.
Build
();
// start Full Node - this will also start the API
node
.
Start
();
Console
.
WriteLine
(
"Press any key to stop"
);
Console
.
ReadLine
();
node
.
Dispose
();
node
.
Run
();
}
}
}
Breeze/src/Breeze.TumbleBit.Client/Breeze.TumbleBit.Client.csproj
View file @
8e6ab15d
...
...
@@ -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 @
8e6ab15d
...
...
@@ -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 @
8e6ab15d
...
...
@@ -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 @
8e6ab15d
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
;
namespace
Breeze.Wallet
{
public
class
WalletFeature
:
FullNodeFeature
public
class
Light
WalletFeature
:
FullNodeFeature
{
private
readonly
ITracker
tracker
;
private
readonly
IWalletManager
walletManager
;
private
readonly
TrackNotifier
trackNotifier
;
public
WalletFeature
(
ITracker
tracker
,
IWalletManager
walletManag
er
)
public
LightWalletFeature
(
TrackNotifier
trackNotifi
er
)
{
this
.
tracker
=
tracker
;
this
.
walletManager
=
walletManager
;
this
.
trackNotifier
=
trackNotifier
;
}
public
override
void
Start
()
{
this
.
tracker
.
Initialize
();
this
.
trackNotifier
.
Initialize
().
GetAwaiter
().
GetResult
();
}
public
override
void
Stop
()
{
this
.
walletManager
.
Dispose
();
base
.
Stop
();
}
}
public
static
class
WalletFeatureExtension
{
public
static
IFullNodeBuilder
UseWallet
(
this
IFullNodeBuilder
fullNodeBuilder
)
public
static
IFullNodeBuilder
Use
Light
Wallet
(
this
IFullNodeBuilder
fullNodeBuilder
)
{
// use the wallet and on top of that start to notifier
fullNodeBuilder
.
UseWallet
();
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
<
ITracker
,
Tracker
>();
services
.
AddSingleton
<
ILoggerFactory
>(
loggerFactory
);
services
.
AddSingleton
<
IWalletManager
,
WalletManager
>();
services
.
AddSingleton
<
WalletController
>();
services
.
AddSingleton
<
TrackNotifier
>();
});
});
...
...
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/Tracker.cs
→
Breeze/src/Breeze.Wallet/Track
Notifi
er.cs
View file @
8e6ab15d
...
...
@@ -6,50 +6,44 @@ 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
;
using
Stratis.Bitcoin.Wallet.Notifications
;
namespace
Breeze.Wallet
{
public
class
Track
er
:
ITrack
er
public
class
Track
Notifi
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
TrackNotifier
(
ILoggerFactory
loggerFactory
,
IWalletManager
walletManager
,
ConcurrentChain
chain
,
BlockNotification
blockNotification
,
Network
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
Task
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
();
//await this.WaitForChainDownloadAsync();
// start syncing blocks
var
bestHeightForSyncing
=
this
.
FindBestHeightForSyncing
();
this
.
SyncFrom
(
bestHeightForSyncing
);
this
.
logger
.
LogInformation
(
$"Tracker initialized. Syncing from
{
bestHeightForSyncing
}
."
)
;
return
Task
.
CompletedTask
;
}
private
int
FindBestHeightForSyncing
()
...
...
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