mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Keep coinaverage compatibility, improve UX, hardcode feed provider supported exchanges
This commit is contained in:
@@ -21,6 +21,11 @@ namespace BTCPayServer
|
|||||||
: "http://explorer.litecointools.com/tx/{0}",
|
: "http://explorer.litecointools.com/tx/{0}",
|
||||||
NBXplorerNetwork = nbxplorerNetwork,
|
NBXplorerNetwork = nbxplorerNetwork,
|
||||||
UriScheme = "litecoin",
|
UriScheme = "litecoin",
|
||||||
|
DefaultRateRules = new[]
|
||||||
|
{
|
||||||
|
"LTC_X = LTC_BTC * BTC_X",
|
||||||
|
"LTC_BTC = coingecko(LTC_BTC)"
|
||||||
|
},
|
||||||
CryptoImagePath = "imlegacy/litecoin.svg",
|
CryptoImagePath = "imlegacy/litecoin.svg",
|
||||||
LightningImagePath = "imlegacy/litecoin-lightning.svg",
|
LightningImagePath = "imlegacy/litecoin-lightning.svg",
|
||||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ namespace BTCPayServer
|
|||||||
NetworkType == NetworkType.Mainnet
|
NetworkType == NetworkType.Mainnet
|
||||||
? "https://www.exploremonero.com/transaction/{0}"
|
? "https://www.exploremonero.com/transaction/{0}"
|
||||||
: "https://testnet.xmrchain.net/tx/{0}",
|
: "https://testnet.xmrchain.net/tx/{0}",
|
||||||
|
DefaultRateRules = new[]
|
||||||
|
{
|
||||||
|
"XMR_X = XMR_BTC * BTC_X",
|
||||||
|
"XMR_BTC = kraken(XMR_BTC)"
|
||||||
|
},
|
||||||
CryptoImagePath = "/imlegacy/monero.svg"
|
CryptoImagePath = "/imlegacy/monero.svg"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
namespace BTCPayServer.Rating
|
namespace BTCPayServer.Rating
|
||||||
{
|
{
|
||||||
|
public enum RateSource
|
||||||
|
{
|
||||||
|
Coingecko,
|
||||||
|
CoinAverage,
|
||||||
|
Direct
|
||||||
|
}
|
||||||
public class AvailableRateProvider
|
public class AvailableRateProvider
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; }
|
||||||
public string Url { get; set; }
|
public string Url { get; }
|
||||||
public string Id { get; set; }
|
public string Id { get; }
|
||||||
|
public RateSource Source { get; }
|
||||||
|
|
||||||
public AvailableRateProvider(string id, string name, string url)
|
public AvailableRateProvider(string id, string name, string url, RateSource source)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
Url = url;
|
Url = url;
|
||||||
|
Source = source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,19 +19,6 @@ namespace BTCPayServer.Services.Rates
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetExchangeTickersResponse
|
|
||||||
{
|
|
||||||
public class Exchange
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
[JsonProperty("display_name")]
|
|
||||||
public string DisplayName { get; set; }
|
|
||||||
public string[] Symbols { get; set; }
|
|
||||||
}
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public Exchange[] Exchanges { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RatesSetting
|
public class RatesSetting
|
||||||
{
|
{
|
||||||
public string PublicKey { get; set; }
|
public string PublicKey { get; set; }
|
||||||
@@ -196,32 +183,6 @@ namespace BTCPayServer.Services.Rates
|
|||||||
response.RequestsPerPeriod = jobj["requests_per_period"].Value<int>();
|
response.RequestsPerPeriod = jobj["requests_per_period"].Value<int>();
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<GetExchangeTickersResponse> GetExchangeTickersAsync()
|
|
||||||
{
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/symbols/exchanges/ticker");
|
|
||||||
var auth = Authenticator;
|
|
||||||
if (auth != null)
|
|
||||||
{
|
|
||||||
await auth.AddHeader(request);
|
|
||||||
}
|
|
||||||
var resp = await HttpClient.SendAsync(request);
|
|
||||||
resp.EnsureSuccessStatusCode();
|
|
||||||
var jobj = JObject.Parse(await resp.Content.ReadAsStringAsync());
|
|
||||||
var response = new GetExchangeTickersResponse();
|
|
||||||
response.Success = jobj["success"].Value<bool>();
|
|
||||||
var exchanges = (JObject)jobj["exchanges"];
|
|
||||||
response.Exchanges = exchanges
|
|
||||||
.Properties()
|
|
||||||
.Select(p =>
|
|
||||||
{
|
|
||||||
var exchange = JsonConvert.DeserializeObject<GetExchangeTickersResponse.Exchange>(p.Value.ToString());
|
|
||||||
exchange.Name = p.Name;
|
|
||||||
return exchange;
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetRateLimitsResponse
|
public class GetRateLimitsResponse
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -9,6 +9,7 @@ using ExchangeSharp;
|
|||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
|
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Rates
|
namespace BTCPayServer.Services.Rates
|
||||||
@@ -57,6 +58,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
_CacheOptions = cacheOptions;
|
_CacheOptions = cacheOptions;
|
||||||
// We use 15 min because of limits with free version of bitcoinaverage
|
// We use 15 min because of limits with free version of bitcoinaverage
|
||||||
CacheSpan = TimeSpan.FromMinutes(15.0);
|
CacheSpan = TimeSpan.FromMinutes(15.0);
|
||||||
|
InitExchanges();
|
||||||
}
|
}
|
||||||
private IOptions<MemoryCacheOptions> _CacheOptions;
|
private IOptions<MemoryCacheOptions> _CacheOptions;
|
||||||
TimeSpan _CacheSpan;
|
TimeSpan _CacheSpan;
|
||||||
@@ -96,8 +98,22 @@ namespace BTCPayServer.Services.Rates
|
|||||||
return _DirectProviders;
|
return _DirectProviders;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
internal IEnumerable<AvailableRateProvider> GetDirectlySupportedExchanges()
|
||||||
|
{
|
||||||
|
yield return new AvailableRateProvider("binance", "Binance", "https://api.binance.com/api/v1/ticker/24hr", RateSource.Direct);
|
||||||
|
yield return new AvailableRateProvider("bittrex", "Bittrex", "https://bittrex.com/api/v1.1/public/getmarketsummaries", RateSource.Direct);
|
||||||
|
yield return new AvailableRateProvider("poloniex", "Poloniex", "https://poloniex.com/public?command=returnTicker", RateSource.Direct);
|
||||||
|
yield return new AvailableRateProvider("hitbtc", "HitBTC", "https://api.hitbtc.com/api/2/public/ticker", RateSource.Direct);
|
||||||
|
yield return new AvailableRateProvider("ndax", "NDAX", "https://ndax.io/api/returnTicker", RateSource.Direct);
|
||||||
|
|
||||||
public async Task InitExchanges()
|
yield return new AvailableRateProvider(CoinGeckoRateProvider.CoinGeckoName, "Coin Gecko", "https://api.coingecko.com/api/v3/exchange_rates", RateSource.Direct);
|
||||||
|
yield return new AvailableRateProvider(CoinAverageRateProvider.CoinAverageName, "Coin Average", "https://apiv2.bitcoinaverage.com/indices/global/ticker/short", RateSource.Direct);
|
||||||
|
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", RateSource.Direct);
|
||||||
|
yield return new AvailableRateProvider("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD", RateSource.Direct);
|
||||||
|
yield return new AvailableRateProvider("bitbank", "Bitbank", "https://public.bitbank.cc/prices", RateSource.Direct);
|
||||||
|
yield return new AvailableRateProvider("bitpay", "Bitpay", "https://bitpay.com/rates", RateSource.Direct);
|
||||||
|
}
|
||||||
|
void InitExchanges()
|
||||||
{
|
{
|
||||||
// We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request
|
// We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request
|
||||||
Providers.Add("binance", new ExchangeSharpRateProvider("binance", new ExchangeBinanceAPI(), true));
|
Providers.Add("binance", new ExchangeSharpRateProvider("binance", new ExchangeBinanceAPI(), true));
|
||||||
@@ -106,10 +122,6 @@ namespace BTCPayServer.Services.Rates
|
|||||||
Providers.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitBTCAPI(), true));
|
Providers.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitBTCAPI(), true));
|
||||||
Providers.Add("ndax", new ExchangeSharpRateProvider("ndax", new ExchangeNDAXAPI(), true));
|
Providers.Add("ndax", new ExchangeSharpRateProvider("ndax", new ExchangeNDAXAPI(), true));
|
||||||
|
|
||||||
// Cryptopia is often not available
|
|
||||||
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
|
|
||||||
// Providers.Add("cryptopia", new ExchangeSharpRateProvider("cryptopia", new ExchangeCryptopiaAPI(), false));
|
|
||||||
|
|
||||||
// Handmade providers
|
// Handmade providers
|
||||||
Providers.Add(CoinGeckoRateProvider.CoinGeckoName, new CoinGeckoRateProvider(_httpClientFactory));
|
Providers.Add(CoinGeckoRateProvider.CoinGeckoName, new CoinGeckoRateProvider(_httpClientFactory));
|
||||||
Providers.Add(CoinAverageRateProvider.CoinAverageName, new CoinAverageRateProvider() { Exchange = CoinAverageRateProvider.CoinAverageName, HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_COINAVERAGE"), Authenticator = _CoinAverageSettings });
|
Providers.Add(CoinAverageRateProvider.CoinAverageName, new CoinAverageRateProvider() { Exchange = CoinAverageRateProvider.CoinAverageName, HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_COINAVERAGE"), Authenticator = _CoinAverageSettings });
|
||||||
@@ -129,7 +141,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
if (provider.Key == "cryptopia") // Shitty exchange, rate often unavailable, it spams the logs
|
if (provider.Key == "cryptopia") // Shitty exchange, rate often unavailable, it spams the logs
|
||||||
continue;
|
continue;
|
||||||
var prov = new BackgroundFetcherRateProvider(provider.Key, Providers[provider.Key]);
|
var prov = new BackgroundFetcherRateProvider(provider.Key, Providers[provider.Key]);
|
||||||
if(provider.Key == CoinGeckoRateProvider.CoinGeckoName)
|
if (provider.Key == CoinGeckoRateProvider.CoinGeckoName)
|
||||||
{
|
{
|
||||||
prov.RefreshRate = CacheSpan;
|
prov.RefreshRate = CacheSpan;
|
||||||
prov.ValidatyTime = CacheSpan + TimeSpan.FromMinutes(1.0);
|
prov.ValidatyTime = CacheSpan + TimeSpan.FromMinutes(1.0);
|
||||||
@@ -143,7 +155,22 @@ namespace BTCPayServer.Services.Rates
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cache = new MemoryCache(_CacheOptions);
|
var cache = new MemoryCache(_CacheOptions);
|
||||||
foreach (var supportedExchange in await GetSupportedExchanges(true))
|
foreach (var supportedExchange in GetCoinGeckoSupportedExchanges())
|
||||||
|
{
|
||||||
|
if (!Providers.ContainsKey(supportedExchange.Id))
|
||||||
|
{
|
||||||
|
var coinAverage = new CoinGeckoRateProvider(_httpClientFactory)
|
||||||
|
{
|
||||||
|
Exchange = supportedExchange.Id
|
||||||
|
};
|
||||||
|
var cached = new CachedRateProvider(supportedExchange.Id, coinAverage, cache)
|
||||||
|
{
|
||||||
|
CacheSpan = CacheSpan
|
||||||
|
};
|
||||||
|
Providers.Add(supportedExchange.Id, cached);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (var supportedExchange in GetCoinAverageSupportedExchanges())
|
||||||
{
|
{
|
||||||
if (!Providers.ContainsKey(supportedExchange.Id))
|
if (!Providers.ContainsKey(supportedExchange.Id))
|
||||||
{
|
{
|
||||||
@@ -160,34 +187,114 @@ namespace BTCPayServer.Services.Rates
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<AvailableRateProvider>> GetSupportedExchanges(bool reload = false)
|
IEnumerable<AvailableRateProvider> _AvailableRateProviders = null;
|
||||||
|
public IEnumerable<AvailableRateProvider> GetSupportedExchanges()
|
||||||
{
|
{
|
||||||
IEnumerable<AvailableRateProvider> exchanges;
|
if (_AvailableRateProviders == null)
|
||||||
switch (Providers[CoinGeckoRateProvider.CoinGeckoName])
|
|
||||||
{
|
{
|
||||||
case BackgroundFetcherRateProvider backgroundFetcherRateProvider:
|
var availableProviders = new Dictionary<string, AvailableRateProvider>();
|
||||||
exchanges = await ((CoinGeckoRateProvider)((BackgroundFetcherRateProvider)Providers[
|
foreach (var exchange in GetDirectlySupportedExchanges())
|
||||||
CoinGeckoRateProvider.CoinGeckoName]).Inner).GetAvailableExchanges(reload);
|
{
|
||||||
break;
|
availableProviders.Add(exchange.Id, exchange);
|
||||||
case CoinGeckoRateProvider coinGeckoRateProvider:
|
}
|
||||||
exchanges = await coinGeckoRateProvider.GetAvailableExchanges(reload);
|
foreach (var exchange in GetCoinGeckoSupportedExchanges())
|
||||||
break;
|
{
|
||||||
default:
|
availableProviders.TryAdd(exchange.Id, exchange);
|
||||||
exchanges = new AvailableRateProvider[0];
|
}
|
||||||
break;
|
foreach (var exchange in GetCoinAverageSupportedExchanges())
|
||||||
|
{
|
||||||
|
availableProviders.TryAdd(exchange.Id, exchange);
|
||||||
|
}
|
||||||
|
_AvailableRateProviders = availableProviders.Values.OrderBy(o => o.Name).ToArray();
|
||||||
}
|
}
|
||||||
// Add other exchanges supported here
|
return _AvailableRateProviders;
|
||||||
return new[]
|
}
|
||||||
|
|
||||||
|
internal IEnumerable<AvailableRateProvider> GetCoinAverageSupportedExchanges()
|
||||||
|
{
|
||||||
|
foreach (var item in
|
||||||
|
new[] {
|
||||||
|
(DisplayName: "Idex", Name: "idex"),
|
||||||
|
(DisplayName: "Coinfloor", Name: "coinfloor"),
|
||||||
|
(DisplayName: "Okex", Name: "okex"),
|
||||||
|
(DisplayName: "Bitfinex", Name: "bitfinex"),
|
||||||
|
(DisplayName: "Bittylicious", Name: "bittylicious"),
|
||||||
|
(DisplayName: "BTC Markets", Name: "btcmarkets"),
|
||||||
|
(DisplayName: "Kucoin", Name: "kucoin"),
|
||||||
|
(DisplayName: "IDAX", Name: "idax"),
|
||||||
|
(DisplayName: "Kraken", Name: "kraken"),
|
||||||
|
(DisplayName: "Bit2C", Name: "bit2c"),
|
||||||
|
(DisplayName: "Mercado Bitcoin", Name: "mercado"),
|
||||||
|
(DisplayName: "CEX.IO", Name: "cex"),
|
||||||
|
(DisplayName: "Bitex.la", Name: "bitex"),
|
||||||
|
(DisplayName: "Quoine", Name: "quoine"),
|
||||||
|
(DisplayName: "Stex", Name: "stex"),
|
||||||
|
(DisplayName: "CoinTiger", Name: "cointiger"),
|
||||||
|
(DisplayName: "Poloniex", Name: "poloniex"),
|
||||||
|
(DisplayName: "Zaif", Name: "zaif"),
|
||||||
|
(DisplayName: "Huobi", Name: "huobi"),
|
||||||
|
(DisplayName: "QuickBitcoin", Name: "quickbitcoin"),
|
||||||
|
(DisplayName: "Tidex", Name: "tidex"),
|
||||||
|
(DisplayName: "Tokenomy", Name: "tokenomy"),
|
||||||
|
(DisplayName: "Bitcoin.co.id", Name: "bitcoin_co_id"),
|
||||||
|
(DisplayName: "Kryptono", Name: "kryptono"),
|
||||||
|
(DisplayName: "Bitso", Name: "bitso"),
|
||||||
|
(DisplayName: "Korbit", Name: "korbit"),
|
||||||
|
(DisplayName: "Yobit", Name: "yobit"),
|
||||||
|
(DisplayName: "BitBargain", Name: "bitbargain"),
|
||||||
|
(DisplayName: "Livecoin", Name: "livecoin"),
|
||||||
|
(DisplayName: "Hotbit", Name: "hotbit"),
|
||||||
|
(DisplayName: "Coincheck", Name: "coincheck"),
|
||||||
|
(DisplayName: "Binance", Name: "binance"),
|
||||||
|
(DisplayName: "Bit-Z", Name: "bitz"),
|
||||||
|
(DisplayName: "Coinbase Pro", Name: "coinbasepro"),
|
||||||
|
(DisplayName: "Rock Trading", Name: "rocktrading"),
|
||||||
|
(DisplayName: "Bittrex", Name: "bittrex"),
|
||||||
|
(DisplayName: "BitBay", Name: "bitbay"),
|
||||||
|
(DisplayName: "Tokenize", Name: "tokenize"),
|
||||||
|
(DisplayName: "Hitbtc", Name: "hitbtc"),
|
||||||
|
(DisplayName: "Upbit", Name: "upbit"),
|
||||||
|
(DisplayName: "Bitstamp", Name: "bitstamp"),
|
||||||
|
(DisplayName: "Luno", Name: "luno"),
|
||||||
|
(DisplayName: "Trade.io", Name: "tradeio"),
|
||||||
|
(DisplayName: "LocalBitcoins", Name: "localbitcoins"),
|
||||||
|
(DisplayName: "Independent Reserve", Name: "independentreserve"),
|
||||||
|
(DisplayName: "Coinsquare", Name: "coinsquare"),
|
||||||
|
(DisplayName: "Exmoney", Name: "exmoney"),
|
||||||
|
(DisplayName: "Coinegg", Name: "coinegg"),
|
||||||
|
(DisplayName: "FYB-SG", Name: "fybsg"),
|
||||||
|
(DisplayName: "Cryptonit", Name: "cryptonit"),
|
||||||
|
(DisplayName: "BTCTurk", Name: "btcturk"),
|
||||||
|
(DisplayName: "bitFlyer", Name: "bitflyer"),
|
||||||
|
(DisplayName: "Negocie Coins", Name: "negociecoins"),
|
||||||
|
(DisplayName: "OasisDEX", Name: "oasisdex"),
|
||||||
|
(DisplayName: "CoinMate", Name: "coinmate"),
|
||||||
|
(DisplayName: "BitForex", Name: "bitforex"),
|
||||||
|
(DisplayName: "Bitsquare", Name: "bitsquare"),
|
||||||
|
(DisplayName: "FYB-SE", Name: "fybse"),
|
||||||
|
(DisplayName: "itBit", Name: "itbit"),
|
||||||
|
})
|
||||||
{
|
{
|
||||||
new AvailableRateProvider(CoinGeckoRateProvider.CoinGeckoName, "Coin Gecko",
|
yield return new AvailableRateProvider(item.Name, item.DisplayName, $"https://apiv2.bitcoinaverage.com/exchanges/{item.Name}", RateSource.CoinAverage);
|
||||||
"https://api.coingecko.com/api/v3/exchange_rates"),
|
}
|
||||||
new AvailableRateProvider("bylls", "Bylls",
|
yield return new AvailableRateProvider("gdax", string.Empty, $"https://apiv2.bitcoinaverage.com/exchanges/gdax", RateSource.CoinAverage);
|
||||||
"https://bylls.com/api/price?from_currency=BTC&to_currency=CAD"),
|
}
|
||||||
new AvailableRateProvider("ndax", "NDAX", "https://ndax.io/api/returnTicker"),
|
|
||||||
new AvailableRateProvider("bitbank", "Bitbank", "https://public.bitbank.cc/prices"),
|
internal IEnumerable<AvailableRateProvider> GetCoinGeckoSupportedExchanges()
|
||||||
new AvailableRateProvider(CoinAverageRateProvider.CoinAverageName, "Coin Average",
|
{
|
||||||
"https://apiv2.bitcoinaverage.com/indices/global/ticker/short")
|
return JArray.Parse(CoinGeckoRateProvider.SupportedExchanges).Select(token =>
|
||||||
}.Concat(exchanges);
|
new AvailableRateProvider(Normalize(token["id"].ToString().ToLowerInvariant()), token["name"].ToString(),
|
||||||
|
$"https://api.coingecko.com/api/v3/exchanges/{token["id"]}/tickers", RateSource.Coingecko))
|
||||||
|
.Concat(new[] { new AvailableRateProvider("gdax", string.Empty, $"https://api.coingecko.com/api/v3/exchanges/gdax", 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)
|
||||||
|
|||||||
@@ -215,8 +215,8 @@ namespace BTCPayServer.Tests
|
|||||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
{
|
{
|
||||||
Exchange = "coingecko",
|
Exchange = "coingecko",
|
||||||
CurrencyPair = CurrencyPair.Parse("LTC_BTC"),
|
CurrencyPair = CurrencyPair.Parse("BTC_LTC"),
|
||||||
BidAsk = new BidAsk(0.001m)
|
BidAsk = new BidAsk(162m)
|
||||||
});
|
});
|
||||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
{
|
{
|
||||||
@@ -262,15 +262,6 @@ namespace BTCPayServer.Tests
|
|||||||
BidAsk = new BidAsk(0.000136m)
|
BidAsk = new BidAsk(0.000136m)
|
||||||
});
|
});
|
||||||
rateProvider.Providers.Add("bitfinex", bitfinex);
|
rateProvider.Providers.Add("bitfinex", bitfinex);
|
||||||
|
|
||||||
|
|
||||||
coinAverageMock.AvailableRateProviders.AddRange(new []
|
|
||||||
{
|
|
||||||
new AvailableRateProvider("bitflyer", "bitflyer", "bitflyer"),
|
|
||||||
new AvailableRateProvider("quadrigacx", "quadrigacx", "quadrigacx"),
|
|
||||||
new AvailableRateProvider("bittrex", "bittrex", "bittrex"),
|
|
||||||
new AvailableRateProvider("bitfinex", "bitfinex", "bitfinex"),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,23 +8,17 @@ using BTCPayServer.Services.Rates;
|
|||||||
|
|
||||||
namespace BTCPayServer.Tests.Mocks
|
namespace BTCPayServer.Tests.Mocks
|
||||||
{
|
{
|
||||||
public class MockRateProvider : CoinGeckoRateProvider
|
public class MockRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
public ExchangeRates ExchangeRates { get; set; } = new ExchangeRates();
|
public ExchangeRates ExchangeRates { get; set; } = new ExchangeRates();
|
||||||
public List<AvailableRateProvider> AvailableRateProviders { get; set; } = new List<AvailableRateProvider>();
|
|
||||||
|
|
||||||
public MockRateProvider():base(null)
|
public MockRateProvider()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
public override Task<ExchangeRates> GetRatesAsync(CancellationToken cancellationToken)
|
public Task<ExchangeRates> GetRatesAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.FromResult(ExchangeRates);
|
return Task.FromResult(ExchangeRates);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<IEnumerable<AvailableRateProvider>> GetAvailableExchanges(bool reload = false)
|
|
||||||
{
|
|
||||||
return Task.FromResult((IEnumerable<AvailableRateProvider>)AvailableRateProviders);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -962,9 +962,9 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Null(GetRatesResult?.Data);
|
Assert.Null(GetRatesResult?.Data);
|
||||||
|
|
||||||
var store = acc.GetController<StoresController>();
|
var store = acc.GetController<StoresController>();
|
||||||
var ratesVM = (RatesViewModel)(Assert.IsType<ViewResult>(await store.Rates()).Model);
|
var ratesVM = (RatesViewModel)(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||||
ratesVM.DefaultCurrencyPairs = "BTC_USD,LTC_USD";
|
ratesVM.DefaultCurrencyPairs = "BTC_USD,LTC_USD";
|
||||||
store.Rates(ratesVM).Wait();
|
await store.Rates(ratesVM);
|
||||||
store = acc.GetController<StoresController>();
|
store = acc.GetController<StoresController>();
|
||||||
rateController = acc.GetController<RateController>();
|
rateController = acc.GetController<RateController>();
|
||||||
GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, default)
|
GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, default)
|
||||||
@@ -1240,9 +1240,9 @@ namespace BTCPayServer.Tests
|
|||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
user.RegisterDerivationScheme("BTC");
|
user.RegisterDerivationScheme("BTC");
|
||||||
List<decimal> rates = new List<decimal>();
|
List<decimal> rates = new List<decimal>();
|
||||||
rates.Add(CreateInvoice(tester, user, "coingecko"));
|
rates.Add(await CreateInvoice(tester, user, "coingecko"));
|
||||||
var bitflyer = CreateInvoice(tester, user, "bitflyer", "JPY");
|
var bitflyer = await CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||||
var bitflyer2 = CreateInvoice(tester, user, "bitflyer", "JPY");
|
var bitflyer2 = await CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||||
Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache
|
Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache
|
||||||
rates.Add(bitflyer);
|
rates.Add(bitflyer);
|
||||||
|
|
||||||
@@ -1253,13 +1253,13 @@ namespace BTCPayServer.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static decimal CreateInvoice(ServerTester tester, TestAccount user, string exchange, string currency = "USD")
|
private static async Task<decimal> CreateInvoice(ServerTester tester, TestAccount user, string exchange, string currency = "USD")
|
||||||
{
|
{
|
||||||
var storeController = user.GetController<StoresController>();
|
var storeController = user.GetController<StoresController>();
|
||||||
var vm = (RatesViewModel)((ViewResult)storeController.Rates().GetAwaiter().GetResult()).Model;
|
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
||||||
vm.PreferredExchange = exchange;
|
vm.PreferredExchange = exchange;
|
||||||
storeController.Rates(vm).Wait();
|
await storeController.Rates(vm);
|
||||||
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
|
var invoice2 = await user.BitPay.CreateInvoiceAsync(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 5000.0m,
|
Price = 5000.0m,
|
||||||
Currency = currency,
|
Currency = currency,
|
||||||
@@ -1337,10 +1337,10 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Equal(Money.Coins(1.0m), invoice1.BtcPrice);
|
Assert.Equal(Money.Coins(1.0m), invoice1.BtcPrice);
|
||||||
|
|
||||||
var storeController = user.GetController<StoresController>();
|
var storeController = user.GetController<StoresController>();
|
||||||
var vm = (RatesViewModel)((ViewResult)storeController.Rates().GetAwaiter().GetResult()).Model;
|
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
||||||
Assert.Equal(0.0, vm.Spread);
|
Assert.Equal(0.0, vm.Spread);
|
||||||
vm.Spread = 40;
|
vm.Spread = 40;
|
||||||
storeController.Rates(vm).Wait();
|
await storeController.Rates(vm);
|
||||||
|
|
||||||
|
|
||||||
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
|
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
|
||||||
@@ -1438,37 +1438,37 @@ namespace BTCPayServer.Tests
|
|||||||
user.RegisterDerivationScheme("BTC");
|
user.RegisterDerivationScheme("BTC");
|
||||||
|
|
||||||
var store = user.GetController<StoresController>();
|
var store = user.GetController<StoresController>();
|
||||||
var rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>( await store.Rates()).Model);
|
var rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||||
Assert.False(rateVm.ShowScripting);
|
Assert.False(rateVm.ShowScripting);
|
||||||
Assert.Equal(CoinGeckoRateProvider.CoinGeckoName, rateVm.PreferredExchange);
|
Assert.Equal(CoinGeckoRateProvider.CoinGeckoName, rateVm.PreferredExchange);
|
||||||
Assert.Equal(0.0, rateVm.Spread);
|
Assert.Equal(0.0, rateVm.Spread);
|
||||||
Assert.Null(rateVm.TestRateRules);
|
Assert.Null(rateVm.TestRateRules);
|
||||||
|
|
||||||
rateVm.PreferredExchange = "bitflyer";
|
rateVm.PreferredExchange = "bitflyer";
|
||||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
Assert.IsType<RedirectToActionResult>(await store.Rates(rateVm, "Save"));
|
||||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>( await store.Rates()).Model);
|
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||||
Assert.Equal("bitflyer", rateVm.PreferredExchange);
|
Assert.Equal("bitflyer", rateVm.PreferredExchange);
|
||||||
|
|
||||||
rateVm.ScriptTest = "BTC_JPY,BTC_CAD";
|
rateVm.ScriptTest = "BTC_JPY,BTC_CAD";
|
||||||
rateVm.Spread = 10;
|
rateVm.Spread = 10;
|
||||||
store = user.GetController<StoresController>();
|
store = user.GetController<StoresController>();
|
||||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(await store.Rates(rateVm, "Test")).Model);
|
||||||
Assert.NotNull(rateVm.TestRateRules);
|
Assert.NotNull(rateVm.TestRateRules);
|
||||||
Assert.Equal(2, rateVm.TestRateRules.Count);
|
Assert.Equal(2, rateVm.TestRateRules.Count);
|
||||||
Assert.False(rateVm.TestRateRules[0].Error);
|
Assert.False(rateVm.TestRateRules[0].Error);
|
||||||
Assert.StartsWith("(bitflyer(BTC_JPY)) * (0.9, 1.1) =", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
|
Assert.StartsWith("(bitflyer(BTC_JPY)) * (0.9, 1.1) =", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
|
||||||
Assert.True(rateVm.TestRateRules[1].Error);
|
Assert.True(rateVm.TestRateRules[1].Error);
|
||||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
Assert.IsType<RedirectToActionResult>(await store.Rates(rateVm, "Save"));
|
||||||
|
|
||||||
Assert.IsType<RedirectToActionResult>(store.ShowRateRulesPost(true).Result);
|
Assert.IsType<RedirectToActionResult>(store.ShowRateRulesPost(true).Result);
|
||||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
Assert.IsType<RedirectToActionResult>(await store.Rates(rateVm, "Save"));
|
||||||
store = user.GetController<StoresController>();
|
store = user.GetController<StoresController>();
|
||||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>( await store.Rates()).Model);
|
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||||
Assert.Equal(rateVm.StoreId, user.StoreId);
|
Assert.Equal(rateVm.StoreId, user.StoreId);
|
||||||
Assert.Equal(rateVm.DefaultScript, rateVm.Script);
|
Assert.Equal(rateVm.DefaultScript, rateVm.Script);
|
||||||
Assert.True(rateVm.ShowScripting);
|
Assert.True(rateVm.ShowScripting);
|
||||||
rateVm.ScriptTest = "BTC_JPY";
|
rateVm.ScriptTest = "BTC_JPY";
|
||||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(await store.Rates(rateVm, "Test")).Model);
|
||||||
Assert.True(rateVm.ShowScripting);
|
Assert.True(rateVm.ShowScripting);
|
||||||
Assert.Contains("(bitflyer(BTC_JPY)) * (0.9, 1.1) = ", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
|
Assert.Contains("(bitflyer(BTC_JPY)) * (0.9, 1.1) = ", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -1477,11 +1477,11 @@ namespace BTCPayServer.Tests
|
|||||||
"X_CAD = quadrigacx(X_CAD);\n" +
|
"X_CAD = quadrigacx(X_CAD);\n" +
|
||||||
"X_X = coingecko(X_X);";
|
"X_X = coingecko(X_X);";
|
||||||
rateVm.Spread = 50;
|
rateVm.Spread = 50;
|
||||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(await store.Rates(rateVm, "Test")).Model);
|
||||||
Assert.True(rateVm.TestRateRules.All(t => !t.Error));
|
Assert.True(rateVm.TestRateRules.All(t => !t.Error));
|
||||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
Assert.IsType<RedirectToActionResult>(await store.Rates(rateVm, "Save"));
|
||||||
store = user.GetController<StoresController>();
|
store = user.GetController<StoresController>();
|
||||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>( await store.Rates()).Model);
|
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||||
Assert.Equal(50, rateVm.Spread);
|
Assert.Equal(50, rateVm.Spread);
|
||||||
Assert.True(rateVm.ShowScripting);
|
Assert.True(rateVm.ShowScripting);
|
||||||
Assert.Contains("DOGE_X", rateVm.Script, StringComparison.OrdinalIgnoreCase);
|
Assert.Contains("DOGE_X", rateVm.Script, StringComparison.OrdinalIgnoreCase);
|
||||||
@@ -1569,7 +1569,7 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.NotNull(ltcCryptoInfo);
|
Assert.NotNull(ltcCryptoInfo);
|
||||||
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
|
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
|
||||||
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
|
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
|
||||||
cashCow.Generate(2); // LTC is not worth a lot, so just to make sure we have money...
|
cashCow.Generate(4); // LTC is not worth a lot, so just to make sure we have money...
|
||||||
cashCow.SendToAddress(invoiceAddress, secondPayment);
|
cashCow.SendToAddress(invoiceAddress, secondPayment);
|
||||||
Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress);
|
Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress);
|
||||||
TestUtils.Eventually(() =>
|
TestUtils.Eventually(() =>
|
||||||
@@ -2680,7 +2680,7 @@ noninventoryitem:
|
|||||||
public void CanQueryDirectProviders()
|
public void CanQueryDirectProviders()
|
||||||
{
|
{
|
||||||
var factory = CreateBTCPayRateFactory();
|
var factory = CreateBTCPayRateFactory();
|
||||||
|
var all = string.Join("\r\n", factory.GetSupportedExchanges().Select(e => e.Id).ToArray());
|
||||||
foreach (var result in factory
|
foreach (var result in factory
|
||||||
.Providers
|
.Providers
|
||||||
.Where(p => p.Value is BackgroundFetcherRateProvider)
|
.Where(p => p.Value is BackgroundFetcherRateProvider)
|
||||||
@@ -2785,9 +2785,7 @@ noninventoryitem:
|
|||||||
|
|
||||||
public static RateProviderFactory CreateBTCPayRateFactory()
|
public static RateProviderFactory CreateBTCPayRateFactory()
|
||||||
{
|
{
|
||||||
var result = new RateProviderFactory(CreateMemoryCache(), new MockHttpClientFactory(), new CoinAverageSettings());
|
return new RateProviderFactory(CreateMemoryCache(), new MockHttpClientFactory(), new CoinAverageSettings());
|
||||||
result.InitExchanges().GetAwaiter().GetResult();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MemoryCacheOptions CreateMemoryCache()
|
private static MemoryCacheOptions CreateMemoryCache()
|
||||||
|
|||||||
@@ -86,14 +86,14 @@ namespace BTCPayServer.Configuration
|
|||||||
|
|
||||||
var supportedChains = conf.GetOrDefault<string>("chains", "btc")
|
var supportedChains = conf.GetOrDefault<string>("chains", "btc")
|
||||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(t => t.ToUpperInvariant()).ToList();
|
.Select(t => t.ToUpperInvariant()).ToHashSet();
|
||||||
|
|
||||||
var networkProvider = new BTCPayNetworkProvider(NetworkType);
|
var networkProvider = new BTCPayNetworkProvider(NetworkType);
|
||||||
var filtered = networkProvider.Filter(supportedChains.ToArray());
|
var filtered = networkProvider.Filter(supportedChains.ToArray());
|
||||||
var elementsBased = filtered.GetAll().OfType<ElementsBTCPayNetwork>();
|
var elementsBased = filtered.GetAll().OfType<ElementsBTCPayNetwork>();
|
||||||
var parentChains = elementsBased.Select(network => network.NetworkCryptoCode.ToUpperInvariant()).Distinct();
|
var parentChains = elementsBased.Select(network => network.NetworkCryptoCode.ToUpperInvariant()).Distinct();
|
||||||
var allSubChains = networkProvider.GetAll().OfType<ElementsBTCPayNetwork>()
|
var allSubChains = networkProvider.GetAll().OfType<ElementsBTCPayNetwork>()
|
||||||
.Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode);
|
.Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode.ToUpperInvariant());
|
||||||
supportedChains.AddRange(allSubChains);
|
supportedChains.AddRange(allSubChains);
|
||||||
NetworkProvider = networkProvider.Filter(supportedChains.ToArray());
|
NetworkProvider = networkProvider.Filter(supportedChains.ToArray());
|
||||||
foreach (var chain in supportedChains)
|
foreach (var chain in supportedChains)
|
||||||
|
|||||||
@@ -193,9 +193,9 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{storeId}/rates")]
|
[Route("{storeId}/rates")]
|
||||||
public async Task<IActionResult> Rates()
|
public IActionResult Rates()
|
||||||
{
|
{
|
||||||
var exchanges = await GetSupportedExchanges();
|
var exchanges = GetSupportedExchanges();
|
||||||
var storeBlob = CurrentStore.GetStoreBlob();
|
var storeBlob = CurrentStore.GetStoreBlob();
|
||||||
var vm = new RatesViewModel();
|
var vm = new RatesViewModel();
|
||||||
vm.SetExchangeRates(exchanges, storeBlob.PreferredExchange ?? CoinGeckoRateProvider.CoinGeckoName);
|
vm.SetExchangeRates(exchanges, storeBlob.PreferredExchange ?? CoinGeckoRateProvider.CoinGeckoName);
|
||||||
@@ -221,7 +221,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return RedirectToAction(nameof(ShowRateRules), new {scripting = false, storeId = model.StoreId});
|
return RedirectToAction(nameof(ShowRateRules), new {scripting = false, storeId = model.StoreId});
|
||||||
}
|
}
|
||||||
|
|
||||||
var exchanges = await GetSupportedExchanges();
|
var exchanges = GetSupportedExchanges();
|
||||||
model.SetExchangeRates(exchanges, model.PreferredExchange);
|
model.SetExchangeRates(exchanges, model.PreferredExchange);
|
||||||
model.StoreId = storeId ?? model.StoreId;
|
model.StoreId = storeId ?? model.StoreId;
|
||||||
CurrencyPair[] currencyPairs = null;
|
CurrencyPair[] currencyPairs = null;
|
||||||
@@ -338,7 +338,7 @@ namespace BTCPayServer.Controllers
|
|||||||
Description = scripting ?
|
Description = scripting ?
|
||||||
"This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)"
|
"This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)"
|
||||||
: "This action will delete your rate script. Are you sure to turn off rate rules scripting?",
|
: "This action will delete your rate script. Are you sure to turn off rate rules scripting?",
|
||||||
ButtonClass = "btn-primary"
|
ButtonClass = scripting ? "btn-primary" : "btn-danger"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,9 +603,9 @@ namespace BTCPayServer.Controllers
|
|||||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IEnumerable<AvailableRateProvider>> GetSupportedExchanges()
|
private IEnumerable<AvailableRateProvider> GetSupportedExchanges()
|
||||||
{
|
{
|
||||||
var exchanges = await _RateFactory.RateProviderFactory.GetSupportedExchanges();
|
var exchanges = _RateFactory.RateProviderFactory.GetSupportedExchanges();
|
||||||
return exchanges
|
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);
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ namespace BTCPayServer.HostedServices
|
|||||||
{
|
{
|
||||||
return new Task[]
|
return new Task[]
|
||||||
{
|
{
|
||||||
CreateLoopTask(RefreshCoinAverageSupportedExchanges),
|
|
||||||
CreateLoopTask(RefreshCoinAverageSettings),
|
CreateLoopTask(RefreshCoinAverageSettings),
|
||||||
CreateLoopTask(RefreshRates)
|
CreateLoopTask(RefreshRates)
|
||||||
};
|
};
|
||||||
@@ -144,12 +143,6 @@ namespace BTCPayServer.HostedServices
|
|||||||
await _SettingsRepository.UpdateSetting(cache);
|
await _SettingsRepository.UpdateSetting(cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task RefreshCoinAverageSupportedExchanges()
|
|
||||||
{
|
|
||||||
await _RateProviderFactory.InitExchanges();
|
|
||||||
await Task.Delay(TimeSpan.FromHours(5), Cancellation);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task RefreshCoinAverageSettings()
|
async Task RefreshCoinAverageSettings()
|
||||||
{
|
{
|
||||||
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
|
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
@@ -17,13 +18,29 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
}
|
}
|
||||||
public void SetExchangeRates(IEnumerable<AvailableRateProvider> supportedList, string preferredExchange)
|
public void SetExchangeRates(IEnumerable<AvailableRateProvider> supportedList, string preferredExchange)
|
||||||
{
|
{
|
||||||
var defaultStore = preferredExchange ?? CoinAverageRateProvider.CoinAverageName;
|
var defaultStore = preferredExchange ?? CoinGeckoRateProvider.CoinGeckoName;
|
||||||
|
supportedList = supportedList.Select(a => new AvailableRateProvider(a.Id, GetName(a), a.Url, a.Source)).ToArray();
|
||||||
var chosen = supportedList.FirstOrDefault(f => f.Id == defaultStore) ?? supportedList.FirstOrDefault();
|
var chosen = supportedList.FirstOrDefault(f => f.Id == defaultStore) ?? 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;
|
||||||
RateSource = chosen.Url;
|
RateSource = chosen.Url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetName(AvailableRateProvider a)
|
||||||
|
{
|
||||||
|
switch (a.Source)
|
||||||
|
{
|
||||||
|
case Rating.RateSource.Direct:
|
||||||
|
return a.Name;
|
||||||
|
case Rating.RateSource.Coingecko:
|
||||||
|
return $"{a.Name} (via CoinGecko, free)";
|
||||||
|
case Rating.RateSource.CoinAverage:
|
||||||
|
return $"{a.Name} (via BitcoinAverage, commercial)";
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException(a.Source.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<TestResultViewModel> TestRateRules { get; set; }
|
public List<TestResultViewModel> TestRateRules { get; set; }
|
||||||
|
|
||||||
public SelectList Exchanges { get; set; }
|
public SelectList Exchanges { get; set; }
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
"BTCPAY_ALLOW-ADMIN-REGISTRATION": "true",
|
"BTCPAY_ALLOW-ADMIN-REGISTRATION": "true",
|
||||||
"BTCPAY_DISABLE-REGISTRATION": "false",
|
"BTCPAY_DISABLE-REGISTRATION": "false",
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
"BTCPAY_CHAINS": "btc,lbtc",
|
"BTCPAY_CHAINS": "btc,ltc,lbtc",
|
||||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver",
|
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver",
|
||||||
"BTCPAY_EXTERNALSERVICES": "totoservice:totolink;",
|
"BTCPAY_EXTERNALSERVICES": "totoservice:totolink;",
|
||||||
"BTCPAY_SSHCONNECTION": "root@127.0.0.1:21622",
|
"BTCPAY_SSHCONNECTION": "root@127.0.0.1:21622",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12 text-center">
|
<div class="col-lg-12 text-center">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<button id="continue" type="submit" class="btn btn-secondary @Model.ButtonClass w-25">@Model.Action</button>
|
<button id="continue" type="submit" class="btn @Model.ButtonClass w-25">@Model.Action</button>
|
||||||
<button type="submit" class="btn btn-secondary w-25" onclick="history.back(); return false;">Go back</button>
|
<button type="submit" class="btn btn-secondary w-25" onclick="history.back(); return false;">Go back</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,15 +18,61 @@
|
|||||||
{
|
{
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<h5>Scripting</h5>
|
<h5>Scripting</h5>
|
||||||
<span>Rate script allows you to express precisely how you want to calculate rates for currency pairs.</span>
|
<p>Rate script allows you to express precisely how you want to calculate rates for currency pairs.</p>
|
||||||
<p class="text-muted overflow-auto" style="max-height: 300px">
|
<p>We are retrieving the rate of each exchange either directly, via <a href="https://www.coingecko.com/" target="_blank">CoinGecko (free)</a> or <a href="https://bitcoinaverage.com/" target="_blank">BitcoinAverage (commercial)</a></p>
|
||||||
<b>Supported exchanges are</b>:
|
<div class="accordion" id="accordion-info">
|
||||||
@for (int i = 0; i < Model.AvailableExchanges.Count(); i++)
|
<div class="card">
|
||||||
{
|
<div class="card-header" id="direct-header">
|
||||||
<a href="@Model.AvailableExchanges.ElementAt(i).Url">@Model.AvailableExchanges.ElementAt(i).Name</a><span>@(i == Model.AvailableExchanges.Count() - 1 ? "" : ",")</span>
|
<h2 class="mb-0">
|
||||||
}
|
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#direct-content" aria-expanded="true">
|
||||||
</p>
|
Direct integration
|
||||||
<p><a href="#help" data-toggle="collapse"><b>Click here for more information</b></a></p>
|
</button>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="collapse" id="direct-content">
|
||||||
|
<div class="card-body text-muted overflow-auto">
|
||||||
|
@foreach (var exchange in Model.AvailableExchanges.Where(a => a.Source == BTCPayServer.Rating.RateSource.Direct))
|
||||||
|
{
|
||||||
|
<a href="@exchange.Url">@exchange.Id</a><span> </span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header" id="coingecko-header">
|
||||||
|
<h2 class="mb-0">
|
||||||
|
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#coingecko-content" aria-expanded="true">
|
||||||
|
Coingecko integration
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div id="coingecko-content" class="collapse">
|
||||||
|
<div class="card-body text-muted overflow-auto">
|
||||||
|
@foreach (var exchange in Model.AvailableExchanges.Where(a => a.Source == BTCPayServer.Rating.RateSource.Coingecko))
|
||||||
|
{
|
||||||
|
<a href="@exchange.Url">@exchange.Id</a><span> </span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header" id="coinaverage-header">
|
||||||
|
<h2 class="mb-0">
|
||||||
|
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#coinaverage-content" aria-expanded="true">
|
||||||
|
CoinAverage integration (commercial API)
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div id="coinaverage-content" class="collapse">
|
||||||
|
<div class="card-body text-muted overflow-auto">
|
||||||
|
@foreach (var exchange in Model.AvailableExchanges.Where(a => a.Source == BTCPayServer.Rating.RateSource.CoinAverage))
|
||||||
|
{
|
||||||
|
<a href="@exchange.Url">@exchange.Id</a><span> </span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (Model.TestRateRules != null)
|
@if (Model.TestRateRules != null)
|
||||||
@@ -110,7 +156,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="Script"></label>
|
<label asp-for="Script"></label> <a href="#help" data-toggle="collapse"><span class="fa fa-question-circle-o" title="More information..."></span></a>
|
||||||
<textarea asp-for="Script" rows="20" cols="80" class="form-control"></textarea>
|
<textarea asp-for="Script" rows="20" cols="80" class="form-control"></textarea>
|
||||||
<span asp-validation-for="Script" class="text-danger"></span>
|
<span asp-validation-for="Script" class="text-danger"></span>
|
||||||
<a href="#" onclick="$('#Script').val(defaultScript); return false;">Set to default settings</a>
|
<a href="#" onclick="$('#Script').val(defaultScript); return false;">Set to default settings</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user