mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-10 17:26:05 +01:00
* 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>
124 lines
4.9 KiB
C#
124 lines
4.9 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Security.Cryptography;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using BTCPayServer.Rating;
|
|
using BTCPayServer.Rating.Providers;
|
|
using ExchangeSharp;
|
|
using NBitcoin;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
namespace BTCPayServer.Services.Rates
|
|
{
|
|
public class RateProviderFactory
|
|
{
|
|
class WrapperRateProvider
|
|
{
|
|
private readonly IRateProvider _inner;
|
|
public Exception? Exception { get; private set; }
|
|
public TimeSpan Latency { get; set; }
|
|
public WrapperRateProvider(IRateProvider inner)
|
|
{
|
|
_inner = inner;
|
|
}
|
|
public async Task<PairRate[]> GetRatesAsync(IRateContext? context, CancellationToken cancellationToken)
|
|
{
|
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
|
try
|
|
{
|
|
return await _inner.GetRatesAsyncWithMaybeContext(context, cancellationToken);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Exception = ex;
|
|
return Array.Empty<PairRate>();
|
|
}
|
|
finally
|
|
{
|
|
Latency = DateTimeOffset.UtcNow - now;
|
|
}
|
|
}
|
|
}
|
|
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 string Exchange { get; internal set; }
|
|
}
|
|
public RateProviderFactory(IHttpClientFactory httpClientFactory,IEnumerable<IRateProvider> rateProviders)
|
|
{
|
|
_httpClientFactory = httpClientFactory;
|
|
foreach (var prov in rateProviders)
|
|
{
|
|
Providers.Add(prov.RateSourceInfo.Id, prov);
|
|
}
|
|
InitExchanges();
|
|
}
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
|
public Dictionary<string, IRateProvider> Providers { get; } = new Dictionary<string, IRateProvider>();
|
|
void InitExchanges()
|
|
{
|
|
foreach (var provider in Providers.ToArray())
|
|
{
|
|
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));
|
|
}
|
|
|
|
foreach (var supportedExchange in CoinGeckoRateProvider.SupportedExchanges.Values)
|
|
{
|
|
if (!Providers.ContainsKey(supportedExchange.Id) && supportedExchange.Id != CoinGeckoRateProvider.CoinGeckoName)
|
|
{
|
|
var coingecko = new CoinGeckoRateProvider(_httpClientFactory)
|
|
{
|
|
UnderlyingExchange = supportedExchange.Id
|
|
};
|
|
var bgFetcher = new BackgroundFetcherRateProvider(coingecko);
|
|
bgFetcher.RefreshRate = TimeSpan.FromMinutes(1.0);
|
|
bgFetcher.ValidatyTime = TimeSpan.FromMinutes(5.0);
|
|
Providers.Add(supportedExchange.Id, bgFetcher);
|
|
AvailableRateProviders.Add(coingecko.RateSourceInfo);
|
|
}
|
|
}
|
|
AvailableRateProviders.Sort((a, b) => StringComparer.Ordinal.Compare(a.DisplayName, b.DisplayName));
|
|
}
|
|
|
|
public List<RateSourceInfo> AvailableRateProviders { get; } = new List<RateSourceInfo>();
|
|
|
|
public async Task<QueryRateResult> QueryRates(string exchangeName, IRateContext? context = null, CancellationToken cancellationToken = default)
|
|
{
|
|
Providers.TryGetValue(exchangeName, out var directProvider);
|
|
directProvider ??= NullRateProvider.Instance;
|
|
|
|
var wrapper = new WrapperRateProvider(directProvider);
|
|
var value = await wrapper.GetRatesAsync(context, cancellationToken);
|
|
return new QueryRateResult(exchangeName, wrapper.Latency, value)
|
|
{
|
|
Exception = wrapper.Exception != null ? new ExchangeException() { Exception = wrapper.Exception, ExchangeName = exchangeName } : null
|
|
};
|
|
}
|
|
}
|
|
}
|