mirror of
https://github.com/status-im/status-go.git
synced 2025-01-12 07:35:02 +00:00
c84b136b4a
For tokens that it does not support and some that were listed in mapping we responded with error
351 lines
8.8 KiB
Go
351 lines
8.8 KiB
Go
package coingecko
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
|
|
"golang.org/x/exp/maps"
|
|
|
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
|
"github.com/status-im/status-go/services/wallet/thirdparty/utils"
|
|
)
|
|
|
|
var coinGeckoMapping = map[string]string{
|
|
"STT": "status",
|
|
"SNT": "status",
|
|
"ETH": "ethereum",
|
|
"AST": "airswap",
|
|
"ABT": "arcblock",
|
|
"BNB": "binancecoin",
|
|
"BLT": "bloom",
|
|
"COMP": "compound-coin",
|
|
"EDG": "edgeless",
|
|
"ENG": "enigma",
|
|
"EOS": "eos",
|
|
"GEN": "daostack",
|
|
"MANA": "decentraland-wormhole",
|
|
"LEND": "ethlend",
|
|
"LRC": "loopring",
|
|
"MET": "metronome",
|
|
"POLY": "polymath",
|
|
"PPT": "populous",
|
|
"SAN": "santiment-network-token",
|
|
"DNT": "district0x",
|
|
"SPN": "sapien",
|
|
"USDS": "stableusd",
|
|
"STX": "stox",
|
|
"SUB": "substratum",
|
|
"PAY": "tenx",
|
|
"GRT": "the-graph",
|
|
"TNT": "tierion",
|
|
"TRX": "tron",
|
|
"RARE": "superrare",
|
|
"UNI": "uniswap",
|
|
"USDC": "usd-coin",
|
|
"USDP": "paxos-standard",
|
|
"USDT": "tether",
|
|
"SHIB": "shiba-inu",
|
|
"LINK": "chainlink",
|
|
"MATIC": "matic-network",
|
|
"DAI": "dai",
|
|
"ARB": "arbitrum",
|
|
"OP": "optimism",
|
|
}
|
|
|
|
const baseURL = "https://api.coingecko.com/api/v3/"
|
|
|
|
type HistoricalPriceContainer struct {
|
|
Prices [][]float64 `json:"prices"`
|
|
}
|
|
type GeckoMarketValues struct {
|
|
ID string `json:"id"`
|
|
Symbol string `json:"symbol"`
|
|
Name string `json:"name"`
|
|
MarketCap float64 `json:"market_cap"`
|
|
High24h float64 `json:"high_24h"`
|
|
Low24h float64 `json:"low_24h"`
|
|
PriceChange24h float64 `json:"price_change_24h"`
|
|
PriceChangePercentage24h float64 `json:"price_change_percentage_24h"`
|
|
PriceChangePercentage1hInCurrency float64 `json:"price_change_percentage_1h_in_currency"`
|
|
}
|
|
|
|
type GeckoToken struct {
|
|
ID string `json:"id"`
|
|
Symbol string `json:"symbol"`
|
|
Name string `json:"name"`
|
|
EthPlatform bool
|
|
}
|
|
|
|
type Client struct {
|
|
httpClient *thirdparty.HTTPClient
|
|
tokens map[string][]GeckoToken
|
|
tokensURL string
|
|
fetchTokensMutex sync.Mutex
|
|
}
|
|
|
|
func NewClient() *Client {
|
|
return &Client{
|
|
httpClient: thirdparty.NewHTTPClient(),
|
|
tokens: make(map[string][]GeckoToken),
|
|
tokensURL: fmt.Sprintf("%scoins/list", baseURL),
|
|
}
|
|
}
|
|
|
|
func (gt *GeckoToken) UnmarshalJSON(data []byte) error {
|
|
// Define an auxiliary struct to hold the JSON data
|
|
var aux struct {
|
|
ID string `json:"id"`
|
|
Symbol string `json:"symbol"`
|
|
Name string `json:"name"`
|
|
Platforms struct {
|
|
Ethereum string `json:"ethereum"`
|
|
// Other platforms can be added here if needed
|
|
} `json:"platforms"`
|
|
}
|
|
|
|
// Unmarshal the JSON data into the auxiliary struct
|
|
if err := json.Unmarshal(data, &aux); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set the fields of GeckoToken from the auxiliary struct
|
|
gt.ID = aux.ID
|
|
gt.Symbol = aux.Symbol
|
|
gt.Name = aux.Name
|
|
|
|
// Check if "ethereum" key exists in the platforms map
|
|
if aux.Platforms.Ethereum != "" {
|
|
gt.EthPlatform = true
|
|
} else {
|
|
gt.EthPlatform = false
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func mapTokensToSymbols(tokens []GeckoToken, tokenMap map[string][]GeckoToken) {
|
|
for _, token := range tokens {
|
|
symbol := strings.ToUpper(token.Symbol)
|
|
if id, ok := coinGeckoMapping[symbol]; ok {
|
|
if id != token.ID {
|
|
continue
|
|
}
|
|
}
|
|
tokenMap[symbol] = append(tokenMap[symbol], token)
|
|
}
|
|
}
|
|
|
|
func getGeckoTokenFromSymbol(tokens map[string][]GeckoToken, symbol string) (GeckoToken, error) {
|
|
tokenList, ok := tokens[strings.ToUpper(symbol)]
|
|
if !ok {
|
|
return GeckoToken{}, fmt.Errorf("token not found for symbol %s", symbol)
|
|
}
|
|
for _, t := range tokenList {
|
|
if t.EthPlatform {
|
|
return t, nil
|
|
}
|
|
}
|
|
return tokenList[0], nil
|
|
}
|
|
|
|
func getIDFromSymbol(tokens map[string][]GeckoToken, symbol string) (string, error) {
|
|
token, err := getGeckoTokenFromSymbol(tokens, symbol)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return token.ID, nil
|
|
}
|
|
|
|
func (c *Client) getTokens() (map[string][]GeckoToken, error) {
|
|
c.fetchTokensMutex.Lock()
|
|
defer c.fetchTokensMutex.Unlock()
|
|
|
|
if len(c.tokens) > 0 {
|
|
return c.tokens, nil
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Add("include_platform", "true")
|
|
|
|
url := c.tokensURL
|
|
response, err := c.httpClient.DoGetRequest(context.Background(), url, params, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var tokens []GeckoToken
|
|
err = json.Unmarshal(response, &tokens)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mapTokensToSymbols(tokens, c.tokens)
|
|
return c.tokens, nil
|
|
}
|
|
|
|
func (c *Client) mapSymbolsToIds(symbols []string) (map[string]string, error) {
|
|
tokens, err := c.getTokens()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ids := make(map[string]string, 0)
|
|
for _, symbol := range symbols {
|
|
id, err := getIDFromSymbol(tokens, utils.GetRealSymbol(symbol))
|
|
if err == nil {
|
|
ids[symbol] = id
|
|
}
|
|
}
|
|
|
|
return ids, nil
|
|
}
|
|
|
|
func (c *Client) FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error) {
|
|
ids, err := c.mapSymbolsToIds(symbols)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Add("ids", strings.Join(maps.Values(ids), ","))
|
|
params.Add("vs_currencies", strings.Join(currencies, ","))
|
|
|
|
url := fmt.Sprintf("%ssimple/price", baseURL)
|
|
response, err := c.httpClient.DoGetRequest(context.Background(), url, params, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prices := make(map[string]map[string]float64)
|
|
err = json.Unmarshal(response, &prices)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s - %s", err, string(response))
|
|
}
|
|
|
|
result := make(map[string]map[string]float64)
|
|
for symbol, id := range ids {
|
|
result[symbol] = map[string]float64{}
|
|
for _, currency := range currencies {
|
|
result[symbol][currency] = prices[id][strings.ToLower(currency)]
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (c *Client) FetchTokenDetails(symbols []string) (map[string]thirdparty.TokenDetails, error) {
|
|
tokens, err := c.getTokens()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := make(map[string]thirdparty.TokenDetails)
|
|
for _, symbol := range symbols {
|
|
token, err := getGeckoTokenFromSymbol(tokens, utils.GetRealSymbol(symbol))
|
|
if err == nil {
|
|
result[symbol] = thirdparty.TokenDetails{
|
|
ID: token.ID,
|
|
Name: token.Name,
|
|
Symbol: symbol,
|
|
}
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (c *Client) FetchTokenMarketValues(symbols []string, currency string) (map[string]thirdparty.TokenMarketValues, error) {
|
|
ids, err := c.mapSymbolsToIds(symbols)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Add("ids", strings.Join(maps.Values(ids), ","))
|
|
params.Add("vs_currency", currency)
|
|
params.Add("order", "market_cap_desc")
|
|
params.Add("per_page", "250")
|
|
params.Add("page", "1")
|
|
params.Add("sparkline", "false")
|
|
params.Add("price_change_percentage", "1h,24h")
|
|
|
|
url := fmt.Sprintf("%scoins/markets", baseURL)
|
|
response, err := c.httpClient.DoGetRequest(context.Background(), url, params, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var marketValues []GeckoMarketValues
|
|
err = json.Unmarshal(response, &marketValues)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s - %s", err, string(response))
|
|
}
|
|
|
|
result := make(map[string]thirdparty.TokenMarketValues)
|
|
for symbol, id := range ids {
|
|
for _, marketValue := range marketValues {
|
|
if id != marketValue.ID {
|
|
continue
|
|
}
|
|
|
|
result[symbol] = thirdparty.TokenMarketValues{
|
|
MKTCAP: marketValue.MarketCap,
|
|
HIGHDAY: marketValue.High24h,
|
|
LOWDAY: marketValue.Low24h,
|
|
CHANGEPCTHOUR: marketValue.PriceChangePercentage1hInCurrency,
|
|
CHANGEPCTDAY: marketValue.PriceChangePercentage24h,
|
|
CHANGEPCT24HOUR: marketValue.PriceChangePercentage24h,
|
|
CHANGE24HOUR: marketValue.PriceChange24h,
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (c *Client) FetchHistoricalHourlyPrices(symbol string, currency string, limit int, aggregate int) ([]thirdparty.HistoricalPrice, error) {
|
|
return []thirdparty.HistoricalPrice{}, nil
|
|
}
|
|
|
|
func (c *Client) FetchHistoricalDailyPrices(symbol string, currency string, limit int, allData bool, aggregate int) ([]thirdparty.HistoricalPrice, error) {
|
|
tokens, err := c.getTokens()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
id, err := getIDFromSymbol(tokens, utils.GetRealSymbol(symbol))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Add("vs_currency", currency)
|
|
params.Add("days", "30")
|
|
|
|
url := fmt.Sprintf("%scoins/%s/market_chart", baseURL, id)
|
|
response, err := c.httpClient.DoGetRequest(context.Background(), url, params, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var container HistoricalPriceContainer
|
|
err = json.Unmarshal(response, &container)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make([]thirdparty.HistoricalPrice, 0)
|
|
for _, price := range container.Prices {
|
|
result = append(result, thirdparty.HistoricalPrice{
|
|
Timestamp: int64(price[0]),
|
|
Value: price[1],
|
|
})
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (c *Client) ID() string {
|
|
return "coingecko"
|
|
}
|