diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index ccd7aec85..980b57cf6 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -72,7 +72,7 @@ namespace BTCPayServer.Tests { if (!Directory.Exists(_Directory)) Directory.CreateDirectory(_Directory); - string chain = ChainType.Regtest.ToNetwork().Name; + string chain = NBXplorerDefaultSettings.GetFolderName(NetworkType.Regtest); string chainDirectory = Path.Combine(_Directory, chain); if (!Directory.Exists(chainDirectory)) Directory.CreateDirectory(chainDirectory); diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 2e3c4f651..957501fe4 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -39,7 +39,7 @@ namespace BTCPayServer.Tests if (!Directory.Exists(_Directory)) Directory.CreateDirectory(_Directory); - NetworkProvider = new BTCPayNetworkProvider(ChainType.Regtest); + NetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest); ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("BTC").NBitcoinNetwork); LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("LTC").NBitcoinNetwork); diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 7571bf6f4..78477346d 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -632,7 +632,7 @@ namespace BTCPayServer.Tests var bitflyer2 = CreateInvoice(tester, user, "bitflyer"); Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache rates.Add(bitflyer); - + foreach(var rate in rates) { Assert.Single(rates.Where(r => r == rate)); @@ -873,7 +873,7 @@ namespace BTCPayServer.Tests [Fact] public void CanParseDerivationScheme() { - var parser = new DerivationSchemeParser(Network.TestNet, NBXplorer.ChainType.Test); + var parser = new DerivationSchemeParser(Network.TestNet); NBXplorer.DerivationStrategy.DerivationStrategyBase result; // Passing electrum stuff // Native diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index 583d5a9b0..ec572053d 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -46,7 +46,7 @@ services: - lightning-charged nbxplorer: - image: nicolasdorier/nbxplorer:1.0.1.34 + image: nicolasdorier/nbxplorer:1.0.2.0 ports: - "32838:32838" expose: diff --git a/BTCPayServer/BTCPayNetwork.cs b/BTCPayServer/BTCPayNetwork.cs index 7a7cf1614..540a090c8 100644 --- a/BTCPayServer/BTCPayNetwork.cs +++ b/BTCPayServer/BTCPayNetwork.cs @@ -14,34 +14,28 @@ namespace BTCPayServer { static BTCPayDefaultSettings() { - _Settings = new Dictionary(); - foreach (var chainType in new[] { ChainType.Main, ChainType.Test, ChainType.Regtest }) + _Settings = new Dictionary(); + foreach (var chainType in new[] { NetworkType.Mainnet, NetworkType.Testnet, NetworkType.Regtest }) { - var btcNetwork = (chainType == ChainType.Main ? Network.Main : - chainType == ChainType.Regtest ? Network.RegTest : - chainType == ChainType.Test ? Network.TestNet : throw new NotSupportedException(chainType.ToString())); - var settings = new BTCPayDefaultSettings(); _Settings.Add(chainType, settings); - settings.ChainType = chainType; - settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", btcNetwork.Name); + settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType)); settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config"); - settings.DefaultPort = (chainType == ChainType.Main ? 23000 : - chainType == ChainType.Regtest ? 23002 : - chainType == ChainType.Test ? 23001 : throw new NotSupportedException(chainType.ToString())); + settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 : + chainType == NetworkType.Regtest ? 23002 : + chainType == NetworkType.Testnet ? 23001 : throw new NotSupportedException(chainType.ToString())); } } - static Dictionary _Settings; + static Dictionary _Settings; - public static BTCPayDefaultSettings GetDefaultSettings(ChainType chainType) + public static BTCPayDefaultSettings GetDefaultSettings(NetworkType chainType) { return _Settings[chainType]; } public string DefaultDataDirectory { get; set; } public string DefaultConfigurationFile { get; set; } - public ChainType ChainType { get; internal set; } public int DefaultPort { get; set; } } public class BTCPayNetwork diff --git a/BTCPayServer/BTCPayNetworkProvider.Bitcoin.cs b/BTCPayServer/BTCPayNetworkProvider.Bitcoin.cs index 17fb163c0..ca803f0a4 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Bitcoin.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Bitcoin.cs @@ -20,15 +20,15 @@ namespace BTCPayServer Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, - BlockExplorerLink = NBXplorerNetworkProvider.ChainType == ChainType.Main ? "https://www.smartbit.com.au/tx/{0}" : "https://testnet.smartbit.com.au/tx/{0}", + BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://www.smartbit.com.au/tx/{0}" : "https://testnet.smartbit.com.au/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, UriScheme = "bitcoin", DefaultRateProvider = btcRate, CryptoImagePath = "imlegacy/bitcoin-symbol.svg", LightningImagePath = "imlegacy/btc-lightning.svg", - DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType), - CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("0'") : new KeyPath("1'") + DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), + CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("0'") : new KeyPath("1'") }); } } diff --git a/BTCPayServer/BTCPayNetworkProvider.Dogecoin.cs b/BTCPayServer/BTCPayNetworkProvider.Dogecoin.cs index 8a388d8fa..18091ad91 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Dogecoin.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Dogecoin.cs @@ -12,20 +12,18 @@ namespace BTCPayServer { public void InitDogecoin() { - NBitcoin.Altcoins.Dogecoin.EnsureRegistered(); - var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("DOGE"); Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, - BlockExplorerLink = NBXplorerNetworkProvider.ChainType == ChainType.Main ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}", + BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, UriScheme = "dogecoin", DefaultRateProvider = new CoinAverageRateProviderDescription("DOGE"), CryptoImagePath = "imlegacy/dogecoin.png", - DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType), - CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("3'") : new KeyPath("1'") + DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), + CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'") }); } } diff --git a/BTCPayServer/BTCPayNetworkProvider.Litecoin.cs b/BTCPayServer/BTCPayNetworkProvider.Litecoin.cs index b95fff3c6..98550f9e0 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Litecoin.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Litecoin.cs @@ -12,21 +12,19 @@ namespace BTCPayServer { public void InitLitecoin() { - NBitcoin.Altcoins.Litecoin.EnsureRegistered(); - var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LTC"); Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, - BlockExplorerLink = NBXplorerNetworkProvider.ChainType == ChainType.Main ? "https://live.blockcypher.com/ltc/tx/{0}/" : "http://explorer.litecointools.com/tx/{0}", + BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://live.blockcypher.com/ltc/tx/{0}/" : "http://explorer.litecointools.com/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, UriScheme = "litecoin", DefaultRateProvider = new CoinAverageRateProviderDescription("LTC"), CryptoImagePath = "imlegacy/litecoin-symbol.svg", LightningImagePath = "imlegacy/ltc-lightning.svg", - DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType), - CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("2'") : new KeyPath("1'") + DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), + CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("2'") : new KeyPath("1'") }); } } diff --git a/BTCPayServer/BTCPayNetworkProvider.cs b/BTCPayServer/BTCPayNetworkProvider.cs index 523eebd84..6e640f763 100644 --- a/BTCPayServer/BTCPayNetworkProvider.cs +++ b/BTCPayServer/BTCPayNetworkProvider.cs @@ -27,8 +27,8 @@ namespace BTCPayServer BTCPayNetworkProvider(BTCPayNetworkProvider filtered, string[] cryptoCodes) { - ChainType = filtered.ChainType; - _NBXplorerNetworkProvider = new NBXplorerNetworkProvider(filtered.ChainType); + NetworkType = filtered.NetworkType; + _NBXplorerNetworkProvider = new NBXplorerNetworkProvider(filtered.NetworkType); _Networks = new Dictionary(); cryptoCodes = cryptoCodes.Select(c => c.ToUpperInvariant()).ToArray(); foreach (var network in filtered._Networks) @@ -40,11 +40,11 @@ namespace BTCPayServer } } - public ChainType ChainType { get; set; } - public BTCPayNetworkProvider(ChainType chainType) + public NetworkType NetworkType { get; private set; } + public BTCPayNetworkProvider(NetworkType networkType) { - _NBXplorerNetworkProvider = new NBXplorerNetworkProvider(chainType); - ChainType = chainType; + _NBXplorerNetworkProvider = new NBXplorerNetworkProvider(networkType); + NetworkType = networkType; InitBitcoin(); InitLitecoin(); InitDogecoin(); diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 767120e1e..b9d7b5fb4 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - 1.0.1.85 + 1.0.1.88 NU1701,CA1816,CA1308,CA1810,CA2208 @@ -39,10 +39,10 @@ - + - + @@ -66,7 +66,7 @@ - + @@ -112,6 +112,7 @@ + diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs index 804e77fa5..2ed2e665d 100644 --- a/BTCPayServer/Configuration/BTCPayServerOptions.cs +++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs @@ -23,7 +23,7 @@ namespace BTCPayServer.Configuration public class BTCPayServerOptions { - public ChainType ChainType + public NetworkType NetworkType { get; set; } @@ -51,15 +51,15 @@ namespace BTCPayServer.Configuration public void LoadArgs(IConfiguration conf) { - ChainType = DefaultConfiguration.GetChainType(conf); - var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(ChainType); + NetworkType = DefaultConfiguration.GetNetworkType(conf); + var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType); DataDir = conf.GetOrDefault("datadir", defaultSettings.DefaultDataDirectory); - Logs.Configuration.LogInformation("Network: " + ChainType.ToString()); + Logs.Configuration.LogInformation("Network: " + NetworkType.ToString()); var supportedChains = conf.GetOrDefault("chains", "btc") .Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(t => t.ToUpperInvariant()); - NetworkProvider = new BTCPayNetworkProvider(ChainType).Filter(supportedChains.ToArray()); + NetworkProvider = new BTCPayNetworkProvider(NetworkType).Filter(supportedChains.ToArray()); foreach (var chain in supportedChains) { if (NetworkProvider.GetNetwork(chain) == null) diff --git a/BTCPayServer/Configuration/DefaultConfiguration.cs b/BTCPayServer/Configuration/DefaultConfiguration.cs index a25fb5bbd..d4bb141a3 100644 --- a/BTCPayServer/Configuration/DefaultConfiguration.cs +++ b/BTCPayServer/Configuration/DefaultConfiguration.cs @@ -18,7 +18,7 @@ namespace BTCPayServer.Configuration { protected override CommandLineApplication CreateCommandLineApplicationCore() { - var provider = new BTCPayNetworkProvider(ChainType.Main); + var provider = new BTCPayNetworkProvider(NetworkType.Mainnet); var chains = string.Join(",", provider.GetAll().Select(n => n.CryptoCode.ToLowerInvariant()).ToArray()); CommandLineApplication app = new CommandLineApplication(true) { @@ -48,12 +48,12 @@ namespace BTCPayServer.Configuration protected override string GetDefaultDataDir(IConfiguration conf) { - return BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf)).DefaultDataDirectory; + return BTCPayDefaultSettings.GetDefaultSettings(GetNetworkType(conf)).DefaultDataDirectory; } protected override string GetDefaultConfigurationFile(IConfiguration conf) { - var network = BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf)); + var network = BTCPayDefaultSettings.GetDefaultSettings(GetNetworkType(conf)); var dataDir = conf["datadir"]; if (dataDir == null) return network.DefaultConfigurationFile; @@ -69,7 +69,7 @@ namespace BTCPayServer.Configuration return Path.Combine(chainDir, fileName); } - public static ChainType GetChainType(IConfiguration conf) + public static NetworkType GetNetworkType(IConfiguration conf) { var network = conf.GetOrDefault("network", null); if (network != null) @@ -79,17 +79,18 @@ namespace BTCPayServer.Configuration { throw new ConfigException($"Invalid network parameter '{network}'"); } - return n.ToChainType(); + return n.NetworkType; } - var net = conf.GetOrDefault("regtest", false) ? ChainType.Regtest : - conf.GetOrDefault("testnet", false) ? ChainType.Test : ChainType.Main; + var net = conf.GetOrDefault("regtest", false) ? NetworkType.Regtest : + conf.GetOrDefault("testnet", false) ? NetworkType.Testnet : NetworkType.Mainnet; return net; } protected override string GetDefaultConfigurationFileTemplate(IConfiguration conf) { - var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf)); + var networkType = GetNetworkType(conf); + var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType); StringBuilder builder = new StringBuilder(); builder.AppendLine("### Global settings ###"); builder.AppendLine("#network=mainnet"); @@ -102,7 +103,7 @@ namespace BTCPayServer.Configuration builder.AppendLine("#postgres=User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;"); builder.AppendLine(); builder.AppendLine("### NBXplorer settings ###"); - foreach (var n in new BTCPayNetworkProvider(defaultSettings.ChainType).GetAll()) + foreach (var n in new BTCPayNetworkProvider(networkType).GetAll()) { builder.AppendLine($"#{n.CryptoCode}.explorer.url={n.NBXplorerNetwork.DefaultSettings.DefaultUrl}"); builder.AppendLine($"#{n.CryptoCode}.explorer.cookiefile={ n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}"); @@ -116,7 +117,7 @@ namespace BTCPayServer.Configuration protected override IPEndPoint GetDefaultEndpoint(IConfiguration conf) { - return new IPEndPoint(IPAddress.Parse("127.0.0.1"), BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf)).DefaultPort); + return new IPEndPoint(IPAddress.Parse("127.0.0.1"), BTCPayDefaultSettings.GetDefaultSettings(GetNetworkType(conf)).DefaultPort); } } } diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 91c39408d..4fb199d07 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -85,54 +85,66 @@ namespace BTCPayServer.Controllers model.CryptoPayments.Add(cryptoPayment); } - var payments = invoice + var onChainPayments = invoice .GetPayments() - .Where(p => p.GetPaymentMethodId().PaymentType == PaymentTypes.BTCLike) - .Select(async payment => + .Select>(async payment => { - var paymentData = (Payments.Bitcoin.BitcoinLikePaymentData)payment.GetCryptoPaymentData(); - var m = new InvoiceDetailsModel.Payment(); var paymentNetwork = _NetworkProvider.GetNetwork(payment.GetCryptoCode()); - m.PaymentMethod = ToString(payment.GetPaymentMethodId()); - m.DepositAddress = paymentData.Output.ScriptPubKey.GetDestinationAddress(paymentNetwork.NBitcoinNetwork); - - int confirmationCount = 0; - if ( (paymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted) - && (paymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) // The confirmation count in the paymentData is not up to date + var paymentData = payment.GetCryptoPaymentData(); + if (paymentData is Payments.Bitcoin.BitcoinLikePaymentData onChainPaymentData) { - confirmationCount = (await ((ExplorerClientProvider)_ServiceProvider.GetService(typeof(ExplorerClientProvider))).GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(paymentData.Outpoint.Hash))?.Confirmations ?? 0; - paymentData.ConfirmationCount = confirmationCount; - payment.SetCryptoPaymentData(paymentData); - await _InvoiceRepository.UpdatePayments(new List { payment }); + var m = new InvoiceDetailsModel.Payment(); + m.Crypto = payment.GetPaymentMethodId().CryptoCode; + m.DepositAddress = onChainPaymentData.Output.ScriptPubKey.GetDestinationAddress(paymentNetwork.NBitcoinNetwork); + + int confirmationCount = 0; + if ((onChainPaymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted) + && (onChainPaymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) // The confirmation count in the paymentData is not up to date + { + confirmationCount = (await ((ExplorerClientProvider)_ServiceProvider.GetService(typeof(ExplorerClientProvider))).GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(onChainPaymentData.Outpoint.Hash))?.Confirmations ?? 0; + onChainPaymentData.ConfirmationCount = confirmationCount; + payment.SetCryptoPaymentData(onChainPaymentData); + await _InvoiceRepository.UpdatePayments(new List { payment }); + } + else + { + confirmationCount = onChainPaymentData.ConfirmationCount; + } + if (confirmationCount >= paymentNetwork.MaxTrackedConfirmation) + { + m.Confirmations = "At least " + (paymentNetwork.MaxTrackedConfirmation); + } + else + { + m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture); + } + + m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString(); + m.ReceivedTime = payment.ReceivedTime; + m.TransactionLink = string.Format(CultureInfo.InvariantCulture, paymentNetwork.BlockExplorerLink, m.TransactionId); + m.Replaced = !payment.Accounted; + return m; } else { - confirmationCount = paymentData.ConfirmationCount; + var lightningPaymentData = (Payments.Lightning.LightningLikePaymentData)paymentData; + return new InvoiceDetailsModel.OffChainPayment() + { + Crypto = paymentNetwork.CryptoCode, + BOLT11 = lightningPaymentData.BOLT11 + }; } - if (confirmationCount >= paymentNetwork.MaxTrackedConfirmation) - { - m.Confirmations = "At least " + (paymentNetwork.MaxTrackedConfirmation); - } - else - { - m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture); - } - - m.TransactionId = paymentData.Outpoint.Hash.ToString(); - m.ReceivedTime = payment.ReceivedTime; - m.TransactionLink = string.Format(CultureInfo.InvariantCulture, paymentNetwork.BlockExplorerLink, m.TransactionId); - m.Replaced = !payment.Accounted; - return m; }) .ToArray(); - await Task.WhenAll(payments); + await Task.WhenAll(onChainPayments); model.Addresses = invoice.HistoricalAddresses.Select(h => new InvoiceDetailsModel.AddressModel { Destination = h.GetAddress(), PaymentMethod = ToString(h.GetPaymentMethodId()), Current = !h.UnAssigned.HasValue }).ToArray(); - model.Payments = payments.Select(p => p.GetAwaiter().GetResult()).ToList(); + model.OnChainPayments = onChainPayments.Select(p => p.GetAwaiter().GetResult()).OfType().ToList(); + model.OffChainPayments = onChainPayments.Select(p => p.GetAwaiter().GetResult()).OfType().ToList(); model.StatusMessage = StatusMessage; return View(model); } @@ -253,7 +265,7 @@ namespace BTCPayServer.Controllers }; var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds); - model.TimeLeft = PrettyPrint(expiration); + model.TimeLeft = expiration.PrettyPrint(); return model; } @@ -272,17 +284,6 @@ namespace BTCPayServer.Controllers return price.ToString("C", _CurrencyNameTable.GetCurrencyProvider(currency)) + $" ({currency})"; } - private string PrettyPrint(TimeSpan expiration) - { - StringBuilder builder = new StringBuilder(); - if (expiration.Days >= 1) - builder.Append(expiration.Days.ToString(CultureInfo.InvariantCulture)); - if (expiration.Hours >= 1) - builder.Append(expiration.Hours.ToString("00", CultureInfo.InvariantCulture)); - builder.Append($"{expiration.Minutes.ToString("00", CultureInfo.InvariantCulture)}:{expiration.Seconds.ToString("00", CultureInfo.InvariantCulture)}"); - return builder.ToString(); - } - [HttpGet] [Route("i/{invoiceId}/status")] [Route("i/{invoiceId}/{paymentMethodId}/status")] @@ -373,7 +374,7 @@ namespace BTCPayServer.Controllers model.Invoices.Add(new InvoiceModel() { Status = invoice.Status, - Date = Prettify(invoice.InvoiceTime), + Date = (DateTimeOffset.UtcNow - invoice.InvoiceTime).Prettify() + " ago", InvoiceId = invoice.Id, OrderId = invoice.OrderId ?? string.Empty, RedirectUrl = invoice.RedirectURL ?? string.Empty, @@ -386,30 +387,6 @@ namespace BTCPayServer.Controllers return View(model); } - private string Prettify(DateTimeOffset invoiceTime) - { - var ago = DateTime.UtcNow - invoiceTime; - - if (ago.TotalMinutes < 1) - { - return $"{(int)ago.TotalSeconds} second{Plural((int)ago.TotalSeconds)} ago"; - } - if (ago.TotalHours < 1) - { - return $"{(int)ago.TotalMinutes} minute{Plural((int)ago.TotalMinutes)} ago"; - } - if (ago.Days < 1) - { - return $"{(int)ago.TotalHours} hour{Plural((int)ago.TotalHours)} ago"; - } - return $"{(int)ago.TotalDays} day{Plural((int)ago.TotalDays)} ago"; - } - - private string Plural(int totalDays) - { - return totalDays > 1 ? "s" : string.Empty; - } - [HttpGet] [Route("invoices/create")] [Authorize(AuthenticationSchemes = "Identity.Application")] @@ -450,7 +427,7 @@ namespace BTCPayServer.Controllers return View(model); } - if(StatusMessage != null) + if (StatusMessage != null) { return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new { diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index de4bf4b49..b13839ab7 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -1,4 +1,5 @@ -using BTCPayServer.Models; +using BTCPayServer.HostedServices; +using BTCPayServer.Models; using BTCPayServer.Models.ServerViewModels; using BTCPayServer.Services; using BTCPayServer.Services.Mails; @@ -24,45 +25,47 @@ namespace BTCPayServer.Controllers private UserManager _UserManager; SettingsRepository _SettingsRepository; private IRateProviderFactory _RateProviderFactory; + private CssThemeManager _CssThemeManager; public ServerController(UserManager userManager, IRateProviderFactory rateProviderFactory, - SettingsRepository settingsRepository) + SettingsRepository settingsRepository, + CssThemeManager cssThemeManager) { _UserManager = userManager; _SettingsRepository = settingsRepository; _RateProviderFactory = rateProviderFactory; + _CssThemeManager = cssThemeManager; } [Route("server/rates")] public async Task Rates() { var rates = (await _SettingsRepository.GetSettingAsync()) ?? new RatesSetting(); - return View(new RatesViewModel() + + var vm = new RatesViewModel() { CacheMinutes = rates.CacheInMinutes, PrivateKey = rates.PrivateKey, PublicKey = rates.PublicKey - }); - + }; + await FetchRateLimits(vm); + return View(vm); } - class TestCoinAverageAuthenticator : ICoinAverageAuthenticator + private static async Task FetchRateLimits(RatesViewModel vm) { - private RatesSetting settings; - - public TestCoinAverageAuthenticator(RatesSetting settings) + var coinAverage = GetCoinaverageService(vm, false); + if (coinAverage != null) { - this.settings = settings; - } - public Task AddHeader(HttpRequestMessage message) - { - var sig = settings.GetCoinAverageSignature(); - if (sig != null) - message.Headers.Add("X-signature", settings.GetCoinAverageSignature()); - return Task.CompletedTask; + try + { + vm.RateLimits = await coinAverage.GetRateLimitsAsync(); + } + catch { } } } + [Route("server/rates")] [HttpPost] public async Task Rates(RatesViewModel vm) @@ -73,24 +76,38 @@ namespace BTCPayServer.Controllers rates.CacheInMinutes = vm.CacheMinutes; try { - if (rates.GetCoinAverageSignature() != null) - { - await new CoinAverageRateProvider("BTC") - { Authenticator = new TestCoinAverageAuthenticator(rates) }.TestAuthAsync(); - } + var service = GetCoinaverageService(vm, true); + if(service != null) + await service.TestAuthAsync(); } catch { ModelState.AddModelError(nameof(vm.PrivateKey), "Invalid API key pair"); } if (!ModelState.IsValid) + { + await FetchRateLimits(vm); return View(vm); + } await _SettingsRepository.UpdateSetting(rates); - ((BTCPayRateProviderFactory)_RateProviderFactory).CacheSpan = TimeSpan.FromMinutes(vm.CacheMinutes); StatusMessage = "Rate settings successfully updated"; return RedirectToAction(nameof(Rates)); } + private static CoinAverageRateProvider GetCoinaverageService(RatesViewModel vm, bool withAuth) + { + var settings = new CoinAverageSettings() + { + KeyPair = (vm.PublicKey, vm.PrivateKey) + }; + if (!withAuth || settings.GetCoinAverageSignature() != null) + { + return new CoinAverageRateProvider("BTC") + { Authenticator = settings }; + } + return null; + } + [Route("server/users")] public IActionResult ListUsers() { @@ -115,6 +132,7 @@ namespace BTCPayServer.Controllers var roles = await _UserManager.GetRolesAsync(user); var userVM = new UserViewModel(); userVM.Id = user.Id; + userVM.Email = user.Email; userVM.IsAdmin = IsAdmin(roles); return View(userVM); } @@ -201,7 +219,25 @@ namespace BTCPayServer.Controllers public async Task Policies(PoliciesSettings settings) { await _SettingsRepository.UpdateSetting(settings); - TempData["StatusMessage"] = "Policies upadated successfully"; + TempData["StatusMessage"] = "Policies updated successfully"; + return View(settings); + } + + [Route("server/theme")] + public async Task Theme() + { + var data = (await _SettingsRepository.GetSettingAsync()) ?? new ThemeSettings(); + return View(data); + } + [Route("server/theme")] + [HttpPost] + public async Task Theme(ThemeSettings settings) + { + await _SettingsRepository.UpdateSetting(settings); + // TODO: remove controller/class-level property and have only reference to + // CssThemeManager here in this method + _CssThemeManager.Update(settings); + TempData["StatusMessage"] = "Theme settings updated successfully"; return View(settings); } diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 1c1ae5e90..936172127 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -5,6 +5,7 @@ using BTCPayServer.HostedServices; using BTCPayServer.Models; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Services; +using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Authorization; @@ -47,7 +48,8 @@ namespace BTCPayServer.Controllers ExplorerClientProvider explorerProvider, IFeeProviderFactory feeRateProvider, LanguageService langService, - IHostingEnvironment env) + IHostingEnvironment env, + CoinAverageSettings coinAverage) { _Dashboard = dashboard; _Repo = repo; @@ -64,7 +66,9 @@ namespace BTCPayServer.Controllers _ServiceProvider = serviceProvider; _BtcpayServerOptions = btcpayServerOptions; _BTCPayEnv = btcpayEnv; + _CoinAverage = coinAverage; } + CoinAverageSettings _CoinAverage; NBXplorerDashboard _Dashboard; BTCPayServerOptions _BtcpayServerOptions; BTCPayServerEnvironment _BTCPayEnv; @@ -237,7 +241,7 @@ namespace BTCPayServer.Controllers model.SetCryptoCurrencies(_ExplorerProvider, model.DefaultCryptoCurrency); model.SetLanguages(_LangService, model.DefaultLang); - if(!ModelState.IsValid) + if (!ModelState.IsValid) { return View(model); } @@ -273,6 +277,7 @@ namespace BTCPayServer.Controllers var storeBlob = store.GetStoreBlob(); var vm = new StoreViewModel(); + vm.SetExchangeRates(GetSupportedExchanges(), storeBlob.PreferredExchange.IsCoinAverage() ? "coinaverage" : storeBlob.PreferredExchange); vm.Id = store.Id; vm.StoreName = store.StoreName; vm.StoreWebsite = store.StoreWebsite; @@ -283,7 +288,6 @@ namespace BTCPayServer.Controllers vm.InvoiceExpiration = storeBlob.InvoiceExpiration; vm.RateMultiplier = (double)storeBlob.GetRateMultiplier(); vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate; - vm.PreferredExchange = storeBlob.PreferredExchange.IsCoinAverage() ? "coinaverage" : storeBlob.PreferredExchange; return View(vm); } @@ -325,6 +329,7 @@ namespace BTCPayServer.Controllers [Route("{storeId}")] public async Task UpdateStore(string storeId, StoreViewModel model) { + model.SetExchangeRates(GetSupportedExchanges(), model.PreferredExchange); if (!ModelState.IsValid) { return View(model); @@ -371,14 +376,11 @@ namespace BTCPayServer.Controllers if (!blob.PreferredExchange.IsCoinAverage() && newExchange) { - using (HttpClient client = new HttpClient()) + + if (!GetSupportedExchanges().Select(c => c.Name).Contains(blob.PreferredExchange, StringComparer.OrdinalIgnoreCase)) { - var rate = await client.GetAsync(model.RateSource); - if (rate.StatusCode == System.Net.HttpStatusCode.NotFound) - { - ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange ({model.RateSource})"); - return View(model); - } + ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange ({model.RateSource})"); + return View(model); } } @@ -394,9 +396,14 @@ namespace BTCPayServer.Controllers }); } + private (String DisplayName, String Name)[] GetSupportedExchanges() + { + return new[] { ("Coin Average", "coinaverage") }.Concat(_CoinAverage.AvailableExchanges).ToArray(); + } + private DerivationStrategy ParseDerivationStrategy(string derivationScheme, Script hint, BTCPayNetwork network) { - var parser = new DerivationSchemeParser(network.NBitcoinNetwork, network.DefaultSettings.ChainType); + var parser = new DerivationSchemeParser(network.NBitcoinNetwork); parser.HintScriptPubKey = hint; return new DerivationStrategy(parser.Parse(derivationScheme), network); } diff --git a/BTCPayServer/DerivationSchemeParser.cs b/BTCPayServer/DerivationSchemeParser.cs index 92736159c..30f188423 100644 --- a/BTCPayServer/DerivationSchemeParser.cs +++ b/BTCPayServer/DerivationSchemeParser.cs @@ -13,13 +13,11 @@ namespace BTCPayServer public class DerivationSchemeParser { public Network Network { get; set; } - public ChainType ChainType { get; set; } public Script HintScriptPubKey { get; set; } - public DerivationSchemeParser(Network expectedNetwork, ChainType chainType) + public DerivationSchemeParser(Network expectedNetwork) { Network = expectedNetwork; - ChainType = chainType; } public DerivationStrategyBase Parse(string str) @@ -78,7 +76,7 @@ namespace BTCPayServer if (data.Length < 4) continue; var prefix = Utils.ToUInt32(data, false); - var standardPrefix = Utils.ToBytes(ChainType == NBXplorer.ChainType.Main ? 0x0488b21eU : 0x043587cf, false); + var standardPrefix = Utils.ToBytes(Network.NetworkType == NetworkType.Mainnet ? 0x0488b21eU : 0x043587cf, false); for (int ii = 0; ii < 4; ii++) data[ii] = standardPrefix[ii]; diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index 5f9df05c9..1746562ea 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -28,11 +28,44 @@ using BTCPayServer.Payments; using Microsoft.AspNetCore.Identity; using BTCPayServer.Models; using System.Security.Claims; +using System.Globalization; namespace BTCPayServer { public static class Extensions { + public static string Prettify(this TimeSpan timeSpan) + { + if (timeSpan.TotalMinutes < 1) + { + return $"{(int)timeSpan.TotalSeconds} second{Plural((int)timeSpan.TotalSeconds)}"; + } + if (timeSpan.TotalHours < 1) + { + return $"{(int)timeSpan.TotalMinutes} minute{Plural((int)timeSpan.TotalMinutes)}"; + } + if (timeSpan.Days < 1) + { + return $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}"; + } + return $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}"; + } + + private static string Plural(int totalDays) + { + return totalDays > 1 ? "s" : string.Empty; + } + + public static string PrettyPrint(this TimeSpan expiration) + { + StringBuilder builder = new StringBuilder(); + if (expiration.Days >= 1) + builder.Append(expiration.Days.ToString(CultureInfo.InvariantCulture)); + if (expiration.Hours >= 1) + builder.Append(expiration.Hours.ToString("00", CultureInfo.InvariantCulture)); + builder.Append($"{expiration.Minutes.ToString("00", CultureInfo.InvariantCulture)}:{expiration.Seconds.ToString("00", CultureInfo.InvariantCulture)}"); + return builder.ToString(); + } public static decimal RoundUp(decimal value, int precision) { for (int i = 0; i < precision; i++) diff --git a/BTCPayServer/HostedServices/CssThemeManager.cs b/BTCPayServer/HostedServices/CssThemeManager.cs new file mode 100644 index 000000000..09f733d44 --- /dev/null +++ b/BTCPayServer/HostedServices/CssThemeManager.cs @@ -0,0 +1,62 @@ +using System; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Logging; +using Microsoft.Extensions.Hosting; +using NBXplorer; +using NBXplorer.Models; +using System.Collections.Concurrent; +using BTCPayServer.Events; +using BTCPayServer.Services; + +namespace BTCPayServer.HostedServices +{ + public class CssThemeManager + { + public CssThemeManager(SettingsRepository settingsRepository) + { + Update(settingsRepository); + } + + private async void Update(SettingsRepository settingsRepository) + { + var data = (await settingsRepository.GetSettingAsync()) ?? new ThemeSettings(); + Update(data); + } + + public void Update(ThemeSettings data) + { + UpdateBootstrap(data.BootstrapCssUri); + UpdateCreativeStart(data.CreativeStartCssUri); + } + + private string _bootstrapUri = "/vendor/bootstrap4/css/bootstrap.css?v=" + DateTime.Now.Ticks; + public string BootstrapUri + { + get { return _bootstrapUri; } + } + public void UpdateBootstrap(string newUri) + { + if (String.IsNullOrWhiteSpace(newUri)) + _bootstrapUri = "/vendor/bootstrap4/css/bootstrap.css?v="+ DateTime.Now.Ticks; + else + _bootstrapUri = newUri; + } + + private string _creativeStartUri = "/vendor/bootstrap4-creativestart/creative.css?v=" + DateTime.Now.Ticks; + public string CreativeStartUri + { + get { return _creativeStartUri; } + } + public void UpdateCreativeStart(string newUri) + { + if (String.IsNullOrWhiteSpace(newUri)) + _creativeStartUri = "/vendor/bootstrap4-creativestart/creative.css?v=" + DateTime.Now.Ticks; + else + _creativeStartUri = newUri; + } + } +} diff --git a/BTCPayServer/HostedServices/NBXplorerWaiter.cs b/BTCPayServer/HostedServices/NBXplorerWaiter.cs index f66b544d6..3ac9b52ce 100644 --- a/BTCPayServer/HostedServices/NBXplorerWaiter.cs +++ b/BTCPayServer/HostedServices/NBXplorerWaiter.cs @@ -184,8 +184,8 @@ namespace BTCPayServer.HostedServices if(status != null && error == null) { - if(status.ChainType != _Network.NBXplorerNetwork.DefaultSettings.ChainType) - error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.ChainType}, expected: {_Network.NBXplorerNetwork.DefaultSettings.ChainType})"; + if(status.NetworkType != _Network.NBitcoinNetwork.NetworkType) + error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.NetworkType}, expected: {_Network.NBitcoinNetwork.NetworkType})"; } if (error != null) diff --git a/BTCPayServer/HostedServices/RatesHostedService.cs b/BTCPayServer/HostedServices/RatesHostedService.cs index 18b0b3a6b..5bd1c1f0b 100644 --- a/BTCPayServer/HostedServices/RatesHostedService.cs +++ b/BTCPayServer/HostedServices/RatesHostedService.cs @@ -8,6 +8,7 @@ using BTCPayServer.Services; using BTCPayServer.Services.Rates; using Microsoft.Extensions.Hosting; using BTCPayServer.Logging; +using System.Runtime.CompilerServices; namespace BTCPayServer.HostedServices { @@ -15,41 +16,86 @@ namespace BTCPayServer.HostedServices { private SettingsRepository _SettingsRepository; private IRateProviderFactory _RateProviderFactory; - public RatesHostedService(SettingsRepository repo, + private CoinAverageSettings _coinAverageSettings; + public RatesHostedService(SettingsRepository repo, + CoinAverageSettings coinAverageSettings, IRateProviderFactory rateProviderFactory) { this._SettingsRepository = repo; _RateProviderFactory = rateProviderFactory; + _coinAverageSettings = coinAverageSettings; } + + + CancellationTokenSource _Cts = new CancellationTokenSource(); + + List _Tasks = new List(); + public Task StartAsync(CancellationToken cancellationToken) { - Init(); + _Tasks.Add(RefreshCoinAverageSupportedExchanges(_Cts.Token)); + _Tasks.Add(RefreshCoinAverageSettings(_Cts.Token)); return Task.CompletedTask; } - async void Init() - { - var rates = (await _SettingsRepository.GetSettingAsync()) ?? new RatesSetting(); - _RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes); - //string[] availableExchanges = null; - //// So we don't run this in testing - //if(_RateProviderFactory is BTCPayRateProviderFactory) - //{ - // try - // { - // await new CoinAverageRateProvider("BTC").GetExchangeTickersAsync(); - // } - // catch(Exception ex) - // { - // Logs.PayServer.LogWarning(ex, "Failed to get exchange tickers"); - // } - //} + async Task Timer(Func act, CancellationToken cancellation, [CallerMemberName]string caller = null) + { + await new SynchronizationContextRemover(); + while (!cancellation.IsCancellationRequested) + { + try + { + await act(); + } + catch (OperationCanceledException) when (cancellation.IsCancellationRequested) + { + } + catch (Exception ex) + { + Logs.PayServer.LogWarning(ex, caller + " failed"); + try + { + await Task.Delay(TimeSpan.FromMinutes(1), cancellation); + } + catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { } + } + } + } + Task RefreshCoinAverageSupportedExchanges(CancellationToken cancellation) + { + return Timer(async () => + { + await new SynchronizationContextRemover(); + var tickers = await new CoinAverageRateProvider("BTC").GetExchangeTickersAsync(); + _coinAverageSettings.AvailableExchanges = tickers + .Exchanges + .Select(c => (c.DisplayName, c.Name)) + .ToArray(); + + await Task.Delay(TimeSpan.FromHours(5), cancellation); + }, cancellation); + } + + Task RefreshCoinAverageSettings(CancellationToken cancellation) + { + return Timer(async () => + { + await new SynchronizationContextRemover(); + var rates = (await _SettingsRepository.GetSettingAsync()) ?? new RatesSetting(); + _RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes); + if (!string.IsNullOrWhiteSpace(rates.PrivateKey) && !string.IsNullOrWhiteSpace(rates.PublicKey)) + { + _coinAverageSettings.KeyPair = (rates.PublicKey, rates.PrivateKey); + } + await _SettingsRepository.WaitSettingsChanged(cancellation); + }, cancellation); } public Task StopAsync(CancellationToken cancellationToken) { - return Task.CompletedTask; + _Cts.Cancel(); + return Task.WhenAll(_Tasks.ToArray()); } } } diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index d98db23a2..b51651bc1 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -109,7 +109,8 @@ namespace BTCPayServer.Hosting services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(o => { var opts = o.GetRequiredService(); @@ -137,6 +138,7 @@ namespace BTCPayServer.Hosting services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -160,7 +162,7 @@ namespace BTCPayServer.Hosting services.TryAddSingleton(); services.TryAddSingleton(o => { - if (o.GetRequiredService().ChainType == ChainType.Main) + if (o.GetRequiredService().NetworkType == NetworkType.Mainnet) return new Bitpay(new Key(), new Uri("https://bitpay.com/")); else return new Bitpay(new Key(), new Uri("https://test.bitpay.com/")); diff --git a/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs b/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs index e889200b9..36b270c2b 100644 --- a/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs +++ b/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs @@ -27,7 +27,7 @@ namespace BTCPayServer.Models.InvoicingModels } public class Payment { - public string PaymentMethod { get; set; } + public string Crypto { get; set; } public string Confirmations { get; set; @@ -72,7 +72,13 @@ namespace BTCPayServer.Models.InvoicingModels get; set; } = new List(); - public List Payments { get; set; } = new List(); + public List OnChainPayments { get; set; } = new List(); + public List OffChainPayments { get; set; } = new List(); + public class OffChainPayment + { + public string Crypto { get; set; } + public string BOLT11 { get; set; } + } public string Status { diff --git a/BTCPayServer/Models/ServerViewModels/RatesViewModel.cs b/BTCPayServer/Models/ServerViewModels/RatesViewModel.cs index b0e1c3cba..b72f1ba49 100644 --- a/BTCPayServer/Models/ServerViewModels/RatesViewModel.cs +++ b/BTCPayServer/Models/ServerViewModels/RatesViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; +using BTCPayServer.Services.Rates; namespace BTCPayServer.Models.ServerViewModels { @@ -14,5 +15,6 @@ namespace BTCPayServer.Models.ServerViewModels [Display(Name = "Cache the rates for ... minutes")] [Range(0, 60)] public int CacheMinutes { get; set; } + public GetRateLimitsResponse RateLimits { get; internal set; } } } diff --git a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs index dc4a71e80..1ed1dce96 100644 --- a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs @@ -12,6 +12,11 @@ namespace BTCPayServer.Models.StoreViewModels { public class StoreViewModel { + class Format + { + public string Name { get; set; } + public string Value { get; set; } + } public class DerivationScheme { public string Crypto { get; set; } @@ -44,6 +49,17 @@ namespace BTCPayServer.Models.StoreViewModels public List DerivationSchemes { get; set; } = new List(); + public void SetExchangeRates((String DisplayName, String Name)[] supportedList, string preferredExchange) + { + var defaultStore = preferredExchange ?? "coinaverage"; + var choices = supportedList.Select(o => new Format() { Name = o.DisplayName, Value = o.Name }).ToArray(); + var chosen = choices.FirstOrDefault(f => f.Value == defaultStore) ?? choices.FirstOrDefault(); + Exchanges = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen); + PreferredExchange = chosen.Value; + } + + public SelectList Exchanges { get; set; } + [Display(Name = "Preferred price source (eg. bitfinex, bitstamp...)")] public string PreferredExchange { get; set; } diff --git a/BTCPayServer/Services/BTCPayServerEnvironment.cs b/BTCPayServer/Services/BTCPayServerEnvironment.cs index fd8fc5bca..a8e5d1105 100644 --- a/BTCPayServer/Services/BTCPayServerEnvironment.cs +++ b/BTCPayServer/Services/BTCPayServerEnvironment.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Threading.Tasks; using System.Text; using NBXplorer; +using NBitcoin; namespace BTCPayServer.Services { @@ -20,13 +21,13 @@ namespace BTCPayServer.Services Build = "Release"; #endif Environment = env; - ChainType = provider.NBXplorerNetworkProvider.ChainType; + NetworkType = provider.NetworkType; } public IHostingEnvironment Environment { get; set; } - public ChainType ChainType { get; set; } + public NetworkType NetworkType { get; set; } public string Version { get; set; @@ -40,7 +41,7 @@ namespace BTCPayServer.Services { get { - return ChainType == ChainType.Regtest && Environment.IsDevelopment(); + return NetworkType == NetworkType.Regtest && Environment.IsDevelopment(); } } public override string ToString() diff --git a/BTCPayServer/Services/HardwareWalletService.cs b/BTCPayServer/Services/HardwareWalletService.cs index 3a138e918..28ae5a182 100644 --- a/BTCPayServer/Services/HardwareWalletService.cs +++ b/BTCPayServer/Services/HardwareWalletService.cs @@ -101,7 +101,7 @@ namespace BTCPayServer.Services var pubKey = await ledger.GetWalletPubKeyAsync(account); if (pubKey.Address.Network != network.NBitcoinNetwork) { - if (network.DefaultSettings.ChainType == NBXplorer.ChainType.Main) + if (network.NBitcoinNetwork.NetworkType == NetworkType.Mainnet) throw new Exception($"The opened ledger app should be for {network.NBitcoinNetwork.Name}, not for {pubKey.Address.Network}"); } var fingerprint = onlyChaincode ? new byte[4] : (await ledger.GetWalletPubKeyAsync(account.Parent)).UncompressedPublicKey.Compress().Hash.ToBytes().Take(4).ToArray(); diff --git a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs index 7d98f25a9..6ec20b634 100644 --- a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs +++ b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs @@ -59,41 +59,11 @@ namespace BTCPayServer.Services.Rates public class RatesSetting { - private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public string PublicKey { get; set; } public string PrivateKey { get; set; } [DefaultValue(15)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] public int CacheInMinutes { get; set; } = 15; - - public string GetCoinAverageSignature() - { - if (string.IsNullOrEmpty(PublicKey) || string.IsNullOrEmpty(PrivateKey)) - return null; - var timestamp = (int)((DateTime.UtcNow - _epochUtc).TotalSeconds); - var payload = timestamp + "." + PublicKey; - var digestValueBytes = new HMACSHA256(Encoding.ASCII.GetBytes(PrivateKey)).ComputeHash(Encoding.ASCII.GetBytes(payload)); - var digestValueHex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(digestValueBytes); - return payload + "." + digestValueHex; - } - } - public class BTCPayCoinAverageAuthenticator : ICoinAverageAuthenticator - { - private SettingsRepository settingsRepo; - - public BTCPayCoinAverageAuthenticator(SettingsRepository settingsRepo) - { - this.settingsRepo = settingsRepo; - } - public async Task AddHeader(HttpRequestMessage message) - { - var settings = (await settingsRepo.GetSettingAsync()) ?? new RatesSetting(); - var signature = settings.GetCoinAverageSignature(); - if (signature != null) - { - message.Headers.Add("X-signature", signature); - } - } } public interface ICoinAverageAuthenticator @@ -200,6 +170,37 @@ namespace BTCPayServer.Services.Rates resp.EnsureSuccessStatusCode(); } + public async Task GetRateLimitsAsync() + { + var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/info/ratelimits"); + var auth = Authenticator; + if (auth != null) + { + await auth.AddHeader(request); + } + var resp = await _Client.SendAsync(request); + resp.EnsureSuccessStatusCode(); + var jobj = JObject.Parse(await resp.Content.ReadAsStringAsync()); + var response = new GetRateLimitsResponse(); + response.CounterReset = TimeSpan.FromSeconds(jobj["counter_reset"].Value()); + var totalPeriod = jobj["total_period"].Value(); + if (totalPeriod == "24h") + { + response.TotalPeriod = TimeSpan.FromHours(24); + } + else if (totalPeriod == "30d") + { + response.TotalPeriod = TimeSpan.FromDays(30); + } + else + { + response.TotalPeriod = TimeSpan.FromSeconds(jobj["total_period"].Value()); + } + response.RequestsLeft = jobj["requests_left"].Value(); + response.RequestsPerPeriod = jobj["requests_per_period"].Value(); + return response; + } + public async Task GetExchangeTickersAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/symbols/exchanges/ticker"); @@ -221,4 +222,12 @@ namespace BTCPayServer.Services.Rates return response; } } + + public class GetRateLimitsResponse + { + public TimeSpan CounterReset { get; set; } + public int RequestsLeft { get; set; } + public int RequestsPerPeriod { get; set; } + public TimeSpan TotalPeriod { get; set; } + } } diff --git a/BTCPayServer/Services/Rates/CoinAverageSettings.cs b/BTCPayServer/Services/Rates/CoinAverageSettings.cs new file mode 100644 index 000000000..0d666fb45 --- /dev/null +++ b/BTCPayServer/Services/Rates/CoinAverageSettings.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace BTCPayServer.Services.Rates +{ + public class CoinAverageSettings : ICoinAverageAuthenticator + { + private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public (String PublicKey, String PrivateKey)? KeyPair { get; set; } + public (String DisplayName, String Name)[] AvailableExchanges { get; set; } = Array.Empty<(String DisplayName, String Name)>(); + + public Task AddHeader(HttpRequestMessage message) + { + var signature = GetCoinAverageSignature(); + if (signature != null) + { + message.Headers.Add("X-signature", signature); + } + return Task.CompletedTask; + } + + public string GetCoinAverageSignature() + { + var keyPair = KeyPair; + if (!keyPair.HasValue) + return null; + if (string.IsNullOrEmpty(keyPair.Value.PublicKey) || string.IsNullOrEmpty(keyPair.Value.PrivateKey)) + return null; + var timestamp = (int)((DateTime.UtcNow - _epochUtc).TotalSeconds); + var payload = timestamp + "." + keyPair.Value.PublicKey; + var digestValueBytes = new HMACSHA256(Encoding.ASCII.GetBytes(keyPair.Value.PrivateKey)).ComputeHash(Encoding.ASCII.GetBytes(payload)); + var digestValueHex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(digestValueBytes); + return payload + "." + digestValueHex; + } + } +} diff --git a/BTCPayServer/Services/SettingsRepository.cs b/BTCPayServer/Services/SettingsRepository.cs index 51e80bfa7..a6911abc2 100644 --- a/BTCPayServer/Services/SettingsRepository.cs +++ b/BTCPayServer/Services/SettingsRepository.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore; using BTCPayServer.Models; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Newtonsoft.Json; +using System.Threading; namespace BTCPayServer.Services { @@ -51,6 +52,22 @@ namespace BTCPayServer.Services await ctx.SaveChangesAsync(); } } + + IReadOnlyCollection> value; + lock (_Subscriptions) + { + if(_Subscriptions.TryGetValue(typeof(T), out value)) + { + _Subscriptions.Remove(typeof(T)); + } + } + if(value != null) + { + foreach(var v in value) + { + v.TrySetResult(true); + } + } } private T Deserialize(string value) @@ -62,5 +79,35 @@ namespace BTCPayServer.Services { return JsonConvert.SerializeObject(obj); } + + MultiValueDictionary> _Subscriptions = new MultiValueDictionary>(); + public async Task WaitSettingsChanged(CancellationToken cancellation) + { + var tcs = new TaskCompletionSource(); + using (cancellation.Register(() => + { + try + { + tcs.TrySetCanceled(); + } + catch { } + })) + { + lock (_Subscriptions) + { + _Subscriptions.Add(typeof(T), tcs); + } +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + tcs.Task.ContinueWith(_ => + { + lock (_Subscriptions) + { + _Subscriptions.Remove(typeof(T), tcs); + } + }, TaskScheduler.Default); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + await tcs.Task; + } + } } } diff --git a/BTCPayServer/Services/ThemesSettings.cs b/BTCPayServer/Services/ThemesSettings.cs new file mode 100644 index 000000000..a13787e71 --- /dev/null +++ b/BTCPayServer/Services/ThemesSettings.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace BTCPayServer.Services +{ + public class ThemeSettings + { + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public string BootstrapCssUri { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public string CreativeStartCssUri { get; set; } + } +} diff --git a/BTCPayServer/SynchronizationContextRemover.cs b/BTCPayServer/SynchronizationContextRemover.cs new file mode 100644 index 000000000..edd06e3ff --- /dev/null +++ b/BTCPayServer/SynchronizationContextRemover.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace BTCPayServer +{ + public struct SynchronizationContextRemover : INotifyCompletion + { + public bool IsCompleted => SynchronizationContext.Current == null; + + public void OnCompleted(Action continuation) + { + var prev = SynchronizationContext.Current; + try + { + SynchronizationContext.SetSynchronizationContext(null); + continuation(); + } + finally + { + SynchronizationContext.SetSynchronizationContext(prev); + } + } + + public SynchronizationContextRemover GetAwaiter() + { + return this; + } + + public void GetResult() + { + } + } +} diff --git a/BTCPayServer/Views/Apps/ViewPointOfSale.cshtml b/BTCPayServer/Views/Apps/ViewPointOfSale.cshtml index 2fd64e279..807cfc080 100644 --- a/BTCPayServer/Views/Apps/ViewPointOfSale.cshtml +++ b/BTCPayServer/Views/Apps/ViewPointOfSale.cshtml @@ -15,7 +15,7 @@
-
+

@Model.Title

diff --git a/BTCPayServer/Views/Home/Home.cshtml b/BTCPayServer/Views/Home/Home.cshtml index 0781b27f1..c5851118c 100644 --- a/BTCPayServer/Views/Home/Home.cshtml +++ b/BTCPayServer/Views/Home/Home.cshtml @@ -6,8 +6,9 @@
-
-

Welcome to BTCPay Server

+
+

Welcome to BTCPay Server

+

BTCPay Server is a free and open source server for merchants wanting to accept Bitcoin for their business. The API is compatible with Bitpay service to allow seamless migration.

Getting started
@@ -28,21 +29,21 @@
-

Secure

+

Secure

The payment server does not need to know your private keys, so your money can't be stolen.

-

Easy

+

Easy

A user-friendly Bitcoin checkout page for your customers.

-

Visibility

+

Visibility

Manage, generate reports, and search for your invoices easily.

@@ -52,7 +53,7 @@ -
+

Video tutorials

@@ -102,12 +103,12 @@
-
+

Donate

Donation to this address will be reinvested into the development of this tool

- -

3BpfdkF93GwFRWdrAN3SNsRAsi6d158YQi

+

+

3BpfdkF93GwFRWdrAN3SNsRAsi6d158YQi

@@ -117,7 +118,10 @@

Let's Get In Touch!


-

An open source project is nothing without its community, come and get in touch with us!

+

+ An open source project is nothing without its community
+ Come and get in touch with us! +

diff --git a/BTCPayServer/Views/Invoice/Invoice.cshtml b/BTCPayServer/Views/Invoice/Invoice.cshtml index feb6d5d71..1fecc4b14 100644 --- a/BTCPayServer/Views/Invoice/Invoice.cshtml +++ b/BTCPayServer/Views/Invoice/Invoice.cshtml @@ -4,14 +4,8 @@ } @@ -28,14 +22,13 @@

@ViewData["Title"]


-

Invoice details

Information

- +
@@ -93,10 +86,9 @@

Buyer information

-
Store @Model.StoreName
+
- @@ -134,7 +126,7 @@
Name - + Name @Model.BuyerInformation.BuyerName

Product information

- +
@@ -153,77 +145,105 @@

Paid summary

-
Item code @Model.ProductInformation.ItemCode
+
- + - - - + + + - @foreach(var payment in Model.CryptoPayments) - { - - - - - - - - } + @foreach (var payment in Model.CryptoPayments) + { + + + + + + + + }
Payment methodPayment method AddressRatePaidDueRatePaidDue
@payment.PaymentMethod@payment.Address@payment.Rate@payment.Paid@payment.Due
@payment.PaymentMethod@payment.Address@payment.Rate@payment.Paid@payment.Due
-
-
-

Payments

- - - - - - - - - - - @foreach(var payment in Model.Payments) - { - var replaced = payment.Replaced ? "text-decoration: line-through;" : ""; - - - - - - - } - -
Payment methodDeposit addressTransaction IdConfirmations
@payment.PaymentMethod@payment.DepositAddress@payment.TransactionId@payment.Confirmations
+ @if(Model.OnChainPayments.Count > 0) + { +
+
+

On-Chain payments

+ + + + + + + + + + + @foreach(var payment in Model.OnChainPayments) + { + var replaced = payment.Replaced ? "class='linethrough'" : ""; + + + + + + + } + +
CryptoDeposit addressTransaction IdConfirmations
@payment.Crypto@payment.DepositAddress@payment.TransactionId@payment.Confirmations
+
-
+ } + @if(Model.OffChainPayments.Count > 0) + { +
+
+

Off-Chain payments

+ + + + + + + + + @foreach(var payment in Model.OffChainPayments) + { + + + + + } + +
CryptoBOLT11
@payment.Crypto@payment.BOLT11
+
+
+ }

Addresses

- +
- + - @foreach(var address in Model.Addresses) + @foreach (var address in Model.Addresses) { - var current = address.Current ? "font-weight: bold;" : ""; - - - - - } + var current = address.Current ? "class='font-weight-bold'" : ""; + + + + + }
Payment methodPayment method Address
@address.PaymentMethod@address.Destination
@address.PaymentMethod@address.Destination
@@ -232,7 +252,7 @@

Events

- +
@@ -240,7 +260,7 @@ - @foreach(var evt in Model.Events) + @foreach (var evt in Model.Events) { diff --git a/BTCPayServer/Views/Manage/ChangePassword.cshtml b/BTCPayServer/Views/Manage/ChangePassword.cshtml index 4cb15dbda..8c3a4dcef 100644 --- a/BTCPayServer/Views/Manage/ChangePassword.cshtml +++ b/BTCPayServer/Views/Manage/ChangePassword.cshtml @@ -1,7 +1,6 @@ @model ChangePasswordViewModel @{ - ViewData["Title"] = "Change password"; - ViewData.AddActivePage(ManageNavPages.ChangePassword); + ViewData.SetActivePageAndTitle(ManageNavPages.ChangePassword, "Change password"); }

@ViewData["Title"]

diff --git a/BTCPayServer/Views/Manage/Disable2fa.cshtml b/BTCPayServer/Views/Manage/Disable2fa.cshtml index 5c6d4e240..52963342d 100644 --- a/BTCPayServer/Views/Manage/Disable2fa.cshtml +++ b/BTCPayServer/Views/Manage/Disable2fa.cshtml @@ -1,6 +1,5 @@ @{ - ViewData["Title"] = "Disable two-factor authentication (2FA)"; - ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); + ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Disable two-factor authentication (2FA)"); }

@ViewData["Title"]

diff --git a/BTCPayServer/Views/Manage/EnableAuthenticator.cshtml b/BTCPayServer/Views/Manage/EnableAuthenticator.cshtml index 3da7075ba..ab0187380 100644 --- a/BTCPayServer/Views/Manage/EnableAuthenticator.cshtml +++ b/BTCPayServer/Views/Manage/EnableAuthenticator.cshtml @@ -1,7 +1,6 @@ @model EnableAuthenticatorViewModel @{ - ViewData["Title"] = "Enable authenticator"; - ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); + ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Enable authenticator"); }

@ViewData["Title"]

diff --git a/BTCPayServer/Views/Manage/ExternalLogins.cshtml b/BTCPayServer/Views/Manage/ExternalLogins.cshtml index 39fc54b9c..488f7542f 100644 --- a/BTCPayServer/Views/Manage/ExternalLogins.cshtml +++ b/BTCPayServer/Views/Manage/ExternalLogins.cshtml @@ -1,7 +1,6 @@ @model ExternalLoginsViewModel @{ - ViewData["Title"] = "Manage your external logins"; - ViewData.AddActivePage(ManageNavPages.ExternalLogins); + ViewData.SetActivePageAndTitle(ManageNavPages.ExternalLogins, "Manage your external logins"); } @Html.Partial("_StatusMessage", Model.StatusMessage) diff --git a/BTCPayServer/Views/Manage/GenerateRecoveryCodes.cshtml b/BTCPayServer/Views/Manage/GenerateRecoveryCodes.cshtml index 534fef4f7..7041dca04 100644 --- a/BTCPayServer/Views/Manage/GenerateRecoveryCodes.cshtml +++ b/BTCPayServer/Views/Manage/GenerateRecoveryCodes.cshtml @@ -1,7 +1,6 @@ @model GenerateRecoveryCodesViewModel @{ - ViewData["Title"] = "Recovery codes"; - ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); + ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Recovery codes"); }

@ViewData["Title"]

diff --git a/BTCPayServer/Views/Manage/Index.cshtml b/BTCPayServer/Views/Manage/Index.cshtml index 62a46c999..c94ed5b22 100644 --- a/BTCPayServer/Views/Manage/Index.cshtml +++ b/BTCPayServer/Views/Manage/Index.cshtml @@ -1,7 +1,6 @@ @model IndexViewModel @{ - ViewData["Title"] = "Profile"; - ViewData.AddActivePage(ManageNavPages.Index); + ViewData.SetActivePageAndTitle(ManageNavPages.Index, "Profile"); }

@ViewData["Title"]

diff --git a/BTCPayServer/Views/Manage/ManageNavPages.cs b/BTCPayServer/Views/Manage/ManageNavPages.cs index ce3bf2a39..b1fd60d2f 100644 --- a/BTCPayServer/Views/Manage/ManageNavPages.cs +++ b/BTCPayServer/Views/Manage/ManageNavPages.cs @@ -2,41 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewFeatures; namespace BTCPayServer.Views.Manage { - public static class ManageNavPages + public enum ManageNavPages { - public static string ActivePageKey => "ActivePage"; - - public static string Index => "Index"; - - public static string ChangePassword => "ChangePassword"; - - public static string ExternalLogins => "ExternalLogins"; - - public static string TwoFactorAuthentication => "TwoFactorAuthentication"; - - public static string Tokens => "Tokens"; - - public static string TokensNavClass(ViewContext viewContext) => PageNavClass(viewContext, Tokens); - - public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); - - public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); - - public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); - - public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); - - public static string PageNavClass(ViewContext viewContext, string page) - { - var activePage = viewContext.ViewData["ActivePage"] as string; - return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; - } - - public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage; + Index, ChangePassword, ExternalLogins, TwoFactorAuthentication, Tokens } } diff --git a/BTCPayServer/Views/Manage/ResetAuthenticator.cshtml b/BTCPayServer/Views/Manage/ResetAuthenticator.cshtml index 8518a1ca2..fd0fd5e01 100644 --- a/BTCPayServer/Views/Manage/ResetAuthenticator.cshtml +++ b/BTCPayServer/Views/Manage/ResetAuthenticator.cshtml @@ -1,6 +1,5 @@ @{ - ViewData["Title"] = "Reset authenticator key"; - ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); + ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Reset authenticator key"); }

@ViewData["Title"]

diff --git a/BTCPayServer/Views/Manage/SetPassword.cshtml b/BTCPayServer/Views/Manage/SetPassword.cshtml index 02f99df15..6cd28ef16 100644 --- a/BTCPayServer/Views/Manage/SetPassword.cshtml +++ b/BTCPayServer/Views/Manage/SetPassword.cshtml @@ -1,7 +1,6 @@ @model SetPasswordViewModel @{ - ViewData["Title"] = "Set password"; - ViewData.AddActivePage(ManageNavPages.ChangePassword); + ViewData.SetActivePageAndTitle(ManageNavPages.ChangePassword, "Set password"); }

Set your password

diff --git a/BTCPayServer/Views/Manage/TwoFactorAuthentication.cshtml b/BTCPayServer/Views/Manage/TwoFactorAuthentication.cshtml index 767da9e24..f36cbfbee 100644 --- a/BTCPayServer/Views/Manage/TwoFactorAuthentication.cshtml +++ b/BTCPayServer/Views/Manage/TwoFactorAuthentication.cshtml @@ -1,7 +1,6 @@ @model TwoFactorAuthenticationViewModel @{ - ViewData["Title"] = "Two-factor authentication"; - ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); + ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Two-factor authentication"); }

@ViewData["Title"]

diff --git a/BTCPayServer/Views/Manage/_Nav.cshtml b/BTCPayServer/Views/Manage/_Nav.cshtml index 32c0774a1..556d9fe57 100644 --- a/BTCPayServer/Views/Manage/_Nav.cshtml +++ b/BTCPayServer/Views/Manage/_Nav.cshtml @@ -1,16 +1,15 @@ -@using BTCPayServer.Views.Manage -@inject SignInManager SignInManager +@inject SignInManager SignInManager @{ var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); } diff --git a/BTCPayServer/Views/Server/Emails.cshtml b/BTCPayServer/Views/Server/Emails.cshtml index 04694abd1..f52bda25c 100644 --- a/BTCPayServer/Views/Server/Emails.cshtml +++ b/BTCPayServer/Views/Server/Emails.cshtml @@ -1,7 +1,6 @@ @model EmailsViewModel @{ - ViewData["Title"] = ServerNavPages.Emails; - ViewData.AddActivePage(ServerNavPages.Emails); + ViewData.SetActivePageAndTitle(ServerNavPages.Emails); } diff --git a/BTCPayServer/Views/Server/ListUsers.cshtml b/BTCPayServer/Views/Server/ListUsers.cshtml index 5a6fc702a..5326eefff 100644 --- a/BTCPayServer/Views/Server/ListUsers.cshtml +++ b/BTCPayServer/Views/Server/ListUsers.cshtml @@ -1,7 +1,6 @@ @model UsersViewModel @{ - ViewData["Title"] = "Users"; - ViewData.AddActivePage(ServerNavPages.Users); + ViewData.SetActivePageAndTitle(ServerNavPages.Users); } diff --git a/BTCPayServer/Views/Server/Policies.cshtml b/BTCPayServer/Views/Server/Policies.cshtml index f8c99b5d5..69767f659 100644 --- a/BTCPayServer/Views/Server/Policies.cshtml +++ b/BTCPayServer/Views/Server/Policies.cshtml @@ -1,19 +1,18 @@ @model BTCPayServer.Services.PoliciesSettings @{ - ViewData["Title"] = ServerNavPages.Policies; - ViewData.AddActivePage(ServerNavPages.Policies); + ViewData.SetActivePageAndTitle(ServerNavPages.Policies); }

@ViewData["Title"]

@Html.Partial("_StatusMessage", TempData["StatusMessage"])
-
+
-
+
diff --git a/BTCPayServer/Views/Server/Rates.cshtml b/BTCPayServer/Views/Server/Rates.cshtml index 15b2a5522..61641a7c4 100644 --- a/BTCPayServer/Views/Server/Rates.cshtml +++ b/BTCPayServer/Views/Server/Rates.cshtml @@ -1,7 +1,6 @@ @model RatesViewModel @{ - ViewData["Title"] = ServerNavPages.Rates; - ViewData.AddActivePage(ServerNavPages.Rates); + ViewData.SetActivePageAndTitle(ServerNavPages.Rates); } @@ -28,14 +27,31 @@ - -

You can find the information on bitcoinaverage api key page

+

You can find the information on bitcoinaverage api key page

+ @if(Model.RateLimits != null) + { +
Current Bitcoin Average Quotas:
+
Date
@evt.Timestamp
+ + + + + + + + + + + + +
Quota period@Model.RateLimits.TotalPeriod.Prettify()
Requests quota@Model.RateLimits.RequestsLeft/@Model.RateLimits.RequestsPerPeriod
Quota reset in@Model.RateLimits.CounterReset.Prettify()
+ }
diff --git a/BTCPayServer/Views/Server/ServerNavPages.cs b/BTCPayServer/Views/Server/ServerNavPages.cs index 45dfe7887..2f266c383 100644 --- a/BTCPayServer/Views/Server/ServerNavPages.cs +++ b/BTCPayServer/Views/Server/ServerNavPages.cs @@ -2,37 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewFeatures; namespace BTCPayServer.Views.Server { - public static class ServerNavPages + public enum ServerNavPages { - public static string ActivePageKey => "ActivePage"; - public static string Index => "Index"; - - - public static string Users => "Users"; - public static string Rates => "Rates"; - public static string Emails => "Email server"; - public static string Policies => "Policies"; - public static string Hangfire => "Hangfire"; - - public static string UsersNavClass(ViewContext viewContext) => PageNavClass(viewContext, Users); - public static string EmailsNavClass(ViewContext viewContext) => PageNavClass(viewContext, Emails); - public static string RatesNavClass(ViewContext viewContext) => PageNavClass(viewContext, Rates); - public static string PoliciesNavClass(ViewContext viewContext) => PageNavClass(viewContext, Policies); - public static string HangfireNavClass(ViewContext viewContext) => PageNavClass(viewContext, Hangfire); - - public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); - - public static string PageNavClass(ViewContext viewContext, string page) - { - var activePage = viewContext.ViewData["ActivePage"] as string; - return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; - } - - public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage; + Index, Users, Rates, Emails, Policies, Theme, Hangfire } } diff --git a/BTCPayServer/Views/Server/Theme.cshtml b/BTCPayServer/Views/Server/Theme.cshtml new file mode 100644 index 000000000..b9f71fae4 --- /dev/null +++ b/BTCPayServer/Views/Server/Theme.cshtml @@ -0,0 +1,43 @@ +@model BTCPayServer.Services.ThemeSettings +@{ + ViewData.SetActivePageAndTitle(ServerNavPages.Theme); +} + + +

@ViewData["Title"]

+@Html.Partial("_StatusMessage", TempData["StatusMessage"]) +
+
+
+
+
+
+
+
+ +
+ + + +

+ Creative Start theme + is used on top of Bootstrap +

+
+ + +
+
+
+ +@section Scripts { + @await Html.PartialAsync("_ValidationScriptsPartial") +} diff --git a/BTCPayServer/Views/Server/User.cshtml b/BTCPayServer/Views/Server/User.cshtml index 8cf3b44d4..47f4225ac 100644 --- a/BTCPayServer/Views/Server/User.cshtml +++ b/BTCPayServer/Views/Server/User.cshtml @@ -1,11 +1,10 @@ @model UserViewModel @{ - ViewData["Title"] = Model.Email; - ViewData.AddActivePage(ServerNavPages.Users); + ViewData.SetActivePageAndTitle(ServerNavPages.Users); } -

@ViewData["Title"]

+

Modify User - @Model.Email

@Html.Partial("_StatusMessage", Model.StatusMessage) diff --git a/BTCPayServer/Views/Server/_Nav.cshtml b/BTCPayServer/Views/Server/_Nav.cshtml index ed3bf77cc..6d4de07fb 100644 --- a/BTCPayServer/Views/Server/_Nav.cshtml +++ b/BTCPayServer/Views/Server/_Nav.cshtml @@ -1,10 +1,9 @@ -@using BTCPayServer.Views.Server - -