diff --git a/BTCPayServer.Tests/docker-compose.altcoins.yml b/BTCPayServer.Tests/docker-compose.altcoins.yml index 420f72cb1..c9467b3a1 100644 --- a/BTCPayServer.Tests/docker-compose.altcoins.yml +++ b/BTCPayServer.Tests/docker-compose.altcoins.yml @@ -84,7 +84,7 @@ services: - customer_lnd - merchant_lnd nbxplorer: - image: nicolasdorier/nbxplorer:2.2.0 + image: nicolasdorier/nbxplorer:2.2.1 restart: unless-stopped ports: - "32838:32838" diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index 7aed9dc7c..7db38d04c 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -81,7 +81,7 @@ services: - customer_lnd - merchant_lnd nbxplorer: - image: nicolasdorier/nbxplorer:2.2.0 + image: nicolasdorier/nbxplorer:2.2.1 restart: unless-stopped ports: - "32838:32838" diff --git a/BTCPayServer/Controllers/StoresController.Onchain.cs b/BTCPayServer/Controllers/StoresController.Onchain.cs index 1c86afae0..f1ddd597a 100644 --- a/BTCPayServer/Controllers/StoresController.Onchain.cs +++ b/BTCPayServer/Controllers/StoresController.Onchain.cs @@ -55,6 +55,7 @@ namespace BTCPayServer.Controllers vm.RootKeyPath = network.GetRootKeyPath(); vm.CanUseHotWallet = hotWallet; vm.CanUseRPCImport = rpcImport; + vm.CanUseTaproot = TaprootSupported(vm.CryptoCode); if (vm.Method == null) { @@ -109,6 +110,11 @@ namespace BTCPayServer.Controllers try { strategy = ParseDerivationStrategy(vm.DerivationScheme, network); + if(strategy.AccountDerivation is TaprootDerivationStrategy && !TaprootSupported(vm.CryptoCode)) + { + ModelState.AddModelError(nameof(vm.DerivationScheme), "Taproot is not supported"); + return View(vm.ViewName, vm); + } strategy.Source = "ManualDerivationScheme"; if (!string.IsNullOrEmpty(vm.AccountKey)) { @@ -208,6 +214,7 @@ namespace BTCPayServer.Controllers vm.CanUseHotWallet = hotWallet; vm.CanUseRPCImport = rpcImport; + vm.CanUseTaproot = TaprootSupported(vm.CryptoCode); vm.RootKeyPath = network.GetRootKeyPath(); vm.Network = network; @@ -262,6 +269,12 @@ namespace BTCPayServer.Controllers CanUseRPCImport = rpcImport }; + if (request.ScriptPubKeyType == ScriptPubKeyType.TaprootBIP86 && !TaprootSupported(cryptoCode) ) + { + ModelState.AddModelError(nameof(request.ScriptPubKeyType), $"Taproot not supported"); + return View(vm.ViewName, vm); + } + if (isImport && string.IsNullOrEmpty(request.ExistingMnemonic)) { ModelState.AddModelError(nameof(request.ExistingMnemonic), "Please provide your existing seed"); diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index a4f99b0e9..e2f00163d 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -68,7 +68,8 @@ namespace BTCPayServer.Controllers EventAggregator eventAggregator, AppService appService, WebhookNotificationManager webhookNotificationManager, - IDataProtectionProvider dataProtector) + IDataProtectionProvider dataProtector, + NBXplorerDashboard Dashboard) { _RateFactory = rateFactory; _Repo = repo; @@ -89,6 +90,7 @@ namespace BTCPayServer.Controllers _ServiceProvider = serviceProvider; _BtcpayServerOptions = btcpayServerOptions; _BTCPayEnv = btcpayEnv; + _Dashboard = Dashboard; } readonly BTCPayServerOptions _BtcpayServerOptions; @@ -107,6 +109,7 @@ namespace BTCPayServer.Controllers private readonly IAuthorizationService _authorizationService; private readonly AppService _appService; private readonly EventAggregator _EventAggregator; + private readonly NBXplorerDashboard _Dashboard; [TempData] public bool StoreNotConfigured @@ -143,6 +146,13 @@ namespace BTCPayServer.Controllers } } + public bool TaprootSupported(string crytoCode) + { + var networkSupport = ((BTCPayNetwork)_NetworkProvider.GetNetwork(crytoCode))?.NBitcoinNetwork?.Consensus?.SupportTaproot is true; + var status = _Dashboard.Get(crytoCode).Status; + return networkSupport && !(status.NetworkType == ChainName.Mainnet && status.ChainHeight < 709632); + } + [HttpPost] [Route("{storeId}/users")] @@ -444,7 +454,7 @@ namespace BTCPayServer.Controllers } SetCryptoCurrencies(model, CurrentStore); model.SetLanguages(_LangService, model.DefaultLang); - model.PaymentMethodCriteria??= new List(); + model.PaymentMethodCriteria ??= new List(); for (var index = 0; index < model.PaymentMethodCriteria.Count; index++) { var methodCriterion = model.PaymentMethodCriteria[index]; @@ -457,7 +467,7 @@ namespace BTCPayServer.Controllers } } } - + if (!ModelState.IsValid) { return View(model); @@ -550,7 +560,7 @@ namespace BTCPayServer.Controllers } } } - + [HttpGet("{storeId}")] public async Task UpdateStore() { @@ -575,16 +585,16 @@ namespace BTCPayServer.Controllers vm.PayJoinEnabled = storeBlob.PayJoinEnabled; vm.HintWallet = storeBlob.Hints.Wallet; vm.HintLightning = storeBlob.Hints.Lightning; - + (bool canUseHotWallet, _) = await CanUseHotWallet(); vm.CanUsePayJoin = canUseHotWallet && store .GetSupportedPaymentMethods(_NetworkProvider) .OfType() .Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet); - + return View(vm); } - + [HttpPost("{storeId}")] public async Task UpdateStore(StoreViewModel model, string command = null) { @@ -704,7 +714,7 @@ namespace BTCPayServer.Controllers { // ignored } - + return new DerivationSchemeSettings(parser.Parse(derivationScheme), network); } diff --git a/BTCPayServer/Controllers/VaultController.cs b/BTCPayServer/Controllers/VaultController.cs index a4a5bc358..ae6104692 100644 --- a/BTCPayServer/Controllers/VaultController.cs +++ b/BTCPayServer/Controllers/VaultController.cs @@ -222,7 +222,21 @@ namespace BTCPayServer.Controllers continue; } - if (addressType == "segwit") + if (!network.NBitcoinNetwork.Consensus.SupportTaproot && addressType == "taproot") + { + await websocketHelper.Send("{ \"error\": \"taproot-notsupported\"}", cancellationToken); + continue; + } + if (addressType == "taproot") + { + keyPath = new KeyPath("86'").Derive(network.CoinType).Derive(accountNumber, true); + xpub = await device.GetXPubAsync(keyPath); + strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions() + { + ScriptPubKeyType = ScriptPubKeyType.TaprootBIP86 + }); + } + else if (addressType == "segwit") { keyPath = new KeyPath("84'").Derive(network.CoinType).Derive(accountNumber, true); xpub = await device.GetXPubAsync(keyPath); @@ -337,6 +351,8 @@ askdevice: private ScriptPubKeyType GetScriptPubKeyType(RootedKeyPath keyPath) { var path = keyPath.KeyPath.ToString(); + if (path.StartsWith("86'", StringComparison.OrdinalIgnoreCase)) + return ScriptPubKeyType.TaprootBIP86; if (path.StartsWith("84'", StringComparison.OrdinalIgnoreCase)) return ScriptPubKeyType.Segwit; if (path.StartsWith("49'", StringComparison.OrdinalIgnoreCase)) diff --git a/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs b/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs index 14d40d918..eaf2f2baf 100644 --- a/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs @@ -39,7 +39,8 @@ namespace BTCPayServer.Models.StoreViewModels public bool CanUseHotWallet { get; set; } [Display(Name = "Can use RPC import")] public bool CanUseRPCImport { get; set; } - + [Display(Name = "Can use Taproot")] + public bool CanUseTaproot { get; set; } public RootedKeyPath GetAccountKeypath() { if (KeyPath != null && RootFingerprint != null && diff --git a/BTCPayServer/Views/Stores/GenerateWallet.cshtml b/BTCPayServer/Views/Stores/GenerateWallet.cshtml index a2c428aa8..e5d99c66b 100644 --- a/BTCPayServer/Views/Stores/GenerateWallet.cshtml +++ b/BTCPayServer/Views/Stores/GenerateWallet.cshtml @@ -7,6 +7,7 @@ ViewData.SetActivePageAndTitle(StoreNavPages.Wallet, $"Create {Model.CryptoCode} {type} Wallet", Context.GetStoreData().StoreName); ViewData.Add(nameof(Model.CanUseHotWallet), Model.CanUseHotWallet); ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport); + ViewData.Add(nameof(Model.CanUseTaproot), Model.CanUseTaproot); ViewData.Add(nameof(Model.Method), Model.Method); } diff --git a/BTCPayServer/Views/Stores/ImportWallet/Hardware.cshtml b/BTCPayServer/Views/Stores/ImportWallet/Hardware.cshtml index 15fdc8e25..02f890f12 100644 --- a/BTCPayServer/Views/Stores/ImportWallet/Hardware.cshtml +++ b/BTCPayServer/Views/Stores/ImportWallet/Hardware.cshtml @@ -31,6 +31,10 @@ + @if (ViewData["CanUseTaproot"] is true) + { + + }
diff --git a/BTCPayServer/Views/Stores/ImportWallet/Seed.cshtml b/BTCPayServer/Views/Stores/ImportWallet/Seed.cshtml index eed3b9b8e..c82b6cb40 100644 --- a/BTCPayServer/Views/Stores/ImportWallet/Seed.cshtml +++ b/BTCPayServer/Views/Stores/ImportWallet/Seed.cshtml @@ -21,6 +21,7 @@ { ViewData.Add(nameof(Model.CanUseHotWallet), Model.CanUseHotWallet); ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport); + ViewData.Add(nameof(Model.CanUseTaproot), Model.CanUseTaproot); ViewData.Add(nameof(Model.Method), Model.Method); diff --git a/BTCPayServer/Views/Stores/ImportWallet/Xpub.cshtml b/BTCPayServer/Views/Stores/ImportWallet/Xpub.cshtml index 078f2325c..ea7f1e94b 100644 --- a/BTCPayServer/Views/Stores/ImportWallet/Xpub.cshtml +++ b/BTCPayServer/Views/Stores/ImportWallet/Xpub.cshtml @@ -83,6 +83,13 @@ pkh(xpub…/0/*) + @if (Model.CanUseTaproot) + { + + P2TR + xpub…-[taproot] + + } Multi-sig P2WSH 2-of-xpub1…-xpub2… diff --git a/BTCPayServer/Views/Stores/_GenerateWalletForm.cshtml b/BTCPayServer/Views/Stores/_GenerateWalletForm.cshtml index 2b2519274..bdc2eea21 100644 --- a/BTCPayServer/Views/Stores/_GenerateWalletForm.cshtml +++ b/BTCPayServer/Views/Stores/_GenerateWalletForm.cshtml @@ -7,6 +7,7 @@ var isHotWallet = method is WalletSetupMethod.HotWallet; var canUseHotWallet = ViewData["CanUseHotWallet"] is true; var canUseRpcImport = ViewData["CanUseRPCImport"] is true; + var canUseTaproot = ViewData["CanUseTaproot"] is true; } @if (!User.IsInRole(Roles.ServerAdmin)) @@ -36,6 +37,10 @@ + @if (canUseTaproot) + { + + }