From 3a41a81890f7f9200c4c6f5f91777b55012620bf Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Mon, 13 Nov 2023 13:47:04 +0100 Subject: [PATCH] feat(@desktop/wallet): Add new go api to get token market values not bundled with token balances fixes #12668 --- src/app/boot/app_controller.nim | 5 +- .../wallet_section/all_tokens/controller.nim | 28 +- .../all_tokens/flat_tokens_model.nim | 60 +++- .../all_tokens/io_interface.nim | 20 +- .../all_tokens/market_details_item.nim | 86 +++++ .../main/wallet_section/all_tokens/module.nim | 54 ++- .../all_tokens/token_by_symbol_model.nim | 52 ++- .../main/wallet_section/all_tokens/view.nim | 20 +- .../service/network_connection/service.nim | 14 +- src/app_service/service/token/async_tasks.nim | 80 ++++- src/app_service/service/token/dto.nim | 15 + src/app_service/service/token/service.nim | 334 +++++++++++++----- .../service/token/service_items.nim | 32 +- src/backend/backend.nim | 4 + 14 files changed, 653 insertions(+), 151 deletions(-) create mode 100644 src/app/modules/main/wallet_section/all_tokens/market_details_item.nim diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index ac772c29bb..20cab3a214 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -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]( diff --git a/src/app/modules/main/wallet_section/all_tokens/controller.nim b/src/app/modules/main/wallet_section/all_tokens/controller.nim index 6a335b8eb5..66942f38e7 100644 --- a/src/app/modules/main/wallet_section/all_tokens/controller.nim +++ b/src/app/modules/main/wallet_section/all_tokens/controller.nim @@ -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() diff --git a/src/app/modules/main/wallet_section/all_tokens/flat_tokens_model.nim b/src/app/modules/main/wallet_section/all_tokens/flat_tokens_model.nim index 64dedb0a07..0d021cc070 100644 --- a/src/app/modules/main/wallet_section/all_tokens/flat_tokens_model.nim +++ b/src/app/modules/main/wallet_section/all_tokens/flat_tokens_model.nim @@ -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() diff --git a/src/app/modules/main/wallet_section/all_tokens/io_interface.nim b/src/app/modules/main/wallet_section/all_tokens/io_interface.nim index 28e1122e15..078454281b 100644 --- a/src/app/modules/main/wallet_section/all_tokens/io_interface.nim +++ b/src/app/modules/main/wallet_section/all_tokens/io_interface.nim @@ -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. diff --git a/src/app/modules/main/wallet_section/all_tokens/market_details_item.nim b/src/app/modules/main/wallet_section/all_tokens/market_details_item.nim new file mode 100644 index 0000000000..1fce6647b5 --- /dev/null +++ b/src/app/modules/main/wallet_section/all_tokens/market_details_item.nim @@ -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 diff --git a/src/app/modules/main/wallet_section/all_tokens/module.nim b/src/app/modules/main/wallet_section/all_tokens/module.nim index eefbe42821..72cbc0f4f7 100644 --- a/src/app/modules/main/wallet_section/all_tokens/module.nim +++ b/src/app/modules/main/wallet_section/all_tokens/module.nim @@ -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]) = diff --git a/src/app/modules/main/wallet_section/all_tokens/token_by_symbol_model.nim b/src/app/modules/main/wallet_section/all_tokens/token_by_symbol_model.nim index 65826beec2..3cff824f08 100644 --- a/src/app/modules/main/wallet_section/all_tokens/token_by_symbol_model.nim +++ b/src/app/modules/main/wallet_section/all_tokens/token_by_symbol_model.nim @@ -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() diff --git a/src/app/modules/main/wallet_section/all_tokens/view.nim b/src/app/modules/main/wallet_section/all_tokens/view.nim index 1191dcfdde..a79c88f140 100644 --- a/src/app/modules/main/wallet_section/all_tokens/view.nim +++ b/src/app/modules/main/wallet_section/all_tokens/view.nim @@ -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() diff --git a/src/app_service/service/network_connection/service.nim b/src/app_service/service/network_connection/service.nim index 2a6d962306..02103c023d 100644 --- a/src/app_service/service/network_connection/service.nim +++ b/src/app_service/service/network_connection/service.nim @@ -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 - diff --git a/src/app_service/service/token/async_tasks.nim b/src/app_service/service/token/async_tasks.nim index 80fefe6ffd..d4375d26fb 100644 --- a/src/app_service/service/token/async_tasks.nim +++ b/src/app_service/service/token/async_tasks.nim @@ -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 diff --git a/src/app_service/service/token/dto.nim b/src/app_service/service/token/dto.nim index f6f46096cb..5398d70bd8 100644 --- a/src/app_service/service/token/dto.nim +++ b/src/app_service/service/token/dto.nim @@ -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 diff --git a/src/app_service/service/token/service.nim b/src/app_service/service/token/service.nim index f365230804..382eb20651 100644 --- a/src/app_service/service/token/service.nim +++ b/src/app_service/service/token/service.nim @@ -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 diff --git a/src/app_service/service/token/service_items.nim b/src/app_service/service/token/service_items.nim index c7d0bf3b68..b1565eaa99 100644 --- a/src/app_service/service/token/service_items.nim +++ b/src/app_service/service/token/service_items.nim @@ -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) diff --git a/src/backend/backend.nim b/src/backend/backend.nim index 4de6d55f2a..c746d4a1bf 100644 --- a/src/backend/backend.nim +++ b/src/backend/backend.nim @@ -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