diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 547836332..c8b68526a 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -166,6 +166,9 @@ $(IncludeRazorContentInPack) + + $(IncludeRazorContentInPack) + $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Controllers/StoresController.BTCLike.cs b/BTCPayServer/Controllers/StoresController.BTCLike.cs index 9f52b79c8..1848b48ed 100644 --- a/BTCPayServer/Controllers/StoresController.BTCLike.cs +++ b/BTCPayServer/Controllers/StoresController.BTCLike.cs @@ -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 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(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 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) @@ -109,7 +161,7 @@ namespace BTCPayServer.Controllers // - The user is setting a new derivation scheme (!vm.Confirmation && strategy != null && exisingStrategy != strategy.DerivationStrategyBase.ToString()) || // - The user is clicking on continue without changing anything - (!vm.Confirmation && willBeExcluded == wasExcluded); + (!vm.Confirmation && willBeExcluded == wasExcluded); if (!showAddress) { diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 3bd6615a5..79742a36c 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -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, 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 { get; } [HttpGet] [Route("/api-tokens")] diff --git a/BTCPayServer/Controllers/WalletsController.cs b/BTCPayServer/Controllers/WalletsController.cs index 22cbf819c..b7500bfe0 100644 --- a/BTCPayServer/Controllers/WalletsController.cs +++ b/BTCPayServer/Controllers/WalletsController.cs @@ -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 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 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 WalletRescan( @@ -204,7 +283,7 @@ namespace BTCPayServer.Controllers vm.IsSupportedByCurrency = _dashboard.Get(walletId.CryptoCode)?.Status?.BitcoinStatus?.Capabilities?.CanScanTxoutSet == true; var explorer = ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode); var scanProgress = await explorer.GetScanUTXOSetInformationAsync(paymentMethod.DerivationStrategyBase); - if(scanProgress != null) + if (scanProgress != null) { vm.PreviousError = scanProgress.Error; if (scanProgress.Status == ScanUTXOStatus.Queued || scanProgress.Status == ScanUTXOStatus.Pending) @@ -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 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 diff --git a/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs b/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs index aee99dcd4..ffa1c8448 100644 --- a/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs @@ -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; } } diff --git a/BTCPayServer/Models/WalletViewModels/WalletSendLedgerModel.cs b/BTCPayServer/Models/WalletViewModels/WalletSendLedgerModel.cs new file mode 100644 index 000000000..a3aec83bc --- /dev/null +++ b/BTCPayServer/Models/WalletViewModels/WalletSendLedgerModel.cs @@ -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; } + } +} diff --git a/BTCPayServer/Models/WalletViewModels/WalletSendModel.cs b/BTCPayServer/Models/WalletViewModels/WalletSendModel.cs new file mode 100644 index 000000000..4b006b619 --- /dev/null +++ b/BTCPayServer/Models/WalletViewModels/WalletSendModel.cs @@ -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; } + } +} diff --git a/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml b/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml index fa06cea84..c3c0b4ad9 100644 --- a/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml +++ b/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml @@ -128,9 +128,6 @@ @section Scripts { @await Html.PartialAsync("_ValidationScriptsPartial") - } diff --git a/BTCPayServer/Views/Wallets/WalletSend.cshtml b/BTCPayServer/Views/Wallets/WalletSend.cshtml index 1233f5ab6..f601aa21a 100644 --- a/BTCPayServer/Views/Wallets/WalletSend.cshtml +++ b/BTCPayServer/Views/Wallets/WalletSend.cshtml @@ -1,4 +1,4 @@ -@model WalletModel +@model WalletSendModel @{ Layout = "../Shared/_NavLayout.cshtml"; ViewData["Title"] = "Manage wallet"; @@ -6,75 +6,60 @@ }

@ViewData["Title"]

- +

- You can send money received by this store to an address with the help of your Ledger Wallet.
- If you don't have a Ledger Wallet, use Electrum with your favorite hardware wallet to transfer crypto.
- If your Ledger wallet is not detected: + Send funds to a destination address.

-
    -
  • Make sure you are running the Ledger app with version superior or equal to 1.2.4
  • -
  • Use Google Chrome browser and open the coin app on your Ledger
  • -
-

Detecting hardware wallet...

- - - - - -
+
-
@section Scripts - { - - - +{ + } diff --git a/BTCPayServer/Views/Wallets/WalletSendLedger.cshtml b/BTCPayServer/Views/Wallets/WalletSendLedger.cshtml new file mode 100644 index 000000000..3ea9d344d --- /dev/null +++ b/BTCPayServer/Views/Wallets/WalletSendLedger.cshtml @@ -0,0 +1,43 @@ +@model WalletSendLedgerModel +@{ + Layout = "../Shared/_NavLayout.cshtml"; + ViewData["Title"] = "Manage wallet"; + ViewData.SetActivePageAndTitle(WalletsNavPages.Send); +} + +

Sign the transaction with Ledger

+ +
+
+ + + + +

+ You can send money received by this store to an address with the help of your Ledger Wallet.
+ If you don't have a Ledger Wallet, use Electrum with your favorite hardware wallet to transfer crypto.
+ If your Ledger wallet is not detected: +

+
    +
  • Make sure you are running the Ledger app with version superior or equal to 1.2.4
  • +
  • Use Google Chrome browser and open the coin app on your Ledger
  • +
+

Detecting hardware wallet...

+ + + + + + +
+
+ + +@section Scripts +{ + + +} diff --git a/BTCPayServer/Views/Wallets/WalletTransactions.cshtml b/BTCPayServer/Views/Wallets/WalletTransactions.cshtml index 3d1706ee1..ced5e9e7a 100644 --- a/BTCPayServer/Views/Wallets/WalletTransactions.cshtml +++ b/BTCPayServer/Views/Wallets/WalletTransactions.cshtml @@ -17,6 +17,11 @@ } +
+
+ +
+

@ViewData["Title"]

diff --git a/BTCPayServer/wwwroot/js/StoreAddDerivationScheme.js b/BTCPayServer/wwwroot/js/StoreAddDerivationScheme.js index 7379e4f3c..9a96571a1 100644 --- a/BTCPayServer/wwwroot/js/StoreAddDerivationScheme.js +++ b/BTCPayServer/wwwroot/js/StoreAddDerivationScheme.js @@ -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() { diff --git a/BTCPayServer/wwwroot/js/StoreWallet.js b/BTCPayServer/wwwroot/js/StoreWallet.js deleted file mode 100644 index 5408a5b77..000000000 --- a/BTCPayServer/wwwroot/js/StoreWallet.js +++ /dev/null @@ -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(); - } - }); - } - }); -}); diff --git a/BTCPayServer/wwwroot/js/WalletSend.js b/BTCPayServer/wwwroot/js/WalletSend.js new file mode 100644 index 000000000..f855c6ae5 --- /dev/null +++ b/BTCPayServer/wwwroot/js/WalletSend.js @@ -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; + }); +}); diff --git a/BTCPayServer/wwwroot/js/WalletSendLedger.js b/BTCPayServer/wwwroot/js/WalletSendLedger.js new file mode 100644 index 000000000..e0c3577bd --- /dev/null +++ b/BTCPayServer/wwwroot/js/WalletSendLedger.js @@ -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(); + } + }); + } + }); +});