Commit 3a206f7a authored by nopara73's avatar nopara73

Merge branch 'master' into documentation

# Conflicts:
#	Breeze.Documentation/ApiSpecification.md
parents 2f5365de b32ce1f4
...@@ -238,6 +238,44 @@ ModelManifest.xml ...@@ -238,6 +238,44 @@ ModelManifest.xml
# Paket dependency manager # Paket dependency manager
.paket/paket.exe .paket/paket.exe
# UI ignores
# compiled output
**/dist
**/tmp
**/build
# dependencies
**/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
**/.sass-cache
**/connect.lock
**/coverage/*
**/libpeerconnection.log
npm-debug.log
testem.log
**/typings
# e2e
**/e2e/*.js
**/e2e/*.map
#System Files
.DS_Store
Thumbs.db
# ORIGINAL .gitignore STARTS HERE # ORIGINAL .gitignore STARTS HERE
......
foo `/api/v1/`
\ No newline at end of file
## Request/Response
RESPONSE: responsecode (`200` if success, `400`/`500` if error, see later)
```
{
"success": "true"
}
```
HEADERS
`Content-Type:application/json`
## Errors
### General errors
`400` series status codes for client issues & `500` series status codes for server issues.
API should standardize that all `400` series errors come with consumable JSON error representation.
BODY
```
{
"success": "false",
"message": "something bad happened", // ex.Message maybe?
"description": ex.ToString()
}
```
### wallet is not created
This error message comes at all request if the wallet is not created yet, except
- `POST /wallet/create`
- `POST /wallet/recover`
- `POST /wallet/send-transaction`
```
{
"success": "false",
"message": "wallet is not created",
"description": ""
}
```
### wallet is not decrypted
This error message comes at all request if the wallet is not loaded yet, except
- `POST /wallet/create`
- `POST /wallet/recover`
- `POST /wallet/send-transaction`
- `POST /wallet/load`
- `DELETE /wallet`
```
{
"success": "false",
"message": "wallet is not decrypted",
"description": ""
}
```
## Key Management
```
GET /wallet/general - Displays general information on the wallet
GET /wallet/sensitive - Displays sensitive information on the wallet
GET /wallet/status - Displays dynamic information on the wallet
POST /wallet/create - Creates the wallet
POST /wallet/load - Loads the wallet and starts syncing
POST /wallet/recover - Recovers the wallet
DELETE /wallet - Deletes the wallet
```
## Syncing
```
GET /wallet/mempool/?allow=[true/false] - Allows or disallows mempool syncing
```
## Key Monitoring
```
GET /wallet/receive/[account1/account2] - Displays unused receive addresses of the specified wallet account
GET /wallet/history/[account1/account2] - Displays the history of the specified wallet account
GET /wallet/balance/[account1/account2] - Displays the balances of the specified wallet account
```
## Transaction building and sending
```
POST /wallet/build-transaction/[account1/account2] - Attempts to build a transaction with the specified wallet account
POST /wallet/send-transaction - Attempts to send a transaction
```
# Details
## GET /wallet/general - Displays general information on the wallet
### Responses
```
{
"success": "true",
"walletFilePath": "path to the wallet file",
"encryptedSeed": "6PYKWP34en1wELfcJDgXaFRPugjgkDdEk2p2Pzytm1158dxgNyLAUXwpKL",
"chainCode": "q/Fn7+RSIVM0p0Nj6rIuNkybF+0WKeSZPMQS2QCbDzY=",
"network": "main", // main/testnet
"creationTime": "2017-03-21",
"decrypted": "true",
"uniqueId": "sadwpiqjdpijwqdpijwqidjoi" // can only get if decrypted, if not it's empty string
}
```
## GET /wallet/sensitive - Displays sensitive information on the wallet
### Parameters
```
{
"password": "password"
}
```
### Responses
```
{
"success": "true",
"extkey": "sadwqdpqoijedqcdoijsadoijsadisa",
"extpubkey": "dalkdsaklkjdlkjdsaljlkjdsalkjdsalk",
}
```
## GET /wallet/status - Displays dynamic information on the wallet
### Responses
```
{
"success": "true",
"connectedNodeCount": "7",
"maxConnextedNodeCount": "8",
"headerChainHeight": "1048",
"trackingHeight": "1047",
"trackedTransactionCount": "306",
"trackedScriptPubKeyCount": "100",
"walletState": "syncingBlocks", // notStarted/syncingHeaders/syncingBlocks/mempoolStalling/syncingMempool/synced
"historyChangeBump": "231321" // every time something changes this number is dumped. So it's enough to poll this get request and no need to poll the history or balances get request constantly to update the up, but only when this number changes
}
```
## POST /wallet/create - Creates the wallet
### Parameters
```
{
"network": "main", // "main" or "testnet"
"password": "password"
}
```
### Responses
```
{
"success": "true",
"mnemonic": "foo bar buz",
}
```
## POST /wallet/load - Loads the wallet and starts syncing
### Parameters
```
{
"password": "password"
}
```
## POST /wallet/recover - Recovers the wallet
### Parameters
```
{
"network": "main", // "main" or "testnet"
"password": "password",
"mnemonic": "foo bar buz",
"creationTime": "2017-02-03" // DateTimeOffset.ParseExact("1998-01-01", "yyyy-MM-dd", CultureInfo.InvariantCulture), utc time
}
```
### Response
Cannot check if the password is good or not. If the password is wrong it'll recover a wallet with the wrong password.
## DELETE /wallet - Deletes the wallet
Works as expected.
## GET /wallet/mempool/?allow=[true/false] - Allows or disallows mempool syncing
Works as expected.
## GET /wallet/receive/[account1/account2] - Displays unused receive addresses of the specified wallet account
### Responses
```
{
"success": "true",
"addresses": // 7 unused receive address (7 is the best number: https://www.psychologytoday.com/blog/fulfillment-any-age/201109/7-reasons-we-7-reasons)
[
"mzz63n3n89KVeHQXRqJEVsQX8MZj5zeqCw",
"mhm1pFe2hH7yqkdQhwbBQ8qLnMZqfL6jXb",
"mmRzqMDBrfNxMfryQSYec3rfPHXURNapBA",
"my2ELDBqLGVz1ER7CMynDqG4BUpV2pwfR5",
"mmwccp4GefhPn4P6Mui6DGLGzHTVyQ12tD",
"miTedyDXJAz6GYMRasiJk9M3ibnGnb99M1",
"mrsb39MmPceSPfKAURTH23hYgLRH1M1Uhg"
]
}
```
## GET /wallet/history/[account1/account2] - Displays the history of the specified wallet account
### Responses
```
{
"success": "true",
"history":
[
{
"txid": "9a9949476b629b4075b31d8faad64dad352586a18df8f2810c5a7bb900478c60",
"amount": "0.1",
"confirmed": "true",
"timestamp": "2016.12.19. 23:15:05" // if confirmed it's the blocktime, utc
},
{
"txid": "9a9949476b629b4075b31d8faad64dad352586a18df8f2810c5a7bb900478c60",
"amount": "-0.1",
"confirmed": "false",
"timestamp": "2016.12.20. 1:15:36" // if unconfirmed it's the time our node first seen this transaction, utc
}
]
}
```
## GET /wallet/balance/[account1/account2] - Displays the balances of the specified wallet account
### Responses
```
{
"success": "true",
"synced": "true",
"confirmed": "0.144",
"unconfirmed": "-6.23"
}
```
If the synced is false, then the balances might not be accurate.
Confirmed balance is the (amount of unspent confirmed outputs - unconfirmed outgoing transactions). It cannot be negative.
Unconfirmed balance is the difference of unconfirmed incoming and outgoing transactions. It can be negative.
## POST /wallet/build-transaction/[account1/account2] - Attempts to build a transaction with the specified wallet account
### Parameters
```
{
"password": "password",
"address": "1Xyz...",
"amount": "0.12", // in btc, if 0, then spends all available
"feeType": "low", // "low"/"medium"/"high"
"allowUnconfirmed": "true" // if spending unconfirmed outputs is allowed
}
```
### Responses
#### Successful
```
{
"success": "true",
"spendsUnconfirmed": "false", // If spends unconfirmed you can ask the user if it's sure about spending unconfirmed transaction (if inputs are malleated or inputs never confirm then this transaction will never confirm either"
"fee": "0.0001",
"feePercentOfSent": "0.1" // Percentage of the total spent amount, there must be a safety limit implemented here
"hex": "0100000002d9dced2b6fc80c706d3564670cb6706afe7a798863a9218efcdcf415d58f0f82000000006a473044022030b8bea478444bd52f08de33b082cde1176d3137111f506eefefa91b47b1f6bf02204f12746abd1aeac5805872d163592cf145967fa0619339a9c5348d674852ef4801210224ec1e4c270ce373e6999eebfa01d0a7e7db3c537c026f265233350d5aab81fbfeffffffa0706db65c5e3594d43df5a2a8b6dfd3c9ee506b678f8c26f7820b324b26aa0f000000006a473044022061b718034f876590d6d80bac77a63248b2548d934849acd02c4f4236309e853002201aded6b24f553b6902cf571276b37b12f76b75650164d8738c74469b4edd547e012103d649294a0ca4db920a69eacd6a75cb8a38ae1b81129900621ce45e6ba3438a7bfeffffff0280a90300000000001976a914d0965947ebb329b776328624ebde8f8b32dc639788ac1cc80f00000000001976a914c2a420d34fc86cff932b8c3191549a0ddfd2b0d088acba770f00"
"transaction": // NBitcoin.Transaction.ToString()
{
"transaction": "0100000002d9dced2b6fc80c706d3564670cb6706afe7a798863a9218efcdcf415d58f0f82000000006a473044022030b8bea478444bd52f08de33b082cde1176d3137111f506eefefa91b47b1f6bf02204f12746abd1aeac5805872d163592cf145967fa0619339a9c5348d674852ef4801210224ec1e4c270ce373e6999eebfa01d0a7e7db3c537c026f265233350d5aab81fbfeffffffa0706db65c5e3594d43df5a2a8b6dfd3c9ee506b678f8c26f7820b324b26aa0f000000006a473044022061b718034f876590d6d80bac77a63248b2548d934849acd02c4f4236309e853002201aded6b24f553b6902cf571276b37b12f76b75650164d8738c74469b4edd547e012103d649294a0ca4db920a69eacd6a75cb8a38ae1b81129900621ce45e6ba3438a7bfeffffff0280a90300000000001976a914d0965947ebb329b776328624ebde8f8b32dc639788ac1cc80f00000000001976a914c2a420d34fc86cff932b8c3191549a0ddfd2b0d088acba770f00",
"transactionId": "22ab5e9b703c0d4cb6023e3a1622b493adc8f83a79771c83a73dfa38ef35b07c",
"isCoinbase": false,
"block": null,
"spentCoins": [
{
"transactionId": "820f8fd515f4dcfc8e21a96388797afe6a70b60c6764356d700cc86f2beddcd9",
"index": 0,
"value": 100000,
"scriptPubKey": "76a914e7c1345fc8f87c68170b3aa798a956c2fe6a9eff88ac",
"redeemScript": null
},
{
"transactionId": "0faa264b320b82f7268c8f676b50eec9d3dfb6a8a2f53dd494355e5cb66d70a0",
"index": 0,
"value": 1180443,
"scriptPubKey": "76a914f3821cff5a90328271d8596198f68e97fbe2ea0e88ac",
"redeemScript": null
}
],
"receivedCoins": [
{
"transactionId": "22ab5e9b703c0d4cb6023e3a1622b493adc8f83a79771c83a73dfa38ef35b07c",
"index": 0,
"value": 240000,
"scriptPubKey": "76a914d0965947ebb329b776328624ebde8f8b32dc639788ac",
"redeemScript": null
},
{
"transactionId": "22ab5e9b703c0d4cb6023e3a1622b493adc8f83a79771c83a73dfa38ef35b07c",
"index": 1,
"value": 1034268,
"scriptPubKey": "76a914c2a420d34fc86cff932b8c3191549a0ddfd2b0d088ac",
"redeemScript": null
}
],
"firstSeen": "2016-10-31T09:13:18.4420023+00:00",
"fees": 6175
}
}
```
#### Errors
```
{
"success": "false",
"message": "wallet is not synced",
"description": ""
}
```
## POST /wallet/send-transaction - Attempts to send a transaction
### Parameters
```
{
"hex": "01000000061b1ca819e76f9131b23335ec905ffc5fc27e36a7843a5b7c6d1b455b904359f7000000006b483045022100c11f78ce7f02b2312b6675d3ad99cec6ede879d446c2b14628ef4f8ce9b3fdc5022073649a14971568a1cd2aa84b5dd404645f29e49882f60a9642850539443872fe012102a41e4348bb233e40cf3a3402e2dc92a31b69ef56090fff242aa7e4bff828929fffffffff1d3c389af5fdd047e307e5d5f87656bb2ef0c40b6ee879d342a59192090d3fbc000000006b483045022100dc7e0445fe98f3e76d68906c640ecca597598a03b48e6b85d72918347b9da7330220340ce9e9533ea84375a1f2122b7868b8ab556da53f1e1af14d1a71b0b123aade012102a41e4348bb233e40cf3a3402e2dc92a31b69ef56090fff242aa7e4bff828929fffffffff1ed624bad3df9d3a7be56dcd5d97c996fccc78164f16f59658b33f8da8859deb000000006b483045022100ba2a55f55a37b6712dd25dbef411aed869190ef60a208b39d4bd8e0ce8635b4d02201976d63489e23205aab651a9def43d6b3a740ba06de2ecabc43504241a71f229012102a41e4348bb233e40cf3a3402e2dc92a31b69ef56090fff242aa7e4bff828929fffffffff24e5cb4893beb0bb60193dbe11a9778d07e127c1cbc939c0a0388b1013ef75d9000000006a47304402203c27eea34db0ba070bee38d625d2cfcec1a0f5d8a9124023c84e9963d37f6145022015f7657cc57be515e6aa43c93c73457f5583b7c90c0af4e8a2f913257df27b0b012102a41e4348bb233e40cf3a3402e2dc92a31b69ef56090fff242aa7e4bff828929fffffffff997e6738c45eaf7af8bed7dc09e258139bffb0d2be8b4167473b6943adc0b28b000000006a47304402202d4c6df39b725d571d67bef14f0c6baa0cf4b93aa54aac2d2a15d3d940510d0602203643162545d5b63c007986627a317ed962f4d5023e4c15e9636a4eede86930c7012102a41e4348bb233e40cf3a3402e2dc92a31b69ef56090fff242aa7e4bff828929fffffffffcff2021b6b0bcd2a8b38583539dc140b98da4f41ae1e4adb089dc2cf3b66d6c6000000006a473044022019d5264c99145c7203e690fb2f57b0e218af2761e024f9ec1b774c703939b96e02204c2430fc4ae0fa43afb19a722f7b5d706bf5f2d5ee85229cbdc7a7b26433f5fd012102a41e4348bb233e40cf3a3402e2dc92a31b69ef56090fff242aa7e4bff828929fffffffff018f73e606000000001976a914ec093b0943ec524769553e1b7261b67ecab47e8688ac00000000"
}
```
...@@ -13,15 +13,15 @@ namespace Breeze.Api.Tests ...@@ -13,15 +13,15 @@ namespace Breeze.Api.Tests
public class ControllersTests public class ControllersTests
{ {
[Fact] [Fact]
public void CreateSafeSuccessfullyReturnsMnemonic() public void CreateWalletSuccessfullyReturnsMnemonic()
{ {
var mockSafeCreate = new Mock<ISafeWrapper>(); var mockWalletCreate = new Mock<IWalletWrapper>();
mockSafeCreate.Setup(safe => safe.Create(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns("mnemonic"); mockWalletCreate.Setup(wallet => wallet.Create(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns("mnemonic");
var controller = new SafeController(mockSafeCreate.Object); var controller = new WalletController(mockWalletCreate.Object);
// Act // Act
var result = controller.Create(new SafeCreationModel var result = controller.Create(new WalletCreationModel
{ {
Name = "myName", Name = "myName",
FolderPath = "", FolderPath = "",
...@@ -30,29 +30,29 @@ namespace Breeze.Api.Tests ...@@ -30,29 +30,29 @@ namespace Breeze.Api.Tests
}); });
// Assert // Assert
mockSafeCreate.VerifyAll(); mockWalletCreate.VerifyAll();
var viewResult = Assert.IsType<JsonResult>(result); var viewResult = Assert.IsType<JsonResult>(result);
Assert.Equal("mnemonic", viewResult.Value); Assert.Equal("mnemonic", viewResult.Value);
Assert.NotNull(result); Assert.NotNull(result);
} }
[Fact] [Fact]
public void LoadSafeSuccessfullyReturnsSafeModel() public void LoadWalletSuccessfullyReturnsWalletModel()
{ {
SafeModel safeModel = new SafeModel WalletModel walletModel = new WalletModel
{ {
FileName = "myWallet", FileName = "myWallet",
Network = "MainNet", Network = "MainNet",
Addresses = new List<string> { "address1", "address2", "address3", "address4", "address5" } Addresses = new List<string> { "address1", "address2", "address3", "address4", "address5" }
}; };
var mockSafeWrapper = new Mock<ISafeWrapper>(); var mockWalletWrapper = new Mock<IWalletWrapper>();
mockSafeWrapper.Setup(safe => safe.Recover(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(safeModel); mockWalletWrapper.Setup(wallet => wallet.Recover(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(walletModel);
var controller = new SafeController(mockSafeWrapper.Object); var controller = new WalletController(mockWalletWrapper.Object);
// Act // Act
var result = controller.Recover(new SafeRecoveryModel var result = controller.Recover(new WalletRecoveryModel
{ {
Name = "myName", Name = "myName",
FolderPath = "", FolderPath = "",
...@@ -62,32 +62,32 @@ namespace Breeze.Api.Tests ...@@ -62,32 +62,32 @@ namespace Breeze.Api.Tests
}); });
// Assert // Assert
mockSafeWrapper.VerifyAll(); mockWalletWrapper.VerifyAll();
var viewResult = Assert.IsType<JsonResult>(result); var viewResult = Assert.IsType<JsonResult>(result);
Assert.NotNull(viewResult.Value); Assert.NotNull(viewResult.Value);
Assert.IsType<SafeModel>(viewResult.Value); Assert.IsType<WalletModel>(viewResult.Value);
var model = viewResult.Value as SafeModel; var model = viewResult.Value as WalletModel;
Assert.Equal("myWallet", model.FileName); Assert.Equal("myWallet", model.FileName);
} }
[Fact] [Fact]
public void RecoverSafeSuccessfullyReturnsSafeModel() public void RecoverWalletSuccessfullyReturnsWalletModel()
{ {
SafeModel safeModel = new SafeModel WalletModel walletModel = new WalletModel
{ {
FileName = "myWallet", FileName = "myWallet",
Network = "MainNet", Network = "MainNet",
Addresses = new List<string> { "address1", "address2", "address3", "address4", "address5" } Addresses = new List<string> { "address1", "address2", "address3", "address4", "address5" }
}; };
var mockSafeWrapper = new Mock<ISafeWrapper>(); var mockWalletWrapper = new Mock<IWalletWrapper>();
mockSafeWrapper.Setup(safe => safe.Load(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(safeModel); mockWalletWrapper.Setup(wallet => wallet.Load(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(walletModel);
var controller = new SafeController(mockSafeWrapper.Object); var controller = new WalletController(mockWalletWrapper.Object);
// Act // Act
var result = controller.Load(new SafeLoadModel var result = controller.Load(new WalletLoadModel
{ {
Name = "myName", Name = "myName",
FolderPath = "", FolderPath = "",
...@@ -95,25 +95,25 @@ namespace Breeze.Api.Tests ...@@ -95,25 +95,25 @@ namespace Breeze.Api.Tests
}); });
// Assert // Assert
mockSafeWrapper.VerifyAll(); mockWalletWrapper.VerifyAll();
var viewResult = Assert.IsType<JsonResult>(result); var viewResult = Assert.IsType<JsonResult>(result);
Assert.NotNull(viewResult.Value); Assert.NotNull(viewResult.Value);
Assert.IsType<SafeModel>(viewResult.Value); Assert.IsType<WalletModel>(viewResult.Value);
var model = viewResult.Value as SafeModel; var model = viewResult.Value as WalletModel;
Assert.Equal("myWallet", model.FileName); Assert.Equal("myWallet", model.FileName);
} }
[Fact] [Fact]
public void FileNotFoundExceptionandReturns404() public void FileNotFoundExceptionandReturns404()
{ {
var mockSafeWrapper = new Mock<ISafeWrapper>(); var mockWalletWrapper = new Mock<IWalletWrapper>();
mockSafeWrapper.Setup(safe => safe.Load(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Throws<FileNotFoundException>(); mockWalletWrapper.Setup(wallet => wallet.Load(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Throws<FileNotFoundException>();
var controller = new SafeController(mockSafeWrapper.Object); var controller = new WalletController(mockWalletWrapper.Object);
// Act // Act
var result = controller.Load(new SafeLoadModel var result = controller.Load(new WalletLoadModel
{ {
Name = "myName", Name = "myName",
FolderPath = "", FolderPath = "",
...@@ -121,7 +121,7 @@ namespace Breeze.Api.Tests ...@@ -121,7 +121,7 @@ namespace Breeze.Api.Tests
}); });
// Assert // Assert
mockSafeWrapper.VerifyAll(); mockWalletWrapper.VerifyAll();
var viewResult = Assert.IsType<ObjectResult>(result); var viewResult = Assert.IsType<ObjectResult>(result);
Assert.NotNull(viewResult); Assert.NotNull(viewResult);
Assert.Equal(404, viewResult.StatusCode); Assert.Equal(404, viewResult.StatusCode);
......
{ {
"variables": [], "variables": [],
"info": { "info": {
"name": "Safe", "name": "Wallet",
"_postman_id": "11f915f1-7aac-bfeb-000c-b66348f4636f", "_postman_id": "11f915f1-7aac-bfeb-000c-b66348f4636f",
"description": "Requests relating to operations on the safe", "description": "Requests relating to operations on the wallet",
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
}, },
"item": [ "item": [
{ {
"name": "Create safe - success", "name": "Create wallet - success",
"request": { "request": {
"url": "http://localhost:5000/api/safe/", "url": "http://localhost:5000/api/v1/wallet/",
"method": "POST", "method": "POST",
"header": [ "header": [
{ {
...@@ -28,9 +28,9 @@ ...@@ -28,9 +28,9 @@
"response": [] "response": []
}, },
{ {
"name": "Create safe - validation errors", "name": "Create wallet - validation errors",
"request": { "request": {
"url": "http://localhost:5000/api/safe/", "url": "http://localhost:5000/api/v1/wallet/",
"method": "POST", "method": "POST",
"header": [ "header": [
{ {
...@@ -48,9 +48,9 @@ ...@@ -48,9 +48,9 @@
"response": [] "response": []
}, },
{ {
"name": "Load safe", "name": "Load wallet",
"request": { "request": {
"url": "http://localhost:5000/api/safe/?password=123456&folderPath=MyWallets&name=myFirstWallet", "url": "http://localhost:5000/api/v1/wallet/?password=123456&folderPath=MyWallets&name=myFirstWallet",
"method": "GET", "method": "GET",
"header": [ "header": [
{ {
...@@ -68,9 +68,9 @@ ...@@ -68,9 +68,9 @@
"response": [] "response": []
}, },
{ {
"name": "Recover safe", "name": "Recover wallet",
"request": { "request": {
"url": "http://localhost:5000/api/safe/recover", "url": "http://localhost:5000/api/v1/wallet/recover",
"method": "POST", "method": "POST",
"header": [ "header": [
{ {
......
...@@ -2,19 +2,15 @@ ...@@ -2,19 +2,15 @@
namespace Breeze.Api.Controllers namespace Breeze.Api.Controllers
{ {
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]
public class NodeController : Controller public class NodeController : Controller
{ {
[Route("connect")] [HttpGet]
public IActionResult Connect(string[] args)
{
return NotFound();
}
[Route("status")] [Route("status")]
public IActionResult Status() public IActionResult Status()
{ {
return this.NotFound(); return this.NotFound();
} }
} }
} }
\ No newline at end of file
using Microsoft.AspNetCore.Builder; using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Swashbuckle.AspNetCore.Swagger;
namespace Breeze.Api namespace Breeze.Api
{ {
...@@ -28,6 +31,31 @@ namespace Breeze.Api ...@@ -28,6 +31,31 @@ namespace Breeze.Api
// add serializers for NBitcoin objects // add serializers for NBitcoin objects
.AddJsonOptions(options => NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(options.SerializerSettings)) .AddJsonOptions(options => NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(options.SerializerSettings))
.AddControllers(services); .AddControllers(services);
services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
});
// Register the Swagger generator, defining one or more Swagger documents
services.AddSwaggerGen(setup =>
{
setup.SwaggerDoc("v1", new Info { Title = "Breeze.Api", Version = "v1" });
// FIXME: prepopulates the version in the URL of the Swagger UI found at http://localhost:5000/swagger
// temporary needed until Swashbuckle supports it out-of-the-box
setup.DocInclusionPredicate((version, apiDescription) =>
{
apiDescription.RelativePath = apiDescription.RelativePath.Replace("v{version}", version);
var versionParameter = apiDescription.ParameterDescriptions.SingleOrDefault(p => p.Name == "version");
if (versionParameter != null)
{
apiDescription.ParameterDescriptions.Remove(versionParameter);
}
return true;
});
});
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
...@@ -37,6 +65,15 @@ namespace Breeze.Api ...@@ -37,6 +65,15 @@ namespace Breeze.Api
loggerFactory.AddDebug(); loggerFactory.AddDebug();
app.UseMvc(); app.UseMvc();
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Breeze.Api V1");
});
} }
} }
} }
{ {
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Mvc": "1.1.2", "Microsoft.AspNetCore.Mvc": "1.1.2",
"Microsoft.AspNetCore.Mvc.Versioning": "1.0.3",
"Microsoft.AspNetCore.Routing": "1.1.1", "Microsoft.AspNetCore.Routing": "1.1.1",
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.1", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.1",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.1", "Microsoft.AspNetCore.Server.Kestrel": "1.1.1",
...@@ -15,6 +16,7 @@ ...@@ -15,6 +16,7 @@
"Microsoft.NETCore.App": "1.1.0", "Microsoft.NETCore.App": "1.1.0",
"NBitcoin": "3.0.2.10", "NBitcoin": "3.0.2.10",
"Stratis.Bitcoin": "1.0.1.2-alpha", "Stratis.Bitcoin": "1.0.1.2-alpha",
"Swashbuckle.AspNetCore": "1.0.0-rc3",
"System.Reactive": "3.1.1", "System.Reactive": "3.1.1",
"System.Runtime.Loader": "4.3.0" "System.Runtime.Loader": "4.3.0"
}, },
......
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
DEBUG=true
HOST=http://localhost:4200
# Breeze-UI
Graphical User Interface for Stratis Breeze Wallet.
{
"project": {
"name": "breeze-ui"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.json",
"prefix": "app",
"styles": [
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
"styles.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"files": "src/**/*.ts",
"project": "src/tsconfig.json"
},
{
"files": "e2e/**/*.ts",
"project": "e2e/tsconfig.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"prefixInterfaces": false,
"inline": {
"style": false,
"template": false
},
"spec": {
"class": false,
"component": true,
"directive": true,
"module": false,
"pipe": true,
"service": true
}
}
}
import { BreezeUIPage } from './app.po';
describe('breeze-ui App', function() {
let page: BreezeUIPage;
beforeEach(() => {
page = new BreezeUIPage();
});
it('should display message saying app works', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('app works!');
});
});
import { browser, element, by } from 'protractor';
export class BreezeUIPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}
{
"compileOnSave": false,
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../dist/out-tsc-e2e",
"sourceMap": true,
"target": "es5",
"typeRoots": [
"../node_modules/@types"
]
}
}
const electron = require('electron')
const dotenv = require('dotenv')
// const edge = require('electron-edge')
// Module to control application life.
const app = electron.app
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow
const path = require('path')
const url = require('url')
// Require dotenv
dotenv.config();
if (process.env.DEBUG === 'true'){
// Require electron-reload for dev options
require('electron-reload')(__dirname);
}
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow = null;
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 1000, height: 600, frame: true, minWidth: 1000, minHeight: 600, icon: "./src/assets/images/stratis-tray.png"})
if (process.env.DEBUG === 'false'){
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}));
} else {
mainWindow.loadURL('http://localhost:4200');
// Open the DevTools.
mainWindow.webContents.openDevTools();
}
// mainWindow.loadURL(url.format({
// pathname: path.join(__dirname, 'index.html'),
// protocol: 'file:',
// slashes: true
// }));
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', function () {
startServer()
createWindow()
createTray()
})
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
function startServer() {
// var startServer = edge.func('./assets/dll/Wallet.WebApi.dll');
}
function createTray() {
//Put the app in system tray
const Menu = electron.Menu
const Tray = electron.Tray
let appIcon = null
const iconName = process.platform === 'win32' ? './src/assets/images/stratis-tray.png' : './src/assets/images/stratis-tray.png'
const iconPath = path.join(__dirname, iconName)
appIcon = new Tray(iconPath)
const contextMenu = Menu.buildFromTemplate([{
label: 'Hide/Show',
click: function () {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
}
}])
appIcon.setToolTip('Breeze Wallet')
appIcon.setContextMenu(contextMenu)
app.on('window-all-closed', function () {
if (appIcon) appIcon.destroy()
})
}
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', 'angular-cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-remap-istanbul'),
require('angular-cli/plugins/karma')
],
files: [
{ pattern: './src/test.ts', watched: false }
],
preprocessors: {
'./src/test.ts': ['angular-cli']
},
mime: {
'text/x-typescript': ['ts','tsx']
},
remapIstanbulReporter: {
reports: {
html: 'coverage',
lcovonly: './coverage/coverage.lcov'
}
},
angularCli: {
config: './angular-cli.json',
environment: 'dev'
},
reporters: config.angularCli && config.angularCli.codeCoverage
? ['progress', 'karma-remap-istanbul']
: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
{
"name": "breeze-ui",
"version": "0.1.0",
"description": "Graphical User Interface for the Breeze Wallet.",
"main": "electron.js",
"license": "MIT",
"angular-cli": {},
"scripts": {
"ng": "ng",
"start": "ng serve --prod",
"test": "ng test",
"pree2e": "webdriver-manager update --standalone false --gecko false",
"e2e": "protractor",
"build": "ng build --prod",
"electron": "electron .",
"electron:build": "npm run electron:prepare && npm run electron:pack-win",
"electron:prepare": "npm run build && xcopy package.json dist && xcopy electron.js dist",
"electron:package": "npm run electron:prepare && npm run electron:pack-mac && npm run electron:pack-win && npm run electron:pack-linux",
"electron:pack-mac": "electron-packager dist --asar --overwrite --platform=darwin --arch=x64 --out=build",
"electron:pack-win": "electron-packager dist --asar --overwrite --platform=win32 --arch=ia32 --out=build",
"electron:pack-linux": "electron-packager dist --asar --overwrite --platform=linux --arch=x64 --out=build"
},
"private": true,
"dependencies": {
"@angular/animations": "^4.0.0",
"@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0",
"@angular/compiler-cli": "^4.0.0",
"@angular/core": "^4.0.0",
"@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/platform-server": "^4.0.0",
"@angular/router": "^4.0.0",
"bootstrap": "^4.0.0-alpha.6",
"core-js": "^2.4.1",
"dotenv": "^4.0.0",
"electron-edge": "^6.5.4",
"rxjs": "^5.2.0",
"ts-helpers": "^1.1.2",
"typescript": "^2.2.2",
"zone.js": "^0.8.5"
},
"devDependencies": {
"@angular/cli": "^1.0.0",
"@angular/compiler-cli": "^4.0.0",
"@types/jasmine": "2.5.46",
"@types/node": "~7.0.12",
"codelyzer": "~2.0.0",
"dotenv": "^4.0.0",
"electron": "^1.6.2",
"electron-reload": "^1.1.0",
"jasmine-core": "~2.5.2",
"jasmine-spec-reporter": "~3.2.0",
"karma": "~1.5.0",
"karma-chrome-launcher": "~2.0.0",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.0.0",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.1",
"ts-node": "~3.0.2",
"tslint": "~4.5.1"
}
}
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
/*global jasmine */
var SpecReporter = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
useAllAngular2AppRoots: true,
beforeLaunch: function() {
require('ts-node').register({
project: 'e2e'
});
},
onPrepare: function() {
jasmine.getEnv().addReporter(new SpecReporter());
}
};
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
<router-outlet></router-outlet>
\ No newline at end of file
/* tslint:disable:no-unused-variable */
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
});
TestBed.compileComponents();
});
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app works!'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app works!');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('app works!');
}));
});
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
constructor(private router: Router) {}
private isConfigured: boolean = true;
private checkConfigured(){
if (this.isConfigured) {
this.router.navigateByUrl('/wallet')
} else {
this.router.navigateByUrl('/setup')
}
}
ngOnInit() {
this.checkConfigured();
}
}
\ No newline at end of file
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { SetupModule } from './setup/setup.module';
import { WalletModule } from './wallet/wallet.module';
import { SharedModule } from './shared/shared.module';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [
AppRoutingModule,
BrowserModule,
HttpModule,
SetupModule,
WalletModule,
SharedModule.forRoot()
],
declarations: [
AppComponent
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
\ No newline at end of file
import { Observable } from 'rxjs/Observable';
import { Component, OnInit } from '@angular/core';
import { ApiService } from '../../shared/api/api.service';
@Component({
selector: 'is-connected',
template: 'Wallet connected to API: {{result}}',
})
export class ApiComponent implements OnInit {
result: string;
constructor(private apiService: ApiService) {}
ngOnInit() {
this.apiService
.isConnected()
.subscribe((data: string) => this.result = data,
() => console.log("isConnected() complete from init"));
if (!this.result) {
this.result = "false"
}
}
}
<div class="content-wrapper">
<h1>
Please create a new wallet.
</h1>
<div>
<is-connected></is-connected>
</div>
<div class="form-group">
<label for="name">Name:</label>
<input class="form-control" type="text" #walletName required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input class="form-control" type="password" #walletPassword required>
</div>
<div class="form-group">
<label for="networklabel">Network:</label>
<select name="network" #walletNetwork>
<option value="main">Main</option>
<option value="test">Testnet</option>
</select>
</div>
<div class="form-group">
<label for="path">Path:</label>
<input class="form-control" type="text" #walletPath required>
</div>
<button type="submit" (click)="createWallet(walletPassword.value, walletNetwork.value, walletPath.value, walletName.value)">Create</button>
<div>
<label>Mnemonic:</label>
</div>
<div>
<label>{{body}}</label>
</div>
</div>
\ No newline at end of file
import { Component, Injectable } from '@angular/core';
import { ApiService } from '../../shared/api/api.service';
import { SafeCreation } from '../../shared/safe-creation';
import { Mnemonic } from '../../shared/mnemonic';
@Component({
selector: 'create-component',
templateUrl: './create.component.html',
styleUrls: ['./create.component.css'],
})
export class CreateComponent {
constructor(private apiService: ApiService) {}
private newWallet: SafeCreation;
private body: string;
private createWallet(password: string, network: string, folderPath: string, name: string, ) {
this.newWallet.password = password;
this.newWallet.network = network;
this.newWallet.folderPath = folderPath;
this.newWallet.name = name;
this.apiService
.createWallet(this.newWallet)
//.map(res => {let body = res.text()})
.subscribe((response: string) => this.body = response,
() => console.log("createWallet() complete from init"));
}
}
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SetupComponent } from './setup.component';
const routes: Routes = [
{ path: 'setup', component: SetupComponent }
];
@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ]
})
export class SetupRoutingModule {}
<h1>Welcome to setup</h1>
<create-component></create-component>
\ No newline at end of file
import { Component } from '@angular/core';
@Component({
selector: 'setup-component',
templateUrl: './setup.component.html',
styleUrls: ['./setup.component.css'],
})
export class SetupComponent {
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import { SetupComponent } from './setup.component';
import { CreateComponent } from './create/create.component';
import { ApiComponent } from './create/api.component';
import { SharedModule } from '../shared/shared.module';
import { SetupRoutingModule } from './setup-routing.module';
import { ApiService } from '../shared/api/api.service';
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
SetupRoutingModule,
SharedModule
],
declarations: [
CreateComponent,
SetupComponent,
ApiComponent
],
exports: [SetupComponent],
providers: []
})
export class SetupModule { }
import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import { SafeCreation } from '../safe-creation';
import { Mnemonic } from '../mnemonic';
@Injectable()
export class ApiService {
constructor(private http: Http) {};
private webApiUrl = 'http://localhost:5000/';
private headers = new Headers({'Content-Type': 'application/json'});
isConnected(): Observable<string> {
return this.http
.get(this.webApiUrl + 'api/safe/connected')
.map(data => data.json())
}
createWallet(data: SafeCreation): Observable<any> {
console.log(JSON.stringify(data));
return this.http
.post(this.webApiUrl + 'api/safe', JSON.stringify(data), {headers: this.headers})
.map(response => response.json());
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
}
export class Mnemonic {
mnemonic: string;
}
export class SafeCreation {
password: string;
network: string;
folderPath: string;
name: string;
}
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { ApiService} from './api/api.service';
@NgModule({
imports: [CommonModule, RouterModule],
declarations: [],
exports: [CommonModule, FormsModule, RouterModule]
})
export class SharedModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: SharedModule,
providers: [ApiService]
};
}
}
\ No newline at end of file
import { Component } from '@angular/core';
@Component({
selector: 'history-component',
templateUrl: './history.component.html',
styleUrls: ['./history.component.css'],
})
export class HistoryComponent {
}
<nav class="navbar navbar-fixed-top">
<div class="container">
<a class="navbar-brand">Breeze</a>
<ul class="nav navbar-nav" routerLinkActive="active">
<li class="nav-item"><a class="nav-link" routerLink="send">Send</a></li>
<li class="nav-item"><a class="nav-link" routerLink="receive">Receive</a></li>
<li class="nav-item"><a class="nav-link" routerLink="history">History</a></li>
</ul>
</div>
</nav>
import { Component } from '@angular/core';
@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.css'],
})
export class MenuComponent {
}
<div class="content-wrapper">
<h1>Receive</h1>
<div>
<label>Unused Receive Addresses</label>
<div>
</div>
</div>
<div>
<label>Used Receive Addresses</label>
</div>
<div>
<label>Change Addresses</label>
</div>
</div>
\ No newline at end of file
import { Component } from '@angular/core';
@Component({
selector: 'receive-component',
templateUrl: './receive.component.html',
styleUrls: ['./receive.component.css'],
})
export class ReceiveComponent {
}
\ No newline at end of file
<div class="content-wrapper">
<h1>Send</h1>
<form (ngSubmit)="onSubmit()" #sendForm="ngForm">
<div class="form-group">
<label for="toAddress">Pay To: </label>
<input type="text" class="form-control" id="name" required name="toAddress">
</div>
<div class="form-group">
<label for="amount">Amount</label>
<input type="text" class="form-control" id="amount" required name="amount">
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>
\ No newline at end of file
import { Component } from '@angular/core';
import { ApiService } from '../../shared/api/api.service';
@Component({
selector: 'send-component',
templateUrl: './send.component.html',
styleUrls: ['./send.component.css'],
})
export class SendComponent {
constructor(private apiService: ApiService) {}
}
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { WalletComponent } from './wallet.component';
import { SendComponent } from './send/send.component';
import { ReceiveComponent } from './receive/receive.component';
import { HistoryComponent } from './history/history.component';
const routes: Routes = [
{ path: 'wallet', component: WalletComponent,
children: [
{ path: '', redirectTo:'send', pathMatch:'full' },
{ path: 'send', component: SendComponent},
{ path: 'receive', component: ReceiveComponent},
{ path: 'history', component: HistoryComponent}
]
},
];
@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ]
})
export class WalletRoutingModule {}
#sidepanel {
margin: 0;
padding: 0;
height: 100%;
width: 200px;
position: fixed;
overflow: auto;
}
#content {
margin-left: 200px;
}
\ No newline at end of file
<div id="container">
<div id="sidepanel">
<app-menu></app-menu>
</div>
<div id="content">
<router-outlet></router-outlet>
</div>
</div>
\ No newline at end of file
import { Component } from '@angular/core';
@Component({
selector: 'wallet-component',
templateUrl: './wallet.component.html',
styleUrls: ['./wallet.component.css'],
})
export class WalletComponent {
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import { WalletComponent } from './wallet.component';
import { MenuComponent } from './menu/menu.component';
import { SendComponent } from './send/send.component';
import { ReceiveComponent } from './receive/receive.component';
import { HistoryComponent } from './history/history.component';
import { SharedModule } from '../shared/shared.module';
import { WalletRoutingModule } from './wallet-routing.module';
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
WalletRoutingModule,
SharedModule
],
declarations: [
WalletComponent,
MenuComponent,
ReceiveComponent,
SendComponent,
HistoryComponent,
],
exports: [
WalletComponent
]
})
export class WalletModule { }
export const environment = {
production: true
};
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `angular-cli.json`.
export const environment = {
production: false
};
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>BreezeUI</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
import { AppModule } from './app/app.module';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);
// This file includes polyfills needed by Angular and is loaded before the app.
// You can add your own extra polyfills to this file.
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/set';
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
// If you need to support the browsers/features below, uncomment the import
// and run `npm install import-name-here';
// Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
// Needed for: IE9
// import 'classlist.js';
// Animations
// Needed for: All but Chrome and Firefox, Not supported in IE9
// import 'web-animations-js';
// Date, currency, decimal and percent pipes
// Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
// import 'intl';
// NgClass on SVG elements
// Needed for: IE10, IE11
// import 'classlist.js';
/* You can add global styles to this file, and also import other style files */
.content-wrapper {
width: 100%;
height: 100%;
}
\ No newline at end of file
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare var __karma__: any;
declare var require: any;
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
// Finally, start Karma to run the tests.
__karma__.start();
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [ "es2015", "dom" ],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
"exclude": [
"node_modules",
"dist"
]
}
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"import-blacklist": [true, "rxjs"],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"typeof-compare": true,
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"directive-selector": [true, "attribute", "app", "camelCase"],
"component-selector": [true, "element", "app", "kebab-case"],
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true,
"no-access-missing-member": true,
"templates-use-public": true,
"invoke-injectable": true
}
}
...@@ -6,28 +6,26 @@ using System.Security; ...@@ -6,28 +6,26 @@ using System.Security;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Breeze.Wallet.Models; using Breeze.Wallet.Models;
using Breeze.Wallet.Wrappers; using Breeze.Wallet.Wrappers;
using Stratis.Bitcoin;
namespace Breeze.Wallet.Controllers namespace Breeze.Wallet.Controllers
{ {
[Route("api/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
public class SafeController : Controller public class WalletController : Controller
{ {
private readonly ISafeWrapper safeWrapper; private readonly IWalletWrapper walletWrapper;
public SafeController(ISafeWrapper safeWrapper) public WalletController(IWalletWrapper walletWrapper)
{ {
this.safeWrapper = safeWrapper; this.walletWrapper = walletWrapper;
} }
/// <summary> /// <summary>
/// Creates a new safe on the local machine. /// Creates a new wallet on the local machine.
/// </summary> /// </summary>
/// <param name="safeCreation">The object containing the parameters used to create the wallet.</param> /// <param name="walletCreation">The object containing the parameters used to create the wallet.</param>
/// <returns>A JSON object contaibibg the mnemonic created for the new wallet.</returns> /// <returns>A JSON object containing the mnemonic created for the new wallet.</returns>
[HttpPost] [HttpPost]
public IActionResult Create([FromBody]SafeCreationModel safeCreation) public IActionResult Create([FromBody]WalletCreationModel walletCreation)
{ {
// checks the request is valid // checks the request is valid
if (!this.ModelState.IsValid) if (!this.ModelState.IsValid)
...@@ -38,7 +36,7 @@ namespace Breeze.Wallet.Controllers ...@@ -38,7 +36,7 @@ namespace Breeze.Wallet.Controllers
try try
{ {
var mnemonic = this.safeWrapper.Create(safeCreation.Password, safeCreation.FolderPath, safeCreation.Name, safeCreation.Network); var mnemonic = this.walletWrapper.Create(walletCreation.Password, walletCreation.FolderPath, walletCreation.Name, walletCreation.Network);
return this.Json(mnemonic); return this.Json(mnemonic);
} }
catch (NotSupportedException e) catch (NotSupportedException e)
...@@ -50,7 +48,8 @@ namespace Breeze.Wallet.Controllers ...@@ -50,7 +48,8 @@ namespace Breeze.Wallet.Controllers
} }
} }
public IActionResult Load(SafeLoadModel safeLoad) [HttpGet]
public IActionResult Load([FromQuery]WalletLoadModel walletLoad)
{ {
// checks the request is valid // checks the request is valid
if (!this.ModelState.IsValid) if (!this.ModelState.IsValid)
...@@ -61,8 +60,8 @@ namespace Breeze.Wallet.Controllers ...@@ -61,8 +60,8 @@ namespace Breeze.Wallet.Controllers
try try
{ {
var safe = this.safeWrapper.Load(safeLoad.Password, safeLoad.FolderPath, safeLoad.Name); var wallet = this.walletWrapper.Load(walletLoad.Password, walletLoad.FolderPath, walletLoad.Name);
return this.Json(safe); return this.Json(wallet);
} }
catch (FileNotFoundException e) catch (FileNotFoundException e)
...@@ -88,7 +87,7 @@ namespace Breeze.Wallet.Controllers ...@@ -88,7 +87,7 @@ namespace Breeze.Wallet.Controllers
[Route("recover")] [Route("recover")]
[HttpPost] [HttpPost]
public IActionResult Recover([FromBody]SafeRecoveryModel safeRecovery) public IActionResult Recover([FromBody]WalletRecoveryModel walletRecovery)
{ {
// checks the request is valid // checks the request is valid
if (!this.ModelState.IsValid) if (!this.ModelState.IsValid)
...@@ -99,8 +98,8 @@ namespace Breeze.Wallet.Controllers ...@@ -99,8 +98,8 @@ namespace Breeze.Wallet.Controllers
try try
{ {
var safe = this.safeWrapper.Recover(safeRecovery.Password, safeRecovery.FolderPath, safeRecovery.Name, safeRecovery.Network, safeRecovery.Mnemonic); var wallet = this.walletWrapper.Recover(walletRecovery.Password, walletRecovery.FolderPath, walletRecovery.Name, walletRecovery.Network, walletRecovery.Mnemonic);
return this.Json(safe); return this.Json(wallet);
} }
catch (FileNotFoundException e) catch (FileNotFoundException e)
......
...@@ -7,21 +7,21 @@ namespace Breeze.Wallet.Models ...@@ -7,21 +7,21 @@ namespace Breeze.Wallet.Models
/// <summary> /// <summary>
/// Object used to create a new wallet /// Object used to create a new wallet
/// </summary> /// </summary>
public class SafeCreationModel public class WalletCreationModel
{ {
[Required(ErrorMessage = "A password is required.")] [Required(ErrorMessage = "A password is required.")]
public string Password { get; set; } public string Password { get; set; }
public string Network { get; set; } public string Network { get; set; }
[Required(ErrorMessage = "The folder path where the safe will be created is required.")] [Required(ErrorMessage = "The folder path where the wallet will be created is required.")]
public string FolderPath { get; set; } public string FolderPath { get; set; }
[Required(ErrorMessage = "The name of the safe to create is missing.")] [Required(ErrorMessage = "The name of the wallet to create is missing.")]
public string Name { get; set; } public string Name { get; set; }
} }
public class SafeLoadModel public class WalletLoadModel
{ {
[Required(ErrorMessage = "A password is required.")] [Required(ErrorMessage = "A password is required.")]
public string Password { get; set; } public string Password { get; set; }
...@@ -29,11 +29,11 @@ namespace Breeze.Wallet.Models ...@@ -29,11 +29,11 @@ namespace Breeze.Wallet.Models
[Required(ErrorMessage = "The folder path is required.")] [Required(ErrorMessage = "The folder path is required.")]
public string FolderPath { get; set; } public string FolderPath { get; set; }
[Required(ErrorMessage = "The name of the safe is missing.")] [Required(ErrorMessage = "The name of the wallet is missing.")]
public string Name { get; set; } public string Name { get; set; }
} }
public class SafeRecoveryModel public class WalletRecoveryModel
{ {
[Required(ErrorMessage = "A mnemonic is required.")] [Required(ErrorMessage = "A mnemonic is required.")]
public string Mnemonic { get; set; } public string Mnemonic { get; set; }
...@@ -44,7 +44,7 @@ namespace Breeze.Wallet.Models ...@@ -44,7 +44,7 @@ namespace Breeze.Wallet.Models
[Required(ErrorMessage = "The folder path is required.")] [Required(ErrorMessage = "The folder path is required.")]
public string FolderPath { get; set; } public string FolderPath { get; set; }
[Required(ErrorMessage = "The name of the safe is missing.")] [Required(ErrorMessage = "The name of the wallet is missing.")]
public string Name { get; set; } public string Name { get; set; }
public string Network { get; set; } public string Network { get; set; }
......
...@@ -23,9 +23,9 @@ namespace Breeze.Wallet ...@@ -23,9 +23,9 @@ namespace Breeze.Wallet
.AddFeature<WalletFeature>() .AddFeature<WalletFeature>()
.FeatureServices(services => .FeatureServices(services =>
{ {
services.AddTransient<ISafeWrapper, SafeWrapper>(); services.AddTransient<IWalletWrapper, WalletWrapper>();
services.AddTransient<ITrackerWrapper, TrackerWrapper>(); services.AddTransient<ITrackerWrapper, TrackerWrapper>();
services.AddSingleton<SafeController>(); services.AddSingleton<WalletController>();
}); });
}); });
......
...@@ -5,7 +5,7 @@ using System.Threading.Tasks; ...@@ -5,7 +5,7 @@ using System.Threading.Tasks;
namespace Breeze.Wallet namespace Breeze.Wallet
{ {
public class SafeModel public class WalletModel
{ {
public string Network { get; set; } public string Network { get; set; }
......
...@@ -4,12 +4,12 @@ namespace Breeze.Wallet.Wrappers ...@@ -4,12 +4,12 @@ namespace Breeze.Wallet.Wrappers
/// <summary> /// <summary>
/// An interface enabling wallet operations. /// An interface enabling wallet operations.
/// </summary> /// </summary>
public interface ISafeWrapper public interface IWalletWrapper
{ {
string Create(string password, string folderPath, string name, string network); string Create(string password, string folderPath, string name, string network);
SafeModel Load(string password, string folderPath, string name); WalletModel Load(string password, string folderPath, string name);
SafeModel Recover(string password, string folderPath, string name, string network, string mnemonic); WalletModel Recover(string password, string folderPath, string name, string network, string mnemonic);
} }
} }
...@@ -6,64 +6,64 @@ using NBitcoin; ...@@ -6,64 +6,64 @@ using NBitcoin;
namespace Breeze.Wallet.Wrappers namespace Breeze.Wallet.Wrappers
{ {
/// <summary> /// <summary>
/// An implementation of the <see cref="ISafeWrapper"/> interface. /// An implementation of the <see cref="IWalletWrapper"/> interface.
/// </summary> /// </summary>
public class SafeWrapper : ISafeWrapper public class WalletWrapper : IWalletWrapper
{ {
/// <summary> /// <summary>
/// Creates a safe on the local device. /// Creates a wallet on the local device.
/// </summary> /// </summary>
/// <param name="password">The user's password.</param> /// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the safe will be saved.</param> /// <param name="folderPath">The folder where the wallet will be saved.</param>
/// <param name="name">The name of the safe.</param> /// <param name="name">The name of the wallet.</param>
/// <param name="network">The network for which to create a safe.</param> /// <param name="network">The network for which to create a wallet.</param>
/// <returns>A mnemonic allowing recovery of the safe.</returns> /// <returns>A mnemonic allowing recovery of the wallet.</returns>
public string Create(string password, string folderPath, string name, string network) public string Create(string password, string folderPath, string name, string network)
{ {
Mnemonic mnemonic; Mnemonic mnemonic;
Safe safe = Safe.Create(out mnemonic, password, Path.Combine(folderPath, $"{name}.json"), this.GetNetwork(network)); Safe wallet = Safe.Create(out mnemonic, password, Path.Combine(folderPath, $"{name}.json"), this.GetNetwork(network));
return mnemonic.ToString(); return mnemonic.ToString();
} }
/// <summary> /// <summary>
/// Loads a safe from the local device. /// Loads a wallet from the local device.
/// </summary> /// </summary>
/// <param name="password">The user's password.</param> /// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the safe will be loaded.</param> /// <param name="folderPath">The folder where the wallet will be loaded.</param>
/// <param name="name">The name of the safe.</param> /// <param name="name">The name of the wallet.</param>
/// <returns>The safe loaded from the local device</returns> /// <returns>The wallet loaded from the local device</returns>
public SafeModel Load(string password, string folderPath, string name) public WalletModel Load(string password, string folderPath, string name)
{ {
Safe safe = Safe.Load(password, Path.Combine(folderPath, $"{name}.json")); Safe wallet = Safe.Load(password, Path.Combine(folderPath, $"{name}.json"));
//TODO review here which data should be returned //TODO review here which data should be returned
return new SafeModel return new WalletModel
{ {
Network = safe.Network.Name, Network = wallet.Network.Name,
Addresses = safe.GetFirstNAddresses(10).Select(a => a.ToWif()), Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = safe.WalletFilePath FileName = wallet.WalletFilePath
}; };
} }
/// <summary> /// <summary>
/// Recovers a safe from the local device. /// Recovers a wallet from the local device.
/// </summary> /// </summary>
/// <param name="password">The user's password.</param> /// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the safe will be loaded.</param> /// <param name="folderPath">The folder where the wallet will be loaded.</param>
/// <param name="name">The name of the safe.</param> /// <param name="name">The name of the wallet.</param>
/// <param name="network">The network in which to creae this wallet</param> /// <param name="network">The network in which to creae this wallet</param>
/// <param name="mnemonic">The user's mnemonic for the safe.</param> /// <param name="mnemonic">The user's mnemonic for the wallet.</param>
/// <returns></returns> /// <returns></returns>
public SafeModel Recover(string password, string folderPath, string name, string network, string mnemonic) public WalletModel Recover(string password, string folderPath, string name, string network, string mnemonic)
{ {
Safe safe = Safe.Recover(new Mnemonic(mnemonic), password, Path.Combine(folderPath, $"{name}.json"), this.GetNetwork(network)); Safe wallet = Safe.Recover(new Mnemonic(mnemonic), password, Path.Combine(folderPath, $"{name}.json"), this.GetNetwork(network));
//TODO review here which data should be returned //TODO review here which data should be returned
return new SafeModel return new WalletModel
{ {
Network = safe.Network.Name, Network = wallet.Network.Name,
Addresses = safe.GetFirstNAddresses(10).Select(a => a.ToWif()), Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = safe.WalletFilePath FileName = wallet.WalletFilePath
}; };
} }
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
"dependencies": { "dependencies": {
"HBitcoin": "0.1.5", "HBitcoin": "0.1.5",
"Microsoft.AspNetCore.Mvc.Versioning": "1.0.3",
"NBitcoin": "3.0.2.10", "NBitcoin": "3.0.2.10",
"NETStandard.Library": "1.6.1", "NETStandard.Library": "1.6.1",
"Stratis.Bitcoin": "1.0.1.2-alpha" "Stratis.Bitcoin": "1.0.1.2-alpha"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment