wormhole-explorer/jobs/internal/coingecko/coingecko.go

104 lines
2.5 KiB
Go

package coingecko
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
// CoingeckoAPI is a client for the coingecko API
type CoingeckoAPI struct {
url string
chunkSize int
client *http.Client
logger *zap.Logger
headerKey string
apiKey string
}
// NewCoingeckoAPI creates a new coingecko client
func NewCoingeckoAPI(url string, headerKey, apiKey string, logger *zap.Logger) *CoingeckoAPI {
return &CoingeckoAPI{
url: url,
chunkSize: 200,
client: http.DefaultClient,
headerKey: headerKey,
apiKey: apiKey,
logger: logger,
}
}
// NotionalUSD is the response from the coingecko API.
type NotionalUSD struct {
Price *decimal.Decimal `json:"usd"`
}
// GetNotionalUSD returns the notional USD value for the given ids
// ids is a list of coingecko chain identifier.
func (c *CoingeckoAPI) GetNotionalUSD(ids []string) (map[string]NotionalUSD, error) {
response := map[string]NotionalUSD{}
chunksIds := chunkChainIds(ids, c.chunkSize)
c.logger.Info("fetching notional value of assets", zap.Int("total_chunks", len(chunksIds)))
// iterate over chunks of ids.
for i, chunk := range chunksIds {
notionalUrl := fmt.Sprintf("%s/api/v3/simple/price?ids=%s&vs_currencies=usd", c.url, strings.Join(chunk, ","))
req, err := http.NewRequest(http.MethodGet, notionalUrl, nil)
if err != nil {
return response, err
}
if c.headerKey != "" && c.apiKey != "" {
req.Header.Add(c.headerKey, c.apiKey)
}
res, err := c.client.Do(req)
if err != nil {
return response, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
c.logger.Error("failed to get notional value of assets", zap.Int("statusCode", res.StatusCode), zap.Int("chunk", i))
return response, fmt.Errorf("failed to get notional value of assets, status code: %d", res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return response, err
}
chunkResponse := map[string]NotionalUSD{}
err = json.Unmarshal(body, &chunkResponse)
if err != nil {
return response, err
}
// merge chunk response with response.
for k, v := range chunkResponse {
response[k] = v
}
}
return response, nil
}
func chunkChainIds(slice []string, chunkSize int) [][]string {
var chunks [][]string
for i := 0; i < len(slice); i += chunkSize {
end := i + chunkSize
if end > len(slice) {
end = len(slice)
}
chunks = append(chunks, slice[i:end])
}
return chunks
}