diff --git a/BTCPayServer.Tests/PayJoinTests.cs b/BTCPayServer.Tests/PayJoinTests.cs index 2832089ad..bba3d05b0 100644 --- a/BTCPayServer.Tests/PayJoinTests.cs +++ b/BTCPayServer.Tests/PayJoinTests.cs @@ -201,7 +201,7 @@ namespace BTCPayServer.Tests var receiverUser = tester.NewAccount(); receiverUser.GrantAccess(true); receiverUser.RegisterDerivationScheme("BTC", receiverAddressType, true); - await receiverUser.ModifyWalletSettings(p => p.PayJoinEnabled = true); + await receiverUser.ModifyOnchainPaymentSettings(p => p.PayJoinEnabled = true); var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network); string errorCode = receiverAddressType == senderAddressType ? null : "unavailable|any UTXO available"; @@ -301,10 +301,8 @@ namespace BTCPayServer.Tests var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn")) .GetAttribute("href"); Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21); - - s.GoToHome(); - s.GoToStore(receiver.storeId); - s.Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click(); + + s.GoToWalletSettings(receiver.storeId, cryptoCode); Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected); var sender = s.CreateNewStore(); @@ -573,7 +571,7 @@ namespace BTCPayServer.Tests address = (await nbx.GetUnusedAsync(bob.DerivationScheme, DerivationFeature.Deposit)).Address; tester.ExplorerNode.SendToAddress(address, Money.Coins(1.1m)); await notifications.NextEventAsync(); - await bob.ModifyWalletSettings(p => p.PayJoinEnabled = true); + await bob.ModifyOnchainPaymentSettings(p => p.PayJoinEnabled = true); var invoice = bob.BitPay.CreateInvoice( new Invoice { Price = 0.1m, Currency = "BTC", FullNotifications = true }); var invoiceBIP21 = new BitcoinUrlBuilder(invoice.CryptoInfo.First().PaymentUrls.BIP21, @@ -662,7 +660,7 @@ namespace BTCPayServer.Tests var receiverUser = tester.NewAccount(); receiverUser.GrantAccess(true); receiverUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, true); - await receiverUser.ModifyWalletSettings(p => p.PayJoinEnabled = true); + await receiverUser.ModifyOnchainPaymentSettings(p => p.PayJoinEnabled = true); var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network); string lastInvoiceId = null; @@ -859,7 +857,7 @@ retry: receiverUser.GrantAccess(true); receiverUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, true); - await receiverUser.ModifyWalletSettings(p => p.PayJoinEnabled = true); + await receiverUser.ModifyOnchainPaymentSettings(p => p.PayJoinEnabled = true); // payjoin is enabled, with a segwit wallet, and the keys are available in nbxplorer invoice = receiverUser.BitPay.CreateInvoice( new Invoice() { Price = 0.02m, Currency = "BTC", FullNotifications = true }); diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index 3a513816e..4f525596c 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -154,7 +154,8 @@ namespace BTCPayServer.Tests // Replace previous wallet case if (Driver.PageSource.Contains("id=\"ChangeWalletLink\"")) { - Driver.FindElement(By.Id("ChangeWalletLink")).Click(); + Driver.FindElement(By.Id("ActionsDropdownToggle")).Click(); + Driver.WaitForElement(By.Id("ChangeWalletLink")).Click(); Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("REPLACE"); Driver.FindElement(By.Id("ConfirmContinue")).Click(); } @@ -337,6 +338,18 @@ namespace BTCPayServer.Tests } } + public void GoToWalletSettings(string storeId, string cryptoCode = "BTC") + { + GoToStore(storeId); + Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click(); + } + + public void GoToLightningSettings(string storeId, string cryptoCode = "BTC") + { + GoToStore(storeId); + Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click(); + } + public void GoToInvoiceCheckout(string invoiceId) { Driver.FindElement(By.Id("Invoices")).Click(); diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 4e50cedbe..61e20e3e9 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -811,10 +811,10 @@ namespace BTCPayServer.Tests s.RegisterNewUser(true); foreach (var isHotwallet in new[] { false, true }) { - var (storeName, storeId) = s.CreateNewStore(); - s.GenerateWallet(privkeys: isHotwallet, - seed: "melody lizard phrase voice unique car opinion merge degree evil swift cargo"); - s.GoToWallet(s.WalletId, WalletsNavPages.Settings); + var cryptoCode = "BTC"; + var (_, storeId) = s.CreateNewStore(); + s.GenerateWallet(cryptoCode, "melody lizard phrase voice unique car opinion merge degree evil swift cargo", privkeys: isHotwallet); + s.GoToWalletSettings(storeId, cryptoCode); if (isHotwallet) Assert.Contains("View seed", s.Driver.PageSource); else @@ -831,10 +831,11 @@ namespace BTCPayServer.Tests await s.StartAsync(); s.RegisterNewUser(true); var (storeName, storeId) = s.CreateNewStore(); + var cryptoCode = "BTC"; // In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', // then try to use the seed to sign the transaction - s.GenerateWallet("BTC", "", true); + s.GenerateWallet(cryptoCode, "", true); //let's test quickly the receive wallet page s.Driver.FindElement(By.Id("Wallets")).Click(); @@ -872,7 +873,7 @@ namespace BTCPayServer.Tests //change the wallet and ensure old address is not there and generating a new one does not result in the prev one s.GoToStore(storeId); - s.GenerateWallet("BTC", "", true); + s.GenerateWallet(cryptoCode, "", true); s.Driver.FindElement(By.Id("Wallets")).Click(); s.Driver.FindElement(By.LinkText("Manage")).Click(); s.Driver.FindElement(By.Id("WalletReceive")).Click(); @@ -889,7 +890,7 @@ namespace BTCPayServer.Tests await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest)); Assert.True(result.IsWatchOnly); s.GoToStore(storeId); - var mnemonic = s.GenerateWallet("BTC", "", true, true); + var mnemonic = s.GenerateWallet(cryptoCode, "", true, true); //lets import and save private keys var root = mnemonic.DeriveExtKey(); @@ -906,23 +907,22 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("Wallets")).Click(); s.Driver.FindElement(By.LinkText("Manage")).Click(); - s.ClickOnAllSideMenus(); // Make sure wallet info is correct - s.Driver.FindElement(By.Id("WalletSettings")).Click(); + s.GoToWalletSettings(storeId, cryptoCode); Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(), s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value")); Assert.Contains("m/84'/1'/0'", s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value")); - + + s.Driver.FindElement(By.Id("Wallets")).Click(); + s.Driver.FindElement(By.LinkText("Manage")).Click(); + // Make sure we can rescan, because we are admin! s.Driver.FindElement(By.Id("WalletRescan")).Click(); Assert.Contains("The batch size make sure", s.Driver.PageSource); - - // We setup the fingerprint and the account key path - s.Driver.FindElement(By.Id("WalletSettings")).Click(); - + // Check the tx sent earlier arrived s.Driver.FindElement(By.Id("WalletTransactions")).Click(); @@ -971,10 +971,10 @@ namespace BTCPayServer.Tests Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id("Outputs_0__DestinationAddress")).GetAttribute("value")); - s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings); - var walletUrl = s.Driver.Url; - s.Driver.FindElement(By.Id("OtherActionsDropdownToggle")).Click(); - s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click(); + s.GoToWalletSettings(storeId, cryptoCode); + var settingsUrl = s.Driver.Url; + s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click(); + s.Driver.FindElement(By.Id("ViewSeed")).Click(); // Seed backup page var recoveryPhrase = s.Driver.FindElements(By.Id("RecoveryPhrase")).First() @@ -986,7 +986,7 @@ namespace BTCPayServer.Tests // No confirmation, just a link to return to the wallet Assert.Empty(s.Driver.FindElements(By.Id("confirm"))); s.Driver.FindElement(By.Id("proceed")).Click(); - Assert.Equal(walletUrl, s.Driver.Url); + Assert.Equal(settingsUrl, s.Driver.Url); } } @@ -997,12 +997,12 @@ namespace BTCPayServer.Tests { await s.StartAsync(); s.RegisterNewUser(true); - var (_, storeId) = s.CreateNewStore(); - var mnemonic = s.GenerateWallet("BTC", - "click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel"); + (string _, string storeId) = s.CreateNewStore(); + var cryptoCode = "BTC"; + var mnemonic = s.GenerateWallet(cryptoCode, "click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel"); // Make sure wallet info is correct - s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings); + s.GoToWalletSettings(storeId, cryptoCode); Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(), s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value")); Assert.Contains("m/84'/1'/0'", @@ -1267,11 +1267,12 @@ namespace BTCPayServer.Tests await s.Server.EnsureChannelsSetup(); s.RegisterNewUser(true); - var store = s.CreateNewStore(); - var network = s.Server.NetworkProvider.GetNetwork("BTC").NBitcoinNetwork; - s.GoToStore(store.storeId); - s.AddLightningNode("BTC", LightningConnectionType.CLightning, false); - s.Driver.FindElement(By.Id($"Modify-LightningBTC")).Click(); + var cryptoCode = "BTC"; + (_, string storeId) = s.CreateNewStore(); + var network = s.Server.NetworkProvider.GetNetwork(cryptoCode).NBitcoinNetwork; + s.GoToStore(storeId); + s.AddLightningNode(cryptoCode, LightningConnectionType.CLightning, false); + s.GoToLightningSettings(storeId, cryptoCode); s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true); s.GoToApps(); s.Driver.FindElement(By.Id("CreateNewApp")).Click(); @@ -1312,11 +1313,11 @@ namespace BTCPayServer.Tests new[] { s.Server.MerchantLightningD }, new[] { s.Server.MerchantLnd.Client }); s.RegisterNewUser(true); - var store = s.CreateNewStore(); + (string storeName, string storeId) = s.CreateNewStore(); var network = s.Server.NetworkProvider.GetNetwork(cryptoCode).NBitcoinNetwork; - s.GoToStore(store.storeId); + s.GoToStore(storeId); s.AddLightningNode(cryptoCode, LightningConnectionType.CLightning); - s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click(); + s.GoToLightningSettings(storeId, cryptoCode); // LNURL is false by default Assert.False(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected); // LNURL settings are not expanded when LNURL is disabled @@ -1326,7 +1327,7 @@ namespace BTCPayServer.Tests Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text); // Topup Invoice test - var i = s.CreateInvoice(store.storeName, null, cryptoCode); + var i = s.CreateInvoice(storeName, null, cryptoCode); s.GoToInvoiceCheckout(i); s.Driver.FindElement(By.Id("copy-tab")).Click(); var lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value"); @@ -1359,7 +1360,7 @@ namespace BTCPayServer.Tests // Standard invoice test s.GoToHome(); - i = s.CreateInvoice(store.storeName, 0.0000001m, cryptoCode); + i = s.CreateInvoice(storeName, 0.0000001m, cryptoCode); s.GoToInvoiceCheckout(i); s.Driver.FindElement(By.ClassName("payment__currencies")).Click(); // BOLT11 is also available for standard invoices @@ -1404,12 +1405,12 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("save")).Click(); Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text); - i = s.CreateInvoice(store.storeName, 0.000001m, cryptoCode); + i = s.CreateInvoice(storeName, 0.000001m, cryptoCode); s.GoToInvoiceCheckout(i); s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); s.GoToHome(); - i = s.CreateInvoice(store.storeName, null, cryptoCode); + i = s.CreateInvoice(storeName, null, cryptoCode); s.GoToInvoiceCheckout(i); s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); @@ -1429,9 +1430,9 @@ namespace BTCPayServer.Tests Assert.False(s.Driver.FindElement(By.Id("DisableBolt11PaymentMethod")).Selected); Assert.False(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected); Assert.False(s.Driver.FindElement(By.Id("LNURLBech32Mode")).Selected); - s.CreateInvoice(store.storeName, 0.0000001m, cryptoCode,"",null, expectedSeverity: StatusMessageModel.StatusSeverity.Error); + s.CreateInvoice(storeName, 0.0000001m, cryptoCode,"",null, expectedSeverity: StatusMessageModel.StatusSeverity.Error); - i = s.CreateInvoice(store.storeName, null, cryptoCode); + i = s.CreateInvoice(storeName, null, cryptoCode); s.GoToInvoiceCheckout(i); s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); s.Driver.FindElement(By.Id("copy-tab")).Click(); @@ -1502,29 +1503,27 @@ namespace BTCPayServer.Tests using var s = SeleniumTester.Create(); s.Server.ActivateLightning(); await s.StartAsync(); - await s.Server.EnsureChannelsSetup(); + var cryptoCode = "BTC"; s.RegisterNewUser(true); //ln address tests - var store = s.CreateNewStore(); + s.CreateNewStore(); s.GoToStore(s.StoreId, StoreNavPages.Integrations); //ensure ln address is not available as Lightning is not enable s.Driver.FindElement(By.Id("lightning-address-option")) .FindElement(By.LinkText("You need to setup Lightning first")); - s.GoToStore(s.StoreId, StoreNavPages.PaymentMethods); + s.GoToStore(s.StoreId); s.AddLightningNode("BTC", LightningConnectionType.LndREST, false); - s.GoToStore(s.StoreId, StoreNavPages.Integrations); //ensure ln address is not available as lnurl is not configured s.Driver.FindElement(By.Id("lightning-address-option")) .FindElement(By.LinkText("You need LNURL configured first")); - s.GoToStore(s.StoreId, StoreNavPages.PaymentMethods); - s.Driver.FindElement(By.Id($"Modify-LightningBTC")).Click(); + s.GoToLightningSettings(s.StoreId, cryptoCode); s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true); s.Driver.WaitForAndClick(By.Id("save")); - Assert.Contains($"BTC Lightning settings successfully updated", s.FindAlertMessage().Text); + Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text); s.GoToStore(s.StoreId, StoreNavPages.Integrations); s.Driver.FindElement(By.Id("lightning-address-option")) @@ -1580,7 +1579,6 @@ namespace BTCPayServer.Tests } } - private static void CanBrowseContent(SeleniumTester s) { s.Driver.FindElement(By.ClassName("delivery-content")).Click(); diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 59fd0a5bc..0362a9c45 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -150,6 +150,15 @@ namespace BTCPayServer.Tests modify(walletSettings); storeController.UpdateWalletSettings(walletSettings).GetAwaiter().GetResult(); } + + public async Task ModifyOnchainPaymentSettings(Action modify) + { + var storeController = GetController(); + var response = await storeController.WalletSettings(StoreId, "BTC"); + WalletSettingsViewModel walletSettings = (WalletSettingsViewModel)((ViewResult)response).Model; + modify(walletSettings); + storeController.UpdatePaymentSettings(walletSettings).GetAwaiter().GetResult(); + } public T GetController(bool setImplicitStore = true) where T : Controller { diff --git a/BTCPayServer/Controllers/StoresController.Onchain.cs b/BTCPayServer/Controllers/StoresController.Onchain.cs index 06d616c5b..749da9a00 100644 --- a/BTCPayServer/Controllers/StoresController.Onchain.cs +++ b/BTCPayServer/Controllers/StoresController.Onchain.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; @@ -18,6 +19,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using NBitcoin; +using NBitcoin.DataEncoders; using NBXplorer; using NBXplorer.DerivationStrategy; using NBXplorer.Models; @@ -335,7 +337,7 @@ namespace BTCPayServer.Controllers IsStored = request.SavePrivateKeys, ReturnUrl = Url.Action(nameof(GenerateWalletConfirm), new { storeId, cryptoCode }) }; - if (this._BTCPayEnv.IsDeveloping) + if (_BTCPayEnv.IsDeveloping) { GenerateWalletResponse = response; } @@ -380,33 +382,52 @@ namespace BTCPayServer.Controllers { return NotFound(); } - + var storeBlob = store.GetStoreBlob(); (bool canUseHotWallet, bool rpcImport) = await CanUseHotWallet(); + var client = _ExplorerProvider.GetExplorerClient(network); var vm = new WalletSettingsViewModel - { - StoreId = storeId, - CryptoCode = cryptoCode, - Network = network, - Source = derivation.Source, - RootFingerprint = derivation.GetSigningAccountKeySettings().RootFingerprint.ToString(), - DerivationScheme = derivation.AccountDerivation.ToString(), - KeyPath = derivation.GetSigningAccountKeySettings().AccountKeyPath?.ToString(), - Config = ProtectString(derivation.ToJson()), - IsHotWallet = derivation.IsHotWallet, - PayJoinEnabled = storeBlob.PayJoinEnabled, - MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes, - SpeedPolicy = store.SpeedPolicy, - ShowRecommendedFee = storeBlob.ShowRecommendedFee, - RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget, - CanUseHotWallet = canUseHotWallet, - CanUseRPCImport = rpcImport, - CanUsePayJoin = canUseHotWallet && store - .GetSupportedPaymentMethods(_NetworkProvider) - .OfType() - .Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet) - }; + { + StoreId = storeId, + CryptoCode = cryptoCode, + WalletId = new WalletId(storeId, cryptoCode), + Network = network, + IsHotWallet = derivation.IsHotWallet, + Source = derivation.Source, + RootFingerprint = derivation.GetSigningAccountKeySettings().RootFingerprint.ToString(), + DerivationScheme = derivation.AccountDerivation.ToString(), + DerivationSchemeInput = derivation.AccountOriginal, + KeyPath = derivation.GetSigningAccountKeySettings().AccountKeyPath?.ToString(), + UriScheme = derivation.Network.NBitcoinNetwork.UriScheme, + Label = derivation.Label, + SelectedSigningKey = derivation.SigningKey.ToString(), + NBXSeedAvailable = derivation.IsHotWallet && + canUseHotWallet && + !string.IsNullOrEmpty(await client.GetMetadataAsync(derivation.AccountDerivation, + WellknownMetadataKeys.MasterHDKey)), + AccountKeys = derivation.AccountKeySettings + .Select(e => new WalletSettingsAccountKeyViewModel + { + AccountKey = e.AccountKey.ToString(), + MasterFingerprint = e.RootFingerprint is HDFingerprint fp ? fp.ToString() : null, + AccountKeyPath = e.AccountKeyPath == null ? "" : $"m/{e.AccountKeyPath}" + }).ToList(), + Config = ProtectString(derivation.ToJson()), + PayJoinEnabled = storeBlob.PayJoinEnabled, + MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes, + SpeedPolicy = store.SpeedPolicy, + ShowRecommendedFee = storeBlob.ShowRecommendedFee, + RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget, + CanUseHotWallet = canUseHotWallet, + CanUseRPCImport = rpcImport, + CanUsePayJoin = canUseHotWallet && store + .GetSupportedPaymentMethods(_NetworkProvider) + .OfType() + .Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet), + StoreName = store.StoreName, + + }; ViewData["ReplaceDescription"] = WalletReplaceWarning(derivation.IsHotWallet); ViewData["RemoveDescription"] = WalletRemoveWarning(derivation.IsHotWallet, network.CryptoCode); @@ -414,38 +435,140 @@ namespace BTCPayServer.Controllers return View(vm); } - [HttpPost("{storeId}/onchain/{cryptoCode}/settings")] + [HttpPost("{storeId}/onchain/{cryptoCode}/settings/wallet")] public async Task UpdateWalletSettings(WalletSettingsViewModel vm) { - bool needUpdate = false; - if (CurrentStore.SpeedPolicy != vm.SpeedPolicy) + var checkResult = IsAvailable(vm.CryptoCode, out var store, out _); + if (checkResult != null) { - needUpdate = true; - CurrentStore.SpeedPolicy = vm.SpeedPolicy; + return checkResult; } - var blob = CurrentStore.GetStoreBlob(); + var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store); + if (derivation == null) + { + return NotFound(); + } + bool needUpdate = false; + string errorMessage = null; + if (derivation.Label != vm.Label) + { + needUpdate = true; + derivation.Label = vm.Label; + } + + var signingKey = string.IsNullOrEmpty(vm.SelectedSigningKey) + ? null + : new BitcoinExtPubKey(vm.SelectedSigningKey, derivation.Network.NBitcoinNetwork); + if (derivation.SigningKey != signingKey && signingKey != null) + { + needUpdate = true; + derivation.SigningKey = signingKey; + } + + for (int i = 0; i < derivation.AccountKeySettings.Length; i++) + { + KeyPath accountKeyPath = null; + HDFingerprint? rootFingerprint = null; + + try + { + accountKeyPath = string.IsNullOrWhiteSpace(vm.AccountKeys[i].AccountKeyPath) + ? null + : new KeyPath(vm.AccountKeys[i].AccountKeyPath); + + if (accountKeyPath != null && derivation.AccountKeySettings[i].AccountKeyPath != accountKeyPath) + { + needUpdate = true; + derivation.AccountKeySettings[i].AccountKeyPath = accountKeyPath; + } + } + catch (Exception ex) + { + errorMessage = $"{ex.Message}: {vm.AccountKeys[i].AccountKeyPath}"; + } + + try + { + rootFingerprint = string.IsNullOrWhiteSpace(vm.AccountKeys[i].MasterFingerprint) + ? (HDFingerprint?)null + : new HDFingerprint(Encoders.Hex.DecodeData(vm.AccountKeys[i].MasterFingerprint)); + + if (rootFingerprint != null && derivation.AccountKeySettings[i].RootFingerprint != rootFingerprint) + { + needUpdate = true; + derivation.AccountKeySettings[i].RootFingerprint = rootFingerprint; + } + } + catch (Exception ex) + { + errorMessage = $"{ex.Message}: {vm.AccountKeys[i].MasterFingerprint}"; + } + } + + if (needUpdate) + { + store.SetSupportedPaymentMethod(derivation); + + await _Repo.UpdateStore(store); + + if (string.IsNullOrEmpty(errorMessage)) + { + TempData[WellKnownTempData.SuccessMessage] = "Wallet settings successfully updated"; + } + else + { + TempData[WellKnownTempData.ErrorMessage] = errorMessage; + } + } + + return RedirectToAction(nameof(WalletSettings), new { vm.StoreId, vm.CryptoCode }); + } + + [HttpPost("{storeId}/onchain/{cryptoCode}/settings/payment")] + public async Task UpdatePaymentSettings(WalletSettingsViewModel vm) + { + var checkResult = IsAvailable(vm.CryptoCode, out var store, out _); + if (checkResult != null) + { + return checkResult; + } + + var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store); + if (derivation == null) + { + return NotFound(); + } + + bool needUpdate = false; + if (store.SpeedPolicy != vm.SpeedPolicy) + { + needUpdate = true; + store.SpeedPolicy = vm.SpeedPolicy; + } + + var blob = store.GetStoreBlob(); + var payjoinChanged = blob.PayJoinEnabled != vm.PayJoinEnabled; blob.MonitoringExpiration = TimeSpan.FromMinutes(vm.MonitoringExpiration); blob.ShowRecommendedFee = vm.ShowRecommendedFee; blob.RecommendedFeeBlockTarget = vm.RecommendedFeeBlockTarget; - - var payjoinChanged = blob.PayJoinEnabled != vm.PayJoinEnabled; blob.PayJoinEnabled = vm.PayJoinEnabled; - if (CurrentStore.SetStoreBlob(blob)) + + if (store.SetStoreBlob(blob)) { needUpdate = true; } if (needUpdate) { - await _Repo.UpdateStore(CurrentStore); + await _Repo.UpdateStore(store); TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated"; if (payjoinChanged && blob.PayJoinEnabled) { - var problematicPayjoinEnabledMethods = CurrentStore.GetSupportedPaymentMethods(_NetworkProvider) + var problematicPayjoinEnabledMethods = store.GetSupportedPaymentMethods(_NetworkProvider) .OfType() .Where(settings => settings.Network.SupportPayJoin && !settings.IsHotWallet) .Select(settings => settings.PaymentId.CryptoCode) @@ -457,17 +580,59 @@ namespace BTCPayServer.Controllers TempData.SetStatusMessageModel(new StatusMessageModel() { Severity = StatusMessageModel.StatusSeverity.Warning, - Html = $"The payment settings were updated successfully. However, payjoin will not work for {string.Join(", ", problematicPayjoinEnabledMethods)} until you configure them to be a hot wallet." + Html = $"The payment settings were updated successfully. However, PayJoin will not work for {string.Join(", ", problematicPayjoinEnabledMethods)} until you configure them to be a hot wallet." }); } } } - - return RedirectToAction(nameof(WalletSettings), new + + return RedirectToAction(nameof(WalletSettings), new { vm.StoreId, vm.CryptoCode }); + } + + [HttpGet("{storeId}/onchain/{cryptoCode}/seed")] + public async Task WalletSeed(string storeId, string cryptoCode, CancellationToken cancellationToken = default) + { + var checkResult = IsAvailable(cryptoCode, out var store, out var network); + if (checkResult != null) { - storeId = vm.StoreId, - cryptoCode = vm.CryptoCode + return checkResult; + } + + var derivation = GetExistingDerivationStrategy(cryptoCode, store); + if (derivation == null) + { + return NotFound(); + } + + (bool canUseHotWallet, bool _) = await CanUseHotWallet(); + if (!canUseHotWallet) + { + return NotFound(); + } + + var client = _ExplorerProvider.GetExplorerClient(network); + if (await GetSeed(client, derivation) != null) + { + var mnemonic = await client.GetMetadataAsync(derivation.AccountDerivation, + WellknownMetadataKeys.Mnemonic, cancellationToken); + var recoveryVm = new RecoverySeedBackupViewModel + { + CryptoCode = cryptoCode, + Mnemonic = mnemonic, + IsStored = true, + RequireConfirm = false, + ReturnUrl = Url.Action(nameof(WalletSettings), new { storeId, cryptoCode }) + }; + return this.RedirectToRecoverySeedBackup(recoveryVm); + } + + TempData.SetStatusMessageModel(new StatusMessageModel + { + Severity = StatusMessageModel.StatusSeverity.Error, + Message = "The seed was not found" }); + + return RedirectToAction(nameof(WalletSettings)); } [HttpGet("{storeId}/onchain/{cryptoCode}/replace")] @@ -632,6 +797,13 @@ namespace BTCPayServer.Controllers .FirstOrDefault(d => d.PaymentId == id); return existing; } + + private async Task GetSeed(ExplorerClient client, DerivationSchemeSettings derivation) + { + return derivation.IsHotWallet && + await client.GetMetadataAsync(derivation.AccountDerivation, WellknownMetadataKeys.MasterHDKey) is string seed && + !string.IsNullOrEmpty(seed) ? seed : null; + } private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet() { diff --git a/BTCPayServer/Controllers/WalletsController.cs b/BTCPayServer/Controllers/WalletsController.cs index 2ddb7826a..814d1ad73 100644 --- a/BTCPayServer/Controllers/WalletsController.cs +++ b/BTCPayServer/Controllers/WalletsController.cs @@ -12,7 +12,6 @@ using BTCPayServer.Data; using BTCPayServer.HostedServices; using BTCPayServer.ModelBinders; using BTCPayServer.Models; -using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Models.WalletViewModels; using BTCPayServer.Payments; using BTCPayServer.Services; @@ -25,14 +24,13 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using NBitcoin; using BTCPayServer.BIP78.Sender; +using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Payments.PayJoin; -using NBitcoin.DataEncoders; using NBXplorer; using NBXplorer.DerivationStrategy; using NBXplorer.Models; using Newtonsoft.Json; using StoreData = BTCPayServer.Data.StoreData; -using WalletSettingsViewModel = BTCPayServer.Models.WalletViewModels.WalletSettingsViewModel; namespace BTCPayServer.Controllers { @@ -41,10 +39,12 @@ namespace BTCPayServer.Controllers [AutoValidateAntiforgeryToken] public partial class WalletsController : Controller { - public StoreRepository Repository { get; } - public WalletRepository WalletRepository { get; } - public BTCPayNetworkProvider NetworkProvider { get; } - public ExplorerClientProvider ExplorerClientProvider { get; } + private StoreRepository Repository { get; } + private WalletRepository WalletRepository { get; } + private BTCPayNetworkProvider NetworkProvider { get; } + private ExplorerClientProvider ExplorerClientProvider { get; } + + public RateFetcher RateFetcher { get; } private readonly UserManager _userManager; private readonly JsonSerializerSettings _serializerSettings; @@ -63,8 +63,6 @@ namespace BTCPayServer.Controllers private readonly PullPaymentHostedService _pullPaymentService; private readonly IEnumerable _payoutHandlers; - public RateFetcher RateFetcher { get; } - readonly CurrencyNameTable _currencyTable; public WalletsController(StoreRepository repo, WalletRepository walletRepository, @@ -86,7 +84,7 @@ namespace BTCPayServer.Controllers LabelFactory labelFactory, ApplicationDbContextFactory dbContextFactory, BTCPayNetworkJsonSerializerSettings jsonSerializerSettings, - HostedServices.PullPaymentHostedService pullPaymentService, + PullPaymentHostedService pullPaymentService, IEnumerable payoutHandlers) { _currencyTable = currencyTable; @@ -114,7 +112,7 @@ namespace BTCPayServer.Controllers } // Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md - readonly string[] LabelColorScheme = new string[] + readonly string[] LabelColorScheme = { "#fbca04", "#0e8a16", @@ -274,9 +272,8 @@ namespace BTCPayServer.Controllers return View(wallets); } - [HttpGet] - [Route("{walletId}")] - [Route("{walletId}/transactions")] + [HttpGet("{walletId}")] + [HttpGet("{walletId}/transactions")] public async Task WalletTransactions( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, @@ -359,8 +356,7 @@ namespace BTCPayServer.Controllers return $"{walletId}:{txId}"; } - [HttpGet] - [Route("{walletId}/receive")] + [HttpGet("{walletId}/receive")] public IActionResult WalletReceive([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId) { @@ -854,7 +850,7 @@ namespace BTCPayServer.Controllers private IActionResult RedirectToWalletPSBT(WalletPSBTViewModel vm) { - var redirectVm = new PostRedirectViewModel() + var redirectVm = new PostRedirectViewModel { AspController = "Wallets", AspAction = nameof(WalletPSBT), @@ -871,7 +867,7 @@ namespace BTCPayServer.Controllers public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, SigningContextModel signingContext) { - return View(nameof(SignWithSeed), new SignWithSeedViewModel() + return View(nameof(SignWithSeed), new SignWithSeedViewModel { SigningContext = signingContext, }); @@ -970,8 +966,7 @@ namespace BTCPayServer.Controllers return RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() }); } - [HttpGet] - [Route("{walletId}/rescan")] + [HttpGet("{walletId}/rescan")] public async Task WalletRescan( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId) @@ -1012,8 +1007,7 @@ namespace BTCPayServer.Controllers return View(vm); } - [HttpPost] - [Route("{walletId}/rescan")] + [HttpPost("{walletId}/rescan")] [Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task WalletRescan( [ModelBinder(typeof(WalletIdModelBinder))] @@ -1036,12 +1030,9 @@ namespace BTCPayServer.Controllers return RedirectToAction(); } - public StoreData CurrentStore + private StoreData CurrentStore { - get - { - return HttpContext.GetStoreData(); - } + get => HttpContext.GetStoreData(); } internal DerivationSchemeSettings GetDerivationSchemeSettings(WalletId walletId) @@ -1055,11 +1046,11 @@ namespace BTCPayServer.Controllers try { var b = await wallet.GetBalance(derivationStrategy, cts.Token); - return (b.Available ?? b.Total); + return b.Available ?? b.Total; } catch { - return NBitcoin.Money.Zero; + return Money.Zero; } } @@ -1079,138 +1070,51 @@ namespace BTCPayServer.Controllers { return _userManager.GetUserId(User); } - - [Route("{walletId}/settings")] - public async Task WalletSettings( - [ModelBinder(typeof(WalletIdModelBinder))] - WalletId walletId) - { - var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); - if (derivationSchemeSettings == null || derivationSchemeSettings.Network.ReadonlyWallet) - return NotFound(); - var store = (await Repository.FindStore(walletId.StoreId, GetUserId())); - var vm = new WalletSettingsViewModel() - { - StoreName = store.StoreName, - UriScheme = derivationSchemeSettings.Network.NBitcoinNetwork.UriScheme, - Label = derivationSchemeSettings.Label, - DerivationScheme = derivationSchemeSettings.AccountDerivation.ToString(), - DerivationSchemeInput = derivationSchemeSettings.AccountOriginal, - SelectedSigningKey = derivationSchemeSettings.SigningKey.ToString(), - NBXSeedAvailable = derivationSchemeSettings.IsHotWallet && - await CanUseHotWallet() && - !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode) - .GetMetadataAsync(GetDerivationSchemeSettings(walletId).AccountDerivation, - WellknownMetadataKeys.MasterHDKey)) - }; - vm.AccountKeys = derivationSchemeSettings.AccountKeySettings - .Select(e => new WalletSettingsAccountKeyViewModel() - { - AccountKey = e.AccountKey.ToString(), - MasterFingerprint = e.RootFingerprint is HDFingerprint fp ? fp.ToString() : null, - AccountKeyPath = e.AccountKeyPath == null ? "" : $"m/{e.AccountKeyPath}" - }).ToList(); - return View(vm); - } - - [Route("{walletId}/settings")] - [HttpPost] - public async Task WalletSettings( + + [HttpPost("{walletId}/actions")] + public async Task WalletActions( [ModelBinder(typeof(WalletIdModelBinder))] - WalletId walletId, WalletSettingsViewModel vm, string command = "save", + WalletId walletId, string command, CancellationToken cancellationToken = default) { - if (!ModelState.IsValid) - return View(vm); var derivationScheme = GetDerivationSchemeSettings(walletId); if (derivationScheme == null || derivationScheme.Network.ReadonlyWallet) return NotFound(); - if (command == "save") + switch (command) { - derivationScheme.Label = vm.Label; - derivationScheme.SigningKey = string.IsNullOrEmpty(vm.SelectedSigningKey) - ? null - : new BitcoinExtPubKey(vm.SelectedSigningKey, derivationScheme.Network.NBitcoinNetwork); - for (int i = 0; i < derivationScheme.AccountKeySettings.Length; i++) + case "prune": { - derivationScheme.AccountKeySettings[i].AccountKeyPath = - string.IsNullOrWhiteSpace(vm.AccountKeys[i].AccountKeyPath) - ? null - : new KeyPath(vm.AccountKeys[i].AccountKeyPath); - derivationScheme.AccountKeySettings[i].RootFingerprint = - string.IsNullOrWhiteSpace(vm.AccountKeys[i].MasterFingerprint) - ? (HDFingerprint?)null - : new HDFingerprint(Encoders.Hex.DecodeData(vm.AccountKeys[i].MasterFingerprint)); - } + var result = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode).PruneAsync(derivationScheme.AccountDerivation, new PruneRequest(), cancellationToken); + if (result.TotalPruned == 0) + { + TempData[WellKnownTempData.SuccessMessage] = "The wallet is already pruned"; + } + else + { + TempData[WellKnownTempData.SuccessMessage] = + $"The wallet has been successfully pruned ({result.TotalPruned} transactions have been removed from the history)"; + } - var store = (await Repository.FindStore(walletId.StoreId, GetUserId())); - store.SetSupportedPaymentMethod(derivationScheme); - await Repository.UpdateStore(store); - TempData[WellKnownTempData.SuccessMessage] = "Wallet settings updated"; - return RedirectToAction(nameof(WalletSettings)); - } - else if (command == "prune") - { - var result = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode).PruneAsync(derivationScheme.AccountDerivation, new PruneRequest(), cancellationToken); - if (result.TotalPruned == 0) - { - TempData[WellKnownTempData.SuccessMessage] = $"The wallet is already pruned"; + return RedirectToAction(nameof(WalletTransactions), new { walletId }); } - else + case "clear" when User.IsInRole(Roles.ServerAdmin): { - TempData[WellKnownTempData.SuccessMessage] = - $"The wallet has been successfully pruned ({result.TotalPruned} transactions have been removed from the history)"; - } - - return RedirectToAction(nameof(WalletSettings)); - } - else if (command == "clear" && User.IsInRole(Roles.ServerAdmin)) - { - if (Version.TryParse(_dashboard.Get(walletId.CryptoCode)?.Status?.Version ?? "0.0.0.0", out var v) && - v < new Version(2, 2, 4)) - { - TempData[WellKnownTempData.ErrorMessage] = $"This version of NBXplorer doesn't support this operation, please upgrade to 2.2.4 or above"; - } - else - { - await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode) + if (Version.TryParse(_dashboard.Get(walletId.CryptoCode)?.Status?.Version ?? "0.0.0.0", out var v) && + v < new Version(2, 2, 4)) + { + TempData[WellKnownTempData.ErrorMessage] = "This version of NBXplorer doesn't support this operation, please upgrade to 2.2.4 or above"; + } + else + { + await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode) .WipeAsync(derivationScheme.AccountDerivation, cancellationToken); - TempData[WellKnownTempData.SuccessMessage] = $"The transactions have been wiped out, to restore your balance, rescan the wallet."; + TempData[WellKnownTempData.SuccessMessage] = "The transactions have been wiped out, to restore your balance, rescan the wallet."; + } + return RedirectToAction(nameof(WalletTransactions), new { walletId }); } - return RedirectToAction(nameof(WalletSettings)); - } - else if (command == "view-seed" && await CanUseHotWallet()) - { - if (await GetSeed(walletId, derivationScheme.Network) != null) - { - var mnemonic = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode) - .GetMetadataAsync(derivationScheme.AccountDerivation, - WellknownMetadataKeys.Mnemonic, cancellationToken); - var recoveryVm = new RecoverySeedBackupViewModel() - { - CryptoCode = walletId.CryptoCode, - Mnemonic = mnemonic, - IsStored = true, - RequireConfirm = false, - ReturnUrl = Url.Action(nameof(WalletSettings), new { walletId }) - }; - return this.RedirectToRecoverySeedBackup(recoveryVm); - } - else - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Severity = StatusMessageModel.StatusSeverity.Error, - Message = "The seed was not found" - }); - } - - return RedirectToAction(nameof(WalletSettings)); - } - else - { - return NotFound(); + default: + return NotFound(); } } @@ -1231,11 +1135,6 @@ namespace BTCPayServer.Controllers public string PaymentLink { get; set; } } - - public class GetInfoResult - { - } - public class SendToAddressResult { [JsonProperty("psbt")] diff --git a/BTCPayServer/Models/StoreViewModels/WalletSettingsViewModel.cs b/BTCPayServer/Models/StoreViewModels/WalletSettingsViewModel.cs index ccb433ad4..3b0853fda 100644 --- a/BTCPayServer/Models/StoreViewModels/WalletSettingsViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/WalletSettingsViewModel.cs @@ -1,10 +1,13 @@ +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using BTCPayServer.Client.Models; +using Newtonsoft.Json; namespace BTCPayServer.Models.StoreViewModels { public class WalletSettingsViewModel : DerivationSchemeViewModel { + public WalletId WalletId { get; set; } public string StoreId { get; set; } public bool IsHotWallet { get; set; } public bool CanUsePayJoin { get; set; } @@ -25,5 +28,30 @@ namespace BTCPayServer.Models.StoreViewModels [Display(Name = "Consider the invoice confirmed when the payment transaction …")] public SpeedPolicy SpeedPolicy { get; set; } + + public string Label { get; set; } + + public string DerivationSchemeInput { get; set; } + [Display(Name = "Is signing key")] + public string SelectedSigningKey { get; set; } + public bool IsMultiSig => AccountKeys.Count > 1; + + public List AccountKeys { get; set; } = new List(); + public bool NBXSeedAvailable { get; set; } + public string StoreName { get; set; } + public string UriScheme { get; set; } + } + + public class WalletSettingsAccountKeyViewModel + { + [JsonProperty("ExtPubKey")] + [Display(Name = "Account key")] + public string AccountKey { get; set; } + [Display(Name = "Master fingerprint")] + [Validation.HDFingerPrintValidator] + public string MasterFingerprint { get; set; } + [Display(Name = "Account key path")] + [Validation.KeyPathValidator] + public string AccountKeyPath { get; set; } } } diff --git a/BTCPayServer/Models/WalletViewModels/WalletSettingsViewModel.cs b/BTCPayServer/Models/WalletViewModels/WalletSettingsViewModel.cs deleted file mode 100644 index 83ae47a6e..000000000 --- a/BTCPayServer/Models/WalletViewModels/WalletSettingsViewModel.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using Newtonsoft.Json; - -namespace BTCPayServer.Models.WalletViewModels -{ - public class WalletSettingsViewModel - { - public string Label { get; set; } - [DisplayName("Derivation scheme")] - public string DerivationScheme { get; set; } - public string DerivationSchemeInput { get; set; } - [Display(Name = "Is signing key")] - public string SelectedSigningKey { get; set; } - public bool IsMultiSig => AccountKeys.Count > 1; - - public List AccountKeys { get; set; } = new List(); - public bool NBXSeedAvailable { get; set; } - public string StoreName { get; set; } - public string UriScheme { get; set; } - } - - public class WalletSettingsAccountKeyViewModel - { - [JsonProperty("ExtPubKey")] - [DisplayName("Account key")] - public string AccountKey { get; set; } - [DisplayName("Master fingerprint")] - [Validation.HDFingerPrintValidator] - public string MasterFingerprint { get; set; } - [DisplayName("Account key path")] - [Validation.KeyPathValidator] - public string AccountKeyPath { get; set; } - } -} diff --git a/BTCPayServer/Services/Labels/LabelFactory.cs b/BTCPayServer/Services/Labels/LabelFactory.cs index e5307f8b2..fab6529c6 100644 --- a/BTCPayServer/Services/Labels/LabelFactory.cs +++ b/BTCPayServer/Services/Labels/LabelFactory.cs @@ -73,7 +73,7 @@ namespace BTCPayServer.Services.Labels : _linkGenerator.AppLink(refLabel.Reference, request.Scheme, request.Host, request.PathBase); break; case "pj-exposed": - coloredLabel.Tooltip = $"This utxo was exposed through a payjoin proposal for an invoice {refInLabel}"; + coloredLabel.Tooltip = $"This UTXO was exposed through a PayJoin proposal for an invoice {refInLabel}"; coloredLabel.Link = string.IsNullOrEmpty(refLabel.Reference) ? null : _linkGenerator.InvoiceLink(refLabel.Reference, request.Scheme, request.Host, request.PathBase); diff --git a/BTCPayServer/Views/Shared/Bitcoin/BitcoinLikeMethodCheckout.cshtml b/BTCPayServer/Views/Shared/Bitcoin/BitcoinLikeMethodCheckout.cshtml index 544be7699..4fff26401 100644 --- a/BTCPayServer/Views/Shared/Bitcoin/BitcoinLikeMethodCheckout.cshtml +++ b/BTCPayServer/Views/Shared/Bitcoin/BitcoinLikeMethodCheckout.cshtml @@ -43,7 +43,7 @@
-
+
diff --git a/BTCPayServer/Views/Stores/LightningSettings.cshtml b/BTCPayServer/Views/Stores/LightningSettings.cshtml index 10e0eddb3..7cb1de178 100644 --- a/BTCPayServer/Views/Stores/LightningSettings.cshtml +++ b/BTCPayServer/Views/Stores/LightningSettings.cshtml @@ -23,7 +23,7 @@ { Connection String - @Model.ConnectionString + @Model.ConnectionString } diff --git a/BTCPayServer/Views/Stores/WalletSettings.cshtml b/BTCPayServer/Views/Stores/WalletSettings.cshtml index 7fb774cbe..4e492c251 100644 --- a/BTCPayServer/Views/Stores/WalletSettings.cshtml +++ b/BTCPayServer/Views/Stores/WalletSettings.cshtml @@ -1,60 +1,125 @@ +@using NBitcoin.DataEncoders +@using Newtonsoft.Json +@using System.Text @model WalletSettingsViewModel @{ Layout = "../Shared/_NavLayout.cshtml"; ViewData.SetActivePageAndTitle(StoreNavPages.PaymentMethods, $"{Model.CryptoCode} Wallet Settings", Context.GetStoreData().StoreName); } + +@section PageHeadContent { + + +} +

@ViewData["Title"]

- - - - - - - - - - - - - - - @if (!string.IsNullOrEmpty(Model.KeyPath)) + + + + + + +
+ + + +
+
+ +
+ + +
+
+ @if (!string.IsNullOrEmpty(Model.DerivationSchemeInput) && Model.DerivationSchemeInput != Model.DerivationScheme) { - - - - +
+ +
+ + +
+
} - -
Type - @(Model.IsHotWallet ? "Hot wallet" : "Watch-only wallet") - - Replace wallet - -
- +
Derivation Scheme@Model.DerivationScheme
Root Fingerprint@Model.RootFingerprint
KeyPath@Model.KeyPath
+ @for (var i = 0; i < Model.AccountKeys.Count; i++) + { +
Account key @i
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ @if (Model.IsMultiSig) + { +
+ + +
+ } + } + +

Payment

-
+ @if (Model.CanUsePayJoin) {
@@ -102,23 +167,39 @@
- +
- +
+ @section PageFootContent { } diff --git a/BTCPayServer/Views/Wallets/WalletSettings.cshtml b/BTCPayServer/Views/Wallets/WalletSettings.cshtml deleted file mode 100644 index 51602463d..000000000 --- a/BTCPayServer/Views/Wallets/WalletSettings.cshtml +++ /dev/null @@ -1,128 +0,0 @@ -@using Newtonsoft.Json -@using System.Text -@using NBitcoin.DataEncoders -@model BTCPayServer.Models.WalletViewModels.WalletSettingsViewModel -@{ - Layout = "../Shared/_NavLayout.cshtml"; - ViewData.SetActivePageAndTitle(WalletsNavPages.Settings, "Wallet settings", Context.GetStoreData().StoreName); -} - -

@ViewData["PageTitle"]

- -
-
-
- - -
- - - -
-
- - - -
- @if (!string.IsNullOrEmpty(Model.DerivationSchemeInput) && Model.DerivationSchemeInput != Model.DerivationScheme) - { -
- - - -
- } - @for (var i = 0; i < Model.AccountKeys.Count; i++) - { -
-

Account key @i

- -
-
- - - -
-
-
- - - -
-
- - - -
-
- @if (Model.IsMultiSig) - { -
- - -
- } - } -
- - -
-
-
-
- - -@section PageHeadContent { - - - -} - -@section PageFootContent { - -} - diff --git a/BTCPayServer/Views/Wallets/WalletTransactions.cshtml b/BTCPayServer/Views/Wallets/WalletTransactions.cshtml index 669c7aab5..74a0a86f4 100644 --- a/BTCPayServer/Views/Wallets/WalletTransactions.cshtml +++ b/BTCPayServer/Views/Wallets/WalletTransactions.cshtml @@ -7,12 +7,12 @@ @section PageHeadContent {