137 lines
3.7 KiB
Go
137 lines
3.7 KiB
Go
package fiat
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/golang/glog"
|
|
"spacecruft.org/spacecruft/blockbook/db"
|
|
)
|
|
|
|
// Coingecko is a structure that implements RatesDownloaderInterface
|
|
type Coingecko struct {
|
|
url string
|
|
coin string
|
|
httpTimeoutSeconds time.Duration
|
|
timeFormat string
|
|
}
|
|
|
|
// NewCoinGeckoDownloader creates a coingecko structure that implements the RatesDownloaderInterface
|
|
func NewCoinGeckoDownloader(url string, coin string, timeFormat string) RatesDownloaderInterface {
|
|
return &Coingecko{
|
|
url: url,
|
|
coin: coin,
|
|
httpTimeoutSeconds: 15 * time.Second,
|
|
timeFormat: timeFormat,
|
|
}
|
|
}
|
|
|
|
// makeRequest retrieves the response from Coingecko API at the specified date.
|
|
// If timestamp is nil, it fetches the latest market data available.
|
|
func (cg *Coingecko) makeRequest(timestamp *time.Time) ([]byte, error) {
|
|
requestURL := cg.url + "/coins/" + cg.coin
|
|
if timestamp != nil {
|
|
requestURL += "/history"
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", requestURL, nil)
|
|
if err != nil {
|
|
glog.Errorf("Error creating a new request for %v: %v", requestURL, err)
|
|
return nil, err
|
|
}
|
|
req.Close = true
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Add query parameters
|
|
q := req.URL.Query()
|
|
|
|
// Add a unix timestamp to query parameters to get uncached responses
|
|
currentTimestamp := strconv.FormatInt(time.Now().UTC().UnixNano(), 10)
|
|
q.Add("current_timestamp", currentTimestamp)
|
|
|
|
if timestamp == nil {
|
|
q.Add("market_data", "true")
|
|
q.Add("localization", "false")
|
|
q.Add("tickers", "false")
|
|
q.Add("community_data", "false")
|
|
q.Add("developer_data", "false")
|
|
} else {
|
|
timestampFormatted := timestamp.Format(cg.timeFormat)
|
|
q.Add("date", timestampFormatted)
|
|
}
|
|
req.URL.RawQuery = q.Encode()
|
|
|
|
client := &http.Client{
|
|
Timeout: cg.httpTimeoutSeconds,
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, errors.New("Invalid response status: " + string(resp.Status))
|
|
}
|
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bodyBytes, nil
|
|
}
|
|
|
|
// GetData gets fiat rates from API at the specified date and returns a CurrencyRatesTicker
|
|
// If timestamp is nil, it will download the current fiat rates.
|
|
func (cg *Coingecko) getTicker(timestamp *time.Time) (*db.CurrencyRatesTicker, error) {
|
|
dataTimestamp := timestamp
|
|
if timestamp == nil {
|
|
timeNow := time.Now()
|
|
dataTimestamp = &timeNow
|
|
}
|
|
dataTimestampUTC := dataTimestamp.UTC()
|
|
ticker := &db.CurrencyRatesTicker{Timestamp: &dataTimestampUTC}
|
|
bodyBytes, err := cg.makeRequest(timestamp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
type FiatRatesResponse struct {
|
|
MarketData struct {
|
|
Prices map[string]float64 `json:"current_price"`
|
|
} `json:"market_data"`
|
|
}
|
|
|
|
var data FiatRatesResponse
|
|
err = json.Unmarshal(bodyBytes, &data)
|
|
if err != nil {
|
|
glog.Errorf("Error parsing FiatRates response: %v", err)
|
|
return nil, err
|
|
}
|
|
ticker.Rates = data.MarketData.Prices
|
|
return ticker, nil
|
|
}
|
|
|
|
// MarketDataExists checks if there's data available for the specific timestamp.
|
|
func (cg *Coingecko) marketDataExists(timestamp *time.Time) (bool, error) {
|
|
resp, err := cg.makeRequest(timestamp)
|
|
if err != nil {
|
|
glog.Error("Error getting market data: ", err)
|
|
return false, err
|
|
}
|
|
type FiatRatesResponse struct {
|
|
MarketData struct {
|
|
Prices map[string]interface{} `json:"current_price"`
|
|
} `json:"market_data"`
|
|
}
|
|
var data FiatRatesResponse
|
|
err = json.Unmarshal(resp, &data)
|
|
if err != nil {
|
|
glog.Errorf("Error parsing Coingecko response: %v", err)
|
|
return false, err
|
|
}
|
|
return len(data.MarketData.Prices) != 0, nil
|
|
}
|