mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Register rate providers in DI, so it can be accessed by plugins (#4551)
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace BTCPayServer.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20230123062447_migrateoldratesource")]
|
||||||
|
public partial class migrateoldratesource : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
if (migrationBuilder.IsNpgsql())
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("UPDATE \"Stores\" SET \"StoreBlob\"=jsonb_set(\"StoreBlob\", \'{preferredExchange}\', \'{\"oasis_trade\": \"oasisdev\", \"gdax\":\"coinbasepro\", \"coinaverage\":\"coingecko\"}\'::jsonb->(\"StoreBlob\"->>\'preferredExchange\')) WHERE \"StoreBlob\"->>\'preferredExchange\' = ANY (ARRAY[\'oasis_trade\', \'gdax\', \'coinaverage\']);");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// Not supported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,17 +12,15 @@ namespace BTCPayServer.Rating
|
|||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public string Url { get; }
|
public string Url { get; }
|
||||||
public string Id { get; }
|
public string Id { get; }
|
||||||
public string SourceId { get; }
|
|
||||||
public RateSource Source { get; }
|
public RateSource Source { get; }
|
||||||
|
|
||||||
public AvailableRateProvider(string id, string name, string url) : this(id, id, name, url, RateSource.Direct)
|
public AvailableRateProvider(string id, string name, string url) : this(id, name, url, RateSource.Direct)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
public AvailableRateProvider(string id, string sourceId, string name, string url, RateSource source)
|
public AvailableRateProvider(string id, string name, string url, RateSource source)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
SourceId = sourceId;
|
|
||||||
Name = name;
|
Name = name;
|
||||||
Url = url;
|
Url = url;
|
||||||
Source = source;
|
Source = source;
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ namespace BTCPayServer.Services.Rates
|
|||||||
_httpClient = httpClient ?? new HttpClient();
|
_httpClient = httpClient ?? new HttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => new("argoneum", "Argoneum", "https://rates.argoneum.net/rates");
|
||||||
|
|
||||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Example result: AGM to BTC rate: {"agm":5000000.000000}
|
// Example result: AGM to BTC rate: {"agm":5000000.000000}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ namespace BTCPayServer.Rating.Providers
|
|||||||
public decimal? ask { get; set; }
|
public decimal? ask { get; set; }
|
||||||
}
|
}
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => new RateSourceInfo("btcturk", "BtcTurk", "https://api.btcturk.com/api/v2/ticker");
|
||||||
|
|
||||||
public BtcTurkRateProvider(HttpClient httpClient)
|
public BtcTurkRateProvider(HttpClient httpClient)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient ?? new HttpClient();
|
_httpClient = httpClient ?? new HttpClient();
|
||||||
|
|||||||
@@ -222,6 +222,8 @@ namespace BTCPayServer.Services.Rates
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => _Inner.RateSourceInfo;
|
||||||
|
|
||||||
private async Task<LatestFetch> Fetch(CancellationToken cancellationToken)
|
private async Task<LatestFetch> Fetch(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ namespace BTCPayServer.Services.Rates
|
|||||||
public class BitbankRateProvider : IRateProvider
|
public class BitbankRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => new("bitbank", "Bitbank", "https://public.bitbank.cc/tickers");
|
||||||
|
|
||||||
public BitbankRateProvider(HttpClient httpClient)
|
public BitbankRateProvider(HttpClient httpClient)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient ?? new HttpClient();
|
_httpClient = httpClient ?? new HttpClient();
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ namespace BTCPayServer.Services.Rates
|
|||||||
_httpClient = httpClient ?? new HttpClient();
|
_httpClient = httpClient ?? new HttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => new RateSourceInfo("bitflyer", "Bitflyer", "https://api.bitflyer.com/v1/ticker");
|
||||||
|
|
||||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.GetAsync("https://api.bitflyer.jp/v1/ticker", cancellationToken);
|
var response = await _httpClient.GetAsync("https://api.bitflyer.jp/v1/ticker", cancellationToken);
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ namespace BTCPayServer.Services.Rates
|
|||||||
_httpClient = httpClient ?? new HttpClient();
|
_httpClient = httpClient ?? new HttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => new("bitpay", "Bitpay", "https://bitpay.com/rates");
|
||||||
|
|
||||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.GetAsync("https://bitpay.com/rates", cancellationToken);
|
var response = await _httpClient.GetAsync("https://bitpay.com/rates", cancellationToken);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ public class BudaRateProvider : IRateProvider
|
|||||||
_httpClient = httpClient ?? new HttpClient();
|
_httpClient = httpClient ?? new HttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => new RateSourceInfo("buda", "Buda", "https://www.buda.com/api/v2/markets/btc-clp/ticker");
|
||||||
|
|
||||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.GetAsync("https://www.buda.com/api/v2/markets/btc-clp/ticker", cancellationToken);
|
var response = await _httpClient.GetAsync("https://www.buda.com/api/v2/markets/btc-clp/ticker", cancellationToken);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ namespace BTCPayServer.Services.Rates
|
|||||||
_httpClient = httpClient ?? new HttpClient();
|
_httpClient = httpClient ?? new HttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => new RateSourceInfo("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD");
|
||||||
|
|
||||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.GetAsync("https://bylls.com/api/price?from_currency=BTC&to_currency=CAD", cancellationToken);
|
var response = await _httpClient.GetAsync("https://bylls.com/api/price?from_currency=BTC&to_currency=CAD", cancellationToken);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -11,6 +11,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
{
|
{
|
||||||
public class CryptoMarketExchangeRateProvider : IRateProvider
|
public class CryptoMarketExchangeRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
|
public RateSourceInfo RateSourceInfo => new("cryptomarket", "CryptoMarket", "https://api.exchange.cryptomkt.com/api/3/public/ticker/");
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
public CryptoMarketExchangeRateProvider(HttpClient httpClient)
|
public CryptoMarketExchangeRateProvider(HttpClient httpClient)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ namespace BTCPayServer.Services.Rates
|
|||||||
|
|
||||||
// ExchangeSymbolToGlobalSymbol throws exception which would kill perf
|
// ExchangeSymbolToGlobalSymbol throws exception which would kill perf
|
||||||
readonly ConcurrentDictionary<string, string> notFoundSymbols = new ConcurrentDictionary<string, string>();
|
readonly ConcurrentDictionary<string, string> notFoundSymbols = new ConcurrentDictionary<string, string>();
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo { get; set; }
|
||||||
|
|
||||||
private async Task<PairRate> CreateExchangeRate(T exchangeAPI, KeyValuePair<string, ExchangeTicker> ticker)
|
private async Task<PairRate> CreateExchangeRate(T exchangeAPI, KeyValuePair<string, ExchangeTicker> ticker)
|
||||||
{
|
{
|
||||||
if (notFoundSymbols.TryGetValue(ticker.Key, out _))
|
if (notFoundSymbols.TryGetValue(ticker.Key, out _))
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BTCPayServer.Rating;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Rates
|
|
||||||
{
|
|
||||||
public class FallbackRateProvider : IRateProvider
|
|
||||||
{
|
|
||||||
readonly IRateProvider[] _Providers;
|
|
||||||
public FallbackRateProvider(IRateProvider[] providers)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(providers);
|
|
||||||
_Providers = providers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
foreach (var p in _Providers)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await p.GetRatesAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch when (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex) { Exceptions.Add(ex); }
|
|
||||||
}
|
|
||||||
return Array.Empty<PairRate>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Exception> Exceptions { get; set; } = new List<Exception>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,7 @@ namespace BTCPayServer.Rating
|
|||||||
{
|
{
|
||||||
public class HitBTCRateProvider : IRateProvider
|
public class HitBTCRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
|
public RateSourceInfo RateSourceInfo => new("hitbtc", "HitBTC", "https://api.hitbtc.com/api/2/public/ticker");
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
public HitBTCRateProvider(HttpClient httpClient)
|
public HitBTCRateProvider(HttpClient httpClient)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
{
|
{
|
||||||
public interface IRateProvider
|
public interface IRateProvider
|
||||||
{
|
{
|
||||||
|
RateSourceInfo RateSourceInfo { get; }
|
||||||
Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken);
|
Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
// Make sure that only one request is sent to kraken in general
|
// Make sure that only one request is sent to kraken in general
|
||||||
public class KrakenExchangeRateProvider : IRateProvider
|
public class KrakenExchangeRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
|
public RateSourceInfo RateSourceInfo => new("kraken", "Kraken", "https://api.kraken.com/0/public/Ticker?pair=ATOMETH,ATOMEUR,ATOMUSD,ATOMXBT,BATETH,BATEUR,BATUSD,BATXBT,BCHEUR,BCHUSD,BCHXBT,DAIEUR,DAIUSD,DAIUSDT,DASHEUR,DASHUSD,DASHXBT,EOSETH,EOSXBT,ETHCHF,ETHDAI,ETHUSDC,ETHUSDT,GNOETH,GNOXBT,ICXETH,ICXEUR,ICXUSD,ICXXBT,LINKETH,LINKEUR,LINKUSD,LINKXBT,LSKETH,LSKEUR,LSKUSD,LSKXBT,NANOETH,NANOEUR,NANOUSD,NANOXBT,OMGETH,OMGEUR,OMGUSD,OMGXBT,PAXGETH,PAXGEUR,PAXGUSD,PAXGXBT,SCETH,SCEUR,SCUSD,SCXBT,USDCEUR,USDCUSD,USDCUSDT,USDTCAD,USDTEUR,USDTGBP,USDTZUSD,WAVESETH,WAVESEUR,WAVESUSD,WAVESXBT,XBTCHF,XBTDAI,XBTUSDC,XBTUSDT,XDGEUR,XDGUSD,XETCXETH,XETCXXBT,XETCZEUR,XETCZUSD,XETHXXBT,XETHZCAD,XETHZEUR,XETHZGBP,XETHZJPY,XETHZUSD,XLTCXXBT,XLTCZEUR,XLTCZUSD,XMLNXETH,XMLNXXBT,XMLNZEUR,XMLNZUSD,XREPXETH,XREPXXBT,XREPZEUR,XXBTZCAD,XXBTZEUR,XXBTZGBP,XXBTZJPY,XXBTZUSD,XXDGXXBT,XXLMXXBT,XXMRXXBT,XXMRZEUR,XXMRZUSD,XXRPXXBT,XXRPZEUR,XXRPZUSD,XZECXXBT,XZECZEUR,XZECZUSD");
|
||||||
public HttpClient HttpClient
|
public HttpClient HttpClient
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ namespace BTCPayServer.Services.Rates
|
|||||||
return _Instance;
|
return _Instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => new RateSourceInfo("NULL","NULL", "https://NULL.NULL");
|
||||||
|
|
||||||
public Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
public Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.FromResult(Array.Empty<PairRate>());
|
return Task.FromResult(Array.Empty<PairRate>());
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
{
|
{
|
||||||
public class RipioExchangeProvider : IRateProvider
|
public class RipioExchangeProvider : IRateProvider
|
||||||
{
|
{
|
||||||
|
public RateSourceInfo RateSourceInfo => new("ripio", "Ripio", "https://api.exchange.ripio.com/api/v1/rate/all/");
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
public RipioExchangeProvider(HttpClient httpClient)
|
public RipioExchangeProvider(HttpClient httpClient)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
{
|
{
|
||||||
public class YadioRateProvider : IRateProvider
|
public class YadioRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
|
public RateSourceInfo RateSourceInfo => new("yadio", "Yadio", "https://api.yadio.io/exrates/BTC");
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
public YadioRateProvider(HttpClient httpClient)
|
public YadioRateProvider(HttpClient httpClient)
|
||||||
{
|
{
|
||||||
|
|||||||
21
BTCPayServer.Rating/RateSourceInfo.cs
Normal file
21
BTCPayServer.Rating/RateSourceInfo.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Rating
|
||||||
|
{
|
||||||
|
public class RateSourceInfo
|
||||||
|
{
|
||||||
|
public RateSourceInfo(string id, string displayName, string url)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
DisplayName = displayName;
|
||||||
|
Url = url;
|
||||||
|
}
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,13 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Rating.Providers;
|
using BTCPayServer.Rating.Providers;
|
||||||
using ExchangeSharp;
|
using ExchangeSharp;
|
||||||
|
using NBitcoin;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Rates
|
namespace BTCPayServer.Services.Rates
|
||||||
@@ -15,6 +17,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
{
|
{
|
||||||
class WrapperRateProvider : IRateProvider
|
class WrapperRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
|
public RateSourceInfo RateSourceInfo => _inner.RateSourceInfo;
|
||||||
private readonly IRateProvider _inner;
|
private readonly IRateProvider _inner;
|
||||||
public Exception Exception { get; private set; }
|
public Exception Exception { get; private set; }
|
||||||
public TimeSpan Latency { get; set; }
|
public TimeSpan Latency { get; set; }
|
||||||
@@ -47,148 +50,49 @@ namespace BTCPayServer.Services.Rates
|
|||||||
public ExchangeException Exception { get; internal set; }
|
public ExchangeException Exception { get; internal set; }
|
||||||
public string Exchange { get; internal set; }
|
public string Exchange { get; internal set; }
|
||||||
}
|
}
|
||||||
public RateProviderFactory(IHttpClientFactory httpClientFactory)
|
public RateProviderFactory(IHttpClientFactory httpClientFactory, IEnumerable<IRateProvider> rateProviders)
|
||||||
{
|
{
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
|
foreach (var prov in rateProviders)
|
||||||
|
{
|
||||||
|
Providers.Add(prov.RateSourceInfo.Id, prov);
|
||||||
|
}
|
||||||
InitExchanges();
|
InitExchanges();
|
||||||
}
|
}
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly Dictionary<string, IRateProvider> _DirectProviders = new Dictionary<string, IRateProvider>();
|
public Dictionary<string, IRateProvider> Providers { get; } = new Dictionary<string, IRateProvider>();
|
||||||
public Dictionary<string, IRateProvider> Providers
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _DirectProviders;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal IEnumerable<AvailableRateProvider> GetDirectlySupportedExchanges()
|
|
||||||
{
|
|
||||||
yield return new AvailableRateProvider("binance", "Binance", "https://api.binance.com/api/v1/ticker/24hr");
|
|
||||||
yield return new AvailableRateProvider("bittrex", "Bittrex", "https://bittrex.com/api/v1.1/public/getmarketsummaries");
|
|
||||||
yield return new AvailableRateProvider("poloniex", "Poloniex", "https://poloniex.com/public?command=returnTicker");
|
|
||||||
yield return new AvailableRateProvider("hitbtc", "HitBTC", "https://api.hitbtc.com/api/2/public/ticker");
|
|
||||||
yield return new AvailableRateProvider("ndax", "NDAX", "https://ndax.io/api/returnTicker");
|
|
||||||
|
|
||||||
yield return new AvailableRateProvider("coingecko", "CoinGecko", "https://api.coingecko.com/api/v3/exchange_rates");
|
|
||||||
yield return new AvailableRateProvider("kraken", "Kraken", "https://api.kraken.com/0/public/Ticker?pair=ATOMETH,ATOMEUR,ATOMUSD,ATOMXBT,BATETH,BATEUR,BATUSD,BATXBT,BCHEUR,BCHUSD,BCHXBT,DAIEUR,DAIUSD,DAIUSDT,DASHEUR,DASHUSD,DASHXBT,EOSETH,EOSXBT,ETHCHF,ETHDAI,ETHUSDC,ETHUSDT,GNOETH,GNOXBT,ICXETH,ICXEUR,ICXUSD,ICXXBT,LINKETH,LINKEUR,LINKUSD,LINKXBT,LSKETH,LSKEUR,LSKUSD,LSKXBT,NANOETH,NANOEUR,NANOUSD,NANOXBT,OMGETH,OMGEUR,OMGUSD,OMGXBT,PAXGETH,PAXGEUR,PAXGUSD,PAXGXBT,SCETH,SCEUR,SCUSD,SCXBT,USDCEUR,USDCUSD,USDCUSDT,USDTCAD,USDTEUR,USDTGBP,USDTZUSD,WAVESETH,WAVESEUR,WAVESUSD,WAVESXBT,XBTCHF,XBTDAI,XBTUSDC,XBTUSDT,XDGEUR,XDGUSD,XETCXETH,XETCXXBT,XETCZEUR,XETCZUSD,XETHXXBT,XETHZCAD,XETHZEUR,XETHZGBP,XETHZJPY,XETHZUSD,XLTCXXBT,XLTCZEUR,XLTCZUSD,XMLNXETH,XMLNXXBT,XMLNZEUR,XMLNZUSD,XREPXETH,XREPXXBT,XREPZEUR,XXBTZCAD,XXBTZEUR,XXBTZGBP,XXBTZJPY,XXBTZUSD,XXDGXXBT,XXLMXXBT,XXMRXXBT,XXMRZEUR,XXMRZUSD,XXRPXXBT,XXRPZEUR,XXRPZUSD,XZECXXBT,XZECZEUR,XZECZUSD");
|
|
||||||
yield return new AvailableRateProvider("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD");
|
|
||||||
yield return new AvailableRateProvider("buda", "Buda", "https://www.buda.com/api/v2/markets/btc-clp/ticker");
|
|
||||||
yield return new AvailableRateProvider("bitbank", "Bitbank", "https://public.bitbank.cc/tickers");
|
|
||||||
yield return new AvailableRateProvider("bitflyer", "Bitflyer", "https://api.bitflyer.com/v1/ticker");
|
|
||||||
yield return new AvailableRateProvider("bitpay", "Bitpay", "https://bitpay.com/rates");
|
|
||||||
yield return new AvailableRateProvider("ripio", "Ripio", "https://api.exchange.ripio.com/api/v1/rate/all/");
|
|
||||||
yield return new AvailableRateProvider("cryptomarket", "CryptoMarket", "https://api.exchange.cryptomkt.com/api/3/public/ticker/");
|
|
||||||
yield return new AvailableRateProvider("btcturk", "BtcTurk", "https://api.btcturk.com/api/v2/ticker");
|
|
||||||
|
|
||||||
yield return new AvailableRateProvider("bitfinex", "Bitfinex", "https://api.bitfinex.com/v2/tickers?symbols=tBTCUSD,tLTCUSD,tLTCBTC,tETHUSD,tETHBTC,tETCBTC,tETCUSD,tRRTUSD,tRRTBTC,tZECUSD,tZECBTC,tXMRUSD,tXMRBTC,tDSHUSD,tDSHBTC,tBTCEUR,tBTCJPY,tXRPUSD,tXRPBTC,tIOTUSD,tIOTBTC,tIOTETH,tEOSUSD,tEOSBTC,tEOSETH,tSANUSD,tSANBTC,tSANETH,tOMGUSD,tOMGBTC,tOMGETH,tNEOUSD,tNEOBTC,tNEOETH,tETPUSD,tETPBTC,tETPETH,tQTMUSD,tQTMBTC,tQTMETH,tAVTUSD,tAVTBTC,tAVTETH,tEDOUSD,tEDOBTC,tEDOETH,tBTGUSD,tBTGBTC,tDATUSD,tDATBTC,tDATETH,tQSHUSD,tQSHBTC,tQSHETH,tYYWUSD,tYYWBTC,tYYWETH,tGNTUSD,tGNTBTC,tGNTETH,tSNTUSD,tSNTBTC,tSNTETH,tIOTEUR,tBATUSD,tBATBTC,tBATETH,tMNAUSD,tMNABTC,tMNAETH,tFUNUSD,tFUNBTC,tFUNETH,tZRXUSD,tZRXBTC,tZRXETH,tTNBUSD,tTNBBTC,tTNBETH,tSPKUSD,tSPKBTC,tSPKETH,tTRXUSD,tTRXBTC,tTRXETH,tRCNUSD,tRCNBTC,tRCNETH,tRLCUSD,tRLCBTC,tRLCETH,tAIDUSD,tAIDBTC,tAIDETH,tSNGUSD,tSNGBTC,tSNGETH,tREPUSD,tREPBTC,tREPETH,tELFUSD,tELFBTC,tELFETH,tNECUSD,tNECBTC,tNECETH,tBTCGBP,tETHEUR,tETHJPY,tETHGBP,tNEOEUR,tNEOJPY,tNEOGBP,tEOSEUR,tEOSJPY,tEOSGBP,tIOTJPY,tIOTGBP,tIOSUSD,tIOSBTC,tIOSETH,tAIOUSD,tAIOBTC,tAIOETH,tREQUSD,tREQBTC,tREQETH,tRDNUSD,tRDNBTC,tRDNETH,tLRCUSD,tLRCBTC,tLRCETH,tWAXUSD,tWAXBTC,tWAXETH,tDAIUSD,tDAIBTC,tDAIETH,tAGIUSD,tAGIBTC,tAGIETH,tBFTUSD,tBFTBTC,tBFTETH,tMTNUSD,tMTNBTC,tMTNETH,tODEUSD,tODEBTC,tODEETH,tANTUSD,tANTBTC,tANTETH,tDTHUSD,tDTHBTC,tDTHETH,tMITUSD,tMITBTC,tMITETH,tSTJUSD,tSTJBTC,tSTJETH,tXLMUSD,tXLMEUR,tXLMJPY,tXLMGBP,tXLMBTC,tXLMETH,tXVGUSD,tXVGEUR,tXVGJPY,tXVGGBP,tXVGBTC,tXVGETH,tBCIUSD,tBCIBTC,tMKRUSD,tMKRBTC,tMKRETH,tKNCUSD,tKNCBTC,tKNCETH,tPOAUSD,tPOABTC,tPOAETH,tEVTUSD,tLYMUSD,tLYMBTC,tLYMETH,tUTKUSD,tUTKBTC,tUTKETH,tVEEUSD,tVEEBTC,tVEEETH,tDADUSD,tDADBTC,tDADETH,tORSUSD,tORSBTC,tORSETH,tAUCUSD,tAUCBTC,tAUCETH,tPOYUSD,tPOYBTC,tPOYETH,tFSNUSD,tFSNBTC,tFSNETH,tCBTUSD,tCBTBTC,tCBTETH,tZCNUSD,tZCNBTC,tZCNETH,tSENUSD,tSENBTC,tSENETH,tNCAUSD,tNCABTC,tNCAETH,tCNDUSD,tCNDBTC,tCNDETH,tCTXUSD,tCTXBTC,tCTXETH,tPAIUSD,tPAIBTC,tSEEUSD,tSEEBTC,tSEEETH,tESSUSD,tESSBTC,tESSETH,tATMUSD,tATMBTC,tATMETH,tHOTUSD,tHOTBTC,tHOTETH,tDTAUSD,tDTABTC,tDTAETH,tIQXUSD,tIQXBTC,tIQXEOS,tWPRUSD,tWPRBTC,tWPRETH,tZILUSD,tZILBTC,tZILETH,tBNTUSD,tBNTBTC,tBNTETH,tABSUSD,tABSETH,tXRAUSD,tXRAETH,tMANUSD,tMANETH,tBBNUSD,tBBNETH,tNIOUSD,tNIOETH,tDGXUSD,tDGXETH,tVETUSD,tVETBTC,tVETETH,tUTNUSD,tUTNETH,tTKNUSD,tTKNETH,tGOTUSD,tGOTEUR,tGOTETH,tXTZUSD,tXTZBTC,tCNNUSD,tCNNETH,tBOXUSD,tBOXETH,tTRXEUR,tTRXGBP,tTRXJPY,tMGOUSD,tMGOETH,tRTEUSD,tRTEETH,tYGGUSD,tYGGETH,tMLNUSD,tMLNETH,tWTCUSD,tWTCETH,tCSXUSD,tCSXETH,tOMNUSD,tOMNBTC,tINTUSD,tINTETH,tDRNUSD,tDRNETH,tPNKUSD,tPNKETH,tDGBUSD,tDGBBTC,tBSVUSD,tBSVBTC,tBABUSD,tBABBTC,tWLOUSD,tWLOXLM,tVLDUSD,tVLDETH,tENJUSD,tENJETH,tONLUSD,tONLETH,tRBTUSD,tRBTBTC,tUSTUSD,tEUTEUR,tEUTUSD,tGSDUSD,tUDCUSD,tTSDUSD,tPAXUSD,tRIFUSD,tRIFBTC,tPASUSD,tPASETH,tVSYUSD,tVSYBTC,tZRXDAI,tMKRDAI,tOMGDAI,tBTTUSD,tBTTBTC,tBTCUST,tETHUST,tCLOUSD,tCLOBTC,tIMPUSD,tIMPETH,tLTCUST,tEOSUST,tBABUST,tSCRUSD,tSCRETH,tGNOUSD,tGNOETH,tGENUSD,tGENETH,tATOUSD,tATOBTC,tATOETH,tWBTUSD,tXCHUSD,tEUSUSD,tWBTETH,tXCHETH,tEUSETH,tLEOUSD,tLEOBTC,tLEOUST,tLEOEOS,tLEOETH,tASTUSD,tASTETH,tFOAUSD,tFOAETH,tUFRUSD,tUFRETH,tZBTUSD,tZBTUST,tOKBUSD,tUSKUSD,tGTXUSD,tKANUSD,tOKBUST,tOKBETH,tOKBBTC,tUSKUST,tUSKETH,tUSKBTC,tUSKEOS,tGTXUST,tKANUST,tAMPUSD,tALGUSD,tALGBTC,tALGUST,tBTCXCH,tSWMUSD,tSWMETH,tTRIUSD,tTRIETH,tLOOUSD,tLOOETH,tAMPUST,tDUSK:USD,tDUSK:BTC,tUOSUSD,tUOSBTC,tRRBUSD,tRRBUST,tDTXUSD,tDTXUST,tAMPBTC,tFTTUSD,tFTTUST,tPAXUST,tUDCUST,tTSDUST,tBTC:CNHT,tUST:CNHT,tCNH:CNHT,tCHZUSD,tCHZUST,tBTCF0:USTF0,tETHF0:USTF0");
|
|
||||||
yield return new AvailableRateProvider("okex", "OKEx", "https://www.okex.com/api/futures/v3/instruments/ticker");
|
|
||||||
yield return new AvailableRateProvider("coinbasepro", "Coinbase Pro", "https://api.pro.coinbase.com/products");
|
|
||||||
|
|
||||||
yield return new AvailableRateProvider("argoneum", "Argoneum", "https://rates.argoneum.net/rates");
|
|
||||||
yield return new AvailableRateProvider("yadio", "Yadio", "https://api.yadio.io/exrates/BTC");
|
|
||||||
}
|
|
||||||
void InitExchanges()
|
void InitExchanges()
|
||||||
{
|
{
|
||||||
// We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request
|
|
||||||
AddExchangeSharpProviders<ExchangeBinanceAPI>("binance");
|
|
||||||
AddExchangeSharpProviders<ExchangeBittrexAPI>("bittrex");
|
|
||||||
AddExchangeSharpProviders<ExchangePoloniexAPI>("poloniex");
|
|
||||||
AddExchangeSharpProviders<ExchangeNDAXAPI>("ndax");
|
|
||||||
|
|
||||||
// Handmade providers
|
|
||||||
Providers.Add("hitbtc", new HitBTCRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_HITBTC")));
|
|
||||||
Providers.Add("coingecko", new CoinGeckoRateProvider(_httpClientFactory));
|
|
||||||
Providers.Add("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_KRAKEN") });
|
|
||||||
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));
|
|
||||||
Providers.Add("buda", new BudaRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BUDA")));
|
|
||||||
Providers.Add("bitbank", new BitbankRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITBANK")));
|
|
||||||
Providers.Add("bitpay", new BitpayRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITPAY")));
|
|
||||||
Providers.Add("ripio", new RipioExchangeProvider(_httpClientFactory?.CreateClient("EXCHANGE_RIPIO")));
|
|
||||||
Providers.Add("cryptomarket", new CryptoMarketExchangeRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_CRYPTOMARKET")));
|
|
||||||
Providers.Add("bitflyer", new BitflyerRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITFLYER")));
|
|
||||||
Providers.Add("yadio", new YadioRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_YADIO")));
|
|
||||||
Providers.Add("btcturk", new BtcTurkRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BTCTURK")));
|
|
||||||
// Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));
|
|
||||||
|
|
||||||
|
|
||||||
// Backward compatibility: coinaverage should be using coingecko to prevent stores from breaking
|
|
||||||
Providers.Add("coinaverage", new CoinGeckoRateProvider(_httpClientFactory));
|
|
||||||
|
|
||||||
AddExchangeSharpProviders<ExchangeBitfinexAPI>("bitfinex");
|
|
||||||
AddExchangeSharpProviders<ExchangeOKExAPI>("okex");
|
|
||||||
AddExchangeSharpProviders<ExchangeCoinbaseAPI>("coinbasepro");
|
|
||||||
// Those exchanges make too many requests, exchange sharp do not parallelize so it is too slow...
|
|
||||||
//AddExchangeSharpProviders<ExchangeGeminiAPI>("gemini");
|
|
||||||
//AddExchangeSharpProviders<ExchangeBitstampAPI>("bitstamp");
|
|
||||||
//AddExchangeSharpProviders<ExchangeBitMEXAPI>("bitmex");
|
|
||||||
|
|
||||||
foreach (var provider in Providers.ToArray())
|
foreach (var provider in Providers.ToArray())
|
||||||
{
|
{
|
||||||
var prov = new BackgroundFetcherRateProvider(Providers[provider.Key]);
|
var prov = new BackgroundFetcherRateProvider(Providers[provider.Key]);
|
||||||
prov.RefreshRate = TimeSpan.FromMinutes(1.0);
|
prov.RefreshRate = TimeSpan.FromMinutes(1.0);
|
||||||
prov.ValidatyTime = TimeSpan.FromMinutes(5.0);
|
prov.ValidatyTime = TimeSpan.FromMinutes(5.0);
|
||||||
Providers[provider.Key] = prov;
|
Providers[provider.Key] = prov;
|
||||||
|
var rsi = provider.Value.RateSourceInfo;
|
||||||
|
AvailableRateProviders.Add(new(rsi.Id, rsi.DisplayName, rsi.Url));
|
||||||
}
|
}
|
||||||
Providers["gdax"] = Providers["coinbasepro"];
|
|
||||||
|
|
||||||
foreach (var supportedExchange in GetCoinGeckoSupportedExchanges())
|
foreach (var supportedExchange in CoinGeckoRateProvider.SupportedExchanges.Values)
|
||||||
{
|
{
|
||||||
if (!Providers.ContainsKey(supportedExchange.Id) && supportedExchange.Id != CoinGeckoRateProvider.CoinGeckoName)
|
if (!Providers.ContainsKey(supportedExchange.Id) && supportedExchange.Id != CoinGeckoRateProvider.CoinGeckoName)
|
||||||
{
|
{
|
||||||
var coingecko = new CoinGeckoRateProvider(_httpClientFactory)
|
var coingecko = new CoinGeckoRateProvider(_httpClientFactory)
|
||||||
{
|
{
|
||||||
UnderlyingExchange = supportedExchange.SourceId
|
UnderlyingExchange = supportedExchange.Id
|
||||||
};
|
};
|
||||||
var bgFetcher = new BackgroundFetcherRateProvider(coingecko);
|
var bgFetcher = new BackgroundFetcherRateProvider(coingecko);
|
||||||
bgFetcher.RefreshRate = TimeSpan.FromMinutes(1.0);
|
bgFetcher.RefreshRate = TimeSpan.FromMinutes(1.0);
|
||||||
bgFetcher.ValidatyTime = TimeSpan.FromMinutes(5.0);
|
bgFetcher.ValidatyTime = TimeSpan.FromMinutes(5.0);
|
||||||
Providers.Add(supportedExchange.Id, bgFetcher);
|
Providers.Add(supportedExchange.Id, bgFetcher);
|
||||||
|
var rsi = coingecko.RateSourceInfo;
|
||||||
|
AvailableRateProviders.Add(new(rsi.Id, rsi.DisplayName, rsi.Url, RateSource.Coingecko));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AvailableRateProviders.Sort((a,b) => StringComparer.Ordinal.Compare(a.DisplayName, b.DisplayName));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRateProvider AddExchangeSharpProviders<T>(string providerName) where T : ExchangeAPI
|
public List<AvailableRateProvider> AvailableRateProviders { get; } = new List<AvailableRateProvider>();
|
||||||
{
|
|
||||||
var provider = new ExchangeSharpRateProvider<T>(_httpClientFactory.CreateClient($"EXCHANGE_{providerName}".ToUpperInvariant()));
|
|
||||||
Providers.Add(providerName, provider);
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<AvailableRateProvider> _AvailableRateProviders = null;
|
|
||||||
public IEnumerable<AvailableRateProvider> GetSupportedExchanges()
|
|
||||||
{
|
|
||||||
if (_AvailableRateProviders == null)
|
|
||||||
{
|
|
||||||
var availableProviders = new Dictionary<string, AvailableRateProvider>();
|
|
||||||
foreach (var exchange in GetDirectlySupportedExchanges())
|
|
||||||
{
|
|
||||||
availableProviders.Add(exchange.Id, exchange);
|
|
||||||
}
|
|
||||||
foreach (var exchange in GetCoinGeckoSupportedExchanges())
|
|
||||||
{
|
|
||||||
availableProviders.TryAdd(exchange.Id, exchange);
|
|
||||||
}
|
|
||||||
_AvailableRateProviders = availableProviders.Values.OrderBy(o => o.Name).ToArray();
|
|
||||||
}
|
|
||||||
return _AvailableRateProviders;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal IEnumerable<AvailableRateProvider> GetCoinGeckoSupportedExchanges()
|
|
||||||
{
|
|
||||||
return JArray.Parse(CoinGeckoRateProvider.SupportedExchanges).Select(token =>
|
|
||||||
new AvailableRateProvider(Normalize(token["id"].ToString().ToLowerInvariant()), token["id"].ToString().ToLowerInvariant(), token["name"].ToString(),
|
|
||||||
$"https://api.coingecko.com/api/v3/exchanges/{token["id"]}/tickers", RateSource.Coingecko));
|
|
||||||
}
|
|
||||||
|
|
||||||
private string Normalize(string name)
|
|
||||||
{
|
|
||||||
if (name == "oasis_trade")
|
|
||||||
return "oasisdex";
|
|
||||||
if (name == "gdax")
|
|
||||||
return "coinbasepro";
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<QueryRateResult> QueryRates(string exchangeName, CancellationToken cancellationToken)
|
public async Task<QueryRateResult> QueryRates(string exchangeName, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using BTCPayServer.Configuration;
|
|||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
|
using BTCPayServer.Hosting;
|
||||||
using BTCPayServer.JsonConverters;
|
using BTCPayServer.JsonConverters;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Bitcoin;
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
@@ -29,6 +30,7 @@ using BTCPayServer.Validation;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Configuration.Memory;
|
using Microsoft.Extensions.Configuration.Memory;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.DataEncoders;
|
using NBitcoin.DataEncoders;
|
||||||
@@ -787,13 +789,19 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
public static RateProviderFactory CreateBTCPayRateFactory()
|
public static RateProviderFactory CreateBTCPayRateFactory()
|
||||||
{
|
{
|
||||||
return new RateProviderFactory(TestUtils.CreateHttpFactory());
|
ServiceCollection services = new ServiceCollection();
|
||||||
|
services.AddHttpClient();
|
||||||
|
BTCPayServerServices.RegisterRateSources(services);
|
||||||
|
var o = services.BuildServiceProvider();
|
||||||
|
return new RateProviderFactory(TestUtils.CreateHttpFactory(), o.GetService<IEnumerable<IRateProvider>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpyRateProvider : IRateProvider
|
class SpyRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
public bool Hit { get; set; }
|
public bool Hit { get; set; }
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => new("spy", "SPY", "https://spy.org");
|
||||||
|
|
||||||
public Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
public Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Hit = true;
|
Hit = true;
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ namespace BTCPayServer.Tests.Mocks
|
|||||||
{
|
{
|
||||||
public List<PairRate> ExchangeRates { get; set; } = new List<PairRate>();
|
public List<PairRate> ExchangeRates { get; set; } = new List<PairRate>();
|
||||||
|
|
||||||
|
public RateSourceInfo RateSourceInfo => new RateSourceInfo("mock", "Mock", "https://mock.rf");
|
||||||
|
|
||||||
public MockRateProvider()
|
public MockRateProvider()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ namespace BTCPayServer.Tests
|
|||||||
var r = RandomUtils.GetUInt32();
|
var r = RandomUtils.GetUInt32();
|
||||||
PayTester.Postgres = PayTester.Postgres.Replace("btcpayserver", $"btcpayserver{r}");
|
PayTester.Postgres = PayTester.Postgres.Replace("btcpayserver", $"btcpayserver{r}");
|
||||||
PayTester.MySQL = PayTester.MySQL.Replace("btcpayserver", $"btcpayserver{r}");
|
PayTester.MySQL = PayTester.MySQL.Replace("btcpayserver", $"btcpayserver{r}");
|
||||||
|
TestLogs.LogInformation($"Database used: btcpayserver{r}");
|
||||||
}
|
}
|
||||||
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture);
|
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture);
|
||||||
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace BTCPayServer.Tests
|
|||||||
string[] brokenShitcoinCasinos = { };
|
string[] brokenShitcoinCasinos = { };
|
||||||
var skipped = 0;
|
var skipped = 0;
|
||||||
var factory = FastTests.CreateBTCPayRateFactory();
|
var factory = FastTests.CreateBTCPayRateFactory();
|
||||||
var directlySupported = factory.GetSupportedExchanges().Where(s => s.Source == RateSource.Direct)
|
var directlySupported = factory.AvailableRateProviders.Where(s => s.Source == RateSource.Direct)
|
||||||
.Select(s => s.Id).ToHashSet();
|
.Select(s => s.Id).ToHashSet();
|
||||||
foreach (var result in factory
|
foreach (var result in factory
|
||||||
.Providers
|
.Providers
|
||||||
|
|||||||
@@ -2627,6 +2627,28 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Single(payoutLabel.PullPaymentPayouts["pp2"]);
|
Assert.Single(payoutLabel.PullPaymentPayouts["pp2"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact(Timeout = LongRunningTestTimeout)]
|
||||||
|
[Trait("Integration", "Integration")]
|
||||||
|
public async Task CanDoRateSourceMigration()
|
||||||
|
{
|
||||||
|
using var tester = CreateServerTester(newDb: true);
|
||||||
|
await tester.StartAsync();
|
||||||
|
var acc = tester.NewAccount();
|
||||||
|
await acc.CreateStoreAsync();
|
||||||
|
var db = tester.PayTester.GetService<ApplicationDbContextFactory>();
|
||||||
|
using var ctx = db.CreateContext();
|
||||||
|
var store = (await ctx.Stores.AsNoTracking().ToListAsync())[0];
|
||||||
|
var b = store.GetStoreBlob();
|
||||||
|
b.PreferredExchange = "coinaverage";
|
||||||
|
store.SetStoreBlob(b);
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
await ctx.Database.ExecuteSqlRawAsync("DELETE FROM \"__EFMigrationsHistory\" WHERE \"MigrationId\"='20230123062447_migrateoldratesource'");
|
||||||
|
await ctx.Database.MigrateAsync();
|
||||||
|
store = (await ctx.Stores.AsNoTracking().ToListAsync())[0];
|
||||||
|
b = store.GetStoreBlob();
|
||||||
|
Assert.Equal("coingecko", b.PreferredExchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact(Timeout = LongRunningTestTimeout)]
|
[Fact(Timeout = LongRunningTestTimeout)]
|
||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)]
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)]
|
||||||
public ActionResult<List<RateSource>> GetRateSources()
|
public ActionResult<List<RateSource>> GetRateSources()
|
||||||
{
|
{
|
||||||
return Ok(_rateProviderFactory.RateProviderFactory.GetSupportedExchanges().Select(provider =>
|
return Ok(_rateProviderFactory.RateProviderFactory.AvailableRateProviders.Select(provider =>
|
||||||
new RateSource() { Id = provider.Id, Name = provider.DisplayName }));
|
new RateSource() { Id = provider.Id, Name = provider.DisplayName }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ $"The preferredSource is required if you aren't using custom scripts");
|
|||||||
|
|
||||||
configuration.PreferredSource = _rateProviderFactory
|
configuration.PreferredSource = _rateProviderFactory
|
||||||
.RateProviderFactory
|
.RateProviderFactory
|
||||||
.GetSupportedExchanges()
|
.AvailableRateProviders
|
||||||
.FirstOrDefault(s =>
|
.FirstOrDefault(s =>
|
||||||
s.Id.Equals(configuration.PreferredSource,
|
s.Id.Equals(configuration.PreferredSource,
|
||||||
StringComparison.InvariantCultureIgnoreCase))?.Id;
|
StringComparison.InvariantCultureIgnoreCase))?.Id;
|
||||||
|
|||||||
@@ -759,8 +759,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
private IEnumerable<AvailableRateProvider> GetSupportedExchanges()
|
private IEnumerable<AvailableRateProvider> GetSupportedExchanges()
|
||||||
{
|
{
|
||||||
var exchanges = _RateFactory.RateProviderFactory.GetSupportedExchanges();
|
return _RateFactory.RateProviderFactory.AvailableRateProviders
|
||||||
return exchanges
|
|
||||||
.Where(r => !string.IsNullOrWhiteSpace(r.Name))
|
.Where(r => !string.IsNullOrWhiteSpace(r.Name))
|
||||||
.OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase);
|
.OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ namespace BTCPayServer.Controllers
|
|||||||
private SelectList GetExchangesSelectList(string selected)
|
private SelectList GetExchangesSelectList(string selected)
|
||||||
{
|
{
|
||||||
var exchanges = _rateFactory.RateProviderFactory
|
var exchanges = _rateFactory.RateProviderFactory
|
||||||
.GetSupportedExchanges()
|
.AvailableRateProviders
|
||||||
.Where(r => !string.IsNullOrWhiteSpace(r.Name))
|
.Where(r => !string.IsNullOrWhiteSpace(r.Name))
|
||||||
.OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase)
|
.OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ using NBXplorer.DerivationStrategy;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NicolasDorier.RateLimits;
|
using NicolasDorier.RateLimits;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using ExchangeSharp;
|
||||||
|
using BTCPayServer.Rating;
|
||||||
|
using System.Configuration.Provider;
|
||||||
|
using BTCPayServer.Rating.Providers;
|
||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
using BTCPayServer.Services.Altcoins.Monero;
|
using BTCPayServer.Services.Altcoins.Monero;
|
||||||
using BTCPayServer.Services.Altcoins.Zcash;
|
using BTCPayServer.Services.Altcoins.Zcash;
|
||||||
@@ -423,6 +427,7 @@ namespace BTCPayServer.Hosting
|
|||||||
else
|
else
|
||||||
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
|
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
|
||||||
});
|
});
|
||||||
|
RegisterRateSources(services);
|
||||||
services.TryAddSingleton<RateProviderFactory>();
|
services.TryAddSingleton<RateProviderFactory>();
|
||||||
services.TryAddSingleton<RateFetcher>();
|
services.TryAddSingleton<RateFetcher>();
|
||||||
|
|
||||||
@@ -476,6 +481,56 @@ namespace BTCPayServer.Hosting
|
|||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void RegisterRateSources(IServiceCollection services)
|
||||||
|
{
|
||||||
|
// We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request
|
||||||
|
services.AddRateProviderExchangeSharp<ExchangeBinanceAPI>(new("binance", "Binance", "https://api.binance.com/api/v1/ticker/24hr"));
|
||||||
|
services.AddRateProviderExchangeSharp<ExchangeBittrexAPI>(new("bittrex", "Bittrex", "https://bittrex.com/api/v1.1/public/getmarketsummaries"));
|
||||||
|
services.AddRateProviderExchangeSharp<ExchangePoloniexAPI>(new("poloniex", "Poloniex", "https://poloniex.com/public?command=returnTicker"));
|
||||||
|
services.AddRateProviderExchangeSharp<ExchangeNDAXAPI>(new("ndax", "NDAX", "https://ndax.io/api/returnTicker"));
|
||||||
|
|
||||||
|
services.AddRateProviderExchangeSharp<ExchangeBitfinexAPI>(new("bitfinex", "Bitfinex", "https://api.bitfinex.com/v2/tickers?symbols=tBTCUSD,tLTCUSD,tLTCBTC,tETHUSD,tETHBTC,tETCBTC,tETCUSD,tRRTUSD,tRRTBTC,tZECUSD,tZECBTC,tXMRUSD,tXMRBTC,tDSHUSD,tDSHBTC,tBTCEUR,tBTCJPY,tXRPUSD,tXRPBTC,tIOTUSD,tIOTBTC,tIOTETH,tEOSUSD,tEOSBTC,tEOSETH,tSANUSD,tSANBTC,tSANETH,tOMGUSD,tOMGBTC,tOMGETH,tNEOUSD,tNEOBTC,tNEOETH,tETPUSD,tETPBTC,tETPETH,tQTMUSD,tQTMBTC,tQTMETH,tAVTUSD,tAVTBTC,tAVTETH,tEDOUSD,tEDOBTC,tEDOETH,tBTGUSD,tBTGBTC,tDATUSD,tDATBTC,tDATETH,tQSHUSD,tQSHBTC,tQSHETH,tYYWUSD,tYYWBTC,tYYWETH,tGNTUSD,tGNTBTC,tGNTETH,tSNTUSD,tSNTBTC,tSNTETH,tIOTEUR,tBATUSD,tBATBTC,tBATETH,tMNAUSD,tMNABTC,tMNAETH,tFUNUSD,tFUNBTC,tFUNETH,tZRXUSD,tZRXBTC,tZRXETH,tTNBUSD,tTNBBTC,tTNBETH,tSPKUSD,tSPKBTC,tSPKETH,tTRXUSD,tTRXBTC,tTRXETH,tRCNUSD,tRCNBTC,tRCNETH,tRLCUSD,tRLCBTC,tRLCETH,tAIDUSD,tAIDBTC,tAIDETH,tSNGUSD,tSNGBTC,tSNGETH,tREPUSD,tREPBTC,tREPETH,tELFUSD,tELFBTC,tELFETH,tNECUSD,tNECBTC,tNECETH,tBTCGBP,tETHEUR,tETHJPY,tETHGBP,tNEOEUR,tNEOJPY,tNEOGBP,tEOSEUR,tEOSJPY,tEOSGBP,tIOTJPY,tIOTGBP,tIOSUSD,tIOSBTC,tIOSETH,tAIOUSD,tAIOBTC,tAIOETH,tREQUSD,tREQBTC,tREQETH,tRDNUSD,tRDNBTC,tRDNETH,tLRCUSD,tLRCBTC,tLRCETH,tWAXUSD,tWAXBTC,tWAXETH,tDAIUSD,tDAIBTC,tDAIETH,tAGIUSD,tAGIBTC,tAGIETH,tBFTUSD,tBFTBTC,tBFTETH,tMTNUSD,tMTNBTC,tMTNETH,tODEUSD,tODEBTC,tODEETH,tANTUSD,tANTBTC,tANTETH,tDTHUSD,tDTHBTC,tDTHETH,tMITUSD,tMITBTC,tMITETH,tSTJUSD,tSTJBTC,tSTJETH,tXLMUSD,tXLMEUR,tXLMJPY,tXLMGBP,tXLMBTC,tXLMETH,tXVGUSD,tXVGEUR,tXVGJPY,tXVGGBP,tXVGBTC,tXVGETH,tBCIUSD,tBCIBTC,tMKRUSD,tMKRBTC,tMKRETH,tKNCUSD,tKNCBTC,tKNCETH,tPOAUSD,tPOABTC,tPOAETH,tEVTUSD,tLYMUSD,tLYMBTC,tLYMETH,tUTKUSD,tUTKBTC,tUTKETH,tVEEUSD,tVEEBTC,tVEEETH,tDADUSD,tDADBTC,tDADETH,tORSUSD,tORSBTC,tORSETH,tAUCUSD,tAUCBTC,tAUCETH,tPOYUSD,tPOYBTC,tPOYETH,tFSNUSD,tFSNBTC,tFSNETH,tCBTUSD,tCBTBTC,tCBTETH,tZCNUSD,tZCNBTC,tZCNETH,tSENUSD,tSENBTC,tSENETH,tNCAUSD,tNCABTC,tNCAETH,tCNDUSD,tCNDBTC,tCNDETH,tCTXUSD,tCTXBTC,tCTXETH,tPAIUSD,tPAIBTC,tSEEUSD,tSEEBTC,tSEEETH,tESSUSD,tESSBTC,tESSETH,tATMUSD,tATMBTC,tATMETH,tHOTUSD,tHOTBTC,tHOTETH,tDTAUSD,tDTABTC,tDTAETH,tIQXUSD,tIQXBTC,tIQXEOS,tWPRUSD,tWPRBTC,tWPRETH,tZILUSD,tZILBTC,tZILETH,tBNTUSD,tBNTBTC,tBNTETH,tABSUSD,tABSETH,tXRAUSD,tXRAETH,tMANUSD,tMANETH,tBBNUSD,tBBNETH,tNIOUSD,tNIOETH,tDGXUSD,tDGXETH,tVETUSD,tVETBTC,tVETETH,tUTNUSD,tUTNETH,tTKNUSD,tTKNETH,tGOTUSD,tGOTEUR,tGOTETH,tXTZUSD,tXTZBTC,tCNNUSD,tCNNETH,tBOXUSD,tBOXETH,tTRXEUR,tTRXGBP,tTRXJPY,tMGOUSD,tMGOETH,tRTEUSD,tRTEETH,tYGGUSD,tYGGETH,tMLNUSD,tMLNETH,tWTCUSD,tWTCETH,tCSXUSD,tCSXETH,tOMNUSD,tOMNBTC,tINTUSD,tINTETH,tDRNUSD,tDRNETH,tPNKUSD,tPNKETH,tDGBUSD,tDGBBTC,tBSVUSD,tBSVBTC,tBABUSD,tBABBTC,tWLOUSD,tWLOXLM,tVLDUSD,tVLDETH,tENJUSD,tENJETH,tONLUSD,tONLETH,tRBTUSD,tRBTBTC,tUSTUSD,tEUTEUR,tEUTUSD,tGSDUSD,tUDCUSD,tTSDUSD,tPAXUSD,tRIFUSD,tRIFBTC,tPASUSD,tPASETH,tVSYUSD,tVSYBTC,tZRXDAI,tMKRDAI,tOMGDAI,tBTTUSD,tBTTBTC,tBTCUST,tETHUST,tCLOUSD,tCLOBTC,tIMPUSD,tIMPETH,tLTCUST,tEOSUST,tBABUST,tSCRUSD,tSCRETH,tGNOUSD,tGNOETH,tGENUSD,tGENETH,tATOUSD,tATOBTC,tATOETH,tWBTUSD,tXCHUSD,tEUSUSD,tWBTETH,tXCHETH,tEUSETH,tLEOUSD,tLEOBTC,tLEOUST,tLEOEOS,tLEOETH,tASTUSD,tASTETH,tFOAUSD,tFOAETH,tUFRUSD,tUFRETH,tZBTUSD,tZBTUST,tOKBUSD,tUSKUSD,tGTXUSD,tKANUSD,tOKBUST,tOKBETH,tOKBBTC,tUSKUST,tUSKETH,tUSKBTC,tUSKEOS,tGTXUST,tKANUST,tAMPUSD,tALGUSD,tALGBTC,tALGUST,tBTCXCH,tSWMUSD,tSWMETH,tTRIUSD,tTRIETH,tLOOUSD,tLOOETH,tAMPUST,tDUSK:USD,tDUSK:BTC,tUOSUSD,tUOSBTC,tRRBUSD,tRRBUST,tDTXUSD,tDTXUST,tAMPBTC,tFTTUSD,tFTTUST,tPAXUST,tUDCUST,tTSDUST,tBTC:CNHT,tUST:CNHT,tCNH:CNHT,tCHZUSD,tCHZUST,tBTCF0:USTF0,tETHF0:USTF0"));
|
||||||
|
services.AddRateProviderExchangeSharp<ExchangeOKExAPI>(new("okex", "OKEx", "https://www.okex.com/api/futures/v3/instruments/ticker"));
|
||||||
|
services.AddRateProviderExchangeSharp<ExchangeCoinbaseAPI>(new("coinbasepro", "Coinbase Pro", "https://api.pro.coinbase.com/products"));
|
||||||
|
|
||||||
|
|
||||||
|
// Handmade providers
|
||||||
|
services.AddRateProvider<HitBTCRateProvider>();
|
||||||
|
services.AddRateProvider<CoinGeckoRateProvider>();
|
||||||
|
services.AddRateProvider<KrakenExchangeRateProvider>();
|
||||||
|
services.AddRateProvider<ByllsRateProvider>();
|
||||||
|
services.AddRateProvider<BudaRateProvider>();
|
||||||
|
services.AddRateProvider<BitbankRateProvider>();
|
||||||
|
services.AddRateProvider<BitpayRateProvider>();
|
||||||
|
services.AddRateProvider<RipioExchangeProvider>();
|
||||||
|
services.AddRateProvider<CryptoMarketExchangeRateProvider>();
|
||||||
|
services.AddRateProvider<BitflyerRateProvider>();
|
||||||
|
services.AddRateProvider<YadioRateProvider>();
|
||||||
|
services.AddRateProvider<BtcTurkRateProvider>();
|
||||||
|
|
||||||
|
// Broken
|
||||||
|
// Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));
|
||||||
|
|
||||||
|
// Those exchanges make too many requests, exchange sharp do not parallelize so it is too slow...
|
||||||
|
//AddExchangeSharpProviders<ExchangeGeminiAPI>("gemini");
|
||||||
|
//AddExchangeSharpProviders<ExchangeBitstampAPI>("bitstamp");
|
||||||
|
//AddExchangeSharpProviders<ExchangeBitMEXAPI>("bitmex");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddRateProvider<T>(this IServiceCollection services) where T : class, IRateProvider
|
||||||
|
{
|
||||||
|
services.AddSingleton<IRateProvider, T>();
|
||||||
|
}
|
||||||
|
public static void AddRateProviderExchangeSharp<T>(this IServiceCollection services, RateSourceInfo rateInfo) where T : ExchangeAPI
|
||||||
|
{
|
||||||
|
services.AddSingleton<IRateProvider, ExchangeSharpRateProvider<T>>(o =>
|
||||||
|
{
|
||||||
|
var instance = ActivatorUtilities.CreateInstance<ExchangeSharpRateProvider<T>>(o);
|
||||||
|
instance.RateSourceInfo = rateInfo;
|
||||||
|
return instance;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddSettingsAccessor<T>(IServiceCollection services) where T : class, new()
|
private static void AddSettingsAccessor<T>(IServiceCollection services) where T : class, new()
|
||||||
{
|
{
|
||||||
services.TryAddSingleton<ISettingsAccessor<T>, SettingsAccessor<T>>();
|
services.TryAddSingleton<ISettingsAccessor<T>, SettingsAccessor<T>>();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
|
|
||||||
public void SetExchangeRates(IEnumerable<AvailableRateProvider> supportedList, string preferredExchange)
|
public void SetExchangeRates(IEnumerable<AvailableRateProvider> supportedList, string preferredExchange)
|
||||||
{
|
{
|
||||||
supportedList = supportedList.Select(a => new AvailableRateProvider(a.Id, a.SourceId, a.DisplayName, a.Url, a.Source)).ToArray();
|
supportedList = supportedList.Select(a => new AvailableRateProvider(a.Id, a.DisplayName, a.Url, a.Source)).ToArray();
|
||||||
var chosen = supportedList.FirstOrDefault(f => f.Id == preferredExchange) ?? supportedList.FirstOrDefault();
|
var chosen = supportedList.FirstOrDefault(f => f.Id == preferredExchange) ?? supportedList.FirstOrDefault();
|
||||||
Exchanges = new SelectList(supportedList, nameof(chosen.Id), nameof(chosen.Name), chosen);
|
Exchanges = new SelectList(supportedList, nameof(chosen.Id), nameof(chosen.Name), chosen);
|
||||||
PreferredExchange = chosen?.Id;
|
PreferredExchange = chosen?.Id;
|
||||||
|
|||||||
Reference in New Issue
Block a user