mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Support pluginable rate providers (#5777)
* Support pluginable rate providers This PR allows plugins to provide custom rate providers, that can be contextual to a store. For example, if you use the upcoming fiat offramp plugin, or the Blink plugin, you'll probably want to configure the fetch the rates from them since they are determining the actual fiat rrate to you. However, they require API keys. This PR enables these scenarios, even much more advanced ones, but for example: * Install fiat offramp plugin * Configure it * You can now use the fiat offramp rate provider (no additional config steps beyond selecting the rate source from the select, or maybe the plugin would automatically set it for you once configured) * Apply suggestions from code review * Simplify * Do not use BackgroundFetcherRateProvider for contextual rate prov --------- Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
4821f77304
commit
6049fa23a7
18 changed files with 152 additions and 50 deletions
|
@ -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<PairRate[]> 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);
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace BTCPayServer.Services.Rates
|
|||
/// <summary>
|
||||
/// This class is a decorator which handle caching and pre-emptive query to the underlying rate provider
|
||||
/// </summary>
|
||||
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<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
public Task<PairRate[]> GetRatesAsync(IRateContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
return GetRatesAsyncCore(context, cancellationToken);
|
||||
}
|
||||
async Task<PairRate[]> 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<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return GetRatesAsyncCore(null, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -224,15 +235,16 @@ namespace BTCPayServer.Services.Rates
|
|||
|
||||
public RateSourceInfo RateSourceInfo => _Inner.RateSourceInfo;
|
||||
|
||||
private async Task<LatestFetch> Fetch(CancellationToken cancellationToken)
|
||||
private async Task<LatestFetch> 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;
|
||||
|
|
|
@ -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; }
|
||||
/// <summary>
|
||||
/// Returns rates of the provider
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotSupportedException">If using this provider isn't supported (For example if a <see cref="IContextualRateProvider"/> requires a context)</exception>
|
||||
Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface IRateContext { }
|
||||
public interface IHasStoreIdRateContext : IRateContext
|
||||
{
|
||||
string StoreId { get; }
|
||||
}
|
||||
public record StoreIdRateContext(string StoreId) : IHasStoreIdRateContext;
|
||||
|
||||
/// <summary>
|
||||
/// A rate provider which know additional context about the rate query.
|
||||
/// </summary>
|
||||
public interface IContextualRateProvider : IRateProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns rates of the provider when a context is available
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotSupportedException">If using this provider isn't getting an expected context</exception>
|
||||
Task<PairRate[]> GetRatesAsync(IRateContext context, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<RateResult> FetchRate(CurrencyPair pair, RateRules rules, CancellationToken cancellationToken)
|
||||
public async Task<RateResult> FetchRate(CurrencyPair pair, RateRules rules, IRateContext? context, CancellationToken cancellationToken)
|
||||
{
|
||||
return await FetchRates(new HashSet<CurrencyPair>(new[] { pair }), rules, cancellationToken).First().Value;
|
||||
return await FetchRates(new HashSet<CurrencyPair>(new[] { pair }), rules, context, cancellationToken).First().Value;
|
||||
}
|
||||
|
||||
public Dictionary<CurrencyPair, Task<RateResult>> FetchRates(HashSet<CurrencyPair> pairs, RateRules rules, CancellationToken cancellationToken)
|
||||
public Dictionary<CurrencyPair, Task<RateResult>> FetchRates(HashSet<CurrencyPair> pairs, RateRules rules, IRateContext? context, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(rules);
|
||||
|
||||
var fetchingRates = new Dictionary<CurrencyPair, Task<RateResult>>();
|
||||
var fetchingExchanges = new Dictionary<string, Task<QueryRateResult>>();
|
||||
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<RateResult> FetchRate(RateRule rateRule, CancellationToken cancellationToken)
|
||||
public Task<RateResult> FetchRate(RateRule rateRule, IRateContext? context, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(rateRule);
|
||||
var fetchingExchanges = new Dictionary<string, Task<QueryRateResult>>();
|
||||
|
@ -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);
|
||||
|
|
|
@ -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<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
public async Task<PairRate[]> 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<IRateProvider> rateProviders)
|
||||
public RateProviderFactory(IHttpClientFactory httpClientFactory,IEnumerable<IRateProvider> 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<RateSourceInfo> AvailableRateProviders { get; } = new List<RateSourceInfo>();
|
||||
|
||||
public async Task<QueryRateResult> QueryRates(string exchangeName, CancellationToken cancellationToken)
|
||||
public async Task<QueryRateResult> 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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<InvalidOperationException>(() => 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<PairRate[]> GetRatesAsync(IRateContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
Assert.IsAssignableFrom<IHasStoreIdRateContext>(context);
|
||||
return Task.FromResult(new [] { new PairRate(new CurrencyPair("BTC", "USD"), ExpectedBidAsk) });
|
||||
}
|
||||
|
||||
public Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
public static RateProviderFactory CreateBTCPayRateFactory()
|
||||
{
|
||||
ServiceCollection services = new ServiceCollection();
|
||||
services.AddHttpClient();
|
||||
BTCPayServerServices.RegisterRateSources(services);
|
||||
services.AddRateProvider<SpyContextualRateProvider>();
|
||||
var o = services.BuildServiceProvider();
|
||||
return new RateProviderFactory(TestUtils.CreateHttpFactory(), o.GetService<IEnumerable<IRateProvider>>());
|
||||
}
|
||||
|
|
|
@ -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<RateResult> 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<RateResult> value) in result)
|
||||
{
|
||||
var rateResult = await value;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<StoreRateResult>();
|
||||
foreach (var rateTask in rateTasks)
|
||||
|
|
|
@ -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<StoreRateResult>();
|
||||
foreach (var rateTask in rateTasks)
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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<RatesViewModel.TestResultViewModel>();
|
||||
foreach (var fetch in fetchs)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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<PayoutApproval.ApprovalResult> Approve(PayoutApproval approval)
|
||||
|
|
|
@ -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}");
|
||||
}
|
||||
|
|
|
@ -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<CurrencyPair> failedRates = new HashSet<CurrencyPair>();
|
||||
foreach (var fetching in fetchingRates)
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue