Commit 36cf64fb authored by Dan Gershony's avatar Dan Gershony Committed by GitHub

Merge pull request #52 from bokobza/feature/build-transaction

Sending funds
parents 86d060a2 3c3778b8
...@@ -147,10 +147,18 @@ POST /wallet/send-transaction - Attempts to send a transaction ...@@ -147,10 +147,18 @@ POST /wallet/send-transaction - Attempts to send a transaction
## POST /wallet/load - Loads the wallet and starts syncing ## POST /wallet/load - Loads the wallet and starts syncing
### Parameters ### Parameters
``` ```
{ {
"password": "password" "password": "123456",
"folderPath": "Wallets", // optional, if the folder path is not the default one
"name": "myWallet"
} }
``` ```
### Response
```
200 (OK)
```
## POST /wallet/recover - Recovers the wallet ## POST /wallet/recover - Recovers the wallet
### Parameters ### Parameters
``` ```
...@@ -158,11 +166,16 @@ POST /wallet/send-transaction - Attempts to send a transaction ...@@ -158,11 +166,16 @@ POST /wallet/send-transaction - Attempts to send a transaction
"network": "main", // "main" or "testnet" "network": "main", // "main" or "testnet"
"password": "password", "password": "password",
"mnemonic": "foo bar buz", "mnemonic": "foo bar buz",
"creationTime": "2017-02-03" // DateTimeOffset.ParseExact("1998-01-01", "yyyy-MM-dd", CultureInfo.InvariantCulture), utc time "name": "testwallet-recovered",
"folderPath": "Wallets", // optional, if the folder path is not the default one
"creationTime": "2017-02-25 16:20:33" // date from which to start looking for transactions
} }
``` ```
### Response ### Response
Cannot check if the password is good or not. If the password is wrong it'll recover a wallet with the wrong password. Cannot check if the password is good or not. If the password is wrong it'll recover a wallet with the wrong password.
```
200 (OK)
```
## DELETE /wallet - Deletes the wallet ## DELETE /wallet - Deletes the wallet
Works as expected. Works as expected.
......
...@@ -16,12 +16,12 @@ ...@@ -16,12 +16,12 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
<PackageReference Include="NStratis" Version="3.0.2.17" /> <PackageReference Include="NStratis" Version="3.0.2.23" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta5-build1225" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta5-build1225" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" /> <PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
<PackageReference Include="xunit" Version="2.2.0-beta5-build3474" /> <PackageReference Include="xunit" Version="2.2.0-beta5-build3474" />
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" /> <PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
<PackageReference Include="Moq" Version="4.7.8" /> <PackageReference Include="Moq" Version="4.7.10" />
</ItemGroup> </ItemGroup>
</Project> </Project>
...@@ -66,12 +66,8 @@ namespace Breeze.Api.Tests ...@@ -66,12 +66,8 @@ namespace Breeze.Api.Tests
// Assert // Assert
mockWalletWrapper.VerifyAll(); mockWalletWrapper.VerifyAll();
var viewResult = Assert.IsType<JsonResult>(result); var viewResult = Assert.IsType<OkResult>(result);
Assert.NotNull(viewResult.Value); Assert.Equal(200, viewResult.StatusCode);
Assert.IsType<WalletModel>(viewResult.Value);
var model = viewResult.Value as WalletModel;
Assert.Equal("Main", model.Network);
} }
[Fact] [Fact]
...@@ -97,12 +93,8 @@ namespace Breeze.Api.Tests ...@@ -97,12 +93,8 @@ namespace Breeze.Api.Tests
// Assert // Assert
mockWalletWrapper.VerifyAll(); mockWalletWrapper.VerifyAll();
var viewResult = Assert.IsType<JsonResult>(result); var viewResult = Assert.IsType<OkResult>(result);
Assert.NotNull(viewResult.Value); Assert.Equal(200, viewResult.StatusCode);
Assert.IsType<WalletModel>(viewResult.Value);
var model = viewResult.Value as WalletModel;
Assert.Equal("Main", model.Network);
} }
[Fact] [Fact]
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"variables": [], "variables": [],
"info": { "info": {
"name": "Wallet", "name": "Wallet",
"_postman_id": "57013f2c-02dc-df32-41e9-6e4aaa14ad5e", "_postman_id": "b5720ab4-24a5-6957-0ea6-766a9cbaf488",
"description": "Requests relating to operations on the wallet", "description": "Requests relating to operations on the wallet",
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
}, },
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{ \n\t\"password\": \"123456\",\n\t\"network\": \"Main\",\n\t\"folderPath\": \"Wallets\",\n\t\"name\": \"myFirstWallet\"\n}" "raw": "{ \n\t\"password\": \"123456\",\n\t\"network\": \"testnet\",\n\t\"name\": \"testwallet\"\n}"
}, },
"description": "" "description": ""
}, },
...@@ -161,7 +161,7 @@ ...@@ -161,7 +161,7 @@
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\r\n \"password\": \"password\",\r\n \"address\": \"1FYp9uguYCz7DgSF9jTWDeZF8kdRKQTXPg\",\r\n \"amount\": \"0.12\",\r\n \"feeType\": \"low\",\r\n \"allowUnconfirmed\": \"true\"\r\n}" "raw": "{\r\n\t\"walletName\": \"testwallet\",\r\n\t\"accountName\": \"account 0\",\r\n\t\"coinType\": 1,\r\n \"password\": \"password\",\r\n \"destinationAddress\": \"1FYp9uguYCz7DgSF9jTWDeZF8kdRKQTXPg\",\r\n \"amount\": \"0.12\",\r\n \"feeType\": \"low\",\r\n \"allowUnconfirmed\": \"true\"\r\n}"
}, },
"description": "" "description": ""
}, },
...@@ -206,7 +206,7 @@ ...@@ -206,7 +206,7 @@
"description": "Gets all the wallets files stored in the default folder" "description": "Gets all the wallets files stored in the default folder"
}, },
"response": [] "response": []
}, },
{ {
"name": "Get unused account in wallet", "name": "Get unused account in wallet",
"request": { "request": {
......
...@@ -78,14 +78,9 @@ namespace Breeze.Wallet.Controllers ...@@ -78,14 +78,9 @@ namespace Breeze.Wallet.Controllers
{ {
// get the wallet folder // get the wallet folder
DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath); DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath);
Wallet wallet = this.walletManager.LoadWallet(request.Password, walletFolder.FullName, request.Name); Wallet wallet = this.walletManager.LoadWallet(request.Password, walletFolder.FullName, request.Name);
return this.Json(new WalletModel
{ return this.Ok();
Network = wallet.Network.Name,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = wallet.WalletFilePath
});
} }
catch (FileNotFoundException e) catch (FileNotFoundException e)
{ {
...@@ -122,18 +117,12 @@ namespace Breeze.Wallet.Controllers ...@@ -122,18 +117,12 @@ namespace Breeze.Wallet.Controllers
{ {
// get the wallet folder // get the wallet folder
DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath); DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath);
Wallet wallet = this.walletManager.RecoverWallet(request.Password, walletFolder.FullName, request.Name, request.Network, request.Mnemonic, null, request.CreationDate); Wallet wallet = this.walletManager.RecoverWallet(request.Password, walletFolder.FullName, request.Name, request.Network, request.Mnemonic, null, request.CreationDate);
// start syncing the wallet from the creation date // start syncing the wallet from the creation date
this.tracker.SyncFrom(request.CreationDate); this.tracker.SyncFrom(request.CreationDate);
return this.Json(new WalletModel return this.Ok();
{
Network = wallet.Network.Name,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = wallet.WalletFilePath
});
} }
catch (InvalidOperationException e) catch (InvalidOperationException e)
{ {
...@@ -287,12 +276,11 @@ namespace Breeze.Wallet.Controllers ...@@ -287,12 +276,11 @@ namespace Breeze.Wallet.Controllers
try try
{ {
var transaction = this.walletManager.BuildTransaction(request.WalletName, request.AccountName, request.CoinType, request.Password, request.DestinationAddress, request.Amount, request.FeeType, request.AllowUnconfirmed); var transactionResult = this.walletManager.BuildTransaction(request.WalletName, request.AccountName, request.CoinType, request.Password, request.DestinationAddress, request.Amount, request.FeeType, request.AllowUnconfirmed);
var fee = transaction.TotalOut - request.Amount;
var model = new WalletBuildTransactionModel var model = new WalletBuildTransactionModel
{ {
Hex = transaction.ToHex(), Hex = transactionResult.hex,
Fee = fee Fee = transactionResult.fee
}; };
return this.Json(model); return this.Json(model);
} }
......
...@@ -135,25 +135,28 @@ namespace Breeze.Wallet ...@@ -135,25 +135,28 @@ namespace Breeze.Wallet
/// <param name="feeType">The type of fee to be included.</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> /// <param name="allowUnconfirmed">Whether or not we allow this transaction to rely on unconfirmed outputs.</param>
/// <returns></returns> /// <returns></returns>
NBitcoin.Transaction BuildTransaction(string walletName, string accountName, CoinType coinType, string password, string destinationAddress, Money amount, string feeType, bool allowUnconfirmed); (string hex, 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); bool SendTransaction(string transactionHex);
/// <summary> /// <summary>
/// Processes a block received from the network. /// Processes a block received from the network.
/// </summary> /// </summary>
/// <param name="coinType">The type of coin this block relates to.</param>
/// <param name="height">The height of the block in the blockchain.</param> /// <param name="height">The height of the block in the blockchain.</param>
/// <param name="block">The block.</param> /// <param name="block">The block.</param>
void ProcessBlock(CoinType coinType, int height, Block block); void ProcessBlock(int height, Block block);
/// <summary> /// <summary>
/// Processes a transaction received from the network. /// Processes a transaction received from the network.
/// </summary> /// </summary>
/// <param name="coinType">The type of coin this transaction relates to.</param>
/// <param name="transaction">The transaction.</param> /// <param name="transaction">The transaction.</param>
/// <param name="blockHeight">The height of the block this transaction came from. Null if it was not a transaction included in a block.</param> /// <param name="blockHeight">The height of the block this transaction came from. Null if it was not a transaction included in a block.</param>
/// <param name="blockTime">The block time.</param> /// <param name="blockTime">The block time.</param>
void ProcessTransaction(CoinType coinType, NBitcoin.Transaction transaction, int? blockHeight = null, uint? blockTime = null); void ProcessTransaction(Transaction transaction, int? blockHeight = null, uint? blockTime = null);
} }
} }
...@@ -9,13 +9,11 @@ namespace Breeze.Wallet.Notifications ...@@ -9,13 +9,11 @@ namespace Breeze.Wallet.Notifications
public class BlockObserver : SignalObserver<Block> public class BlockObserver : SignalObserver<Block>
{ {
private readonly ConcurrentChain chain; private readonly ConcurrentChain chain;
private readonly CoinType coinType;
private readonly IWalletManager walletManager; private readonly IWalletManager walletManager;
public BlockObserver(ConcurrentChain chain, CoinType coinType, IWalletManager walletManager) public BlockObserver(ConcurrentChain chain, IWalletManager walletManager)
{ {
this.chain = chain; this.chain = chain;
this.coinType = coinType;
this.walletManager = walletManager; this.walletManager = walletManager;
} }
...@@ -28,7 +26,7 @@ namespace Breeze.Wallet.Notifications ...@@ -28,7 +26,7 @@ namespace Breeze.Wallet.Notifications
var hash = block.Header.GetHash(); var hash = block.Header.GetHash();
var height = this.chain.GetBlock(hash).Height; var height = this.chain.GetBlock(hash).Height;
this.walletManager.ProcessBlock(this.coinType, height, block); this.walletManager.ProcessBlock(height, block);
} }
} }
} }
...@@ -7,14 +7,11 @@ namespace Breeze.Wallet.Notifications ...@@ -7,14 +7,11 @@ namespace Breeze.Wallet.Notifications
/// Observer that receives notifications about the arrival of new <see cref="Transaction"/>s. /// Observer that receives notifications about the arrival of new <see cref="Transaction"/>s.
/// </summary> /// </summary>
public class TransactionObserver : SignalObserver<Transaction> public class TransactionObserver : SignalObserver<Transaction>
{ {
private readonly CoinType coinType;
private readonly IWalletManager walletManager; private readonly IWalletManager walletManager;
public TransactionObserver(CoinType coinType, IWalletManager walletManager) public TransactionObserver(IWalletManager walletManager)
{ {
this.coinType = coinType;
this.walletManager = walletManager; this.walletManager = walletManager;
} }
...@@ -24,7 +21,7 @@ namespace Breeze.Wallet.Notifications ...@@ -24,7 +21,7 @@ namespace Breeze.Wallet.Notifications
/// <param name="transaction">The new transaction</param> /// <param name="transaction">The new transaction</param>
protected override void OnNextCore(Transaction transaction) protected override void OnNextCore(Transaction transaction)
{ {
this.walletManager.ProcessTransaction(this.coinType, transaction); this.walletManager.ProcessTransaction(transaction);
} }
} }
} }
...@@ -38,9 +38,9 @@ namespace Breeze.Wallet ...@@ -38,9 +38,9 @@ namespace Breeze.Wallet
await this.WaitForChainDownloadAsync(); await this.WaitForChainDownloadAsync();
// subscribe to receiving blocks and transactions // subscribe to receiving blocks and transactions
BlockSubscriber sub = new BlockSubscriber(this.signals.Blocks, new BlockObserver(this.chain, this.coinType, this.walletManager)); BlockSubscriber sub = new BlockSubscriber(this.signals.Blocks, new BlockObserver(this.chain, this.walletManager));
sub.Subscribe(); sub.Subscribe();
TransactionSubscriber txSub = new TransactionSubscriber(this.signals.Transactions, new TransactionObserver(this.coinType, this.walletManager)); TransactionSubscriber txSub = new TransactionSubscriber(this.signals.Transactions, new TransactionObserver(this.walletManager));
txSub.Subscribe(); txSub.Subscribe();
// start syncing blocks // start syncing blocks
......
...@@ -84,6 +84,20 @@ namespace Breeze.Wallet ...@@ -84,6 +84,20 @@ namespace Breeze.Wallet
} }
return result; 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> /// <summary>
...@@ -107,7 +121,7 @@ namespace Breeze.Wallet ...@@ -107,7 +121,7 @@ namespace Breeze.Wallet
/// The accounts used in the wallet. /// The accounts used in the wallet.
/// </summary> /// </summary>
[JsonProperty(PropertyName = "accounts")] [JsonProperty(PropertyName = "accounts")]
public IEnumerable<HdAccount> Accounts { get; set; } public ICollection<HdAccount> Accounts { get; set; }
/// <summary> /// <summary>
/// Gets the first account that contains no transaction. /// Gets the first account that contains no transaction.
...@@ -142,6 +156,9 @@ namespace Breeze.Wallet ...@@ -142,6 +156,9 @@ namespace Breeze.Wallet
} }
return account; return account;
} }
} }
/// <summary> /// <summary>
...@@ -210,13 +227,13 @@ namespace Breeze.Wallet ...@@ -210,13 +227,13 @@ namespace Breeze.Wallet
/// The list of external addresses, typically used for receiving money. /// The list of external addresses, typically used for receiving money.
/// </summary> /// </summary>
[JsonProperty(PropertyName = "externalAddresses")] [JsonProperty(PropertyName = "externalAddresses")]
public IEnumerable<HdAddress> ExternalAddresses { get; set; } public ICollection<HdAddress> ExternalAddresses { get; set; }
/// <summary> /// <summary>
/// The list of internal addresses, typically used to receive change. /// The list of internal addresses, typically used to receive change.
/// </summary> /// </summary>
[JsonProperty(PropertyName = "internalAddresses")] [JsonProperty(PropertyName = "internalAddresses")]
public IEnumerable<HdAddress> InternalAddresses { get; set; } public ICollection<HdAddress> InternalAddresses { get; set; }
/// <summary> /// <summary>
/// Gets the type of coin this account is for. /// Gets the type of coin this account is for.
...@@ -357,7 +374,7 @@ namespace Breeze.Wallet ...@@ -357,7 +374,7 @@ namespace Breeze.Wallet
/// A list of transactions involving this address. /// A list of transactions involving this address.
/// </summary> /// </summary>
[JsonProperty(PropertyName = "transactions")] [JsonProperty(PropertyName = "transactions")]
public IEnumerable<TransactionData> Transactions { get; set; } public ICollection<TransactionData> Transactions { get; set; }
} }
/// <summary> /// <summary>
......
This diff is collapsed.
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