github.com/wormhole-foundation/wormhole-explorer/common@v0.0.0-20240604151348-09585b5b97c5/coingecko/api.go (about)

     1  package coingecko
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  )
     9  
    10  var ErrCoinNotFound = fmt.Errorf("coin not found")
    11  var ErrTooManyRequests = fmt.Errorf("too many requests")
    12  
    13  type CoinGeckoAPI struct {
    14  	ApiURL     string
    15  	HeaderKey  string
    16  	ApiKey     string
    17  	tokenCache map[string]TokenItem
    18  }
    19  
    20  type TokenItem struct {
    21  	Id       string
    22  	Chain    string
    23  	Symbol   string
    24  	Decimals int
    25  }
    26  
    27  func NewCoinGeckoAPI(url, headerKey string, apiKey string) *CoinGeckoAPI {
    28  	return &CoinGeckoAPI{
    29  		ApiURL:     url,
    30  		HeaderKey:  headerKey,
    31  		ApiKey:     apiKey,
    32  		tokenCache: make(map[string]TokenItem),
    33  	}
    34  }
    35  
    36  func (cg *CoinGeckoAPI) GetSymbolDailyPrice(coinID, days string) (*CoinHistoryResponse, error) {
    37  
    38  	url := fmt.Sprintf("%s/api/v3/coins/%s/market_chart?vs_currency=usd&days=%s&interval=daily", cg.ApiURL, coinID, days)
    39  	method := "GET"
    40  
    41  	client := &http.Client{}
    42  	req, err := http.NewRequest(method, url, nil)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	if cg.HeaderKey != "" && cg.ApiKey != "" {
    48  		req.Header.Add(cg.HeaderKey, cg.ApiKey)
    49  	}
    50  
    51  	res, err := client.Do(req)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	defer res.Body.Close()
    56  
    57  	if res.StatusCode != 200 {
    58  		switch res.StatusCode {
    59  		case 404:
    60  			return nil, ErrCoinNotFound
    61  		case 429:
    62  			return nil, ErrTooManyRequests
    63  		default:
    64  			return nil, fmt.Errorf("failed request with status code; %d", res.StatusCode)
    65  		}
    66  	}
    67  
    68  	body, err := ioutil.ReadAll(res.Body)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	var td CoinHistoryResponse
    74  	err = json.Unmarshal(body, &td)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	return &td, nil
    80  
    81  }
    82  
    83  func (cg *CoinGeckoAPI) GetSymbol(ChainId string, ContractId string) (string, error) {
    84  
    85  	// lookup on cache first
    86  	if val, ok := cg.tokenCache[ContractId]; ok {
    87  		return val.Symbol, nil
    88  	}
    89  
    90  	// lookup on coingecko
    91  	ti, err := cg.GetSymbolByContract(ChainId, ContractId)
    92  	if err != nil {
    93  
    94  		// if not found, return none
    95  		if err.Error() == "token not found" {
    96  			return "none", nil
    97  		}
    98  
    99  		return "", err
   100  
   101  	}
   102  
   103  	// add to cache
   104  	fmt.Printf("adding to cache: %s\n", ti.Symbol)
   105  	cg.tokenCache[ContractId] = *ti
   106  	return ti.Symbol, nil
   107  
   108  }
   109  
   110  func (cg *CoinGeckoAPI) convertChain(chain string) string {
   111  
   112  	// check if chain exsist on map
   113  	if val, ok := convertionMap[chain]; ok {
   114  		return val
   115  	}
   116  	return chain
   117  
   118  }
   119  
   120  // GetSymbolByContract returns the symbol of the token
   121  // Input: ChaindId is the name of the chain: ie: ethereum, solana, etc
   122  // Input: ContractId is the contract address of the token (ECR-20 or other)
   123  func (cg *CoinGeckoAPI) GetSymbolByContract(ChainId string, ContractId string) (*TokenItem, error) {
   124  
   125  	chain := cg.convertChain(ChainId)
   126  	//url := "https://api.coingecko.com/api/v3/coins/avalanche/contract/0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be"
   127  	url := fmt.Sprintf("https://%s/api/v3/coins/%s/contract/%s", cg.ApiURL, chain, ContractId)
   128  	method := "GET"
   129  
   130  	client := &http.Client{}
   131  	req, err := http.NewRequest(method, url, nil)
   132  
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	//req.Header.Add("Cookie", "__cf_bm=jUWxA1U8U3SdvDF2EXgCZUmnDopOozWnB5VpXIjWH.c-1682970763-0-AaLD4yVrSy53aAJQwVNe61P5IcXSnW4vIMeRrsRDIMGJ/+PbEcOv/lene34+FB4Q4kapT//4660lx/Rw507zw7Q=")
   137  
   138  	res, err := client.Do(req)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	defer res.Body.Close()
   143  
   144  	if res.StatusCode == 404 {
   145  		return nil, fmt.Errorf("token not found")
   146  	}
   147  
   148  	body, err := ioutil.ReadAll(res.Body)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	var td TokenData
   154  	err = json.Unmarshal(body, &td)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	ti := TokenItem{
   160  		Id:     td.ID,
   161  		Chain:  ChainId,
   162  		Symbol: td.Symbol,
   163  	}
   164  
   165  	fmt.Printf("\"%s\": \"%s\",\n", ContractId, ti.Symbol)
   166  
   167  	return &ti, nil
   168  }