mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 17:36:59 +01:00
Wallet settings merge (#3072)
* Wallet settings merge Merges both wallet settings screen from the wallets and the store section. Closes #2626. * Improve wallet transactions view * Remove unnecessary row/col construct
This commit is contained in:
parent
be7cef29d8
commit
28694859c9
16 changed files with 641 additions and 592 deletions
|
@ -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";
|
||||
|
@ -302,9 +302,7 @@ namespace BTCPayServer.Tests
|
|||
.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 });
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<BTCPayNetwork>("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<BTCPayNetwork>(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<BTCPayNetwork>(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();
|
||||
|
|
|
@ -151,6 +151,15 @@ namespace BTCPayServer.Tests
|
|||
storeController.UpdateWalletSettings(walletSettings).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task ModifyOnchainPaymentSettings(Action<WalletSettingsViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<StoresController>();
|
||||
var response = await storeController.WalletSettings(StoreId, "BTC");
|
||||
WalletSettingsViewModel walletSettings = (WalletSettingsViewModel)((ViewResult)response).Model;
|
||||
modify(walletSettings);
|
||||
storeController.UpdatePaymentSettings(walletSettings).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
||||
{
|
||||
var controller = parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null, IsAdmin);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -383,18 +385,35 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
(bool canUseHotWallet, bool rpcImport) = await CanUseHotWallet();
|
||||
var client = _ExplorerProvider.GetExplorerClient(network);
|
||||
|
||||
var vm = new WalletSettingsViewModel
|
||||
{
|
||||
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<string>(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()),
|
||||
IsHotWallet = derivation.IsHotWallet,
|
||||
PayJoinEnabled = storeBlob.PayJoinEnabled,
|
||||
MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes,
|
||||
SpeedPolicy = store.SpeedPolicy,
|
||||
|
@ -405,7 +424,9 @@ namespace BTCPayServer.Controllers
|
|||
CanUsePayJoin = canUseHotWallet && store
|
||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet)
|
||||
.Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet),
|
||||
StoreName = store.StoreName,
|
||||
|
||||
};
|
||||
|
||||
ViewData["ReplaceDescription"] = WalletReplaceWarning(derivation.IsHotWallet);
|
||||
|
@ -414,38 +435,140 @@ namespace BTCPayServer.Controllers
|
|||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/onchain/{cryptoCode}/settings")]
|
||||
[HttpPost("{storeId}/onchain/{cryptoCode}/settings/wallet")]
|
||||
public async Task<IActionResult> 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<IActionResult> 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<DerivationSchemeSettings>()
|
||||
.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 <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>."
|
||||
Html = $"The payment settings were updated successfully. However, PayJoin will not work for {string.Join(", ", problematicPayjoinEnabledMethods)} until you configure them to be a <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>."
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(WalletSettings), new
|
||||
return RedirectToAction(nameof(WalletSettings), new { vm.StoreId, vm.CryptoCode });
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/onchain/{cryptoCode}/seed")]
|
||||
public async Task<IActionResult> WalletSeed(string storeId, string cryptoCode, CancellationToken cancellationToken = default)
|
||||
{
|
||||
storeId = vm.StoreId,
|
||||
cryptoCode = vm.CryptoCode
|
||||
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
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<string>(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")]
|
||||
|
@ -633,6 +798,13 @@ namespace BTCPayServer.Controllers
|
|||
return existing;
|
||||
}
|
||||
|
||||
private async Task<string> GetSeed(ExplorerClient client, DerivationSchemeSettings derivation)
|
||||
{
|
||||
return derivation.IsHotWallet &&
|
||||
await client.GetMetadataAsync<string>(derivation.AccountDerivation, WellknownMetadataKeys.MasterHDKey) is string seed &&
|
||||
!string.IsNullOrEmpty(seed) ? seed : null;
|
||||
}
|
||||
|
||||
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
|
||||
{
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
|
|
|
@ -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<ApplicationUser> _userManager;
|
||||
private readonly JsonSerializerSettings _serializerSettings;
|
||||
|
@ -63,8 +63,6 @@ namespace BTCPayServer.Controllers
|
|||
private readonly PullPaymentHostedService _pullPaymentService;
|
||||
private readonly IEnumerable<IPayoutHandler> _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<IPayoutHandler> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1080,82 +1071,24 @@ namespace BTCPayServer.Controllers
|
|||
return _userManager.GetUserId(User);
|
||||
}
|
||||
|
||||
[Route("{walletId}/settings")]
|
||||
public async Task<IActionResult> WalletSettings(
|
||||
[HttpPost("{walletId}/actions")]
|
||||
public async Task<IActionResult> WalletActions(
|
||||
[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<string>(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<IActionResult> WalletSettings(
|
||||
[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++)
|
||||
{
|
||||
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 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")
|
||||
case "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";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "The wallet is already pruned";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1163,53 +1096,24 @@ namespace BTCPayServer.Controllers
|
|||
$"The wallet has been successfully pruned ({result.TotalPruned} transactions have been removed from the history)";
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(WalletSettings));
|
||||
return RedirectToAction(nameof(WalletTransactions), new { walletId });
|
||||
}
|
||||
else if (command == "clear" && User.IsInRole(Roles.ServerAdmin))
|
||||
case "clear" when 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";
|
||||
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(WalletSettings));
|
||||
return RedirectToAction(nameof(WalletTransactions), new { walletId });
|
||||
}
|
||||
else if (command == "view-seed" && await CanUseHotWallet())
|
||||
{
|
||||
if (await GetSeed(walletId, derivationScheme.Network) != null)
|
||||
{
|
||||
var mnemonic = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
||||
.GetMetadataAsync<string>(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
|
||||
{
|
||||
default:
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
@ -1231,11 +1135,6 @@ namespace BTCPayServer.Controllers
|
|||
public string PaymentLink { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class GetInfoResult
|
||||
{
|
||||
}
|
||||
|
||||
public class SendToAddressResult
|
||||
{
|
||||
[JsonProperty("psbt")]
|
||||
|
|
|
@ -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<WalletSettingsAccountKeyViewModel> AccountKeys { get; set; } = new List<WalletSettingsAccountKeyViewModel>();
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<WalletSettingsAccountKeyViewModel> AccountKeys { get; set; } = new List<WalletSettingsAccountKeyViewModel>();
|
||||
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; }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="separatorGem" v-if="srvModel.invoiceBitcoinUrl"></div>
|
||||
<div class="copySectionBox" v-if="srvModel.invoiceBitcoinUrl" :title="$t(hasPayjoin? 'BIP21 payment link' : 'BIP21 payment link with payjoin support') " >
|
||||
<div class="copySectionBox" v-if="srvModel.invoiceBitcoinUrl" :title="$t(hasPayjoin? 'BIP21 payment link' : 'BIP21 payment link with PayJoin support') " >
|
||||
<label>{{$t("Payment link")}}</label>
|
||||
<div class="inputWithIcon _copyInput">
|
||||
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.invoiceBitcoinUrl" readonly="readonly"/>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
{
|
||||
<tr>
|
||||
<th>Connection String</th>
|
||||
<td>@Model.ConnectionString</td>
|
||||
<td class="text-break">@Model.ConnectionString</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
|
|
|
@ -1,21 +1,42 @@
|
|||
@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 {
|
||||
<bundle name="wwwroot/bundles/camera-bundle.min.js"></bundle>
|
||||
<link href="~/vendor/vue-qrcode-reader/vue-qrcode-reader.css" rel="stylesheet" asp-append-version="true"/>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-10 col-xl-9">
|
||||
<div class="mb-5">
|
||||
<h4 class="mb-3">@ViewData["Title"]</h4>
|
||||
<table class="table table-borderless table-responsive-md">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<td class="d-flex flex-wrap align-items-center">
|
||||
<div class="mb-3 d-flex align-items-center">
|
||||
<span class="me-2">Type:</span>
|
||||
<span title="@Model.Source" data-bs-toggle="tooltip" class="me-3">@(Model.IsHotWallet ? "Hot wallet" : "Watch-only wallet")</span>
|
||||
|
||||
<form method="get" asp-action="DeleteWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" class="d-inline">
|
||||
<div class="dropdown">
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle py-1 px-3" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Actions
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="ActionsDropdownToggle">
|
||||
@if (Model.NBXSeedAvailable)
|
||||
{
|
||||
<a asp-action="WalletSeed" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" class="dropdown-item" id="ViewSeed">View seed</a>
|
||||
}
|
||||
@if (Model.UriScheme == "bitcoin")
|
||||
{
|
||||
<button type="button" class="dropdown-item" id="RegisterWallet" data-store="@Model.StoreName" data-scheme="@Model.UriScheme" data-url="@Url.Action("WalletSend", "Wallets", new {walletId = Model.WalletId, bip21 = "%s"})" hidden>Register wallet for payment links</button>
|
||||
}
|
||||
<a asp-controller="Stores" asp-action="ReplaceWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode"
|
||||
id="ChangeWalletLink"
|
||||
class="text-secondary me-3"
|
||||
class="dropdown-item"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#ConfirmModal"
|
||||
data-title="Replace @Model.CryptoCode wallet"
|
||||
|
@ -24,37 +45,81 @@
|
|||
data-confirm-input="REPLACE">
|
||||
Replace wallet
|
||||
</a>
|
||||
<form method="get" asp-controller="Stores" asp-action="DeleteWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" class="d-inline">
|
||||
<button type="submit" class="btn btn-link text-secondary text-start p-0" id="Delete"
|
||||
<button type="submit"
|
||||
id="Delete"
|
||||
class="dropdown-item"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#ConfirmModal"
|
||||
data-title="Remove @Model.CryptoCode wallet"
|
||||
data-description="@ViewData["RemoveDescription"]"
|
||||
data-confirm="Remove"
|
||||
data-confirm-input="REMOVE">Remove wallet</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="w-150px">Derivation Scheme</th>
|
||||
<td class="text-break">@Model.DerivationScheme</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Root Fingerprint</th>
|
||||
<td>@Model.RootFingerprint</td>
|
||||
</tr>
|
||||
@if (!string.IsNullOrEmpty(Model.KeyPath))
|
||||
</div>
|
||||
|
||||
<form method="post" asp-action="UpdateWalletSettings" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode">
|
||||
<div class="form-group">
|
||||
<label asp-for="Label" class="form-label"></label>
|
||||
<input asp-for="Label" class="form-control" style="max-width:24em;" />
|
||||
<span asp-validation-for="Label" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="DerivationScheme" class="form-label"></label>
|
||||
<div class="input-group" data-clipboard="@Model.DerivationScheme">
|
||||
<input asp-for="DerivationScheme" class="form-control" style="cursor:copy" readonly />
|
||||
<button type="button" class="input-group-text btn btn-outline-secondary" data-clipboard-confirm style="min-width:8em;">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(Model.DerivationSchemeInput) && Model.DerivationSchemeInput != Model.DerivationScheme)
|
||||
{
|
||||
<tr>
|
||||
<th>KeyPath</th>
|
||||
<td>@Model.KeyPath</td>
|
||||
</tr>
|
||||
<div class="form-group">
|
||||
<label asp-for="DerivationSchemeInput" class="form-label"></label>
|
||||
<div class="input-group" data-clipboard="@Model.DerivationSchemeInput">
|
||||
<input asp-for="DerivationSchemeInput" class="form-control" style="cursor:copy" readonly/>
|
||||
<button type="button" class="input-group-text btn btn-outline-secondary" data-clipboard-confirm style="min-width:8em;">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@for (var i = 0; i < Model.AccountKeys.Count; i++)
|
||||
{
|
||||
<h5 class="mt-5">Account key @i</h5>
|
||||
<div class="form-group">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<label asp-for="@Model.AccountKeys[i].AccountKey" class="form-label"></label>
|
||||
<button type="button" class="d-inline-block ms-2 btn text-secondary btn-link p-0 mb-2" data-account-key="@i" title="">
|
||||
<span class="fa fa-qrcode"></span> Show export QR
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group" data-clipboard="@Model.AccountKeys[i].AccountKey">
|
||||
<input asp-for="@Model.AccountKeys[i].AccountKey" class="form-control" style="cursor:copy" readonly/>
|
||||
<button type="button" class="input-group-text btn btn-outline-secondary" data-clipboard-confirm style="min-width:8em;">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-auto">
|
||||
<label asp-for="@Model.AccountKeys[i].MasterFingerprint" class="form-label"></label>
|
||||
<input asp-for="@Model.AccountKeys[i].MasterFingerprint" class="form-control" style="max-width:16ch;" />
|
||||
</div>
|
||||
<div class="form-group col-auto">
|
||||
<label asp-for="@Model.AccountKeys[i].AccountKeyPath" class="form-label"></label>
|
||||
<input asp-for="@Model.AccountKeys[i].AccountKeyPath" class="form-control" style="max-width:16ch;" />
|
||||
</div>
|
||||
</div>
|
||||
@if (Model.IsMultiSig)
|
||||
{
|
||||
<div class="form-check">
|
||||
<input asp-for="SelectedSigningKey" class="form-check-input" type="radio" value="@Model.AccountKeys[i].AccountKey"/>
|
||||
<label asp-for="SelectedSigningKey" class="form-check-label"></label>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<button type="submit" class="btn btn-primary" id="SaveWalletSettings">Save Wallet Settings</button>
|
||||
</form>
|
||||
|
||||
<h4 class="mt-5 mb-3">Payment</h4>
|
||||
<form method="post" asp-action="UpdateWalletSettings" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode">
|
||||
<form method="post" asp-action="UpdatePaymentSettings" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode">
|
||||
@if (Model.CanUsePayJoin)
|
||||
{
|
||||
<div class="form-group">
|
||||
|
@ -102,23 +167,39 @@
|
|||
</div>
|
||||
<div class="form-group mt-2 mb-4">
|
||||
<label asp-for="RecommendedFeeBlockTarget" class="form-label"></label>
|
||||
<input asp-for="RecommendedFeeBlockTarget" class="form-control" style="width:8ch" min="1" />
|
||||
<input asp-for="RecommendedFeeBlockTarget" class="form-control" min="1" style="width:8ch" />
|
||||
<span asp-validation-for="RecommendedFeeBlockTarget" class="text-danger"></span>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-primary" value="Save" id="Save">Save</button>
|
||||
<button type="submit" class="btn btn-primary" id="SavePaymentSettings">Save Payment Settings</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel($"{Model.CryptoCode} wallet", "Change", "Update"))" />
|
||||
<partial name="ShowQR"/>
|
||||
|
||||
@section PageFootContent {
|
||||
<script>
|
||||
const deleteButton = document.getElementById('Delete')
|
||||
deleteButton.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
});
|
||||
const wallets = @Safe.Json(Model.AccountKeys.Select(model => Encoders.Hex.EncodeData(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model, Formatting.None)))))
|
||||
const qrApp = initQRShow("Wallet QR", "", "scan-qr-modal")
|
||||
|
||||
delegate('click', '#Delete', event => { event.preventDefault() })
|
||||
|
||||
delegate('click', 'button[data-account-key]', event => {
|
||||
const { accountKey } = event.target.dataset
|
||||
qrApp.data = wallets[parseInt(accountKey)]
|
||||
$("#scan-qr-modal").modal("show")
|
||||
})
|
||||
|
||||
if (navigator.registerProtocolHandler) {
|
||||
document.getElementById('RegisterWallet').removeAttribute('hidden');
|
||||
delegate('click', '#RegisterWallet', event => {
|
||||
const { store, scheme, url } = event.target.dataset
|
||||
const uri = decodeURIComponent(url)
|
||||
navigator.registerProtocolHandler(scheme, uri, `BTCPay Wallet: ${store}`)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<partial name="_ValidationScriptsPartial"/>
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
<h4 class="mb-3">@ViewData["PageTitle"]</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-lg-6">
|
||||
<form method="post" asp-action="WalletSettings">
|
||||
<input type="hidden" asp-for="StoreName"/>
|
||||
<input type="hidden" asp-for="UriScheme"/>
|
||||
<div class="form-group">
|
||||
<label asp-for="Label" class="form-label"></label>
|
||||
<input asp-for="Label" class="form-control"/>
|
||||
<span asp-validation-for="Label" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="DerivationScheme" class="form-label"></label>
|
||||
<input asp-for="DerivationScheme" class="form-control" readonly/>
|
||||
<span asp-validation-for="DerivationScheme" class="text-danger"></span>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(Model.DerivationSchemeInput) && Model.DerivationSchemeInput != Model.DerivationScheme)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="DerivationSchemeInput" class="form-label"></label>
|
||||
<input asp-for="DerivationSchemeInput" class="form-control" readonly/>
|
||||
<span asp-validation-for="DerivationSchemeInput" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
@for (var i = 0; i < Model.AccountKeys.Count; i++)
|
||||
{
|
||||
<div class="d-flex mt-5 mb-3">
|
||||
<h4 class="mb-0">Account key @i</h4>
|
||||
<button type="button" class="btn btn-link only-for-js mb-2 fa fa-qrcode text-decoration-none" data-wallet="@i" title="Show QR"></button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="@Model.AccountKeys[i].AccountKey" class="form-label"></label>
|
||||
<input asp-for="@Model.AccountKeys[i].AccountKey" class="form-control" readonly/>
|
||||
<span asp-validation-for="@Model.AccountKeys[i].AccountKey" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-auto">
|
||||
<label asp-for="@Model.AccountKeys[i].MasterFingerprint" class="form-label"></label>
|
||||
<input asp-for="@Model.AccountKeys[i].MasterFingerprint" class="form-control" style="max-width:16ch;"/>
|
||||
<span asp-validation-for="@Model.AccountKeys[i].MasterFingerprint" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group col-auto">
|
||||
<label asp-for="@Model.AccountKeys[i].AccountKeyPath" class="form-label"></label>
|
||||
<input asp-for="@Model.AccountKeys[i].AccountKeyPath" class="form-control" style="max-width:16ch;"/>
|
||||
<span asp-validation-for="@Model.AccountKeys[i].AccountKeyPath" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
@if (Model.IsMultiSig)
|
||||
{
|
||||
<div class="form-check">
|
||||
<input asp-for="SelectedSigningKey" class="form-check-input" type="radio" value="@Model.AccountKeys[i].AccountKey"/>
|
||||
<label asp-for="SelectedSigningKey" class="form-check-label"></label>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div class="form-group d-flex mt-3">
|
||||
<button name="command" type="submit" class="btn btn-primary me-2" value="save">Save</button>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="OtherActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Other actions...
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="OtherActionsDropdownToggle">
|
||||
<button name="command" type="submit" class="dropdown-item" value="prune">Prune old transactions from history</button>
|
||||
@if (User.IsInRole(Roles.ServerAdmin))
|
||||
{
|
||||
<button name="command" type="submit" class="dropdown-item" value="clear">Clear all transactions from history</button>
|
||||
}
|
||||
@if (Model.NBXSeedAvailable)
|
||||
{
|
||||
<button name="command" type="submit" class="dropdown-item" value="view-seed">View seed</button>
|
||||
}
|
||||
@if (Model.UriScheme == "bitcoin")
|
||||
{
|
||||
<button type="button" class="dropdown-item register-wallet" data-storename="@Model.StoreName" data-scheme="@Model.UriScheme" data-url="@Url.Action("WalletSend", "Wallets", new {walletId = Context.GetRouteValue("walletId"), bip21 = "%s"})">Open this bitcoin wallet on payment links</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="ShowQR"/>
|
||||
@section PageHeadContent {
|
||||
<bundle name="wwwroot/bundles/camera-bundle.min.js"></bundle>
|
||||
<link href="~/vendor/vue-qrcode-reader/vue-qrcode-reader.css" rel="stylesheet" asp-append-version="true"/>
|
||||
<style>
|
||||
.register-wallet{ display: none; }
|
||||
</style>
|
||||
}
|
||||
|
||||
@section PageFootContent {
|
||||
<script>
|
||||
var wallets = @Safe.Json(Model.AccountKeys.Select(model => Encoders.Hex.EncodeData(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model, Formatting.None)))));
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
var qrApp = initQRShow("Wallet QR", "", "scan-qr-modal");
|
||||
$("button[data-wallet]").on("click", function (){
|
||||
var data = $(this).data("wallet");
|
||||
var wallet = wallets[parseInt(data)];
|
||||
qrApp.data = wallet;
|
||||
$("#scan-qr-modal").modal("show");
|
||||
});
|
||||
|
||||
|
||||
if(navigator.registerProtocolHandler){
|
||||
$(".register-wallet")
|
||||
.show()
|
||||
.on("click", function(){
|
||||
var store = $(this).data("storename");
|
||||
var scheme = $(this).data("scheme");
|
||||
var url = decodeURIComponent($(this).data("url"));
|
||||
navigator.registerProtocolHandler(scheme, url, "BTCPay Wallet -" + store);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
|
@ -7,12 +7,12 @@
|
|||
@section PageHeadContent {
|
||||
<style>
|
||||
.smMaxWidth {
|
||||
max-width: 200px;
|
||||
max-width: 125px;
|
||||
}
|
||||
|
||||
@@media (min-width: 768px) {
|
||||
@@media (min-width: 990px) {
|
||||
.smMaxWidth {
|
||||
max-width: 400px;
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,8 +71,26 @@
|
|||
</script>
|
||||
}
|
||||
|
||||
<h4 class="mb-3">@ViewData["PageTitle"]</h4>
|
||||
<p>
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<h4 class="mb-0">@ViewData["PageTitle"]</h4>
|
||||
<form method="post" asp-action="WalletActions" asp-route-walletId="@Context.GetRouteValue("walletId")">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle py-1 px-3" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Actions
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="ActionsDropdownToggle">
|
||||
<a asp-action="WalletRescan" asp-route-walletId="@Context.GetRouteValue("walletId")" class="dropdown-item">Rescan wallet for missing transactions</a>
|
||||
<button name="command" type="submit" class="dropdown-item" value="prune">Prune old transactions from history</button>
|
||||
@if (User.IsInRole(Roles.ServerAdmin))
|
||||
{
|
||||
<button name="command" type="submit" class="dropdown-item" value="clear">Clear all transactions from history</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<p class="mb-0">
|
||||
If BTCPay Server shows you an invalid balance, <a asp-action="WalletRescan" asp-route-walletId="@Context.GetRouteValue("walletId")">rescan your wallet</a>.
|
||||
<br/>
|
||||
If some transactions appear in BTCPay Server, but are missing in another wallet, <a href="https://docs.btcpayserver.org/FAQ/Wallet/#missing-payments-in-my-software-or-hardware-wallet" rel="noreferrer noopener">follow these instructions</a>.
|
||||
|
@ -94,9 +112,8 @@
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-hover table-responsive-md">
|
||||
<div class="table-responsive-md">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th style="min-width: 90px;" class="col-md-auto">
|
||||
|
@ -226,9 +243,8 @@
|
|||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<vc:pager view-model="Model"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace BTCPayServer.Views.Wallets
|
|||
Transactions,
|
||||
Rescan,
|
||||
PSBT,
|
||||
Settings,
|
||||
Receive
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
@if (!network.ReadonlyWallet)
|
||||
{
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.PSBT)" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")">PSBT</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Settings)" asp-action="WalletSettings" asp-route-walletId="@Context.GetRouteValue("walletId")" id="WalletSettings">Settings</a>
|
||||
}
|
||||
<a class="nav-link" asp-controller="Stores" asp-action="WalletSettings" asp-route-storeId="@wallet.StoreId" asp-route-cryptoCode="@wallet.CryptoCode" id="WalletSettings">Settings</a>
|
||||
<vc:ui-extension-point location="wallet-nav" model="@Model" />
|
||||
</nav>
|
||||
|
|
Loading…
Add table
Reference in a new issue