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 }