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( result.tokenService = token_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.networkService 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.collectibleService = collectible_service.newService(statusFoundation.events, statusFoundation.threadpool, result.networkService)
result.walletAccountService = wallet_account_service.newService( result.walletAccountService = wallet_account_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.settingsService, result.accountsService, statusFoundation.events, statusFoundation.threadpool, result.settingsService, result.accountsService,

View File

@ -27,6 +27,7 @@ type
currentAccountIndex: int currentAccountIndex: int
proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[WalletTokenDto]]) proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[WalletTokenDto]])
proc onCurrencyFormatsUpdated(self: Module)
proc newModule*( proc newModule*(
delegate: delegate_interface.AccessInterface, delegate: delegate_interface.AccessInterface,
@ -105,6 +106,9 @@ method load*(self: Module) =
let arg = TokensPerAccountArgs(e) let arg = TokensPerAccountArgs(e)
self.onTokensRebuilt(arg.accountsTokens) self.onTokensRebuilt(arg.accountsTokens)
self.events.on(SIGNAL_CURRENCY_FORMATS_UPDATED) do(e:Args):
self.onCurrencyFormatsUpdated()
self.controller.init() self.controller.init()
self.view.load() self.view.load()
self.switchAccount(0) self.switchAccount(0)
@ -125,5 +129,10 @@ proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[Wall
return return
self.setAssets(accountsTokens[walletAccount.address]) 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 = method findTokenSymbolByAddress*(self: Module, address: string): string =
return self.controller.findTokenSymbolByAddress(address) 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.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args):
self.refreshWalletAccounts() self.refreshWalletAccounts()
self.events.on(SIGNAL_CURRENCY_FORMATS_UPDATED) do(e:Args):
self.refreshWalletAccounts()
self.events.on(SIGNAL_NEW_KEYCARD_SET) do(e: Args): self.events.on(SIGNAL_NEW_KEYCARD_SET) do(e: Args):
let args = KeycardActivityArgs(e) let args = KeycardActivityArgs(e)
if not args.success: if not args.success:

View File

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

View File

@ -84,6 +84,10 @@ proc init*(self: Controller) =
let args = TransactionsLoadedArgs(e) let args = TransactionsLoadedArgs(e)
self.delegate.setHistoryFetchState(args.address, isFetching = false) 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] = proc watchPendingTransactions*(self: Controller): seq[TransactionDto] =
return self.transactionService.watchPendingTransactions() return self.transactionService.watchPendingTransactions()

View File

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

View File

@ -17,6 +17,14 @@ template getProp(obj: JsonNode, prop: string, value: var typedesc[int64]): bool
success 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 = template getProp(obj: JsonNode, prop: string, value: var typedesc[uint64]): bool =
var success = false var success = false
if (obj.kind == JObject and obj.contains(prop)): 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 type
CurrencyFormatDto* = ref object CurrencyFormatDto* = ref object
symbol*: string symbol*: string
displayDecimals*: int displayDecimals*: uint
stripTrailingZeroes*: bool 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 ../settings/service as settings_service
import ../token/service as token_service import ../token/service as token_service
import ./dto, ./utils import ./dto
import ../../common/cache
include ../../common/json_utils
include async_tasks
export dto 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: QtObject:
type Service* = ref object of QObject type Service* = ref object of QObject
events: EventEmitter
threadpool: ThreadPool
tokenService: token_service.Service tokenService: token_service.Service
settingsService: settings_service.Service settingsService: settings_service.Service
isCurrencyFiatCache: Table[string, bool] # Fiat info does not change, we can fetch/calculate once and currencyFormatCache: Table[string, CurrencyFormatDto]
fiatCurrencyFormatCache: Table[string, CurrencyFormatDto] # keep the results forever.
tokenCurrencyFormatCache: TimedCache[CurrencyFormatDto] # Token format changes with price, so we use a timed cache. # Forward declarations
proc fetchAllCurrencyFormats(self: Service)
proc getCachedCurrencyFormats(self: Service): Table[string, CurrencyFormatDto]
proc delete*(self: Service) = proc delete*(self: Service) =
self.QObject.delete self.QObject.delete
proc newService*( proc newService*(
events: EventEmitter,
threadpool: ThreadPool,
tokenService: token_service.Service, tokenService: token_service.Service,
settingsService: settings_service.Service, settingsService: settings_service.Service,
): Service = ): Service =
new(result, delete) new(result, delete)
result.QObject.setup result.QObject.setup
result.events = events
result.threadpool = threadpool
result.tokenService = tokenService result.tokenService = tokenService
result.settingsService = settingsService result.settingsService = settingsService
result.tokenCurrencyFormatCache = newTimedCache[CurrencyFormatDto]()
proc init*(self: Service) = 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 = proc jsonToFormatsTable(node: JsonNode) : Table[string, CurrencyFormatDto] =
if not self.isCurrencyFiatCache.hasKey(symbol): result = initTable[string, CurrencyFormatDto]()
self.isCurrencyFiatCache[symbol] = isCurrencyFiat(symbol)
return self.isCurrencyFiatCache[symbol]
proc getFiatCurrencyFormat(self: Service, symbol: string): CurrencyFormatDto = for (symbol, formatObj) in node.pairs:
if not self.fiatCurrencyFormatCache.hasKey(symbol): result[symbol] = formatObj.toCurrencyFormatDto()
self.fiatCurrencyFormatCache[symbol] = CurrencyFormatDto(
symbol: toUpperAscii(symbol),
displayDecimals: getFiatDisplayDecimals(symbol),
stripTrailingZeroes: false
)
return self.fiatCurrencyFormatCache[symbol]
proc getTokenCurrencyFormat(self: Service, symbol: string): CurrencyFormatDto = proc getCachedCurrencyFormats(self: Service): Table[string, CurrencyFormatDto] =
if self.tokenCurrencyFormatCache.isCached(symbol): try:
return self.tokenCurrencyFormatCache.get(symbol) let response = backend.getCachedCurrencyFormats()
result = jsonToFormatsTable(response.result)
except Exception as e:
let errDesription = e.msg
error "error getCachedCurrencyFormats: ", errDesription
var updateCache = true proc onAllCurrencyFormatsFetched(self: Service, response: string) {.slot.} =
let pegSymbol = self.tokenService.getTokenPegSymbol(symbol) try:
if pegSymbol != "": let responseObj = response.parseJson
let currencyFormat = self.getFiatCurrencyFormat(pegSymbol) if (responseObj.kind == JObject):
result = CurrencyFormatDto( let formatsPerSymbol = jsonToFormatsTable(responseObj)
symbol: symbol, for symbol, format in formatsPerSymbol:
displayDecimals: currencyFormat.displayDecimals, self.currencyFormatCache[symbol] = format
stripTrailingZeroes: currencyFormat.stripTrailingZeroes self.events.emit(SIGNAL_CURRENCY_FORMATS_UPDATED, CurrencyFormatsUpdatedArgs())
) except Exception as e:
updateCache = true let errDescription = e.msg
else: error "error onAllCurrencyFormatsFetched: ", errDescription
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)
if updateCache: proc fetchAllCurrencyFormats(self: Service) =
self.tokenCurrencyFormatCache.set(symbol, result) 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 = proc getCurrencyFormat*(self: Service, symbol: string): CurrencyFormatDto =
if self.isCurrencyFiat(symbol): if not self.currencyFormatCache.hasKey(symbol):
return self.getFiatCurrencyFormat(symbol) return newCurrencyFormatDto(symbol)
else: return self.currencyFormatCache[symbol]
return self.getTokenCurrencyFormat(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 export stickers_dto
# Default values: # Default values:
const DEFAULT_CURRENCY* = "usd" const DEFAULT_CURRENCY* = "USD"
const DEFAULT_TELEMETRY_SERVER_URL* = "https://telemetry.status.im" const DEFAULT_TELEMETRY_SERVER_URL* = "https://telemetry.status.im"
const DEFAULT_FLEET* = $Fleet.StatusProd const DEFAULT_FLEET* = $Fleet.StatusProd
@ -106,7 +106,7 @@ QtObject:
if(self.settings.currency.len == 0): if(self.settings.currency.len == 0):
self.settings.currency = DEFAULT_CURRENCY self.settings.currency = DEFAULT_CURRENCY
return self.settings.currency return self.settings.currency.toUpperAscii()
proc saveDappsAddress*(self: Service, value: string): bool = proc saveDappsAddress*(self: Service, value: string): bool =
if(self.saveSetting(KEY_DAPPS_ADDRESS, value)): if(self.saveSetting(KEY_DAPPS_ADDRESS, value)):

View File

@ -50,7 +50,6 @@ QtObject:
priceCache: TimedCache[float64] priceCache: TimedCache[float64]
proc updateCachedTokenPrice(self: Service, crypto: string, fiat: string, price: 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 jsonToPricesMap(node: JsonNode): Table[string, Table[string, float64]]
proc delete*(self: Service) = proc delete*(self: Service) =
@ -73,13 +72,6 @@ QtObject:
if(not singletonInstance.localAccountSensitiveSettings.getIsWalletEnabled()): if(not singletonInstance.localAccountSensitiveSettings.getIsWalletEnabled()):
return 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: try:
let networks = self.networkService.getNetworks() let networks = self.networkService.getNetworks()
@ -158,14 +150,6 @@ QtObject:
for (currency, price) in pricePerCurrency.pairs: for (currency, price) in pricePerCurrency.pairs:
result[symbol][currency] = price.getFloat 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]) = proc updateTokenPrices*(self: Service, tokens: seq[WalletTokenDto]) =
# Use data fetched by walletAccountService to update local price cache # Use data fetched by walletAccountService to update local price cache
for token in tokens: for token in tokens:

View File

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

2
vendor/status-go vendored

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