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
## POST /wallet/load - Loads the wallet and starts syncing
### 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
### Parameters
```
......@@ -158,11 +166,16 @@ POST /wallet/send-transaction - Attempts to send a transaction
"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
"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
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
Works as expected.
......
......@@ -16,12 +16,12 @@
<ItemGroup>
<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="System.Runtime.Serialization.Primitives" Version="4.3.0" />
<PackageReference Include="xunit" Version="2.2.0-beta5-build3474" />
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
<PackageReference Include="Moq" Version="4.7.8" />
<PackageReference Include="Moq" Version="4.7.10" />
</ItemGroup>
</Project>
......@@ -66,12 +66,8 @@ namespace Breeze.Api.Tests
// Assert
mockWalletWrapper.VerifyAll();
var viewResult = Assert.IsType<JsonResult>(result);
Assert.NotNull(viewResult.Value);
Assert.IsType<WalletModel>(viewResult.Value);
var model = viewResult.Value as WalletModel;
Assert.Equal("Main", model.Network);
var viewResult = Assert.IsType<OkResult>(result);
Assert.Equal(200, viewResult.StatusCode);
}
[Fact]
......@@ -97,12 +93,8 @@ namespace Breeze.Api.Tests
// Assert
mockWalletWrapper.VerifyAll();
var viewResult = Assert.IsType<JsonResult>(result);
Assert.NotNull(viewResult.Value);
Assert.IsType<WalletModel>(viewResult.Value);
var model = viewResult.Value as WalletModel;
Assert.Equal("Main", model.Network);
var viewResult = Assert.IsType<OkResult>(result);
Assert.Equal(200, viewResult.StatusCode);
}
[Fact]
......
......@@ -2,7 +2,7 @@
"variables": [],
"info": {
"name": "Wallet",
"_postman_id": "57013f2c-02dc-df32-41e9-6e4aaa14ad5e",
"_postman_id": "b5720ab4-24a5-6957-0ea6-766a9cbaf488",
"description": "Requests relating to operations on the wallet",
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
},
......@@ -21,7 +21,7 @@
],
"body": {
"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": ""
},
......@@ -161,7 +161,7 @@
],
"body": {
"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": ""
},
......@@ -206,7 +206,7 @@
"description": "Gets all the wallets files stored in the default folder"
},
"response": []
},
},
{
"name": "Get unused account in wallet",
"request": {
......
......@@ -78,14 +78,9 @@ namespace Breeze.Wallet.Controllers
{
// get the wallet folder
DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath);
Wallet wallet = this.walletManager.LoadWallet(request.Password, walletFolder.FullName, request.Name);
return this.Json(new WalletModel
{
Network = wallet.Network.Name,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = wallet.WalletFilePath
});
return this.Ok();
}
catch (FileNotFoundException e)
{
......@@ -122,18 +117,12 @@ namespace Breeze.Wallet.Controllers
{
// get the wallet folder
DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath);
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
this.tracker.SyncFrom(request.CreationDate);
return this.Json(new WalletModel
{
Network = wallet.Network.Name,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = wallet.WalletFilePath
});
return this.Ok();
}
catch (InvalidOperationException e)
{
......@@ -287,12 +276,11 @@ namespace Breeze.Wallet.Controllers
try
{
var transaction = 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 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 = transaction.ToHex(),
Fee = fee
Hex = transactionResult.hex,
Fee = transactionResult.fee
};
return this.Json(model);
}
......
......@@ -135,25 +135,28 @@ namespace Breeze.Wallet
/// <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>
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);
/// <summary>
/// Processes a block received from the network.
/// </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="block">The block.</param>
void ProcessBlock(CoinType coinType, int height, Block block);
void ProcessBlock(int height, Block block);
/// <summary>
/// Processes a transaction received from the network.
/// </summary>
/// <param name="coinType">The type of coin this transaction relates to.</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="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
public class BlockObserver : SignalObserver<Block>
{
private readonly ConcurrentChain chain;
private readonly CoinType coinType;
private readonly IWalletManager walletManager;
public BlockObserver(ConcurrentChain chain, CoinType coinType, IWalletManager walletManager)
public BlockObserver(ConcurrentChain chain, IWalletManager walletManager)
{
this.chain = chain;
this.coinType = coinType;
this.walletManager = walletManager;
}
......@@ -28,7 +26,7 @@ namespace Breeze.Wallet.Notifications
var hash = block.Header.GetHash();
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
/// Observer that receives notifications about the arrival of new <see cref="Transaction"/>s.
/// </summary>
public class TransactionObserver : SignalObserver<Transaction>
{
private readonly CoinType coinType;
{
private readonly IWalletManager walletManager;
public TransactionObserver(CoinType coinType, IWalletManager walletManager)
public TransactionObserver(IWalletManager walletManager)
{
this.coinType = coinType;
this.walletManager = walletManager;
}
......@@ -24,7 +21,7 @@ namespace Breeze.Wallet.Notifications
/// <param name="transaction">The new transaction</param>
protected override void OnNextCore(Transaction transaction)
{
this.walletManager.ProcessTransaction(this.coinType, transaction);
this.walletManager.ProcessTransaction(transaction);
}
}
}
......@@ -38,9 +38,9 @@ namespace Breeze.Wallet
await this.WaitForChainDownloadAsync();
// 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();
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();
// start syncing blocks
......
......@@ -84,6 +84,20 @@ namespace Breeze.Wallet
}
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>
......@@ -107,7 +121,7 @@ namespace Breeze.Wallet
/// The accounts used in the wallet.
/// </summary>
[JsonProperty(PropertyName = "accounts")]
public IEnumerable<HdAccount> Accounts { get; set; }
public ICollection<HdAccount> Accounts { get; set; }
/// <summary>
/// Gets the first account that contains no transaction.
......@@ -142,6 +156,9 @@ namespace Breeze.Wallet
}
return account;
}
}
/// <summary>
......@@ -210,13 +227,13 @@ namespace Breeze.Wallet
/// The list of external addresses, typically used for receiving money.
/// </summary>
[JsonProperty(PropertyName = "externalAddresses")]
public IEnumerable<HdAddress> ExternalAddresses { get; set; }
public ICollection<HdAddress> ExternalAddresses { get; set; }
/// <summary>
/// The list of internal addresses, typically used to receive change.
/// </summary>
[JsonProperty(PropertyName = "internalAddresses")]
public IEnumerable<HdAddress> InternalAddresses { get; set; }
public ICollection<HdAddress> InternalAddresses { get; set; }
/// <summary>
/// Gets the type of coin this account is for.
......@@ -357,7 +374,7 @@ namespace Breeze.Wallet
/// A list of transactions involving this address.
/// </summary>
[JsonProperty(PropertyName = "transactions")]
public IEnumerable<TransactionData> Transactions { get; set; }
public ICollection<TransactionData> Transactions { get; set; }
}
/// <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