synapsecns/sanguine

View on GitHub
services/rfq/relayer/pricer/fetcher.go

Summary

Maintainability
A
50 mins
Test Coverage
package pricer

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

// CoingeckoPriceFetcher is an interface for fetching prices from coingecko.
//
//go:generate go run github.com/vektra/mockery/v2 --name CoingeckoPriceFetcher --output ./mocks --case=underscore
type CoingeckoPriceFetcher interface {
    GetPrice(ctx context.Context, token string) (float64, error)
}

// CoingeckoPriceFetcherImpl is an implementation of CoingeckoPriceFetcher.
type CoingeckoPriceFetcherImpl struct {
    client *http.Client
}

// NewCoingeckoPriceFetcher creates a new instance of CoingeckoPriceFetcherImpl.
func NewCoingeckoPriceFetcher(timeout time.Duration) *CoingeckoPriceFetcherImpl {
    return &CoingeckoPriceFetcherImpl{
        client: &http.Client{
            Timeout: timeout,
        },
    }
}

var coingeckoIDLookup = map[string]string{
    "ETH": "ethereum",
}

// GetPrice fetches the price of a token from coingecko.
func (c *CoingeckoPriceFetcherImpl) GetPrice(ctx context.Context, token string) (price float64, err error) {
    coingeckoID, ok := coingeckoIDLookup[token]
    if !ok {
        return price, fmt.Errorf("could not get coingecko id for token: %s", token)
    }
    url := fmt.Sprintf("https://api.coingecko.com/api/v3/simple/price?ids=%s&vs_currencies=USD", coingeckoID)

    // fetch price from coingecko
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return price, fmt.Errorf("could not build request: %w", err)
    }
    r, err := c.client.Do(req)
    if err != nil {
        return price, fmt.Errorf("could not get price from coingecko: %w", err)
    }
    if r.StatusCode != http.StatusOK {
        return price, fmt.Errorf("bad status code fetching price from coingecko: %v", r.Status)
    }
    defer func() {
        err = r.Body.Close()
    }()

    respBytes, err := io.ReadAll(r.Body)
    if err != nil {
        return price, fmt.Errorf("could not read response body: %w", err)
    }

    // parse the price
    var resp map[string]map[string]float64
    err = json.Unmarshal(respBytes, &resp)
    if err != nil {
        return price, fmt.Errorf("could not unmarshal response body: %w", err)
    }
    price, ok = resp[coingeckoID]["usd"]
    if !ok {
        return price, fmt.Errorf("could not get price from coingecko response: %v", resp)
    }
    return price, nil
}