fix(@desktop/wallet): show all tokens in wallet that have balance > 0
Fixes #5859
This commit is contained in:
parent
64882dffbe
commit
3a3b2c9dc9
|
@ -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])
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
)
|
||||
|
||||
|
|
|
@ -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])
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
closingApp: bool
|
||||
ignoreTimeInitiatedTokensBuild: bool
|
||||
events: EventEmitter
|
||||
threadpool: ThreadPool
|
||||
settingsService: settings_service.Service
|
||||
accountsService: accounts_service.Service
|
||||
tokenService: token_service.Service
|
||||
networkService: network_service.Service
|
||||
accounts: OrderedTable[string, WalletAccountDto]
|
||||
walletAccounts: OrderedTable[string, WalletAccountDto]
|
||||
|
||||
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()
|
Loading…
Reference in New Issue