Decouple Wallet Send screen from Ledger Wallet

This commit is contained in:
nicolas.dorier
2018-11-01 00:19:25 +09:00
parent 38edbf8362
commit 88044f6b76
15 changed files with 460 additions and 242 deletions

View File

@@ -166,6 +166,9 @@
<Content Update="Views\Wallets\WalletRescan.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\Wallets\WalletSendLedger.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\Wallets\WalletTransactions.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>

View File

@@ -34,13 +34,66 @@ namespace BTCPayServer.Controllers
}
DerivationSchemeViewModel vm = new DerivationSchemeViewModel();
vm.ServerUrl = WalletsController.GetLedgerWebsocketUrl(this.HttpContext, cryptoCode, null);
vm.CryptoCode = cryptoCode;
vm.RootKeyPath = network.GetRootKeyPath();
SetExistingValues(store, vm);
return View(vm);
}
[HttpGet]
[Route("{storeId}/derivations/{cryptoCode}/ledger/ws")]
public async Task<IActionResult> AddDerivationSchemeLedger(
string storeId,
string cryptoCode,
string command,
int account = 0)
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
return NotFound();
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
var hw = new HardwareWalletService(webSocket);
object result = null;
var network = _NetworkProvider.GetNetwork(cryptoCode);
using (var normalOperationTimeout = new CancellationTokenSource())
{
normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30));
try
{
if (command == "test")
{
result = await hw.Test(normalOperationTimeout.Token);
}
if (command == "getxpub")
{
var getxpubResult = await hw.GetExtPubKey(network, account, normalOperationTimeout.Token);
result = getxpubResult;
}
}
catch (OperationCanceledException)
{ result = new LedgerTestResult() { Success = false, Error = "Timeout" }; }
catch (Exception ex)
{ result = new LedgerTestResult() { Success = false, Error = ex.Message }; }
finally { hw.Dispose(); }
try
{
if (result != null)
{
UTF8Encoding UTF8NOBOM = new UTF8Encoding(false);
var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, MvcJsonOptions.Value.SerializerSettings));
await webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, new CancellationTokenSource(2000).Token);
}
}
catch { }
finally
{
await webSocket.CloseSocket();
}
}
return new EmptyResult();
}
private void SetExistingValues(StoreData store, DerivationSchemeViewModel vm)
{
vm.DerivationScheme = GetExistingDerivationStrategy(vm.CryptoCode, store)?.DerivationStrategyBase.ToString();
@@ -60,7 +113,6 @@ namespace BTCPayServer.Controllers
[Route("{storeId}/derivations/{cryptoCode}")]
public async Task<IActionResult> AddDerivationScheme(string storeId, DerivationSchemeViewModel vm, string cryptoCode)
{
vm.ServerUrl = WalletsController.GetLedgerWebsocketUrl(this.HttpContext, cryptoCode, null);
vm.CryptoCode = cryptoCode;
var store = HttpContext.GetStoreData();
if (store == null)

View File

@@ -23,6 +23,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Options;
using NBitcoin;
using NBitcoin.DataEncoders;
@@ -51,6 +52,7 @@ namespace BTCPayServer.Controllers
IFeeProviderFactory feeRateProvider,
LanguageService langService,
ChangellyClientProvider changellyClientProvider,
IOptions<MvcJsonOptions> mvcJsonOptions,
IHostingEnvironment env, IHttpClientFactory httpClientFactory)
{
_RateFactory = rateFactory;
@@ -59,6 +61,7 @@ namespace BTCPayServer.Controllers
_UserManager = userManager;
_LangService = langService;
_changellyClientProvider = changellyClientProvider;
MvcJsonOptions = mvcJsonOptions;
_TokenController = tokenController;
_WalletProvider = walletProvider;
_Env = env;
@@ -673,6 +676,7 @@ namespace BTCPayServer.Controllers
}
public string GeneratedPairingCode { get; set; }
public IOptions<MvcJsonOptions> MvcJsonOptions { get; }
[HttpGet]
[Route("/api-tokens")]

View File

@@ -46,6 +46,9 @@ namespace BTCPayServer.Controllers
private readonly IFeeProviderFactory _feeRateProvider;
private readonly BTCPayWalletProvider _walletProvider;
public RateFetcher RateFetcher { get; }
[TempData]
public string StatusMessage { get; set; }
CurrencyNameTable _currencyTable;
public WalletsController(StoreRepository repo,
CurrencyNameTable currencyTable,
@@ -150,18 +153,27 @@ namespace BTCPayServer.Controllers
DerivationStrategy paymentMethod = GetPaymentMethod(walletId, store);
if (paymentMethod == null)
return NotFound();
var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode);
if (network == null)
return NotFound();
var storeData = store.GetStoreBlob();
var rateRules = store.GetStoreBlob().GetRateRules(NetworkProvider);
rateRules.Spread = 0.0m;
var currencyPair = new Rating.CurrencyPair(paymentMethod.PaymentId.CryptoCode, GetCurrencyCode(storeData.DefaultLang) ?? "USD");
WalletModel model = new WalletModel()
WalletSendModel model = new WalletSendModel()
{
DefaultAddress = defaultDestination,
DefaultAmount = defaultAmount,
ServerUrl = GetLedgerWebsocketUrl(this.HttpContext, walletId.CryptoCode, paymentMethod.DerivationStrategyBase),
CryptoCurrency = walletId.CryptoCode
Destination = defaultDestination,
CryptoCode = walletId.CryptoCode
};
if (double.TryParse(defaultAmount, out var amount))
model.Amount = (decimal)amount;
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
var recommendedFees = feeProvider.GetFeeRateAsync();
var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.DerivationStrategyBase);
model.CurrentBalance = (await balance).ToDecimal(MoneyUnit.BTC);
model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi;
model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte;
using (CancellationTokenSource cts = new CancellationTokenSource())
{
@@ -185,6 +197,73 @@ namespace BTCPayServer.Controllers
return View(model);
}
[HttpPost]
[Route("{walletId}/send")]
public async Task<IActionResult> WalletSend(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, WalletSendModel vm)
{
if (walletId?.StoreId == null)
return NotFound();
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
if (store == null)
return NotFound();
var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode);
if (network == null)
return NotFound();
var destination = ParseDestination(vm.Destination, network.NBitcoinNetwork);
if (destination == null)
ModelState.AddModelError(nameof(vm.Destination), "Invalid address");
if (vm.Amount.HasValue)
{
if (vm.CurrentBalance == vm.Amount.Value && !vm.SubstractFees)
ModelState.AddModelError(nameof(vm.Amount), "You are sending all your balance to the same destination, you should substract the fees");
if (vm.CurrentBalance < vm.Amount.Value)
ModelState.AddModelError(nameof(vm.Amount), "You are sending more than what you own");
}
if (!ModelState.IsValid)
return View(vm);
return RedirectToAction(nameof(WalletSendLedger), new WalletSendLedgerModel()
{
Destination = vm.Destination,
Amount = vm.Amount.Value,
SubstractFees = vm.SubstractFees,
FeeSatoshiPerByte = vm.FeeSatoshiPerByte
});
}
[HttpGet]
[Route("{walletId}/send/ledger")]
public async Task<IActionResult> WalletSendLedger(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, WalletSendLedgerModel vm)
{
if (walletId?.StoreId == null)
return NotFound();
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
DerivationStrategy paymentMethod = GetPaymentMethod(walletId, store);
if (paymentMethod == null)
return NotFound();
var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode);
if (network == null)
return NotFound();
return View(vm);
}
private IDestination[] ParseDestination(string destination, Network network)
{
try
{
return new IDestination[] { BitcoinAddress.Create(destination, network) };
}
catch
{
return null;
}
}
[HttpGet]
[Route("{walletId}/rescan")]
public async Task<IActionResult> WalletRescan(
@@ -298,20 +377,25 @@ namespace BTCPayServer.Controllers
return _userManager.GetUserId(User);
}
public static string GetLedgerWebsocketUrl(HttpContext httpContext, string cryptoCode, DerivationStrategyBase derivationStrategy)
[HttpGet]
[Route("{walletId}/send/ledger/success")]
public IActionResult WalletSendLedgerSuccess(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId,
string txid)
{
return $"{httpContext.Request.GetAbsoluteRoot().WithTrailingSlash()}ws/ledger/{cryptoCode}/{derivationStrategy?.ToString() ?? string.Empty}";
StatusMessage = $"Transaction broadcasted ({txid})";
return RedirectToAction(nameof(this.WalletTransactions), new { walletId = walletId.ToString() });
}
[HttpGet]
[Route("/ws/ledger/{cryptoCode}/{derivationScheme?}")]
[Route("{walletId}/send/ledger/ws")]
public async Task<IActionResult> LedgerConnection(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId,
string command,
// getinfo
string cryptoCode = null,
// getxpub
[ModelBinder(typeof(ModelBinders.DerivationSchemeModelBinder))]
DerivationStrategyBase derivationScheme = null,
int account = 0,
// sendtoaddress
string destination = null, string amount = null, string feeRate = null, string substractFees = null
@@ -319,6 +403,11 @@ namespace BTCPayServer.Controllers
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
return NotFound();
var cryptoCode = walletId.CryptoCode;
var storeBlob = (await Repository.FindStore(walletId.StoreId, GetUserId()));
var derivationScheme = GetPaymentMethod(walletId, storeBlob).DerivationStrategyBase;
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
using (var normalOperationTimeout = new CancellationTokenSource())
@@ -382,15 +471,6 @@ namespace BTCPayServer.Controllers
}
catch { throw new FormatException("Invalid value for subtract fees"); }
}
if (command == "test")
{
result = await hw.Test(normalOperationTimeout.Token);
}
if (command == "getxpub")
{
var getxpubResult = await hw.GetExtPubKey(network, account, normalOperationTimeout.Token);
result = getxpubResult;
}
if (command == "getinfo")
{
var strategy = GetDirectDerivationStrategy(derivationScheme);
@@ -398,13 +478,12 @@ namespace BTCPayServer.Controllers
{
throw new Exception($"This store is not configured to use this ledger");
}
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
var recommendedFees = feeProvider.GetFeeRateAsync();
var balance = _walletProvider.GetWallet(network).GetBalance(derivationScheme);
result = new GetInfoResult() { Balance = (double)(await balance).ToDecimal(MoneyUnit.BTC), RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi };
result = new GetInfoResult();
}
if (command == "test")
{
result = await hw.Test(normalOperationTimeout.Token);
}
if (command == "sendtoaddress")
{
if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary))
@@ -549,8 +628,6 @@ namespace BTCPayServer.Controllers
public class GetInfoResult
{
public int RecommendedSatoshiPerByte { get; set; }
public double Balance { get; set; }
}
public class SendToAddressResult

View File

@@ -29,7 +29,6 @@ namespace BTCPayServer.Models.StoreViewModels
public bool Confirmation { get; set; }
public bool Enabled { get; set; } = true;
public string ServerUrl { get; set; }
public string StatusMessage { get; internal set; }
public KeyPath RootKeyPath { get; set; }
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Models.WalletViewModels
{
public class WalletSendLedgerModel
{
public int FeeSatoshiPerByte { get; set; }
public bool SubstractFees { get; set; }
public decimal Amount { get; set; }
public string Destination { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Models.WalletViewModels
{
public class WalletSendModel
{
[Required]
public string Destination { get; set; }
[Range(0.0, double.MaxValue)]
[Required]
public decimal? Amount { get; set; }
public decimal CurrentBalance { get; set; }
public string CryptoCode { get; set; }
public int RecommendedSatoshiPerByte { get; set; }
[Display(Name = "Subtract fees from amount")]
public bool SubstractFees { get; set; }
[Range(1, int.MaxValue)]
[Display(Name = "Fee rate (satoshi per byte)")]
[Required]
public int FeeSatoshiPerByte { get; set; }
public decimal? Rate { get; set; }
public int Divisibility { get; set; }
public string Fiat { get; set; }
public string RateError { get; set; }
}
}

View File

@@ -128,9 +128,6 @@
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<script type="text/javascript">
var srvModel = @Html.Raw(Json.Serialize(Model.ServerUrl));
</script>
<script src="~/js/ledgerwebsocket.js" type="text/javascript" defer="defer"></script>
<script src="~/js/StoreAddDerivationScheme.js" type="text/javascript" defer="defer"></script>
}

View File

@@ -1,4 +1,4 @@
@model WalletModel
@model WalletSendModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["Title"] = "Manage wallet";
@@ -6,75 +6,60 @@
}
<h4>@ViewData["Title"]</h4>
<div id="walletAlert" class="alert alert-danger alert-dismissible" style="display:none;" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span id="alertMessage"></span>
</div>
<div class="row">
<div class="col-md-10">
<p>
You can send money received by this store to an address with the help of your Ledger Wallet. <br />
If you don't have a Ledger Wallet, use Electrum with your favorite hardware wallet to transfer crypto. <br />
If your Ledger wallet is not detected:
Send funds to a destination address.
</p>
<ul>
<li>Make sure you are running the Ledger app with version superior or equal to 1.2.4</li>
<li>Use Google Chrome browser and open the coin app on your Ledger</li>
</ul>
<p id="hw-loading"><span class="fa fa-question-circle" style="color:orange"></span> <span>Detecting hardware wallet...</span></p>
<p id="hw-error" style="display:none;"><span class="fa fa-times-circle" style="color:red;"></span> <span class="hw-label">An error happened</span></p>
<p id="hw-success" style="display:none;"><span class="fa fa-check-circle" style="color:green;"></span> <span class="hw-label">Detecting hardware wallet...</span></p>
</div>
</div>
<p id="check-loading" style="display:none;"><span class="fa fa-question-circle" style="color:orange"></span> <span class="check-label">Detecting hardware wallet...</span></p>
<p id="check-error" style="display:none;"><span class="fa fa-times-circle" style="color:red;"></span> <span class="check-label">An error happened</span></p>
<p id="check-success" style="display:none;"><span class="fa fa-check-circle" style="color:green;"></span> <span class="check-label">Detecting hardware wallet...</span></p>
</div>
</div>
<div class="row">
<div class="col-md-6">
<form id="sendform" style="display:none;">
<input type="hidden" id="cryptoCode" asp-for="CryptoCurrency" />
<form method="post">
<input type="hidden" asp-for="Divisibility" />
<input type="hidden" asp-for="Fiat" />
<input type="hidden" asp-for="Rate" />
<input type="hidden" asp-for="CurrentBalance" />
<input type="hidden" asp-for="RecommendedSatoshiPerByte" />
<input type="hidden" asp-for="CryptoCode" />
<div class="form-group">
<label>Destination</label>
<input id="destination-textbox" name="Destination" class="form-control" type="text" />
<span id="Destination-Error" class="text-danger"></span>
<label asp-for="Destination"></label>
<input asp-for="Destination" class="form-control" />
<span asp-validation-for="Destination" class="text-danger"></span>
</div>
<div class="form-group">
<label>Amount</label>
<label asp-for="Amount"></label>
<div class="input-group">
<input id="amount-textbox" name="Amount" class="form-control" type="text" onkeyup='updateFiatValue();' />
<input asp-for="Amount" class="form-control" onkeyup='updateFiatValue();' />
<div class="input-group-prepend">
<span class="input-group-text text-muted" style="display:none;" id="fiatValue"></span>
</div>
</div>
@*<span class="text-muted" id="fiatValue"></span>*@
<span id="Amount-Error" class="text-danger"></span>
<p class="form-text text-muted crypto-info" style="display: none;">
Your current balance is <a id="crypto-balance-link" href="#"><span id="crypto-balance"></span></a> <span id="crypto-code"></span>.
<span asp-validation-for="Amount" class="text-danger"></span>
<p class="form-text text-muted crypto-info">
Your current balance is <a id="crypto-balance-link" href="#"><span>@Model.CurrentBalance</span></a> <span>@Model.CryptoCode</span>.
</p>
</div>
<div class="form-group">
<label>Fee rate (satoshi per byte)</label>
<input id="fee-textbox" name="FeeRate" class="form-control" type="text" />
<label asp-for="FeeSatoshiPerByte"></label>
<input asp-for="FeeSatoshiPerByte" class="form-control" />
<span id="FeeRate-Error" class="text-danger"></span>
<p class="form-text text-muted crypto-info" style="display: none;">
The recommended value is <a id="crypto-fee-link" href="#"><span id="crypto-fee"></span></a> satoshi per byte.
<p class="form-text text-muted crypto-info">
The recommended value is <a id="crypto-fee-link" href="#"><span>@Model.RecommendedSatoshiPerByte</span></a> satoshi per byte.
</p>
</div>
<div class="form-group">
<label>Subtract fees from amount</label>
<input id="substract-checkbox" name="SubstractFees" class="form-check" type="checkbox" />
<label asp-for="SubstractFees"></label>
<input asp-for="SubstractFees" class="form-check" />
</div>
<button id="confirm-button" name="command" type="submit" class="btn btn-primary">Confirm</button>
<button type="submit" class="btn btn-primary">Confirm</button>
</form>
</div>
</div>
@section Scripts
{
<script type="text/javascript">
var srvModel = @Html.Raw(Json.Serialize(Model));
</script>
<script src="~/js/ledgerwebsocket.js" type="text/javascript" defer="defer"></script>
<script src="~/js/StoreWallet.js" type="text/javascript" defer="defer"></script>
<script src="~/js/WalletSend.js" type="text/javascript" defer="defer"></script>
}

View File

@@ -0,0 +1,43 @@
@model WalletSendLedgerModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["Title"] = "Manage wallet";
ViewData.SetActivePageAndTitle(WalletsNavPages.Send);
}
<h4>Sign the transaction with Ledger</h4>
<div id="walletAlert" class="alert alert-danger alert-dismissible" style="display:none;" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span id="alertMessage"></span>
</div>
<div class="row">
<div class="col-md-10">
<input type="hidden" asp-for="Destination" />
<input type="hidden" asp-for="Amount" />
<input type="hidden" asp-for="FeeSatoshiPerByte" />
<input type="hidden" asp-for="SubstractFees" />
<p>
You can send money received by this store to an address with the help of your Ledger Wallet. <br />
If you don't have a Ledger Wallet, use Electrum with your favorite hardware wallet to transfer crypto. <br />
If your Ledger wallet is not detected:
</p>
<ul>
<li>Make sure you are running the Ledger app with version superior or equal to 1.2.4</li>
<li>Use Google Chrome browser and open the coin app on your Ledger</li>
</ul>
<p id="hw-loading"><span class="fa fa-question-circle" style="color:orange"></span> <span>Detecting hardware wallet...</span></p>
<p id="hw-error" style="display:none;"><span class="fa fa-times-circle" style="color:red;"></span> <span class="hw-label">An error happened</span></p>
<p id="hw-success" style="display:none;"><span class="fa fa-check-circle" style="color:green;"></span> <span class="hw-label">Detecting hardware wallet...</span></p>
<p id="check-loading" style="display:none;"><span class="fa fa-question-circle" style="color:orange"></span> <span class="check-label">Detecting hardware wallet...</span></p>
<p id="check-error" style="display:none;"><span class="fa fa-times-circle" style="color:red;"></span> <span class="check-label">An error happened</span></p>
<p id="check-success" style="display:none;"><span class="fa fa-check-circle" style="color:green;"></span> <span class="check-label">Detecting hardware wallet...</span></p>
</div>
</div>
@section Scripts
{
<script src="~/js/ledgerwebsocket.js" type="text/javascript" defer="defer"></script>
<script src="~/js/WalletSendLedger.js" type="text/javascript" defer="defer"></script>
}

View File

@@ -17,6 +17,11 @@
}
</style>
<div class="row">
<div class="col-md-10 text-center">
<partial name="_StatusMessage" for="@TempData["TempDataProperty-StatusMessage"]" />
</div>
</div>
<h4>@ViewData["Title"]</h4>
<div class="row">
<div class="col-md-10">

View File

@@ -1,6 +1,16 @@
$(function () {
var ledgerDetected = false;
var bridge = new ledgerwebsocket.LedgerWebSocketBridge(srvModel);
var loc = window.location, new_uri;
if (loc.protocol === "https:") {
new_uri = "wss:";
} else {
new_uri = "ws:";
}
new_uri += "//" + loc.host;
new_uri += loc.pathname + "/ledger/ws";
var bridge = new ledgerwebsocket.LedgerWebSocketBridge(new_uri);
var cryptoSelector = $("#CryptoCurrency");
function GetSelectedCryptoCode() {

View File

@@ -1,160 +0,0 @@

function updateFiatValue() {
if (srvModel.rate !== null) {
var fiatValue = $("#fiatValue");
fiatValue.css("display", "inline");
var amountValue = parseFloat($("#amount-textbox").val());
if (!isNaN(amountValue)) {
fiatValue.text("= " + (srvModel.rate * amountValue).toFixed(srvModel.divisibility) + " " + srvModel.fiat);
}
}
}
$(function () {
var ledgerDetected = false;
var bridge = new ledgerwebsocket.LedgerWebSocketBridge(srvModel.serverUrl);
var recommendedFees = "";
var recommendedBalance = "";
var cryptoCode = $("#cryptoCode").val();
if (srvModel.defaultAddress !== null) {
$("#destination-textbox").val(srvModel.defaultAddress);
}
if (srvModel.defaultAmount !== null) {
$("#amount-textbox").val(srvModel.defaultAmount);
}
function WriteAlert(type, message) {
$("#walletAlert").removeClass("alert-danger");
$("#walletAlert").removeClass("alert-warning");
$("#walletAlert").removeClass("alert-success");
$("#walletAlert").addClass("alert-" + type);
$("#walletAlert").css("display", "block");
$("#alertMessage").text(message);
}
function Write(prefix, type, message) {
$("#" + prefix + "-loading").css("display", "none");
$("#" + prefix + "-error").css("display", "none");
$("#" + prefix + "-success").css("display", "none");
$("#" + prefix + "-" + type).css("display", "block");
$("." + prefix + "-label").text(message);
}
$("#sendform").on("submit", function (elem) {
elem.preventDefault();
if ($("#amount-textbox").val() === "") {
$("#amount-textbox").val(recommendedBalance);
$("#substract-checkbox").prop("checked", true);
}
if ($("#fee-textbox").val() === "") {
$("#fee-textbox").val(recommendedFees);
}
var args = "";
args += "cryptoCode=" + cryptoCode;
args += "&destination=" + $("#destination-textbox").val();
args += "&amount=" + $("#amount-textbox").val();
args += "&feeRate=" + $("#fee-textbox").val();
args += "&substractFees=" + $("#substract-checkbox").prop("checked");
WriteAlert("warning", 'Please validate the transaction on your ledger');
var confirmButton = $("#confirm-button");
confirmButton.prop("disabled", true);
confirmButton.addClass("disabled");
bridge.sendCommand('sendtoaddress', args, 60 * 10 /* timeout */)
.catch(function (reason) {
WriteAlert("danger", reason);
confirmButton.prop("disabled", false);
confirmButton.removeClass("disabled");
})
.then(function (result) {
if (!result)
return;
confirmButton.prop("disabled", false);
confirmButton.removeClass("disabled");
if (result.error) {
WriteAlert("danger", result.error);
} else {
WriteAlert("success", 'Transaction broadcasted (' + result.transactionId + ')');
$("#fee-textbox").val("");
$("#amount-textbox").val("");
$("#destination-textbox").val("");
$("#substract-checkbox").prop("checked", false);
updateInfo();
}
});
return false;
});
$("#crypto-balance-link").on("click", function (elem) {
elem.preventDefault();
var val = $("#crypto-balance-link").text();
$("#amount-textbox").val(val);
$("#substract-checkbox").prop('checked', true);
return false;
});
$("#crypto-fee-link").on("click", function (elem) {
elem.preventDefault();
var val = $("#crypto-fee-link").text();
$("#fee-textbox").val(val);
return false;
});
var updateInfo = function () {
if (!ledgerDetected)
return false;
$(".crypto-info").css("display", "none");
bridge.sendCommand("getinfo", "cryptoCode=" + cryptoCode)
.catch(function (reason) { Write('check', 'error', reason); })
.then(function (result) {
if (!result)
return;
if (result.error) {
Write('check', 'error', result.error);
return;
}
else {
Write('check', 'success', 'This store is configured to use your ledger');
$(".crypto-info").css("display", "block");
recommendedFees = result.recommendedSatoshiPerByte;
recommendedBalance = result.balance;
$("#crypto-fee").text(result.recommendedSatoshiPerByte);
$("#crypto-balance").text(result.balance);
$("#crypto-code").text(cryptoCode);
}
});
};
bridge.isSupported()
.then(function (supported) {
if (!supported) {
Write('hw', 'error', 'U2F or Websocket are not supported by this browser');
}
else {
bridge.sendCommand('test', null, 5)
.catch(function (reason) {
if (reason.name === "TransportError")
reason = "Are you running the ledger app with version equals or above 1.2.4?";
Write('hw', 'error', reason);
})
.then(function (result) {
if (!result)
return;
if (result.error) {
Write('hw', 'error', result.error);
} else {
Write('hw', 'success', 'Ledger detected');
$("#sendform").css("display", "block");
ledgerDetected = true;
updateInfo();
}
});
}
});
});

View File

@@ -0,0 +1,32 @@
function updateFiatValue() {
var rateStr = $("#Rate").val();
var divisibilityStr = $("#Divisibility").val();
var fiat = $("#Fiat").val();
var rate = parseFloat(rateStr);
var divisibility = parseInt(divisibilityStr);
if (!isNaN(rate) && !isNaN(divisibility)) {
var fiatValue = $("#fiatValue");
var amountValue = parseFloat($("#Amount").val());
if (!isNaN(amountValue)) {
fiatValue.css("display", "inline");
fiatValue.text("= " + (rate * amountValue).toFixed(divisibility) + " " + fiat);
}
}
}
$(function () {
updateFiatValue();
$("#crypto-fee-link").on("click", function (elem) {
elem.preventDefault();
var val = $("#crypto-fee-link").text();
$("#FeeSatoshiPerByte").val(val);
return false;
});
$("#crypto-balance-link").on("click", function (elem) {
elem.preventDefault();
var val = $("#crypto-balance-link").text();
$("#Amount").val(val);
$("#SubstractFees").prop('checked', true);
return false;
});
});

View File

@@ -0,0 +1,120 @@
$(function () {
var destination = $("#Destination").val();
var amount = $("#Amount").val();
var fee = $("#FeeSatoshiPerByte").val();
var substractFee = $("#SubstractFees").val();
var loc = window.location, ws_uri;
if (loc.protocol === "https:") {
ws_uri = "wss:";
} else {
ws_uri = "ws:";
}
ws_uri += "//" + loc.host;
ws_uri += loc.pathname + "/ws";
var successCallback = loc.protocol + "//" + loc.host + loc.pathname + "/success";
var ledgerDetected = false;
var bridge = new ledgerwebsocket.LedgerWebSocketBridge(ws_uri);
var cryptoCode = $("#cryptoCode").val();
function WriteAlert(type, message) {
$("#walletAlert").removeClass("alert-danger");
$("#walletAlert").removeClass("alert-warning");
$("#walletAlert").removeClass("alert-success");
$("#walletAlert").addClass("alert-" + type);
$("#walletAlert").css("display", "block");
$("#alertMessage").text(message);
}
function Write(prefix, type, message) {
$("#" + prefix + "-loading").css("display", "none");
$("#" + prefix + "-error").css("display", "none");
$("#" + prefix + "-success").css("display", "none");
$("#" + prefix + "-" + type).css("display", "block");
$("." + prefix + "-label").text(message);
}
var updateInfo = function () {
if (!ledgerDetected)
return false;
$(".crypto-info").css("display", "none");
bridge.sendCommand("getinfo", "cryptoCode=" + cryptoCode)
.catch(function (reason) { Write('check', 'error', reason); })
.then(function (result) {
if (!result)
return;
if (result.error) {
Write('check', 'error', result.error);
return;
}
else {
Write('check', 'success', 'This store is configured to use your ledger');
$(".crypto-info").css("display", "block");
var args = "";
args += "cryptoCode=" + cryptoCode;
args += "&destination=" + destination;
args += "&amount=" + amount;
args += "&feeRate=" + fee;
args += "&substractFees=" + substractFee;
WriteAlert("warning", 'Please validate the transaction on your ledger');
var confirmButton = $("#confirm-button");
confirmButton.prop("disabled", true);
confirmButton.addClass("disabled");
bridge.sendCommand('sendtoaddress', args, 60 * 10 /* timeout */)
.catch(function (reason) {
WriteAlert("danger", reason);
confirmButton.prop("disabled", false);
confirmButton.removeClass("disabled");
})
.then(function (result) {
if (!result)
return;
confirmButton.prop("disabled", false);
confirmButton.removeClass("disabled");
if (result.error) {
WriteAlert("danger", result.error);
} else {
WriteAlert("success", 'Transaction broadcasted (' + result.transactionId + ')');
window.location.replace(successCallback + "?txid=" + result.transactionId);
}
});
}
});
};
bridge.isSupported()
.then(function (supported) {
if (!supported) {
Write('hw', 'error', 'U2F or Websocket are not supported by this browser');
}
else {
bridge.sendCommand('test', null, 5)
.catch(function (reason) {
if (reason.name === "TransportError")
reason = "Are you running the ledger app with version equals or above 1.2.4?";
Write('hw', 'error', reason);
})
.then(function (result) {
if (!result)
return;
if (result.error) {
Write('hw', 'error', result.error);
} else {
Write('hw', 'success', 'Ledger detected');
$("#sendform").css("display", "block");
ledgerDetected = true;
updateInfo();
}
});
}
});
});