github.com/jackcoble/blockbook@v0.3.2/fiat/coingecko.go (about)

     1  package fiat
     2  
     3  import (
     4  	"blockbook/db"
     5  	"encoding/json"
     6  	"errors"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/golang/glog"
    13  )
    14  
    15  // Coingecko is a structure that implements RatesDownloaderInterface
    16  type Coingecko struct {
    17  	url                string
    18  	coin               string
    19  	httpTimeoutSeconds time.Duration
    20  	timeFormat         string
    21  }
    22  
    23  // NewCoinGeckoDownloader creates a coingecko structure that implements the RatesDownloaderInterface
    24  func NewCoinGeckoDownloader(url string, coin string, timeFormat string) RatesDownloaderInterface {
    25  	return &Coingecko{
    26  		url:                url,
    27  		coin:               coin,
    28  		httpTimeoutSeconds: 15 * time.Second,
    29  		timeFormat:         timeFormat,
    30  	}
    31  }
    32  
    33  // makeRequest retrieves the response from Coingecko API at the specified date.
    34  // If timestamp is nil, it fetches the latest market data available.
    35  func (cg *Coingecko) makeRequest(timestamp *time.Time) ([]byte, error) {
    36  	requestURL := cg.url + "/coins/" + cg.coin
    37  	if timestamp != nil {
    38  		requestURL += "/history"
    39  	}
    40  
    41  	req, err := http.NewRequest("GET", requestURL, nil)
    42  	if err != nil {
    43  		glog.Errorf("Error creating a new request for %v: %v", requestURL, err)
    44  		return nil, err
    45  	}
    46  	req.Close = true
    47  	req.Header.Set("Content-Type", "application/json")
    48  
    49  	// Add query parameters
    50  	q := req.URL.Query()
    51  
    52  	// Add a unix timestamp to query parameters to get uncached responses
    53  	currentTimestamp := strconv.FormatInt(time.Now().UTC().UnixNano(), 10)
    54  	q.Add("current_timestamp", currentTimestamp)
    55  
    56  	if timestamp == nil {
    57  		q.Add("market_data", "true")
    58  		q.Add("localization", "false")
    59  		q.Add("tickers", "false")
    60  		q.Add("community_data", "false")
    61  		q.Add("developer_data", "false")
    62  	} else {
    63  		timestampFormatted := timestamp.Format(cg.timeFormat)
    64  		q.Add("date", timestampFormatted)
    65  	}
    66  	req.URL.RawQuery = q.Encode()
    67  
    68  	client := &http.Client{
    69  		Timeout: cg.httpTimeoutSeconds,
    70  	}
    71  	resp, err := client.Do(req)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	defer resp.Body.Close()
    76  	if resp.StatusCode != http.StatusOK {
    77  		return nil, errors.New("Invalid response status: " + string(resp.Status))
    78  	}
    79  	bodyBytes, err := ioutil.ReadAll(resp.Body)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	return bodyBytes, nil
    84  }
    85  
    86  // GetData gets fiat rates from API at the specified date and returns a CurrencyRatesTicker
    87  // If timestamp is nil, it will download the current fiat rates.
    88  func (cg *Coingecko) getTicker(timestamp *time.Time) (*db.CurrencyRatesTicker, error) {
    89  	dataTimestamp := timestamp
    90  	if timestamp == nil {
    91  		timeNow := time.Now()
    92  		dataTimestamp = &timeNow
    93  	}
    94  	dataTimestampUTC := dataTimestamp.UTC()
    95  	ticker := &db.CurrencyRatesTicker{Timestamp: &dataTimestampUTC}
    96  	bodyBytes, err := cg.makeRequest(timestamp)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	type FiatRatesResponse struct {
   102  		MarketData struct {
   103  			Prices map[string]float64 `json:"current_price"`
   104  		} `json:"market_data"`
   105  	}
   106  
   107  	var data FiatRatesResponse
   108  	err = json.Unmarshal(bodyBytes, &data)
   109  	if err != nil {
   110  		glog.Errorf("Error parsing FiatRates response: %v", err)
   111  		return nil, err
   112  	}
   113  	ticker.Rates = data.MarketData.Prices
   114  	return ticker, nil
   115  }
   116  
   117  // MarketDataExists checks if there's data available for the specific timestamp.
   118  func (cg *Coingecko) marketDataExists(timestamp *time.Time) (bool, error) {
   119  	resp, err := cg.makeRequest(timestamp)
   120  	if err != nil {
   121  		glog.Error("Error getting market data: ", err)
   122  		return false, err
   123  	}
   124  	type FiatRatesResponse struct {
   125  		MarketData struct {
   126  			Prices map[string]interface{} `json:"current_price"`
   127  		} `json:"market_data"`
   128  	}
   129  	var data FiatRatesResponse
   130  	err = json.Unmarshal(resp, &data)
   131  	if err != nil {
   132  		glog.Errorf("Error parsing Coingecko response: %v", err)
   133  		return false, err
   134  	}
   135  	return len(data.MarketData.Prices) != 0, nil
   136  }