decred.org/dcrdex@v1.0.3/dex/fiatrates/fiatrates.go (about)

     1  package fiatrates
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"decred.org/dcrdex/dex"
    10  	"decred.org/dcrdex/dex/dexnet"
    11  )
    12  
    13  const (
    14  	coinpaprikaURL     = "https://api.coinpaprika.com/v1/tickers"
    15  	fiatRequestTimeout = time.Second * 5
    16  )
    17  
    18  func CoinpapSlug(name, symbol string) string {
    19  	name, symbol = parseCoinpapNameSymbol(name, symbol)
    20  	slug := fmt.Sprintf("%s-%s", symbol, name)
    21  	// Special handling for asset names with multiple space, e.g Bitcoin Cash.
    22  	return strings.ToLower(strings.ReplaceAll(slug, " ", "-"))
    23  }
    24  
    25  type CoinpaprikaAsset struct {
    26  	AssetID uint32
    27  	Name    string
    28  	Symbol  string
    29  }
    30  
    31  func parseCoinpapNameSymbol(name, symbol string) (string, string) {
    32  	parts := strings.Split(symbol, ".")
    33  	network := symbol
    34  	if len(parts) == 2 {
    35  		symbol, network = parts[0], parts[1]
    36  	}
    37  	switch symbol {
    38  	case "usdc":
    39  		name = "usd-coin"
    40  	case "polygon":
    41  		symbol = "matic"
    42  		name = "polygon"
    43  	case "weth":
    44  		name = "weth"
    45  	case "matic":
    46  		switch network {
    47  		case "eth":
    48  			symbol, name = "matic", "polygon"
    49  		}
    50  	}
    51  	return name, symbol
    52  }
    53  
    54  // FetchCoinpaprikaRates retrieves and parses fiat rate data from the
    55  // Coinpaprika API. See https://api.coinpaprika.com/#operation/getTickersById
    56  // for sample request and response information.
    57  func FetchCoinpaprikaRates(ctx context.Context, assets []*CoinpaprikaAsset, log dex.Logger) map[uint32]float64 {
    58  	fiatRates := make(map[uint32]float64)
    59  	slugAssets := make(map[string][]uint32)
    60  	for _, a := range assets {
    61  		slug := CoinpapSlug(a.Name, a.Symbol)
    62  		slugAssets[slug] = append(slugAssets[slug], a.AssetID)
    63  	}
    64  
    65  	ctx, cancel := context.WithTimeout(ctx, fiatRequestTimeout)
    66  	defer cancel()
    67  
    68  	var res []*struct {
    69  		ID     string `json:"id"`
    70  		Quotes struct {
    71  			USD struct {
    72  				Price float64 `json:"price"`
    73  			} `json:"USD"`
    74  		} `json:"quotes"`
    75  	}
    76  
    77  	if err := getRates(ctx, coinpaprikaURL, &res); err != nil {
    78  		log.Errorf("Error getting fiat exchange rates from coinpaprika: %v", err)
    79  		return fiatRates
    80  	}
    81  	for _, coinInfo := range res {
    82  		assetIDs, found := slugAssets[coinInfo.ID]
    83  		if !found {
    84  			continue
    85  		}
    86  
    87  		price := coinInfo.Quotes.USD.Price
    88  		if price == 0 {
    89  			log.Errorf("zero-price returned from coinpaprika for slug %s", coinInfo.ID)
    90  			continue
    91  		}
    92  		for _, assetID := range assetIDs {
    93  			fiatRates[assetID] = price
    94  		}
    95  	}
    96  	return fiatRates
    97  }
    98  
    99  func getRates(ctx context.Context, uri string, thing any) error {
   100  	return dexnet.Get(ctx, uri, thing, dexnet.WithSizeLimit(1<<22))
   101  }