github.com/status-im/status-go@v1.1.0/services/wallet/currency/currency.go (about)

     1  package currency
     2  
     3  import (
     4  	"errors"
     5  	"math"
     6  	"strings"
     7  
     8  	iso4217 "github.com/ladydascalie/currency"
     9  
    10  	"github.com/status-im/status-go/services/wallet/market"
    11  	"github.com/status-im/status-go/services/wallet/token"
    12  )
    13  
    14  const decimalsCalculationCurrency = "USD"
    15  
    16  const lowerTokenResolutionInUsd = 0.1
    17  const higherTokenResolutionInUsd = 0.01
    18  
    19  type Format struct {
    20  	Symbol              string `json:"symbol"`
    21  	DisplayDecimals     uint   `json:"displayDecimals"`
    22  	StripTrailingZeroes bool   `json:"stripTrailingZeroes"`
    23  }
    24  
    25  type FormatPerSymbol = map[string]Format
    26  
    27  type Currency struct {
    28  	marketManager *market.Manager
    29  }
    30  
    31  func NewCurrency(marketManager *market.Manager) *Currency {
    32  	return &Currency{
    33  		marketManager: marketManager,
    34  	}
    35  }
    36  
    37  func IsCurrencyFiat(symbol string) bool {
    38  	return iso4217.Valid(strings.ToUpper(symbol))
    39  }
    40  
    41  func GetAllFiatCurrencySymbols() []string {
    42  	return iso4217.ValidCodes
    43  }
    44  
    45  func calculateFiatDisplayDecimals(symbol string) (uint, error) {
    46  	currency, err := iso4217.Get(strings.ToUpper(symbol))
    47  
    48  	if err != nil {
    49  		return 0, err
    50  	}
    51  
    52  	return uint(currency.MinorUnits()), nil
    53  }
    54  
    55  func calculateFiatCurrencyFormat(symbol string) (*Format, error) {
    56  	displayDecimals, err := calculateFiatDisplayDecimals(symbol)
    57  
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	format := &Format{
    63  		Symbol:              symbol,
    64  		DisplayDecimals:     displayDecimals,
    65  		StripTrailingZeroes: false,
    66  	}
    67  
    68  	return format, nil
    69  }
    70  
    71  func calculateTokenDisplayDecimals(price float64) uint {
    72  	var displayDecimals float64 = 0.0
    73  
    74  	if price > 0 {
    75  		lowerDecimalsBound := math.Max(0.0, math.Log10(price)-math.Log10(lowerTokenResolutionInUsd))
    76  		upperDecimalsBound := math.Max(0.0, math.Log10(price)-math.Log10(higherTokenResolutionInUsd))
    77  
    78  		// Use as few decimals as needed to ensure lower precision
    79  		displayDecimals = math.Ceil(lowerDecimalsBound)
    80  		if displayDecimals+1.0 <= upperDecimalsBound {
    81  			// If allowed by upper bound, ensure resolution changes as soon as currency hits multiple of 10
    82  			displayDecimals += 1.0
    83  		}
    84  	}
    85  
    86  	return uint(displayDecimals)
    87  }
    88  
    89  func (cm *Currency) calculateTokenCurrencyFormat(symbol string, price float64) (*Format, error) {
    90  	pegSymbol := token.GetTokenPegSymbol(symbol)
    91  
    92  	if pegSymbol != "" {
    93  		var currencyFormat, err = calculateFiatCurrencyFormat(pegSymbol)
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  		currencyFormat.Symbol = symbol
    98  		return currencyFormat, nil
    99  	}
   100  
   101  	currencyFormat := &Format{
   102  		Symbol:              symbol,
   103  		DisplayDecimals:     calculateTokenDisplayDecimals(price),
   104  		StripTrailingZeroes: true,
   105  	}
   106  	return currencyFormat, nil
   107  }
   108  
   109  func GetFiatCurrencyFormats(symbols []string) (FormatPerSymbol, error) {
   110  	formats := make(FormatPerSymbol)
   111  
   112  	for _, symbol := range symbols {
   113  		format, err := calculateFiatCurrencyFormat(symbol)
   114  
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  
   119  		formats[symbol] = *format
   120  	}
   121  
   122  	return formats, nil
   123  }
   124  
   125  func (cm *Currency) FetchTokenCurrencyFormats(symbols []string) (FormatPerSymbol, error) {
   126  	formats := make(FormatPerSymbol)
   127  
   128  	// Get latest cached price, fetch only if not available
   129  	prices, err := cm.marketManager.GetOrFetchPrices(symbols, []string{decimalsCalculationCurrency}, math.MaxInt64)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	for _, symbol := range symbols {
   135  		priceData, ok := prices[symbol][decimalsCalculationCurrency]
   136  
   137  		if !ok {
   138  			return nil, errors.New("Could not get price for: " + symbol)
   139  		}
   140  
   141  		format, err := cm.calculateTokenCurrencyFormat(symbol, priceData.Price)
   142  
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  
   147  		formats[symbol] = *format
   148  	}
   149  
   150  	return formats, nil
   151  }