diff --git a/BTCPayServer/Controllers/StoresController.BTCLike.cs b/BTCPayServer/Controllers/StoresController.BTCLike.cs index 372de61e0..99faddd50 100644 --- a/BTCPayServer/Controllers/StoresController.BTCLike.cs +++ b/BTCPayServer/Controllers/StoresController.BTCLike.cs @@ -102,7 +102,8 @@ namespace BTCPayServer.Controllers if (vm.WalletFile != null) { - if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, out strategy)) + if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, + out strategy)) { TempData.SetStatusMessageModel(new StatusMessageModel() { @@ -113,6 +114,19 @@ namespace BTCPayServer.Controllers return View(nameof(AddDerivationScheme), vm); } } + else if (!string.IsNullOrEmpty(vm.WalletFileContent)) + { + if (!DerivationSchemeSettings.TryParseFromWalletFile(vm.WalletFileContent, network, out strategy)) + { + TempData.SetStatusMessageModel(new StatusMessageModel() + { + Severity = StatusMessageModel.StatusSeverity.Error, + Message = "QR import was not in the correct format" + }); + vm.Confirmation = false; + return View(nameof(AddDerivationScheme), vm); + } + } else { try @@ -122,16 +136,24 @@ namespace BTCPayServer.Controllers var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network); if (newStrategy.AccountDerivation != strategy?.AccountDerivation) { - var accountKey = string.IsNullOrEmpty(vm.AccountKey) ? null : new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork); + var accountKey = string.IsNullOrEmpty(vm.AccountKey) + ? null + : new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork); if (accountKey != null) { - var accountSettings = newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey); + var accountSettings = + newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey); if (accountSettings != null) { - accountSettings.AccountKeyPath = vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath); - accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint) ? (HDFingerprint?)null : new HDFingerprint(NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint)); + accountSettings.AccountKeyPath = + vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath); + accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint) + ? (HDFingerprint?)null + : new HDFingerprint( + NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint)); } } + strategy = newStrategy; strategy.Source = vm.Source; vm.DerivationScheme = strategy.AccountDerivation.ToString(); @@ -163,7 +185,7 @@ namespace BTCPayServer.Controllers var willBeExcluded = !vm.Enabled; var showAddress = // Show addresses if: - // - If the user is testing the hint address in confirmation screen + // - If the user is testing the hint address in confirmation screen (vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) || // - The user is clicking on continue after changing the config (!vm.Confirmation && oldConfig != vm.Config) || @@ -189,22 +211,22 @@ namespace BTCPayServer.Controllers } await _Repo.UpdateStore(store); - _EventAggregator.Publish(new WalletChangedEvent() - { - WalletId = new WalletId(storeId, cryptoCode) - }); + _EventAggregator.Publish(new WalletChangedEvent() {WalletId = new WalletId(storeId, cryptoCode)}); if (willBeExcluded != wasExcluded) { var label = willBeExcluded ? "disabled" : "enabled"; - TempData[WellKnownTempData.SuccessMessage] = $"On-Chain payments for {network.CryptoCode} has been {label}."; + TempData[WellKnownTempData.SuccessMessage] = + $"On-Chain payments for {network.CryptoCode} has been {label}."; } else { - TempData[WellKnownTempData.SuccessMessage] = $"Derivation settings for {network.CryptoCode} has been modified."; + TempData[WellKnownTempData.SuccessMessage] = + $"Derivation settings for {network.CryptoCode} has been modified."; } + // This is success case when derivation scheme is added to the store - return RedirectToAction(nameof(UpdateStore), new { storeId = storeId }); + return RedirectToAction(nameof(UpdateStore), new {storeId = storeId}); } else if (!string.IsNullOrEmpty(vm.HintAddress)) { @@ -269,7 +291,7 @@ namespace BTCPayServer.Controllers Severity = StatusMessageModel.StatusSeverity.Error, Html = $"There was an error generating your wallet: {e.Message}" }); - return RedirectToAction(nameof(AddDerivationScheme), new { storeId, cryptoCode }); + return RedirectToAction(nameof(AddDerivationScheme), new {storeId, cryptoCode}); } if (response == null) @@ -279,7 +301,7 @@ namespace BTCPayServer.Controllers Severity = StatusMessageModel.StatusSeverity.Error, Html = "There was an error generating your wallet. Is your node available?" }); - return RedirectToAction(nameof(AddDerivationScheme), new { storeId, cryptoCode }); + return RedirectToAction(nameof(AddDerivationScheme), new {storeId, cryptoCode}); } var store = HttpContext.GetStoreData(); @@ -315,7 +337,7 @@ namespace BTCPayServer.Controllers Mnemonic = response.Mnemonic, Passphrase = response.Passphrase, IsStored = request.SavePrivateKeys, - ReturnUrl = Url.Action(nameof(UpdateStore), new { storeId }) + ReturnUrl = Url.Action(nameof(UpdateStore), new {storeId}) }; return this.RedirectToRecoverySeedBackup(vm); } @@ -332,7 +354,8 @@ namespace BTCPayServer.Controllers private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet() { - var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings)).Succeeded; + var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings)) + .Succeeded; if (isAdmin) return (true, true); var policies = await _settingsRepository.GetSettingAsync(); diff --git a/BTCPayServer/Controllers/WalletsController.PSBT.cs b/BTCPayServer/Controllers/WalletsController.PSBT.cs index cb43843a2..69ba4dcee 100644 --- a/BTCPayServer/Controllers/WalletsController.PSBT.cs +++ b/BTCPayServer/Controllers/WalletsController.PSBT.cs @@ -80,6 +80,7 @@ namespace BTCPayServer.Controllers { vm.Decoded = psbt.ToString(); vm.PSBT = psbt.ToBase64(); + vm.PSBTHex = psbt.ToHex(); } return View(nameof(WalletPSBT), vm ?? new WalletPSBTViewModel() { CryptoCode = walletId.CryptoCode }); @@ -104,6 +105,8 @@ namespace BTCPayServer.Controllers ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT"); return View(vm); } + + vm.PSBTHex = psbt.ToHex(); var res = await TryHandleSigningCommands(walletId, psbt, command, new SigningContextModel(psbt)); if (res != null) { @@ -117,6 +120,7 @@ namespace BTCPayServer.Controllers ModelState.Remove(nameof(vm.FileName)); ModelState.Remove(nameof(vm.UploadedPSBTFile)); vm.PSBT = psbt.ToBase64(); + vm.PSBTHex = psbt.ToHex(); vm.FileName = vm.UploadedPSBTFile?.FileName; return View(vm); diff --git a/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs b/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs index 21372d30a..cf7522399 100644 --- a/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/DerivationSchemeViewModel.cs @@ -35,6 +35,8 @@ namespace BTCPayServer.Models.StoreViewModels [Display(Name = "Wallet File")] public IFormFile WalletFile { get; set; } + [Display(Name = "Wallet File Content")] + public string WalletFileContent { get; set; } public string Config { get; set; } public string Source { get; set; } public string DerivationSchemeFormat { get; set; } diff --git a/BTCPayServer/Models/WalletViewModels/WalletPSBTViewModel.cs b/BTCPayServer/Models/WalletViewModels/WalletPSBTViewModel.cs index 44f857528..8f4ea0455 100644 --- a/BTCPayServer/Models/WalletViewModels/WalletPSBTViewModel.cs +++ b/BTCPayServer/Models/WalletViewModels/WalletPSBTViewModel.cs @@ -13,6 +13,7 @@ namespace BTCPayServer.Models.WalletViewModels public string CryptoCode { get; set; } public string Decoded { get; set; } string _FileName; + public string PSBTHex { get; set; } public bool NBXSeedAvailable { get; set; } public string FileName diff --git a/BTCPayServer/Models/WalletViewModels/WalletSettingsViewModel.cs b/BTCPayServer/Models/WalletViewModels/WalletSettingsViewModel.cs index 28c9be42a..83ae47a6e 100644 --- a/BTCPayServer/Models/WalletViewModels/WalletSettingsViewModel.cs +++ b/BTCPayServer/Models/WalletViewModels/WalletSettingsViewModel.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; namespace BTCPayServer.Models.WalletViewModels { @@ -22,6 +23,7 @@ namespace BTCPayServer.Models.WalletViewModels public class WalletSettingsAccountKeyViewModel { + [JsonProperty("ExtPubKey")] [DisplayName("Account key")] public string AccountKey { get; set; } [DisplayName("Master fingerprint")] diff --git a/BTCPayServer/Views/Shared/CameraScanner.cshtml b/BTCPayServer/Views/Shared/CameraScanner.cshtml new file mode 100644 index 000000000..3497d76fa --- /dev/null +++ b/BTCPayServer/Views/Shared/CameraScanner.cshtml @@ -0,0 +1,220 @@ +
+ +
+ + + + diff --git a/BTCPayServer/Views/Shared/ShowQR.cshtml b/BTCPayServer/Views/Shared/ShowQR.cshtml new file mode 100644 index 000000000..fe25db48e --- /dev/null +++ b/BTCPayServer/Views/Shared/ShowQR.cshtml @@ -0,0 +1,105 @@ +
+ +
+ diff --git a/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml b/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml index c059a5282..6b1b0943d 100644 --- a/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml +++ b/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml @@ -1,4 +1,5 @@ @model DerivationSchemeViewModel +@addTagHelper *, BundlerMinifier.TagHelpers @{ Layout = "../Shared/_NavLayout.cshtml"; ViewData.SetActivePageAndTitle(StoreNavPages.Index, $"{Model.CryptoCode} Derivation scheme"); @@ -94,6 +95,7 @@ } + @if (Model.CanUseHotWallet) { @@ -216,7 +218,16 @@ + + } diff --git a/BTCPayServer/Views/Stores/AddDerivationSchemes_HardwareWalletDialogs.cshtml b/BTCPayServer/Views/Stores/AddDerivationSchemes_HardwareWalletDialogs.cshtml index 231df990a..1c04f7df3 100644 --- a/BTCPayServer/Views/Stores/AddDerivationSchemes_HardwareWalletDialogs.cshtml +++ b/BTCPayServer/Views/Stores/AddDerivationSchemes_HardwareWalletDialogs.cshtml @@ -6,7 +6,10 @@ } - + +
+ +