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 }