feat(@desktop/Wallet): use new status-go currency formatting API
Fixes #9453
This commit is contained in:
parent
b70e1f0fb4
commit
27b8180fdd
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
@ -69,6 +70,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()
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -5,6 +5,6 @@ proc currencyAmountToItem*(amount: float64, format: CurrencyFormatDto) : Currenc
|
|||
return newCurrencyAmount(
|
||||
amount,
|
||||
format.symbol,
|
||||
format.displayDecimals,
|
||||
int(format.displayDecimals),
|
||||
format.stripTrailingZeroes
|
||||
)
|
||||
|
|
|
@ -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)):
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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) =
|
||||
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
|
||||
for (symbol, formatObj) in node.pairs:
|
||||
result[symbol] = formatObj.toCurrencyFormatDto()
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
proc fetchAllCurrencyFormats(self: Service) =
|
||||
let arg = FetchAllCurrencyFormatsTaskArg(
|
||||
tptr: cast[ByteAddress](fetchAllCurrencyFormatsTaskArg),
|
||||
vptr: cast[ByteAddress](self.vptr),
|
||||
slot: "onAllCurrencyFormatsFetched",
|
||||
)
|
||||
return self.fiatCurrencyFormatCache[symbol]
|
||||
|
||||
proc getTokenCurrencyFormat(self: Service, symbol: string): CurrencyFormatDto =
|
||||
if self.tokenCurrencyFormatCache.isCached(symbol):
|
||||
return self.tokenCurrencyFormatCache.get(symbol)
|
||||
|
||||
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)
|
||||
|
||||
if updateCache:
|
||||
self.tokenCurrencyFormatCache.set(symbol, result)
|
||||
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]
|
|
@ -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
|
|
@ -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)):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit c108b5d0f1d3014d3296f8a47ea8608077f584c9
|
||||
Subproject commit 1d1a95091df0197199ea502aae24c823faf9b989
|
Loading…
Reference in New Issue