Add coin control feature
MODIFY
HiddenWallet.Daemon/Controllers/WalletController.cs
HiddenWallet.Daemon/Controllers/WalletController.cs
@@ -232,6 +232,12 @@ namespace HiddenWallet.Daemon.Controllers
var fail = GetAccount(account, out SafeAccount safeAccount);
if (fail != null) return new ObjectResult(fail);
+ Script customChangeScriptPubKey = null; // if it stays null then scriptubkey is not custom
+ if(request.DonateChange)
+ {
+ customChangeScriptPubKey = BitcoinAddress.Create("186n7me3QKajQZJnUsVsezVhVrSwyFCCZ", Network.Main).ScriptPubKey;
+ }
+
BitcoinAddress address;
try
{
@@ -276,7 +282,80 @@ namespace HiddenWallet.Daemon.Controllers
return new ObjectResult(new FailureResponse { Message = "Wrong fee type", Details = "" });
}
- return new ObjectResult(await Global.WalletWrapper.BuildTransactionAsync(request.Password, safeAccount, address, amount, feeType));
+ var ret = await Global.WalletWrapper.BuildTransactionAsync(request.Password, safeAccount, address, amount, feeType, false, customChangeScriptPubKey, null);
+ if(request.DonateChange && ret is BuildTransactionResponse)
+ {
+ var retB = ret as BuildTransactionResponse;
+ retB.ChangeOutputAmount = "0";
+ return new ObjectResult(retB);
+ }
+
+ return new ObjectResult(ret);
+ }
+ catch (Exception ex)
+ {
+ return new ObjectResult(new FailureResponse { Message = ex.Message, Details = ex.ToString() });
+ }
+ }
+
+ [Route("fund-transaction/{account}")]
+ [HttpPost]
+ public async Task<IActionResult> FundTransactionAsync(string account, [FromBody]FundTransactionRequest request)
+ {
+ try
+ {
+ if (request == null || request.Password == null || request.Address == null || request.Inputs == null || request.Inputs.Count() == 0 || request.FeeType == null)
+ {
+ return new ObjectResult(new FailureResponse { Message = "Bad request", Details = "" });
+ }
+
+ var fail = GetAccount(account, out SafeAccount safeAccount);
+ if (fail != null) return new ObjectResult(fail);
+
+ BitcoinAddress address;
+ try
+ {
+ address = BitcoinAddress.Create(request.Address, Global.WalletWrapper.Network);
+ }
+ catch (Exception)
+ {
+ return new ObjectResult(new FailureResponse { Message = "Wrong address", Details = "" });
+ }
+
+ var inputs = new List<OutPoint>();
+ try
+ {
+ foreach(var input in request.Inputs)
+ {
+ var inputParts = input.Split(":");
+ inputs.Add(new OutPoint(new uint256(inputParts[1]), int.Parse(inputParts[0])));
+ }
+ }
+ catch (Exception)
+ {
+ return new ObjectResult(new FailureResponse { Message = "Wrong amount", Details = "" });
+ }
+
+
+ FeeType feeType;
+ if (request.FeeType.Equals("high", StringComparison.OrdinalIgnoreCase))
+ {
+ feeType = FeeType.High;
+ }
+ else if (request.FeeType.Equals("medium", StringComparison.OrdinalIgnoreCase))
+ {
+ feeType = FeeType.Medium;
+ }
+ else if (request.FeeType.Equals("low", StringComparison.OrdinalIgnoreCase))
+ {
+ feeType = FeeType.Low;
+ }
+ else
+ {
+ return new ObjectResult(new FailureResponse { Message = "Wrong fee type", Details = "" });
+ }
+
+ return new ObjectResult(await Global.WalletWrapper.BuildTransactionAsync(request.Password, safeAccount, address, Money.Zero, feeType, true, null, inputs));
}
catch (Exception ex)
{

MODIFY
HiddenWallet.Daemon/Models/BuildTransactionRequest.cs
HiddenWallet.Daemon/Models/BuildTransactionRequest.cs
@@ -11,5 +11,6 @@ namespace HiddenWallet.Daemon.Models
public string Address { get; set; }
public string Amount { get; set; }
public string FeeType { get; set; }
+ public bool DonateChange { get; set; }
}
}

MODIFY
HiddenWallet.Daemon/Wrappers/WalletWrapper.cs
HiddenWallet.Daemon/Wrappers/WalletWrapper.cs
@@ -265,10 +265,10 @@ namespace HiddenWallet.Daemon.Wrappers
else return new StatusResponse { HeaderHeight = 0, TrackingHeight = 0, ConnectedNodeCount = 0, MemPoolTransactionCount = 0, WalletState = WalletState.NotStarted.ToString(), TorState = ts, ChangeBump = 0 };
}
- public async Task<BaseResponse> BuildTransactionAsync(string password, SafeAccount safeAccount, BitcoinAddress address, Money amount, FeeType feeType)
+ public async Task<BaseResponse> BuildTransactionAsync(string password, SafeAccount safeAccount, BitcoinAddress address, Money amount, FeeType feeType, bool subtractFeeFromAmount, Script customChangeScriptPubKey, IEnumerable<OutPoint> allowedInputs)
{
if (password != _password) throw new InvalidOperationException("Wrong password");
- var result = await _walletJob.BuildTransactionAsync(address.ScriptPubKey, amount, feeType, safeAccount, (bool)Global.Config.CanSpendUnconfirmed);
+ var result = await _walletJob.BuildTransactionAsync(address.ScriptPubKey, amount, feeType, safeAccount, (bool)Global.Config.CanSpendUnconfirmed, subtractFeeFromAmount, customChangeScriptPubKey, allowedInputs);
if (result.Success)
{

MODIFY
HiddenWallet.FullSpvWallet/WalletJob.cs
HiddenWallet.FullSpvWallet/WalletJob.cs
@@ -900,7 +900,7 @@ namespace HiddenWallet.FullSpv
AssertAccount(account);
// 1. Get the script pubkey of the change.
- Script changeScriptPubKey = await GetChangeScriptPubKey(account, customChangeScriptPubKey);
+ Script changeScriptPubKey = await GetChangeScriptPubKeyAsync(account, customChangeScriptPubKey);
// 2. Find all coins I can spend from the account
// 3. How much money we can spend?
@@ -1110,7 +1110,7 @@ namespace HiddenWallet.FullSpv
}
}
- private async Task<Script> GetChangeScriptPubKey(SafeAccount account, Script customChangeScriptPubKey)
+ private async Task<Script> GetChangeScriptPubKeyAsync(SafeAccount account, Script customChangeScriptPubKey)
{
Debug.WriteLine("Select change address...");
Script changeScriptPubKey;

MODIFY
HiddenWallet.Gui/app/css/default.css
HiddenWallet.Gui/app/css/default.css
@@ -16,8 +16,6 @@ body {
bottom: 0px;
}
-
-
#title-bar {
background-color: #2D2D30;
-webkit-app-region: drag;

MODIFY
HiddenWallet.Gui/app/html/broadcast-transaction-window.html
HiddenWallet.Gui/app/html/broadcast-transaction-window.html
</head>
<body>
<div class="container">
- <h4><span class="label label-success">Transaction built successfully</span></h4>
+ <h4><span class="label label-success">Transaction built successfully!</span></h4>
+ <hr />
+ <button class="btn btn-sm btn-primary btn-inline" onclick="window.parent.focus(); window.close();">Cancel</button>
+ <button class="btn btn-sm btn-primary btn-inline" onclick="showTransaction()">Show Transaction</button>
+ <button class="btn btn-sm btn-primary btn-inline" onclick="copyHex()">Copy Hex</button>
+ <button id="broadcast-button" class="btn btn-sm btn-inline" onclick="broadcastTransaction()">
+ <span class="mdi mdi-tor"></span> Broadcast Transaction
+ </button>
+ <button id="rebuild-button" class="btn btn-sm btn-success btn-inline" onclick="rebuildTransaction()" style="display:none">
+ <span class="mdi mdi-tor"></span> Rebuild Transaction
+ </button>
+ <hr />
+ <div id="privacy-improvement-area" style="overflow-y: scroll; height:230px; border: 1px solid #2D2D30; padding: 5px;" hidden>
+ <h4><span class="label label-danger">Privacy Improvement Suggestion</span></h4>
+ <div id="multiple-coins-content" hidden>
+ <hr />
+ <h4>
+ <span>
+ <small>
+ This transaction requires joining together multiple unspent transaction outputs. This is used by Blockchain analysis to link together your wallet addresses.
+ Consider lowering the number of coins you are spending:
+ </small>
+ </span>
+ </h4>
+ <div id="coin-selection-checkboxes">
+ </div>
+ </div>
+ <div id="change-content" hidden>
+ <hr />
+ <h4>
+ <span>
+ <small>
+ This transaction generates a change output.
+ When you later spend this change output, that transaction will be connected to this transaction of yours.
+ More importantly: If you later join this change output together with your other unspent transaction outputs, this would be used by Blockchain analysis to link together your wallet addresses.
+ </small>
+ </span>
+ <small>
+ <label class="radio" id="keep-change-radio-label">
+ <input onchange="actOnChangeRadio()" id="keep-change-radio" type="radio" name="change-radio" checked style="vertical-align: middle; margin: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Keep the change output
+ </label>
+ <label class="radio" id="spend-whole-coin-radio-label">
+ <input onchange="actOnChangeRadio()" id="spend-whole-coin-radio" type="radio" name="change-radio" style="vertical-align: middle; margin: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span id="spend-whole-coin-radio-span"></span>
+ </label>
+ <label class="radio" id="donate-change-radio-label">
+ <input onchange="actOnChangeRadio()" id="donate-change-radio" type="radio" name="change-radio" style="vertical-align: middle; margin: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span id="donate-change-radio-span"></span>
+ </label>
+ </small>
+ </h4>
+ </div>
+ </div>
<h4>
<span class="label label-info">Spending</span>
<span class="label label-success" id="number-of-inputs"></span>
<span class="label label-info">Does it spend unconfirmed output?</span>
<span class="label label-success" id="spends-unconfirmed"></span>
</h4>
- <h4><span class="label label-info">Programmer nonsense:</span> </h4>
- <textarea id= "transaction" rows="9" cols="76" disabled></textarea><br />
- <button class="btn btn-sm btn-primary btn-inline" onclick="closeWindow()">Cancel</button>
- <button id="broadcast-button" class="btn btn-sm btn-success btn-inline" onclick="broadcastTransaction()">
- <span class="mdi mdi-tor"></span> Broadcast
- </button>
</div>
<script src="../js/copypaste.js"></script>
<script src="../js/broadcast-window.js"></script>

MODIFY
HiddenWallet.Gui/app/js/wallet-phase.ts
HiddenWallet.Gui/app/js/wallet-phase.ts
@@ -220,11 +220,12 @@ function setAmountToAll() {
amount.value = "all";
}
-interface Transaction {
+interface BuildTransactionRequest {
Password: string;
Address: string;
Amount: string;
FeeType: string;
+ DonateChange: boolean;
}
function buildTransaction() {
@@ -266,7 +267,7 @@ function buildTransaction() {
feeType = "low";
}
- var obj: Transaction = { Password: password, Address: address, Amount: amount, FeeType: feeType };
+ let buildTransactionRequest: BuildTransactionRequest = { Password: password, Address: address, Amount: amount, FeeType: feeType, DonateChange: false };
let json: any;
let tabs: HTMLElement = document.getElementById("tabs");
@@ -295,7 +296,7 @@ function buildTransaction() {
buildTXButton.innerHTML = '<span class="glyphicon glyphicon-cog spinning"></span> Building...';
- httpPostWalletAsync(`build-transaction/${bobOrAlice}`, obj, function (json) {
+ httpPostWalletAsync(`build-transaction/${bobOrAlice}`, buildTransactionRequest, function (json) {
if (json.Success === false) {
let alertMessage: string = `Couldn't build the transaciton: ${json.Message}`;
@@ -312,13 +313,13 @@ function buildTransaction() {
const remote = require('electron').remote;
const window = remote.getCurrentWindow();
const BrowserWindow = remote.BrowserWindow;
- let broadcastWindow = new BrowserWindow({ width: 600, height: 500, frame: false, resizable: false, alwaysOnTop: true, parent: window, icon: __dirname + '/app/assets/TumbleBit.png' });
+ let broadcastWindow = new BrowserWindow({ width: 800, height: 600, frame: false, resizable: false, alwaysOnTop: false, parent: window, icon: __dirname + '/app/assets/TumbleBit.png' });
broadcastWindow.show();
broadcastWindow.focus();
broadcastWindow.loadURL(`file://${__dirname}/app/html/broadcast-transaction-window.html`);
broadcastWindow.webContents.on('did-finish-load', () => {
- broadcastWindow.webContents.send('broadcast-response', json);
+ broadcastWindow.webContents.send('broadcast-response', json, bobOrAlice, buildTransactionRequest);
})
}

Add coin control feature