diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Litecoin.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Litecoin.cs
index b492947b3..3b7458f67 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Litecoin.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Litecoin.cs
@@ -21,6 +21,11 @@ namespace BTCPayServer
: "http://explorer.litecointools.com/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "litecoin",
+ DefaultRateRules = new[]
+ {
+ "LTC_X = LTC_BTC * BTC_X",
+ "LTC_BTC = coingecko(LTC_BTC)"
+ },
CryptoImagePath = "imlegacy/litecoin.svg",
LightningImagePath = "imlegacy/litecoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
diff --git a/BTCPayServer.Common/Altcoins/Monero/BTCPayNetworkProvider.Monero.cs b/BTCPayServer.Common/Altcoins/Monero/BTCPayNetworkProvider.Monero.cs
index 208aea77c..f81731ff0 100644
--- a/BTCPayServer.Common/Altcoins/Monero/BTCPayNetworkProvider.Monero.cs
+++ b/BTCPayServer.Common/Altcoins/Monero/BTCPayNetworkProvider.Monero.cs
@@ -14,6 +14,11 @@ namespace BTCPayServer
NetworkType == NetworkType.Mainnet
? "https://www.exploremonero.com/transaction/{0}"
: "https://testnet.xmrchain.net/tx/{0}",
+ DefaultRateRules = new[]
+ {
+ "XMR_X = XMR_BTC * BTC_X",
+ "XMR_BTC = kraken(XMR_BTC)"
+ },
CryptoImagePath = "/imlegacy/monero.svg"
});
}
diff --git a/BTCPayServer.Rating/AvailableRateProvider.cs b/BTCPayServer.Rating/AvailableRateProvider.cs
new file mode 100644
index 000000000..74c636cb4
--- /dev/null
+++ b/BTCPayServer.Rating/AvailableRateProvider.cs
@@ -0,0 +1,24 @@
+namespace BTCPayServer.Rating
+{
+ public enum RateSource
+ {
+ Coingecko,
+ CoinAverage,
+ Direct
+ }
+ public class AvailableRateProvider
+ {
+ public string Name { get; }
+ public string Url { get; }
+ public string Id { get; }
+ public RateSource Source { get; }
+
+ public AvailableRateProvider(string id, string name, string url, RateSource source)
+ {
+ Id = id;
+ Name = name;
+ Url = url;
+ Source = source;
+ }
+ }
+}
diff --git a/BTCPayServer.Rating/CurrencyPair.cs b/BTCPayServer.Rating/CurrencyPair.cs
index af4d3ab96..5307331e1 100644
--- a/BTCPayServer.Rating/CurrencyPair.cs
+++ b/BTCPayServer.Rating/CurrencyPair.cs
@@ -1,7 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
namespace BTCPayServer.Rating
{
diff --git a/BTCPayServer.Rating/ExchangeRates.cs b/BTCPayServer.Rating/ExchangeRates.cs
index 6b3642d59..c9142e2b2 100644
--- a/BTCPayServer.Rating/ExchangeRates.cs
+++ b/BTCPayServer.Rating/ExchangeRates.cs
@@ -3,7 +3,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Threading.Tasks;
namespace BTCPayServer.Rating
{
diff --git a/BTCPayServer.Rating/Providers/CoinAverageRateProvider.cs b/BTCPayServer.Rating/Providers/CoinAverageRateProvider.cs
index 203e7105d..dbd504ad3 100644
--- a/BTCPayServer.Rating/Providers/CoinAverageRateProvider.cs
+++ b/BTCPayServer.Rating/Providers/CoinAverageRateProvider.cs
@@ -1,13 +1,9 @@
using Newtonsoft.Json;
-using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
-using System.Security.Cryptography;
-using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using BTCPayServer.Rating;
@@ -23,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 string PublicKey { get; set; }
@@ -200,32 +183,6 @@ namespace BTCPayServer.Services.Rates
response.RequestsPerPeriod = jobj["requests_per_period"].Value();
return response;
}
-
- public async Task 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();
- var exchanges = (JObject)jobj["exchanges"];
- response.Exchanges = exchanges
- .Properties()
- .Select(p =>
- {
- var exchange = JsonConvert.DeserializeObject(p.Value.ToString());
- exchange.Name = p.Name;
- return exchange;
- })
- .ToArray();
- return response;
- }
}
public class GetRateLimitsResponse
diff --git a/BTCPayServer.Rating/Providers/CoinAverageSettings.cs b/BTCPayServer.Rating/Providers/CoinAverageSettings.cs
index 92769535a..29d19004f 100644
--- a/BTCPayServer.Rating/Providers/CoinAverageSettings.cs
+++ b/BTCPayServer.Rating/Providers/CoinAverageSettings.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
@@ -20,125 +18,12 @@ namespace BTCPayServer.Services.Rates
return _Settings.AddHeader(message);
}
}
-
- public class CoinAverageExchange
- {
- public CoinAverageExchange(string name, string display, string url)
- {
- Name = name;
- Display = display;
- Url = url;
- }
- public string Name { get; set; }
- public string Display { get; set; }
- public string Url
- {
- get;
- set;
- }
- }
- public class CoinAverageExchanges : Dictionary
- {
- public CoinAverageExchanges()
- {
- }
-
- public void Add(CoinAverageExchange exchange)
- {
- if (!TryAdd(exchange.Name, exchange))
- {
- this.Remove(exchange.Name);
- this.Add(exchange.Name, exchange);
- }
- }
- }
public class CoinAverageSettings : ICoinAverageAuthenticator
{
private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public (String PublicKey, String PrivateKey)? KeyPair { get; set; }
- public CoinAverageExchanges AvailableExchanges { get; set; } = new CoinAverageExchanges();
-
- public CoinAverageSettings()
- {
- //GENERATED BY:
- //StringBuilder b = new StringBuilder();
- //b.AppendLine("_coinAverageSettings.AvailableExchanges = new[] {");
- //foreach (var availableExchange in _coinAverageSettings.AvailableExchanges)
- //{
- // b.AppendLine($"(DisplayName: \"{availableExchange.DisplayName}\", Name: \"{availableExchange.Name}\"),");
- //}
- //b.AppendLine("}.ToArray()");
- AvailableExchanges = new CoinAverageExchanges();
- 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"),
- })
- {
- AvailableExchanges.TryAdd(item.Name, new CoinAverageExchange(item.Name, item.DisplayName, $"https://apiv2.bitcoinaverage.com/exchanges/{item.Name}"));
- }
- // Keep back-compat
- AvailableExchanges.Add(new CoinAverageExchange("gdax", string.Empty, $"https://apiv2.bitcoinaverage.com/exchanges/coinbasepro"));
- }
-
+
public Task AddHeader(HttpRequestMessage message)
{
var signature = GetCoinAverageSignature();
diff --git a/BTCPayServer.Rating/Providers/CoinGeckoRateProvider.cs b/BTCPayServer.Rating/Providers/CoinGeckoRateProvider.cs
new file mode 100644
index 000000000..24d4f4b1e
--- /dev/null
+++ b/BTCPayServer.Rating/Providers/CoinGeckoRateProvider.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using BTCPayServer.Rating;
+using Newtonsoft.Json.Linq;
+
+namespace BTCPayServer.Services.Rates
+{
+ public class CoinGeckoRateProvider : IRateProvider, IHasExchangeName
+ {
+ // https://api.coingecko.com/api/v3/exchanges/list
+ internal static readonly string SupportedExchanges = "[{\"id\":\"abcc\",\"name\":\"ABCC\"},{\"id\":\"acx\",\"name\":\"ACX\"},{\"id\":\"aex\",\"name\":\"AEX\"},{\"id\":\"airswap\",\"name\":\"AirSwap\"},{\"id\":\"allbit\",\"name\":\"Allbit\"},{\"id\":\"allcoin\",\"name\":\"Allcoin\"},{\"id\":\"alterdice\",\"name\":\"AlterDice\"},{\"id\":\"altilly\",\"name\":\"Altilly\"},{\"id\":\"altmarkets\",\"name\":\"Altmarkets\"},{\"id\":\"anx\",\"name\":\"ANX\"},{\"id\":\"aphelion\",\"name\":\"Aphelion\"},{\"id\":\"atomars\",\"name\":\"Atomars\"},{\"id\":\"axnet\",\"name\":\"AXNET\"},{\"id\":\"b2bx\",\"name\":\"B2BX\"},{\"id\":\"bakkt\",\"name\":\"Bakkt\"},{\"id\":\"bamboo_relay\",\"name\":\"Bamboo Relay\"},{\"id\":\"bancor\",\"name\":\"Bancor Network\"},{\"id\":\"bankera\",\"name\":\"Bankera\"},{\"id\":\"basefex\",\"name\":\"BaseFEX\"},{\"id\":\"bcex\",\"name\":\"BCEX\"},{\"id\":\"beaxy\",\"name\":\"Beaxy\"},{\"id\":\"bgogo\",\"name\":\"Bgogo\"},{\"id\":\"bhex\",\"name\":\"BHEX\"},{\"id\":\"bibox\",\"name\":\"Bibox\"},{\"id\":\"bibox_futures\",\"name\":\"Bibox (Futures)\"},{\"id\":\"bigmarkets\",\"name\":\"BIG markets\"},{\"id\":\"bigone\",\"name\":\"BigONE\"},{\"id\":\"bihodl\",\"name\":\"BiHODL \"},{\"id\":\"biki\",\"name\":\"Biki\"},{\"id\":\"bilaxy\",\"name\":\"Bilaxy\"},{\"id\":\"binance\",\"name\":\"Binance\"},{\"id\":\"binance_dex\",\"name\":\"Binance DEX\"},{\"id\":\"binance_futures\",\"name\":\"Binance (Futures)\"},{\"id\":\"binance_jersey\",\"name\":\"Binance Jersey\"},{\"id\":\"binance_us\",\"name\":\"Binance US\"},{\"id\":\"bione\",\"name\":\"Bione\"},{\"id\":\"birake\",\"name\":\"Birake\"},{\"id\":\"bisq\",\"name\":\"Bisq\"},{\"id\":\"bit2c\",\"name\":\"Bit2c\"},{\"id\":\"bitalong\",\"name\":\"Bitalong\"},{\"id\":\"bitasset\",\"name\":\"BitAsset\"},{\"id\":\"bitbank\",\"name\":\"Bitbank\"},{\"id\":\"bitbay\",\"name\":\"BitBay\"},{\"id\":\"bitbegin\",\"name\":\"Bitbegin\"},{\"id\":\"bitbox\",\"name\":\"BITBOX\"},{\"id\":\"bitc3\",\"name\":\"Bitc3\"},{\"id\":\"bitci\",\"name\":\"Bitci\"},{\"id\":\"bitcoin_com\",\"name\":\"Bitcoin.com\"},{\"id\":\"bitcratic\",\"name\":\"Bitcratic\"},{\"id\":\"bitex\",\"name\":\"Bitex.la\"},{\"id\":\"bitexbook\",\"name\":\"BITEXBOOK\"},{\"id\":\"bitexlive\",\"name\":\"Bitexlive\"},{\"id\":\"bitfex\",\"name\":\"Bitfex\"},{\"id\":\"bitfinex\",\"name\":\"Bitfinex\"},{\"id\":\"bitfinex_futures\",\"name\":\"Bitfinex (Futures)\"},{\"id\":\"bitflyer\",\"name\":\"bitFlyer\"},{\"id\":\"bitflyer_futures\",\"name\":\"Bitflyer (Futures)\"},{\"id\":\"bitforex\",\"name\":\"Bitforex\"},{\"id\":\"bitforex_futures\",\"name\":\"Bitforex (Futures)\"},{\"id\":\"bithash\",\"name\":\"BitHash\"},{\"id\":\"bitholic\",\"name\":\"Bithumb Singapore\"},{\"id\":\"bithumb\",\"name\":\"Bithumb\"},{\"id\":\"bithumb_global\",\"name\":\"Bithumb Global\"},{\"id\":\"bitinfi\",\"name\":\"Bitinfi\"},{\"id\":\"bitker\",\"name\":\"BITKER\"},{\"id\":\"bitkonan\",\"name\":\"BitKonan\"},{\"id\":\"bitkub\",\"name\":\"Bitkub\"},{\"id\":\"bitlish\",\"name\":\"Bitlish\"},{\"id\":\"bitmart\",\"name\":\"BitMart\"},{\"id\":\"bitmax\",\"name\":\"BitMax\"},{\"id\":\"bitmesh\",\"name\":\"Bitmesh\"},{\"id\":\"bitmex\",\"name\":\"Bitmex\"},{\"id\":\"bitoffer\",\"name\":\"Bitoffer\"},{\"id\":\"bitonbay\",\"name\":\"BitOnBay\"},{\"id\":\"bitopro\",\"name\":\"BitoPro\"},{\"id\":\"bitpanda\",\"name\":\"Bitpanda Global Exchange\"},{\"id\":\"bitrabbit\",\"name\":\"BitRabbit\"},{\"id\":\"bitrue\",\"name\":\"Bitrue\"},{\"id\":\"bits_blockchain\",\"name\":\"Bits Blockchain\"},{\"id\":\"bitsdaq\",\"name\":\"Bitsdaq\"},{\"id\":\"bitshares_assets\",\"name\":\"Bitshares Assets\"},{\"id\":\"bitso\",\"name\":\"Bitso\"},{\"id\":\"bitsonic\",\"name\":\"Bitsonic\"},{\"id\":\"bitstamp\",\"name\":\"Bitstamp\"},{\"id\":\"bitsten\",\"name\":\"Bitsten\"},{\"id\":\"bitstorage\",\"name\":\"BitStorage\"},{\"id\":\"bittrex\",\"name\":\"Bittrex\"},{\"id\":\"bit_z\",\"name\":\"Bit-Z\"},{\"id\":\"bitz_futures\",\"name\":\"Bitz (Futures)\"},{\"id\":\"bkex\",\"name\":\"BKEX\"},{\"id\":\"bleutrade\",\"name\":\"bleutrade\"},{\"id\":\"blockonix\",\"name\":\"Blockonix\"},{\"id\":\"boa\",\"name\":\"BOA Exchange\"},{\"id\":\"braziliex\",\"name\":\"Braziliex\"},{\"id\":\"btc_alpha\",\"name\":\"BTC-Alpha\"},{\"id\":\"btcbox\",\"name\":\"BTCBOX\"},{\"id\":\"btcc\",\"name\":\"BTCC\"},{\"id\":\"btcexa\",\"name\":\"BTCEXA\"},{\"id\":\"btc_exchange\",\"name\":\"Btc Exchange\"},{\"id\":\"btcmarkets\",\"name\":\"BTCMarkets\"},{\"id\":\"btcnext\",\"name\":\"BTCNEXT\"},{\"id\":\"btcsquare\",\"name\":\"BTCSquare\"},{\"id\":\"btc_trade_ua\",\"name\":\"BTC Trade UA\"},{\"id\":\"btcturk\",\"name\":\"BTCTurk\"},{\"id\":\"btse\",\"name\":\"BTSE\"},{\"id\":\"btse_futures\",\"name\":\"BTSE (Futures)\"},{\"id\":\"buyucoin\",\"name\":\"BuyUcoin\"},{\"id\":\"bvnex\",\"name\":\"Bvnex\"},{\"id\":\"bw\",\"name\":\"BW.com\"},{\"id\":\"bx_thailand\",\"name\":\"BX Thailand\"},{\"id\":\"bybit\",\"name\":\"Bybit\"},{\"id\":\"c2cx\",\"name\":\"C2CX\"},{\"id\":\"cashierest\",\"name\":\"Cashierest\"},{\"id\":\"cashpayz\",\"name\":\"Cashpayz\"},{\"id\":\"catex\",\"name\":\"Catex\"},{\"id\":\"cbx\",\"name\":\"CBX\"},{\"id\":\"ccex\",\"name\":\"C-CEX\"},{\"id\":\"ccx\",\"name\":\"CCXCanada\"},{\"id\":\"cex\",\"name\":\"CEX.IO\"},{\"id\":\"cezex\",\"name\":\"Cezex\"},{\"id\":\"chainex\",\"name\":\"ChainEX\"},{\"id\":\"chainrift\",\"name\":\"Chainrift\"},{\"id\":\"chaoex\",\"name\":\"CHAOEX\"},{\"id\":\"citex\",\"name\":\"CITEX\"},{\"id\":\"cme_futures\",\"name\":\"CME Bitcoin Futures\"},{\"id\":\"codex\",\"name\":\"CODEX\"},{\"id\":\"coinall\",\"name\":\"CoinAll\"},{\"id\":\"coinasset\",\"name\":\"CoinAsset\"},{\"id\":\"coinbe\",\"name\":\"Coinbe\"},{\"id\":\"coinbene\",\"name\":\"CoinBene\"},{\"id\":\"coinbig\",\"name\":\"COINBIG\"},{\"id\":\"coinbit\",\"name\":\"Coinbit\"},{\"id\":\"coinchangex\",\"name\":\"Coinchangex\"},{\"id\":\"coincheck\",\"name\":\"Coincheck\"},{\"id\":\"coindeal\",\"name\":\"Coindeal\"},{\"id\":\"coindirect\",\"name\":\"CoinDirect\"},{\"id\":\"coineal\",\"name\":\"Coineal\"},{\"id\":\"coin_egg\",\"name\":\"CoinEgg\"},{\"id\":\"coinex\",\"name\":\"CoinEx\"},{\"id\":\"coinfalcon\",\"name\":\"Coinfalcon\"},{\"id\":\"coinfield\",\"name\":\"Coinfield\"},{\"id\":\"coinfinit\",\"name\":\"Coinfinit\"},{\"id\":\"coinflex\",\"name\":\"CoinFLEX\"},{\"id\":\"coinflex_futures\",\"name\":\"CoinFLEX (Futures)\"},{\"id\":\"coinfloor\",\"name\":\"Coinfloor\"},{\"id\":\"coingi\",\"name\":\"Coingi\"},{\"id\":\"coinhe\",\"name\":\"CoinHe\"},{\"id\":\"coinhub\",\"name\":\"Coinhub\"},{\"id\":\"coinjar\",\"name\":\"CoinJar Exchange\"},{\"id\":\"coinlim\",\"name\":\"Coinlim\"},{\"id\":\"coin_metro\",\"name\":\"Coinmetro\"},{\"id\":\"coinmex\",\"name\":\"CoinMex\"},{\"id\":\"coinnest\",\"name\":\"CoinNest\"},{\"id\":\"coinone\",\"name\":\"Coinone\"},{\"id\":\"coinpark\",\"name\":\"Coinpark\"},{\"id\":\"coinplace\",\"name\":\"Coinplace\"},{\"id\":\"coinsbank\",\"name\":\"Coinsbank\"},{\"id\":\"coinsbit\",\"name\":\"Coinsbit\"},{\"id\":\"coinsuper\",\"name\":\"Coinsuper\"},{\"id\":\"cointiger\",\"name\":\"CoinTiger\"},{\"id\":\"coinxpro\",\"name\":\"COINX.PRO\"},{\"id\":\"coinzest\",\"name\":\"Coinzest\"},{\"id\":\"coinzo\",\"name\":\"Coinzo\"},{\"id\":\"c_patex\",\"name\":\"C-Patex\"},{\"id\":\"cpdax\",\"name\":\"CPDAX\"},{\"id\":\"credoex\",\"name\":\"CredoEx\"},{\"id\":\"crex24\",\"name\":\"CREX24\"},{\"id\":\"crxzone\",\"name\":\"CRXzone\"},{\"id\":\"cryptaldash\",\"name\":\"CryptalDash\"},{\"id\":\"cryptex\",\"name\":\"Cryptex\"},{\"id\":\"crypto_bridge\",\"name\":\"CryptoBridge\"},{\"id\":\"cryptology\",\"name\":\"Cryptology\"},{\"id\":\"cryptonit\",\"name\":\"Cryptonit\"},{\"id\":\"crytrex\",\"name\":\"CryTrEx\"},{\"id\":\"cybex\",\"name\":\"Cybex DEX\"},{\"id\":\"dach_exchange\",\"name\":\"Dach Exchange\"},{\"id\":\"dakuce\",\"name\":\"Dakuce\"},{\"id\":\"darb_finance\",\"name\":\"Darb Finance\"},{\"id\":\"daybit\",\"name\":\"Daybit\"},{\"id\":\"dcoin\",\"name\":\"Dcoin\"},{\"id\":\"ddex\",\"name\":\"DDEX\"},{\"id\":\"decoin\",\"name\":\"Decoin\"},{\"id\":\"delta_futures\",\"name\":\"Delta Exchange\"},{\"id\":\"deribit\",\"name\":\"Deribit\"},{\"id\":\"dextop\",\"name\":\"DEx.top\"},{\"id\":\"dextrade\",\"name\":\"Dex-Trade\"},{\"id\":\"dflow\",\"name\":\"Dflow\"},{\"id\":\"digifinex\",\"name\":\"Digifinex\"},{\"id\":\"digitalprice\",\"name\":\"Altsbit\"},{\"id\":\"dobitrade\",\"name\":\"Dobitrade\"},{\"id\":\"dove_wallet\",\"name\":\"Dove Wallet\"},{\"id\":\"dragonex\",\"name\":\"DragonEx\"},{\"id\":\"dsx\",\"name\":\"DSX\"},{\"id\":\"dydx\",\"name\":\"dYdX\"},{\"id\":\"ecxx\",\"name\":\"Ecxx\"},{\"id\":\"elitex\",\"name\":\"Elitex\"},{\"id\":\"eosex\",\"name\":\"EOSex\"},{\"id\":\"escodex\",\"name\":\"Escodex\"},{\"id\":\"eterbase\",\"name\":\"Eterbase\"},{\"id\":\"etherflyer\",\"name\":\"EtherFlyer\"},{\"id\":\"ethex\",\"name\":\"Ethex\"},{\"id\":\"everbloom\",\"name\":\"Everbloom\"},{\"id\":\"exmarkets\",\"name\":\"ExMarkets\"},{\"id\":\"exmo\",\"name\":\"EXMO\"},{\"id\":\"exnce\",\"name\":\"EXNCE\"},{\"id\":\"exrates\",\"name\":\"Exrates\"},{\"id\":\"extstock\",\"name\":\"ExtStock\"},{\"id\":\"exx\",\"name\":\"EXX\"},{\"id\":\"f1cx\",\"name\":\"F1CX\"},{\"id\":\"fatbtc\",\"name\":\"FatBTC\"},{\"id\":\"fcoin\",\"name\":\"FCoin\"},{\"id\":\"fex\",\"name\":\"FEX\"},{\"id\":\"financex\",\"name\":\"FinanceX\"},{\"id\":\"finexbox\",\"name\":\"FinexBox\"},{\"id\":\"fisco\",\"name\":\"Fisco\"},{\"id\":\"floatsv\",\"name\":\"Float SV\"},{\"id\":\"fmex\",\"name\":\"FMex\"},{\"id\":\"forkdelta\",\"name\":\"ForkDelta\"},{\"id\":\"freiexchange\",\"name\":\"Freiexchange\"},{\"id\":\"ftx\",\"name\":\"FTX\"},{\"id\":\"ftx_spot\",\"name\":\"FTX (Spot)\"},{\"id\":\"fubt\",\"name\":\"FUBT\"},{\"id\":\"gate\",\"name\":\"Gate.io\"},{\"id\":\"gate_futures\",\"name\":\"Gate.io (Futures)\"},{\"id\":\"gbx\",\"name\":\"Gibraltar Blockchain Exchange\"},{\"id\":\"gdac\",\"name\":\"GDAC\"},{\"id\":\"gdax\",\"name\":\"Coinbase Pro\"},{\"id\":\"gemini\",\"name\":\"Gemini\"},{\"id\":\"getbtc\",\"name\":\"GetBTC\"},{\"id\":\"gmo_japan\",\"name\":\"GMO Japan\"},{\"id\":\"gmo_japan_futures\",\"name\":\"GMO Japan (Futures)\"},{\"id\":\"gobaba\",\"name\":\"Gobaba\"},{\"id\":\"go_exchange\",\"name\":\"Go Exchange\"},{\"id\":\"gopax\",\"name\":\"GoPax\"},{\"id\":\"graviex\",\"name\":\"Graviex\"},{\"id\":\"hanbitco\",\"name\":\"Hanbitco\"},{\"id\":\"hb_top\",\"name\":\"Hb.top\"},{\"id\":\"hitbtc\",\"name\":\"HitBTC\"},{\"id\":\"hotbit\",\"name\":\"Hotbit\"},{\"id\":\"hpx\",\"name\":\"HPX\"},{\"id\":\"hubi\",\"name\":\"Hubi\"},{\"id\":\"huobi\",\"name\":\"Huobi Global\"},{\"id\":\"huobi_dm\",\"name\":\"Huobi DM\"},{\"id\":\"huobi_japan\",\"name\":\"Huobi Japan\"},{\"id\":\"huobi_korea\",\"name\":\"Huobi Korea\"},{\"id\":\"huobi_us\",\"name\":\"Huobi US (HBUS)\"},{\"id\":\"ice3x\",\"name\":\"Ice3x\"},{\"id\":\"idcm\",\"name\":\"IDCM\"},{\"id\":\"idex\",\"name\":\"Idex\"},{\"id\":\"incorex\",\"name\":\"IncoreX\"},{\"id\":\"independent_reserve\",\"name\":\"Independent Reserve\"},{\"id\":\"indodax\",\"name\":\"Indodax\"},{\"id\":\"indoex\",\"name\":\"Indoex\"},{\"id\":\"infinity_coin\",\"name\":\"Infinity Coin\"},{\"id\":\"instantbitex\",\"name\":\"Instant Bitex\"},{\"id\":\"iqfinex\",\"name\":\"IQFinex\"},{\"id\":\"ironex\",\"name\":\"Ironex\"},{\"id\":\"itbit\",\"name\":\"itBit\"},{\"id\":\"jex\",\"name\":\"Binance JEX\"},{\"id\":\"jex_futures\",\"name\":\"Binance JEX (Futures)\"},{\"id\":\"joyso\",\"name\":\"Joyso\"},{\"id\":\"kairex\",\"name\":\"KAiREX\"},{\"id\":\"kkcoin\",\"name\":\"KKCoin\"},{\"id\":\"k_kex\",\"name\":\"KKEX\"},{\"id\":\"koinok\",\"name\":\"Koinok\"},{\"id\":\"koinx\",\"name\":\"Koinx\"},{\"id\":\"korbit\",\"name\":\"Korbit\"},{\"id\":\"kraken\",\"name\":\"Kraken\"},{\"id\":\"kraken_futures\",\"name\":\"Kraken (Futures)\"},{\"id\":\"kryptono\",\"name\":\"Kryptono\"},{\"id\":\"kucoin\",\"name\":\"KuCoin\"},{\"id\":\"kumex\",\"name\":\"Kumex\"},{\"id\":\"kuna\",\"name\":\"Kuna Exchange\"},{\"id\":\"kyber_network\",\"name\":\"Kyber Network\"},{\"id\":\"lakebtc\",\"name\":\"LakeBTC\"},{\"id\":\"latoken\",\"name\":\"LATOKEN\"},{\"id\":\"lbank\",\"name\":\"LBank\"},{\"id\":\"letsdocoinz\",\"name\":\"Letsdocoinz\"},{\"id\":\"livecoin\",\"name\":\"Livecoin\"},{\"id\":\"localtrade\",\"name\":\"LocalTrade\"},{\"id\":\"lukki\",\"name\":\"Lukki\"},{\"id\":\"luno\",\"name\":\"Luno\"},{\"id\":\"lykke\",\"name\":\"Lykke\"},{\"id\":\"mandala\",\"name\":\"Mandala\"},{\"id\":\"max_maicoin\",\"name\":\"Max Maicoin\"},{\"id\":\"mercado_bitcoin\",\"name\":\"Mercado Bitcoin\"},{\"id\":\"mercatox\",\"name\":\"Mercatox\"},{\"id\":\"mercuriex\",\"name\":\"MercuriEx\"},{\"id\":\"mxc\",\"name\":\"MXC\"},{\"id\":\"nanu_exchange\",\"name\":\"Nanu Exchange\"},{\"id\":\"nash\",\"name\":\"Nash\"},{\"id\":\"neblidex\",\"name\":\"Neblidex\"},{\"id\":\"negociecoins\",\"name\":\"Negociecoins\"},{\"id\":\"neraex\",\"name\":\"Neraex\"},{\"id\":\"newdex\",\"name\":\"Newdex\"},{\"id\":\"nexybit\",\"name\":\"Nexybit\"},{\"id\":\"ninecoin\",\"name\":\"9coin\"},{\"id\":\"nlexch\",\"name\":\"NLexch\"},{\"id\":\"novadax\",\"name\":\"NovaDAX\"},{\"id\":\"novadex\",\"name\":\"Novadex\"},{\"id\":\"oasis_trade\",\"name\":\"OasisDEX\"},{\"id\":\"oceanex\",\"name\":\"Oceanex\"},{\"id\":\"oex\",\"name\":\"OEX\"},{\"id\":\"okcoin\",\"name\":\"OKCoin\"},{\"id\":\"okex\",\"name\":\"OKEx\"},{\"id\":\"okex_korea\",\"name\":\"OKEx Korea\"},{\"id\":\"okex_swap\",\"name\":\"OKEx (Futures)\"},{\"id\":\"omgfin\",\"name\":\"Omgfin\"},{\"id\":\"omnitrade\",\"name\":\"OmniTrade\"},{\"id\":\"ooobtc\",\"name\":\"OOOBTC\"},{\"id\":\"openledger\",\"name\":\"OpenLedger DEX\"},{\"id\":\"orderbook\",\"name\":\"Orderbook.io\"},{\"id\":\"ore_bz\",\"name\":\"Ore BZ\"},{\"id\":\"otcbtc\",\"name\":\"OTCBTC\"},{\"id\":\"ovex\",\"name\":\"Ovex\"},{\"id\":\"p2pb2b\",\"name\":\"P2PB2B\"},{\"id\":\"paribu\",\"name\":\"Paribu\"},{\"id\":\"paroexchange\",\"name\":\"Paro Exchange\"},{\"id\":\"paymium\",\"name\":\"Paymium\"},{\"id\":\"piexgo\",\"name\":\"Piexgo\"},{\"id\":\"poloniex\",\"name\":\"Poloniex\"},{\"id\":\"prime_xbt\",\"name\":\"Prime XBT\"},{\"id\":\"probit\",\"name\":\"Probit\"},{\"id\":\"purcow\",\"name\":\"Purcow\"},{\"id\":\"qbtc\",\"name\":\"QBTC\"},{\"id\":\"qtrade\",\"name\":\"qTrade\"},{\"id\":\"quoine\",\"name\":\"Liquid\"},{\"id\":\"radar_relay\",\"name\":\"Radar Relay\"},{\"id\":\"raidofinance\",\"name\":\"Raidofinance\"},{\"id\":\"raisex\",\"name\":\"Raisex\"},{\"id\":\"resfinex\",\"name\":\"Resfinex\"},{\"id\":\"rfinex\",\"name\":\"Rfinex\"},{\"id\":\"safe_trade\",\"name\":\"SafeTrade\"},{\"id\":\"satoexchange\",\"name\":\"SatoExchange\"},{\"id\":\"sato_wallet_ex\",\"name\":\"SatowalletEx\"},{\"id\":\"saturn_network\",\"name\":\"Saturn Network\"},{\"id\":\"secondbtc\",\"name\":\"SecondBTC\"},{\"id\":\"shortex\",\"name\":\"Shortex\"},{\"id\":\"simex\",\"name\":\"Simex\"},{\"id\":\"sistemkoin\",\"name\":\"Sistemkoin\"},{\"id\":\"six_x\",\"name\":\"6x\"},{\"id\":\"south_xchange\",\"name\":\"SouthXchange\"},{\"id\":\"stake_cube\",\"name\":\"StakeCube Exchange\"},{\"id\":\"stellar_term\",\"name\":\"StellarTerm\"},{\"id\":\"stocks_exchange\",\"name\":\"STEX\"},{\"id\":\"swiftex\",\"name\":\"Swiftex\"},{\"id\":\"switcheo\",\"name\":\"Switcheo\"},{\"id\":\"syex\",\"name\":\"Shangya Exchange\"},{\"id\":\"synthetix\",\"name\":\"Synthetix Exchange\"},{\"id\":\"tdax\",\"name\":\"Satang Pro\"},{\"id\":\"therocktrading\",\"name\":\"TheRockTrading\"},{\"id\":\"thetokenstore\",\"name\":\"Token.Store\"},{\"id\":\"thinkbit\",\"name\":\"ThinkBit Pro\"},{\"id\":\"three_xbit\",\"name\":\"3XBIT\"},{\"id\":\"tidebit\",\"name\":\"Tidebit\"},{\"id\":\"tidex\",\"name\":\"Tidex\"},{\"id\":\"tokenize\",\"name\":\"Tokenize\"},{\"id\":\"tokenjar\",\"name\":\"TokenJar\"},{\"id\":\"tokenomy\",\"name\":\"Tokenomy\"},{\"id\":\"tokens_net\",\"name\":\"TokensNet\"},{\"id\":\"toko_crypto\",\"name\":\"TokoCrypto\"},{\"id\":\"tokok\",\"name\":\"TOKOK\"},{\"id\":\"tokpie\",\"name\":\"Tokpie\"},{\"id\":\"topbtc\",\"name\":\"TopBTC\"},{\"id\":\"tradeio\",\"name\":\"Trade.io\"},{\"id\":\"trade_ogre\",\"name\":\"TradeOgre\"},{\"id\":\"trade_satoshi\",\"name\":\"Trade Satoshi\"},{\"id\":\"troca_ninja\",\"name\":\"Troca.Ninja\"},{\"id\":\"tron_trade\",\"name\":\"TronTrade\"},{\"id\":\"trx_market\",\"name\":\"PoloniDEX\"},{\"id\":\"tux_exchange\",\"name\":\"Tux Exchange\"},{\"id\":\"txbit\",\"name\":\"Txbit\"},{\"id\":\"uex\",\"name\":\"UEX\"},{\"id\":\"uniswap\",\"name\":\"Uniswap\"},{\"id\":\"unnamed\",\"name\":\"Unnamed\"},{\"id\":\"upbit\",\"name\":\"Upbit\"},{\"id\":\"upbit_indonesia\",\"name\":\"Upbit Indonesia \"},{\"id\":\"vb\",\"name\":\"VB\"},{\"id\":\"vbitex\",\"name\":\"Vbitex\"},{\"id\":\"vcc\",\"name\":\"VCC Exchange\"},{\"id\":\"vebitcoin\",\"name\":\"Vebitcoin\"},{\"id\":\"velic\",\"name\":\"Velic\"},{\"id\":\"vindax\",\"name\":\"Vindax\"},{\"id\":\"vinex\",\"name\":\"Vinex\"},{\"id\":\"vitex\",\"name\":\"ViteX\"},{\"id\":\"waves\",\"name\":\"Waves.Exchange\"},{\"id\":\"wazirx\",\"name\":\"WazirX\"},{\"id\":\"whale_ex\",\"name\":\"WhaleEx\"},{\"id\":\"whitebit\",\"name\":\"Whitebit\"},{\"id\":\"worldcore\",\"name\":\"Worldcore\"},{\"id\":\"xfutures\",\"name\":\"xFutures\"},{\"id\":\"xt\",\"name\":\"XT\"},{\"id\":\"yobit\",\"name\":\"YoBit\"},{\"id\":\"yunex\",\"name\":\"Yunex.io\"},{\"id\":\"zaif\",\"name\":\"Zaif\"},{\"id\":\"zb\",\"name\":\"ZB\"},{\"id\":\"zbg\",\"name\":\"ZBG\"},{\"id\":\"zbmega\",\"name\":\"ZB Mega\"},{\"id\":\"zebpay\",\"name\":\"Zebpay\"},{\"id\":\"zg\",\"name\":\"ZG.com\"},{\"id\":\"zgtop\",\"name\":\"ZG.TOP\"}]";
+ private readonly HttpClient Client;
+ public static string CoinGeckoName { get; } = "coingecko";
+ public string Exchange { get; set; }
+ public string ExchangeName => Exchange ?? CoinGeckoName;
+
+ public CoinGeckoRateProvider(IHttpClientFactory httpClientFactory)
+ {
+ if (httpClientFactory == null)
+ {
+ return;;
+ }
+ Client = httpClientFactory.CreateClient();
+ Client.BaseAddress = new Uri("https://api.coingecko.com/api/v3/");
+ Client.DefaultRequestHeaders.Add("Accept", "application/json");
+ }
+
+ public virtual Task GetRatesAsync(CancellationToken cancellationToken)
+ {
+ return ExchangeName == CoinGeckoName ? GetCoinGeckoRates() : GetCoinGeckoExchangeSpecificRates();
+ }
+
+ private async Task GetCoinGeckoRates()
+ {
+ using var resp = await Client.GetAsync("exchange_rates");
+ resp.EnsureSuccessStatusCode();
+ return new ExchangeRates(JObject.Parse(await resp.Content.ReadAsStringAsync()).GetValue("rates").Children()
+ .Where(token => ((JProperty)token).Name != "btc")
+ .Select(token => new ExchangeRate(CoinGeckoName,
+ new CurrencyPair("BTC", ((JProperty)token).Name.ToString()),
+ new BidAsk(((JProperty)token).Value["value"].Value()))));
+ }
+
+ private async Task GetCoinGeckoExchangeSpecificRates(int page = 1)
+ {
+ using var resp = await Client.GetAsync($"exchanges/{Exchange}/tickers?page={page}");
+
+ resp.EnsureSuccessStatusCode();
+ List result = JObject.Parse(await resp.Content.ReadAsStringAsync()).GetValue("tickers")
+ .Select(token => new ExchangeRate(ExchangeName,
+ new CurrencyPair(token.Value("base"), token.Value("target")),
+ new BidAsk(token.Value("last")))).ToList();
+ if (page == 1 && resp.Headers.TryGetValues("total", out var total) &&
+ resp.Headers.TryGetValues("per-page", out var perPage))
+ {
+ var totalItems = int.Parse(total.First());
+ var perPageItems = int.Parse(perPage.First());
+
+ var totalPages = totalItems / perPageItems;
+ if (totalItems % perPageItems != 0)
+ {
+ totalPages++;
+ }
+
+ var tasks = new List>();
+ for (int i = 2; i <= totalPages; i++)
+ {
+ tasks.Add(GetCoinGeckoExchangeSpecificRates(i));
+ }
+
+ foreach (var t in (await Task.WhenAll(tasks)))
+ {
+ result.AddRange(t);
+ }
+ }
+
+ return new ExchangeRates(result);
+ }
+ }
+}
diff --git a/BTCPayServer.Rating/RateRules.cs b/BTCPayServer.Rating/RateRules.cs
index 9dfbfedea..2b47b34fa 100644
--- a/BTCPayServer.Rating/RateRules.cs
+++ b/BTCPayServer.Rating/RateRules.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
diff --git a/BTCPayServer.Rating/Services/RateProviderFactory.cs b/BTCPayServer.Rating/Services/RateProviderFactory.cs
index 154b871d9..e177eee12 100644
--- a/BTCPayServer.Rating/Services/RateProviderFactory.cs
+++ b/BTCPayServer.Rating/Services/RateProviderFactory.cs
@@ -9,6 +9,7 @@ using ExchangeSharp;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Newtonsoft.Json.Linq;
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
namespace BTCPayServer.Services.Rates
@@ -81,7 +82,7 @@ namespace BTCPayServer.Services.Rates
provider.CacheSpan = CacheSpan;
provider.MemoryCache = cache;
}
- if (Providers.TryGetValue(CoinAverageRateProvider.CoinAverageName, out var coinAverage) && coinAverage is BackgroundFetcherRateProvider c)
+ if (Providers.TryGetValue(CoinGeckoRateProvider.CoinGeckoName, out var coinAverage) && coinAverage is BackgroundFetcherRateProvider c)
{
c.RefreshRate = CacheSpan;
c.ValidatyTime = CacheSpan + TimeSpan.FromMinutes(1.0);
@@ -97,8 +98,22 @@ namespace BTCPayServer.Services.Rates
return _DirectProviders;
}
}
+ internal IEnumerable 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);
- private void 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
Providers.Add("binance", new ExchangeSharpRateProvider("binance", new ExchangeBinanceAPI(), true));
@@ -107,11 +122,8 @@ namespace BTCPayServer.Services.Rates
Providers.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitBTCAPI(), 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
+ 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("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_KRAKEN") });
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));
@@ -129,7 +141,7 @@ namespace BTCPayServer.Services.Rates
if (provider.Key == "cryptopia") // Shitty exchange, rate often unavailable, it spams the logs
continue;
var prov = new BackgroundFetcherRateProvider(provider.Key, Providers[provider.Key]);
- if(provider.Key == CoinAverageRateProvider.CoinAverageName)
+ if (provider.Key == CoinGeckoRateProvider.CoinGeckoName)
{
prov.RefreshRate = CacheSpan;
prov.ValidatyTime = CacheSpan + TimeSpan.FromMinutes(1.0);
@@ -143,40 +155,146 @@ namespace BTCPayServer.Services.Rates
}
var cache = new MemoryCache(_CacheOptions);
- foreach (var supportedExchange in GetSupportedExchanges())
+ foreach (var supportedExchange in GetCoinGeckoSupportedExchanges())
{
- if (!Providers.ContainsKey(supportedExchange.Key))
+ if (!Providers.ContainsKey(supportedExchange.Id))
{
- var coinAverage = new CoinAverageRateProvider()
+ var coinAverage = new CoinGeckoRateProvider(_httpClientFactory)
{
- Exchange = supportedExchange.Key,
- HttpClient = _httpClientFactory?.CreateClient(),
- Authenticator = _CoinAverageSettings
+ Exchange = supportedExchange.Id
};
- var cached = new CachedRateProvider(supportedExchange.Key, coinAverage, cache)
+ var cached = new CachedRateProvider(supportedExchange.Id, coinAverage, cache)
{
CacheSpan = CacheSpan
};
- Providers.Add(supportedExchange.Key, cached);
+ Providers.Add(supportedExchange.Id, cached);
+ }
+ }
+ foreach (var supportedExchange in GetCoinAverageSupportedExchanges())
+ {
+ 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);
}
}
}
- public CoinAverageExchanges GetSupportedExchanges()
+ IEnumerable _AvailableRateProviders = null;
+ public IEnumerable GetSupportedExchanges()
{
- CoinAverageExchanges exchanges = new CoinAverageExchanges();
- foreach (var exchange in _CoinAverageSettings.AvailableExchanges)
+ if (_AvailableRateProviders == null)
{
- exchanges.Add(exchange.Value);
+ var availableProviders = new Dictionary();
+ foreach (var exchange in GetDirectlySupportedExchanges())
+ {
+ availableProviders.Add(exchange.Id, exchange);
+ }
+ foreach (var exchange in GetCoinGeckoSupportedExchanges())
+ {
+ availableProviders.TryAdd(exchange.Id, exchange);
+ }
+ foreach (var exchange in GetCoinAverageSupportedExchanges())
+ {
+ availableProviders.TryAdd(exchange.Id, exchange);
+ }
+ _AvailableRateProviders = availableProviders.Values.OrderBy(o => o.Name).ToArray();
}
+ return _AvailableRateProviders;
+ }
- // Add other exchanges supported here
- exchanges.Add(new CoinAverageExchange(CoinAverageRateProvider.CoinAverageName, "Coin Average", $"https://apiv2.bitcoinaverage.com/indices/global/ticker/short"));
- exchanges.Add(new CoinAverageExchange("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD"));
- exchanges.Add(new CoinAverageExchange("ndax", "NDAX", "https://ndax.io/api/returnTicker"));
- exchanges.Add(new CoinAverageExchange("bitbank", "Bitbank", "https://public.bitbank.cc/prices"));
+ internal IEnumerable 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"),
+ })
+ {
+ yield return new AvailableRateProvider(item.Name, item.DisplayName, $"https://apiv2.bitcoinaverage.com/exchanges/{item.Name}", RateSource.CoinAverage);
+ }
+ yield return new AvailableRateProvider("gdax", string.Empty, $"https://apiv2.bitcoinaverage.com/exchanges/gdax", RateSource.CoinAverage);
+ }
- return exchanges;
+ internal IEnumerable GetCoinGeckoSupportedExchanges()
+ {
+ return JArray.Parse(CoinGeckoRateProvider.SupportedExchanges).Select(token =>
+ 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 QueryRates(string exchangeName, CancellationToken cancellationToken)
diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs
index 95eea7a02..fd5f2a57d 100644
--- a/BTCPayServer.Tests/BTCPayServerTester.cs
+++ b/BTCPayServer.Tests/BTCPayServerTester.cs
@@ -202,29 +202,29 @@ namespace BTCPayServer.Tests
var coinAverageMock = new MockRateProvider();
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
- Exchange = "coinaverage",
+ Exchange = "coingecko",
CurrencyPair = CurrencyPair.Parse("BTC_USD"),
BidAsk = new BidAsk(5000m)
});
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
- Exchange = "coinaverage",
+ Exchange = "coingecko",
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
BidAsk = new BidAsk(4500m)
});
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
- Exchange = "coinaverage",
- CurrencyPair = CurrencyPair.Parse("LTC_BTC"),
- BidAsk = new BidAsk(0.001m)
+ Exchange = "coingecko",
+ CurrencyPair = CurrencyPair.Parse("BTC_LTC"),
+ BidAsk = new BidAsk(162m)
});
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
- Exchange = "coinaverage",
+ Exchange = "coingecko",
CurrencyPair = CurrencyPair.Parse("LTC_USD"),
BidAsk = new BidAsk(500m)
});
- rateProvider.Providers.Add("coinaverage", coinAverageMock);
+ rateProvider.Providers.Add("coingecko", coinAverageMock);
var bitflyerMock = new MockRateProvider();
bitflyerMock.ExchangeRates.Add(new Rating.ExchangeRate()
diff --git a/BTCPayServer.Tests/Mocks/MockRateProvider.cs b/BTCPayServer.Tests/Mocks/MockRateProvider.cs
index 339762992..ad33e1c92 100644
--- a/BTCPayServer.Tests/Mocks/MockRateProvider.cs
+++ b/BTCPayServer.Tests/Mocks/MockRateProvider.cs
@@ -11,6 +11,11 @@ namespace BTCPayServer.Tests.Mocks
public class MockRateProvider : IRateProvider
{
public ExchangeRates ExchangeRates { get; set; } = new ExchangeRates();
+
+ public MockRateProvider()
+ {
+
+ }
public Task GetRatesAsync(CancellationToken cancellationToken)
{
return Task.FromResult(ExchangeRates);
diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs
index 86e44a32c..af96e6471 100644
--- a/BTCPayServer.Tests/UnitTest1.cs
+++ b/BTCPayServer.Tests/UnitTest1.cs
@@ -964,7 +964,7 @@ namespace BTCPayServer.Tests
var store = acc.GetController();
var ratesVM = (RatesViewModel)(Assert.IsType(store.Rates()).Model);
ratesVM.DefaultCurrencyPairs = "BTC_USD,LTC_USD";
- store.Rates(ratesVM).Wait();
+ await store.Rates(ratesVM);
store = acc.GetController();
rateController = acc.GetController();
GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, default)
@@ -1240,9 +1240,9 @@ namespace BTCPayServer.Tests
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
List rates = new List();
- rates.Add(CreateInvoice(tester, user, "coinaverage"));
- var bitflyer = CreateInvoice(tester, user, "bitflyer", "JPY");
- var bitflyer2 = CreateInvoice(tester, user, "bitflyer", "JPY");
+ rates.Add(await CreateInvoice(tester, user, "coingecko"));
+ var bitflyer = await CreateInvoice(tester, user, "bitflyer", "JPY");
+ var bitflyer2 = await CreateInvoice(tester, user, "bitflyer", "JPY");
Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache
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 CreateInvoice(ServerTester tester, TestAccount user, string exchange, string currency = "USD")
{
var storeController = user.GetController();
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
vm.PreferredExchange = exchange;
- storeController.Rates(vm).Wait();
- var invoice2 = user.BitPay.CreateInvoice(new Invoice()
+ await storeController.Rates(vm);
+ var invoice2 = await user.BitPay.CreateInvoiceAsync(new Invoice()
{
Price = 5000.0m,
Currency = currency,
@@ -1340,7 +1340,7 @@ namespace BTCPayServer.Tests
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
Assert.Equal(0.0, vm.Spread);
vm.Spread = 40;
- storeController.Rates(vm).Wait();
+ await storeController.Rates(vm);
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
@@ -1440,46 +1440,46 @@ namespace BTCPayServer.Tests
var store = user.GetController();
var rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model);
Assert.False(rateVm.ShowScripting);
- Assert.Equal("coinaverage", rateVm.PreferredExchange);
+ Assert.Equal(CoinGeckoRateProvider.CoinGeckoName, rateVm.PreferredExchange);
Assert.Equal(0.0, rateVm.Spread);
Assert.Null(rateVm.TestRateRules);
rateVm.PreferredExchange = "bitflyer";
- Assert.IsType(store.Rates(rateVm, "Save").Result);
+ Assert.IsType(await store.Rates(rateVm, "Save"));
rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model);
Assert.Equal("bitflyer", rateVm.PreferredExchange);
rateVm.ScriptTest = "BTC_JPY,BTC_CAD";
rateVm.Spread = 10;
store = user.GetController();
- rateVm = Assert.IsType(Assert.IsType(store.Rates(rateVm, "Test").Result).Model);
+ rateVm = Assert.IsType(Assert.IsType(await store.Rates(rateVm, "Test")).Model);
Assert.NotNull(rateVm.TestRateRules);
Assert.Equal(2, rateVm.TestRateRules.Count);
Assert.False(rateVm.TestRateRules[0].Error);
Assert.StartsWith("(bitflyer(BTC_JPY)) * (0.9, 1.1) =", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
Assert.True(rateVm.TestRateRules[1].Error);
- Assert.IsType(store.Rates(rateVm, "Save").Result);
+ Assert.IsType(await store.Rates(rateVm, "Save"));
Assert.IsType(store.ShowRateRulesPost(true).Result);
- Assert.IsType(store.Rates(rateVm, "Save").Result);
+ Assert.IsType(await store.Rates(rateVm, "Save"));
store = user.GetController();
rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model);
Assert.Equal(rateVm.StoreId, user.StoreId);
Assert.Equal(rateVm.DefaultScript, rateVm.Script);
Assert.True(rateVm.ShowScripting);
rateVm.ScriptTest = "BTC_JPY";
- rateVm = Assert.IsType(Assert.IsType(store.Rates(rateVm, "Test").Result).Model);
+ rateVm = Assert.IsType(Assert.IsType(await store.Rates(rateVm, "Test")).Model);
Assert.True(rateVm.ShowScripting);
Assert.Contains("(bitflyer(BTC_JPY)) * (0.9, 1.1) = ", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
rateVm.ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD";
rateVm.Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
"X_CAD = quadrigacx(X_CAD);\n" +
- "X_X = coinaverage(X_X);";
+ "X_X = coingecko(X_X);";
rateVm.Spread = 50;
- rateVm = Assert.IsType(Assert.IsType(store.Rates(rateVm, "Test").Result).Model);
+ rateVm = Assert.IsType(Assert.IsType(await store.Rates(rateVm, "Test")).Model);
Assert.True(rateVm.TestRateRules.All(t => !t.Error));
- Assert.IsType(store.Rates(rateVm, "Save").Result);
+ Assert.IsType(await store.Rates(rateVm, "Save"));
store = user.GetController();
rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model);
Assert.Equal(50, rateVm.Spread);
@@ -1569,7 +1569,7 @@ namespace BTCPayServer.Tests
Assert.NotNull(ltcCryptoInfo);
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
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);
Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress);
TestUtils.Eventually(() =>
@@ -2680,16 +2680,19 @@ noninventoryitem:
public void CanQueryDirectProviders()
{
var factory = CreateBTCPayRateFactory();
-
+ var all = string.Join("\r\n", factory.GetSupportedExchanges().Select(e => e.Id).ToArray());
foreach (var result in factory
.Providers
.Where(p => p.Value is BackgroundFetcherRateProvider)
.Select(p => (ExpectedName: p.Key, ResultAsync: p.Value.GetRatesAsync(default), Fetcher: (BackgroundFetcherRateProvider)p.Value))
.ToList())
{
+
Logs.Tester.LogInformation($"Testing {result.ExpectedName}");
if (result.ExpectedName == "quadrigacx")
continue; // 29 january, the exchange is down
+ if (result.ExpectedName == "coinaverage")
+ continue; // no more free plan
result.Fetcher.InvalidateCache();
var exchangeRates = result.ResultAsync.Result;
result.Fetcher.InvalidateCache();
@@ -2782,7 +2785,7 @@ noninventoryitem:
public static RateProviderFactory CreateBTCPayRateFactory()
{
- return new RateProviderFactory(CreateMemoryCache(), null, new CoinAverageSettings());
+ return new RateProviderFactory(CreateMemoryCache(), new MockHttpClientFactory(), new CoinAverageSettings());
}
private static MemoryCacheOptions CreateMemoryCache()
diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs
index 3e21ef995..de189636e 100644
--- a/BTCPayServer/Configuration/BTCPayServerOptions.cs
+++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs
@@ -86,14 +86,14 @@ namespace BTCPayServer.Configuration
var supportedChains = conf.GetOrDefault("chains", "btc")
.Split(',', StringSplitOptions.RemoveEmptyEntries)
- .Select(t => t.ToUpperInvariant()).ToList();
+ .Select(t => t.ToUpperInvariant()).ToHashSet();
var networkProvider = new BTCPayNetworkProvider(NetworkType);
var filtered = networkProvider.Filter(supportedChains.ToArray());
var elementsBased = filtered.GetAll().OfType();
var parentChains = elementsBased.Select(network => network.NetworkCryptoCode.ToUpperInvariant()).Distinct();
var allSubChains = networkProvider.GetAll().OfType()
- .Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode);
+ .Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode.ToUpperInvariant());
supportedChains.AddRange(allSubChains);
NetworkProvider = networkProvider.Filter(supportedChains.ToArray());
foreach (var chain in supportedChains)
diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs
index 11d89f851..e8a67094e 100644
--- a/BTCPayServer/Controllers/StoresController.cs
+++ b/BTCPayServer/Controllers/StoresController.cs
@@ -9,7 +9,6 @@ using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Models;
-using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Changelly;
@@ -23,15 +22,12 @@ using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
-using Microsoft.Extensions.Options;
using NBitcoin;
using NBitcoin.DataEncoders;
-using Newtonsoft.Json;
namespace BTCPayServer.Controllers
{
@@ -199,14 +195,15 @@ namespace BTCPayServer.Controllers
[Route("{storeId}/rates")]
public IActionResult Rates()
{
+ var exchanges = GetSupportedExchanges();
var storeBlob = CurrentStore.GetStoreBlob();
var vm = new RatesViewModel();
- vm.SetExchangeRates(GetSupportedExchanges(), storeBlob.PreferredExchange ?? CoinAverageRateProvider.CoinAverageName);
+ vm.SetExchangeRates(exchanges, storeBlob.PreferredExchange ?? CoinGeckoRateProvider.CoinGeckoName);
vm.Spread = (double)(storeBlob.Spread * 100m);
vm.StoreId = CurrentStore.Id;
vm.Script = storeBlob.GetRateRules(_NetworkProvider).ToString();
vm.DefaultScript = storeBlob.GetDefaultRateRules(_NetworkProvider).ToString();
- vm.AvailableExchanges = GetSupportedExchanges();
+ vm.AvailableExchanges = exchanges;
vm.DefaultCurrencyPairs = storeBlob.GetDefaultCurrencyPairString();
vm.ShowScripting = storeBlob.RateScripting;
return View(vm);
@@ -216,7 +213,16 @@ namespace BTCPayServer.Controllers
[Route("{storeId}/rates")]
public async Task Rates(RatesViewModel model, string command = null, string storeId = null, CancellationToken cancellationToken = default)
{
- model.SetExchangeRates(GetSupportedExchanges(), model.PreferredExchange);
+ if (command == "scripting-on")
+ {
+ return RedirectToAction(nameof(ShowRateRules), new {scripting = true,storeId = model.StoreId});
+ }else if (command == "scripting-off")
+ {
+ return RedirectToAction(nameof(ShowRateRules), new {scripting = false, storeId = model.StoreId});
+ }
+
+ var exchanges = GetSupportedExchanges();
+ model.SetExchangeRates(exchanges, model.PreferredExchange);
model.StoreId = storeId ?? model.StoreId;
CurrencyPair[] currencyPairs = null;
try
@@ -239,14 +245,14 @@ namespace BTCPayServer.Controllers
var blob = CurrentStore.GetStoreBlob();
model.DefaultScript = blob.GetDefaultRateRules(_NetworkProvider).ToString();
- model.AvailableExchanges = GetSupportedExchanges();
+ model.AvailableExchanges = exchanges;
blob.PreferredExchange = model.PreferredExchange;
blob.Spread = (decimal)model.Spread / 100.0m;
blob.DefaultCurrencyPairs = currencyPairs;
if (!model.ShowScripting)
{
- if (!GetSupportedExchanges().Select(c => c.Name).Contains(blob.PreferredExchange, StringComparer.OrdinalIgnoreCase))
+ if (!exchanges.Any(provider => provider.Id.Equals(model.PreferredExchange, StringComparison.InvariantCultureIgnoreCase)))
{
ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange ({model.RateSource})");
return View(model);
@@ -332,7 +338,7 @@ namespace BTCPayServer.Controllers
Description = scripting ?
"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?",
- ButtonClass = "btn-primary"
+ ButtonClass = scripting ? "btn-primary" : "btn-danger"
});
}
@@ -597,13 +603,13 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
- private CoinAverageExchange[] GetSupportedExchanges()
+ private IEnumerable GetSupportedExchanges()
{
- return _RateFactory.RateProviderFactory.GetSupportedExchanges()
- .Where(r => !string.IsNullOrWhiteSpace(r.Value.Display))
- .Select(c => c.Value)
- .OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
- .ToArray();
+ var exchanges = _RateFactory.RateProviderFactory.GetSupportedExchanges();
+ return exchanges
+ .Where(r => !string.IsNullOrWhiteSpace(r.Name))
+ .OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase);
+
}
private DerivationSchemeSettings ParseDerivationStrategy(string derivationScheme, Script hint, BTCPayNetwork network)
diff --git a/BTCPayServer/Data/StoreBlob.cs b/BTCPayServer/Data/StoreBlob.cs
index 8dc12d627..d7782d264 100644
--- a/BTCPayServer/Data/StoreBlob.cs
+++ b/BTCPayServer/Data/StoreBlob.cs
@@ -10,6 +10,7 @@ using BTCPayServer.Rating;
using BTCPayServer.Services.Mails;
using Newtonsoft.Json;
using System.Text;
+using BTCPayServer.Services.Rates;
namespace BTCPayServer.Data
{
@@ -156,7 +157,7 @@ namespace BTCPayServer.Data
}
}
- var preferredExchange = string.IsNullOrEmpty(PreferredExchange) ? "coinaverage" : PreferredExchange;
+ var preferredExchange = string.IsNullOrEmpty(PreferredExchange) ? CoinGeckoRateProvider.CoinGeckoName : PreferredExchange;
builder.AppendLine($"X_X = {preferredExchange}(X_X);");
BTCPayServer.Rating.RateRules.TryParse(builder.ToString(), out var rules);
diff --git a/BTCPayServer/Data/StoreDataExtensions.cs b/BTCPayServer/Data/StoreDataExtensions.cs
index 1df7fd0a0..59f7f00ef 100644
--- a/BTCPayServer/Data/StoreDataExtensions.cs
+++ b/BTCPayServer/Data/StoreDataExtensions.cs
@@ -51,7 +51,7 @@ namespace BTCPayServer.Data
{
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(null).ToObject(Encoding.UTF8.GetString(storeData.StoreBlob));
if (result.PreferredExchange == null)
- result.PreferredExchange = CoinAverageRateProvider.CoinAverageName;
+ result.PreferredExchange = CoinGeckoRateProvider.CoinGeckoName;
return result;
}
diff --git a/BTCPayServer/HostedServices/RatesHostedService.cs b/BTCPayServer/HostedServices/RatesHostedService.cs
index 9e7776e67..b75f4f562 100644
--- a/BTCPayServer/HostedServices/RatesHostedService.cs
+++ b/BTCPayServer/HostedServices/RatesHostedService.cs
@@ -44,7 +44,6 @@ namespace BTCPayServer.HostedServices
{
return new Task[]
{
- CreateLoopTask(RefreshCoinAverageSupportedExchanges),
CreateLoopTask(RefreshCoinAverageSettings),
CreateLoopTask(RefreshRates)
};
@@ -144,19 +143,6 @@ namespace BTCPayServer.HostedServices
await _SettingsRepository.UpdateSetting(cache);
}
- async Task RefreshCoinAverageSupportedExchanges()
- {
- var exchanges = new CoinAverageExchanges();
- foreach (var item in (await new CoinAverageRateProvider() { Authenticator = _coinAverageSettings }.GetExchangeTickersAsync())
- .Exchanges
- .Select(c => new CoinAverageExchange(c.Name, c.DisplayName, $"https://apiv2.bitcoinaverage.com/exchanges/{c.Name}")))
- {
- exchanges.Add(item);
- }
- _coinAverageSettings.AvailableExchanges = exchanges;
- await Task.Delay(TimeSpan.FromHours(5), Cancellation);
- }
-
async Task RefreshCoinAverageSettings()
{
var rates = (await _SettingsRepository.GetSettingAsync()) ?? new RatesSetting();
diff --git a/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs b/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs
index b26d1e10e..0341ed1ab 100644
--- a/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs
+++ b/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using System.Threading.Tasks;
using BTCPayServer.Rating;
using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.Mvc.Rendering;
@@ -17,22 +16,31 @@ namespace BTCPayServer.Models.StoreViewModels
public string Rule { get; set; }
public bool Error { get; set; }
}
- class Format
+ public void SetExchangeRates(IEnumerable supportedList, string preferredExchange)
{
- public string Name { get; set; }
- public string Value { get; set; }
- public string Url { get; set; }
- }
- public void SetExchangeRates(CoinAverageExchange[] supportedList, string preferredExchange)
- {
- var defaultStore = preferredExchange ?? CoinAverageRateProvider.CoinAverageName;
- var choices = supportedList.Select(o => new Format() { Name = o.Display, Value = o.Name, Url = o.Url }).ToArray();
- var chosen = choices.FirstOrDefault(f => f.Value == defaultStore) ?? choices.FirstOrDefault();
- Exchanges = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
- PreferredExchange = chosen.Value;
+ 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();
+ Exchanges = new SelectList(supportedList, nameof(chosen.Id), nameof(chosen.Name), chosen);
+ PreferredExchange = chosen.Id;
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 TestRateRules { get; set; }
public SelectList Exchanges { get; set; }
@@ -46,7 +54,7 @@ namespace BTCPayServer.Models.StoreViewModels
public string ScriptTest { get; set; }
public string DefaultCurrencyPairs { get; set; }
public string StoreId { get; set; }
- public CoinAverageExchange[] AvailableExchanges { get; set; }
+ public IEnumerable AvailableExchanges { get; set; }
[Display(Name = "Add a spread on exchange rate of ... %")]
[Range(0.0, 100.0)]
diff --git a/BTCPayServer/Properties/launchSettings.json b/BTCPayServer/Properties/launchSettings.json
index a3d3c1fd1..08c21a244 100644
--- a/BTCPayServer/Properties/launchSettings.json
+++ b/BTCPayServer/Properties/launchSettings.json
@@ -44,7 +44,7 @@
"BTCPAY_ALLOW-ADMIN-REGISTRATION": "true",
"BTCPAY_DISABLE-REGISTRATION": "false",
"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_EXTERNALSERVICES": "totoservice:totolink;",
"BTCPAY_SSHCONNECTION": "root@127.0.0.1:21622",
diff --git a/BTCPayServer/Views/Shared/Confirm.cshtml b/BTCPayServer/Views/Shared/Confirm.cshtml
index 8632fe749..22f2975cb 100644
--- a/BTCPayServer/Views/Shared/Confirm.cshtml
+++ b/BTCPayServer/Views/Shared/Confirm.cshtml
@@ -27,7 +27,7 @@
diff --git a/BTCPayServer/Views/Stores/Rates.cshtml b/BTCPayServer/Views/Stores/Rates.cshtml
index 6c0fa1518..23973de7d 100644
--- a/BTCPayServer/Views/Stores/Rates.cshtml
+++ b/BTCPayServer/Views/Stores/Rates.cshtml
@@ -18,15 +18,61 @@
{
}
@if (Model.TestRateRules != null)
@@ -110,13 +156,13 @@
}
else
@@ -130,7 +176,7 @@
}