Remove CoinAverage integration

This commit is contained in:
nicolas.dorier
2020-01-17 14:29:22 +09:00
parent 1b9a4e7775
commit 9b165de5e6
8 changed files with 2 additions and 496 deletions

View File

@@ -1,192 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.ComponentModel;
using BTCPayServer.Rating;
using System.Threading;
namespace BTCPayServer.Services.Rates
{
public class CoinAverageException : Exception
{
public CoinAverageException(string message) : base(message)
{
}
}
public class RatesSetting
{
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
}
public interface ICoinAverageAuthenticator
{
Task AddHeader(HttpRequestMessage message);
}
public class CoinAverageRateProvider : IRateProvider, IHasExchangeName
{
public const string CoinAverageName = "coinaverage";
public CoinAverageRateProvider()
{
}
public HttpClient HttpClient
{
get
{
return _LocalClient ?? _Client;
}
set
{
_LocalClient = value;
}
}
HttpClient _LocalClient;
static HttpClient _Client = new HttpClient();
public string Exchange { get; set; } = CoinAverageName;
public string CryptoCode { get; set; }
public string Market
{
get; set;
} = "global";
public ICoinAverageAuthenticator Authenticator { get; set; }
public string ExchangeName => Exchange ?? CoinAverageName;
private bool TryToBidAsk(JProperty p, out BidAsk bidAsk)
{
bidAsk = null;
if (Exchange == CoinAverageName)
{
JToken last = p.Value["last"];
if (!decimal.TryParse(last.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v) ||
v <= 0)
return false;
bidAsk = new BidAsk(v);
return true;
}
else
{
JToken bid = p.Value["bid"];
JToken ask = p.Value["ask"];
if (bid == null || ask == null ||
!decimal.TryParse(bid.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) ||
!decimal.TryParse(ask.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) ||
v1 > v2 ||
v1 <= 0 || v2 <= 0)
return false;
bidAsk = new BidAsk(v1, v2);
return true;
}
}
public async Task<ExchangeRates> GetRatesAsync(CancellationToken cancellationToken)
{
string url = Exchange == CoinAverageName ? $"https://apiv2.bitcoinaverage.com/indices/{Market}/ticker/short"
: $"https://apiv2.bitcoinaverage.com/exchanges/{Exchange}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
var auth = Authenticator;
if (auth != null)
{
await auth.AddHeader(request);
}
var resp = await HttpClient.SendAsync(request, cancellationToken);
using (resp)
{
if ((int)resp.StatusCode == 401)
throw new CoinAverageException("Unauthorized access to the API");
if ((int)resp.StatusCode == 429)
throw new CoinAverageException("Exceed API limits");
if ((int)resp.StatusCode == 403)
throw new CoinAverageException("Unauthorized access to the API, premium plan needed");
resp.EnsureSuccessStatusCode();
var rates = JObject.Parse(await resp.Content.ReadAsStringAsync());
if (Exchange != CoinAverageName)
{
rates = (JObject)rates["symbols"];
}
var exchangeRates = new ExchangeRates();
foreach (var prop in rates.Properties())
{
ExchangeRate exchangeRate = new ExchangeRate();
exchangeRate.Exchange = Exchange;
if (!TryToBidAsk(prop, out var value))
continue;
exchangeRate.BidAsk = value;
if (CurrencyPair.TryParse(prop.Name, out var pair))
{
exchangeRate.CurrencyPair = pair;
exchangeRates.Add(exchangeRate);
}
}
return exchangeRates;
}
}
public async Task TestAuthAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/blockchain/tx_price/BTCUSD/8a3b4394ba811a9e2b0bbf3cc56888d053ea21909299b2703cdc35e156c860ff");
var auth = Authenticator;
if (auth != null)
{
await auth.AddHeader(request);
}
var resp = await HttpClient.SendAsync(request);
resp.EnsureSuccessStatusCode();
}
public async Task<GetRateLimitsResponse> GetRateLimitsAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/info/ratelimits");
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 GetRateLimitsResponse();
response.CounterReset = TimeSpan.FromSeconds(jobj["counter_reset"].Value<int>());
var totalPeriod = jobj["total_period"].Value<string>();
if (totalPeriod == "24h")
{
response.TotalPeriod = TimeSpan.FromHours(24);
}
else if (totalPeriod == "30d")
{
response.TotalPeriod = TimeSpan.FromDays(30);
}
else
{
response.TotalPeriod = TimeSpan.FromSeconds(jobj["total_period"].Value<int>());
}
response.RequestsLeft = jobj["requests_left"].Value<int>();
response.RequestsPerPeriod = jobj["requests_per_period"].Value<int>();
return response;
}
}
public class GetRateLimitsResponse
{
public TimeSpan CounterReset { get; set; }
public int RequestsLeft { get; set; }
public int RequestsPerPeriod { get; set; }
public TimeSpan TotalPeriod { get; set; }
}
}

View File

@@ -1,51 +0,0 @@
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace BTCPayServer.Services.Rates
{
public class CoinAverageSettingsAuthenticator : ICoinAverageAuthenticator
{
CoinAverageSettings _Settings;
public CoinAverageSettingsAuthenticator(CoinAverageSettings settings)
{
_Settings = settings;
}
public Task AddHeader(HttpRequestMessage message)
{
return _Settings.AddHeader(message);
}
}
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 Task AddHeader(HttpRequestMessage message)
{
var signature = GetCoinAverageSignature();
if (signature != null)
{
message.Headers.Add("X-signature", signature);
}
return Task.CompletedTask;
}
public string GetCoinAverageSignature()
{
var keyPair = KeyPair;
if (!keyPair.HasValue)
return null;
if (string.IsNullOrEmpty(keyPair.Value.PublicKey) || string.IsNullOrEmpty(keyPair.Value.PrivateKey))
return null;
var timestamp = (int)((DateTime.UtcNow - _epochUtc).TotalSeconds);
var payload = timestamp + "." + keyPair.Value.PublicKey;
var digestValueBytes = new HMACSHA256(Encoding.ASCII.GetBytes(keyPair.Value.PrivateKey)).ComputeHash(Encoding.ASCII.GetBytes(payload));
var digestValueHex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(digestValueBytes);
return payload + "." + digestValueHex;
}
}
}

View File

@@ -158,24 +158,6 @@ namespace BTCPayServer.Services.Rates
Providers.Add(supportedExchange.Id, bgFetcher);
}
}
var cache = new MemoryCache(_CacheOptions);
foreach (var supportedExchange in GetCoinAverageSupportedExchanges())
{
if (!Providers.ContainsKey(supportedExchange.Id))
{
var coinAverage = new CoinAverageRateProvider()
{
HttpClient = _httpClientFactory.CreateClient("RATEPROVIDER_COINAVERAGE"),
Exchange = supportedExchange.Id
};
var cached = new CachedRateProvider(supportedExchange.Id, coinAverage, cache)
{
CacheSpan = CacheSpan
};
Providers.Add(supportedExchange.Id, cached);
}
}
}
IEnumerable<AvailableRateProvider> _AvailableRateProviders = null;
@@ -192,85 +174,11 @@ namespace BTCPayServer.Services.Rates
{
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;
}
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"),
})
{
yield return new AvailableRateProvider(item.Name, item.Name, item.DisplayName, $"https://apiv2.bitcoinaverage.com/exchanges/{item.Name}", RateSource.CoinAverage);
}
yield return new AvailableRateProvider("gdax", "coinbasepro", string.Empty, $"https://apiv2.bitcoinaverage.com/exchanges/coinbasepro", RateSource.CoinAverage);
}
internal IEnumerable<AvailableRateProvider> GetCoinGeckoSupportedExchanges()
{
return JArray.Parse(CoinGeckoRateProvider.SupportedExchanges).Select(token =>

View File

@@ -84,74 +84,6 @@ namespace BTCPayServer.Controllers
_sshState = sshState;
}
[Route("server/rates")]
public async Task<IActionResult> Rates()
{
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
var vm = new RatesViewModel()
{
PrivateKey = rates.PrivateKey,
PublicKey = rates.PublicKey
};
await FetchRateLimits(vm);
return View(vm);
}
private static async Task FetchRateLimits(RatesViewModel vm)
{
var coinAverage = GetCoinaverageService(vm, false);
if (coinAverage != null)
{
try
{
vm.RateLimits = await coinAverage.GetRateLimitsAsync();
}
catch { }
}
}
[Route("server/rates")]
[HttpPost]
public async Task<IActionResult> Rates(RatesViewModel vm)
{
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
rates.PrivateKey = vm.PrivateKey;
rates.PublicKey = vm.PublicKey;
try
{
var service = GetCoinaverageService(vm, true);
if (service != null)
await service.TestAuthAsync();
}
catch
{
ModelState.AddModelError(nameof(vm.PrivateKey), "Invalid API key pair");
}
if (!ModelState.IsValid)
{
await FetchRateLimits(vm);
return View(vm);
}
await _SettingsRepository.UpdateSetting(rates);
TempData[WellKnownTempData.SuccessMessage] = "Rate settings successfully updated";
return RedirectToAction(nameof(Rates));
}
private static CoinAverageRateProvider GetCoinaverageService(RatesViewModel vm, bool withAuth)
{
var settings = new CoinAverageSettings()
{
KeyPair = (vm.PublicKey, vm.PrivateKey)
};
if (!withAuth || settings.GetCoinAverageSignature() != null)
{
return new CoinAverageRateProvider()
{ Authenticator = settings };
}
return null;
}
[Route("server/users")]
public IActionResult ListUsers(int skip = 0, int count = 50)
{

View File

@@ -29,14 +29,11 @@ namespace BTCPayServer.HostedServices
}
}
private SettingsRepository _SettingsRepository;
private CoinAverageSettings _coinAverageSettings;
RateProviderFactory _RateProviderFactory;
public RatesHostedService(SettingsRepository repo,
RateProviderFactory rateProviderFactory,
CoinAverageSettings coinAverageSettings)
RateProviderFactory rateProviderFactory)
{
this._SettingsRepository = repo;
_coinAverageSettings = coinAverageSettings;
_RateProviderFactory = rateProviderFactory;
}
@@ -44,7 +41,6 @@ namespace BTCPayServer.HostedServices
{
return new Task[]
{
CreateLoopTask(RefreshCoinAverageSettings),
CreateLoopTask(RefreshRates)
};
}
@@ -142,19 +138,5 @@ namespace BTCPayServer.HostedServices
.ToList();
await _SettingsRepository.UpdateSetting(cache);
}
async Task RefreshCoinAverageSettings()
{
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
if (!string.IsNullOrWhiteSpace(rates.PrivateKey) && !string.IsNullOrWhiteSpace(rates.PublicKey))
{
_coinAverageSettings.KeyPair = (rates.PublicKey, rates.PrivateKey);
}
else
{
_coinAverageSettings.KeyPair = null;
}
await _SettingsRepository.WaitSettingsChanged<RatesSetting>(Cancellation);
}
}
}

View File

@@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Rates;
namespace BTCPayServer.Models.ServerViewModels
{
public class RatesViewModel
{
[Display(Name = "Bitcoin average api keys")]
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
public GetRateLimitsResponse RateLimits { get; internal set; }
}
}

View File

@@ -1,56 +0,0 @@
@model BTCPayServer.Models.ServerViewModels.RatesViewModel
@{
ViewData.SetActivePageAndTitle(ServerNavPages.Rates);
}
<partial name="_StatusMessage" />
<div class="row">
<div class="col-md-6">
<div asp-validation-summary="All" class="text-danger"></div>
</div>
</div>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<h5>Bitcoin Average</h5>
<p>BTCPay relies on Bitcoin Average for getting crypto-currency to fiat rates</p>
<p>If you want BTCPay's rate cache to be smaller than 15min, you should register to BitcoinAverage and get a paid API Key.</p>
<p>If your BTCPay server instance supports many merchants or is used a lot, BitcoinAverage will rate limit your server and invoices will be created using less accurate rates. (From Bitpay)<br /></p>
</div>
<form method="post">
<label asp-for="PublicKey"></label>
<div class="form-inline">
<input asp-for="PublicKey" style="width:50%;" class="form-control" placeholder="Public key" />
<label class="sr-only" asp-for="PrivateKey"></label>
<input asp-for="PrivateKey" style="width:50%;" class="form-control" placeholder="Private key" />
<p class="form-text text-muted">You can find the information on the <a target="_blank" href="https://bitcoinaverage.com/en/apikeys">bitcoinaverage api key page</a></p>
</div>
@if(Model.RateLimits != null)
{
<h5>Current Bitcoin Average Quotas:</h5>
<table class="table table-sm removetopborder">
<tr>
<th>Quota period</th>
<td>@Model.RateLimits.TotalPeriod.TimeString()</td>
</tr>
<tr>
<th>Requests quota</th>
<td>@Model.RateLimits.RequestsLeft/@Model.RateLimits.RequestsPerPeriod</td>
</tr>
<tr>
<th>Quota reset in</th>
<td>@Model.RateLimits.CounterReset.TimeString()</td>
</tr>
</table>
}
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}

View File

@@ -7,6 +7,6 @@ namespace BTCPayServer.Views.Server
{
public enum ServerNavPages
{
Index, Users, Rates, Emails, Policies, Theme, Services, Maintenance, Logs, Files
Index, Users, Emails, Policies, Theme, Services, Maintenance, Logs, Files
}
}