fix(@desktop/wallet): show all tokens in wallet that have balance > 0

Fixes #5859
This commit is contained in:
Sale Djenic 2022-05-24 10:46:59 +02:00 committed by saledjenic
parent 64882dffbe
commit 3a3b2c9dc9
9 changed files with 436 additions and 217 deletions

View File

@ -1,8 +1,10 @@
import NimQml
import NimQml, Tables
import ../../../../global/global_singleton
import ../../../../core/eventemitter
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../shared_models/token_model as token_model
import ../../../shared_models/token_item as token_item
import ./io_interface, ./view, ./controller
import ../io_interface as delegate_interface
@ -18,6 +20,8 @@ type
moduleLoaded: bool
currentAccountIndex: int
proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[WalletTokenDto]])
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
@ -34,10 +38,21 @@ proc newModule*(
method delete*(self: Module) =
self.view.delete
proc setAssets(self: Module, tokens: seq[WalletTokenDto]) =
var items: seq[Item]
for t in tokens:
if(t.totalBalance.balance == 0):
continue
let item = token_item.initItem(t.name, t.symbol, t.totalBalance.balance, t.address, t.totalBalance.currencyBalance)
items.add(item)
self.view.getAssetsModel().setItems(items)
method switchAccount*(self: Module, accountIndex: int) =
self.currentAccountIndex = accountIndex
let walletAccount = self.controller.getWalletAccount(accountIndex)
self.view.setData(walletAccount)
self.setAssets(walletAccount.tokens)
method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("browserSectionCurrentAccount", newQVariant(self.view))
@ -47,6 +62,10 @@ method load*(self: Module) =
self.switchAccount(0)
self.view.connectedAccountDeleted()
self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args):
let arg = TokensPerAccountArgs(e)
self.onTokensRebuilt(arg.accountsTokens)
self.controller.init()
self.view.load()
self.switchAccount(0)
@ -60,3 +79,9 @@ method viewDidLoad*(self: Module) =
method switchAccountByAddress*(self: Module, address: string) =
let accountIndex = self.controller.getIndex(address)
self.switchAccount(accountIndex)
proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[WalletTokenDto]]) =
let walletAccount = self.controller.getWalletAccount(self.currentAccountIndex)
if not accountsTokens.contains(walletAccount.address):
return
self.setAssets(accountsTokens[walletAccount.address])

View File

@ -25,12 +25,14 @@ QtObject:
self.QObject.setup
proc delete*(self: View) =
self.assets.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.delegate = delegate
result.setup()
result.delegate = delegate
result.assets = token_model.newModel()
proc load*(self: View) =
self.delegate.viewDidLoad()
@ -107,11 +109,12 @@ QtObject:
read = getCurrencyBalance
notify = currencyBalanceChanged
proc getAssets(self: View): QVariant {.slot.} =
return newQVariant(self.assets)
proc getAssetsModel*(self: View): token_model.Model =
return self.assets
proc assetsChanged(self: View) {.signal.}
proc getAssets*(self: View): QVariant {.slot.} =
return newQVariant(self.assets)
QtProperty[QVariant] assets:
read = getAssets
notify = assetsChanged
@ -151,20 +154,6 @@ proc setData*(self: View, dto: wallet_account_service.WalletAccountDto) =
self.emoji = dto.emoji
self.emojiChanged()
let assets = token_model.newModel()
assets.setItems(
dto.tokens.map(t => token_item.initItem(
t.name,
t.symbol,
t.balance.chainBalance,
t.address,
t.balance.currencyBalance,
))
)
self.assets = assets
self.assetsChanged()
proc isAddressCurrentAccount*(self: View, address: string): bool =
return self.address == address

View File

@ -46,9 +46,9 @@ method refreshWalletAccounts*(self: Module) =
w.tokens.map(t => token_item.initItem(
t.name,
t.symbol,
t.balance.chainBalance,
t.totalBalance.balance,
t.address,
t.balance.currencyBalance,
t.totalBalance.currencyBalance,
))
)

View File

@ -1,8 +1,10 @@
import NimQml
import NimQml, Tables
import ../../../../global/global_singleton
import ../../../../core/eventemitter
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../shared_models/token_model as token_model
import ../../../shared_models/token_item as token_item
import ./io_interface, ./view, ./controller
import ../io_interface as delegate_interface
@ -18,6 +20,8 @@ type
moduleLoaded: bool
currentAccountIndex: int
proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[WalletTokenDto]])
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
@ -50,6 +54,10 @@ method load*(self: Module) =
self.events.on(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED) do(e: Args):
self.switchAccount(self.currentAccountIndex)
self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args):
let arg = TokensPerAccountArgs(e)
self.onTokensRebuilt(arg.accountsTokens)
self.controller.init()
self.view.load()
@ -60,10 +68,30 @@ method viewDidLoad*(self: Module) =
self.moduleLoaded = true
self.delegate.currentAccountModuleDidLoad()
proc setAssetsAndBalance(self: Module, tokens: seq[WalletTokenDto]) =
var totalCurrencyBalanceForAllAssets = 0.0
var items: seq[Item]
for t in tokens:
if(t.totalBalance.balance == 0):
continue
let item = token_item.initItem(t.name, t.symbol, t.totalBalance.balance, t.address, t.totalBalance.currencyBalance)
items.add(item)
totalCurrencyBalanceForAllAssets += t.totalBalance.currencyBalance
self.view.getAssetsModel().setItems(items)
self.view.setCurrencyBalance(totalCurrencyBalanceForAllAssets)
method switchAccount*(self: Module, accountIndex: int) =
self.currentAccountIndex = accountIndex
let walletAccount = self.controller.getWalletAccount(accountIndex)
self.view.setData(walletAccount)
self.setAssetsAndBalance(walletAccount.tokens)
method update*(self: Module, address: string, accountName: string, color: string, emoji: string) =
self.controller.update(address, accountName, color, emoji)
proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[WalletTokenDto]]) =
let walletAccount = self.controller.getWalletAccount(self.currentAccountIndex)
if not accountsTokens.contains(walletAccount.address):
return
self.setAssetsAndBalance(accountsTokens[walletAccount.address])

View File

@ -25,12 +25,14 @@ QtObject:
self.QObject.setup
proc delete*(self: View) =
self.assets.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.delegate = delegate
result.setup()
result.delegate = delegate
result.assets = token_model.newModel()
proc load*(self: View) =
self.delegate.viewDidLoad()
@ -103,20 +105,22 @@ QtObject:
read = getIsChat
notify = isChatChanged
proc getCurrencyBalance(self: View): QVariant {.slot.} =
return newQVariant(self.currencyBalance)
proc currencyBalanceChanged(self: View) {.signal.}
proc getCurrencyBalance*(self: View): QVariant {.slot.} =
return newQVariant(self.currencyBalance)
proc setCurrencyBalance*(self: View, value: float) =
self.currencyBalance = value
self.currencyBalanceChanged()
QtProperty[QVariant] currencyBalance:
read = getCurrencyBalance
notify = currencyBalanceChanged
proc getAssets(self: View): QVariant {.slot.} =
return newQVariant(self.assets)
proc getAssetsModel*(self: View): token_model.Model =
return self.assets
proc assetsChanged(self: View) {.signal.}
proc getAssets*(self: View): QVariant {.slot.} =
return newQVariant(self.assets)
QtProperty[QVariant] assets:
read = getAssets
notify = assetsChanged
@ -158,23 +162,6 @@ QtObject:
if(self.isChat != dto.isChat):
self.isChat = dto.isChat
self.isChatChanged()
if(self.currencyBalance != dto.getCurrencyBalance()):
self.currencyBalance = dto.getCurrencyBalance()
self.currencyBalanceChanged()
if(self.emoji != dto.emoji):
self.emoji = dto.emoji
self.emojiChanged()
let assets = token_model.newModel()
assets.setItems(
dto.tokens.map(t => token_item.initItem(
t.name,
t.symbol,
t.balance.chainBalance,
t.address,
t.balance.currencyBalance,
))
)
self.assets = assets
self.assetsChanged()
self.emojiChanged()

View File

@ -1,6 +1,5 @@
import os, json, sequtils, strutils, uuids
import json_serialization, chronicles
import times as times
import ./dto/accounts as dto_accounts
import ./dto/generated_accounts as dto_generated_accounts

View File

@ -72,3 +72,206 @@ const getDerivedAddressForPrivateKeyTask*: Task = proc(argEncoded: string) {.gcs
}
arg.finish(output)
#################################################
# Async timer
#################################################
type
TimerTaskArg = ref object of QObjectTaskArg
timeoutInMilliseconds: int
const timerTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[TimerTaskArg](argEncoded)
sleep(arg.timeoutInMilliseconds)
arg.finish("")
#################################################
# Async building token
#################################################
type
BuildTokensTaskArg = ref object of QObjectTaskArg
walletAddresses: seq[string]
currency: string
networks: seq[NetworkDto]
proc getCustomTokens(): seq[TokenDto] =
try:
let responseCustomTokens = backend.getCustomTokens()
result = map(responseCustomTokens.result.getElems(), proc(x: JsonNode): TokenDto = x.toTokenDto(@[]))
except Exception as e:
error "error fetching custom tokens: ", message = e.msg
proc getTokensForChainId(chainId: int): seq[TokenDto] =
try:
let responseTokens = backend.getTokens(chainId)
let defaultTokens = map(responseTokens.result.getElems(),
proc(x: JsonNode): TokenDto = x.toTokenDto(@[], hasIcon=true, isCustom=false)
)
result.add(defaultTokens)
except Exception as e:
error "error fetching tokens: ", message = e.msg, chainId=chainId
proc prepareSymbols(networkSymbols: seq[string], allTokens: seq[TokenDto]): seq[seq[string]] =
# we have to use up to 300 characters in a single request when we're fetching prices
let charsMaxLenght = 300
result.add(@[])
var networkSymbolsIndex = 0
var tokenSymbolsIndex = 0
while networkSymbolsIndex < networkSymbols.len or tokenSymbolsIndex < allTokens.len:
var currentCharsLen = 0
var reachTheEnd = false
while networkSymbolsIndex < networkSymbols.len:
if(currentCharsLen + networkSymbols[networkSymbolsIndex].len >= charsMaxLenght):
reachTheEnd = true
result.add(@[])
break
else:
currentCharsLen += networkSymbols[networkSymbolsIndex].len + 1 # we add one for ','
result[result.len - 1].add(networkSymbols[networkSymbolsIndex])
networkSymbolsIndex.inc
while not reachTheEnd and tokenSymbolsIndex < allTokens.len:
if(currentCharsLen + allTokens[tokenSymbolsIndex].symbol.len >= charsMaxLenght):
reachTheEnd = true
result.add(@[])
break
else:
currentCharsLen += allTokens[tokenSymbolsIndex].symbol.len + 1 # we add one for ','
result[result.len - 1].add(allTokens[tokenSymbolsIndex].symbol)
tokenSymbolsIndex.inc
proc fetchNativeChainBalance(chainId: int, nativeCurrencyDecimals: int, accountAddress: string): float64 =
result = 0.0
try:
let nativeBalanceResponse = status_go_eth.getNativeChainBalance(chainId, accountAddress)
result = parsefloat(hex2Balance(nativeBalanceResponse.result.getStr, nativeCurrencyDecimals))
except Exception as e:
error "error getting balance", message = e.msg
proc fetchPrices(networkSymbols: seq[string], allTokens: seq[TokenDto], currency: string): Table[string, float] =
let allSymbols = prepareSymbols(networkSymbols, allTokens)
for symbols in allSymbols:
try:
let response = backend.fetchPrices(symbols, currency)
for (symbol, value) in response.result.pairs:
result[symbol] = value.getFloat
except Exception as e:
error "error fetching prices: ", message = e.msg
proc getTokensBalances(walletAddresses: seq[string], allTokens: seq[TokenDto]): JsonNode =
try:
result = newJObject()
let tokensAddresses = allTokens.map(t => t.addressAsString())
# We need to check, we should use `chainIdsFromSettings` instead `chainIds` deduced from the allTokens list?
let chainIds = deduplicate(allTokens.map(t => t.chainId))
let tokensBalancesResponse = backend.getTokensBalancesForChainIDs(chainIds, walletAddresses, tokensAddresses)
result = tokensBalancesResponse.result
except Exception as e:
error "error fetching tokens balances: ", message = e.msg
proc groupNetworksBySymbol(networks: seq[NetworkDto]): Table[string, seq[NetworkDto]] =
for network in networks:
if not result.hasKey(network.nativeCurrencySymbol):
result[network.nativeCurrencySymbol] = @[]
result[network.nativeCurrencySymbol].add(network)
proc getNetworkByCurrencySymbol(networks: seq[NetworkDto], networkNativeCurrencySymbol: string): NetworkDto =
for network in networks:
if network.nativeCurrencySymbol != networkNativeCurrencySymbol:
continue
return network
proc groupTokensBySymbol(tokens: seq[TokenDto]): Table[string, seq[TokenDto]] =
for token in tokens:
if not result.hasKey(token.symbol):
result[token.symbol] = @[]
result[token.symbol].add(token)
proc getTokenForSymbol(tokens: seq[TokenDto], symbol: string): TokenDto =
for token in tokens:
if token.symbol != symbol:
continue
return token
const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[BuildTokensTaskArg](argEncoded)
var networkSymbols: seq[string]
var chainIdsFromSettings: seq[int]
for network in arg.networks:
networkSymbols.add(network.nativeCurrencySymbol)
chainIdsFromSettings.add(network.chainId)
var allTokens: seq[TokenDto]
allTokens.add(getCustomTokens())
for chainId in chainIdsFromSettings:
allTokens.add(getTokensForChainId(chainId))
allTokens = deduplicate(allTokens)
var prices = fetchPrices(networkSymbols, allTokens, arg.currency)
let tokenBalances = getTokensBalances(arg.walletAddresses, allTokens)
var builtTokensPerAccount = %*{ }
for address in arg.walletAddresses:
var builtTokens: seq[WalletTokenDto]
let groupedNetworks = groupNetworksBySymbol(arg.networks)
for networkNativeCurrencySymbol, networks in groupedNetworks.pairs:
var balancesPerChain = initTable[int, BalanceDto]()
for network in networks:
let chainBalance = fetchNativeChainBalance(network.chainId, network.nativeCurrencyDecimals, address)
balancesPerChain[network.chainId] = BalanceDto(
balance: chainBalance,
currencyBalance: chainBalance * prices[network.nativeCurrencySymbol]
)
let networkDto = getNetworkByCurrencySymbol(arg.networks, networkNativeCurrencySymbol)
var totalTokenBalance: BalanceDto
totalTokenBalance.balance = toSeq(balancesPerChain.values).map(x => x.balance).foldl(a + b)
totalTokenBalance.currencyBalance = totalTokenBalance.balance * prices[networkDto.nativeCurrencySymbol]
builtTokens.add(WalletTokenDto(
name: networkDto.nativeCurrencyName,
address: "0x0000000000000000000000000000000000000000",
symbol: networkDto.nativeCurrencySymbol,
decimals: networkDto.nativeCurrencyDecimals,
hasIcon: true,
color: "blue",
isCustom: false,
totalBalance: totalTokenBalance,
balancesPerChain: balancesPerChain
)
)
let groupedTokens = groupTokensBySymbol(allTokens)
for symbol, tokens in groupedTokens.pairs:
var balancesPerChain = initTable[int, BalanceDto]()
for token in tokens:
let balanceForToken = tokenBalances{address}{token.addressAsString()}.getStr
let chainBalanceForToken = parsefloat(hex2Balance(balanceForToken, token.decimals))
balancesPerChain[token.chainId] = BalanceDto(
balance: chainBalanceForToken,
currencyBalance: chainBalanceForToken * prices[token.symbol]
)
let tokenDto = getTokenForSymbol(allTokens, symbol)
var totalTokenBalance: BalanceDto
totalTokenBalance.balance = toSeq(balancesPerChain.values).map(x => x.balance).foldl(a + b)
totalTokenBalance.currencyBalance = totalTokenBalance.balance * prices[tokenDto.symbol]
builtTokens.add(WalletTokenDto(
name: tokenDto.name,
address: $tokenDto.address,
symbol: tokenDto.symbol,
decimals: tokenDto.decimals,
hasIcon: tokenDto.hasIcon,
color: tokenDto.color,
isCustom: tokenDto.isCustom,
totalBalance: totalTokenBalance,
balancesPerChain: balancesPerChain
)
)
var tokensJArray = newJArray()
for wtDto in builtTokens:
tokensJarray.add(walletTokenDtoToJson(wtDto))
builtTokensPerAccount[address] = tokensJArray
arg.finish(builtTokensPerAccount)

View File

@ -1,13 +1,13 @@
import tables, json, sequtils, sugar
import tables, json, sequtils, sugar, strutils
include ../../common/json_utils
type BalanceDto* = ref object of RootObj
chainBalance*: float64
type BalanceDto* = object
balance*: float64
currencyBalance*: float64
type
WalletTokenDto* = ref object of RootObj
WalletTokenDto* = object
name*: string
address*: string
symbol*: string
@ -15,8 +15,8 @@ type
hasIcon*: bool
color*: string
isCustom*: bool
balance*: BalanceDto
balances*: Table[int, BalanceDto]
totalBalance*: BalanceDto
balancesPerChain*: Table[int, BalanceDto]
type
WalletAccountDto* = ref object of RootObj
@ -73,4 +73,45 @@ proc toWalletAccountDto*(jsonObj: JsonNode): WalletAccountDto =
discard jsonObj.getProp("derived-from", result.derivedfrom)
proc getCurrencyBalance*(self: WalletAccountDto): float64 =
return self.tokens.map(t => t.balance.currencyBalance).foldl(a + b, 0.0)
return self.tokens.map(t => t.totalBalance.currencyBalance).foldl(a + b, 0.0)
proc toBalanceDto*(jsonObj: JsonNode): BalanceDto =
result = BalanceDto()
discard jsonObj.getProp("balance", result.balance)
discard jsonObj.getProp("currencyBalance", result.currencyBalance)
proc toWalletTokenDto*(jsonObj: JsonNode): WalletTokenDto =
result = WalletTokenDto()
discard jsonObj.getProp("name", result.name)
discard jsonObj.getProp("address", result.address)
discard jsonObj.getProp("symbol", result.symbol)
discard jsonObj.getProp("decimals", result.decimals)
discard jsonObj.getProp("hasIcon", result.hasIcon)
discard jsonObj.getProp("color", result.color)
discard jsonObj.getProp("isCustom", result.isCustom)
var totalBalanceObj: JsonNode
if(jsonObj.getProp("totalBalance", totalBalanceObj)):
result.totalBalance = toBalanceDto(totalBalanceObj)
var balancesPerChainObj: JsonNode
if(jsonObj.getProp("balancesPerChain", balancesPerChainObj)):
for chainId, balanceObj in balancesPerChainObj:
result.balancesPerChain[parseInt(chainId)] = toBalanceDto(balanceObj)
proc walletTokenDtoToJson*(dto: WalletTokenDto): JsonNode =
var balancesPerChainJsonObj = newJObject()
for k, v in dto.balancesPerChain.pairs:
balancesPerChainJsonObj[$k] = %* v
result = %* {
"name": dto.name,
"address": dto.address,
"symbol": dto.symbol,
"decimals": dto.decimals,
"hasIcon": dto.hasIcon,
"color": dto.color,
"isCustom": dto.isCustom,
"totalBalance": %* dto.totalBalance,
"balancesPerChain": balancesPerChainJsonObj
}

View File

@ -1,4 +1,4 @@
import Tables, NimQml, json, sequtils, sugar, chronicles, strformat, stint, httpclient, net, strutils
import NimQml, Tables, json, sequtils, sugar, chronicles, strformat, stint, httpclient, net, strutils, os
import web3/[ethtypes, conversions]
import ../settings/service as settings_service
@ -6,6 +6,7 @@ import ../accounts/service as accounts_service
import ../token/service as token_service
import ../network/service as network_service
import ../../common/account_constants
import ../../../app/global/global_singleton
import dto
import derived_address
@ -23,8 +24,6 @@ export derived_address
logScope:
topics = "wallet-account-service"
include async_tasks
const SIGNAL_WALLET_ACCOUNT_SAVED* = "walletAccount/accountSaved"
const SIGNAL_WALLET_ACCOUNT_DELETED* = "walletAccount/accountDeleted"
const SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED* = "walletAccount/currencyUpdated"
@ -32,6 +31,7 @@ const SIGNAL_WALLET_ACCOUNT_TOKEN_VISIBILITY_UPDATED* = "walletAccount/tokenVisi
const SIGNAL_WALLET_ACCOUNT_UPDATED* = "walletAccount/walletAccountUpdated"
const SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED* = "walletAccount/networkEnabledUpdated"
const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY* = "walletAccount/derivedAddressesReady"
const SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT* = "walletAccount/tokensRebuilt"
var
balanceCache {.threadvar.}: Table[string, float64]
@ -56,19 +56,6 @@ proc fetchAccounts(): seq[WalletAccountDto] =
x => x.toWalletAccountDto()
).filter(a => not a.isChat)
proc fetchNativeChainBalance(network: NetworkDto, accountAddress: string): float64 =
let key = "0x0" & accountAddress & $network.chainId
if balanceCache.hasKey(key):
return balanceCache[key]
try:
let nativeBalanceResponse = status_go_eth.getNativeChainBalance(network.chainId, accountAddress)
result = parsefloat(hex2Balance(nativeBalanceResponse.result.getStr, network.nativeCurrencyDecimals))
balanceCache[key] = result
except Exception as e:
error "Error getting balance", message = e.msg
result = 0.0
type AccountSaved = ref object of Args
account: WalletAccountDto
@ -88,20 +75,33 @@ type DerivedAddressesArgs* = ref object of Args
derivedAddresses*: seq[DerivedAddressDto]
error*: string
type TokensPerAccountArgs* = ref object of Args
accountsTokens*: OrderedTable[string, seq[WalletTokenDto]] # [wallet address, list of tokens]
const CheckBalanceIntervalInMilliseconds = 15 * 60 * 1000 # 15 mins
include async_tasks
include ../../common/json_utils
QtObject:
type Service* = ref object of QObject
events: EventEmitter
threadpool: ThreadPool
settingsService: settings_service.Service
accountsService: accounts_service.Service
tokenService: token_service.Service
networkService: network_service.Service
accounts: OrderedTable[string, WalletAccountDto]
closingApp: bool
ignoreTimeInitiatedTokensBuild: bool
events: EventEmitter
threadpool: ThreadPool
settingsService: settings_service.Service
accountsService: accounts_service.Service
tokenService: token_service.Service
networkService: network_service.Service
walletAccounts: OrderedTable[string, WalletAccountDto]
priceCache: TimedCache
priceCache: TimedCache
# Forward declaration
proc buildAllTokens(self: Service, calledFromTimerOrInit = false)
proc delete*(self: Service) =
self.closingApp = true
self.QObject.delete
proc newService*(
@ -114,88 +114,17 @@ QtObject:
): Service =
new(result, delete)
result.QObject.setup
result.closingApp = false
result.ignoreTimeInitiatedTokensBuild = false
result.events = events
result.threadpool = threadpool
result.settingsService = settingsService
result.accountsService = accountsService
result.tokenService = tokenService
result.networkService = networkService
result.accounts = initOrderedTable[string, WalletAccountDto]()
result.walletAccounts = initOrderedTable[string, WalletAccountDto]()
result.priceCache = newTimedCache()
proc buildTokens(
self: Service,
account: WalletAccountDto,
prices: Table[string, float],
tokenBalances: JsonNode
): seq[WalletTokenDto] =
var groupedNetwork = initTable[string, seq[NetworkDto]]()
for network in self.networkService.getEnabledNetworks():
if not groupedNetwork.hasKey(network.nativeCurrencyName):
groupedNetwork[network.nativeCurrencyName] = @[]
groupedNetwork[network.nativeCurrencyName].add(network)
for currencyName, networks in groupedNetwork.pairs:
var balances = initTable[int, BalanceDto]()
for network in networks:
let chainBalance = fetchNativeChainBalance(network, account.address)
balances[network.chainId] = BalanceDto(
chainBalance: chainBalance,
currencyBalance: chainBalance * prices[network.nativeCurrencySymbol]
)
let totalChainBalance = toSeq(balances.values).map(x => x.chainBalance).foldl(a + b)
let balance = BalanceDto(
chainBalance: totalChainBalance,
currencyBalance: totalChainBalance * prices[networks[0].nativeCurrencySymbol]
)
result.add(WalletTokenDto(
name: currencyName,
address: "0x0000000000000000000000000000000000000000",
symbol: networks[0].nativeCurrencySymbol,
decimals: networks[0].nativeCurrencyDecimals,
hasIcon: true,
color: "blue",
isCustom: false,
balance: balance,
balances: balances
))
var groupedToken = initTable[string, seq[TokenDto]]()
for token in self.tokenService.getVisibleTokens():
if not groupedToken.hasKey(token.symbol):
groupedToken[token.symbol] = @[]
groupedToken[token.symbol].add(token)
for symbol, tokens in groupedToken.pairs:
var balances = initTable[int, BalanceDto]()
for token in tokens:
let chainBalance = parsefloat(hex2Balance(tokenBalances{token.addressAsString()}.getStr, token.decimals))
balances[token.chainId] = BalanceDto(
chainBalance: chainBalance,
currencyBalance: chainBalance * prices[symbol]
)
let totalChainBalance = toSeq(balances.values).map(x => x.chainBalance).foldl(a + b)
let balance = BalanceDto(
chainBalance: totalChainBalance,
currencyBalance: totalChainBalance * prices[symbol]
)
result.add(
WalletTokenDto(
name: tokens[0].name,
address: $tokens[0].address,
symbol: symbol,
decimals: tokens[0].decimals,
hasIcon: tokens[0].hasIcon,
color: tokens[0].color,
isCustom: tokens[0].isCustom,
balance: balance,
balances: balances
)
)
proc getPrice*(self: Service, crypto: string, fiat: string): float64 =
let cacheKey = crypto & fiat
if self.priceCache.isCached(cacheKey):
@ -214,68 +143,27 @@ QtObject:
error "error: ", errDesription
return 0.0
proc fetchPrices(self: Service): Table[string, float] =
let currency = self.settingsService.getCurrency()
var symbols: seq[string] = @[]
for network in self.networkService.getEnabledNetworks():
symbols.add(network.nativeCurrencySymbol)
for token in self.tokenService.getVisibleTokens():
symbols.add(token.symbol)
var prices = initTable[string, float]()
if symbols.len == 0:
return prices
try:
let response = backend.fetchPrices(symbols, currency)
for (symbol, value) in response.result.pairs:
prices[symbol] = value.getFloat
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
return prices
proc fetchBalances(self: Service, accounts: seq[string]): JsonNode =
let visibleTokens = self.tokenService.getVisibleTokens()
let tokens = visibleTokens.map(t => t.addressAsString())
let chainIds = visibleTokens.map(t => t.chainId)
return backend.getTokensBalancesForChainIDs(chainIds, accounts, tokens).result
proc refreshBalances(self: Service) =
let prices = self.fetchPrices()
let accounts = toSeq(self.accounts.keys)
let balances = self.fetchBalances(accounts)
for account in toSeq(self.accounts.values):
account.tokens = self.buildTokens(account, prices, balances{account.address})
proc init*(self: Service) =
signalConnect(singletonInstance.localAccountSensitiveSettings, "isWalletEnabledChanged()", self, "onIsWalletEnabledChanged()", 2)
try:
let accounts = fetchAccounts()
for account in accounts:
self.accounts[account.address] = account
self.walletAccounts[account.address] = account
self.refreshBalances()
self.buildAllTokens(true)
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
return
proc getAccountByAddress*(self: Service, address: string): WalletAccountDto =
if not self.accounts.hasKey(address):
if not self.walletAccounts.hasKey(address):
return
return self.accounts[address]
return self.walletAccounts[address]
proc getWalletAccounts*(self: Service): seq[WalletAccountDto] =
return toSeq(self.accounts.values)
return toSeq(self.walletAccounts.values)
proc getWalletAccount*(self: Service, accountIndex: int): WalletAccountDto =
if(accountIndex < 0 or accountIndex >= self.getWalletAccounts().len):
@ -293,17 +181,13 @@ QtObject:
proc addNewAccountToLocalStore(self: Service) =
let accounts = fetchAccounts()
let prices = self.fetchPrices()
var newAccount = accounts[0]
for account in accounts:
if not self.accounts.haskey(account.address):
if not self.walletAccounts.haskey(account.address):
newAccount = account
break
let balances = self.fetchBalances(@[newAccount.address])
newAccount.tokens = self.buildTokens(newAccount, prices, balances{newAccount.address})
self.accounts[newAccount.address] = newAccount
self.walletAccounts[newAccount.address] = newAccount
self.buildAllTokens()
self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountSaved(account: newAccount))
proc generateNewAccount*(self: Service, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
@ -363,35 +247,35 @@ QtObject:
proc deleteAccount*(self: Service, address: string) =
discard status_go_accounts.deleteAccount(address)
let accountDeleted = self.accounts[address]
self.accounts.del(address)
let accountDeleted = self.walletAccounts[address]
self.walletAccounts.del(address)
self.events.emit(SIGNAL_WALLET_ACCOUNT_DELETED, AccountDeleted(account: accountDeleted))
proc updateCurrency*(self: Service, newCurrency: string) =
discard self.settingsService.saveCurrency(newCurrency)
self.refreshBalances()
self.buildAllTokens()
self.events.emit(SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED, CurrencyUpdated())
proc toggleTokenVisible*(self: Service, chainId: int, address: string) =
self.tokenService.toggleVisible(chainId, address)
self.refreshBalances()
self.buildAllTokens()
self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKEN_VISIBILITY_UPDATED, TokenVisibilityToggled())
proc toggleNetworkEnabled*(self: Service, chainId: int) =
self.networkService.toggleNetwork(chainId)
self.tokenService.init()
self.refreshBalances()
self.buildAllTokens()
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, NetwordkEnabledToggled())
method toggleTestNetworksEnabled*(self: Service) =
discard self.settings_service.toggleTestNetworksEnabled()
discard self.settingsService.toggleTestNetworksEnabled()
self.tokenService.init()
self.refreshBalances()
self.buildAllTokens()
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, NetwordkEnabledToggled())
proc updateWalletAccount*(self: Service, address: string, accountName: string, color: string, emoji: string) =
let account = self.accounts[address]
let account = self.walletAccounts[address]
status_go_accounts.updateAccount(
accountName,
account.address,
@ -452,4 +336,67 @@ QtObject:
error: error
))
proc onStartBuildingTokensTimer*(self: Service, response: string) {.slot.} =
if self.ignoreTimeInitiatedTokensBuild:
self.ignoreTimeInitiatedTokensBuild = false
return
self.buildAllTokens(true)
proc startBuildingTokensTimer(self: Service) =
if(self.closingApp):
return
let arg = TimerTaskArg(
tptr: cast[ByteAddress](timerTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onStartBuildingTokensTimer",
timeoutInMilliseconds: CheckBalanceIntervalInMilliseconds
)
self.threadpool.start(arg)
proc onAllTokensBuilt*(self: Service, response: string) {.slot.} =
let responseObj = response.parseJson
if (responseObj.kind != JObject):
info "prepared tokens are not a json object"
return
var data = TokensPerAccountArgs()
let walletAddresses = toSeq(self.walletAccounts.keys)
for wAddress in walletAddresses:
var tokensArr: JsonNode
var tokens: seq[WalletTokenDto]
if(responseObj.getProp(wAddress, tokensArr)):
tokens = map(tokensArr.getElems(), proc(x: JsonNode): WalletTokenDto = x.toWalletTokenDto())
self.walletAccounts[wAddress].tokens = tokens
data.accountsTokens[wAddress] = tokens
self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT, data)
# run timer again...
self.startBuildingTokensTimer()
proc buildAllTokens(self: Service, calledFromTimerOrInit = false) =
if(self.closingApp or not singletonInstance.localAccountSensitiveSettings.getIsWalletEnabled()):
return
# Since we don't have a way to re-run TimerTaskArg (to stop it and run again), we introduced some flags which will
# just ignore buildAllTokens in case that proc is called by some action in the time window between two successive calls
# initiated by TimerTaskArg.
if not calledFromTimerOrInit:
self.ignoreTimeInitiatedTokensBuild = true
let walletAddresses = toSeq(self.walletAccounts.keys)
let arg = BuildTokensTaskArg(
tptr: cast[ByteAddress](prepareTokensTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onAllTokensBuilt",
walletAddresses: walletAddresses,
currency: self.settingsService.getCurrency(),
networks: self.networkService.getEnabledNetworks()
)
self.threadpool.start(arg)
proc onIsWalletEnabledChanged*(self: Service) {.slot.} =
self.buildAllTokens()