feat(@desktop/wallet): Add new go api to get token market values not bundled with token balances

fixes #12668
This commit is contained in:
Khushboo Mehta 2023-11-13 13:47:04 +01:00 committed by Khushboo-dev-cpp
parent 94159746ea
commit 3a41a81890
14 changed files with 653 additions and 151 deletions

View File

@ -175,7 +175,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
)
result.chatService = chat_service.newService(statusFoundation.events, statusFoundation.threadpool, result.contactsService)
result.tokenService = token_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.networkService
statusFoundation.events, statusFoundation.threadpool, result.networkService, result.settingsService
)
result.currencyService = currency_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.tokenService, result.settingsService
@ -227,7 +227,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.tokensService = tokens_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.transactionService, result.tokenService, result.settingsService, result.walletAccountService, result.activityCenterService, result.communityService)
result.providerService = provider_service.newService(statusFoundation.events, statusFoundation.threadpool, result.ensService)
result.networkConnectionService = network_connection_service.newService(statusFoundation.events, result.walletAccountService, result.networkService, result.nodeService)
result.networkConnectionService = network_connection_service.newService(statusFoundation.events,
result.walletAccountService, result.networkService, result.nodeService, result.tokenService)
result.sharedUrlsService = shared_urls_service.newService(statusFoundation.events, statusFoundation.threadpool)
# Modules
result.startupModule = startup_module.newModule[AppController](

View File

@ -1,8 +1,9 @@
import ./io_interface
import ../../../../core/eventemitter
import ../../../../../app_service/service/token/service as token_service
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import app/core/eventemitter
import app_service/service/token/service as token_service
import app_service/service/wallet_account/service as wallet_account_service
import app_service/service/currency/dto
type
Controller* = ref object of RootObj
@ -52,3 +53,24 @@ proc getFlatTokensList*(self: Controller): var seq[TokenItem] =
proc getTokenBySymbolList*(self: Controller): var seq[TokenBySymbolItem] =
return self.tokenService.getTokenBySymbolList()
proc getTokenDetails*(self: Controller, symbol: string): TokenDetailsItem =
return self.tokenService.getTokenDetails(symbol)
proc getMarketValuesBySymbol*(self: Controller, symbol: string): TokenMarketValuesItem =
return self.tokenService.getMarketValuesBySymbol(symbol)
proc getPriceBySymbol*(self: Controller, symbol: string): float64 =
return self.tokenService.getPriceBySymbol(symbol)
proc getCurrentCurrencyFormat*(self: Controller): CurrencyFormatDto =
return self.walletAccountService.getCurrencyFormat(self.tokenService.getCurrency())
proc rebuildMarketData*(self: Controller) =
self.tokenService.rebuildMarketData()
proc getTokensDetailsLoading*(self: Controller): bool =
self.tokenService.getTokensDetailsLoading()
proc getTokensMarketValuesLoading*(self: Controller): bool =
self.tokenService.getTokensMarketValuesLoading()

View File

@ -1,6 +1,6 @@
import NimQml, Tables, strutils
import ./io_interface
import ./io_interface, ./market_details_item
const SOURCES_DELIMITER = ";"
@ -28,22 +28,31 @@ type
# properties below this are optional and may not exist in case of community minted assets
# built from chainId and address using networks service
WebsiteUrl
MarketValues
MarketDetails
DetailsLoading
MarketDetailsLoading
QtObject:
type FlatTokensModel* = ref object of QAbstractListModel
delegate: io_interface.FlatTokenModelDataSource
marketValuesDelegate: io_interface.TokenMarketValuesDataSource
tokenMarketDetails: seq[MarketDetailsItem]
proc setup(self: FlatTokensModel) =
self.QAbstractListModel.setup
self.tokenMarketDetails = @[]
proc delete(self: FlatTokensModel) =
self.QAbstractListModel.delete
proc newFlatTokensModel*(delegate: io_interface.FlatTokenModelDataSource): FlatTokensModel =
proc newFlatTokensModel*(
delegate: io_interface.FlatTokenModelDataSource,
marketValuesDelegate: io_interface.TokenMarketValuesDataSource
): FlatTokensModel =
new(result, delete)
result.setup
result.delegate = delegate
result.marketValuesDelegate = marketValuesDelegate
method rowCount(self: FlatTokensModel, index: QModelIndex = nil): int =
return self.delegate.getFlatTokensList().len
@ -69,7 +78,9 @@ QtObject:
ModelRole.CommunityId.int:"communityId",
ModelRole.Description.int:"description",
ModelRole.WebsiteUrl.int:"websiteUrl",
ModelRole.MarketValues.int:"marketValues",
ModelRole.MarketDetails.int:"marketDetails",
ModelRole.DetailsLoading.int:"detailsLoading",
ModelRole.MarketDetailsLoading.int:"marketDetailsLoading",
}.toTable
method data(self: FlatTokensModel, index: QModelIndex, role: int): QVariant =
@ -101,16 +112,43 @@ QtObject:
result = newQVariant(ord(item.`type`))
of ModelRole.CommunityId:
result = newQVariant(item.communityId)
# ToDo fetching of market values not done yet
of ModelRole.Description:
result = newQVariant("")
let tokenDetails = self.delegate.getTokenDetails(item.symbol)
result = if not tokenDetails.isNil: newQVariant(tokenDetails.description)
else: newQVariant("")
of ModelRole.WebsiteUrl:
result = newQVariant("")
of ModelRole.MarketValues:
result = newQVariant("")
let tokenDetails = self.delegate.getTokenDetails(item.symbol)
result = if not tokenDetails.isNil: newQVariant(tokenDetails.assetWebsiteUrl)
else: newQVariant("")
of ModelRole.MarketDetails:
result = newQVariant(self.tokenMarketDetails[index.row])
of ModelRole.DetailsLoading:
result = newQVariant(self.delegate.getTokensDetailsLoading())
of ModelRole.MarketDetailsLoading:
result = newQVariant(self.delegate.getTokensMarketValuesLoading())
proc modelsAboutToUpdate*(self: FlatTokensModel) =
self.beginResetModel()
self.beginResetModel()
proc modelsUpdated*(self: FlatTokensModel) =
self.endResetModel()
self.tokenMarketDetails = @[]
for token in self.delegate.getFlatTokensList():
self.tokenMarketDetails.add(newMarketDetailsItem(self.marketValuesDelegate, token.symbol))
self.endResetModel()
proc tokensMarketValuesUpdated*(self: FlatTokensModel) =
for i in countup(0, self.rowCount()):
let index = self.createIndex(i, 0, nil)
defer: index.delete
self.dataChanged(index, index, @[ModelRole.MarketDetails.int, ModelRole.MarketDetailsLoading.int])
proc tokensDetailsUpdated*(self: FlatTokensModel) =
for i in countup(0, self.rowCount()):
let index = self.createIndex(i, 0, nil)
defer: index.delete
self.dataChanged(index, index, @[ModelRole.Description.int, ModelRole.WebsiteUrl.int, ModelRole.DetailsLoading.int])
proc currencyFormatsUpdated*(self: FlatTokensModel) =
for mD in self.tokenMarketDetails:
mD.updateCurrencyFormat()

View File

@ -1,4 +1,5 @@
import app_service/service/token/service_items
import app_service/service/currency/dto
type
SourcesOfTokensModelDataSource* = tuple[
@ -6,11 +7,23 @@ type
]
type
FlatTokenModelDataSource* = tuple[
getFlatTokensList: proc(): var seq[TokenItem]
getFlatTokensList: proc(): var seq[TokenItem],
getTokenDetails: proc(symbol: string): TokenDetailsItem,
getTokensDetailsLoading: proc(): bool,
getTokensMarketValuesLoading: proc(): bool,
]
type
TokenBySymbolModelDataSource* = tuple[
getTokenBySymbolList: proc(): var seq[TokenBySymbolItem]
getTokenBySymbolList: proc(): var seq[TokenBySymbolItem],
getTokenDetails: proc(symbol: string): TokenDetailsItem,
getTokensDetailsLoading: proc(): bool,
getTokensMarketValuesLoading: proc(): bool,
]
type
TokenMarketValuesDataSource* = tuple[
getMarketValuesBySymbol: proc(symbol: string): TokenMarketValuesItem,
getPriceBySymbol: proc(symbol: string): float64,
getCurrentCurrencyFormat: proc(): CurrencyFormatDto,
]
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -49,6 +62,9 @@ method getFlatTokenModelDataSource*(self: AccessInterface): FlatTokenModelDataSo
method getTokenBySymbolModelDataSource*(self: AccessInterface): TokenBySymbolModelDataSource {.base.} =
raise newException(ValueError, "No implementation available")
method getTokenMarketValuesDataSource*(self: AccessInterface): TokenMarketValuesDataSource {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim.

View File

@ -0,0 +1,86 @@
import NimQml
import ./io_interface
import app/modules/shared_models/currency_amount
import app/modules/shared/wallet_utils
import app_service/service/currency/dto
QtObject:
type MarketDetailsItem* = ref object of QObject
delegate: io_interface.TokenMarketValuesDataSource
currencyFormat: CurrencyFormatDto
symbol: string
proc setup*(self: MarketDetailsItem) =
self.QObject.setup
proc delete*(self: MarketDetailsItem) =
self.QObject.delete
proc newMarketDetailsItem*(
delegate: io_interface.TokenMarketValuesDataSource, symbol: string): MarketDetailsItem =
new(result)
result.setup()
result.delegate = delegate
result.symbol = symbol
result.currencyFormat = delegate.getCurrentCurrencyFormat()
proc updateCurrencyFormat*(self: MarketDetailsItem) =
self.currencyFormat = self.delegate.getCurrentCurrencyFormat()
proc marketCapChanged*(self: MarketDetailsItem) {.signal.}
proc marketCap*(self: MarketDetailsItem): QVariant {.slot.} =
return newQVariant(self.delegate.getMarketValuesBySymbol(self.symbol).marketCap)
QtProperty[QVariant] marketCap:
read = marketCap
notify = marketCapChanged
proc highDayChanged*(self: MarketDetailsItem) {.signal.}
proc highDay*(self: MarketDetailsItem): QVariant {.slot.} =
return newQVariant(self.delegate.getMarketValuesBySymbol(self.symbol).highDay)
QtProperty[QVariant] highDay:
read = highDay
notify = highDayChanged
proc lowDayChanged*(self: MarketDetailsItem) {.signal.}
proc lowDay*(self: MarketDetailsItem): QVariant {.slot.} =
return newQVariant(self.delegate.getMarketValuesBySymbol(self.symbol).lowDay)
QtProperty[QVariant] lowDay:
read = lowDay
notify = lowDayChanged
proc changePctHourChanged*(self: MarketDetailsItem) {.signal.}
proc changePctHour*(self: MarketDetailsItem): QVariant {.slot.} =
return newQVariant(self.delegate.getMarketValuesBySymbol(self.symbol).changePctHour)
QtProperty[QVariant] changePctHour:
read = changePctHour
notify = changePctHourChanged
proc changePctDayChanged*(self: MarketDetailsItem) {.signal.}
proc changePctDay*(self: MarketDetailsItem): QVariant {.slot.} =
return newQVariant(self.delegate.getMarketValuesBySymbol(self.symbol).changePctDay)
QtProperty[QVariant] changePctDay:
read = changePctDay
notify = changePctDayChanged
proc changePct24hourChanged*(self: MarketDetailsItem) {.signal.}
proc changePct24hour*(self: MarketDetailsItem): QVariant {.slot.} =
return newQVariant(self.delegate.getMarketValuesBySymbol(self.symbol).changePct24hour)
QtProperty[QVariant] changePct24hour:
read = changePct24hour
notify = changePct24hourChanged
proc change24hourChanged*(self: MarketDetailsItem) {.signal.}
proc change24hour*(self: MarketDetailsItem): QVariant {.slot.} =
return newQVariant(self.delegate.getMarketValuesBySymbol(self.symbol).change24hour)
QtProperty[QVariant] change24hour:
read = change24hour
notify = change24hourChanged
proc currencyPriceChanged*(self: MarketDetailsItem) {.signal.}
proc currencyPrice*(self: MarketDetailsItem): QVariant {.slot.} =
let price = self.delegate.getPriceBySymbol(self.symbol)
return newQVariant(currencyAmountToItem(price, self.currencyFormat))
QtProperty[QVariant] currencyPrice:
read = currencyPrice
notify = currencyPriceChanged

View File

@ -3,11 +3,13 @@ import NimQml
import ./io_interface, ./view, ./controller
import ../io_interface as delegate_interface
import ../../../../global/global_singleton
import ../../../../core/eventemitter
import ../../../../../app_service/service/token/service as token_service
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../app_service/service/token/dto
import app/global/global_singleton
import app/core/eventemitter
import app_service/service/token/service as token_service
import app_service/service/wallet_account/service as wallet_account_service
import app_service/service/token/dto
import app_service/service/currency/service
import app_service/service/settings/service
export io_interface
@ -41,11 +43,25 @@ method delete*(self: Module) =
method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("walletSectionAllTokens", newQVariant(self.view))
self.events.on(SIGNAL_CURRENCY_UPDATED) do(e:Args):
self.controller.rebuildMarketData()
self.events.on(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED) do(e:Args):
self.controller.rebuildMarketData()
# Passing on the events for changes in model to abstract model
self.events.on(SIGNAL_TOKENS_LIST_ABOUT_TO_BE_UPDATED) do(e: Args):
self.view.modelsAboutToUpdate()
self.events.on(SIGNAL_TOKENS_LIST_UPDATED) do(e: Args):
self.view.modelsUpdated()
self.events.on(SIGNAL_TOKENS_DETAILS_UPDATED) do(e: Args):
self.view.tokensDetailsUpdated()
self.events.on(SIGNAL_TOKENS_MARKET_VALUES_UPDATED) do(e: Args):
self.view.tokensMarketValuesUpdated()
self.events.on(SIGNAL_TOKENS_PRICES_UPDATED) do(e: Args):
self.view.tokensMarketValuesUpdated()
self.events.on(SIGNAL_CURRENCY_FORMATS_UPDATED) do(e:Args):
self.view.currencyFormatsUpdated()
self.controller.init()
self.view.load()
@ -77,30 +93,34 @@ method fetchHistoricalBalanceForTokenAsJson*(self: Module, address: string, allA
method tokenBalanceHistoryDataResolved*(self: Module, balanceHistoryJson: string) =
self.view.setTokenBalanceHistoryDataReady(balanceHistoryJson)
proc getFlatTokensList*(self: Module): var seq[TokenItem] =
return self.controller.getFlatTokensList()
proc getTokenBySymbolList*(self: Module): var seq[TokenBySymbolItem] =
return self.controller.getTokenBySymbolList()
proc getSourcesOfTokensList*(self: Module): var seq[SupportedSourcesItem] =
return self.controller.getSourcesOfTokensList()
# Interfaces for getting lists from the service files into the abstract models
method getSourcesOfTokensModelDataSource*(self: Module): SourcesOfTokensModelDataSource =
return (
getSourcesOfTokensList: proc(): var seq[SupportedSourcesItem] = self.getSourcesOfTokensList()
getSourcesOfTokensList: proc(): var seq[SupportedSourcesItem] = self.controller.getSourcesOfTokensList()
)
method getFlatTokenModelDataSource*(self: Module): FlatTokenModelDataSource =
return (
getFlatTokensList: proc(): var seq[TokenItem] = self.getFlatTokensList()
getFlatTokensList: proc(): var seq[TokenItem] = self.controller.getFlatTokensList(),
getTokenDetails: proc(symbol: string): TokenDetailsItem = self.controller.getTokenDetails(symbol),
getTokensDetailsLoading: proc(): bool = self.controller.getTokensDetailsLoading(),
getTokensMarketValuesLoading: proc(): bool = self.controller.getTokensMarketValuesLoading()
)
method getTokenBySymbolModelDataSource*(self: Module): TokenBySymbolModelDataSource =
return (
getTokenBySymbolList: proc(): var seq[TokenBySymbolItem] = self.getTokenBySymbolList()
getTokenBySymbolList: proc(): var seq[TokenBySymbolItem] = self.controller.getTokenBySymbolList(),
getTokenDetails: proc(symbol: string): TokenDetailsItem = self.controller.getTokenDetails(symbol),
getTokensDetailsLoading: proc(): bool = self.controller.getTokensDetailsLoading(),
getTokensMarketValuesLoading: proc(): bool = self.controller.getTokensMarketValuesLoading()
)
method getTokenMarketValuesDataSource*(self: Module): TokenMarketValuesDataSource =
return (
getMarketValuesBySymbol: proc(symbol: string): TokenMarketValuesItem = self.controller.getMarketValuesBySymbol(symbol),
getPriceBySymbol: proc(symbol: string): float64 = self.controller.getPriceBySymbol(symbol),
getCurrentCurrencyFormat: proc(): CurrencyFormatDto = self.controller.getCurrentCurrencyFormat()
)
method filterChanged*(self: Module, addresses: seq[string]) =

View File

@ -1,6 +1,6 @@
import NimQml, Tables, strutils
import ./io_interface, ./address_per_chain_model
import ./io_interface, ./address_per_chain_model, ./market_details_item
const SOURCES_DELIMITER = ";"
@ -27,24 +27,31 @@ type
# properties below this are optional and may not exist in case of community minted assets
# built from chainId and address using networks service
WebsiteUrl
MarketValues
MarketDetails
QtObject:
type TokensBySymbolModel* = ref object of QAbstractListModel
delegate: io_interface.TokenBySymbolModelDataSource
marketValuesDelegate: io_interface.TokenMarketValuesDataSource
addressPerChainModel: seq[AddressPerChainModel]
tokenMarketDetails: seq[MarketDetailsItem]
proc setup(self: TokensBySymbolModel) =
self.QAbstractListModel.setup
self.addressPerChainModel = @[]
self.tokenMarketDetails = @[]
proc delete(self: TokensBySymbolModel) =
self.QAbstractListModel.delete
proc newTokensBySymbolModel*(delegate: io_interface.TokenBySymbolModelDataSource): TokensBySymbolModel =
proc newTokensBySymbolModel*(
delegate: io_interface.TokenBySymbolModelDataSource,
marketValuesDelegate: io_interface.TokenMarketValuesDataSource
): TokensBySymbolModel =
new(result, delete)
result.setup
result.delegate = delegate
result.marketValuesDelegate = marketValuesDelegate
method rowCount(self: TokensBySymbolModel, index: QModelIndex = nil): int =
return self.delegate.getTokenBySymbolList().len
@ -69,7 +76,7 @@ QtObject:
ModelRole.CommunityId.int:"communityId",
ModelRole.Description.int:"description",
ModelRole.WebsiteUrl.int:"websiteUrl",
ModelRole.MarketValues.int:"marketValues",
ModelRole.MarketDetails.int:"marketDetails",
}.toTable
method data(self: TokensBySymbolModel, index: QModelIndex, role: int): QVariant =
@ -99,13 +106,16 @@ QtObject:
result = newQVariant(ord(item.`type`))
of ModelRole.CommunityId:
result = newQVariant(item.communityId)
# ToDo fetching of market values not done yet
of ModelRole.Description:
result = newQVariant("")
let tokenDetails = self.delegate.getTokenDetails(item.symbol)
result = if not tokenDetails.isNil: newQVariant(tokenDetails.description)
else: newQVariant("")
of ModelRole.WebsiteUrl:
result = newQVariant("")
of ModelRole.MarketValues:
result = newQVariant("")
let tokenDetails = self.delegate.getTokenDetails(item.symbol)
result = if not tokenDetails.isNil: newQVariant(tokenDetails.assetWebsiteUrl)
else: newQVariant("")
of ModelRole.MarketDetails:
result = newQVariant(self.tokenMarketDetails[index.row])
proc modelsAboutToUpdate*(self: TokensBySymbolModel) =
self.beginResetModel()
@ -113,5 +123,27 @@ QtObject:
proc modelsUpdated*(self: TokensBySymbolModel) =
self.addressPerChainModel = @[]
for index in countup(0, self.delegate.getTokenBySymbolList().len):
self.addressPerChainModel.add(newAddressPerChainModel(self.delegate,index))
self.addressPerChainModel.add(newAddressPerChainModel(self.delegate, index))
self.tokenMarketDetails = @[]
for token in self.delegate.getTokenBySymbolList():
if token.communityId.isEmptyOrWhitespace:
self.tokenMarketDetails.add(newMarketDetailsItem(self.marketValuesDelegate, token.symbol))
self.endResetModel()
proc tokensMarketValuesUpdated*(self: TokensBySymbolModel) =
for i in countup(0, self.rowCount()):
let index = self.createIndex(i, 0, nil)
defer: index.delete
self.dataChanged(index, index, @[ModelRole.MarketDetails.int])
proc tokensDetailsUpdated*(self: TokensBySymbolModel) =
for i in countup(0, self.rowCount()):
let index = self.createIndex(i, 0, nil)
defer: index.delete
self.dataChanged(index, index, @[ModelRole.Description.int, ModelRole.WebsiteUrl.int])
proc currencyFormatsUpdated*(self: TokensBySymbolModel) =
for mD in self.tokenMarketDetails:
mD.updateCurrencyFormat()

View File

@ -29,8 +29,12 @@ QtObject:
result.marketHistoryIsLoading = false
result.balanceHistoryIsLoading = false
result.sourcesOfTokensModel = newSourcesOfTokensModel(delegate.getSourcesOfTokensModelDataSource())
result.flatTokensModel = newFlatTokensModel(delegate.getFlatTokenModelDataSource())
result.tokensBySymbolModel = newTokensBySymbolModel(delegate.getTokenBySymbolModelDataSource())
result.flatTokensModel = newFlatTokensModel(
delegate.getFlatTokenModelDataSource(),
delegate.getTokenMarketValuesDataSource())
result.tokensBySymbolModel = newTokensBySymbolModel(
delegate.getTokenBySymbolModelDataSource(),
delegate.getTokenMarketValuesDataSource())
proc load*(self: View) =
self.delegate.viewDidLoad()
@ -112,3 +116,15 @@ QtObject:
self.sourcesOfTokensModel.modelsUpdated()
self.flatTokensModel.modelsUpdated()
self.tokensBySymbolModel.modelsUpdated()
proc tokensMarketValuesUpdated*(self: View) =
self.flatTokensModel.tokensMarketValuesUpdated()
self.tokensBySymbolModel.tokensMarketValuesUpdated()
proc tokensDetailsUpdated*(self: View) =
self.flatTokensModel.tokensDetailsUpdated()
self.tokensBySymbolModel.tokensDetailsUpdated()
proc currencyFormatsUpdated*(self: View) =
self.flatTokensModel.currencyFormatsUpdated()
self.tokensBySymbolModel.currencyFormatsUpdated()

View File

@ -4,10 +4,11 @@ import ../../../app/global/global_singleton
import ../../../app/core/eventemitter
import ../../../app/core/signals/types
import ../wallet_account/service as wallet_service
import ../network/service as network_service
import ../node/service as node_service
import app_service/service/wallet_account/service as wallet_service
import app_service/service/network/service as network_service
import app_service/service/node/service as node_service
import backend/connection_status as connection_status_backend
import app_service/service/token/service as token_service
import backend/collectibles as collectibles_backend
logScope:
@ -54,6 +55,7 @@ QtObject:
walletService: wallet_service.Service
networkService: network_service.Service
nodeService: node_service.Service
tokenService: token_service.Service
connectionStatus: Table[string, ConnectionStatus]
# Forward declaration
@ -72,6 +74,7 @@ QtObject:
walletService: wallet_service.Service,
networkService: network_service.Service,
nodeService: node_service.Service,
tokenService: token_service.Service
): Service =
new(result, delete)
result.QObject.setup
@ -80,6 +83,7 @@ QtObject:
result.walletService = walletService
result.networkService = networkService
result.nodeService = nodeService
result.tokenService = tokenService
result.connectionStatus = {BLOCKCHAINS: newConnectionStatus(),
MARKET: newConnectionStatus(),
COLLECTIBLES: newConnectionStatus()}.toTable
@ -225,7 +229,9 @@ QtObject:
if(self.connectionStatus.hasKey(MARKET)):
self.connectionStatus[MARKET].connectionState = ConnectionState.Retrying
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(MARKET, self.connectionStatus[MARKET]))
# TODO: remove once market values are removed from tokenService
self.walletService.reloadAccountTokens()
self.tokenService.rebuildMarketData()
proc collectiblesRetry*(self: Service) {.slot.} =
if(self.connectionStatus.hasKey(COLLECTIBLES)):
@ -236,6 +242,7 @@ QtObject:
proc networkConnected*(self: Service, connected: bool) =
if connected:
self.walletService.reloadAccountTokens()
self.tokenService.rebuildMarketData()
discard collectibles_backend.refetchOwnedCollectibles()
else:
if(self.connectionStatus.hasKey(BLOCKCHAINS)):
@ -249,4 +256,3 @@ QtObject:
if self.connectionStatus.hasKey(website) and self.connectionStatus[website].completelyDown:
return false
return true

View File

@ -1,4 +1,4 @@
import times
import times, strformat
import backend/backend as backend
include app_service/common/json_utils
@ -9,25 +9,71 @@ include app_service/common/json_utils
const DAYS_IN_WEEK = 7
const HOURS_IN_DAY = 24
type
GetTokenListTaskArg = ref object of QObjectTaskArg
const getSupportedTokenList*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[GetTokenListTaskArg](argEncoded)
var response: RpcResponse[JsonNode]
let arg = decode[QObjectTaskArg](argEncoded)
var output = %*{
"supportedTokensJson": "",
"error": ""
}
try:
response = backend.getTokenList()
let output = %* {
"supportedTokensJson": response,
"error": ""
}
arg.finish(output)
let response = backend.getTokenList()
output["supportedTokensJson"] = %*response
except Exception as e:
let output = %* {
"supportedTokensJson": response,
"error": e.msg
}
arg.finish(output)
output["error"] = %* fmt"Error fetching supported tokens: {e.msg}"
arg.finish(output)
type
FetchTokensMarketValuesTaskArg = ref object of QObjectTaskArg
symbols: seq[string]
currency: string
const fetchTokensMarketValuesTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchTokensMarketValuesTaskArg](argEncoded)
var output = %*{
"tokenMarketValues": "",
"error": ""
}
try:
let response = backend.fetchMarketValues(arg.symbols, arg.currency)
output["tokenMarketValues"] = %*response
except Exception as e:
output["error"] = %* fmt"Error fetching market values: {e.msg}"
arg.finish(output)
type
FetchTokensDetailsTaskArg = ref object of QObjectTaskArg
symbols: seq[string]
const fetchTokensDetailsTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchTokensDetailsTaskArg](argEncoded)
var output = %*{
"tokensDetails": "",
"error": ""
}
try:
let response = backend.fetchTokenDetails(arg.symbols)
output["tokensDetails"] = %*response
except Exception as e:
output["error"] = %* fmt"Error fetching token details: {e.msg}"
arg.finish(output)
type
FetchTokensPricesTaskArg = ref object of QObjectTaskArg
symbols: seq[string]
currencies: seq[string]
const fetchTokensPricesTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchTokensPricesTaskArg](argEncoded)
var output = %*{
"tokensPrices": "",
"error": ""
}
try:
let response = backend.fetchPrices(arg.symbols, arg.currencies)
output["tokensPrices"] = %*response
except Exception as e:
output["error"] = %* fmt"Error fetching prices: {e.msg}"
arg.finish(output)
type
GetTokenHistoricalDataTaskArg = ref object of QObjectTaskArg

View File

@ -65,3 +65,18 @@ proc `$`*(self: TokenSourceDto): string =
source: {self.source},
version: {self.version}
]"""
type
TokenMarketValuesDto* = object
marketCap* {.serializedFieldName("MKTCAP").}: float64
highDay* {.serializedFieldName("HIGHDAY").}: float64
lowDay* {.serializedFieldName("LOWDAY").}: float64
changePctHour* {.serializedFieldName("CHANGEPCTHOUR").}: float64
changePctDay* {.serializedFieldName("CHANGEPCTDAY").}: float64
changePct24hour* {.serializedFieldName("CHANGEPCT24HOUR").}: float64
change24hour* {.serializedFieldName("CHANGE24HOUR").}: float64
type
TokenDetailsDto* = object
description* {.serializedFieldName("Description").}: string
assetWebsiteUrl* {.serializedFieldName("AssetWebsiteUrl").}: string

View File

@ -5,10 +5,12 @@ from web3/conversions import `$`
import backend/backend as backend
import app_service/service/network/service as network_service
import app_service/service/settings/service as settings_service
import app_service/service/wallet_account/dto/account_dto
import app/core/eventemitter
import app/core/tasks/[qt, threadpool]
import app/core/signals/types
import app_service/common/cache
import ../../../constants as main_constants
import ./dto, ./service_items
@ -33,6 +35,9 @@ const SIGNAL_TOKEN_HISTORICAL_DATA_LOADED* = "tokenHistoricalDataLoaded"
const SIGNAL_BALANCE_HISTORY_DATA_READY* = "tokenBalanceHistoryDataReady"
const SIGNAL_TOKENS_LIST_UPDATED* = "tokensListUpdated"
const SIGNAL_TOKENS_LIST_ABOUT_TO_BE_UPDATED* = "tokensListAboutToBeUpdated"
const SIGNAL_TOKENS_DETAILS_UPDATED* = "tokensDetailsUpdated"
const SIGNAL_TOKENS_MARKET_VALUES_UPDATED* = "tokensMarketValuesUpdated"
const SIGNAL_TOKENS_PRICES_UPDATED* = "tokensPricesValuesUpdated"
type
TokenHistoricalDataArgs* = ref object of Args
@ -52,6 +57,7 @@ QtObject:
events: EventEmitter
threadpool: ThreadPool
networkService: network_service.Service
settingsService: settings_service.Service
# TODO: remove these once community usage of this service is removed etc...
tokens: Table[int, seq[TokenDto]]
@ -63,13 +69,17 @@ QtObject:
sourcesOfTokensList: seq[SupportedSourcesItem]
flatTokenList: seq[TokenItem]
tokenBySymbolList: seq[TokenBySymbolItem]
# TODO: Table[symbol, TokenDetails] fetched from cryptocompare
# will be done under https://github.com/status-im/status-desktop/issues/12668
tokenDetailsList: Table[string, TokenDetails]
tokenDetailsTable: Table[string, TokenDetailsItem]
tokenMarketValuesTable: Table[string, TokenMarketValuesItem]
tokenPriceTable: Table[string, float64]
tokensDetailsLoading: bool
tokensPricesLoading: bool
tokensMarketDetailsLoading: bool
proc getCurrency*(self: Service): string
proc updateCachedTokenPrice(self: Service, crypto: string, fiat: string, price: float64)
proc jsonToPricesMap(node: JsonNode): Table[string, Table[string, float64]]
proc rebuildMarketData*(self: Service)
proc delete*(self: Service) =
self.QObject.delete
@ -78,12 +88,14 @@ QtObject:
events: EventEmitter,
threadpool: ThreadPool,
networkService: network_service.Service,
settingsService: settings_service.Service
): Service =
new(result, delete)
result.QObject.setup
result.events = events
result.threadpool = threadpool
result.networkService = networkService
result.settingsService = settingsService
result.tokens = initTable[int, seq[TokenDto]]()
result.priceCache = newTimedCache[float64]()
result.tokenList = @[]
@ -92,95 +104,223 @@ QtObject:
result.sourcesOfTokensList = @[]
result.flatTokenList = @[]
result.tokenBySymbolList = @[]
result.tokenDetailsList = initTable[string, TokenDetails]()
result.tokenDetailsTable = initTable[string, TokenDetailsItem]()
result.tokenMarketValuesTable = initTable[string, TokenMarketValuesItem]()
result.tokenPriceTable = initTable[string, float64]()
result.tokensDetailsLoading = true
result.tokensPricesLoading = true
result.tokensMarketDetailsLoading = true
proc fetchTokensMarketValues(self: Service, symbols: seq[string]) =
self.tokensMarketDetailsLoading = true
let arg = FetchTokensMarketValuesTaskArg(
tptr: cast[ByteAddress](fetchTokensMarketValuesTask),
vptr: cast[ByteAddress](self.vptr),
slot: "tokensMarketValuesRetrieved",
symbols: symbols,
currency: self.getCurrency()
)
self.threadpool.start(arg)
proc tokensMarketValuesRetrieved(self: Service, response: string) {.slot.} =
# this is emited so that the models can notify about market values being available
self.tokensMarketDetailsLoading = false
defer: self.events.emit(SIGNAL_TOKENS_MARKET_VALUES_UPDATED, Args())
try:
let parsedJson = response.parseJson
var errorString: string
var tokenMarketValues, tokensResult: JsonNode
discard parsedJson.getProp("tokenMarketValues", tokenMarketValues)
discard parsedJson.getProp("error", errorString)
discard tokenMarketValues.getProp("result", tokensResult)
if not errorString.isEmptyOrWhitespace:
raise newException(Exception, "Error getting tokens market values: " & errorString)
if tokensResult.isNil or tokensResult.kind == JNull:
return
for (symbol, marketValuesObj) in tokensResult.pairs:
let marketValuesDto = Json.decode($marketValuesObj, dto.TokenMarketValuesDto, allowUnknownFields = true)
self.tokenMarketValuesTable[symbol] = TokenMarketValuesItem(
marketCap: marketValuesDto.marketCap,
highDay: marketValuesDto.highDay,
lowDay: marketValuesDto.lowDay,
changePctHour: marketValuesDto.changePctHour,
changePctDay: marketValuesDto.changePctDay,
changePct24hour: marketValuesDto.changePct24hour,
change24hour: marketValuesDto.change24hour)
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
proc fetchTokensDetails(self: Service, symbols: seq[string]) =
self.tokensDetailsLoading = true
let arg = FetchTokensDetailsTaskArg(
tptr: cast[ByteAddress](fetchTokensDetailsTask),
vptr: cast[ByteAddress](self.vptr),
slot: "tokensDetailsRetrieved",
symbols: symbols
)
self.threadpool.start(arg)
proc tokensDetailsRetrieved(self: Service, response: string) {.slot.} =
self.tokensDetailsLoading = false
# this is emited so that the models can notify about details being available
defer: self.events.emit(SIGNAL_TOKENS_DETAILS_UPDATED, Args())
try:
let parsedJson = response.parseJson
var errorString: string
var tokensDetails, tokensResult: JsonNode
discard parsedJson.getProp("tokensDetails", tokensDetails)
discard parsedJson.getProp("error", errorString)
discard tokensDetails.getProp("result", tokensResult)
if not errorString.isEmptyOrWhitespace:
raise newException(Exception, "Error getting tokens details: " & errorString)
if tokensResult.isNil or tokensResult.kind == JNull:
return
for (symbol, tokenDetailsObj) in tokensResult.pairs:
let tokenDetailsDto = Json.decode($tokenDetailsObj, dto.TokenDetailsDto, allowUnknownFields = true)
self.tokenDetailsTable[symbol] = TokenDetailsItem(
description: tokenDetailsDto.description,
assetWebsiteUrl: tokenDetailsDto.assetWebsiteUrl)
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
proc fetchTokensPrices(self: Service, symbols: seq[string]) =
self.tokensPricesLoading = true
let arg = FetchTokensPricesTaskArg(
tptr: cast[ByteAddress](fetchTokensPricesTask),
vptr: cast[ByteAddress](self.vptr),
slot: "tokensPricesRetrieved",
symbols: symbols,
currencies: @[self.getCurrency()]
)
self.threadpool.start(arg)
proc tokensPricesRetrieved(self: Service, response: string) {.slot.} =
self.tokensPricesLoading = false
# this is emited so that the models can notify about prices being available
defer: self.events.emit(SIGNAL_TOKENS_PRICES_UPDATED, Args())
try:
let parsedJson = response.parseJson
var errorString: string
var tokensPrices, tokensResult: JsonNode
discard parsedJson.getProp("tokensPrices", tokensPrices)
discard parsedJson.getProp("error", errorString)
discard tokensPrices.getProp("result", tokensResult)
if not errorString.isEmptyOrWhitespace:
raise newException(Exception, "Error getting tokens details: " & errorString)
if tokensResult.isNil or tokensResult.kind == JNull:
return
for (symbol, prices) in tokensResult.pairs:
for (currency, price) in prices.pairs:
if cmpIgnoreCase(self.getCurrency(), currency) == 0:
self.tokenPriceTable[symbol] = price.getFloat
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
# Callback to process the response of getSupportedTokensList call
proc supportedTokensListRetrieved(self: Service, response: string) {.slot.} =
# this is emited so that the models can know that the seq it depends on has been updated
defer: self.events.emit(SIGNAL_TOKENS_LIST_UPDATED, Args())
try:
let parsedJson = response.parseJson
var errorString: string
var supportedTokensJson, tokensResult: JsonNode
discard parsedJson.getProp("supportedTokensJson", supportedTokensJson)
discard parsedJson.getProp("error", errorString)
discard supportedTokensJson.getProp("result", tokensResult)
let parsedJson = response.parseJson
var errorString: string
var supportedTokensJson, tokensResult: JsonNode
discard parsedJson.getProp("supportedTokensJson", supportedTokensJson)
discard parsedJson.getProp("supportedTokensJson", errorString)
discard supportedTokensJson.getProp("result", tokensResult)
if not errorString.isEmptyOrWhitespace:
raise newException(Exception, "Error getting supported tokens list: " & errorString)
let sourcesList = if tokensResult.isNil or tokensResult.kind == JNull: @[]
else: Json.decode($tokensResult, seq[TokenSourceDto], allowUnknownFields = true)
if not errorString.isEmptyOrWhitespace:
raise newException(Exception, "Error getting supported tokens list: " & errorString)
let sourcesList = if tokensResult.isNil or tokensResult.kind == JNull: @[]
else: Json.decode($tokensResult, seq[TokenSourceDto], allowUnknownFields = true)
let supportedNetworkChains = self.networkService.getAllNetworkChainIds()
var flatTokensList: Table[string, TokenItem] = initTable[string, TokenItem]()
var tokenBySymbolList: Table[string, TokenBySymbolItem] = initTable[string, TokenBySymbolItem]()
var tokenSymbols: seq[string] = @[]
let supportedNetworkChains = self.networkService.getAllNetworkChainIds()
var flatTokensList: Table[string, TokenItem] = initTable[string, TokenItem]()
var tokenBySymbolList: Table[string, TokenBySymbolItem] = initTable[string, TokenBySymbolItem]()
for s in sourcesList:
let newSource = SupportedSourcesItem(name: s.name, updatedAt: s.updatedAt, source: s.source, version: s.version, tokensCount: s.tokens.len)
self.sourcesOfTokensList.add(newSource)
for s in sourcesList:
let newSource = SupportedSourcesItem(name: s.name, updatedAt: s.updatedAt, source: s.source, version: s.version, tokensCount: s.tokens.len)
self.sourcesOfTokensList.add(newSource)
for token in s.tokens:
# Remove tokens that are not on list of supported status networks
if supportedNetworkChains.contains(token.chainID):
# logic for building flat tokens list
let unique_key = $token.chainID & token.address
if flatTokensList.hasKey(unique_key):
flatTokensList[unique_key].sources.add(s.name)
else:
let tokenType = if s.name == "native" : TokenType.Native
else: TokenType.ERC20
flatTokensList[unique_key] = TokenItem(
key: unique_key,
name: token.name,
symbol: token.symbol,
sources: @[s.name],
chainID: token.chainID,
address: token.address,
decimals: token.decimals,
image: "",
`type`: tokenType,
communityId: token.communityID)
for token in s.tokens:
# Remove tokens that are not on list of supported status networks
if supportedNetworkChains.contains(token.chainID):
# logic for building flat tokens list
let unique_key = $token.chainID & token.address
if flatTokensList.hasKey(unique_key):
flatTokensList[unique_key].sources.add(s.name)
else:
let tokenType = if s.name == "native" : TokenType.Native
else: TokenType.ERC20
flatTokensList[unique_key] = TokenItem(
key: unique_key,
name: token.name,
symbol: token.symbol,
sources: @[s.name],
chainID: token.chainID,
address: token.address,
decimals: token.decimals,
image: "",
`type`: tokenType,
communityId: token.communityID)
# logic for building tokens by symbol list
# In case the token is not a community token the unique key is symbol
# In case this is a community token the only param reliably unique is its address
# as there is always a rare case that a user can create two or more community token
# with same symbol and cannot be avoided
let token_by_symbol_key = if token.communityID.isEmptyOrWhitespace: token.symbol
else: token.address
if tokenBySymbolList.hasKey(token_by_symbol_key):
if not tokenBySymbolList[token_by_symbol_key].sources.contains(s.name):
tokenBySymbolList[token_by_symbol_key].sources.add(s.name)
# this logic is to check if an entry for same chainId as been made already,
# in that case we simply add it to address per chain
var addedChains: seq[int] = @[]
for addressPerChain in tokenBySymbolList[token_by_symbol_key].addressPerChainId:
addedChains.add(addressPerChain.chainId)
if not addedChains.contains(token.chainID):
tokenBySymbolList[token_by_symbol_key].addressPerChainId.add(AddressPerChain(chainId: token.chainID, address: token.address))
else:
let tokenType = if s.name == "native": TokenType.Native
else: TokenType.ERC20
tokenBySymbolList[token_by_symbol_key] = TokenBySymbolItem(
key: token_by_symbol_key,
name: token.name,
symbol: token.symbol,
sources: @[s.name],
addressPerChainId: @[AddressPerChain(chainId: token.chainID, address: token.address)],
decimals: token.decimals,
image: "",
`type`: tokenType,
communityId: token.communityID)
if token.communityID.isEmptyOrWhitespace:
tokenSymbols.add(token.symbol)
# logic for building tokens by symbol list
# In case the token is not a community token the unique key is symbol
# In case this is a community token the only param reliably unique is its address
# as there is always a rare case that a user can create two or more community token
# with same symbol and cannot be avoided
let token_by_symbol_key = if token.communityID.isEmptyOrWhitespace: token.symbol
else: token.address
if tokenBySymbolList.hasKey(token_by_symbol_key):
if not tokenBySymbolList[token_by_symbol_key].sources.contains(s.name):
tokenBySymbolList[token_by_symbol_key].sources.add(s.name)
# this logic is to check if an entry for same chainId as been made already,
# in that case we simply add it to address per chain
var addedChains: seq[int] = @[]
for addressPerChain in tokenBySymbolList[token_by_symbol_key].addressPerChainId:
addedChains.add(addressPerChain.chainId)
if not addedChains.contains(token.chainID):
tokenBySymbolList[token_by_symbol_key].addressPerChainId.add(AddressPerChain(chainId: token.chainID, address: token.address))
else:
let tokenType = if s.name == "native": TokenType.Native
else: TokenType.ERC20
tokenBySymbolList[token_by_symbol_key] = TokenBySymbolItem(
key: token_by_symbol_key,
name: token.name,
symbol: token.symbol,
sources: @[s.name],
addressPerChainId: @[AddressPerChain(chainId: token.chainID, address: token.address)],
decimals: token.decimals,
image: "",
`type`: tokenType,
communityId: token.communityID)
self.flatTokenList = toSeq(flatTokensList.values)
self.flatTokenList.sort(cmpTokenItem)
self.tokenBySymbolList = toSeq(tokenBySymbolList.values)
self.tokenBySymbolList.sort(cmpTokenBySymbolItem)
self.fetchTokensMarketValues(tokenSymbols)
self.fetchTokensDetails(tokenSymbols)
self.fetchTokensPrices(tokenSymbols)
self.flatTokenList = toSeq(flatTokensList.values)
self.flatTokenList.sort(cmpTokenItem)
self.tokenBySymbolList = toSeq(tokenBySymbolList.values)
self.tokenBySymbolList.sort(cmpTokenBySymbolItem)
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
proc getSupportedTokensList(self: Service) =
# this is emited so that the models can know that an update is about to happen
self.events.emit(SIGNAL_TOKENS_LIST_ABOUT_TO_BE_UPDATED, Args())
let arg = GetTokenListTaskArg(
let arg = QObjectTaskArg(
tptr: cast[ByteAddress](getSupportedTokenList),
vptr: cast[ByteAddress](self.vptr),
slot: "supportedTokensListRetrieved",
@ -246,8 +386,16 @@ QtObject:
return
self.loadData()
self.getSupportedTokensList()
# ToDo: on self.events.on(SignalType.Message.event) do(e: Args):
# update and populate internal list and then emit signal
self.events.on(SignalType.Wallet.event) do(e:Args):
var data = WalletSignal(e)
case data.eventType:
of "wallet-tick-reload":
self.rebuildMarketData()
# update and populate internal list and then emit signal when new custom token detected?
proc getCurrency*(self: Service): string =
return self.settingsService.getCurrency()
proc getSourcesOfTokensList*(self: Service): var seq[SupportedSourcesItem] =
return self.sourcesOfTokensList
@ -258,6 +406,32 @@ QtObject:
proc getTokenBySymbolList*(self: Service): var seq[TokenBySymbolItem] =
return self.tokenBySymbolList
proc getTokenDetails*(self: Service, symbol: string): TokenDetailsItem =
if not self.tokenDetailsTable.hasKey(symbol):
return TokenDetailsItem()
return self.tokenDetailsTable[symbol]
proc getMarketValuesBySymbol*(self: Service, symbol: string): TokenMarketValuesItem =
if not self.tokenMarketValuesTable.hasKey(symbol):
return TokenMarketValuesItem()
return self.tokenMarketValuesTable[symbol]
proc getPriceBySymbol*(self: Service, symbol: string): float64 =
if not self.tokenPriceTable.hasKey(symbol):
return 0.0
return self.tokenPriceTable[symbol]
proc getTokensDetailsLoading*(self: Service): bool =
return self.tokensDetailsLoading
proc getTokensMarketValuesLoading*(self: Service): bool =
return self.tokensPricesLoading and self.tokensMarketDetailsLoading
proc rebuildMarketData*(self: Service) =
let symbols = self.tokenDetailsTable.keys.toSeq()
self.fetchTokensMarketValues(symbols)
self.fetchTokensPrices(symbols)
# TODO: Remove after https://github.com/status-im/status-desktop/issues/12513
proc getTokenList*(self: Service): seq[TokenDto] =
return self.tokenList

View File

@ -81,10 +81,36 @@ proc `$`*(self: TokenBySymbolItem): string =
]"""
# In case of community tokens only the description will be available
type TokenDetails* = ref object of RootObj
type TokenDetailsItem* = ref object of RootObj
description*: string
websiteUrl*: string
marketValues*: TokenMarketValuesDto
assetWebsiteUrl*: string
proc `$`*(self: TokenDetailsItem): string =
result = fmt"""TokenDetailsItem[
description: {self.description},
assetWebsiteUrl: {self.assetWebsiteUrl}
]"""
type
TokenMarketValuesItem* = object
marketCap*: float64
highDay*: float64
lowDay*: float64
changePctHour*: float64
changePctDay*: float64
changePct24hour*: float64
change24hour*: float64
proc `$`*(self: TokenMarketValuesItem): string =
result = fmt"""TokenBySymbolItem[
marketCap: {self.marketCap},
highDay: {self.highDay},
lowDay: {self.lowDay},
changePctHour: {self.changePctHour},
changePctDay: {self.changePctDay},
changePct24hour: {self.changePct24hour},
change24hour: {self.change24hour}
]"""
proc cmpTokenItem*(x, y: TokenItem): int =
cmp(x.name, y.name)

View File

@ -138,6 +138,10 @@ rpc(getTransfersForIdentities, "wallet"):
rpc(getWalletToken, "wallet"):
accounts: seq[string]
rpc(fetchMarketValues, "wallet"):
symbols: seq[string]
currency: string
rpc(startWallet, "wallet"):
discard