feat(@desktop/Wallet): use new status-go currency formatting API

Fixes #9453
This commit is contained in:
Dario Gabriel Lipicar 2023-02-14 15:08:35 -03:00 committed by dlipicar
parent b70e1f0fb4
commit 27b8180fdd
16 changed files with 160 additions and 110 deletions

View File

@ -172,7 +172,9 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.tokenService = token_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.networkService
)
result.currencyService = currency_service.newService(result.tokenService, result.settingsService)
result.currencyService = currency_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.tokenService, result.settingsService
)
result.collectibleService = collectible_service.newService(statusFoundation.events, statusFoundation.threadpool, result.networkService)
result.walletAccountService = wallet_account_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.settingsService, result.accountsService,

View File

@ -27,6 +27,7 @@ type
currentAccountIndex: int
proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[WalletTokenDto]])
proc onCurrencyFormatsUpdated(self: Module)
proc newModule*(
delegate: delegate_interface.AccessInterface,
@ -105,6 +106,9 @@ method load*(self: Module) =
let arg = TokensPerAccountArgs(e)
self.onTokensRebuilt(arg.accountsTokens)
self.events.on(SIGNAL_CURRENCY_FORMATS_UPDATED) do(e:Args):
self.onCurrencyFormatsUpdated()
self.controller.init()
self.view.load()
self.switchAccount(0)
@ -125,5 +129,10 @@ proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[Wall
return
self.setAssets(accountsTokens[walletAccount.address])
proc onCurrencyFormatsUpdated(self: Module) =
# Update assets
let walletAccount = self.controller.getWalletAccount(self.currentAccountIndex)
self.setAssets(walletAccount.tokens)
method findTokenSymbolByAddress*(self: Module, address: string): string =
return self.controller.findTokenSymbolByAddress(address)

View File

@ -141,6 +141,9 @@ method load*(self: Module) =
self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args):
self.refreshWalletAccounts()
self.events.on(SIGNAL_CURRENCY_FORMATS_UPDATED) do(e:Args):
self.refreshWalletAccounts()
self.events.on(SIGNAL_NEW_KEYCARD_SET) do(e: Args):
let args = KeycardActivityArgs(e)
if not args.success:

View File

@ -29,6 +29,7 @@ type
currentAccountIndex: int
proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[WalletTokenDto]])
proc onCurrencyFormatsUpdated(self: Module)
proc newModule*(
delegate: delegate_interface.AccessInterface,
@ -68,6 +69,9 @@ method load*(self: Module) =
self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args):
let arg = TokensPerAccountArgs(e)
self.onTokensRebuilt(arg.accountsTokens)
self.events.on(SIGNAL_CURRENCY_FORMATS_UPDATED) do(e:Args):
self.onCurrencyFormatsUpdated()
self.controller.init()
self.view.load()
@ -153,6 +157,11 @@ proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[Wall
return
self.setAssetsAndBalance(accountsTokens[walletAccount.address])
proc onCurrencyFormatsUpdated(self: Module) =
# Update assets
let walletAccount = self.controller.getWalletAccount(self.currentAccountIndex)
self.setAssetsAndBalance(walletAccount.tokens)
method findTokenSymbolByAddress*(self: Module, address: string): string =
return self.controller.findTokenSymbolByAddress(address)

View File

@ -121,6 +121,8 @@ method load*(self: Module) =
self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args):
self.setTotalCurrencyBalance()
self.view.setTokensLoading(false)
self.events.on(SIGNAL_CURRENCY_FORMATS_UPDATED) do(e:Args):
self.setTotalCurrencyBalance()
self.controller.init()
self.view.load()

View File

@ -84,6 +84,10 @@ proc init*(self: Controller) =
let args = TransactionsLoadedArgs(e)
self.delegate.setHistoryFetchState(args.address, isFetching = false)
self.events.on(SIGNAL_CURRENCY_FORMATS_UPDATED) do(e:Args):
# TODO: Rebuild Transaction items
discard
proc watchPendingTransactions*(self: Controller): seq[TransactionDto] =
return self.transactionService.watchPendingTransactions()

View File

@ -5,6 +5,6 @@ proc currencyAmountToItem*(amount: float64, format: CurrencyFormatDto) : Currenc
return newCurrencyAmount(
amount,
format.symbol,
format.displayDecimals,
int(format.displayDecimals),
format.stripTrailingZeroes
)

View File

@ -17,6 +17,14 @@ template getProp(obj: JsonNode, prop: string, value: var typedesc[int64]): bool
success
template getProp(obj: JsonNode, prop: string, value: var typedesc[uint]): bool =
var success = false
if (obj.kind == JObject and obj.contains(prop)):
value = uint(obj[prop].getInt)
success = true
success
template getProp(obj: JsonNode, prop: string, value: var typedesc[uint64]): bool =
var success = false
if (obj.kind == JObject and obj.contains(prop)):

View File

@ -0,0 +1,16 @@
type
FetchAllCurrencyFormatsTaskArg = ref object of QObjectTaskArg
discard
const fetchAllCurrencyFormatsTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchAllCurrencyFormatsTaskArg](argEncoded)
let output = %* {
"formats": ""
}
try:
let response = backend.fetchAllCurrencyFormats()
output["formats"] = response.result
except Exception as e:
let errDesription = e.msg
error "error fetchAllCurrencyFormatsTaskArg: ", errDesription
arg.finish(output)

View File

@ -1,6 +1,32 @@
import json
include ../../common/json_utils
type
CurrencyFormatDto* = ref object
symbol*: string
displayDecimals*: int
displayDecimals*: uint
stripTrailingZeroes*: bool
proc newCurrencyFormatDto*(
symbol: string,
displayDecimals: uint,
stripTrailingZeroes: bool,
): CurrencyFormatDto =
return CurrencyFormatDto(
symbol: symbol,
displayDecimals: displayDecimals,
stripTrailingZeroes: stripTrailingZeroes
)
proc newCurrencyFormatDto*(symbol: string = ""): CurrencyFormatDto =
return CurrencyFormatDto(
symbol: symbol,
displayDecimals: 8,
stripTrailingZeroes: true
)
proc toCurrencyFormatDto*(jsonObj: JsonNode): CurrencyFormatDto =
result = CurrencyFormatDto()
discard jsonObj.getProp("symbol", result.symbol)
discard jsonObj.getProp("displayDecimals", result.displayDecimals)
discard jsonObj.getProp("stripTrailingZeroes", result.stripTrailingZeroes)

View File

@ -1,79 +1,102 @@
import NimQml, strformat, strutils, tables
import NimQml, strformat, chronicles, strutils, tables, json
import ../../../backend/backend as backend
import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool]
import ../../../app/core/signals/types
import ../settings/service as settings_service
import ../token/service as token_service
import ./dto, ./utils
import ../../common/cache
import ./dto
include ../../common/json_utils
include async_tasks
export dto
const DECIMALS_CALCULATION_CURRENCY = "USD"
# Signals which may be emitted by this service:
const SIGNAL_CURRENCY_FORMATS_UPDATED* = "currencyFormatsUpdated"
type
CurrencyFormatsUpdatedArgs* = ref object of Args
discard
QtObject:
type Service* = ref object of QObject
events: EventEmitter
threadpool: ThreadPool
tokenService: token_service.Service
settingsService: settings_service.Service
isCurrencyFiatCache: Table[string, bool] # Fiat info does not change, we can fetch/calculate once and
fiatCurrencyFormatCache: Table[string, CurrencyFormatDto] # keep the results forever.
tokenCurrencyFormatCache: TimedCache[CurrencyFormatDto] # Token format changes with price, so we use a timed cache.
currencyFormatCache: Table[string, CurrencyFormatDto]
# Forward declarations
proc fetchAllCurrencyFormats(self: Service)
proc getCachedCurrencyFormats(self: Service): Table[string, CurrencyFormatDto]
proc delete*(self: Service) =
self.QObject.delete
proc newService*(
events: EventEmitter,
threadpool: ThreadPool,
tokenService: token_service.Service,
settingsService: settings_service.Service,
): Service =
new(result, delete)
result.QObject.setup
result.events = events
result.threadpool = threadpool
result.tokenService = tokenService
result.settingsService = settingsService
result.tokenCurrencyFormatCache = newTimedCache[CurrencyFormatDto]()
proc init*(self: Service) =
discard
self.events.on(SignalType.Wallet.event) do(e:Args):
var data = WalletSignal(e)
case data.eventType:
of "wallet-currency-tick-update-format":
self.fetchAllCurrencyFormats()
discard
# Load cache from DB
self.currencyFormatCache = self.getCachedCurrencyFormats()
# Trigger async fetch
self.fetchAllCurrencyFormats()
proc isCurrencyFiat(self: Service, symbol: string): bool =
if not self.isCurrencyFiatCache.hasKey(symbol):
self.isCurrencyFiatCache[symbol] = isCurrencyFiat(symbol)
return self.isCurrencyFiatCache[symbol]
proc jsonToFormatsTable(node: JsonNode) : Table[string, CurrencyFormatDto] =
result = initTable[string, CurrencyFormatDto]()
proc getFiatCurrencyFormat(self: Service, symbol: string): CurrencyFormatDto =
if not self.fiatCurrencyFormatCache.hasKey(symbol):
self.fiatCurrencyFormatCache[symbol] = CurrencyFormatDto(
symbol: toUpperAscii(symbol),
displayDecimals: getFiatDisplayDecimals(symbol),
stripTrailingZeroes: false
)
return self.fiatCurrencyFormatCache[symbol]
for (symbol, formatObj) in node.pairs:
result[symbol] = formatObj.toCurrencyFormatDto()
proc getTokenCurrencyFormat(self: Service, symbol: string): CurrencyFormatDto =
if self.tokenCurrencyFormatCache.isCached(symbol):
return self.tokenCurrencyFormatCache.get(symbol)
proc getCachedCurrencyFormats(self: Service): Table[string, CurrencyFormatDto] =
try:
let response = backend.getCachedCurrencyFormats()
result = jsonToFormatsTable(response.result)
except Exception as e:
let errDesription = e.msg
error "error getCachedCurrencyFormats: ", errDesription
var updateCache = true
let pegSymbol = self.tokenService.getTokenPegSymbol(symbol)
if pegSymbol != "":
let currencyFormat = self.getFiatCurrencyFormat(pegSymbol)
result = CurrencyFormatDto(
symbol: symbol,
displayDecimals: currencyFormat.displayDecimals,
stripTrailingZeroes: currencyFormat.stripTrailingZeroes
)
updateCache = true
else:
let price = self.tokenService.getCachedTokenPrice(symbol, DECIMALS_CALCULATION_CURRENCY, true)
result = CurrencyFormatDto(
symbol: symbol,
displayDecimals: getTokenDisplayDecimals(price),
stripTrailingZeroes: true
)
updateCache = self.tokenService.isCachedTokenPriceRecent(symbol, DECIMALS_CALCULATION_CURRENCY)
proc onAllCurrencyFormatsFetched(self: Service, response: string) {.slot.} =
try:
let responseObj = response.parseJson
if (responseObj.kind == JObject):
let formatsPerSymbol = jsonToFormatsTable(responseObj)
for symbol, format in formatsPerSymbol:
self.currencyFormatCache[symbol] = format
self.events.emit(SIGNAL_CURRENCY_FORMATS_UPDATED, CurrencyFormatsUpdatedArgs())
except Exception as e:
let errDescription = e.msg
error "error onAllCurrencyFormatsFetched: ", errDescription
if updateCache:
self.tokenCurrencyFormatCache.set(symbol, result)
proc fetchAllCurrencyFormats(self: Service) =
let arg = FetchAllCurrencyFormatsTaskArg(
tptr: cast[ByteAddress](fetchAllCurrencyFormatsTaskArg),
vptr: cast[ByteAddress](self.vptr),
slot: "onAllCurrencyFormatsFetched",
)
self.threadpool.start(arg)
proc getCurrencyFormat*(self: Service, symbol: string): CurrencyFormatDto =
if self.isCurrencyFiat(symbol):
return self.getFiatCurrencyFormat(symbol)
else:
return self.getTokenCurrencyFormat(symbol)
if not self.currencyFormatCache.hasKey(symbol):
return newCurrencyFormatDto(symbol)
return self.currencyFormatCache[symbol]

View File

@ -1,33 +0,0 @@
import math, chronicles, json, strutils
import ../../../backend/backend as backend
logScope:
topics = "currency-utils"
proc isCurrencyFiat*(symbol: string): bool =
let response = backend.isCurrencyFiat(symbol)
return response.result.getBool
proc getFiatDisplayDecimals*(symbol: string): int =
result = 0
try:
let response = backend.getFiatCurrencyMinorUnit(symbol)
result = response.result.getInt
except Exception as e:
let errDesription = e.msg
error "error getFiatDisplayDecimals: ", errDesription
proc getTokenDisplayDecimals*(currencyPrice: float): int =
var decimals = 0.0
if currencyPrice > 0:
const lowerCurrencyResolution = 0.1
const higherCurrencyResolution = 0.01
let lowerDecimalsBound = max(0.0, log10(currencyPrice) - log10(lowerCurrencyResolution))
let upperDecimalsBound = max(0.0, log10(currencyPrice) - log10(higherCurrencyResolution))
# Use as few decimals as needed to ensure lower precision
decimals = ceil(lowerDecimalsBound)
if (decimals + 1 < upperDecimalsBound):
# If allowed by upper bound, ensure resolution changes as soon as currency hits multiple of 10
decimals += 1
return decimals.int

View File

@ -17,7 +17,7 @@ export settings_dto
export stickers_dto
# Default values:
const DEFAULT_CURRENCY* = "usd"
const DEFAULT_CURRENCY* = "USD"
const DEFAULT_TELEMETRY_SERVER_URL* = "https://telemetry.status.im"
const DEFAULT_FLEET* = $Fleet.StatusProd
@ -106,7 +106,7 @@ QtObject:
if(self.settings.currency.len == 0):
self.settings.currency = DEFAULT_CURRENCY
return self.settings.currency
return self.settings.currency.toUpperAscii()
proc saveDappsAddress*(self: Service, value: string): bool =
if(self.saveSetting(KEY_DAPPS_ADDRESS, value)):

View File

@ -50,7 +50,6 @@ QtObject:
priceCache: TimedCache[float64]
proc updateCachedTokenPrice(self: Service, crypto: string, fiat: string, price: float64)
proc initTokenPrices(self: Service, prices: Table[string, Table[string, float64]])
proc jsonToPricesMap(node: JsonNode): Table[string, Table[string, float64]]
proc delete*(self: Service) =
@ -73,13 +72,6 @@ QtObject:
if(not singletonInstance.localAccountSensitiveSettings.getIsWalletEnabled()):
return
try:
let response = backend.getCachedPrices()
let prices = jsonToPricesMap(response.result)
self.initTokenPrices(prices)
except Exception as e:
error "Cached prices init error", errDesription = e.msg
try:
let networks = self.networkService.getNetworks()
@ -158,14 +150,6 @@ QtObject:
for (currency, price) in pricePerCurrency.pairs:
result[symbol][currency] = price.getFloat
proc initTokenPrices(self: Service, prices: Table[string, Table[string, float64]]) =
var cacheTable: Table[string, float64]
for token, pricesPerCurrency in prices:
for currency, price in pricesPerCurrency:
let cacheKey = getTokenPriceCacheKey(token, currency)
cacheTable[cacheKey] = price
self.priceCache.init(cacheTable)
proc updateTokenPrices*(self: Service, tokens: seq[WalletTokenDto]) =
# Use data fetched by walletAccountService to update local price cache
for token in tokens:

View File

@ -108,9 +108,6 @@ rpc(fetchPrices, "wallet"):
symbols: seq[string]
currencies: seq[string]
rpc(getCachedPrices, "wallet"):
discard
rpc(generateAccountWithDerivedPath, "accounts"):
password: string
name: string
@ -303,8 +300,8 @@ rpc(getBalanceHistory, "wallet"):
currencySymbol: string
timeInterval: int
rpc(isCurrencyFiat, "wallet"):
code: string
rpc(getCachedCurrencyFormats, "wallet"):
discard
rpc(getFiatCurrencyMinorUnit, "wallet"):
code: string
rpc(fetchAllCurrencyFormats, "wallet"):
discard

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit c108b5d0f1d3014d3296f8a47ea8608077f584c9
Subproject commit 1d1a95091df0197199ea502aae24c823faf9b989