diff --git a/BTCPayServer.Rating/Extensions.cs b/BTCPayServer.Rating/Extensions.cs index 9d1af928a..727b975ac 100644 --- a/BTCPayServer.Rating/Extensions.cs +++ b/BTCPayServer.Rating/Extensions.cs @@ -1,9 +1,26 @@ +#nullable enable using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Services.Rates; namespace BTCPayServer.Rating { public static class Extensions { + public static Task GetRatesAsyncWithMaybeContext(this IRateProvider rateProvider, IRateContext? context, CancellationToken cancellationToken) + { + if (rateProvider is IContextualRateProvider contextualRateProvider && context is { }) + { + return contextualRateProvider.GetRatesAsync(context, cancellationToken); + } + else + { + return rateProvider.GetRatesAsync(cancellationToken); + } + } public static decimal RoundToSignificant(this decimal value, int divisibility) { return RoundToSignificant(value, ref divisibility); diff --git a/BTCPayServer.Rating/Providers/BackgroundFetcherRateProvider.cs b/BTCPayServer.Rating/Providers/BackgroundFetcherRateProvider.cs index cba9865b2..91058d9ad 100644 --- a/BTCPayServer.Rating/Providers/BackgroundFetcherRateProvider.cs +++ b/BTCPayServer.Rating/Providers/BackgroundFetcherRateProvider.cs @@ -53,7 +53,7 @@ namespace BTCPayServer.Services.Rates /// /// This class is a decorator which handle caching and pre-emptive query to the underlying rate provider /// - public class BackgroundFetcherRateProvider : IRateProvider + public class BackgroundFetcherRateProvider : IContextualRateProvider { public class LatestFetch { @@ -63,6 +63,9 @@ namespace BTCPayServer.Services.Rates public DateTimeOffset Updated; public DateTimeOffset Expiration; public Exception Exception; + + public IRateContext Context { get; internal set; } + internal PairRate[] GetResult() { if (Expiration <= DateTimeOffset.UtcNow) @@ -185,7 +188,7 @@ namespace BTCPayServer.Services.Rates { try { - await Fetch(cancellationToken); + await Fetch(_Latest?.Context, cancellationToken); } catch { } // Exception is inside _Latest return _Latest; @@ -194,7 +197,11 @@ namespace BTCPayServer.Services.Rates } LatestFetch _Latest; - public async Task GetRatesAsync(CancellationToken cancellationToken) + public Task GetRatesAsync(IRateContext context, CancellationToken cancellationToken) + { + return GetRatesAsyncCore(context, cancellationToken); + } + async Task GetRatesAsyncCore(IRateContext context, CancellationToken cancellationToken) { LastRequested = DateTimeOffset.UtcNow; var latest = _Latest; @@ -202,7 +209,11 @@ namespace BTCPayServer.Services.Rates { latest = null; } - return (latest ?? (await Fetch(cancellationToken))).GetResult(); + return (latest ?? (await Fetch(context, cancellationToken))).GetResult(); + } + public Task GetRatesAsync(CancellationToken cancellationToken) + { + return GetRatesAsyncCore(null, cancellationToken); } /// @@ -224,15 +235,16 @@ namespace BTCPayServer.Services.Rates public RateSourceInfo RateSourceInfo => _Inner.RateSourceInfo; - private async Task Fetch(CancellationToken cancellationToken) + private async Task Fetch(IRateContext context, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var previous = _Latest; var fetch = new LatestFetch(); try { - var rates = await _Inner.GetRatesAsync(cancellationToken); + var rates = await _Inner.GetRatesAsyncWithMaybeContext(context, cancellationToken); fetch.Latest = rates; + fetch.Context = context; fetch.Updated = DateTimeOffset.UtcNow; fetch.Expiration = fetch.Updated + ValidatyTime; fetch.NextRefresh = fetch.Updated + RefreshRate; diff --git a/BTCPayServer.Rating/Providers/IRateProvider.cs b/BTCPayServer.Rating/Providers/IRateProvider.cs index 670cd2a34..f93f8d1bd 100644 --- a/BTCPayServer.Rating/Providers/IRateProvider.cs +++ b/BTCPayServer.Rating/Providers/IRateProvider.cs @@ -1,3 +1,5 @@ +#nullable enable +using System; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Rating; @@ -7,6 +9,33 @@ namespace BTCPayServer.Services.Rates public interface IRateProvider { RateSourceInfo RateSourceInfo { get; } + /// + /// Returns rates of the provider + /// + /// + /// + /// If using this provider isn't supported (For example if a requires a context) Task GetRatesAsync(CancellationToken cancellationToken); } + + public interface IRateContext { } + public interface IHasStoreIdRateContext : IRateContext + { + string StoreId { get; } + } + public record StoreIdRateContext(string StoreId) : IHasStoreIdRateContext; + + /// + /// A rate provider which know additional context about the rate query. + /// + public interface IContextualRateProvider : IRateProvider + { + /// + /// Returns rates of the provider when a context is available + /// + /// + /// + /// If using this provider isn't getting an expected context + Task GetRatesAsync(IRateContext context, CancellationToken cancellationToken); + } } diff --git a/BTCPayServer.Rating/Services/RateFetcher.cs b/BTCPayServer.Rating/Services/RateFetcher.cs index df57cba08..19aba5a91 100644 --- a/BTCPayServer.Rating/Services/RateFetcher.cs +++ b/BTCPayServer.Rating/Services/RateFetcher.cs @@ -22,7 +22,7 @@ namespace BTCPayServer.Services.Rates public BidAsk BidAsk { get; set; } public TimeSpan Latency { get; internal set; } } - +#nullable enable public class RateFetcher { private readonly RateProviderFactory _rateProviderFactory; @@ -34,17 +34,18 @@ namespace BTCPayServer.Services.Rates public RateProviderFactory RateProviderFactory => _rateProviderFactory; - public async Task FetchRate(CurrencyPair pair, RateRules rules, CancellationToken cancellationToken) + public async Task FetchRate(CurrencyPair pair, RateRules rules, IRateContext? context, CancellationToken cancellationToken) { - return await FetchRates(new HashSet(new[] { pair }), rules, cancellationToken).First().Value; + return await FetchRates(new HashSet(new[] { pair }), rules, context, cancellationToken).First().Value; } - public Dictionary> FetchRates(HashSet pairs, RateRules rules, CancellationToken cancellationToken) + public Dictionary> FetchRates(HashSet pairs, RateRules rules, IRateContext? context, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(rules); var fetchingRates = new Dictionary>(); var fetchingExchanges = new Dictionary>(); + var consolidatedRates = new ExchangeRates(); foreach (var i in pairs.Select(p => (Pair: p, RateRule: rules.GetRuleFor(p)))) { @@ -53,7 +54,7 @@ namespace BTCPayServer.Services.Rates { if (!fetchingExchanges.TryGetValue(requiredExchange.Exchange, out var fetching)) { - fetching = _rateProviderFactory.QueryRates(requiredExchange.Exchange, cancellationToken); + fetching = _rateProviderFactory.QueryRates(requiredExchange.Exchange, context, cancellationToken); fetchingExchanges.Add(requiredExchange.Exchange, fetching); } dependentQueries.Add(fetching); @@ -63,7 +64,7 @@ namespace BTCPayServer.Services.Rates return fetchingRates; } - public Task FetchRate(RateRule rateRule, CancellationToken cancellationToken) + public Task FetchRate(RateRule rateRule, IRateContext? context, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(rateRule); var fetchingExchanges = new Dictionary>(); @@ -72,7 +73,7 @@ namespace BTCPayServer.Services.Rates { if (!fetchingExchanges.TryGetValue(requiredExchange.Exchange, out var fetching)) { - fetching = _rateProviderFactory.QueryRates(requiredExchange.Exchange, cancellationToken); + fetching = _rateProviderFactory.QueryRates(requiredExchange.Exchange, context, cancellationToken); fetchingExchanges.Add(requiredExchange.Exchange, fetching); } dependentQueries.Add(fetching); diff --git a/BTCPayServer.Rating/Services/RateProviderFactory.cs b/BTCPayServer.Rating/Services/RateProviderFactory.cs index e69089c9a..736e95bc8 100644 --- a/BTCPayServer.Rating/Services/RateProviderFactory.cs +++ b/BTCPayServer.Rating/Services/RateProviderFactory.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections.Generic; using System.Linq; @@ -15,22 +16,21 @@ namespace BTCPayServer.Services.Rates { public class RateProviderFactory { - class WrapperRateProvider : IRateProvider + class WrapperRateProvider { - public RateSourceInfo RateSourceInfo => _inner.RateSourceInfo; private readonly IRateProvider _inner; - public Exception Exception { get; private set; } + public Exception? Exception { get; private set; } public TimeSpan Latency { get; set; } public WrapperRateProvider(IRateProvider inner) { _inner = inner; } - public async Task GetRatesAsync(CancellationToken cancellationToken) + public async Task GetRatesAsync(IRateContext? context, CancellationToken cancellationToken) { DateTimeOffset now = DateTimeOffset.UtcNow; try { - return await _inner.GetRatesAsync(cancellationToken); + return await _inner.GetRatesAsyncWithMaybeContext(context, cancellationToken); } catch (Exception ex) { @@ -45,12 +45,19 @@ namespace BTCPayServer.Services.Rates } public class QueryRateResult { + public QueryRateResult(string exchangeName, TimeSpan latency, PairRate[] pairRates) + { + Exchange = exchangeName; + Latency = latency; + PairRates = pairRates; + } + public TimeSpan Latency { get; set; } public PairRate[] PairRates { get; set; } - public ExchangeException Exception { get; internal set; } + public ExchangeException? Exception { get; internal set; } public string Exchange { get; internal set; } } - public RateProviderFactory(IHttpClientFactory httpClientFactory, IEnumerable rateProviders) + public RateProviderFactory(IHttpClientFactory httpClientFactory,IEnumerable rateProviders) { _httpClientFactory = httpClientFactory; foreach (var prov in rateProviders) @@ -65,10 +72,18 @@ namespace BTCPayServer.Services.Rates { foreach (var provider in Providers.ToArray()) { - var prov = new BackgroundFetcherRateProvider(Providers[provider.Key]); - prov.RefreshRate = TimeSpan.FromMinutes(1.0); - prov.ValidatyTime = TimeSpan.FromMinutes(5.0); - Providers[provider.Key] = prov; + var prov = Providers[provider.Key]; + if (prov is IContextualRateProvider) + { + Providers[provider.Key] = prov; + } + else + { + var prov2 = new BackgroundFetcherRateProvider(prov); + prov2.RefreshRate = TimeSpan.FromMinutes(1.0); + prov2.ValidatyTime = TimeSpan.FromMinutes(5.0); + Providers[provider.Key] = prov2; + } var rsi = provider.Value.RateSourceInfo; AvailableRateProviders.Add(new(rsi.Id, rsi.DisplayName, rsi.Url)); } @@ -93,18 +108,15 @@ namespace BTCPayServer.Services.Rates public List AvailableRateProviders { get; } = new List(); - public async Task QueryRates(string exchangeName, CancellationToken cancellationToken) + public async Task QueryRates(string exchangeName, IRateContext? context = null, CancellationToken cancellationToken = default) { Providers.TryGetValue(exchangeName, out var directProvider); - directProvider = directProvider ?? NullRateProvider.Instance; + directProvider ??= NullRateProvider.Instance; var wrapper = new WrapperRateProvider(directProvider); - var value = await wrapper.GetRatesAsync(cancellationToken); - return new QueryRateResult() + var value = await wrapper.GetRatesAsync(context, cancellationToken); + return new QueryRateResult(exchangeName, wrapper.Latency, value) { - Exchange = exchangeName, - Latency = wrapper.Latency, - PairRates = value, Exception = wrapper.Exception != null ? new ExchangeException() { Exception = wrapper.Exception, ExchangeName = exchangeName } : null }; } diff --git a/BTCPayServer.Tests/FastTests.cs b/BTCPayServer.Tests/FastTests.cs index 4536d270d..4ee95fc44 100644 --- a/BTCPayServer.Tests/FastTests.cs +++ b/BTCPayServer.Tests/FastTests.cs @@ -1110,6 +1110,19 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku Assert.True(parsers.TryParseWalletFile(electrumText, mainnet, out electrum, out _)); } + [Fact] + public async Task CanPassContextToRateProviders() + { + var factory = CreateBTCPayRateFactory(); + var fetcher = new RateFetcher(factory); + Assert.True(RateRules.TryParse("X_X=spy(X_X)", out var rule)); + var result = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rule, null, default); + Assert.Single(result.Errors); + result = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rule, new StoreIdRateContext("hello"), default); + Assert.Empty(result.Errors); + Assert.Equal(SpyContextualRateProvider.ExpectedBidAsk, result.BidAsk); + } + [Fact] public async Task CheckRatesProvider() { @@ -1123,15 +1136,15 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku var fetch = new BackgroundFetcherRateProvider(spy); fetch.DoNotAutoFetchIfExpired = true; factory.Providers.Add("bitpay", fetch); - var fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default); + var fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, null, default); spy.AssertHit(); - fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default); + fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, null, default); spy.AssertNotHit(); await fetch.UpdateIfNecessary(default); spy.AssertNotHit(); fetch.RefreshRate = TimeSpan.FromSeconds(1.0); Thread.Sleep(1020); - fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default); + fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, null, default); spy.AssertNotHit(); fetch.ValidatyTime = TimeSpan.FromSeconds(1.0); await fetch.UpdateIfNecessary(default); @@ -1141,11 +1154,27 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku await Assert.ThrowsAsync(() => fetch.GetRatesAsync(default)); } + class SpyContextualRateProvider : IContextualRateProvider + { + public static BidAsk ExpectedBidAsk = new BidAsk(1.12345m); + public RateSourceInfo RateSourceInfo => new RateSourceInfo("spy", "hello world", "abc..."); + public Task GetRatesAsync(IRateContext context, CancellationToken cancellationToken) + { + Assert.IsAssignableFrom(context); + return Task.FromResult(new [] { new PairRate(new CurrencyPair("BTC", "USD"), ExpectedBidAsk) }); + } + + public Task GetRatesAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } public static RateProviderFactory CreateBTCPayRateFactory() { ServiceCollection services = new ServiceCollection(); services.AddHttpClient(); BTCPayServerServices.RegisterRateSources(services); + services.AddRateProvider(); var o = services.BuildServiceProvider(); return new RateProviderFactory(TestUtils.CreateHttpFactory(), o.GetService>()); } diff --git a/BTCPayServer.Tests/ThirdPartyTests.cs b/BTCPayServer.Tests/ThirdPartyTests.cs index a1ce68c9d..aa632276b 100644 --- a/BTCPayServer.Tests/ThirdPartyTests.cs +++ b/BTCPayServer.Tests/ThirdPartyTests.cs @@ -346,7 +346,7 @@ retry: Assert.True(RateRules.TryParse("X_X=kraken(X_BTC) * kraken(BTC_X)", out var rule)); foreach (var pair in new[] { "DOGE_USD", "DOGE_CAD", "DASH_CAD", "DASH_USD", "DASH_EUR" }) { - var result = fetcher.FetchRate(CurrencyPair.Parse(pair), rule, default).GetAwaiter().GetResult(); + var result = fetcher.FetchRate(CurrencyPair.Parse(pair), rule, null, default).GetAwaiter().GetResult(); Assert.NotNull(result.BidAsk); Assert.Empty(result.Errors); } @@ -365,7 +365,7 @@ retry: b.DefaultCurrency = k.Key; var rules = b.GetDefaultRateRules(provider); var pairs = new[] { CurrencyPair.Parse($"BTC_{k.Key}") }.ToHashSet(); - var result = fetcher.FetchRates(pairs, rules, default); + var result = fetcher.FetchRates(pairs, rules, null, default); foreach ((CurrencyPair key, Task value) in result) { TestLogs.LogInformation($"Testing {key} when default currency is {k.Key}"); @@ -410,7 +410,7 @@ retry: } var rules = new StoreBlob().GetDefaultRateRules(provider); - var result = fetcher.FetchRates(pairs, rules, cts.Token); + var result = fetcher.FetchRates(pairs, rules, null, cts.Token); foreach ((CurrencyPair key, Task value) in result) { var rateResult = await value; diff --git a/BTCPayServer/Components/WalletNav/WalletNav.cs b/BTCPayServer/Components/WalletNav/WalletNav.cs index 62d5e3077..1eb61e8ef 100644 --- a/BTCPayServer/Components/WalletNav/WalletNav.cs +++ b/BTCPayServer/Components/WalletNav/WalletNav.cs @@ -73,7 +73,7 @@ namespace BTCPayServer.Components.WalletNav if (defaultCurrency != network.CryptoCode) { var rule = store.GetStoreBlob().GetRateRules(_networkProvider)?.GetRuleFor(new Rating.CurrencyPair(network.CryptoCode, defaultCurrency)); - var bid = rule is null ? null : (await _rateFetcher.FetchRate(rule, HttpContext.RequestAborted)).BidAsk?.Bid; + var bid = rule is null ? null : (await _rateFetcher.FetchRate(rule, new StoreIdRateContext(walletId.StoreId), HttpContext.RequestAborted)).BidAsk?.Bid; if (bid is decimal b) { var currencyData = _currencies.GetCurrencyData(defaultCurrency, true); diff --git a/BTCPayServer/Controllers/BitpayRateController.cs b/BTCPayServer/Controllers/BitpayRateController.cs index 4e2c3958e..7a25c081a 100644 --- a/BTCPayServer/Controllers/BitpayRateController.cs +++ b/BTCPayServer/Controllers/BitpayRateController.cs @@ -137,7 +137,7 @@ namespace BTCPayServer.Controllers pairs.Add(pair); } - var fetching = _rateProviderFactory.FetchRates(pairs, rules, cancellationToken); + var fetching = _rateProviderFactory.FetchRates(pairs, rules, new StoreIdRateContext(storeId), cancellationToken); await Task.WhenAll(fetching.Select(f => f.Value).ToArray()); return Json(pairs .Select(r => (Pair: r, Value: fetching[r].GetAwaiter().GetResult().BidAsk?.Bid)) diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldInvoiceController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldInvoiceController.cs index eaba47900..496366b03 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldInvoiceController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldInvoiceController.cs @@ -414,8 +414,9 @@ namespace BTCPayServer.Controllers.Greenfield var paidCurrency = Math.Round(cryptoPaid * paymentPrompt.Rate, cdCurrency.Divisibility); var rateResult = await _rateProvider.FetchRate( new CurrencyPair(paymentPrompt.Currency, invoice.Currency), - store.GetStoreBlob().GetRateRules(_networkProvider), - cancellationToken + store.GetStoreBlob().GetRateRules(_networkProvider), new StoreIdRateContext(storeId), + + cancellationToken ); var paidAmount = cryptoPaid.RoundToSignificant(paymentPrompt.Divisibility); var createPullPayment = new CreatePullPayment diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesConfigurationController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesConfigurationController.cs index b4b6ec887..9fac97550 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesConfigurationController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesConfigurationController.cs @@ -120,7 +120,7 @@ namespace BTCPayServer.Controllers.GreenField var rules = blob.GetRateRules(_btcPayNetworkProvider); - var rateTasks = _rateProviderFactory.FetchRates(parsedCurrencyPairs, rules, CancellationToken.None); + var rateTasks = _rateProviderFactory.FetchRates(parsedCurrencyPairs, rules, new StoreIdRateContext(data.Id), CancellationToken.None); await Task.WhenAll(rateTasks.Values); var result = new List(); foreach (var rateTask in rateTasks) diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesController.cs index 734f3f436..b0201101c 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesController.cs @@ -64,7 +64,7 @@ namespace BTCPayServer.Controllers.GreenField var rules = blob.GetRateRules(_btcPayNetworkProvider); - var rateTasks = _rateProviderFactory.FetchRates(parsedCurrencyPairs, rules, CancellationToken.None); + var rateTasks = _rateProviderFactory.FetchRates(parsedCurrencyPairs, rules, new StoreIdRateContext(data.Id), CancellationToken.None); await Task.WhenAll(rateTasks.Values); var result = new List(); foreach (var rateTask in rateTasks) diff --git a/BTCPayServer/Controllers/UIInvoiceController.UI.cs b/BTCPayServer/Controllers/UIInvoiceController.UI.cs index ad2b29afe..5c365b2c7 100644 --- a/BTCPayServer/Controllers/UIInvoiceController.UI.cs +++ b/BTCPayServer/Controllers/UIInvoiceController.UI.cs @@ -391,7 +391,7 @@ namespace BTCPayServer.Controllers model.RateThenText = _displayFormatter.Currency(model.CryptoAmountThen, paymentMethodCurrency); rules = store.GetStoreBlob().GetRateRules(_NetworkProvider); rateResult = await _RateProvider.FetchRate( - new CurrencyPair(paymentMethodCurrency, invoice.Currency), rules, + new CurrencyPair(paymentMethodCurrency, invoice.Currency), rules, new StoreIdRateContext(store.Id), cancellationToken); //TODO: What if fetching rate failed? if (rateResult.BidAsk is null) @@ -500,7 +500,7 @@ namespace BTCPayServer.Controllers rules = store.GetStoreBlob().GetRateRules(_NetworkProvider); rateResult = await _RateProvider.FetchRate( - new CurrencyPair(paymentMethodCurrency, model.CustomCurrency), rules, + new CurrencyPair(paymentMethodCurrency, model.CustomCurrency), rules, new StoreIdRateContext(store.Id), cancellationToken); //TODO: What if fetching rate failed? diff --git a/BTCPayServer/Controllers/UIStoresController.Rates.cs b/BTCPayServer/Controllers/UIStoresController.Rates.cs index c1b12cbf4..21074acba 100644 --- a/BTCPayServer/Controllers/UIStoresController.Rates.cs +++ b/BTCPayServer/Controllers/UIStoresController.Rates.cs @@ -11,6 +11,7 @@ using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Rating; +using BTCPayServer.Services.Rates; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -124,7 +125,7 @@ public partial class UIStoresController pairs.Add(currencyPair); } - var fetchs = _rateFactory.FetchRates(pairs.ToHashSet(), rules, cancellationToken); + var fetchs = _rateFactory.FetchRates(pairs.ToHashSet(), rules, new StoreIdRateContext(model.StoreId), cancellationToken); var testResults = new List(); foreach (var fetch in fetchs) { diff --git a/BTCPayServer/Controllers/UIWalletsController.cs b/BTCPayServer/Controllers/UIWalletsController.cs index 55769a03a..4bb60a3a0 100644 --- a/BTCPayServer/Controllers/UIWalletsController.cs +++ b/BTCPayServer/Controllers/UIWalletsController.cs @@ -531,7 +531,7 @@ namespace BTCPayServer.Controllers try { cts.CancelAfter(TimeSpan.FromSeconds(5)); - var result = await RateFetcher.FetchRate(currencyPair, rateRules, cts.Token) + var result = await RateFetcher.FetchRate(currencyPair, rateRules, new StoreIdRateContext(walletId.StoreId), cts.Token) .WithCancellation(cts.Token); if (result.BidAsk != null) { diff --git a/BTCPayServer/HostedServices/PullPaymentHostedService.cs b/BTCPayServer/HostedServices/PullPaymentHostedService.cs index cd954abcc..5fd6ff8d8 100644 --- a/BTCPayServer/HostedServices/PullPaymentHostedService.cs +++ b/BTCPayServer/HostedServices/PullPaymentHostedService.cs @@ -412,7 +412,7 @@ namespace BTCPayServer.HostedServices throw new FormatException("Invalid RateRule"); } - return _rateFetcher.FetchRate(rule, cancellationToken); + return _rateFetcher.FetchRate(rule, new StoreIdRateContext(payout.StoreDataId), cancellationToken); } public Task Approve(PayoutApproval approval) diff --git a/BTCPayServer/HostedServices/RatesHostedService.cs b/BTCPayServer/HostedServices/RatesHostedService.cs index 70ad24e5a..8fba4ecc7 100644 --- a/BTCPayServer/HostedServices/RatesHostedService.cs +++ b/BTCPayServer/HostedServices/RatesHostedService.cs @@ -75,7 +75,7 @@ namespace BTCPayServer.HostedServices await Task.WhenAll(usedProviders .Select(p => p.Fetcher.UpdateIfNecessary(timeout.Token).ContinueWith(t => { - if (t.Result.Exception != null) + if (t.Result.Exception != null && t.Result.Exception is not NotSupportedException) { Logs.PayServer.LogWarning($"Error while contacting exchange {p.ExchangeName}: {t.Result.Exception.Message}"); } diff --git a/BTCPayServer/Payments/IPaymentMethodHandler.cs b/BTCPayServer/Payments/IPaymentMethodHandler.cs index b4b929007..c5baaf3ca 100644 --- a/BTCPayServer/Payments/IPaymentMethodHandler.cs +++ b/BTCPayServer/Payments/IPaymentMethodHandler.cs @@ -163,7 +163,7 @@ namespace BTCPayServer.Payments public async Task FetchingRates(RateFetcher rateFetcher, RateRules rateRules, CancellationToken cancellationToken) { var currencyPairsToFetch = GetCurrenciesToFetch(); - var fetchingRates = rateFetcher.FetchRates(currencyPairsToFetch, rateRules, cancellationToken); + var fetchingRates = rateFetcher.FetchRates(currencyPairsToFetch, rateRules, new StoreIdRateContext(InvoiceEntity.StoreId), cancellationToken); HashSet failedRates = new HashSet(); foreach (var fetching in fetchingRates) {