mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 09:54:30 +01:00
Add Wallet settings menu, do not rebase keypaths when create the PSBT
This commit is contained in:
parent
698033b0cf
commit
bf37f44795
@ -1644,12 +1644,13 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(psbt);
|
||||
|
||||
var root = new Mnemonic("usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage").DeriveExtKey().AsHDKeyCache();
|
||||
var account = root.Derive(new KeyPath("m/49'/0'/0'"));
|
||||
Assert.All(psbt.PSBT.Inputs, input =>
|
||||
{
|
||||
var keyPath = input.HDKeyPaths.Single();
|
||||
Assert.StartsWith(onchainBTC.AccountKeyPath.ToString(), keyPath.Value.Item2.ToString());
|
||||
Assert.Equal(root.Derive(keyPath.Value.Item2).GetPublicKey(), keyPath.Key);
|
||||
Assert.Equal(keyPath.Value.Item1, onchainBTC.RootFingerprint.Value);
|
||||
Assert.False(keyPath.Value.Item2.IsHardened);
|
||||
Assert.Equal(account.Derive(keyPath.Value.Item2).GetPublicKey(), keyPath.Key);
|
||||
Assert.Equal(keyPath.Value.Item1, onchainBTC.AccountKeySettings[0].AccountKey.GetPublicKey().GetHDFingerPrint());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NBitcoin" Version="4.1.2.20" />
|
||||
<PackageReference Include="NBitcoin" Version="4.1.2.21" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.34" />
|
||||
<PackageReference Include="DBriize" Version="1.0.0.4" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="2.0.0.12" />
|
||||
|
@ -202,10 +202,17 @@ 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);
|
||||
if (accountKey != null)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
strategy = newStrategy;
|
||||
strategy.AccountKeyPath = vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
|
||||
strategy.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint) ? (HDFingerprint?)null : new HDFingerprint(NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
|
||||
strategy.ExplicitAccountKey = string.IsNullOrEmpty(vm.AccountKey) ? null : new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
|
||||
strategy.Source = vm.Source;
|
||||
vm.DerivationScheme = strategy.AccountDerivation.ToString();
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ namespace BTCPayServer.Controllers
|
||||
psbtRequest.ExplicitChangeAddress = psbtDestination.Destination;
|
||||
}
|
||||
psbtDestination.SubstractFees = sendModel.SubstractFees;
|
||||
psbtRequest.RebaseKeyPaths = derivationSettings.GetPSBTRebaseKeyRules().ToList();
|
||||
var psbt = (await nbx.CreatePSBTAsync(derivationSettings.AccountDerivation, psbtRequest, cancellationToken));
|
||||
if (psbt == null)
|
||||
throw new NotSupportedException("You need to update your version of NBXplorer");
|
||||
|
@ -23,6 +23,7 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
@ -452,14 +453,16 @@ namespace BTCPayServer.Controllers
|
||||
if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary))
|
||||
throw new Exception($"{network.CryptoCode}: not started or fully synched");
|
||||
|
||||
var accountKey = derivationSettings.AccountKeySettings.Where(a => a.IsFullySetup()).FirstOrDefault();
|
||||
accountKey = accountKey ?? derivationSettings.AccountKeySettings.FirstOrDefault();
|
||||
// Some deployment does not have the AccountKeyPath set, let's fix this...
|
||||
if (derivationSettings.AccountKeyPath == null)
|
||||
if (accountKey.AccountKeyPath == null)
|
||||
{
|
||||
// If the saved wallet key path is not present or incorrect, let's scan the wallet to see if it can sign strategy
|
||||
var foundKeyPath = await hw.FindKeyPathFromDerivation(network,
|
||||
derivationSettings.AccountDerivation,
|
||||
normalOperationTimeout.Token);
|
||||
derivationSettings.AccountKeyPath = foundKeyPath ?? throw new HardwareWalletException($"This store is not configured to use this ledger");
|
||||
accountKey.AccountKeyPath = foundKeyPath ?? throw new HardwareWalletException($"This store is not configured to use this ledger");
|
||||
storeData.SetSupportedPaymentMethod(derivationSettings);
|
||||
await Repository.UpdateStore(storeData);
|
||||
}
|
||||
@ -468,10 +471,10 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
// Checking if ledger is right with the RootFingerprint is faster as it does not need to make a query to the parent xpub,
|
||||
// but some deployment does not have it, so let's use AccountKeyPath instead
|
||||
if (derivationSettings.RootFingerprint == null)
|
||||
if (accountKey.RootFingerprint == null)
|
||||
{
|
||||
|
||||
var actualPubKey = await hw.GetExtPubKey(network, derivationSettings.AccountKeyPath, normalOperationTimeout.Token);
|
||||
var actualPubKey = await hw.GetExtPubKey(network, accountKey.AccountKeyPath, normalOperationTimeout.Token);
|
||||
if (!derivationSettings.AccountDerivation.GetExtPubKeys().Any(p => p.GetPublicKey() == actualPubKey.GetPublicKey()))
|
||||
throw new HardwareWalletException($"This store is not configured to use this ledger");
|
||||
}
|
||||
@ -479,15 +482,15 @@ namespace BTCPayServer.Controllers
|
||||
else
|
||||
{
|
||||
var actualPubKey = await hw.GetPubKey(network, new KeyPath(), normalOperationTimeout.Token);
|
||||
if (actualPubKey.GetHDFingerPrint() != derivationSettings.RootFingerprint.Value)
|
||||
if (actualPubKey.GetHDFingerPrint() != accountKey.RootFingerprint.Value)
|
||||
throw new HardwareWalletException($"This store is not configured to use this ledger");
|
||||
}
|
||||
}
|
||||
|
||||
// Some deployment does not have the RootFingerprint set, let's fix this...
|
||||
if (derivationSettings.RootFingerprint == null)
|
||||
if (accountKey.RootFingerprint == null)
|
||||
{
|
||||
derivationSettings.RootFingerprint = (await hw.GetPubKey(network, new KeyPath(), normalOperationTimeout.Token)).GetHDFingerPrint();
|
||||
accountKey.RootFingerprint = (await hw.GetPubKey(network, new KeyPath(), normalOperationTimeout.Token)).GetHDFingerPrint();
|
||||
storeData.SetSupportedPaymentMethod(derivationSettings);
|
||||
await Repository.UpdateStore(storeData);
|
||||
}
|
||||
@ -502,7 +505,7 @@ namespace BTCPayServer.Controllers
|
||||
derivationSettings.RebaseKeyPaths(psbtResponse.PSBT);
|
||||
|
||||
signTimeout.CancelAfter(TimeSpan.FromMinutes(5));
|
||||
psbtResponse.PSBT = await hw.SignTransactionAsync(psbtResponse.PSBT, psbtResponse.ChangeAddress?.ScriptPubKey, signTimeout.Token);
|
||||
psbtResponse.PSBT = await hw.SignTransactionAsync(psbtResponse.PSBT, accountKey.RootFingerprint, accountKey.AccountKey, psbtResponse.ChangeAddress?.ScriptPubKey, signTimeout.Token);
|
||||
result = new SendToAddressResult() { PSBT = psbtResponse.PSBT.ToBase64() };
|
||||
}
|
||||
}
|
||||
@ -528,6 +531,57 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
[Route("{walletId}/settings")]
|
||||
public async Task<IActionResult> WalletSettings(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId)
|
||||
{
|
||||
var derivationSchemeSettings = await GetDerivationSchemeSettings(walletId);
|
||||
if (derivationSchemeSettings == null)
|
||||
return NotFound();
|
||||
|
||||
var vm = new WalletSettingsViewModel()
|
||||
{
|
||||
Label = derivationSchemeSettings.Label,
|
||||
DerivationScheme = derivationSchemeSettings.AccountDerivation.ToString(),
|
||||
DerivationSchemeInput = derivationSchemeSettings.AccountOriginal
|
||||
};
|
||||
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)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
var derivationScheme = await GetDerivationSchemeSettings(walletId);
|
||||
if (derivationScheme == null)
|
||||
return NotFound();
|
||||
derivationScheme.Label = vm.Label;
|
||||
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);
|
||||
StatusMessage = "Wallet settings updated";
|
||||
return RedirectToAction(nameof(WalletSettings));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -65,6 +65,9 @@ namespace BTCPayServer
|
||||
{
|
||||
result.AccountOriginal = jobj["xpub"].Value<string>().Trim();
|
||||
result.AccountDerivation = derivationSchemeParser.ParseElectrum(result.AccountOriginal);
|
||||
result.AccountKeySettings = new AccountKeySettings[1];
|
||||
result.AccountKeySettings[0] = new AccountKeySettings();
|
||||
result.AccountKeySettings[0].AccountKey = result.AccountDerivation.GetExtPubKeys().Single().GetWif(network.NBitcoinNetwork);
|
||||
if (result.AccountDerivation is DirectDerivationStrategy direct && !direct.Segwit)
|
||||
result.AccountOriginal = null; // Saving this would be confusing for user, as xpub of electrum is legacy derivation, but for btcpay, it is segwit derivation
|
||||
}
|
||||
@ -91,7 +94,7 @@ namespace BTCPayServer
|
||||
{
|
||||
try
|
||||
{
|
||||
result.RootFingerprint = new HDFingerprint(jobj["ckcc_xfp"].Value<uint>());
|
||||
result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(jobj["ckcc_xfp"].Value<uint>());
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
@ -100,7 +103,7 @@ namespace BTCPayServer
|
||||
{
|
||||
try
|
||||
{
|
||||
result.AccountKeyPath = new KeyPath(jobj["derivation"].Value<string>());
|
||||
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["derivation"].Value<string>());
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
@ -121,20 +124,32 @@ namespace BTCPayServer
|
||||
throw new ArgumentNullException(nameof(derivationStrategy));
|
||||
AccountDerivation = derivationStrategy;
|
||||
Network = network;
|
||||
AccountKeySettings = derivationStrategy.GetExtPubKeys().Select(c => new AccountKeySettings()
|
||||
{
|
||||
AccountKey = c.GetWif(network.NBitcoinNetwork)
|
||||
}).ToArray();
|
||||
}
|
||||
[JsonIgnore]
|
||||
public BTCPayNetwork Network { get; set; }
|
||||
public string Source { get; set; }
|
||||
|
||||
[Obsolete("Use GetAccountKeySettings().AccountKeyPath instead")]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public KeyPath AccountKeyPath { get; set; }
|
||||
|
||||
public DerivationStrategyBase AccountDerivation { get; set; }
|
||||
public string AccountOriginal { get; set; }
|
||||
|
||||
[Obsolete("Use GetAccountKeySettings().RootFingerprint instead")]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public HDFingerprint? RootFingerprint { get; set; }
|
||||
|
||||
[Obsolete("Use GetAccountKeySettings().AccountKey instead")]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public BitcoinExtPubKey ExplicitAccountKey { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[Obsolete("Use GetAccountKeySettings().AccountKey instead")]
|
||||
public BitcoinExtPubKey AccountKey
|
||||
{
|
||||
get
|
||||
@ -143,16 +158,49 @@ namespace BTCPayServer
|
||||
}
|
||||
}
|
||||
|
||||
AccountKeySettings[] _AccountKeySettings;
|
||||
public AccountKeySettings[] AccountKeySettings
|
||||
{
|
||||
get
|
||||
{
|
||||
// Legacy
|
||||
if (_AccountKeySettings == null)
|
||||
{
|
||||
if (this.Network == null)
|
||||
return null;
|
||||
_AccountKeySettings = AccountDerivation.GetExtPubKeys().Select(e => new AccountKeySettings()
|
||||
{
|
||||
AccountKey = e.GetWif(this.Network.NBitcoinNetwork),
|
||||
}).ToArray();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
_AccountKeySettings[0].AccountKeyPath = AccountKeyPath;
|
||||
_AccountKeySettings[0].RootFingerprint = RootFingerprint;
|
||||
ExplicitAccountKey = null;
|
||||
AccountKeyPath = null;
|
||||
RootFingerprint = null;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
return _AccountKeySettings;
|
||||
}
|
||||
set
|
||||
{
|
||||
_AccountKeySettings = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<NBXplorer.Models.PSBTRebaseKeyRules> GetPSBTRebaseKeyRules()
|
||||
{
|
||||
if (AccountKey != null && AccountKeyPath != null && RootFingerprint is HDFingerprint fp)
|
||||
foreach(var accountKey in AccountKeySettings)
|
||||
{
|
||||
yield return new NBXplorer.Models.PSBTRebaseKeyRules()
|
||||
if (accountKey.AccountKeyPath != null && accountKey.RootFingerprint is HDFingerprint fp)
|
||||
{
|
||||
AccountKey = AccountKey,
|
||||
AccountKeyPath = AccountKeyPath,
|
||||
MasterFingerprint = fp
|
||||
};
|
||||
yield return new NBXplorer.Models.PSBTRebaseKeyRules()
|
||||
{
|
||||
AccountKey = accountKey.AccountKey,
|
||||
AccountKeyPath = accountKey.AccountKeyPath,
|
||||
MasterFingerprint = fp
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,4 +233,14 @@ namespace BTCPayServer
|
||||
}
|
||||
}
|
||||
}
|
||||
public class AccountKeySettings
|
||||
{
|
||||
public HDFingerprint? RootFingerprint { get; set; }
|
||||
public KeyPath AccountKeyPath { get; set; }
|
||||
public BitcoinExtPubKey AccountKey { get; set; }
|
||||
public bool IsFullySetup()
|
||||
{
|
||||
return AccountKeyPath != null && RootFingerprint is HDFingerprint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.WalletViewModels
|
||||
{
|
||||
public class WalletSettingsViewModel
|
||||
{
|
||||
public string Label { get; set; }
|
||||
public string DerivationScheme { get; set; }
|
||||
public string DerivationSchemeInput { get; set; }
|
||||
|
||||
public List<WalletSettingsAccountKeyViewModel> AccountKeys { get; set; } = new List<WalletSettingsAccountKeyViewModel>();
|
||||
}
|
||||
|
||||
public class WalletSettingsAccountKeyViewModel
|
||||
{
|
||||
public string AccountKey { get; set; }
|
||||
[Validation.HDFingerPrintValidator]
|
||||
public string MasterFingerprint { get; set; }
|
||||
[Validation.KeyPathValidator]
|
||||
public string AccountKeyPath { get; set; }
|
||||
}
|
||||
}
|
@ -62,8 +62,7 @@ namespace BTCPayServer.Services
|
||||
return foundKeyPath;
|
||||
}
|
||||
|
||||
public abstract Task<PSBT> SignTransactionAsync(PSBT psbt, Script changeHint,
|
||||
CancellationToken cancellationToken);
|
||||
public abstract Task<PSBT> SignTransactionAsync(PSBT psbt, HDFingerprint? rootFingerprint, BitcoinExtPubKey accountKey, Script changeHint, CancellationToken cancellationToken);
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
|
@ -114,25 +114,34 @@ namespace BTCPayServer.Services
|
||||
account.Indexes.Length == 0 ? 0 : account.Indexes.Last()).GetWif(network.NBitcoinNetwork);
|
||||
return extpubkey;
|
||||
}
|
||||
|
||||
public override async Task<PSBT> SignTransactionAsync(PSBT psbt, Script changeHint, CancellationToken cancellationToken)
|
||||
class HDKey
|
||||
{
|
||||
public PubKey PubKey { get; set; }
|
||||
public KeyPath KeyPath { get; set; }
|
||||
}
|
||||
public override async Task<PSBT> SignTransactionAsync(PSBT psbt, HDFingerprint? rootFingerprint, BitcoinExtPubKey accountKey, Script changeHint, CancellationToken cancellationToken)
|
||||
{
|
||||
HashSet<HDFingerprint> knownFingerprints = new HashSet<HDFingerprint>();
|
||||
knownFingerprints.Add(accountKey.GetPublicKey().GetHDFingerPrint());
|
||||
if (rootFingerprint is HDFingerprint fp)
|
||||
knownFingerprints.Add(fp);
|
||||
var unsigned = psbt.GetGlobalTransaction();
|
||||
var changeKeyPath = psbt.Outputs
|
||||
.Where(o => changeHint == null ? true : changeHint == o.ScriptPubKey)
|
||||
.Where(o => o.HDKeyPaths.Any())
|
||||
.Select(o => o.HDKeyPaths.First().Value.Item2)
|
||||
.Select(o => (Output: o, HDKey: GetHDKey(knownFingerprints, accountKey, o)))
|
||||
.Where(o => o.HDKey != null)
|
||||
.Select(o => o.HDKey.KeyPath)
|
||||
.FirstOrDefault();
|
||||
var signatureRequests = psbt
|
||||
.Inputs
|
||||
.Where(o => o.HDKeyPaths.Any())
|
||||
.Where(o => !o.PartialSigs.ContainsKey(o.HDKeyPaths.First().Key))
|
||||
.Select(i => (Input: i, HDKey: GetHDKey(knownFingerprints, accountKey, i)))
|
||||
.Where(i => i.HDKey != null)
|
||||
.Select(i => new SignatureRequest()
|
||||
{
|
||||
InputCoin = i.GetSignableCoin(),
|
||||
InputTransaction = i.NonWitnessUtxo,
|
||||
KeyPath = i.HDKeyPaths.First().Value.Item2,
|
||||
PubKey = i.HDKeyPaths.First().Key
|
||||
InputCoin = i.Input.GetSignableCoin(),
|
||||
InputTransaction = i.Input.NonWitnessUtxo,
|
||||
KeyPath = i.HDKey.KeyPath,
|
||||
PubKey = i.HDKey.PubKey
|
||||
}).ToArray();
|
||||
var signedTransaction = await Ledger.SignTransactionAsync(signatureRequests, unsigned, changeKeyPath, cancellationToken);
|
||||
if (signedTransaction == null)
|
||||
@ -151,6 +160,22 @@ namespace BTCPayServer.Services
|
||||
return psbt;
|
||||
}
|
||||
|
||||
private HDKey GetHDKey(HashSet<HDFingerprint> knownFingerprints, BitcoinExtPubKey accountKey, PSBTCoin coin)
|
||||
{
|
||||
// Check if the accountKey match this coin by checking if the non hardened last part of the path
|
||||
// can derive the same pubkey
|
||||
foreach (var key in coin.HDKeyPaths)
|
||||
{
|
||||
if (!knownFingerprints.Contains(key.Value.Item1))
|
||||
continue;
|
||||
var accountKeyPath = key.Value.Item2.GetAccountKeyPath();
|
||||
// We might have a fingerprint collision, let's check
|
||||
if (accountKey.ExtPubKey.Derive(accountKeyPath).GetPublicKey() == key.Key)
|
||||
return new HDKey() { KeyPath = key.Value.Item2, PubKey = key.Key };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_Transport != null)
|
||||
|
32
BTCPayServer/Validation/HDFingerPrintValidator.cs
Normal file
32
BTCPayServer/Validation/HDFingerPrintValidator.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
|
||||
namespace BTCPayServer.Validation
|
||||
{
|
||||
public class HDFingerPrintValidator : ValidationAttribute
|
||||
{
|
||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||
{
|
||||
var str = value as string;
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
new HDFingerprint(Encoders.Hex.DecodeData(str));
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ValidationResult("Invalid fingerprint");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
BTCPayServer/Validation/KeyPathValidator.cs
Normal file
29
BTCPayServer/Validation/KeyPathValidator.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Validation
|
||||
{
|
||||
public class KeyPathValidator : ValidationAttribute
|
||||
{
|
||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||
{
|
||||
var str = value as string;
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
if (KeyPath.TryParse(str, out _))
|
||||
{
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ValidationResult("Invalid keypath");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
70
BTCPayServer/Views/Wallets/WalletSettings.cshtml
Normal file
70
BTCPayServer/Views/Wallets/WalletSettings.cshtml
Normal file
@ -0,0 +1,70 @@
|
||||
@model WalletSettingsViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Wallet settings";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.Settings);
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-center">
|
||||
<partial name="_StatusMessage" for="@TempData["TempDataProperty-StatusMessage"]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<p>
|
||||
Additional information about your wallet
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="post" asp-action="WalletSettings">
|
||||
<div class="form-group">
|
||||
<label asp-for="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"></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"></label>
|
||||
<input asp-for="DerivationSchemeInput" class="form-control" readonly />
|
||||
<span asp-validation-for="DerivationSchemeInput" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@for (int i = 0; i < Model.AccountKeys.Count; i++)
|
||||
{
|
||||
<hr />
|
||||
<h5>Account key @i</h5>
|
||||
<div class="form-group">
|
||||
<label asp-for="@Model.AccountKeys[i].AccountKey"></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="form-group">
|
||||
<label asp-for="@Model.AccountKeys[i].MasterFingerprint"></label>
|
||||
<input asp-for="@Model.AccountKeys[i].MasterFingerprint" class="form-control" />
|
||||
<span asp-validation-for="@Model.AccountKeys[i].MasterFingerprint" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="@Model.AccountKeys[i].AccountKeyPath"></label>
|
||||
<input asp-for="@Model.AccountKeys[i].AccountKeyPath" class="form-control" />
|
||||
<span asp-validation-for="@Model.AccountKeys[i].AccountKeyPath" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="form-group">
|
||||
<button name="command" type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -10,6 +10,7 @@ namespace BTCPayServer.Views.Wallets
|
||||
Send,
|
||||
Transactions,
|
||||
Rescan,
|
||||
PSBT
|
||||
PSBT,
|
||||
Settings
|
||||
}
|
||||
}
|
||||
|
@ -5,5 +5,6 @@
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Send)" asp-action="WalletSend">Send</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Rescan)" asp-action="WalletRescan">Rescan</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.PSBT)" asp-action="WalletPSBT">PSBT</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Settings)" asp-action="WalletSettings">Settings</a>
|
||||
</div>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user