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();
|
var receiverUser = tester.NewAccount();
|
||||||
receiverUser.GrantAccess(true);
|
receiverUser.GrantAccess(true);
|
||||||
receiverUser.RegisterDerivationScheme("BTC", receiverAddressType, 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);
|
var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);
|
||||||
|
|
||||||
string errorCode = receiverAddressType == senderAddressType ? null : "unavailable|any UTXO available";
|
string errorCode = receiverAddressType == senderAddressType ? null : "unavailable|any UTXO available";
|
||||||
|
@ -302,9 +302,7 @@ namespace BTCPayServer.Tests
|
||||||
.GetAttribute("href");
|
.GetAttribute("href");
|
||||||
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21);
|
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21);
|
||||||
|
|
||||||
s.GoToHome();
|
s.GoToWalletSettings(receiver.storeId, cryptoCode);
|
||||||
s.GoToStore(receiver.storeId);
|
|
||||||
s.Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
|
|
||||||
Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
|
Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
|
||||||
|
|
||||||
var sender = s.CreateNewStore();
|
var sender = s.CreateNewStore();
|
||||||
|
@ -573,7 +571,7 @@ namespace BTCPayServer.Tests
|
||||||
address = (await nbx.GetUnusedAsync(bob.DerivationScheme, DerivationFeature.Deposit)).Address;
|
address = (await nbx.GetUnusedAsync(bob.DerivationScheme, DerivationFeature.Deposit)).Address;
|
||||||
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.1m));
|
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.1m));
|
||||||
await notifications.NextEventAsync();
|
await notifications.NextEventAsync();
|
||||||
await bob.ModifyWalletSettings(p => p.PayJoinEnabled = true);
|
await bob.ModifyOnchainPaymentSettings(p => p.PayJoinEnabled = true);
|
||||||
var invoice = bob.BitPay.CreateInvoice(
|
var invoice = bob.BitPay.CreateInvoice(
|
||||||
new Invoice { Price = 0.1m, Currency = "BTC", FullNotifications = true });
|
new Invoice { Price = 0.1m, Currency = "BTC", FullNotifications = true });
|
||||||
var invoiceBIP21 = new BitcoinUrlBuilder(invoice.CryptoInfo.First().PaymentUrls.BIP21,
|
var invoiceBIP21 = new BitcoinUrlBuilder(invoice.CryptoInfo.First().PaymentUrls.BIP21,
|
||||||
|
@ -662,7 +660,7 @@ namespace BTCPayServer.Tests
|
||||||
var receiverUser = tester.NewAccount();
|
var receiverUser = tester.NewAccount();
|
||||||
receiverUser.GrantAccess(true);
|
receiverUser.GrantAccess(true);
|
||||||
receiverUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, 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);
|
var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);
|
||||||
string lastInvoiceId = null;
|
string lastInvoiceId = null;
|
||||||
|
|
||||||
|
@ -859,7 +857,7 @@ retry:
|
||||||
receiverUser.GrantAccess(true);
|
receiverUser.GrantAccess(true);
|
||||||
receiverUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, 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
|
// payjoin is enabled, with a segwit wallet, and the keys are available in nbxplorer
|
||||||
invoice = receiverUser.BitPay.CreateInvoice(
|
invoice = receiverUser.BitPay.CreateInvoice(
|
||||||
new Invoice() { Price = 0.02m, Currency = "BTC", FullNotifications = true });
|
new Invoice() { Price = 0.02m, Currency = "BTC", FullNotifications = true });
|
||||||
|
|
|
@ -154,7 +154,8 @@ namespace BTCPayServer.Tests
|
||||||
// Replace previous wallet case
|
// Replace previous wallet case
|
||||||
if (Driver.PageSource.Contains("id=\"ChangeWalletLink\""))
|
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.WaitForElement(By.Id("ConfirmInput")).SendKeys("REPLACE");
|
||||||
Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
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)
|
public void GoToInvoiceCheckout(string invoiceId)
|
||||||
{
|
{
|
||||||
Driver.FindElement(By.Id("Invoices")).Click();
|
Driver.FindElement(By.Id("Invoices")).Click();
|
||||||
|
|
|
@ -811,10 +811,10 @@ namespace BTCPayServer.Tests
|
||||||
s.RegisterNewUser(true);
|
s.RegisterNewUser(true);
|
||||||
foreach (var isHotwallet in new[] { false, true })
|
foreach (var isHotwallet in new[] { false, true })
|
||||||
{
|
{
|
||||||
var (storeName, storeId) = s.CreateNewStore();
|
var cryptoCode = "BTC";
|
||||||
s.GenerateWallet(privkeys: isHotwallet,
|
var (_, storeId) = s.CreateNewStore();
|
||||||
seed: "melody lizard phrase voice unique car opinion merge degree evil swift cargo");
|
s.GenerateWallet(cryptoCode, "melody lizard phrase voice unique car opinion merge degree evil swift cargo", privkeys: isHotwallet);
|
||||||
s.GoToWallet(s.WalletId, WalletsNavPages.Settings);
|
s.GoToWalletSettings(storeId, cryptoCode);
|
||||||
if (isHotwallet)
|
if (isHotwallet)
|
||||||
Assert.Contains("View seed", s.Driver.PageSource);
|
Assert.Contains("View seed", s.Driver.PageSource);
|
||||||
else
|
else
|
||||||
|
@ -831,10 +831,11 @@ namespace BTCPayServer.Tests
|
||||||
await s.StartAsync();
|
await s.StartAsync();
|
||||||
s.RegisterNewUser(true);
|
s.RegisterNewUser(true);
|
||||||
var (storeName, storeId) = s.CreateNewStore();
|
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',
|
// 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
|
// 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
|
//let's test quickly the receive wallet page
|
||||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
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
|
//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.GoToStore(storeId);
|
||||||
s.GenerateWallet("BTC", "", true);
|
s.GenerateWallet(cryptoCode, "", true);
|
||||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||||
s.Driver.FindElement(By.Id("WalletReceive")).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));
|
await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||||
Assert.True(result.IsWatchOnly);
|
Assert.True(result.IsWatchOnly);
|
||||||
s.GoToStore(storeId);
|
s.GoToStore(storeId);
|
||||||
var mnemonic = s.GenerateWallet("BTC", "", true, true);
|
var mnemonic = s.GenerateWallet(cryptoCode, "", true, true);
|
||||||
|
|
||||||
//lets import and save private keys
|
//lets import and save private keys
|
||||||
var root = mnemonic.DeriveExtKey();
|
var root = mnemonic.DeriveExtKey();
|
||||||
|
@ -906,23 +907,22 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||||
|
|
||||||
s.ClickOnAllSideMenus();
|
s.ClickOnAllSideMenus();
|
||||||
|
|
||||||
// Make sure wallet info is correct
|
// Make sure wallet info is correct
|
||||||
s.Driver.FindElement(By.Id("WalletSettings")).Click();
|
s.GoToWalletSettings(storeId, cryptoCode);
|
||||||
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(),
|
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(),
|
||||||
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
||||||
Assert.Contains("m/84'/1'/0'",
|
Assert.Contains("m/84'/1'/0'",
|
||||||
s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
|
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!
|
// Make sure we can rescan, because we are admin!
|
||||||
s.Driver.FindElement(By.Id("WalletRescan")).Click();
|
s.Driver.FindElement(By.Id("WalletRescan")).Click();
|
||||||
Assert.Contains("The batch size make sure", s.Driver.PageSource);
|
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
|
// Check the tx sent earlier arrived
|
||||||
s.Driver.FindElement(By.Id("WalletTransactions")).Click();
|
s.Driver.FindElement(By.Id("WalletTransactions")).Click();
|
||||||
|
|
||||||
|
@ -971,10 +971,10 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Equal(parsedBip21.Address.ToString(),
|
Assert.Equal(parsedBip21.Address.ToString(),
|
||||||
s.Driver.FindElement(By.Id("Outputs_0__DestinationAddress")).GetAttribute("value"));
|
s.Driver.FindElement(By.Id("Outputs_0__DestinationAddress")).GetAttribute("value"));
|
||||||
|
|
||||||
s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings);
|
s.GoToWalletSettings(storeId, cryptoCode);
|
||||||
var walletUrl = s.Driver.Url;
|
var settingsUrl = s.Driver.Url;
|
||||||
s.Driver.FindElement(By.Id("OtherActionsDropdownToggle")).Click();
|
s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click();
|
||||||
s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();
|
s.Driver.FindElement(By.Id("ViewSeed")).Click();
|
||||||
|
|
||||||
// Seed backup page
|
// Seed backup page
|
||||||
var recoveryPhrase = s.Driver.FindElements(By.Id("RecoveryPhrase")).First()
|
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
|
// No confirmation, just a link to return to the wallet
|
||||||
Assert.Empty(s.Driver.FindElements(By.Id("confirm")));
|
Assert.Empty(s.Driver.FindElements(By.Id("confirm")));
|
||||||
s.Driver.FindElement(By.Id("proceed")).Click();
|
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();
|
await s.StartAsync();
|
||||||
s.RegisterNewUser(true);
|
s.RegisterNewUser(true);
|
||||||
var (_, storeId) = s.CreateNewStore();
|
(string _, string storeId) = s.CreateNewStore();
|
||||||
var mnemonic = s.GenerateWallet("BTC",
|
var cryptoCode = "BTC";
|
||||||
"click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel");
|
var mnemonic = s.GenerateWallet(cryptoCode, "click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel");
|
||||||
|
|
||||||
// Make sure wallet info is correct
|
// 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(),
|
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(),
|
||||||
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
||||||
Assert.Contains("m/84'/1'/0'",
|
Assert.Contains("m/84'/1'/0'",
|
||||||
|
@ -1267,11 +1267,12 @@ namespace BTCPayServer.Tests
|
||||||
await s.Server.EnsureChannelsSetup();
|
await s.Server.EnsureChannelsSetup();
|
||||||
|
|
||||||
s.RegisterNewUser(true);
|
s.RegisterNewUser(true);
|
||||||
var store = s.CreateNewStore();
|
var cryptoCode = "BTC";
|
||||||
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
|
(_, string storeId) = s.CreateNewStore();
|
||||||
s.GoToStore(store.storeId);
|
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork;
|
||||||
s.AddLightningNode("BTC", LightningConnectionType.CLightning, false);
|
s.GoToStore(storeId);
|
||||||
s.Driver.FindElement(By.Id($"Modify-LightningBTC")).Click();
|
s.AddLightningNode(cryptoCode, LightningConnectionType.CLightning, false);
|
||||||
|
s.GoToLightningSettings(storeId, cryptoCode);
|
||||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||||
s.GoToApps();
|
s.GoToApps();
|
||||||
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
|
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
|
||||||
|
@ -1312,11 +1313,11 @@ namespace BTCPayServer.Tests
|
||||||
new[] { s.Server.MerchantLightningD },
|
new[] { s.Server.MerchantLightningD },
|
||||||
new[] { s.Server.MerchantLnd.Client });
|
new[] { s.Server.MerchantLnd.Client });
|
||||||
s.RegisterNewUser(true);
|
s.RegisterNewUser(true);
|
||||||
var store = s.CreateNewStore();
|
(string storeName, string storeId) = s.CreateNewStore();
|
||||||
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork;
|
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork;
|
||||||
s.GoToStore(store.storeId);
|
s.GoToStore(storeId);
|
||||||
s.AddLightningNode(cryptoCode, LightningConnectionType.CLightning);
|
s.AddLightningNode(cryptoCode, LightningConnectionType.CLightning);
|
||||||
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
s.GoToLightningSettings(storeId, cryptoCode);
|
||||||
// LNURL is false by default
|
// LNURL is false by default
|
||||||
Assert.False(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
|
Assert.False(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
|
||||||
// LNURL settings are not expanded when LNURL is disabled
|
// 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);
|
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||||
|
|
||||||
// Topup Invoice test
|
// Topup Invoice test
|
||||||
var i = s.CreateInvoice(store.storeName, null, cryptoCode);
|
var i = s.CreateInvoice(storeName, null, cryptoCode);
|
||||||
s.GoToInvoiceCheckout(i);
|
s.GoToInvoiceCheckout(i);
|
||||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||||
var lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
var lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||||
|
@ -1359,7 +1360,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
// Standard invoice test
|
// Standard invoice test
|
||||||
s.GoToHome();
|
s.GoToHome();
|
||||||
i = s.CreateInvoice(store.storeName, 0.0000001m, cryptoCode);
|
i = s.CreateInvoice(storeName, 0.0000001m, cryptoCode);
|
||||||
s.GoToInvoiceCheckout(i);
|
s.GoToInvoiceCheckout(i);
|
||||||
s.Driver.FindElement(By.ClassName("payment__currencies")).Click();
|
s.Driver.FindElement(By.ClassName("payment__currencies")).Click();
|
||||||
// BOLT11 is also available for standard invoices
|
// BOLT11 is also available for standard invoices
|
||||||
|
@ -1404,12 +1405,12 @@ namespace BTCPayServer.Tests
|
||||||
s.Driver.FindElement(By.Id("save")).Click();
|
s.Driver.FindElement(By.Id("save")).Click();
|
||||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
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.GoToInvoiceCheckout(i);
|
||||||
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
||||||
|
|
||||||
s.GoToHome();
|
s.GoToHome();
|
||||||
i = s.CreateInvoice(store.storeName, null, cryptoCode);
|
i = s.CreateInvoice(storeName, null, cryptoCode);
|
||||||
s.GoToInvoiceCheckout(i);
|
s.GoToInvoiceCheckout(i);
|
||||||
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
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("DisableBolt11PaymentMethod")).Selected);
|
||||||
Assert.False(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
|
Assert.False(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
|
||||||
Assert.False(s.Driver.FindElement(By.Id("LNURLBech32Mode")).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.GoToInvoiceCheckout(i);
|
||||||
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
||||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||||
|
@ -1502,29 +1503,27 @@ namespace BTCPayServer.Tests
|
||||||
using var s = SeleniumTester.Create();
|
using var s = SeleniumTester.Create();
|
||||||
s.Server.ActivateLightning();
|
s.Server.ActivateLightning();
|
||||||
await s.StartAsync();
|
await s.StartAsync();
|
||||||
|
|
||||||
await s.Server.EnsureChannelsSetup();
|
await s.Server.EnsureChannelsSetup();
|
||||||
|
var cryptoCode = "BTC";
|
||||||
s.RegisterNewUser(true);
|
s.RegisterNewUser(true);
|
||||||
//ln address tests
|
//ln address tests
|
||||||
var store = s.CreateNewStore();
|
s.CreateNewStore();
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
|
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
|
||||||
//ensure ln address is not available as Lightning is not enable
|
//ensure ln address is not available as Lightning is not enable
|
||||||
s.Driver.FindElement(By.Id("lightning-address-option"))
|
s.Driver.FindElement(By.Id("lightning-address-option"))
|
||||||
.FindElement(By.LinkText("You need to setup Lightning first"));
|
.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.AddLightningNode("BTC", LightningConnectionType.LndREST, false);
|
||||||
|
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
|
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
|
||||||
//ensure ln address is not available as lnurl is not configured
|
//ensure ln address is not available as lnurl is not configured
|
||||||
s.Driver.FindElement(By.Id("lightning-address-option"))
|
s.Driver.FindElement(By.Id("lightning-address-option"))
|
||||||
.FindElement(By.LinkText("You need LNURL configured first"));
|
.FindElement(By.LinkText("You need LNURL configured first"));
|
||||||
|
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.PaymentMethods);
|
s.GoToLightningSettings(s.StoreId, cryptoCode);
|
||||||
s.Driver.FindElement(By.Id($"Modify-LightningBTC")).Click();
|
|
||||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||||
s.Driver.WaitForAndClick(By.Id("save"));
|
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.GoToStore(s.StoreId, StoreNavPages.Integrations);
|
||||||
s.Driver.FindElement(By.Id("lightning-address-option"))
|
s.Driver.FindElement(By.Id("lightning-address-option"))
|
||||||
|
@ -1580,7 +1579,6 @@ namespace BTCPayServer.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void CanBrowseContent(SeleniumTester s)
|
private static void CanBrowseContent(SeleniumTester s)
|
||||||
{
|
{
|
||||||
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
|
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
|
||||||
|
|
|
@ -151,6 +151,15 @@ namespace BTCPayServer.Tests
|
||||||
storeController.UpdateWalletSettings(walletSettings).GetAwaiter().GetResult();
|
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
|
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
||||||
{
|
{
|
||||||
var controller = parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null, IsAdmin);
|
var controller = parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null, IsAdmin);
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Abstractions.Models;
|
using BTCPayServer.Abstractions.Models;
|
||||||
|
@ -18,6 +19,7 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using NBitcoin.DataEncoders;
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
using NBXplorer.DerivationStrategy;
|
using NBXplorer.DerivationStrategy;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
|
@ -335,7 +337,7 @@ namespace BTCPayServer.Controllers
|
||||||
IsStored = request.SavePrivateKeys,
|
IsStored = request.SavePrivateKeys,
|
||||||
ReturnUrl = Url.Action(nameof(GenerateWalletConfirm), new { storeId, cryptoCode })
|
ReturnUrl = Url.Action(nameof(GenerateWalletConfirm), new { storeId, cryptoCode })
|
||||||
};
|
};
|
||||||
if (this._BTCPayEnv.IsDeveloping)
|
if (_BTCPayEnv.IsDeveloping)
|
||||||
{
|
{
|
||||||
GenerateWalletResponse = response;
|
GenerateWalletResponse = response;
|
||||||
}
|
}
|
||||||
|
@ -383,18 +385,35 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
(bool canUseHotWallet, bool rpcImport) = await CanUseHotWallet();
|
(bool canUseHotWallet, bool rpcImport) = await CanUseHotWallet();
|
||||||
|
var client = _ExplorerProvider.GetExplorerClient(network);
|
||||||
|
|
||||||
var vm = new WalletSettingsViewModel
|
var vm = new WalletSettingsViewModel
|
||||||
{
|
{
|
||||||
StoreId = storeId,
|
StoreId = storeId,
|
||||||
CryptoCode = cryptoCode,
|
CryptoCode = cryptoCode,
|
||||||
|
WalletId = new WalletId(storeId, cryptoCode),
|
||||||
Network = network,
|
Network = network,
|
||||||
|
IsHotWallet = derivation.IsHotWallet,
|
||||||
Source = derivation.Source,
|
Source = derivation.Source,
|
||||||
RootFingerprint = derivation.GetSigningAccountKeySettings().RootFingerprint.ToString(),
|
RootFingerprint = derivation.GetSigningAccountKeySettings().RootFingerprint.ToString(),
|
||||||
DerivationScheme = derivation.AccountDerivation.ToString(),
|
DerivationScheme = derivation.AccountDerivation.ToString(),
|
||||||
|
DerivationSchemeInput = derivation.AccountOriginal,
|
||||||
KeyPath = derivation.GetSigningAccountKeySettings().AccountKeyPath?.ToString(),
|
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()),
|
Config = ProtectString(derivation.ToJson()),
|
||||||
IsHotWallet = derivation.IsHotWallet,
|
|
||||||
PayJoinEnabled = storeBlob.PayJoinEnabled,
|
PayJoinEnabled = storeBlob.PayJoinEnabled,
|
||||||
MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes,
|
MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes,
|
||||||
SpeedPolicy = store.SpeedPolicy,
|
SpeedPolicy = store.SpeedPolicy,
|
||||||
|
@ -405,7 +424,9 @@ namespace BTCPayServer.Controllers
|
||||||
CanUsePayJoin = canUseHotWallet && store
|
CanUsePayJoin = canUseHotWallet && store
|
||||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||||
.OfType<DerivationSchemeSettings>()
|
.OfType<DerivationSchemeSettings>()
|
||||||
.Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet)
|
.Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet),
|
||||||
|
StoreName = store.StoreName,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ViewData["ReplaceDescription"] = WalletReplaceWarning(derivation.IsHotWallet);
|
ViewData["ReplaceDescription"] = WalletReplaceWarning(derivation.IsHotWallet);
|
||||||
|
@ -414,38 +435,140 @@ namespace BTCPayServer.Controllers
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{storeId}/onchain/{cryptoCode}/settings")]
|
[HttpPost("{storeId}/onchain/{cryptoCode}/settings/wallet")]
|
||||||
public async Task<IActionResult> UpdateWalletSettings(WalletSettingsViewModel vm)
|
public async Task<IActionResult> UpdateWalletSettings(WalletSettingsViewModel vm)
|
||||||
{
|
{
|
||||||
bool needUpdate = false;
|
var checkResult = IsAvailable(vm.CryptoCode, out var store, out _);
|
||||||
if (CurrentStore.SpeedPolicy != vm.SpeedPolicy)
|
if (checkResult != null)
|
||||||
{
|
{
|
||||||
needUpdate = true;
|
return checkResult;
|
||||||
CurrentStore.SpeedPolicy = vm.SpeedPolicy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.MonitoringExpiration = TimeSpan.FromMinutes(vm.MonitoringExpiration);
|
||||||
blob.ShowRecommendedFee = vm.ShowRecommendedFee;
|
blob.ShowRecommendedFee = vm.ShowRecommendedFee;
|
||||||
blob.RecommendedFeeBlockTarget = vm.RecommendedFeeBlockTarget;
|
blob.RecommendedFeeBlockTarget = vm.RecommendedFeeBlockTarget;
|
||||||
|
|
||||||
var payjoinChanged = blob.PayJoinEnabled != vm.PayJoinEnabled;
|
|
||||||
blob.PayJoinEnabled = vm.PayJoinEnabled;
|
blob.PayJoinEnabled = vm.PayJoinEnabled;
|
||||||
if (CurrentStore.SetStoreBlob(blob))
|
|
||||||
|
if (store.SetStoreBlob(blob))
|
||||||
{
|
{
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needUpdate)
|
if (needUpdate)
|
||||||
{
|
{
|
||||||
await _Repo.UpdateStore(CurrentStore);
|
await _Repo.UpdateStore(store);
|
||||||
|
|
||||||
TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated";
|
TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated";
|
||||||
|
|
||||||
if (payjoinChanged && blob.PayJoinEnabled)
|
if (payjoinChanged && blob.PayJoinEnabled)
|
||||||
{
|
{
|
||||||
var problematicPayjoinEnabledMethods = CurrentStore.GetSupportedPaymentMethods(_NetworkProvider)
|
var problematicPayjoinEnabledMethods = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||||
.OfType<DerivationSchemeSettings>()
|
.OfType<DerivationSchemeSettings>()
|
||||||
.Where(settings => settings.Network.SupportPayJoin && !settings.IsHotWallet)
|
.Where(settings => settings.Network.SupportPayJoin && !settings.IsHotWallet)
|
||||||
.Select(settings => settings.PaymentId.CryptoCode)
|
.Select(settings => settings.PaymentId.CryptoCode)
|
||||||
|
@ -457,17 +580,59 @@ namespace BTCPayServer.Controllers
|
||||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||||
{
|
{
|
||||||
Severity = StatusMessageModel.StatusSeverity.Warning,
|
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,
|
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
|
||||||
cryptoCode = vm.CryptoCode
|
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")]
|
[HttpGet("{storeId}/onchain/{cryptoCode}/replace")]
|
||||||
|
@ -633,6 +798,13 @@ namespace BTCPayServer.Controllers
|
||||||
return existing;
|
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()
|
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
|
||||||
{
|
{
|
||||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||||
|
|
|
@ -12,7 +12,6 @@ using BTCPayServer.Data;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
using BTCPayServer.ModelBinders;
|
using BTCPayServer.ModelBinders;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
|
||||||
using BTCPayServer.Models.WalletViewModels;
|
using BTCPayServer.Models.WalletViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
@ -25,14 +24,13 @@ using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using BTCPayServer.BIP78.Sender;
|
using BTCPayServer.BIP78.Sender;
|
||||||
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
using BTCPayServer.Payments.PayJoin;
|
using BTCPayServer.Payments.PayJoin;
|
||||||
using NBitcoin.DataEncoders;
|
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
using NBXplorer.DerivationStrategy;
|
using NBXplorer.DerivationStrategy;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using StoreData = BTCPayServer.Data.StoreData;
|
using StoreData = BTCPayServer.Data.StoreData;
|
||||||
using WalletSettingsViewModel = BTCPayServer.Models.WalletViewModels.WalletSettingsViewModel;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
|
@ -41,10 +39,12 @@ namespace BTCPayServer.Controllers
|
||||||
[AutoValidateAntiforgeryToken]
|
[AutoValidateAntiforgeryToken]
|
||||||
public partial class WalletsController : Controller
|
public partial class WalletsController : Controller
|
||||||
{
|
{
|
||||||
public StoreRepository Repository { get; }
|
private StoreRepository Repository { get; }
|
||||||
public WalletRepository WalletRepository { get; }
|
private WalletRepository WalletRepository { get; }
|
||||||
public BTCPayNetworkProvider NetworkProvider { get; }
|
private BTCPayNetworkProvider NetworkProvider { get; }
|
||||||
public ExplorerClientProvider ExplorerClientProvider { get; }
|
private ExplorerClientProvider ExplorerClientProvider { get; }
|
||||||
|
|
||||||
|
public RateFetcher RateFetcher { get; }
|
||||||
|
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly JsonSerializerSettings _serializerSettings;
|
private readonly JsonSerializerSettings _serializerSettings;
|
||||||
|
@ -63,8 +63,6 @@ namespace BTCPayServer.Controllers
|
||||||
private readonly PullPaymentHostedService _pullPaymentService;
|
private readonly PullPaymentHostedService _pullPaymentService;
|
||||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||||
|
|
||||||
public RateFetcher RateFetcher { get; }
|
|
||||||
|
|
||||||
readonly CurrencyNameTable _currencyTable;
|
readonly CurrencyNameTable _currencyTable;
|
||||||
public WalletsController(StoreRepository repo,
|
public WalletsController(StoreRepository repo,
|
||||||
WalletRepository walletRepository,
|
WalletRepository walletRepository,
|
||||||
|
@ -86,7 +84,7 @@ namespace BTCPayServer.Controllers
|
||||||
LabelFactory labelFactory,
|
LabelFactory labelFactory,
|
||||||
ApplicationDbContextFactory dbContextFactory,
|
ApplicationDbContextFactory dbContextFactory,
|
||||||
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
||||||
HostedServices.PullPaymentHostedService pullPaymentService,
|
PullPaymentHostedService pullPaymentService,
|
||||||
IEnumerable<IPayoutHandler> payoutHandlers)
|
IEnumerable<IPayoutHandler> payoutHandlers)
|
||||||
{
|
{
|
||||||
_currencyTable = currencyTable;
|
_currencyTable = currencyTable;
|
||||||
|
@ -114,7 +112,7 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||||
readonly string[] LabelColorScheme = new string[]
|
readonly string[] LabelColorScheme =
|
||||||
{
|
{
|
||||||
"#fbca04",
|
"#fbca04",
|
||||||
"#0e8a16",
|
"#0e8a16",
|
||||||
|
@ -274,9 +272,8 @@ namespace BTCPayServer.Controllers
|
||||||
return View(wallets);
|
return View(wallets);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet("{walletId}")]
|
||||||
[Route("{walletId}")]
|
[HttpGet("{walletId}/transactions")]
|
||||||
[Route("{walletId}/transactions")]
|
|
||||||
public async Task<IActionResult> WalletTransactions(
|
public async Task<IActionResult> WalletTransactions(
|
||||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
WalletId walletId,
|
WalletId walletId,
|
||||||
|
@ -359,8 +356,7 @@ namespace BTCPayServer.Controllers
|
||||||
return $"{walletId}:{txId}";
|
return $"{walletId}:{txId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet("{walletId}/receive")]
|
||||||
[Route("{walletId}/receive")]
|
|
||||||
public IActionResult WalletReceive([ModelBinder(typeof(WalletIdModelBinder))]
|
public IActionResult WalletReceive([ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
WalletId walletId)
|
WalletId walletId)
|
||||||
{
|
{
|
||||||
|
@ -854,7 +850,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
private IActionResult RedirectToWalletPSBT(WalletPSBTViewModel vm)
|
private IActionResult RedirectToWalletPSBT(WalletPSBTViewModel vm)
|
||||||
{
|
{
|
||||||
var redirectVm = new PostRedirectViewModel()
|
var redirectVm = new PostRedirectViewModel
|
||||||
{
|
{
|
||||||
AspController = "Wallets",
|
AspController = "Wallets",
|
||||||
AspAction = nameof(WalletPSBT),
|
AspAction = nameof(WalletPSBT),
|
||||||
|
@ -871,7 +867,7 @@ namespace BTCPayServer.Controllers
|
||||||
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
|
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
WalletId walletId, SigningContextModel signingContext)
|
WalletId walletId, SigningContextModel signingContext)
|
||||||
{
|
{
|
||||||
return View(nameof(SignWithSeed), new SignWithSeedViewModel()
|
return View(nameof(SignWithSeed), new SignWithSeedViewModel
|
||||||
{
|
{
|
||||||
SigningContext = signingContext,
|
SigningContext = signingContext,
|
||||||
});
|
});
|
||||||
|
@ -970,8 +966,7 @@ namespace BTCPayServer.Controllers
|
||||||
return RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() });
|
return RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() });
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet("{walletId}/rescan")]
|
||||||
[Route("{walletId}/rescan")]
|
|
||||||
public async Task<IActionResult> WalletRescan(
|
public async Task<IActionResult> WalletRescan(
|
||||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
WalletId walletId)
|
WalletId walletId)
|
||||||
|
@ -1012,8 +1007,7 @@ namespace BTCPayServer.Controllers
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost("{walletId}/rescan")]
|
||||||
[Route("{walletId}/rescan")]
|
|
||||||
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
public async Task<IActionResult> WalletRescan(
|
public async Task<IActionResult> WalletRescan(
|
||||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
|
@ -1036,12 +1030,9 @@ namespace BTCPayServer.Controllers
|
||||||
return RedirectToAction();
|
return RedirectToAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StoreData CurrentStore
|
private StoreData CurrentStore
|
||||||
{
|
{
|
||||||
get
|
get => HttpContext.GetStoreData();
|
||||||
{
|
|
||||||
return HttpContext.GetStoreData();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DerivationSchemeSettings GetDerivationSchemeSettings(WalletId walletId)
|
internal DerivationSchemeSettings GetDerivationSchemeSettings(WalletId walletId)
|
||||||
|
@ -1055,11 +1046,11 @@ namespace BTCPayServer.Controllers
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var b = await wallet.GetBalance(derivationStrategy, cts.Token);
|
var b = await wallet.GetBalance(derivationStrategy, cts.Token);
|
||||||
return (b.Available ?? b.Total);
|
return b.Available ?? b.Total;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return NBitcoin.Money.Zero;
|
return Money.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1080,82 +1071,24 @@ namespace BTCPayServer.Controllers
|
||||||
return _userManager.GetUserId(User);
|
return _userManager.GetUserId(User);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("{walletId}/settings")]
|
[HttpPost("{walletId}/actions")]
|
||||||
public async Task<IActionResult> WalletSettings(
|
public async Task<IActionResult> WalletActions(
|
||||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
WalletId walletId)
|
WalletId walletId, string command,
|
||||||
{
|
|
||||||
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",
|
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (!ModelState.IsValid)
|
|
||||||
return View(vm);
|
|
||||||
var derivationScheme = GetDerivationSchemeSettings(walletId);
|
var derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||||
if (derivationScheme == null || derivationScheme.Network.ReadonlyWallet)
|
if (derivationScheme == null || derivationScheme.Network.ReadonlyWallet)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
if (command == "save")
|
switch (command)
|
||||||
{
|
{
|
||||||
derivationScheme.Label = vm.Label;
|
case "prune":
|
||||||
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")
|
|
||||||
{
|
{
|
||||||
var result = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode).PruneAsync(derivationScheme.AccountDerivation, new PruneRequest(), cancellationToken);
|
var result = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode).PruneAsync(derivationScheme.AccountDerivation, new PruneRequest(), cancellationToken);
|
||||||
if (result.TotalPruned == 0)
|
if (result.TotalPruned == 0)
|
||||||
{
|
{
|
||||||
TempData[WellKnownTempData.SuccessMessage] = $"The wallet is already pruned";
|
TempData[WellKnownTempData.SuccessMessage] = "The wallet is already pruned";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1163,53 +1096,24 @@ namespace BTCPayServer.Controllers
|
||||||
$"The wallet has been successfully pruned ({result.TotalPruned} transactions have been removed from the history)";
|
$"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) &&
|
if (Version.TryParse(_dashboard.Get(walletId.CryptoCode)?.Status?.Version ?? "0.0.0.0", out var v) &&
|
||||||
v < new Version(2, 2, 4))
|
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
|
else
|
||||||
{
|
{
|
||||||
await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
||||||
.WipeAsync(derivationScheme.AccountDerivation, cancellationToken);
|
.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())
|
default:
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1231,11 +1135,6 @@ namespace BTCPayServer.Controllers
|
||||||
public string PaymentLink { get; set; }
|
public string PaymentLink { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class GetInfoResult
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SendToAddressResult
|
public class SendToAddressResult
|
||||||
{
|
{
|
||||||
[JsonProperty("psbt")]
|
[JsonProperty("psbt")]
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace BTCPayServer.Models.StoreViewModels
|
namespace BTCPayServer.Models.StoreViewModels
|
||||||
{
|
{
|
||||||
public class WalletSettingsViewModel : DerivationSchemeViewModel
|
public class WalletSettingsViewModel : DerivationSchemeViewModel
|
||||||
{
|
{
|
||||||
|
public WalletId WalletId { get; set; }
|
||||||
public string StoreId { get; set; }
|
public string StoreId { get; set; }
|
||||||
public bool IsHotWallet { get; set; }
|
public bool IsHotWallet { get; set; }
|
||||||
public bool CanUsePayJoin { 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 …")]
|
[Display(Name = "Consider the invoice confirmed when the payment transaction …")]
|
||||||
public SpeedPolicy SpeedPolicy { get; set; }
|
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);
|
: _linkGenerator.AppLink(refLabel.Reference, request.Scheme, request.Host, request.PathBase);
|
||||||
break;
|
break;
|
||||||
case "pj-exposed":
|
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)
|
coloredLabel.Link = string.IsNullOrEmpty(refLabel.Reference)
|
||||||
? null
|
? null
|
||||||
: _linkGenerator.InvoiceLink(refLabel.Reference, request.Scheme, request.Host, request.PathBase);
|
: _linkGenerator.InvoiceLink(refLabel.Reference, request.Scheme, request.Host, request.PathBase);
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="separatorGem" v-if="srvModel.invoiceBitcoinUrl"></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>
|
<label>{{$t("Payment link")}}</label>
|
||||||
<div class="inputWithIcon _copyInput">
|
<div class="inputWithIcon _copyInput">
|
||||||
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.invoiceBitcoinUrl" readonly="readonly"/>
|
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.invoiceBitcoinUrl" readonly="readonly"/>
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<th>Connection String</th>
|
<th>Connection String</th>
|
||||||
<td>@Model.ConnectionString</td>
|
<td class="text-break">@Model.ConnectionString</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -1,21 +1,42 @@
|
||||||
|
@using NBitcoin.DataEncoders
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
@using System.Text
|
||||||
@model WalletSettingsViewModel
|
@model WalletSettingsViewModel
|
||||||
@{
|
@{
|
||||||
Layout = "../Shared/_NavLayout.cshtml";
|
Layout = "../Shared/_NavLayout.cshtml";
|
||||||
ViewData.SetActivePageAndTitle(StoreNavPages.PaymentMethods, $"{Model.CryptoCode} Wallet Settings", Context.GetStoreData().StoreName);
|
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="row">
|
||||||
<div class="col-lg-10 col-xl-9">
|
<div class="col-lg-10 col-xl-9">
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
<h4 class="mb-3">@ViewData["Title"]</h4>
|
<h4 class="mb-3">@ViewData["Title"]</h4>
|
||||||
<table class="table table-borderless table-responsive-md">
|
<div class="mb-3 d-flex align-items-center">
|
||||||
<tbody>
|
<span class="me-2">Type:</span>
|
||||||
<tr>
|
|
||||||
<th>Type</th>
|
|
||||||
<td class="d-flex flex-wrap align-items-center">
|
|
||||||
<span title="@Model.Source" data-bs-toggle="tooltip" class="me-3">@(Model.IsHotWallet ? "Hot wallet" : "Watch-only wallet")</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"
|
<a asp-controller="Stores" asp-action="ReplaceWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode"
|
||||||
id="ChangeWalletLink"
|
id="ChangeWalletLink"
|
||||||
class="text-secondary me-3"
|
class="dropdown-item"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#ConfirmModal"
|
data-bs-target="#ConfirmModal"
|
||||||
data-title="Replace @Model.CryptoCode wallet"
|
data-title="Replace @Model.CryptoCode wallet"
|
||||||
|
@ -24,37 +45,81 @@
|
||||||
data-confirm-input="REPLACE">
|
data-confirm-input="REPLACE">
|
||||||
Replace wallet
|
Replace wallet
|
||||||
</a>
|
</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"
|
||||||
<button type="submit" class="btn btn-link text-secondary text-start p-0" id="Delete"
|
id="Delete"
|
||||||
|
class="dropdown-item"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#ConfirmModal"
|
data-bs-target="#ConfirmModal"
|
||||||
data-title="Remove @Model.CryptoCode wallet"
|
data-title="Remove @Model.CryptoCode wallet"
|
||||||
data-description="@ViewData["RemoveDescription"]"
|
data-description="@ViewData["RemoveDescription"]"
|
||||||
data-confirm="Remove"
|
data-confirm="Remove"
|
||||||
data-confirm-input="REMOVE">Remove wallet</button>
|
data-confirm-input="REMOVE">Remove wallet</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
<tr>
|
<form method="post" asp-action="UpdateWalletSettings" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode">
|
||||||
<th class="w-150px">Derivation Scheme</th>
|
<div class="form-group">
|
||||||
<td class="text-break">@Model.DerivationScheme</td>
|
<label asp-for="Label" class="form-label"></label>
|
||||||
</tr>
|
<input asp-for="Label" class="form-control" style="max-width:24em;" />
|
||||||
<tr>
|
<span asp-validation-for="Label" class="text-danger"></span>
|
||||||
<th>Root Fingerprint</th>
|
</div>
|
||||||
<td>@Model.RootFingerprint</td>
|
<div class="form-group">
|
||||||
</tr>
|
<label asp-for="DerivationScheme" class="form-label"></label>
|
||||||
@if (!string.IsNullOrEmpty(Model.KeyPath))
|
<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>
|
<div class="form-group">
|
||||||
<th>KeyPath</th>
|
<label asp-for="DerivationSchemeInput" class="form-label"></label>
|
||||||
<td>@Model.KeyPath</td>
|
<div class="input-group" data-clipboard="@Model.DerivationSchemeInput">
|
||||||
</tr>
|
<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>
|
@for (var i = 0; i < Model.AccountKeys.Count; i++)
|
||||||
</table>
|
{
|
||||||
|
<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>
|
<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)
|
@if (Model.CanUsePayJoin)
|
||||||
{
|
{
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -102,23 +167,39 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mt-2 mb-4">
|
<div class="form-group mt-2 mb-4">
|
||||||
<label asp-for="RecommendedFeeBlockTarget" class="form-label"></label>
|
<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>
|
<span asp-validation-for="RecommendedFeeBlockTarget" class="text-danger"></span>
|
||||||
</div>
|
</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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<partial name="_Confirm" model="@(new ConfirmModel($"{Model.CryptoCode} wallet", "Change", "Update"))" />
|
<partial name="_Confirm" model="@(new ConfirmModel($"{Model.CryptoCode} wallet", "Change", "Update"))" />
|
||||||
|
<partial name="ShowQR"/>
|
||||||
|
|
||||||
@section PageFootContent {
|
@section PageFootContent {
|
||||||
<script>
|
<script>
|
||||||
const deleteButton = document.getElementById('Delete')
|
const wallets = @Safe.Json(Model.AccountKeys.Select(model => Encoders.Hex.EncodeData(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model, Formatting.None)))))
|
||||||
deleteButton.addEventListener('click', event => {
|
const qrApp = initQRShow("Wallet QR", "", "scan-qr-modal")
|
||||||
event.preventDefault()
|
|
||||||
});
|
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>
|
</script>
|
||||||
<partial name="_ValidationScriptsPartial"/>
|
<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 {
|
@section PageHeadContent {
|
||||||
<style>
|
<style>
|
||||||
.smMaxWidth {
|
.smMaxWidth {
|
||||||
max-width: 200px;
|
max-width: 125px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@media (min-width: 768px) {
|
@@media (min-width: 990px) {
|
||||||
.smMaxWidth {
|
.smMaxWidth {
|
||||||
max-width: 400px;
|
max-width: 250px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,8 +71,26 @@
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
|
|
||||||
<h4 class="mb-3">@ViewData["PageTitle"]</h4>
|
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||||
<p>
|
<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>.
|
If BTCPay Server shows you an invalid balance, <a asp-action="WalletRescan" asp-route-walletId="@Context.GetRouteValue("walletId")">rescan your wallet</a>.
|
||||||
<br/>
|
<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>.
|
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>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row">
|
<div class="table-responsive-md">
|
||||||
<div class="col-md-12">
|
<table class="table table-hover">
|
||||||
<table class="table table-hover table-responsive-md">
|
|
||||||
<thead class="thead-inverse">
|
<thead class="thead-inverse">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="min-width: 90px;" class="col-md-auto">
|
<th style="min-width: 90px;" class="col-md-auto">
|
||||||
|
@ -226,9 +243,8 @@
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
<vc:pager view-model="Model"/>
|
<vc:pager view-model="Model"/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,7 +7,6 @@ namespace BTCPayServer.Views.Wallets
|
||||||
Transactions,
|
Transactions,
|
||||||
Rescan,
|
Rescan,
|
||||||
PSBT,
|
PSBT,
|
||||||
Settings,
|
|
||||||
Receive
|
Receive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
@if (!network.ReadonlyWallet)
|
@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.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" />
|
<vc:ui-extension-point location="wallet-nav" model="@Model" />
|
||||||
</nav>
|
</nav>
|
||||||
|
|
Loading…
Add table
Reference in a new issue