GUi to server linking for CCJ
MODIFY
HiddenWallet.Daemon/Controllers/WalletController.cs
HiddenWallet.Daemon/Controllers/WalletController.cs
@@ -316,31 +316,48 @@ namespace HiddenWallet.Daemon.Controllers
[Route("tumbler-server")]
[HttpGet]
- public IActionResult TumblerServer()
- {
- //return new ObjectResult(new SuccessResponse());
- //This wil go in WalletWrapper:
- // public Network Network => _walletJob.Safe.Network;
- // var addressUri = Network == Network.Main ? Config.TumbleBitServerMainNet : Config.TumbleBitServerTestNet;
- // var address = "";
- // if (addressUri != null)
- // {
- // address = addressUri.AbsoluteUri;
- // }
-
- //Fees:
- //var satoshiFeePerBytes = (await _walletJob.FeeJob.GetFeePerBytesAsync(FeeType.High, default(CancellationToken)).ConfigureAwait(false)).ToDecimal(MoneyUnit.Satoshi);
-
- var address = Global.WalletWrapper.Network == Network.Main ? Global.Config.ChaumianTumblerMainAddress : Global.Config.ChaumianTumblerTestNetAddress;
- return new ObjectResult(new ChaumianCoinJoin.Models.StatusResponse { Address = address, Phase = ChaumianCoinJoin.TumblerPhase.InputRegistration.ToString(), Denomination = "0.15", AnonymitySet = 12, TimeSpentInInputRegistrationInSeconds = 416, MaximumInputsPerAlices = 4, FeePerInputs = "0.00015 BTC", FeePerOutputs = "0.00015 BTC", NetworkFee = "0.002034 BTC", Version = "1" });
- //try
- //{
- // return new ObjectResult(Global.WalletWrapper.GetTumblerServerResponseAsync().Result);
- //}
- //catch (Exception ex)
- //{
- // return new ObjectResult(new FailureResponse { Message = ex.Message, Details = ex.ToString() });
- //}
+ public async Task<IActionResult> TumblerServerAsync()
+ {
+ ChaumianCoinJoin.Models.StatusResponse response = await Global.WalletWrapper.GetTumblerStatusAsync();
+
+ if (response.Success)
+ {
+ response.Address = Global.WalletWrapper.Network == Network.Main ? Global.Config.ChaumianTumblerMainAddress : Global.Config.ChaumianTumblerTestNetAddress;
+ return new ObjectResult(response);
+ }
+ else
+ {
+ return new ObjectResult(new FailureResponse { Message = "Tumbler Status Error", Details = "" });
+ }
+ }
+
+ [Route("tumble")]
+ [HttpPost]
+ public async Task<IActionResult> TumbleAsync([FromBody]TumbleRequest request)
+ {
+ try
+ {
+ var getFrom = GetAccount(request.From, out SafeAccount fromAccount);
+ if (getFrom != null) return new ObjectResult(getFrom);
+
+ var getTo = GetAccount(request.To, out SafeAccount toAccount);
+ if (getTo != null) return new ObjectResult(getTo);
+
+ BaseResponse result = await Global.WalletWrapper.TumbleAsync(fromAccount, toAccount);
+
+ if (result.Success)
+ {
+ return new ObjectResult(new SuccessResponse());
+ }
+ else
+ {
+ return new ObjectResult(new FailureResponse { Message = "Tumbler Error", Details = "Could not submit for mixing [01]" });
+ }
+ }
+ catch (Exception ex)
+ {
+ return new ObjectResult(new FailureResponse { Message = "Tumbler Error", Details = "Could not submit for mixing [02]. " + ex.ToString() });
+ }
}
}
}

MODIFY
HiddenWallet.Daemon/Wrappers/WalletWrapper.cs
HiddenWallet.Daemon/Wrappers/WalletWrapper.cs
@@ -313,5 +313,44 @@ namespace HiddenWallet.Daemon.Wrappers
if (result.Success) return new SuccessResponse();
else return new FailureResponse { Message = result.FailingReason, Details = "" };
}
+
+ public async Task<ChaumianCoinJoin.Models.StatusResponse> GetTumblerStatusAsync()
+ {
+ try
+ {
+ ChaumianCoinJoin.Models.StatusResponse result = await _walletJob.CoinJoinService.GetStatusAsync();
+ return result;
+ }
+ catch (Exception)
+ {
+ return new ChaumianCoinJoin.Models.StatusResponse { Success = false };
+ }
+ }
+
+ public async Task<BaseResponse> TumbleAsync(SafeAccount fromAccount, SafeAccount toAccount)
+ {
+ try
+ {
+ IEnumerable<Script> unusedOutputs = await _walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, toAccount, HdPathType.NonHardened);
+ BitcoinAddress activeOutput = unusedOutputs.RandomElement().GetDestinationAddress(Network); // TODO: this is sub-optimal, it'd be better to not which had been already registered and not reregister it
+ BitcoinWitPubKeyAddress bech32 = new BitcoinWitPubKeyAddress(activeOutput.ToString(), Network);
+
+ uint256 result = await _walletJob.CoinJoinService.TumbleAsync(fromAccount, bech32, CancellationToken.None);
+
+ if (null != result)
+ {
+ return new SuccessResponse();
+ }
+ else
+ {
+ return new FailureResponse { Message = "Failed to start Mix. An error occured. [01]", Details = "" };
+ }
+
+ }
+ catch (Exception)
+ {
+ return new FailureResponse { Message = "Failed to start Mix. An error occured. [02]", Details = "" };
+ }
+ }
}
}

MODIFY
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
@@ -370,6 +370,20 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
}
}
+ public async Task<StatusResponse> GetStatusAsync(CancellationToken cancel = default)
+ {
+ try
+ {
+ StatusResponse status = await TumblerClient.GetStatusAsync(cancel);
+ return status;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine(ex);
+ throw ex;
+ }
+ }
+
#endregion
#region Disposing

MODIFY
HiddenWallet.Gui/app/html/wallet-content.html
HiddenWallet.Gui/app/html/wallet-content.html
<br />
<div id="mixer-settings-content">
<label>Denomination:</label>
- <span class="label label-success" style="font-size: 13px; margin-left:96px;" id="tumbler-denomination">1</span>
- <br />
- <label>Fee Per Input:</label>
- <span class="label label-success" style="font-size: 13px; margin-left:101px;" id="tumbler-fee-input">2</span>
- <br />
- <label>Fee Per Output:</label>
- <span class="label label-success" style="font-size: 13px; margin-left:89px;" id="tumbler-fee-output">3</span>
+ <span class="label label-success" style="font-size: 13px; margin-left:96px;" id="tumbler-denomination"></span>
<br />
<br />
<span class="label label-danger" id="tumbling-to-wallet" style="font-size: 15px;">?</span>
<br />
<br />
- <div id="not-enough-funds-to-mix" style="font-size: 13px;" class="alert alert-danger">Not enough funds to mix</div>
+ <!--<div id="not-enough-funds-to-mix" style="font-size: 13px;" class="alert alert-danger">Not enough funds to mix</div>-->
<div id="wallet-selected">
<form class="form-inline">
<label>Amount:</label>
- <input style="margin-left:1em;" id="amount-input" class="form-control form-inline" onchange="amountChanged()" onkeypress="document.getElementById('amount-input').stepUp(1); return false;" type="number" size="11" />
+ <span class="form-control form-inline" style="font-size: 13px; margin-left:96px;" id="amount-input">?</span>
<label>BTC</label>
</form>
<hr />
<label>Estimated Network Fees:</label>
- <span style="font-size: 13px;" class="label label-success" id="tumbling-network-fees"></span>
+ <span style="font-size: 13px;" class="label label-success" id="network-fees"></span>
<br />
<br />
- <button id="broadcast-button" class="btn btn-lg btn-success btn-block" onclick="">
+ <button id="mixing-button" class="btn btn-lg btn-success btn-block" onclick="mix()">
<span class="mdi mdi-tor"></span> Start Mixing
</button>
</div>
<tr>
<th>Phase</th>
<th>Anonymity Set</th>
- <th>Time in input phase</th>
+ <th>Seconds since mixing round started</th>
</tr>
</thead>
<tbody id="tumbling-phase-records"></tbody>

MODIFY
HiddenWallet.Gui/app/js/wallet-phase.ts
HiddenWallet.Gui/app/js/wallet-phase.ts
@@ -98,10 +98,6 @@ function chooseWalletDropdown(aliceBob: string = "") {
let chooseWallet: HTMLElement = document.getElementById("choose-wallet-dropdown");
let chooseWalletActive: HTMLElement = document.getElementById("choose-wallet-dropdown-active");
let tumblingTo: HTMLElement = document.getElementById("tumbling-to-wallet");
- let notEnoughFunds: HTMLElement = document.getElementById("not-enough-funds-to-mix");
- let walletSelected: HTMLElement = document.getElementById("wallet-selected");
- let amount: HTMLInputElement = document.getElementById("amount-input") as HTMLInputElement;
- let tumblingFees: HTMLElement = document.getElementById("tumbling-network-fees");
let resp: any;
@@ -124,45 +120,9 @@ function chooseWalletDropdown(aliceBob: string = "") {
tumblingTo.classList.remove("label-danger");
tumblingTo.classList.add("label-success");
- //todo - next 2 lines comment out and fake a value - might not be needed
- //let maximumMixed: number = resp.Available - (resp.Available * (tumblerFeePercent / 100));
- let maximumMixed: number = 1;
-
- if (maximumMixed < tumblerDenomination) {
- notEnoughFunds.style.removeProperty("display");
- walletSelected.style.display = "none";
- }
- else {
- var times = Math.floor(maximumMixed / tumblerDenomination);
- //todo - commented out below - might not be needed:
- //maximumMixed -= (estCycleFee * times);
- walletSelected.style.removeProperty("display");
- notEnoughFunds.style.display = "none";
-
- //todo - we shouldn't be able to change this: needs to be fixed not stepped input:
- amount.step = String(tumblerDenomination);
- amount.min = String(tumblerDenomination);
- amount.max = String(tumblerDenomination);
- amount.value = String(tumblerDenomination);
- //todo - commented out might not be needed:
- //tumblingFees.innerText = estCycleFee + " BTC";
+ //todo - check they have enough funds to mix before submitting
}
}
-}
-
-function amountChanged() {
- let amount: HTMLInputElement = document.getElementById("amount-input") as HTMLInputElement;
- let tumblingFees: HTMLElement = document.getElementById("tumbling-network-fees");
-
- //todo - commented out below for now - might not be needed
- //let cycleCount: number = Number(amount.value) / tumblerDenomination;
- //tumblingFees.innerText = round(cycleCount * estCycleFee, 8) + " BTC";
-}
-
-function round(value: number, precision: number) {
- let multiplier: number = Math.pow(10, precision || 0);
- return Math.round(value * multiplier) / multiplier;
-}
let tumblerAddress: string;
let tumblerPhase: string;
@@ -170,11 +130,11 @@ let tumblerDenomination: number;
let anonymitySet: number;
let timeSpentInputRegistration: number;
let maxInputsPerAlice: number;
-let feePerInputs: string;
-let feePerOutputs: string;
let version: number;
-let networkFees: string;
let tumblerStatus: string;
+let networkFees: string;
+let feePerInputs: string;
+let feePerOutputs: string;
function mixerShow() {
let contentElem: HTMLElement = document.getElementById("content");
@@ -190,17 +150,13 @@ function mixerShow() {
let mixerSettingsContentElem: HTMLElement = document.getElementById("mixer-settings-content");
let denominationElem: HTMLElement = document.getElementById("tumbler-denomination");
let anonSetElem: HTMLElement = document.getElementById("anonymity-set");
- let inputPhaseTimeElem: HTMLElement = document.getElementById("input-phase-time");
- let feeInputElem: HTMLElement = document.getElementById("tumbler-fee-input");
- let feeOutputElem: HTMLElement = document.getElementById("tumbler-fee-output");
- let networkFeesElem: HTMLElement = document.getElementById("tumbling-network-fees");
+ let networkFeesElem: HTMLElement = document.getElementById("network-fees");
let walletSelectedElem: HTMLElement = document.getElementById("wallet-selected");
- let notEnoughFundsElem: HTMLElement = document.getElementById("not-enough-funds-to-mix");
+ let amountElem: HTMLElement = document.getElementById("amount-input");
+ //todo - cursor wait around server calls?
resp = httpGetWallet("tumbler-server");
- //TODO - handle resp.Success - show or hide resp.*?
- //todo - return Status in tumbler-server - online/offline?
if (resp.Success) {
tumblerStatus = "ONLINE";
}
@@ -209,7 +165,6 @@ function mixerShow() {
}
tumblerAddress = resp.Address;
- //todo - helper to convert 0 etc to human readable:
tumblerPhase = resp.Phase;
tumblerDenomination = resp.Denomination;
anonymitySet = resp.AnonymitySet;
@@ -217,8 +172,9 @@ function mixerShow() {
maxInputsPerAlice = resp.MaximumInputsPerAlices;
feePerInputs = resp.FeePerInputs;
feePerOutputs = resp.FeePerOutputs;
- networkFees = resp.NetworkFee;
+ networkFees = calculateNetworkFee(feePerInputs, feePerOutputs);
version = resp.Version;
+
//todo - version check and error.
tumblerAddressElem.value = tumblerAddress;
@@ -239,26 +195,21 @@ function mixerShow() {
else {
tumblerStatusElem.classList.add("label-success");
- denominationElem.innerText = tumblerDenomination.toString() + " BTC";
- feeInputElem.innerText = feePerInputs;
- feeOutputElem.innerText = feePerOutputs;
- networkFeesElem.innerText = networkFees;
- //document.getElementById("tumbler-fee").innerText = resp.FeePercent + " %";
- //document.getElementById("tumbling-time").innerText = Math.round((resp.CycleLengthMinutes / 60) * 10) / 10 + " hours";
+ denominationElem.innerText = String(tumblerDenomination) + " BTC";
+ amountElem.innerText = String(tumblerDenomination);
+ networkFeesElem.innerText = String(networkFees);
- walletSelectedElem.style.display = "none";
- notEnoughFundsElem.style.display = "none";
+ //walletSelectedElem.style.display = "none";
- //Although we are just displyaing one row for now we might want to add a row for each phase as we progress through them
+ //Although we are just displaying one row for now we might want to add a row for each phase as we progress through them
//so it has been built to handle this in the future:
-
let phaseRecords: HTMLElement = document.getElementById("tumbling-phase-records");
//for (phase in phases) {
let trNode: HTMLElement = document.createElement("tr");
let tdNodePhase: HTMLElement = document.createElement("td");
- tdNodePhase.innerText = "Input Registration";
+ tdNodePhase.innerText = getPhaseName(tumblerPhase);
let tdNodeAnonSet: HTMLElement = document.createElement("td");
tdNodeAnonSet.innerText = String(anonymitySet);
@@ -274,6 +225,44 @@ function mixerShow() {
}
}
+function calculateNetworkFee(inputFee: string, outputFee: string) {
+ var result = parseFloat(inputFee) + parseFloat(outputFee);
+
+ return result.toFixed(8);
+}
+
+interface TumbleRequest {
+ From: string;
+ To: string;
+}
+
+function mix() {
+
+ let from: HTMLElement = document.getElementById("choose-wallet-dropdown-active");
+ let to: HTMLElement = document.getElementById("tumbling-to-wallet");
+
+ var obj: TumbleRequest = { From: from.innerText, To: to.innerText };
+
+ let json: any;
+
+ let mixingButton: HTMLButtonElement = document.getElementById("mixing-button") as HTMLButtonElement;
+
+ httpPostWalletAsync("tumble", obj, function (json) {
+ if (json.Success === false) {
+ let alertMessage: string = `Couldn't start the mix: ${json.Message}`;
+
+ if (!isBlank(json.Details)) {
+ alertMessage = `${alertMessage}\n\nDetails:\n${json.Details}`;
+ }
+
+ alert(alertMessage);
+ }
+ else {
+ mixingButton.disabled = true;
+ alert("Mix submitted");
+ }
+ });
+}
function updateWalletContent() {
let walletContentFrame: HTMLIFrameElement = <HTMLIFrameElement>document.getElementById("wallet-content-frame");
@@ -306,7 +295,6 @@ function updateWalletContent() {
let extPubKey: HTMLElement = document.getElementById("extpubkey");
let recAddresses: HTMLElement = document.getElementById("receive-addresses");
-
let resp: any = httpGetWallet("receive/alice");
let i: number = 0
@@ -427,24 +415,24 @@ function buildTransaction() {
var slowFeeChecked: boolean = (<HTMLInputElement>document.getElementById("slow-fee-radio")).checked;
if (!address) {
- alert("Couldn't build the transaciton: Wrong address!");
+ alert("Couldn't build the tansaction: Wrong address!");
return;
}
if (!amount || Number(amount) <= 0) {
- alert("Couldn't build the transaciton: Wrong amount!");
+ alert("Couldn't build the tansaction: Wrong amount!");
return;
}
// if both are checked or none are checked (cannot happen)
if ((fastFeeChecked && slowFeeChecked) || (!fastFeeChecked && !slowFeeChecked)) {
- alert("Couldn't build the transaciton: Wrong fee type!");
+ alert("Couldn't build the tansaction: Wrong fee type!");
return;
}
// (cannot happen)
if (password == null) {
- alert("Couldn't build the transaciton: Wrong fee type!");
+ alert("Couldn't build the tansaction: Wrong fee type!");
return;
}
@@ -489,7 +477,7 @@ function buildTransaction() {
httpPostWalletAsync(`build-transaction/${bobOrAlice}`, obj, function (json) {
if (json.Success === false) {
- let alertMessage: string = `Couldn't build the transaciton: ${json.Message}`;
+ let alertMessage: string = `Couldn't build the tansaction: ${json.Message}`;
if (!isBlank(json.Details)) {
alertMessage = `${alertMessage}
@@ -522,3 +510,32 @@ function buildTransaction() {
function isBlank(str: string) {
return (!str || /^\s*$/.test(str));
}
+
+function getPhaseName(phase: string): string {
+ let phaseName: string;
+
+ switch (phase) {
+ case "InputRegistration": {
+ phaseName = "Input Registration";
+ break;
+ }
+ case "ConnectionConfirmation": {
+ phaseName = "Connection Confirmation";
+ break;
+ }
+ case "OutputRegistration": {
+ phaseName = "Output Registration";
+ break;
+ }
+ case "Signing": {
+ phaseName = "Signing";
+ break;
+ }
+ default: {
+ phaseName = "Unknown";
+ break;
+ }
+ }
+
+ return phaseName;
+}
\ No newline at end of file

MODIFY
HiddenWallet/ChaumianCoinJoin/Models/StatusResponse.cs
HiddenWallet/ChaumianCoinJoin/Models/StatusResponse.cs
@@ -17,7 +17,6 @@ namespace HiddenWallet.ChaumianCoinJoin.Models
public int MaximumInputsPerAlices { get; set; }
public string FeePerInputs { get; set; }
public string FeePerOutputs { get; set; }
- public string NetworkFee { get; set; }
public string Version { get; set; }
}
}

GUi to server linking for CCJ