diff --git a/src/app/modules/main/browser_section/current_account/module.nim b/src/app/modules/main/browser_section/current_account/module.nim index 3b9f5eb68f..fbd9003984 100644 --- a/src/app/modules/main/browser_section/current_account/module.nim +++ b/src/app/modules/main/browser_section/current_account/module.nim @@ -50,6 +50,16 @@ proc setAssets(self: Module, tokens: seq[WalletTokenDto]) = t.enabledNetworkBalance.currencybalance, t.visible, toSeq(t.balancesPerChain.values), + t.description, + t.assetWebsiteUrl, + t.builtOn, + t.smartContractAddress, + t.marketCap, + t.highDay, + t.lowDay, + t.changePctHour, + t.changePctDay, + t.changePct24hour, ) items.add(item) @@ -91,4 +101,4 @@ proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[Wall let walletAccount = self.controller.getWalletAccount(self.currentAccountIndex) if not accountsTokens.contains(walletAccount.address): return - self.setAssets(accountsTokens[walletAccount.address]) \ No newline at end of file + self.setAssets(accountsTokens[walletAccount.address]) diff --git a/src/app/modules/main/networks/model.nim b/src/app/modules/main/networks/model.nim index 34653d025b..da5b9513b0 100644 --- a/src/app/modules/main/networks/model.nim +++ b/src/app/modules/main/networks/model.nim @@ -114,3 +114,27 @@ QtObject: self.items = items self.endResetModel() self.countChanged() + + proc getChainColor*(self: Model, chainId: int): string {.slot.} = + for item in self.items: + if(item.getChainId() == chainId): + return item.getChainColor() + return "" + + proc getIconUrl*(self: Model, chainId: int): string {.slot.} = + for item in self.items: + if(item.getChainId() == chainId): + return item.getIconURL() + return "" + + proc getNetworkIconUrl*(self: Model, shortName: string): string {.slot.} = + for item in self.items: + if(item.getShortName() == toLowerAscii(shortName)): + return item.getIconURL() + return "" + + proc getNetworkName*(self: Model, shortName: string): string {.slot.} = + for item in self.items: + if(item.getShortName() == toLowerAscii(shortName)): + return item.getChainName() + return "" diff --git a/src/app/modules/main/wallet_section/accounts/module.nim b/src/app/modules/main/wallet_section/accounts/module.nim index 0be3995cb2..64034313e0 100644 --- a/src/app/modules/main/wallet_section/accounts/module.nim +++ b/src/app/modules/main/wallet_section/accounts/module.nim @@ -54,6 +54,16 @@ method refreshWalletAccounts*(self: Module) = t.enabledNetworkBalance.currencyBalance, t.visible, toSeq(t.balancesPerChain.values), + t.description, + t.assetWebsiteUrl, + t.builtOn, + t.smartContractAddress, + t.marketCap, + t.highDay, + t.lowDay, + t.changePctHour, + t.changePctDay, + t.changePct24hour, )) ) diff --git a/src/app/modules/main/wallet_section/current_account/module.nim b/src/app/modules/main/wallet_section/current_account/module.nim index 8e27dc7bc8..e61a5b8459 100644 --- a/src/app/modules/main/wallet_section/current_account/module.nim +++ b/src/app/modules/main/wallet_section/current_account/module.nim @@ -81,6 +81,16 @@ proc setAssetsAndBalance(self: Module, tokens: seq[WalletTokenDto]) = t.enabledNetworkBalance.currencybalance, t.visible, toSeq(t.balancesPerChain.values), + t.description, + t.assetWebsiteUrl, + t.builtOn, + t.smartContractAddress, + t.marketCap, + t.highDay, + t.lowDay, + t.changePctHour, + t.changePctDay, + t.changePct24hour, ) items.add(item) totalCurrencyBalanceForAllAssets += t.enabledNetworkBalance.currencybalance diff --git a/src/app/modules/shared_models/token_item.nim b/src/app/modules/shared_models/token_item.nim index 871405d002..d618d6d17b 100644 --- a/src/app/modules/shared_models/token_item.nim +++ b/src/app/modules/shared_models/token_item.nim @@ -13,6 +13,16 @@ type enabledNetworkBalance: float networkVisible: bool balances: balance_model.BalanceModel + description: string + assetWebsiteUrl: string + builtOn: string + smartContractAddress: string + marketCap: string + highDay: string + lowDay: string + changePctHour: string + changePctDay: string + changePct24hour: string proc initItem*( name, symbol: string, @@ -21,7 +31,17 @@ proc initItem*( enabledNetworkBalance: float, enabledNetworkCurrencyBalance: float, networkVisible: bool, - balances: seq[BalanceDto] + balances: seq[BalanceDto], + description: string, + assetWebsiteUrl: string, + builtOn: string, + smartContractAddress: string, + marketCap: string, + highDay: string, + lowDay: string, + changePctHour: string, + changePctDay: string, + changePct24hour: string, ): Item = result.name = name result.symbol = symbol @@ -32,6 +52,16 @@ proc initItem*( result.networkVisible = networkVisible result.balances = balance_model.newModel() result.balances.setItems(balances) + result.description = description + result.assetWebsiteUrl = assetWebsiteUrl + result.builtOn = builtOn + result.smartContractAddress = smartContractAddress + result.marketCap = marketCap + result.highDay = highDay + result.lowDay = lowDay + result.changePctHour = changePctHour + result.changePctDay = changePctDay + result.changePct24hour = changePct24hour proc `$`*(self: Item): string = result = fmt"""AllTokensItem( @@ -42,6 +72,16 @@ proc `$`*(self: Item): string = enabledNetworkBalance: {self.enabledNetworkBalance}, enabledNetworkCurrencyBalance: {self.enabledNetworkCurrencyBalance}, networkVisible: {self.networkVisible}, + description: {self.description}, + assetWebsiteUrl: {self.assetWebsiteUrl} + builtOn: {self.builtOn} + smartContractAddress: {self.smartContractAddress} + marketCap: {self.marketCap}, + highDay: {self.highDay}, + lowDay: {self.lowDay}, + changePctHour: {self.changePctHour}, + changePctDay: {self.changePctDay}, + changePct24hour: {self.changePct24hour}, ]""" proc getName*(self: Item): string = @@ -66,4 +106,34 @@ proc getNetworkVisible*(self: Item): bool = return self.networkVisible proc getBalances*(self: Item): balance_model.BalanceModel = - return self.balances \ No newline at end of file + return self.balances + +proc getDescription*(self: Item): string = + return self.description + +proc getAssetWebsiteUrl*(self: Item): string = + return self.assetWebsiteUrl + +proc getBuiltOn*(self: Item): string = + return self.builtOn + +proc getSmartContractAddress*(self: Item): string = + return self.smartContractAddress + +proc getMarketCap*(self: Item): string = + return self.marketCap + +proc getHighDay*(self: Item): string = + return self.highDay + +proc getLowDay*(self: Item): string = + return self.lowDay + +proc getChangePctHour*(self: Item): string = + return self.changePctHour + +proc getChangePctDay*(self: Item): string = + return self.changePctDay + +proc getChangePct24hour*(self: Item): string = + return self.changePct24hour diff --git a/src/app/modules/shared_models/token_model.nim b/src/app/modules/shared_models/token_model.nim index 70b3234f9e..95af7f3972 100644 --- a/src/app/modules/shared_models/token_model.nim +++ b/src/app/modules/shared_models/token_model.nim @@ -12,6 +12,16 @@ type EnabledNetworkBalance NetworkVisible Balances + Description + AssetWebsiteUrl + BuiltOn + SmartContractAddress + MarketCap + HighDay + LowDay + ChangePctHour + ChangePctDay + ChangePct24hour QtObject: type @@ -55,6 +65,16 @@ QtObject: ModelRole.EnabledNetworkBalance.int:"enabledNetworkBalance", ModelRole.NetworkVisible.int:"networkVisible", ModelRole.Balances.int:"balances", + ModelRole.Description.int:"description", + ModelRole.AssetWebsiteUrl.int:"assetWebsiteUrl", + ModelRole.BuiltOn.int:"builtOn", + ModelRole.SmartContractAddress.int:"smartContractAddress", + ModelRole.MarketCap.int:"marketCap", + ModelRole.HighDay.int:"highDay", + ModelRole.LowDay.int:"lowDay", + ModelRole.ChangePctHour.int:"changePctHour", + ModelRole.ChangePctDay.int:"changePctDay", + ModelRole.ChangePct24hour.int:"changePct24hour", }.toTable method data(self: Model, index: QModelIndex, role: int): QVariant = @@ -84,6 +104,27 @@ QtObject: result = newQVariant(item.getNetworkVisible()) of ModelRole.Balances: result = newQVariant(item.getBalances()) + of ModelRole.Description: + result = newQVariant(item.getDescription()) + of ModelRole.AssetWebsiteUrl: + result = newQVariant(item.getAssetWebsiteUrl()) + of ModelRole.BuiltOn: + result = newQVariant(item.getBuiltOn()) + of ModelRole.SmartContractAddress: + result = newQVariant(item.getSmartContractAddress()) + of ModelRole.MarketCap: + result = newQVariant(item.getMarketCap()) + of ModelRole.HighDay: + result = newQVariant(item.getHighDay()) + of ModelRole.LowDay: + result = newQVariant(item.getLowDay()) + of ModelRole.ChangePctHour: + result = newQVariant(item.getChangePctHour()) + of ModelRole.ChangePctDay: + result = newQVariant(item.getChangePctDay()) + of ModelRole.ChangePct24hour: + result = newQVariant(item.getChangePct24hour()) + proc rowData(self: Model, index: int, column: string): string {.slot.} = if (index >= self.items.len): @@ -97,6 +138,17 @@ QtObject: of "enabledNetworkCurrencyBalance": result = $item.getEnabledNetworkCurrencyBalance() of "enabledNetworkBalance": result = $item.getEnabledNetworkBalance() of "networkVisible": result = $item.getNetworkVisible() + of "description": result = $item.getDescription() + of "assetWebsiteUrl": result = $item.getAssetWebsiteUrl() + of "builtOn": result = $item.getBuiltOn() + of "smartContractAddress": result = $item.getSmartContractAddress() + of "marketCap": result = $item.getMarketCap() + of "highDay": result = $item.getHighDay() + of "lowDay": result = $item.getLowDay() + of "changePctHour": result = $item.getChangePctHour() + of "changePctDay": result = $item.getChangePctDay() + of "changePct24hour": result = $item.getChangePct24hour() + proc setItems*(self: Model, items: seq[Item]) = self.beginResetModel() diff --git a/src/app_service/common/network_constants.nim b/src/app_service/common/network_constants.nim index 9d59571f3f..c59585c526 100644 --- a/src/app_service/common/network_constants.nim +++ b/src/app_service/common/network_constants.nim @@ -30,7 +30,7 @@ let DEFAULT_TORRENT_CONFIG_TORRENTDIR* = joinPath(main_constants.defaultDataDir( let NETWORKS* = %* [ { "chainId": 1, - "chainName": "Mainnet", + "chainName": "Ethereum Mainnet", "rpcUrl": "https://mainnet.infura.io/v3/" & INFURA_TOKEN_RESOLVED, "blockExplorerUrl": "https://etherscan.io/", "iconUrl": "network/Network=Ethereum", diff --git a/src/app_service/service/token/dto.nim b/src/app_service/service/token/dto.nim index c66325aa61..39bfb12632 100644 --- a/src/app_service/service/token/dto.nim +++ b/src/app_service/service/token/dto.nim @@ -17,12 +17,54 @@ type color*: string isCustom* {.dontSerialize.}: bool isVisible* {.dontSerialize.}: bool + description* :string + assetWebsiteUrl*: string + builtOn*: string + smartContractAddress*: string + marketCap*: string + highDay*: string + lowDay*: string + changePctHour*: string + changePctDay*: string + changePct24hour*: string proc newTokenDto*( - name: string, chainId: int, address: Address, symbol: string, decimals: int, hasIcon: bool, isCustom: bool = false + name: string, + chainId: int, + address: Address, + symbol: string, + decimals: int, + hasIcon: bool, + isCustom: bool = false, + description: string = "", + assetWebsiteUrl: string = "", + builtOn: string = "", + smartContractAddress: string = "", + marketCap: string = "", + highDay: string = "", + lowDay: string = "", + changePctHour: string = "", + changePctDay: string = "", + changePct24hour: string = "", ): TokenDto = return TokenDto( - name: name, chainId: chainId, address: address, symbol: symbol, decimals: decimals, hasIcon: hasIcon, isCustom: isCustom + name: name, + chainId: chainId, + address: address, + symbol: symbol, + decimals: decimals, + hasIcon: hasIcon, + isCustom: isCustom, + description: description, + assetWebsiteUrl: assetWebsiteUrl, + builtOn: builtOn, + smartContractAddress: smartContractAddress, + marketCap: marketCap, + highDay: highDay, + lowDay: lowDay, + changePctHour: changePctHour, + changePctDay: changePctDay, + changePct24hour: changePct24hour, ) proc toTokenDto*(jsonObj: JsonNode, isVisible: bool, hasIcon: bool = false, isCustom: bool = true): TokenDto = @@ -36,8 +78,18 @@ proc toTokenDto*(jsonObj: JsonNode, isVisible: bool, hasIcon: bool = false, isCu discard jsonObj.getProp("symbol", result.symbol) discard jsonObj.getProp("decimals", result.decimals) discard jsonObj.getProp("color", result.color) + discard jsonObj.getProp("description", result.description) + discard jsonObj.getProp("assetWebsiteUrl", result.assetWebsiteUrl) + discard jsonObj.getProp("builtOn", result.builtOn) + discard jsonObj.getProp("smartContractAddress", result.smartContractAddress) + discard jsonObj.getProp("marketCap", result.marketCap) + discard jsonObj.getProp("highDay", result.highDay) + discard jsonObj.getProp("lowDay", result.lowDay) + discard jsonObj.getProp("changePctHour", result.changePctHour) + discard jsonObj.getProp("changePctDay", result.changePctDay) + discard jsonObj.getProp("changePct24hour", result.changePct24hour) result.isVisible = isVisible proc addressAsString*(self: TokenDto): string = - return $self.address \ No newline at end of file + return $self.address diff --git a/src/app_service/service/wallet_account/async_tasks.nim b/src/app_service/service/wallet_account/async_tasks.nim index daa218aa36..52f867a131 100644 --- a/src/app_service/service/wallet_account/async_tasks.nim +++ b/src/app_service/service/wallet_account/async_tasks.nim @@ -169,6 +169,38 @@ proc fetchPrices(networkSymbols: seq[string], allTokens: seq[TokenDto], currency except Exception as e: error "error fetching prices: ", message = e.msg +proc getMarketValues(networkSymbols: seq[string], allTokens: seq[TokenDto], currency: string): Table[string, Table[string, string]] = + let allSymbols = prepareSymbols(networkSymbols, allTokens) + for symbols in allSymbols: + if symbols.len == 0: + continue + + try: + let response = backend.fetchMarketValues(symbols, currency) + for (symbol, marketValue) in response.result.pairs: + var marketValues: Table[string, string] = initTable[string, string]() + for (key, value) in marketValue.pairs: + marketValues[key] = value.getStr() + result[symbol] = marketValues + except Exception as e: + error "error fetching markey values: ", message = e.msg + +proc getTokenDetails(networkSymbols: seq[string], allTokens: seq[TokenDto]): Table[string, Table[string, string]] = + let allSymbols = prepareSymbols(networkSymbols, allTokens) + for symbols in allSymbols: + if symbols.len == 0: + continue + + try: + let response = backend.fetchTokenDetails(symbols) + for (symbol, tokenDetail) in response.result.pairs: + var tokenDetails: Table[string, string] = initTable[string, string]() + for (key, value) in tokenDetail.pairs: + tokenDetails[key] = value.getStr() + result[symbol] = tokenDetails + except Exception as e: + error "error fetching markey values: ", message = e.msg + proc getTokensBalances(walletAddresses: seq[string], allTokens: seq[TokenDto]): JsonNode = try: result = newJObject() @@ -218,6 +250,8 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = allTokens = deduplicate(allTokens) var prices = fetchPrices(networkSymbols, allTokens, arg.currency) + var marketValues = getMarketValues(networkSymbols, allTokens, arg.currency) + var tokenDetails = getTokenDetails(networkSymbols, allTokens) let tokenBalances = getTokensBalances(arg.walletAddresses, allTokens) var builtTokensPerAccount = %*{ } @@ -252,6 +286,21 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = var totalTokenBalance: BalanceDto totalTokenBalance.balance = toSeq(balancesPerChain.values).map(x => x.balance).foldl(a + b) totalTokenBalance.currencyBalance = totalTokenBalance.balance * prices[networkDto.nativeCurrencySymbol] + var marketCap: string = "" + var highDay: string = "" + var lowDay: string = "" + var changePctHour: string = "" + var changePctDay: string = "" + var changePct24hour: string = "" + + if(marketValues.hasKey(networkDto.nativeCurrencySymbol)): + marketCap = marketValues[networkDto.nativeCurrencySymbol]["MKTCAP"] + highDay = marketValues[networkDto.nativeCurrencySymbol]["HIGHDAY"] + lowDay = marketValues[networkDto.nativeCurrencySymbol]["LOWDAY"] + changePctHour = marketValues[networkDto.nativeCurrencySymbol]["CHANGEPCTHOUR"] + changePctDay = marketValues[networkDto.nativeCurrencySymbol]["CHANGEPCTDAY"] + changePct24hour = marketValues[networkDto.nativeCurrencySymbol]["CHANGEPCT24HOUR"] + builtTokens.add(WalletTokenDto( name: networkDto.nativeCurrencyName, symbol: networkDto.nativeCurrencySymbol, @@ -263,6 +312,16 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = enabledNetworkBalance: enabledNetworkBalance, balancesPerChain: balancesPerChain, visible: networkDto.enabled, + description: "Ethereum is a decentralized platform that runs smart contracts (applications that run exactly as programmed without any possibility of downtime, censorship, fraud or third party interference). In the Ethereum protocol and blockchain, there is a price for each operation. In order to have anything transferred or executed by the network, you have to consume or burn Gas. Ethereum’s native cryptocurrency is Ether (ETH) and it is used to pay for computation time and transaction fees.The introductory whitepaper was originally published in 2013 by Vitalik Buterin, the founder of Ethereum, the project was crowdfunded during August 2014 by fans all around the world and launched in 2015. Ethereum is developed and maintained by ETHDEV with contributions from minds across the globe. There is an Ecosystem Support Program which is a branch of the Ethereum Foundation focused on supporting projects and entities within the greater Ethereum community to promote the success and growth of the ecosystem. Multiple startups work with the Ethereum blockchain covering areas in: DeFi, NFTs, Ethereum Name Service, Wallets, Scaling, etc.The launch of Ethereum is a process divided into 4 main phases: Frontier, Homestead, Metropolis and Serenity.Ethereum 2.0, also known as Serenity, is the final phase of Ethereum, it aims to solve the decentralized scaling challenge. A naive way to solve Ethereum's problems would be to make it more centralized. But decentralization is too important, as it gives Ethereum censorship resistance, openness, data privacy and near-unbreakable security.The Eth2 upgrades will make Ethereum scalable, secure, and decentralized. Sharding will make Ethereum more scalable by increasing transactions per second while decreasing the power needed to run a node and validate the chain. The beacon chain will make Ethereum secure by coordinating validators across shards. And staking will lower the barrier to participation, creating a larger – more decentralized – network.The beacon chain will also introduce proof-of-stake to Ethereum. Ethereum is moving to the proof-of-stake (PoS) consensus mechanism from proof-of-work (PoW). This was always the plan as it's a key part of the community's strategy to scale Ethereum via the Eth2 upgrades. However, getting PoS right is a big technical challenge and not as straightforward as using PoW to reach consensus across the networkKeep up with Ethereum upgradesFor ETH holders and Dapp users, this has no impact whatsoever, however, for users wishing to get involved, there are ways to participate in Ethereum and future Eth2-related efforts. Get involved in Eth 2.0Blockchain data provided by: Etherchain (Main Source), Blockchair (Backup), and Etherscan (Total Supply only).", + assetWebsiteUrl: "https://www.ethereum.org/", + builtOn: "", + smartContractAddress: "", + marketCap: marketCap, + highDay: highDay, + lowDay: lowDay, + changePctHour: changePctHour, + changePctDay: changePctDay, + changePct24hour: changePct24hour, ) ) @@ -294,6 +353,32 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = var totalTokenBalance: BalanceDto totalTokenBalance.balance = toSeq(balancesPerChain.values).map(x => x.balance).foldl(a + b) totalTokenBalance.currencyBalance = totalTokenBalance.balance * prices[tokenDto.symbol] + var marketCap: string = "" + var highDay: string = "" + var lowDay: string = "" + var changePctHour: string = "" + var changePctDay: string = "" + var changePct24hour: string = "" + var description: string = "" + var assetWebsiteUrl: string = "" + var builtOn: string = "" + var smartContractAddress: string = "" + + if(tokenDetails.hasKey(tokenDto.symbol)): + description = tokenDetails[tokenDto.symbol]["Description"] + assetWebsiteUrl = tokenDetails[tokenDto.symbol]["AssetWebsiteUrl"] + builtOn = tokenDetails[tokenDto.symbol]["BuiltOn"] + smartContractAddress = tokenDetails[tokenDto.symbol]["SmartContractAddress"] + + if(marketValues.hasKey(tokenDto.symbol)): + marketCap = marketValues[tokenDto.symbol]["MKTCAP"] + highDay = marketValues[tokenDto.symbol]["HIGHDAY"] + lowDay = marketValues[tokenDto.symbol]["LOWDAY"] + changePctHour = marketValues[tokenDto.symbol]["CHANGEPCTHOUR"] + changePctDay = marketValues[tokenDto.symbol]["CHANGEPCTDAY"] + changePct24hour = marketValues[tokenDto.symbol]["CHANGEPCT24HOUR"] + + let tokenDescription = description.multiReplace([("|", ""),("Facebook",""),("Telegram",""),("Discord",""),("Youtube",""),("YouTube",""),("Instagram",""),("Reddit",""),("Github",""),("GitHub",""),("Whitepaper",""),("Medium",""),("Weibo",""),("LinkedIn",""),("Litepaper",""),("KakaoTalk",""),("BitcoinTalk",""),("Slack",""),("Docs",""),("Kakao",""),("Gitter","")]) builtTokens.add(WalletTokenDto( name: tokenDto.name, symbol: tokenDto.symbol, @@ -304,7 +389,17 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = totalBalance: totalTokenBalance, balancesPerChain: balancesPerChain, enabledNetworkBalance: enabledNetworkBalance, - visible: visible + visible: visible, + description: tokenDescription, + assetWebsiteUrl: assetWebsiteUrl, + builtOn: builtOn, + smartContractAddress: smartContractAddress, + marketCap: marketCap, + highDay: highDay, + lowDay: lowDay, + changePctHour: changePctHour, + changePctDay: changePctDay, + changePct24hour: changePct24hour ) ) diff --git a/src/app_service/service/wallet_account/dto.nim b/src/app_service/service/wallet_account/dto.nim index b8e3ba452f..5423134f87 100644 --- a/src/app_service/service/wallet_account/dto.nim +++ b/src/app_service/service/wallet_account/dto.nim @@ -20,6 +20,16 @@ type enabledNetworkBalance*: BalanceDto balancesPerChain*: Table[int, BalanceDto] visible*: bool + description*: string + assetWebsiteUrl*: string + builtOn*: string + smartContractAddress*: string + marketCap*: string + highDay*: string + lowDay*: string + changePctHour*: string + changePctDay*: string + changePct24hour*: string type WalletAccountDto* = ref object of RootObj @@ -97,6 +107,16 @@ proc toWalletTokenDto*(jsonObj: JsonNode): WalletTokenDto = discard jsonObj.getProp("color", result.color) discard jsonObj.getProp("isCustom", result.isCustom) discard jsonObj.getProp("visible", result.visible) + discard jsonObj.getProp("description", result.description) + discard jsonObj.getProp("assetWebsiteUrl", result.assetWebsiteUrl) + discard jsonObj.getProp("builtOn", result.builtOn) + discard jsonObj.getProp("smartContractAddress", result.smartContractAddress) + discard jsonObj.getProp("marketCap", result.marketCap) + discard jsonObj.getProp("highDay", result.highDay) + discard jsonObj.getProp("lowDay", result.lowDay) + discard jsonObj.getProp("changePctHour", result.changePctHour) + discard jsonObj.getProp("changePctDay", result.changePctDay) + discard jsonObj.getProp("changePct24hour", result.changePct24hour) var totalBalanceObj: JsonNode if(jsonObj.getProp("totalBalance", totalBalanceObj)): @@ -126,5 +146,15 @@ proc walletTokenDtoToJson*(dto: WalletTokenDto): JsonNode = "totalBalance": %* dto.totalBalance, "enabledNetworkBalance": %* dto.enabledNetworkBalance, "balancesPerChain": balancesPerChainJsonObj, - "visible": dto.visible + "visible": dto.visible, + "description": dto.description, + "assetWebsiteUrl": dto.assetWebsiteUrl, + "builtOn": dto.builtOn, + "smartContractAddress": dto.smartContractAddress, + "marketCap": dto.marketCap, + "highDay": dto.highDay, + "lowDay": dto.lowDay, + "changePctHour": dto.changePctHour, + "changePctDay": dto.changePctDay, + "changePct24hour": dto.changePct24hour } diff --git a/src/backend/backend.nim b/src/backend/backend.nim index 6688210dab..34f5ef06ec 100644 --- a/src/backend/backend.nim +++ b/src/backend/backend.nim @@ -203,3 +203,10 @@ rpc(addDappPermissions, "permissions"): rpc(deleteDappPermissionsByNameAndAddress, "permissions"): dapp: string address: string + +rpc(fetchMarketValues, "wallet"): + symbols: seq[string] + currency: string + +rpc(fetchTokenDetails, "wallet"): + symbols: seq[string] diff --git a/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml b/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml index 1f4d69ff48..8537e4f426 100644 --- a/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml +++ b/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml @@ -1,5 +1,4 @@ import QtQuick 2.13 -import QtQuick.Controls 2.14 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 @@ -7,6 +6,7 @@ import StatusQ.Components 0.1 import shared 1.0 import shared.panels 1.0 +import shared.controls 1.0 import utils 1.0 import "../popups" @@ -60,35 +60,10 @@ Item { Repeater { id: chainRepeater model: store.enabledNetworks - delegate: Control { - horizontalPadding: Style.current.halfPadding - verticalPadding: 5 - background: Rectangle { - implicitWidth: 66 - implicitHeight: 32 - color: "transparent" - border.width: 1 - border.color: Theme.palette.baseColor2 - radius: 36 - } - - contentItem: Row { - spacing: 4 - // FIXME this could be StatusIcon but it can't load images from an arbitrary URL - Image { - anchors.verticalCenter: parent.verticalCenter - width: 22 - height: 22 - source: Style.svg("tiny/" + model.iconUrl) - } - StatusBaseText { - anchors.verticalCenter: parent.verticalCenter - text: model.shortName - color: model.chainColor - font.pixelSize: Style.current.primaryTextFontSize - font.weight: Font.Medium - } - } + delegate: InformationTag { + tagPrimaryLabel.text: model.shortName + tagPrimaryLabel.color: model.chainColor + image.source: Style.svg("tiny/" + model.iconUrl) } } } diff --git a/ui/app/AppLayouts/Wallet/popups/ReceiveModal.qml b/ui/app/AppLayouts/Wallet/popups/ReceiveModal.qml index bea5d93d49..90fd1cc716 100644 --- a/ui/app/AppLayouts/Wallet/popups/ReceiveModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/ReceiveModal.qml @@ -1,5 +1,4 @@ import QtQuick 2.13 -import QtQuick.Controls 2.13 import QtGraphicalEffects 1.13 import QtQuick.Layouts 1.13 @@ -138,36 +137,10 @@ StatusModal { spacing: 5 Repeater { model: RootStore.enabledNetworks - delegate: Control { - horizontalPadding: Style.current.halfPadding - verticalPadding: 5 - background: Rectangle { - implicitWidth: 66 - implicitHeight: 32 - color: "transparent" - border.width: 1 - border.color: Theme.palette.baseColor2 - radius: 36 - } - - contentItem: Row { - spacing: 4 - // FIXME this could be StatusIcon but it can't load images from an arbitrary URL - Image { - anchors.verticalCenter: parent.verticalCenter - width: 24 - height: 24 - source: Style.svg("tiny/" + model.iconUrl) - } - StatusBaseText { - anchors.verticalCenter: parent.verticalCenter - text: model.shortName - color: model.chainColor - font.pixelSize: Style.current.primaryTextFontSize - font.weight: Font.Medium - } - } - + delegate: InformationTag { + tagPrimaryLabel.text: model.shortName + tagPrimaryLabel.color: model.chainColor + image.source: Style.svg("tiny/" + model.iconUrl) MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index 0e7e380924..5a42aca4b5 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -67,6 +67,11 @@ Item { AssetsView { account: RootStore.currentAccount + assetDetailsLaunched: stack.currentIndex === 2 + onAssetClicked: { + assetDetailView.token = token + stack.currentIndex = 2 + } } CollectiblesView { onCollectibleClicked: { @@ -79,7 +84,14 @@ Item { } } CollectibleDetailView { - anchors.fill: parent + Layout.fillWidth: true + Layout.fillHeight: true + onGoBack: stack.currentIndex = 0 + } + AssetsDetailView { + id: assetDetailView + Layout.fillWidth: true + Layout.fillHeight: true onGoBack: stack.currentIndex = 0 } } diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleCollectionView.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleCollectionView.qml index 644eb21a7f..fcb7fda582 100644 --- a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleCollectionView.qml +++ b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleCollectionView.qml @@ -7,6 +7,7 @@ import StatusQ.Core.Theme 0.1 import shared.panels 1.0 import "../../stores" +import utils 1.0 Item { id: root diff --git a/ui/imports/shared/controls/AssetsDetailsHeader.qml b/ui/imports/shared/controls/AssetsDetailsHeader.qml new file mode 100644 index 0000000000..d78dd5e377 --- /dev/null +++ b/ui/imports/shared/controls/AssetsDetailsHeader.qml @@ -0,0 +1,87 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.14 + +import utils 1.0 +import shared.controls 1.0 + +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 + +Control { + id: root + + property alias primaryText: tokenName.text + property alias secondaryText: cryptoBalance.text + property alias tertiaryText: fiatBalance.text + property var balances + property StatusImageSettings image: StatusImageSettings { + width: 40 + height: 40 + } + property var getNetworkColor: function(chainId){} + property var getNetworkIcon: function(chainId){} + + topPadding: Style.current.padding + + contentItem: Column { + spacing: 4 + Row { + spacing: 8 + StatusSmartIdenticon { + id: identiconLoader + anchors.verticalCenter: parent.verticalCenter + image: root.image + } + StatusBaseText { + id: tokenName + width: Math.min(root.width - identiconLoader.width - cryptoBalance.width - fiatBalance.width - 24, implicitWidth) + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: 22 + lineHeight: 30 + lineHeightMode: Text.FixedHeight + elide: Text.ElideRight + color: Theme.palette.directColor1 + } + StatusBaseText { + id: cryptoBalance + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: 22 + lineHeight: 30 + lineHeightMode: Text.FixedHeight + color: Theme.palette.baseColor1 + } + StatusBaseText { + id: dotSeparator + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -15 + font.pixelSize: 50 + color: Theme.palette.baseColor1 + text: "." + } + StatusBaseText { + id: fiatBalance + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: 22 + lineHeight: 30 + lineHeightMode: Text.FixedHeight + color: Theme.palette.baseColor1 + } + } + Row { + spacing: Style.current.smallPadding + anchors.left: parent.left + anchors.leftMargin: identiconLoader.width + Repeater { + id: chainRepeater + model: balances ? balances : null + delegate: InformationTag { + tagPrimaryLabel.text: model.balance + tagPrimaryLabel.color: root.getNetworkColor(model.chainId) + image.source: Style.svg("tiny/%1".arg(root.getNetworkIcon(model.chainId))) + } + } + } + } +} diff --git a/ui/imports/shared/controls/InformationTag.qml b/ui/imports/shared/controls/InformationTag.qml new file mode 100644 index 0000000000..4bc0697d59 --- /dev/null +++ b/ui/imports/shared/controls/InformationTag.qml @@ -0,0 +1,66 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.13 +import QtQuick.Controls 2.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +import utils 1.0 + +Control { + property alias image : image + property alias iconAsset : iconAsset + property alias tagPrimaryLabel: tagPrimaryLabel + property alias tagSecondaryLabel: tagSecondaryLabel + property alias controlBackground: controlBackground + + horizontalPadding: Style.current.halfPadding + verticalPadding: 5 + + background: Rectangle { + id: controlBackground + implicitWidth: 66 + implicitHeight: 32 + color: "transparent" + border.width: 1 + border.color: Theme.palette.baseColor2 + radius: 36 + } + + contentItem: RowLayout { + spacing: 4 + // FIXME this could be StatusIcon but it can't load images from an arbitrary URL + Image { + id: image + Layout.alignment: Qt.AlignVCenter + Layout.maximumWidth: visible ? 22 : 0 + Layout.maximumHeight: visible ? 22 : 0 + visible: image.source !== "" + } + StatusIcon { + id: iconAsset + Layout.alignment: Qt.AlignVCenter + Layout.maximumWidth: visible ? 22 : 0 + Layout.maximumHeight: visible ? 22 : 0 + visible: iconAsset.icon !== "" + } + StatusBaseText { + id: tagPrimaryLabel + Layout.alignment: Qt.AlignVCenter + font.pixelSize: Style.current.primaryTextFontSize + font.weight: Font.Medium + color: Theme.palette.directColor1 + visible: text !== "" + } + StatusBaseText { + id: tagSecondaryLabel + Layout.alignment: Qt.AlignVCenter + Layout.maximumWidth: 100 + font.pixelSize: Style.current.primaryTextFontSize + font.weight: Font.Medium + color: Theme.palette.baseColor1 + visible: text !== "" + elide: Text.ElideMiddle + } + } +} diff --git a/ui/imports/shared/controls/InformationTile.qml b/ui/imports/shared/controls/InformationTile.qml index 6455c5d7e7..a8c9125ef2 100644 --- a/ui/imports/shared/controls/InformationTile.qml +++ b/ui/imports/shared/controls/InformationTile.qml @@ -12,6 +12,8 @@ Rectangle { property alias primaryText: primaryText.text property alias secondaryText: secondaryText.text + property alias primaryLabel: primaryText + property alias secondaryLabel: secondaryText property alias tagsModel: tags.model property alias tagsDelegate: tags.delegate property int maxWidth: 0 diff --git a/ui/imports/shared/controls/qmldir b/ui/imports/shared/controls/qmldir index fd0b991b7e..8ae2b0199a 100644 --- a/ui/imports/shared/controls/qmldir +++ b/ui/imports/shared/controls/qmldir @@ -26,3 +26,5 @@ TransactionFormGroup 1.0 TransactionFormGroup.qml EmojiHash 1.0 EmojiHash.qml InformationTile 1.0 InformationTile.qml SocialLinkPreview 1.0 SocialLinkPreview.qml +AssetsDetailsHeader 1.0 AssetsDetailsHeader.qml +InformationTag 1.0 InformationTag.qml diff --git a/ui/imports/shared/stores/RootStore.qml b/ui/imports/shared/stores/RootStore.qml index f2f48ab39a..c21fcdb4c6 100644 --- a/ui/imports/shared/stores/RootStore.qml +++ b/ui/imports/shared/stores/RootStore.qml @@ -40,6 +40,23 @@ QtObject { property var walletTokensModule: walletSectionAllTokens property var tokens: walletSectionAllTokens.all + property var accounts: walletSectionAccounts.model + + function getNetworkColor(chainId) { + return networksModule.all.getChainColor(chainId) + } + + function getNetworkIcon(chainId) { + return networksModule.all.getIconUrl(chainId) + } + + function getNetworkIconUrl(symbol) { + return networksModule.all.getNetworkIconUrl(symbol) + } + + function getNetworkName(symbol) { + return networksModule.all.getNetworkName(symbol) + } readonly property var formationChars: (["*", "`", "~"]) function getSelectedTextWithFormationChars(messageInputField) { diff --git a/ui/imports/shared/views/AssetsDetailView.qml b/ui/imports/shared/views/AssetsDetailView.qml new file mode 100644 index 0000000000..80b8f7e772 --- /dev/null +++ b/ui/imports/shared/views/AssetsDetailView.qml @@ -0,0 +1,192 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.13 +import QtQuick.Controls 2.14 +import QtQuick.Window 2.12 + +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 + +import utils 1.0 +import shared.controls 1.0 + +import "../stores" + +Item { + id: root + + property var token + + signal goBack() + + StatusFlatButton { + id: backButton + anchors.top: parent.top + anchors.left: parent.left + anchors.topMargin: -Style.current.xlPadding + anchors.leftMargin: -Style.current.xlPadding + icon.name: "arrow-left" + icon.width: 20 + icon.height: 20 + text: qsTr("Assets") + size: StatusBaseButton.Size.Large + onClicked: root.goBack() + } + + AssetsDetailsHeader { + id: tokenDetailsHeader + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + width: parent.width + image.source: token && token.symbol ? Style.png("tokens/%1".arg(token.symbol)) : "" + primaryText: token ? token.name : "" + secondaryText: token ? qsTr("%1 %2").arg(Utils.toLocaleString(token.totalBalance, RootStore.locale, {"currency": true})).arg(token.symbol) : "" + tertiaryText: token ? "%1 %2".arg(Utils.toLocaleString(token.totalCurrencyBalance.toFixed(2), RootStore.locale, {"currency": true})).arg(RootStore.currencyStore.currentCurrency.toUpperCase()) : "" + balances: token && token.balances ? token.balances : null + getNetworkColor: function(chainId){ + return RootStore.getNetworkColor(chainId) + } + getNetworkIcon: function(chainId){ + return RootStore.getNetworkIcon(chainId) + } + } + + ColumnLayout { + anchors.top: tokenDetailsHeader.bottom + anchors.topMargin: 24 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + width: parent.width + + spacing: Style.current.padding + + RowLayout { + Layout.fillWidth: true + InformationTile { + maxWidth: parent.width + primaryText: qsTr("Market Cap") + secondaryText: token && token.marketCap !== "" ? token.marketCap : "---" + } + InformationTile { + maxWidth: parent.width + primaryText: qsTr("Day Low") + secondaryText: token && token.lowDay !== "" ? token.lowDay : "---" + } + InformationTile { + maxWidth: parent.width + primaryText: qsTr("Day High") + secondaryText: token && token.highDay ? token.highDay : "---" + } + Item { + Layout.fillWidth: true + } + InformationTile { + readonly property string changePctHour: token ? token.changePctHour : "" + maxWidth: parent.width + primaryText: qsTr("Hour") + secondaryText: changePctHour ? "%1%".arg(changePctHour) : "---" + secondaryLabel.color: Math.sign(Number(changePctHour)) === 0 ? Theme.palette.directColor1 : + Math.sign(Number(changePctHour)) === -1 ? Theme.palette.dangerColor1 : + Theme.palette.successColor1 + } + InformationTile { + readonly property string changePctDay: token ? token.changePctDay : "" + maxWidth: parent.width + primaryText: qsTr("Day") + secondaryText: changePctDay ? "%1%".arg(changePctDay) : "---" + secondaryLabel.color: Math.sign(Number(changePctDay)) === 0 ? Theme.palette.directColor1 : + Math.sign(Number(changePctDay)) === -1 ? Theme.palette.dangerColor1 : + Theme.palette.successColor1 + } + InformationTile { + readonly property string changePct24hour: token ? token.changePct24hour : "" + maxWidth: parent.width + primaryText: qsTr("24 Hours") + secondaryText: changePct24hour ? "%1%".arg(changePct24hour) : "---" + secondaryLabel.color: Math.sign(Number(changePct24hour)) === 0 ? Theme.palette.directColor1 : + Math.sign(Number(changePct24hour)) === -1 ? Theme.palette.dangerColor1 : + Theme.palette.successColor1 + } + } + + StatusTabBar { + Layout.fillWidth: true + Layout.topMargin: Style.current.xlPadding + + StatusTabButton { + leftPadding: 0 + width: implicitWidth + text: qsTr("Overview") + } + } + + StackLayout { + id: stack + Layout.fillWidth: true + Layout.fillHeight: true + StatusScrollView { + id: scrollView + Layout.preferredWidth: parent.width + Layout.preferredHeight: parent.height + ScrollBar.horizontal.policy: ScrollBar.AsNeeded + topPadding: 8 + bottomPadding: 8 + Flow { + id: detailsFlow + + readonly property bool isOverflowing: detailsFlow.width - tagsLayout.width - tokenDescriptionText.width < 24 + + spacing: 24 + + width: scrollView.availableWidth + StatusBaseText { + id: tokenDescriptionText + width: Math.max(536 , scrollView.availableWidth - tagsLayout.width - 24) + + font.pixelSize: 15 + lineHeight: 22 + lineHeightMode: Text.FixedHeight + text: token ? token.description : "" + color: Theme.palette.directColor1 + elide: Text.ElideRight + wrapMode: Text.Wrap + textFormat: Qt.RichText + } + ColumnLayout { + id: tagsLayout + spacing: 10 + InformationTag { + id: website + Layout.alignment: detailsFlow.isOverflowing ? Qt.AlignLeft : Qt.AlignRight + iconAsset.icon: "browser" + tagPrimaryLabel.text: qsTr("Website") + controlBackground.color: Theme.palette.baseColor2 + controlBackground.border.color: "transparent" + visible: token && token.assetWebsiteUrl !== "" + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Global.openLink(token.assetWebsiteUrl) + } + } + InformationTag { + id: smartContractAddress + Layout.alignment: detailsFlow.isOverflowing ? Qt.AlignLeft : Qt.AlignRight + + image.source: token && token.builtOn !== "" ? Style.svg("tiny/" + RootStore.getNetworkIconUrl(token.builtOn)) : "" + tagPrimaryLabel.text: token && token.builtOn !== "" ? RootStore.getNetworkName(token.builtOn) : "---" + tagSecondaryLabel.text: token && token.smartContractAddress !== "" ? token.smartContractAddress : "---" + controlBackground.color: Theme.palette.baseColor2 + controlBackground.border.color: "transparent" + visible: token && token.builtOn !== "" && token.smartContractAddress !== "" + } + } + } + } + } + } +} diff --git a/ui/imports/shared/views/AssetsView.qml b/ui/imports/shared/views/AssetsView.qml index 39ef27a338..fd5cf01f9b 100644 --- a/ui/imports/shared/views/AssetsView.qml +++ b/ui/imports/shared/views/AssetsView.qml @@ -1,32 +1,66 @@ import QtQuick 2.13 -import QtQuick.Layouts 1.3 import QtQuick.Controls 2.14 import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 + +import SortFilterProxyModel 0.2 import utils 1.0 -import shared 1.0 import "../stores" -import "../controls" Item { + id: root + property var account + property bool assetDetailsLaunched: false + + signal assetClicked(var token) + + QtObject { + id: d + readonly property var alwaysVisible : ["ETH", "SNT", "DAI", "STT"] + property int selectedAssetIndex: -1 + } height: assetListView.height StatusListView { id: assetListView - anchors.fill: parent - model: account.assets objectName: "assetViewStatusListView" - delegate: AssetDelegate { - locale: RootStore.locale - currency: RootStore.currentCurrency - currencySymbol: RootStore.currencyStore.currentCurrencySymbol + anchors.fill: parent + model: SortFilterProxyModel { + sourceModel: account.assets + filters: [ + ExpressionFilter { + expression: d.alwaysVisible.includes(symbol) || (networkVisible && enabledNetworkBalance > 0) + } + ] + } + delegate: StatusListItem { + width: parent.width + title: name + subTitle: qsTr("%1 %2").arg(Utils.toLocaleString(enabledNetworkBalance, RootStore.locale, {"currency": true})).arg(symbol) + image.source: symbol ? Style.png("tokens/" + symbol) : "" + components: [ + StatusBaseText { + font.pixelSize: 15 + font.strikeout: false + text: enabledNetworkCurrencyBalance.toLocaleCurrencyString(Qt.locale(), RootStore.currencyStore.currentCurrencySymbol) + } + ] + onClicked: { + d.selectedAssetIndex = index + assetClicked(model) + } + Component.onCompleted: { + // on Model reset if the detail view is shown, update the data in background. + if(root.assetDetailsLaunched && index === d.selectedAssetIndex) + assetClicked(model) + } } - Layout.fillWidth: true - Layout.fillHeight: true ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } } diff --git a/ui/imports/shared/views/qmldir b/ui/imports/shared/views/qmldir index b3711e0c9c..7b478465c6 100644 --- a/ui/imports/shared/views/qmldir +++ b/ui/imports/shared/views/qmldir @@ -10,3 +10,4 @@ PasswordView 1.0 PasswordView.qml ProfileView 1.0 ProfileView.qml AssetsView 1.0 AssetsView.qml HistoryView 1.0 HistoryView.qml +AssetsDetailView 1.0 AssetsDetailView.qml diff --git a/vendor/status-go b/vendor/status-go index e6a3f63ec7..25a80cf20b 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit e6a3f63ec7f2fa691878ed35f921413dc8acfc66 +Subproject commit 25a80cf20b2fd6153eccdb3cd154126181b86561