diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 1c1ae5e90..d7c93dccd 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,6 +396,11 @@ 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); diff --git a/BTCPayServer/HostedServices/RatesHostedService.cs b/BTCPayServer/HostedServices/RatesHostedService.cs index 373e6287d..5f98d24b2 100644 --- a/BTCPayServer/HostedServices/RatesHostedService.cs +++ b/BTCPayServer/HostedServices/RatesHostedService.cs @@ -63,9 +63,14 @@ namespace BTCPayServer.HostedServices } Task RefreshCoinAverageSupportedExchanges(CancellationToken cancellation) { - return Timer(async () => + return Timer(async () => { 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); } 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/Rates/CoinAverageSettings.cs b/BTCPayServer/Services/Rates/CoinAverageSettings.cs index ff69aa38f..0d666fb45 100644 --- a/BTCPayServer/Services/Rates/CoinAverageSettings.cs +++ b/BTCPayServer/Services/Rates/CoinAverageSettings.cs @@ -13,7 +13,7 @@ namespace BTCPayServer.Services.Rates 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[] AvailableExchanges { get; set; } = Array.Empty(); + public (String DisplayName, String Name)[] AvailableExchanges { get; set; } = Array.Empty<(String DisplayName, String Name)>(); public Task AddHeader(HttpRequestMessage message) { diff --git a/BTCPayServer/Views/Stores/UpdateStore.cshtml b/BTCPayServer/Views/Stores/UpdateStore.cshtml index 5672902cc..e35466c79 100644 --- a/BTCPayServer/Views/Stores/UpdateStore.cshtml +++ b/BTCPayServer/Views/Stores/UpdateStore.cshtml @@ -36,10 +36,10 @@
- +

- Current price source is @Model.PreferredExchange. (using 1 minute cache) + Current price source is @Model.PreferredExchange.

@@ -81,13 +81,13 @@ - @foreach (var scheme in Model.DerivationSchemes) + @foreach(var scheme in Model.DerivationSchemes) { @scheme.Crypto @scheme.Value - @if (!string.IsNullOrWhiteSpace(scheme.Value)) + @if(!string.IsNullOrWhiteSpace(scheme.Value)) { Wallet - } @@ -117,7 +117,7 @@ - @foreach (var scheme in Model.LightningNodes) + @foreach(var scheme in Model.LightningNodes) { @scheme.CryptoCode