From 570f312617375f746f0049a9258445d4f6e4e2ac Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Thu, 3 Aug 2023 12:48:31 +0200 Subject: [PATCH] refactor(@desktop/wallet): wallet service improvements Closes: #11752 --- .../main/wallet_section/controller.nim | 5 +- .../service/wallet_account/service.nim | 1081 +---------------- .../wallet_account/service_account.nim | 588 +++++++++ .../wallet_account/service_keycard.nim | 184 +++ .../service/wallet_account/service_token.nim | 178 +++ .../wallet_account/signals_and_payloads.nim | 66 + .../service/wallet_account/utils.nim | 109 ++ 7 files changed, 1142 insertions(+), 1069 deletions(-) create mode 100644 src/app_service/service/wallet_account/service_account.nim create mode 100644 src/app_service/service/wallet_account/service_keycard.nim create mode 100644 src/app_service/service/wallet_account/service_token.nim create mode 100644 src/app_service/service/wallet_account/signals_and_payloads.nim create mode 100644 src/app_service/service/wallet_account/utils.nim diff --git a/src/app/modules/main/wallet_section/controller.nim b/src/app/modules/main/wallet_section/controller.nim index 51608716da..11374a7df2 100644 --- a/src/app/modules/main/wallet_section/controller.nim +++ b/src/app/modules/main/wallet_section/controller.nim @@ -54,16 +54,13 @@ proc getCurrencyAmount*(self: Controller, amount: float64, symbol: string): Curr proc updateCurrency*(self: Controller, currency: string) = self.walletAccountService.updateCurrency(currency) -# proc getIndex*(self: Controller, address: string): int = -# return self.walletAccountService.getIndex(address) - proc getNetworks*(self: Controller): seq[NetworkDto] = return self.networkService.getNetworks() proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] = return self.walletAccountService.getWalletAccounts() -proc getEnabledChainIds*(self: Controller): seq[int] = +proc getEnabledChainIds*(self: Controller): seq[int] = return self.networkService.getNetworks().filter(n => n.enabled).map(n => n.chainId) proc toggleIncludeWatchOnlyAccount*(self: Controller) = diff --git a/src/app_service/service/wallet_account/service.nim b/src/app_service/service/wallet_account/service.nim index 56c68b0a15..4fca384812 100644 --- a/src/app_service/service/wallet_account/service.nim +++ b/src/app_service/service/wallet_account/service.nim @@ -28,105 +28,8 @@ export keypair_dto, derived_address_dto logScope: topics = "wallet-account-service" -const SIGNAL_WALLET_ACCOUNT_SAVED* = "walletAccount/accountSaved" -const SIGNAL_WALLET_ACCOUNT_DELETED* = "walletAccount/accountDeleted" -const SIGNAL_WALLET_ACCOUNT_UPDATED* = "walletAccount/walletAccountUpdated" -const SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED* = "walletAccount/networkEnabledUpdated" -const SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT* = "walletAccount/tokensRebuilt" -const SIGNAL_WALLET_ACCOUNT_TOKENS_BEING_FETCHED* = "walletAccount/tokenFetching" -const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FETCHED* = "walletAccount/derivedAddressesFetched" -const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FROM_MNEMONIC_FETCHED* = "walletAccount/derivedAddressesFromMnemonicFetched" -const SIGNAL_WALLET_ACCOUNT_ADDRESS_DETAILS_FETCHED* = "walletAccount/addressDetailsFetched" -const SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED* = "walletAccount/positionUpdated" -const SIGNAL_WALLET_ACCOUNT_OPERABILITY_UPDATED* = "walletAccount/operabilityUpdated" -const SIGNAL_WALLET_ACCOUNT_CHAIN_ID_FOR_URL_FETCHED* = "walletAccount/chainIdForUrlFetched" -const SIGNAL_WALLET_ACCOUNT_PREFERRED_SHARING_CHAINS_UPDATED* = "walletAccount/preferredSharingChainsUpdated" - -const SIGNAL_KEYPAIR_SYNCED* = "keypairSynced" -const SIGNAL_KEYPAIR_NAME_CHANGED* = "keypairNameChanged" - -const SIGNAL_NEW_KEYCARD_SET* = "newKeycardSet" -const SIGNAL_KEYCARD_REBUILD* = "keycardRebuild" -const SIGNAL_KEYCARD_DELETED* = "keycardDeleted" -const SIGNAL_ALL_KEYCARDS_DELETED* = "allKeycardsDeleted" -const SIGNAL_KEYCARD_ACCOUNTS_REMOVED* = "keycardAccountsRemoved" -const SIGNAL_KEYCARD_LOCKED* = "keycardLocked" -const SIGNAL_KEYCARD_UNLOCKED* = "keycardUnlocked" -const SIGNAL_KEYCARD_UID_UPDATED* = "keycardUidUpdated" -const SIGNAL_KEYCARD_NAME_CHANGED* = "keycardNameChanged" - -var - balanceCache {.threadvar.}: Table[string, float64] - -proc priorityTokenCmp(a, b: WalletTokenDto): int = - for symbol in @["ETH", "SNT", "DAI", "STT"]: - if a.symbol == symbol: - return -1 - if b.symbol == symbol: - return 1 - - cmp(a.name, b.name) - -proc walletAccountsCmp(x, y: WalletAccountDto): int = - cmp(x.position, y.position) - -proc hex2Balance*(input: string, decimals: int): string = - var value = fromHex(Stuint[256], input) - - if decimals == 0: - return fmt"{value}" - - var p = u256(10).pow(decimals) - var i = value.div(p) - var r = value.mod(p) - var leading_zeros = "0".repeat(decimals - ($r).len) - var d = fmt"{leading_zeros}{$r}" - result = $i - if(r > 0): result = fmt"{result}.{d}" - -type AccountArgs* = ref object of Args - account*: WalletAccountDto - -type KeypairArgs* = ref object of Args - keypair*: KeypairDto - oldKeypairName*: string - -type KeycardArgs* = ref object of Args - success*: bool - oldKeycardUid*: string - keycard*: KeycardDto - -type DerivedAddressesArgs* = ref object of Args - uniqueId*: string - derivedAddresses*: seq[DerivedAddressDto] - error*: string - -type TokensPerAccountArgs* = ref object of Args - accountsTokens*: OrderedTable[string, seq[WalletTokenDto]] # [wallet address, list of tokens] - hasBalanceCache*: bool - hasMarketValuesCache*: bool - -type KeycardActivityArgs* = ref object of Args - success*: bool - oldKeycardUid*: string - keycard*: KeycardDto - -type ChainIdForUrlArgs* = ref object of Args - chainId*: int - success*: bool - url*: string - -proc responseHasNoErrors(procName: string, response: RpcResponse[JsonNode]): bool = - var errMsg = "" - if not response.error.isNil: - errMsg = "(" & $response.error.code & ") " & response.error.message - elif response.result.kind == JObject and response.result.contains("error"): - errMsg = response.result["error"].getStr - if(errMsg.len == 0): - return true - error "error: ", procName=procName, errDesription = errMsg - return false - +include signals_and_payloads +include utils include async_tasks include ../../common/json_utils @@ -140,18 +43,24 @@ QtObject: tokenService: token_service.Service networkService: network_service.Service currencyService: currency_service.Service - walletAccounts: OrderedTable[string, WalletAccountDto] + watchOnlyAccounts: Table[string, WalletAccountDto] ## [address, WalletAccountDto] + keypairs: Table[string, KeypairDto] ## [keyUid, KeypairDto] accountsTokens*: Table[string, seq[WalletTokenDto]] ## [address, seq[WalletTokenDto]] # Forward declaration proc buildAllTokens(self: Service, accounts: seq[string], store: bool) - proc checkRecentHistory*(self: Service) - proc startWallet(self: Service) + proc checkRecentHistory*(self: Service, addresses: seq[string]) proc handleWalletAccount(self: Service, account: WalletAccountDto, notify: bool = true) proc handleKeypair(self: Service, keypair: KeypairDto) - proc getAllKnownKeycards*(self: Service): seq[KeycardDto] - proc removeMigratedAccountsForKeycard*(self: Service, keyUid: string, keycardUid: string, accountsToRemove: seq[string]) proc updateAccountsPositions(self: Service) + # All slots defined in included files have to be forward declared + proc onAllTokensBuilt*(self: Service, response: string) {.slot.} + proc onDerivedAddressesFetched*(self: Service, jsonString: string) {.slot.} + proc onDerivedAddressesForMnemonicFetched*(self: Service, jsonString: string) {.slot.} + proc onAddressDetailsFetched*(self: Service, jsonString: string) {.slot.} + proc onKeycardAdded*(self: Service, response: string) {.slot.} + proc onMigratedAccountsForKeycardRemoved*(self: Service, response: string) {.slot.} + proc onFetchChainIdForUrl*(self: Service, jsonString: string) {.slot.} proc delete*(self: Service) = self.closingApp = true @@ -176,965 +85,7 @@ QtObject: result.tokenService = tokenService result.networkService = networkService result.currencyService = currencyService - result.walletAccounts = initOrderedTable[string, WalletAccountDto]() - proc getAccounts*(self: Service): seq[WalletAccountDto] = - try: - let response = status_go_accounts.getAccounts() - return response.result.getElems().map( - x => x.toWalletAccountDto() - ).filter(a => not a.isChat) - except Exception as e: - error "error: ", procName="getAccounts", errName = e.name, errDesription = e.msg - - proc getWatchOnlyAccounts*(self: Service): seq[WalletAccountDto] = - try: - let response = status_go_accounts.getWatchOnlyAccounts() - return response.result.getElems().map(x => x.toWalletAccountDto()) - except Exception as e: - error "error: ", procName="getWatchOnlyAccounts", errName = e.name, errDesription = e.msg - - proc getKeypairs*(self: Service): seq[KeypairDto] = - try: - let response = status_go_accounts.getKeypairs() - return response.result.getElems().map(x => x.toKeypairDto()) - except Exception as e: - error "error: ", procName="getKeypairs", errName = e.name, errDesription = e.msg - - proc getKeypairByKeyUid*(self: Service, keyUid: string): KeypairDto = - if keyUid.len == 0: - return - try: - let response = status_go_accounts.getKeypairByKeyUid(keyUid) - if not response.error.isNil: - return - return response.result.toKeypairDto() - except Exception as e: - info "no known keypair", keyUid=keyUid, procName="getKeypairByKeyUid", errName = e.name, errDesription = e.msg - - proc verifyKeystoreFileForAccount*(self: Service, account, password: string): bool = - try: - let hashedPassword = utils.hashPassword(password) - let response = status_go_accounts.verifyKeystoreFileForAccount(account, hashedPassword) - return response.result.getBool - except Exception as e: - error "error: ", procName="verifyKeystoreFileForAccount", errName = e.name, errDesription = e.msg - return false - - proc setEnsName(self: Service, account: WalletAccountDto) = - let chainId = self.networkService.getNetworkForEns().chainId - try: - let nameResponse = backend.getName(chainId, account.address) - account.ens = nameResponse.result.getStr - except Exception as e: - let errDesription = e.msg - error "error: ", errDesription - return - - proc storeAccount(self: Service, account: WalletAccountDto) = - # add new account to store - self.walletAccounts[account.address] = account - - proc storeTokensForAccount*(self: Service, address: string, tokens: seq[WalletTokenDto], areBalancesCached: bool, areMarketValuesCached: bool) = - if self.walletAccounts.hasKey(address): - self.walletAccounts[address].hasBalanceCache = areBalancesCached - self.walletAccounts[address].hasMarketValuesCache = areMarketValuesCached - self.accountsTokens[address] = tokens - - proc allBalancesForAllTokensHaveError(tokens: seq[WalletTokenDto]): bool = - for token in tokens: - for chainId, balanceDto in token.balancesPerChain: - if not balanceDto.hasError: - return false - return true - - proc anyTokenHasBalanceForAnyChain(tokens: seq[WalletTokenDto]): bool = - for token in tokens: - if len(token.balancesPerChain) > 0: - return true - return false - - proc allMarketValuesForAllTokensHaveError(tokens: seq[WalletTokenDto]): bool = - for token in tokens: - for currency, marketDto in token.marketValuesPerCurrency: - if not marketDto.hasError: - return false - return true - - proc anyTokenHasMarketValuesForAnyChain(tokens: seq[WalletTokenDto]): bool = - for token in tokens: - if len(token.marketValuesPerCurrency) > 0: - return true - return false - - proc walletAccountsContainsAddress(self: Service, address: string): bool = - return self.walletAccounts.hasKey(address) - - proc getAccountByAddress*(self: Service, address: string): WalletAccountDto = - if not self.walletAccountsContainsAddress(address): - return - return self.walletAccounts[address] - - proc updateReceivedTokens*(self: Service, address: string, tokens: var seq[WalletTokenDto]) = - let acc = self.getAccountByAddress(address) - if acc.isNil or not self.accountsTokens.hasKey(address): - return - let allBalancesForAllTokensHaveError = allBalancesForAllTokensHaveError(tokens) - let allMarketValuesForAllTokensHaveError = allMarketValuesForAllTokensHaveError(tokens) - - for storedToken in self.accountsTokens[address]: - for token in tokens.mitems: - if storedToken.name == token.name: - if allBalancesForAllTokensHaveError: - token.balancesPerChain = storedToken.balancesPerChain - if allMarketValuesForAllTokensHaveError: - token.marketValuesPerCurrency = storedToken.marketValuesPerCurrency - - proc getAccountsByAddresses*(self: Service, addresses: seq[string]): seq[WalletAccountDto] = - for address in addresses: - let acc = self.getAccountByAddress(address) - if acc.isNil: - continue - result.add(acc) - - proc getTokensByAddress*(self: Service, address: string): seq[WalletTokenDto] = - if not self.accountsTokens.hasKey(address): - return - return self.accountsTokens[address] - - proc getTokensByAddresses*(self: Service, addresses: seq[string]): seq[WalletTokenDto] = - var tokens = initTable[string, WalletTokenDto]() - for address in addresses: - if not self.accountsTokens.hasKey(address): - continue - for token in self.accountsTokens[address]: - if not tokens.hasKey(token.symbol): - let newToken = token.copyToken() - tokens[token.symbol] = newToken - continue - - for chainId, balanceDto in token.balancesPerChain: - if not tokens[token.symbol].balancesPerChain.hasKey(chainId): - tokens[token.symbol].balancesPerChain[chainId] = balanceDto - continue - - tokens[token.symbol].balancesPerChain[chainId].balance += balanceDto.balance - - result = toSeq(tokens.values) - result.sort(priorityTokenCmp) - - proc getWalletAccounts*(self: Service): seq[WalletAccountDto] = - result = toSeq(self.walletAccounts.values) - result.sort(walletAccountsCmp) - - proc getWalletAccountsForKeypair*(self: Service, keyUid: string): seq[WalletAccountDto] = - return self.getWalletAccounts().filter(kp => kp.keyUid == keyUid) - - proc getAddresses*(self: Service): seq[string] = - result = toSeq(self.walletAccounts.keys()) - - proc init*(self: Service) = - try: - let chainId = self.networkService.getNetworkForEns().chainId - let accounts = self.getAccounts() - for account in accounts: - let account = account # TODO https://github.com/nim-lang/Nim/issues/16740 - self.setEnsName(account) - self.storeAccount(account) - - self.buildAllTokens(self.getAddresses(), store = true) - self.checkRecentHistory() - self.startWallet() - except Exception as e: - let errDesription = e.msg - error "error: ", errDesription - return - - self.events.on(SignalType.Message.event) do(e: Args): - var receivedData = MessageSignal(e) - if receivedData.watchOnlyAccounts.len > 0: - for acc in receivedData.watchOnlyAccounts: - self.handleWalletAccount(acc) - if receivedData.keypairs.len > 0: - for kp in receivedData.keypairs: - self.handleKeypair(kp) - if receivedData.accountsPositions.len > 0: - self.updateAccountsPositions() - self.events.emit(SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED, Args()) - - self.events.on(SignalType.Wallet.event) do(e:Args): - var data = WalletSignal(e) - case data.eventType: - of "wallet-tick-reload": - self.buildAllTokens(self.getAddresses(), store = true) - self.checkRecentHistory() - - self.events.on(SIGNAL_CURRENCY_UPDATED) do(e:Args): - self.buildAllTokens(self.getAddresses(), store = true) - - proc reloadAccountTokens*(self: Service) = - self.buildAllTokens(self.getAddresses(), store = true) - self.checkRecentHistory() - - proc getWalletAccount*(self: Service, accountIndex: int): WalletAccountDto = - let accounts = self.getWalletAccounts() - if accountIndex < 0 or accountIndex >= accounts.len: - return - return accounts[accountIndex] - - proc getIndex*(self: Service, address: string): int = - let accounts = self.getWalletAccounts() - for i in 0 ..< accounts.len: - if cmpIgnoreCase(accounts[i].address, address) == 0: - return i - - proc startWallet(self: Service) = - if(not main_constants.WALLET_ENABLED): - return - - discard backend.startWallet() - - proc checkRecentHistory*(self: Service) = - if(not main_constants.WALLET_ENABLED): - return - - try: - let addresses = self.getWalletAccounts().map(a => a.address) - let chainIds = self.networkService.getNetworks().map(a => a.chainId) - status_go_transactions.checkRecentHistory(chainIds, addresses) - except Exception as e: - let errDescription = e.msg - error "error: ", errDescription - return - - proc addNewAccountToLocalStoreAndNotify(self: Service, notify: bool = true) = - let chainId = self.networkService.getNetworkForEns().chainId - let accounts = self.getAccounts() - var newAccount: WalletAccountDto - var found = false - for account in accounts: - let account = account # TODO https://github.com/nim-lang/Nim/issues/16740 - if not self.walletAccountsContainsAddress(account.address): - found = true - newAccount = account - break - - if not found: - info "no new accounts identified to be stored" - return - - self.setEnsName(newAccount) - self.storeAccount(newAccount) - - self.buildAllTokens(@[newAccount.address], store = true) - if notify: - self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountArgs(account: newAccount)) - - proc removeAccountFromLocalStoreAndNotify(self: Service, address: string, notify: bool = true) = - if not self.walletAccountsContainsAddress(address): - return - let removedAcc = self.walletAccounts[address] - self.walletAccounts.del(address) - if notify: - self.events.emit(SIGNAL_WALLET_ACCOUNT_DELETED, AccountArgs(account: removedAcc)) - - proc updateAccountsPositions(self: Service) = - let dbAccounts = self.getAccounts() - for dbAcc in dbAccounts: - var localAcc = self.getAccountByAddress(dbAcc.address) - if localAcc.isNil: - continue - localAcc.position = dbAcc.position - self.storeAccount(localAcc) - - proc updateAccountInLocalStoreAndNotify(self: Service, address, name, colorId, emoji: string, - positionUpdated: Option[bool] = none(bool), notify: bool = true) = - if address.len > 0: - if not self.walletAccountsContainsAddress(address): - return - var account = self.getAccountByAddress(address) - if account.isNil: - return - if name.len > 0 or colorId.len > 0 or emoji.len > 0: - if name.len > 0 and name != account.name: - account.name = name - if colorId.len > 0 and colorId != account.colorId: - account.colorId = colorId - if emoji.len > 0 and emoji != account.emoji: - account.emoji = emoji - self.storeAccount(account) - if notify: - self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, AccountArgs(account: account)) - else: - if not positionUpdated.isSome: - return - if positionUpdated.get: - ## if reordering was successfully stored, we need to update local storage - self.updateAccountsPositions() - if notify: - self.events.emit(SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED, Args()) - - proc updatePreferredSharingChainsAndNotify(self: Service, address, prodPreferredChains, testPreferredChains: string) = - if address.len == 0: - return - if not self.walletAccountsContainsAddress(address): - return - var account = self.getAccountByAddress(address) - if account.isNil: - return - if testPreferredChains.len > 0 and testPreferredChains != account.testPreferredChainIds: - account.testPreferredChainIds = testPreferredChains - if prodPreferredChains.len > 0 and prodPreferredChains != account.prodPreferredChainIds: - account.prodPreferredChainIds = prodPreferredChains - self.storeAccount(account) - - self.events.emit(SIGNAL_WALLET_ACCOUNT_PREFERRED_SHARING_CHAINS_UPDATED, AccountArgs(account: account)) - - ## if password is not provided local keystore file won't be created - proc addWalletAccount*(self: Service, password: string, doPasswordHashing: bool, name, address, path, publicKey, - keyUid, accountType, colorId, emoji: string): string = - try: - var response: RpcResponse[JsonNode] - if password.len == 0: - response = status_go_accounts.addAccountWithoutKeystoreFileCreation(name, address, path, publicKey, keyUid, - accountType, colorId, emoji) - else: - var finalPassword = password - if doPasswordHashing: - finalPassword = utils.hashPassword(password) - response = status_go_accounts.addAccount(finalPassword, name, address, path, publicKey, keyUid, accountType, - colorId, emoji) - if not response.error.isNil: - error "status-go error", procName="addWalletAccount", errCode=response.error.code, errDesription=response.error.message - return response.error.message - self.addNewAccountToLocalStoreAndNotify() - return "" - except Exception as e: - error "error: ", procName="addWalletAccount", errName=e.name, errDesription=e.msg - return e.msg - - ## Mandatory fields for account: `address`, `keyUid`, `walletType`, `path`, `publicKey`, `name`, `emoji`, `colorId` - proc addNewPrivateKeyKeypair*(self: Service, privateKey, password: string, doPasswordHashing: bool, - keyUid, keypairName, rootWalletMasterKey: string, account: WalletAccountDto): string = - if password.len == 0: - error "for adding new private key account, password must be provided" - return - var finalPassword = password - if doPasswordHashing: - finalPassword = utils.hashPassword(password) - try: - var response = status_go_accounts.importPrivateKey(privateKey, finalPassword) - if not response.error.isNil: - error "status-go error importing private key", procName="addNewPrivateKeyKeypair", errCode=response.error.code, errDesription=response.error.message - return response.error.message - response = status_go_accounts.addKeypair(finalPassword, keyUid, keypairName, KeypairTypeKey, rootWalletMasterKey, @[account]) - if not response.error.isNil: - error "status-go error adding keypair", procName="addNewPrivateKeyKeypair", errCode=response.error.code, errDesription=response.error.message - return response.error.message - self.addNewAccountToLocalStoreAndNotify() - return "" - except Exception as e: - error "error: ", procName="addNewPrivateKeyKeypair", errName=e.name, errDesription=e.msg - return e.msg - - ## Mandatory fields for all accounts: `address`, `keyUid`, `walletType`, `path`, `publicKey`, `name`, `emoji`, `colorId` - proc addNewSeedPhraseKeypair*(self: Service, seedPhrase, password: string, doPasswordHashing: bool, - keyUid, keypairName, rootWalletMasterKey: string, accounts: seq[WalletAccountDto]): string = - var finalPassword = password - if password.len > 0 and doPasswordHashing: - finalPassword = utils.hashPassword(password) - try: - if seedPhrase.len > 0 and password.len > 0: - let response = status_go_accounts.importMnemonic(seedPhrase, finalPassword) - if not response.error.isNil: - error "status-go error importing private key", procName="addNewSeedPhraseKeypair", errCode=response.error.code, errDesription=response.error.message - return response.error.message - let response = status_go_accounts.addKeypair(finalPassword, keyUid, keypairName, KeypairTypeSeed, rootWalletMasterKey, accounts) - if not response.error.isNil: - error "status-go error adding keypair", procName="addNewSeedPhraseKeypair", errCode=response.error.code, errDesription=response.error.message - return response.error.message - for i in 0 ..< accounts.len: - self.addNewAccountToLocalStoreAndNotify() - return "" - except Exception as e: - error "error: ", procName="addNewSeedPhraseKeypair", errName=e.name, errDesription=e.msg - return e.msg - - proc getRandomMnemonic*(self: Service): string = - try: - let response = status_go_accounts.getRandomMnemonic() - if not response.error.isNil: - error "status-go error", procName="getRandomMnemonic", errCode=response.error.code, errDesription=response.error.message - return "" - return response.result.getStr - except Exception as e: - error "error: ", procName="getRandomMnemonic", errName=e.name, errDesription=e.msg - return "" - - proc deleteAccount*(self: Service, address: string) = - try: - let response = status_go_accounts.deleteAccount(address) - if not response.error.isNil: - error "status-go error", procName="deleteAccount", errCode=response.error.code, errDesription=response.error.message - return - self.removeAccountFromLocalStoreAndNotify(address) - except Exception as e: - error "error: ", procName="deleteAccount", errName = e.name, errDesription = e.msg - - proc deleteKeypair*(self: Service, keyUid: string) = - try: - let localKeypairRelatedAccounts = self.getWalletAccountsForKeypair(keyUid) - if localKeypairRelatedAccounts.len == 0: - error "there are no known accounts", keyUid=keyUid, procName="deleteKeypair" - return - let response = status_go_accounts.deleteKeypair(keyUid) - if not response.error.isNil: - error "status-go error", procName="deleteKeypair", errCode=response.error.code, errDesription=response.error.message - return - self.updateAccountsPositions() - for acc in localKeypairRelatedAccounts: - self.removeAccountFromLocalStoreAndNotify(acc.address) - except Exception as e: - error "error: ", procName="deleteKeypair", errName = e.name, errDesription = e.msg - - - - proc updateCurrency*(self: Service, newCurrency: string) = - discard self.settingsService.saveCurrency(newCurrency) - - proc setNetworksState*(self: Service, chainIds: seq[int], enabled: bool) = - self.networkService.setNetworksState(chainIds, enabled) - self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, Args()) - - proc toggleTestNetworksEnabled*(self: Service) = - discard self.settingsService.toggleTestNetworksEnabled() - self.buildAllTokens(self.getAddresses(), store = true) - self.tokenService.loadData() - self.checkRecentHistory() - self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, Args()) - - proc updateWalletAccount*(self: Service, address: string, accountName: string, colorId: string, emoji: string): bool = - if not self.walletAccountsContainsAddress(address): - error "account's address is not among known addresses: ", address=address - return false - try: - var account = self.getAccountByAddress(address) - if account.isNil: - error "on account for given address", procName="updateWalletAccount" - return false - let response = status_go_accounts.updateAccount(accountName, account.address, account.path, account.publicKey, - account.keyUid, account.walletType, colorId, emoji, account.isWallet, account.isChat, account.prodPreferredChainIds, account.testPreferredChainIds) - if not response.error.isNil: - error "status-go error", procName="updateWalletAccount", errCode=response.error.code, errDesription=response.error.message - return false - self.updateAccountInLocalStoreAndNotify(address, accountName, colorId, emoji) - return true - except Exception as e: - error "error: ", procName="updateWalletAccount", errName=e.name, errDesription=e.msg - return false - - proc updateWalletAccountProdPreferredChains*(self: Service, address, preferredChainIds: string): bool = - if not self.walletAccountsContainsAddress(address): - error "account's address is not among known addresses: ", address=address - return false - try: - var account = self.getAccountByAddress(address) - if account.isNil: - error "on account for given address", procName="updateWalletAccount" - return false - let response = status_go_accounts.updateAccount(account.name, account.address, account.path, account.publicKey, - account.keyUid, account.walletType, account.colorId, account.emoji, account.isWallet, account.isChat, preferredChainIds, account.testPreferredChainIds) - if not response.error.isNil: - error "status-go error", procName="updateWalletAccount", errCode=response.error.code, errDesription=response.error.message - return false - self.updatePreferredSharingChainsAndNotify(address, prodPreferredChains = preferredChainIds, testPreferredChains = "") - return true - except Exception as e: - error "error: ", procName="updateWalletAccount", errName=e.name, errDesription=e.msg - return false - - proc updateWalletAccountTestPreferredChains*(self: Service, address, preferredChainIds: string): bool = - if not self.walletAccountsContainsAddress(address): - error "account's address is not among known addresses: ", address=address - return false - try: - var account = self.getAccountByAddress(address) - if account.isNil: - error "on account for given address", procName="updateWalletAccount" - return false - let response = status_go_accounts.updateAccount(account.name, account.address, account.path, account.publicKey, - account.keyUid, account.walletType, account.colorId, account.emoji, account.isWallet, account.isChat, account.prodPreferredChainIds, preferredChainIds) - if not response.error.isNil: - error "status-go error", procName="updateWalletAccount", errCode=response.error.code, errDesription=response.error.message - return false - self.updatePreferredSharingChainsAndNotify(address, prodPreferredChains = "", testPreferredChains = preferredChainIds) - return true - except Exception as e: - error "error: ", procName="updateWalletAccount", errName=e.name, errDesription=e.msg - return false - - proc moveAccountFinally*(self: Service, fromPosition: int, toPosition: int) = - var updated = false - try: - let response = backend.moveWalletAccount(fromPosition, toPosition) - if not response.error.isNil: - error "status-go error", procName="moveAccountFinally", errCode=response.error.code, errDesription=response.error.message - updated = true - except Exception as e: - error "error: ", procName="moveAccountFinally", errName=e.name, errDesription=e.msg - self.updateAccountInLocalStoreAndNotify(address = "", name = "", colorId = "", emoji = "", some(updated)) - - proc updateKeypairName*(self: Service, keyUid: string, name: string) = - try: - let keypair = self.getKeypairByKeyUid(keyUid) - if keypair.isNil: - return - let response = backend.updateKeypairName(keyUid, name) - if not response.error.isNil: - error "status-go error", procName="updateKeypairName", errCode=response.error.code, errDesription=response.error.message - return - # Once we start maintaining local store by keypairs we will need to update that store from here, - # till then we just emit signal from here. - self.events.emit(SIGNAL_KEYPAIR_NAME_CHANGED, KeypairArgs( - keypair: KeypairDto( - keyUid: keyUid, - name: name - ), - oldKeypairName: keypair.name - ) - ) - except Exception as e: - error "error: ", procName="updateKeypairName", errName=e.name, errDesription=e.msg - - proc fetchDerivedAddresses*(self: Service, password: string, derivedFrom: string, paths: seq[string], hashPassword: bool) = - let arg = FetchDerivedAddressesTaskArg( - password: if hashPassword: utils.hashPassword(password) else: password, - derivedFrom: derivedFrom, - paths: paths, - tptr: cast[ByteAddress](fetchDerivedAddressesTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onDerivedAddressesFetched", - ) - self.threadpool.start(arg) - - proc onDerivedAddressesFetched*(self: Service, jsonString: string) {.slot.} = - let response = parseJson(jsonString) - var derivedAddress: seq[DerivedAddressDto] = @[] - derivedAddress = response["derivedAddresses"].getElems().map(x => x.toDerivedAddressDto()) - let error = response["error"].getStr() - self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FETCHED, DerivedAddressesArgs( - derivedAddresses: derivedAddress, - error: error - )) - - proc fetchDerivedAddressesForMnemonic*(self: Service, mnemonic: string, paths: seq[string])= - let arg = FetchDerivedAddressesForMnemonicTaskArg( - mnemonic: mnemonic, - paths: paths, - tptr: cast[ByteAddress](fetchDerivedAddressesForMnemonicTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onDerivedAddressesForMnemonicFetched", - ) - self.threadpool.start(arg) - - proc onDerivedAddressesForMnemonicFetched*(self: Service, jsonString: string) {.slot.} = - let response = parseJson(jsonString) - var derivedAddress: seq[DerivedAddressDto] = @[] - derivedAddress = response["derivedAddresses"].getElems().map(x => x.toDerivedAddressDto()) - let error = response["error"].getStr() - self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FROM_MNEMONIC_FETCHED, DerivedAddressesArgs( - derivedAddresses: derivedAddress, - error: error - )) - - proc fetchDetailsForAddresses*(self: Service, uniqueId: string, addresses: seq[string]) = - let network = self.networkService.getNetworkForActivityCheck() - let arg = FetchDetailsForAddressesTaskArg( - uniqueId: uniqueId, - chainId: network.chainId, - addresses: addresses, - tptr: cast[ByteAddress](fetchDetailsForAddressesTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onAddressDetailsFetched", - ) - self.threadpool.start(arg) - - proc onAddressDetailsFetched*(self: Service, jsonString: string) {.slot.} = - var data = DerivedAddressesArgs() - try: - let response = parseJson(jsonString) - data.uniqueId = response["uniqueId"].getStr() - let addrDto = response{"details"}.toDerivedAddressDto() - data.derivedAddresses.add(addrDto) - data.error = response["error"].getStr() - except Exception as e: - error "error: ", procName="fetchAddressDetails", errName = e.name, errDesription = e.msg - data.error = e.msg - self.events.emit(SIGNAL_WALLET_ACCOUNT_ADDRESS_DETAILS_FETCHED, data) - - proc updateAssetsLoadingState(self: Service, wAddress: string, loading: bool) = - if not self.walletAccountsContainsAddress(wAddress): - return - self.walletAccounts[wAddress].assetsLoading = loading - - proc onAllTokensBuilt*(self: Service, response: string) {.slot.} = - try: - var visibleSymbols: seq[string] - let chainIds = self.networkService.getNetworks().map(n => n.chainId) - - let responseObj = response.parseJson - var storeResult: bool - var resultObj: JsonNode - discard responseObj.getProp("storeResult", storeResult) - discard responseObj.getProp("result", resultObj) - - var data = TokensPerAccountArgs() - data.accountsTokens = initOrderedTable[string, seq[WalletTokenDto]]() - data.hasBalanceCache = false - data.hasMarketValuesCache = false - if resultObj.kind == JObject: - for wAddress, tokensDetailsObj in resultObj: - if tokensDetailsObj.kind == JArray: - var tokens: seq[WalletTokenDto] - tokens = map(tokensDetailsObj.getElems(), proc(x: JsonNode): WalletTokenDto = x.toWalletTokenDto()) - tokens.sort(priorityTokenCmp) - self.updateReceivedTokens(wAddress, tokens) - let hasBalanceCache = anyTokenHasBalanceForAnyChain(tokens) - let hasMarketValuesCache = anyTokenHasMarketValuesForAnyChain(tokens) - data.accountsTokens[wAddress] = @[] - deepCopy(data.accountsTokens[wAddress], tokens) - data.hasBalanceCache = data.hasBalanceCache or hasBalanceCache - data.hasMarketValuesCache = data.hasMarketValuesCache or hasMarketValuesCache - - # set assetsLoading to false once the tokens are loaded - self.updateAssetsLoadingState(wAddress, false) - - if storeResult: - self.storeTokensForAccount(wAddress, tokens, hasBalanceCache, hasMarketValuesCache) - self.tokenService.updateTokenPrices(tokens) # For efficiency. Will be removed when token info fetching gets moved to the tokenService - # Gather symbol for visible tokens - for token in tokens: - if token.getVisibleForNetworkWithPositiveBalance(chainIds) and find(visibleSymbols, token.symbol) == -1: - visibleSymbols.add(token.symbol) - self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT, data) - if visibleSymbols.len > 0: - discard backend.updateVisibleTokens(visibleSymbols) - except Exception as e: - error "error: ", procName="onAllTokensBuilt", errName = e.name, errDesription = e.msg - - proc buildAllTokens(self: Service, accounts: seq[string], store: bool) = - if not main_constants.WALLET_ENABLED or - accounts.len == 0: - return - - # set assetsLoading to true as the tokens are being loaded - for waddress in accounts: - self.updateAssetsLoadingState(waddress, true) - - let arg = BuildTokensTaskArg( - tptr: cast[ByteAddress](prepareTokensTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onAllTokensBuilt", - accounts: accounts, - storeResult: store - ) - self.threadpool.start(arg) - - proc getCurrency*(self: Service): string = - return self.settingsService.getCurrency() - - proc getCurrentCurrencyIfEmpty(self: Service, currency = ""): string = - if currency != "": - return currency - else: - return self.getCurrency() - - proc getCurrencyBalance*(self: Service, address: string, chainIds: seq[int], currency: string): float64 = - if not self.accountsTokens.hasKey(address): - return - return self.accountsTokens[address].map(t => t.getCurrencyBalance(chainIds, currency)).foldl(a + b, 0.0) - - proc getTotalCurrencyBalance*(self: Service, addresses: seq[string], currency: string = ""): float64 = - let chainIds = self.networkService.getNetworks().filter(a => a.enabled).map(a => a.chainId) - let accounts = self.getWalletAccounts().filter(w => addresses.contains(w.address)) - return accounts.map(a => self.getCurrencyBalance(a.address, chainIds, self.getCurrentCurrencyIfEmpty(currency))).foldl(a + b, 0.0) - - proc findTokenSymbolByAddress*(self: Service, address: string): string = - return self.tokenService.findTokenSymbolByAddress(address) - - proc getOrFetchBalanceForAddressInPreferredCurrency*(self: Service, address: string): tuple[balance: float64, fetched: bool] = - let acc = self.getAccountByAddress(address) - if acc.isNil: - self.buildAllTokens(@[address], store = false) - result.balance = 0.0 - result.fetched = false - return - let chainIds = self.networkService.getNetworks().map(n => n.chainId) - result.balance = self.getCurrencyBalance(acc.address, chainIds, self.getCurrentCurrencyIfEmpty()) - result.fetched = true - - proc getTokenBalanceOnChain*(self: Service, address: string, chainId: int, symbol: string): float64 = - if not self.accountsTokens.hasKey(address): - return 0.0 - for token in self.accountsTokens[address]: - if token.symbol == symbol and token.balancesPerChain.hasKey(chainId): - return token.balancesPerChain[chainId].balance - return 0.0 - - proc addKeycardOrAccountsAsync*(self: Service, keycard: KeycardDto, accountsComingFromKeycard: bool = false) = - let arg = SaveOrUpdateKeycardTaskArg( - tptr: cast[ByteAddress](saveOrUpdateKeycardTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onKeycardAdded", - keycard: keycard, - accountsComingFromKeycard: accountsComingFromKeycard - ) - self.threadpool.start(arg) - - proc emitAddKeycardAddAccountsChange(self: Service, success: bool, keycard: KeycardDto) = - let data = KeycardArgs( - success: success, - keycard: keycard - ) - self.events.emit(SIGNAL_NEW_KEYCARD_SET, data) - - proc onKeycardAdded*(self: Service, response: string) {.slot.} = - var keycard = KeycardDto() - var success = false - try: - let responseObj = response.parseJson - discard responseObj.getProp("success", success) - var kpJson: JsonNode - if responseObj.getProp("keycard", kpJson): - keycard = kpJson.toKeycardDto() - except Exception as e: - error "error handilng migrated keycard response", errDesription=e.msg - self.emitAddKeycardAddAccountsChange(success, keycard) - - proc addKeycardOrAccounts*(self: Service, keycard: KeycardDto, accountsComingFromKeycard: bool = false): bool = - var success = false - try: - let response = backend.saveOrUpdateKeycard( - %* { - "keycard-uid": keycard.keycardUid, - "keycard-name": keycard.keycardName, - # "keycard-locked" - no need to set it here, cause it will be set to false by the status-go - "key-uid": keycard.keyUid, - "accounts-addresses": keycard.accountsAddresses, - # "position": - no need to set it here, cause it is fully maintained by the status-go - }, - accountsComingFromKeycard - ) - success = responseHasNoErrors("addKeycardOrAccounts", response) - except Exception as e: - error "error: ", procName="addKeycardOrAccounts", errName = e.name, errDesription = e.msg - self.emitAddKeycardAddAccountsChange(success = success, keycard) - return success - - proc removeMigratedAccountsForKeycard*(self: Service, keyUid: string, keycardUid: string, accountsToRemove: seq[string]) = - let arg = DeleteKeycardAccountsTaskArg( - tptr: cast[ByteAddress](deleteKeycardAccountsTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onMigratedAccountsForKeycardRemoved", - keycard: KeycardDto(keyUid: keyUid, keycardUid: keycardUid, accountsAddresses: accountsToRemove) - ) - self.threadpool.start(arg) - - proc onMigratedAccountsForKeycardRemoved*(self: Service, response: string) {.slot.} = - var data = KeycardArgs( - success: false, - ) - try: - let responseObj = response.parseJson - discard responseObj.getProp("success", data.success) - var kpJson: JsonNode - if responseObj.getProp("keycard", kpJson): - data.keycard = kpJson.toKeycardDto() - except Exception as e: - error "error handilng migrated keycard response", errDesription=e.msg - self.events.emit(SIGNAL_KEYCARD_ACCOUNTS_REMOVED, data) - - proc getAllKnownKeycards*(self: Service): seq[KeycardDto] = - try: - let response = backend.getAllKnownKeycards() - if responseHasNoErrors("getAllKnownKeycards", response): - return map(response.result.getElems(), proc(x: JsonNode): KeycardDto = toKeycardDto(x)) - except Exception as e: - error "error: ", procName="getAllKnownKeycards", errName = e.name, errDesription = e.msg - - proc getKeycardByKeycardUid*(self: Service, keycardUid: string): KeycardDto = - try: - let response = backend.getKeycardByKeycardUID(keycardUid) - if responseHasNoErrors("getKeycardByKeycardUid", response): - return response.result.toKeycardDto() - except Exception as e: - error "error: ", procName="getKeycardByKeycardUid", errName = e.name, errDesription = e.msg - - proc getKeycardsWithSameKeyUid*(self: Service, keyUid: string): seq[KeycardDto] = - try: - let response = backend.getKeycardsWithSameKeyUID(keyUid) - if responseHasNoErrors("getKeycardsWithSameKeyUid", response): - return map(response.result.getElems(), proc(x: JsonNode): KeycardDto = toKeycardDto(x)) - except Exception as e: - error "error: ", procName="getKeycardsWithSameKeyUid", errName = e.name, errDesription = e.msg - - proc isKeycardAccount*(self: Service, account: WalletAccountDto): bool = - if account.isNil or - account.keyUid.len == 0 or - account.path.len == 0 or - utils.isPathOutOfTheDefaultStatusDerivationTree(account.path): - return false - let keycards = self.getKeycardsWithSameKeyUid(account.keyUid) - return keycards.len > 0 - - proc updateKeycardName*(self: Service, keycardUid: string, name: string): bool = - var data = KeycardArgs( - success: false, - keycard: KeycardDto(keycardUid: keycardUid, keycardName: name) - ) - try: - let response = backend.setKeycardName(keycardUid, name) - data.success = responseHasNoErrors("updateKeycardName", response) - except Exception as e: - error "error: ", procName="updateKeycardName", errName = e.name, errDesription = e.msg - self.events.emit(SIGNAL_KEYCARD_NAME_CHANGED, data) - return data.success - - proc setKeycardLocked*(self: Service, keyUid: string, keycardUid: string): bool = - var data = KeycardArgs( - success: false, - keycard: KeycardDto(keyUid: keyUid, keycardUid: keycardUid) - ) - try: - let response = backend.keycardLocked(keycardUid) - data.success = responseHasNoErrors("setKeycardLocked", response) - except Exception as e: - error "error: ", procName="setKeycardLocked", errName = e.name, errDesription = e.msg - self.events.emit(SIGNAL_KEYCARD_LOCKED, data) - return data.success - - proc setKeycardUnlocked*(self: Service, keyUid: string, keycardUid: string): bool = - var data = KeycardArgs( - success: false, - keycard: KeycardDto(keyUid: keyUid, keycardUid: keycardUid) - ) - try: - let response = backend.keycardUnlocked(keycardUid) - data.success = responseHasNoErrors("setKeycardUnlocked", response) - except Exception as e: - error "error: ", procName="setKeycardUnlocked", errName = e.name, errDesription = e.msg - self.events.emit(SIGNAL_KEYCARD_UNLOCKED, data) - return data.success - - proc updateKeycardUid*(self: Service, oldKeycardUid: string, newKeycardUid: string): bool = - var data = KeycardArgs( - success: false, - oldKeycardUid: oldKeycardUid, - keycard: KeycardDto(keycardUid: newKeycardUid) - ) - try: - let response = backend.updateKeycardUID(oldKeycardUid, newKeycardUid) - data.success = responseHasNoErrors("updateKeycardUid", response) - except Exception as e: - error "error: ", procName="updateKeycardUid", errName = e.name, errDesription = e.msg - self.events.emit(SIGNAL_KEYCARD_UID_UPDATED, data) - return data.success - - proc deleteKeycard*(self: Service, keycardUid: string): bool = - var data = KeycardArgs( - success: false, - keycard: KeycardDto(keycardUid: keycardUid) - ) - try: - let response = backend.deleteKeycard(keycardUid) - data.success = responseHasNoErrors("deleteKeycard", response) - except Exception as e: - error "error: ", procName="deleteKeycard", errName = e.name, errDesription = e.msg - self.events.emit(SIGNAL_KEYCARD_DELETED, data) - return data.success - - proc deleteAllKeycardsWithKeyUid*(self: Service, keyUid: string): bool = - var data = KeycardArgs( - success: false, - keycard: KeycardDto(keyUid: keyUid) - ) - try: - let response = backend.deleteAllKeycardsWithKeyUID(keyUid) - data.success = responseHasNoErrors("deleteAllKeycardsWithKeyUid", response) - except Exception as e: - error "error: ", procName="deleteAllKeycardsWithKeyUid", errName = e.name, errDesription = e.msg - self.events.emit(SIGNAL_ALL_KEYCARDS_DELETED, data) - return data.success - - proc handleWalletAccount(self: Service, account: WalletAccountDto, notify: bool = true) = - if account.removed: - self.updateAccountsPositions() - self.removeAccountFromLocalStoreAndNotify(account.address, notify) - else: - if self.walletAccountsContainsAddress(account.address): - self.updateAccountInLocalStoreAndNotify(account.address, account.name, account.colorId, account.emoji, - none(bool), notify) - else: - self.addNewAccountToLocalStoreAndNotify(notify) - - proc handleKeypair(self: Service, keypair: KeypairDto) = - ## In some point in future instead `self.walletAccounts` table we should switch to maintaining local state in the - ## form of keypairs + another list just for watch only accounts. We will benefint from that in terms of maintaining. - ## Keycards details will be in that case tracked easier and stored locally as well. - - # handle keypair related accounts - # - first remove removed accounts from the UI - let localKeypairRelatedAccounts = self.getWalletAccountsForKeypair(keypair.keyUid) - for localAcc in localKeypairRelatedAccounts: - let accAddress = localAcc.address - if keypair.accounts.filter(a => cmpIgnoreCase(a.address, accAddress) == 0).len == 0: - self.handleWalletAccount(WalletAccountDto(address: accAddress, removed: true), notify = false) - # - second add/update new/existing accounts - for acc in keypair.accounts: - self.handleWalletAccount(acc, notify = false) - - # notify all interested parts about the keypair change - self.events.emit(SIGNAL_KEYPAIR_SYNCED, KeypairArgs(keypair: keypair)) - - proc allAccountsTokenBalance*(self: Service, symbol: string): float64 = - var totalTokenBalance = 0.0 - for walletAccount in self.getWalletAccounts(): - if walletAccount.walletType == WalletTypeWatch or - not self.accountsTokens.hasKey(walletAccount.address): - continue - for token in self.accountsTokens[walletAccount.address]: - if token.symbol == symbol: - totalTokenBalance += token.getTotalBalanceOfSupportedChains() - return totalTokenBalance - - proc isIncludeWatchOnlyAccount*(self: Service): bool = - return self.settingsService.isIncludeWatchOnlyAccount() - - proc toggleIncludeWatchOnlyAccount*(self: Service) = - self.settingsService.toggleIncludeWatchOnlyAccount() - - proc onFetchChainIdForUrl*(self: Service, jsonString: string) {.slot.} = - let response = parseJson(jsonString) - self.events.emit(SIGNAL_WALLET_ACCOUNT_CHAIN_ID_FOR_URL_FETCHED, ChainIdForUrlArgs( - chainId: response{"chainId"}.getInt, - success: response{"success"}.getBool, - url: response{"url"}.getStr, - )) - - proc fetchChainIdForUrl*(self: Service, url: string) = - let arg = FetchChainIdForUrlTaskArg( - tptr: cast[ByteAddress](fetchChainIdForUrlTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onFetchChainIdForUrl", - url: url - ) - self.threadpool.start(arg) - -proc getEnabledChainIds*(self: Service): seq[int] = - return self.networkService.getNetworks().filter(n => n.enabled).map(n => n.chainId) - -proc getCurrencyFormat*(self: Service, symbol: string): CurrencyFormatDto = - return self.currencyService.getCurrencyFormat(symbol) - -proc areTestNetworksEnabled*(self: Service): bool = - return self.settingsService.areTestNetworksEnabled() + include service_account + include service_token + include service_keycard \ No newline at end of file diff --git a/src/app_service/service/wallet_account/service_account.nim b/src/app_service/service/wallet_account/service_account.nim new file mode 100644 index 0000000000..1aeb87e0ab --- /dev/null +++ b/src/app_service/service/wallet_account/service_account.nim @@ -0,0 +1,588 @@ + proc storeWatchOnlyAccount(self: Service, account: WalletAccountDto) = + if self.watchOnlyAccounts.hasKey(account.address): + error "trying to store an already existing watch only account" + return + self.watchOnlyAccounts[account.address] = account + +proc storeKeypair(self: Service, keypair: KeypairDto) = + if keypair.keyUid.len == 0: + error "trying to store a keypair with empty keyUid" + return + if self.keypairs.hasKey(keypair.keyUid): + error "trying to store an already existing keypair" + return + self.keypairs[keypair.keyUid] = keypair + +proc storeAccountToKeypair(self: Service, account: WalletAccountDto) = + if account.keyUid.len == 0: + error "trying to store a keypair related account with empty keyUid" + return + if self.keypairs[account.keyUid].accounts.filter(acc => cmpIgnoreCase(acc.address, account.address) == 0).len != 0: + error "trying to store an already existing keytpair related account" + return + self.keypairs[account.keyUid].accounts.add(account) + +proc getKeypairs*(self: Service): seq[KeypairDto] = + return toSeq(self.keypairs.values) + +proc getKeypairByKeyUid*(self: Service, keyUid: string): KeypairDto = + if not self.keypairs.hasKey(keyUid): + return + return self.keypairs[keyUid] + +proc getWatchOnlyAccounts*(self: Service): seq[WalletAccountDto] = + return toSeq(self.watchOnlyAccounts.values) + +proc getWatchOnlyAccountByAddress*(self: Service, address: string): WalletAccountDto = + if not self.watchOnlyAccounts.hasKey(address): + return + return self.watchOnlyAccounts[address] + +proc getAccountByAddress*(self: Service, address: string): WalletAccountDto = + result = self.getWatchOnlyAccountByAddress(address) + if not result.isNil: + return + for _, kp in self.keypairs: + let accounts = kp.accounts.filter(acc => cmpIgnoreCase(acc.address, address) == 0) + if accounts.len == 1: + return accounts[0] + +proc getAccountsByAddresses*(self: Service, addresses: seq[string]): seq[WalletAccountDto] = + for address in addresses: + let acc = self.getAccountByAddress(address) + if acc.isNil: + continue + result.add(acc) + +proc getWalletAccounts*(self: Service): seq[WalletAccountDto] = + for _, kp in self.keypairs: + if kp.keypairType == KeypairTypeProfile: + for acc in kp.accounts: + if acc.isChat: + continue + result.add(acc) + continue + result.add(kp.accounts) + result.add(toSeq(self.watchOnlyAccounts.values)) + result.sort(walletAccountsCmp) + +proc getWalletAddresses*(self: Service): seq[string] = + return self.getWalletAccounts().map(a => a.address) + +proc updateAssetsLoadingState(self: Service, address: string, loading: bool) = + var acc = self.getAccountByAddress(address) + if acc.isNil: + return + acc.assetsLoading = loading + +################################################# +# TODO: remove functions below +# +# The only need for a function `getIndex` below is for switching selected account. +# Switching an account in UI by the index on which an account is stored in wallet settings service cache +# is completely wrong approach we need to handle that properly, at least by using position. +proc getIndex*(self: Service, address: string): int = + let accounts = self.getWalletAccounts() + for i in 0 ..< accounts.len: + if cmpIgnoreCase(accounts[i].address, address) == 0: + return i +# The same for `getWalletAccount`, both of them need to be removed. Parts of the app which are using them +# need refactor for sure. +proc getWalletAccount*(self: Service, accountIndex: int): WalletAccountDto = + let accounts = self.getWalletAccounts() + if accountIndex < 0 or accountIndex >= accounts.len: + return + return accounts[accountIndex] +################################################# + +proc startWallet(self: Service) = + if(not main_constants.WALLET_ENABLED): + return + discard backend.startWallet() + +proc init*(self: Service) = + try: + let chainId = self.networkService.getNetworkForEns().chainId + let woAccounts = getWatchOnlyAccountsFromDb() + for acc in woAccounts: + acc.ens = getEnsName(acc.address, chainId) + self.storeWatchOnlyAccount(acc) + let keypairs = getKeypairsFromDb() + for kp in keypairs: + for acc in kp.accounts: + acc.ens = getEnsName(acc.address, chainId) + self.storeKeypair(kp) + + let addresses = self.getWalletAddresses() + self.buildAllTokens(addresses, store = true) + self.checkRecentHistory(addresses) + self.startWallet() + except Exception as e: + let errDesription = e.msg + error "error: ", errDesription + return + + self.events.on(SignalType.Message.event) do(e: Args): + var receivedData = MessageSignal(e) + if receivedData.watchOnlyAccounts.len > 0: + for acc in receivedData.watchOnlyAccounts: + self.handleWalletAccount(acc) + if receivedData.keypairs.len > 0: + for kp in receivedData.keypairs: + self.handleKeypair(kp) + if receivedData.accountsPositions.len > 0: + self.updateAccountsPositions() + self.events.emit(SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED, Args()) + + self.events.on(SignalType.Wallet.event) do(e:Args): + var data = WalletSignal(e) + case data.eventType: + of "wallet-tick-reload": + let addresses = self.getWalletAddresses() + self.buildAllTokens(addresses, store = true) + self.checkRecentHistory(addresses) + + self.events.on(SIGNAL_CURRENCY_UPDATED) do(e:Args): + self.buildAllTokens(self.getWalletAddresses(), store = true) + +proc addNewKeypairsAccountsToLocalStoreAndNotify(self: Service, notify: bool = true) = + let chainId = self.networkService.getNetworkForEns().chainId + let allLocalAaccounts = self.getWalletAccounts() + # check if there is new watch only account + let woAccountsDb = getWatchOnlyAccountsFromDb() + for woAccDb in woAccountsDb: + var found = false + for localAcc in allLocalAaccounts: + if cmpIgnoreCase(localAcc.address, woAccDb.address) == 0: + found = true + break + if found: + continue + woAccDb.ens = getEnsName(woAccDb.address, chainId) + self.storeWatchOnlyAccount(woAccDb) + self.buildAllTokens(@[woAccDb.address], store = true) + if notify: + self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountArgs(account: woAccDb)) + # check if there is new keypair or any account added to an existing keypair + let keypairsDb = getKeypairsFromDb() + for kpDb in keypairsDb: + var localKp = self.getKeypairByKeyUid(kpDb.keyUid) + if localKp.isNil: + self.storeKeypair(kpDb) + let addresses = kpDb.accounts.map(a => a.address) + self.buildAllTokens(addresses, store = true) + for acc in kpDb.accounts: + acc.ens = getEnsName(acc.address, chainId) + if notify: + self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountArgs(account: acc)) + else: + for accDb in kpDb.accounts: + var found = false + for localAcc in allLocalAaccounts: + if cmpIgnoreCase(localAcc.address, accDb.address) == 0: + found = true + break + if found: + continue + accDb.ens = getEnsName(accDb.address, chainId) + self.storeAccountToKeypair(accDb) + self.buildAllTokens(@[accDb.address], store = true) + if notify: + self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountArgs(account: accDb)) + +proc removeAccountFromLocalStoreAndNotify(self: Service, address: string, notify: bool = true) = + var acc = self.getAccountByAddress(address) + if acc.isNil: + return + if acc.keyUid.len == 0: + self.watchOnlyAccounts.del(acc.address) + else: + var index = -1 + for i in 0 ..< self.keypairs[acc.keyUid].accounts.len: + if cmpIgnoreCase(self.keypairs[acc.keyUid].accounts[i].address, acc.address) == 0: + index = i + break + if index == -1: + error "cannot find account with the address to remove", address=address + return + self.keypairs[acc.keyUid].accounts.del(index) + if self.keypairs[acc.keyUid].accounts.len == 0: + self.keypairs.del(acc.keyUid) + if notify: + self.events.emit(SIGNAL_WALLET_ACCOUNT_DELETED, AccountArgs(account: acc)) + +proc updateAccountsPositions(self: Service) = + let dbAccounts = getAccountsFromDb() + for dbAcc in dbAccounts: + var localAcc = self.getAccountByAddress(dbAcc.address) + if localAcc.isNil: + continue + localAcc.position = dbAcc.position + +proc updateAccountInLocalStoreAndNotify(self: Service, address, name, colorId, emoji: string, + positionUpdated: Option[bool] = none(bool), notify: bool = true) = + if address.len > 0: + var account = self.getAccountByAddress(address) + if account.isNil: + return + if name.len > 0 or colorId.len > 0 or emoji.len > 0: + if name.len > 0 and name != account.name: + account.name = name + if colorId.len > 0 and colorId != account.colorId: + account.colorId = colorId + if emoji.len > 0 and emoji != account.emoji: + account.emoji = emoji + if notify: + self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, AccountArgs(account: account)) + else: + if not positionUpdated.isSome: + return + if positionUpdated.get: + ## if reordering was successfully stored, we need to update local storage + self.updateAccountsPositions() + if notify: + self.events.emit(SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED, Args()) + +proc updatePreferredSharingChainsAndNotify(self: Service, address, prodPreferredChains, testPreferredChains: string) = + var account = self.getAccountByAddress(address) + if account.isNil: + error "account's address is not among known addresses: ", address=address, procName="updatePreferredSharingChainsAndNotify" + return + if testPreferredChains.len > 0 and testPreferredChains != account.testPreferredChainIds: + account.testPreferredChainIds = testPreferredChains + if prodPreferredChains.len > 0 and prodPreferredChains != account.prodPreferredChainIds: + account.prodPreferredChainIds = prodPreferredChains + self.events.emit(SIGNAL_WALLET_ACCOUNT_PREFERRED_SHARING_CHAINS_UPDATED, AccountArgs(account: account)) + +## if password is not provided local keystore file won't be created +proc addWalletAccount*(self: Service, password: string, doPasswordHashing: bool, name, address, path, publicKey, + keyUid, accountType, colorId, emoji: string): string = + try: + var response: RpcResponse[JsonNode] + if password.len == 0: + response = status_go_accounts.addAccountWithoutKeystoreFileCreation(name, address, path, publicKey, keyUid, + accountType, colorId, emoji) + else: + var finalPassword = password + if doPasswordHashing: + finalPassword = utils.hashPassword(password) + response = status_go_accounts.addAccount(finalPassword, name, address, path, publicKey, keyUid, accountType, + colorId, emoji) + if not response.error.isNil: + error "status-go error", procName="addWalletAccount", errCode=response.error.code, errDesription=response.error.message + return response.error.message + self.addNewKeypairsAccountsToLocalStoreAndNotify() + return "" + except Exception as e: + error "error: ", procName="addWalletAccount", errName=e.name, errDesription=e.msg + return e.msg + +## Mandatory fields for account: `address`, `keyUid`, `walletType`, `path`, `publicKey`, `name`, `emoji`, `colorId` +proc addNewPrivateKeyKeypair*(self: Service, privateKey, password: string, doPasswordHashing: bool, + keyUid, keypairName, rootWalletMasterKey: string, account: WalletAccountDto): string = + if password.len == 0: + error "for adding new private key account, password must be provided" + return + var finalPassword = password + if doPasswordHashing: + finalPassword = utils.hashPassword(password) + try: + var response = status_go_accounts.importPrivateKey(privateKey, finalPassword) + if not response.error.isNil: + error "status-go error importing private key", procName="addNewPrivateKeyKeypair", errCode=response.error.code, errDesription=response.error.message + return response.error.message + response = status_go_accounts.addKeypair(finalPassword, keyUid, keypairName, KeypairTypeKey, rootWalletMasterKey, @[account]) + if not response.error.isNil: + error "status-go error adding keypair", procName="addNewPrivateKeyKeypair", errCode=response.error.code, errDesription=response.error.message + return response.error.message + self.addNewKeypairsAccountsToLocalStoreAndNotify() + return "" + except Exception as e: + error "error: ", procName="addNewPrivateKeyKeypair", errName=e.name, errDesription=e.msg + return e.msg + +## Mandatory fields for all accounts: `address`, `keyUid`, `walletType`, `path`, `publicKey`, `name`, `emoji`, `colorId` +proc addNewSeedPhraseKeypair*(self: Service, seedPhrase, password: string, doPasswordHashing: bool, + keyUid, keypairName, rootWalletMasterKey: string, accounts: seq[WalletAccountDto]): string = + var finalPassword = password + if password.len > 0 and doPasswordHashing: + finalPassword = utils.hashPassword(password) + try: + if seedPhrase.len > 0 and password.len > 0: + let response = status_go_accounts.importMnemonic(seedPhrase, finalPassword) + if not response.error.isNil: + error "status-go error importing private key", procName="addNewSeedPhraseKeypair", errCode=response.error.code, errDesription=response.error.message + return response.error.message + let response = status_go_accounts.addKeypair(finalPassword, keyUid, keypairName, KeypairTypeSeed, rootWalletMasterKey, accounts) + if not response.error.isNil: + error "status-go error adding keypair", procName="addNewSeedPhraseKeypair", errCode=response.error.code, errDesription=response.error.message + return response.error.message + for i in 0 ..< accounts.len: + self.addNewKeypairsAccountsToLocalStoreAndNotify() + return "" + except Exception as e: + error "error: ", procName="addNewSeedPhraseKeypair", errName=e.name, errDesription=e.msg + return e.msg + +proc getRandomMnemonic*(self: Service): string = + try: + let response = status_go_accounts.getRandomMnemonic() + if not response.error.isNil: + error "status-go error", procName="getRandomMnemonic", errCode=response.error.code, errDesription=response.error.message + return "" + return response.result.getStr + except Exception as e: + error "error: ", procName="getRandomMnemonic", errName=e.name, errDesription=e.msg + return "" + +proc deleteAccount*(self: Service, address: string) = + try: + let response = status_go_accounts.deleteAccount(address) + if not response.error.isNil: + error "status-go error", procName="deleteAccount", errCode=response.error.code, errDesription=response.error.message + return + self.removeAccountFromLocalStoreAndNotify(address) + except Exception as e: + error "error: ", procName="deleteAccount", errName = e.name, errDesription = e.msg + +proc deleteKeypair*(self: Service, keyUid: string) = + try: + let kp = self.getKeypairByKeyUid(keyUid) + if kp.isNil: + error "there is no known keypair", keyUid=keyUid, procName="deleteKeypair" + return + let response = status_go_accounts.deleteKeypair(keyUid) + if not response.error.isNil: + error "status-go error", procName="deleteKeypair", errCode=response.error.code, errDesription=response.error.message + return + self.updateAccountsPositions() + for acc in kp.accounts: + self.removeAccountFromLocalStoreAndNotify(acc.address) + except Exception as e: + error "error: ", procName="deleteKeypair", errName = e.name, errDesription = e.msg + +proc updateCurrency*(self: Service, newCurrency: string) = + discard self.settingsService.saveCurrency(newCurrency) + +proc setNetworksState*(self: Service, chainIds: seq[int], enabled: bool) = + self.networkService.setNetworksState(chainIds, enabled) + self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, Args()) + +proc toggleTestNetworksEnabled*(self: Service) = + discard self.settingsService.toggleTestNetworksEnabled() + let addresses = self.getWalletAddresses() + self.buildAllTokens(addresses, store = true) + self.tokenService.loadData() + self.checkRecentHistory(addresses) + self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, Args()) + +proc updateWalletAccount*(self: Service, address: string, accountName: string, colorId: string, emoji: string): bool = + try: + var account = self.getAccountByAddress(address) + if account.isNil: + error "account's address is not among known addresses: ", address=address, procName="updateWalletAccount" + return false + let response = status_go_accounts.updateAccount(accountName, account.address, account.path, account.publicKey, + account.keyUid, account.walletType, colorId, emoji, account.isWallet, account.isChat, account.prodPreferredChainIds, account.testPreferredChainIds) + if not response.error.isNil: + error "status-go error", procName="updateWalletAccount", errCode=response.error.code, errDesription=response.error.message + return false + self.updateAccountInLocalStoreAndNotify(address, accountName, colorId, emoji) + return true + except Exception as e: + error "error: ", procName="updateWalletAccount", errName=e.name, errDesription=e.msg + return false + +proc updateWalletAccountProdPreferredChains*(self: Service, address, preferredChainIds: string): bool = + try: + var account = self.getAccountByAddress(address) + if account.isNil: + error "account's address is not among known addresses: ", address=address, procName="updateWalletAccountProdPreferredChains" + return false + let response = status_go_accounts.updateAccount(account.name, account.address, account.path, account.publicKey, + account.keyUid, account.walletType, account.colorId, account.emoji, account.isWallet, account.isChat, preferredChainIds, account.testPreferredChainIds) + if not response.error.isNil: + error "status-go error", procName="updateWalletAccountProdPreferredChains", errCode=response.error.code, errDesription=response.error.message + return false + self.updatePreferredSharingChainsAndNotify(address, prodPreferredChains = preferredChainIds, testPreferredChains = "") + return true + except Exception as e: + error "error: ", procName="updateWalletAccountProdPreferredChains", errName=e.name, errDesription=e.msg + return false + +proc updateWalletAccountTestPreferredChains*(self: Service, address, preferredChainIds: string): bool = + try: + var account = self.getAccountByAddress(address) + if account.isNil: + error "account's address is not among known addresses: ", address=address, procName="updateWalletAccountTestPreferredChains" + return false + let response = status_go_accounts.updateAccount(account.name, account.address, account.path, account.publicKey, + account.keyUid, account.walletType, account.colorId, account.emoji, account.isWallet, account.isChat, account.prodPreferredChainIds, preferredChainIds) + if not response.error.isNil: + error "status-go error", procName="updateWalletAccountTestPreferredChains", errCode=response.error.code, errDesription=response.error.message + return false + self.updatePreferredSharingChainsAndNotify(address, prodPreferredChains = "", testPreferredChains = preferredChainIds) + return true + except Exception as e: + error "error: ", procName="updateWalletAccountTestPreferredChains", errName=e.name, errDesription=e.msg + return false + +proc moveAccountFinally*(self: Service, fromPosition: int, toPosition: int) = + var updated = false + try: + let response = backend.moveWalletAccount(fromPosition, toPosition) + if not response.error.isNil: + error "status-go error", procName="moveAccountFinally", errCode=response.error.code, errDesription=response.error.message + updated = true + except Exception as e: + error "error: ", procName="moveAccountFinally", errName=e.name, errDesription=e.msg + self.updateAccountInLocalStoreAndNotify(address = "", name = "", colorId = "", emoji = "", some(updated)) + +proc updateKeypairName*(self: Service, keyUid: string, name: string) = + try: + let kp = self.getKeypairByKeyUid(keyUid) + if kp.isNil: + error "there is no known keypair", keyUid=keyUid, procName="updateKeypairName" + return + let response = backend.updateKeypairName(keyUid, name) + if not response.error.isNil: + error "status-go error", procName="updateKeypairName", errCode=response.error.code, errDesription=response.error.message + return + var data = KeypairArgs( + keypair: KeypairDto( + keyUid: keyUid, + name: name + ), + oldKeypairName: kp.name + ) + kp.name = name + self.events.emit(SIGNAL_KEYPAIR_NAME_CHANGED, data) + except Exception as e: + error "error: ", procName="updateKeypairName", errName=e.name, errDesription=e.msg + +proc fetchDerivedAddresses*(self: Service, password: string, derivedFrom: string, paths: seq[string], hashPassword: bool) = + let arg = FetchDerivedAddressesTaskArg( + password: if hashPassword: utils.hashPassword(password) else: password, + derivedFrom: derivedFrom, + paths: paths, + tptr: cast[ByteAddress](fetchDerivedAddressesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onDerivedAddressesFetched", + ) + self.threadpool.start(arg) + +proc onDerivedAddressesFetched*(self: Service, jsonString: string) {.slot.} = + let response = parseJson(jsonString) + var derivedAddress: seq[DerivedAddressDto] = @[] + derivedAddress = response["derivedAddresses"].getElems().map(x => x.toDerivedAddressDto()) + let error = response["error"].getStr() + self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FETCHED, DerivedAddressesArgs( + derivedAddresses: derivedAddress, + error: error + )) + +proc fetchDerivedAddressesForMnemonic*(self: Service, mnemonic: string, paths: seq[string])= + let arg = FetchDerivedAddressesForMnemonicTaskArg( + mnemonic: mnemonic, + paths: paths, + tptr: cast[ByteAddress](fetchDerivedAddressesForMnemonicTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onDerivedAddressesForMnemonicFetched", + ) + self.threadpool.start(arg) + +proc onDerivedAddressesForMnemonicFetched*(self: Service, jsonString: string) {.slot.} = + let response = parseJson(jsonString) + var derivedAddress: seq[DerivedAddressDto] = @[] + derivedAddress = response["derivedAddresses"].getElems().map(x => x.toDerivedAddressDto()) + let error = response["error"].getStr() + self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FROM_MNEMONIC_FETCHED, DerivedAddressesArgs( + derivedAddresses: derivedAddress, + error: error + )) + +proc fetchDetailsForAddresses*(self: Service, uniqueId: string, addresses: seq[string]) = + let network = self.networkService.getNetworkForActivityCheck() + let arg = FetchDetailsForAddressesTaskArg( + uniqueId: uniqueId, + chainId: network.chainId, + addresses: addresses, + tptr: cast[ByteAddress](fetchDetailsForAddressesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onAddressDetailsFetched", + ) + self.threadpool.start(arg) + +proc onAddressDetailsFetched*(self: Service, jsonString: string) {.slot.} = + var data = DerivedAddressesArgs() + try: + let response = parseJson(jsonString) + data.uniqueId = response["uniqueId"].getStr() + let addrDto = response{"details"}.toDerivedAddressDto() + data.derivedAddresses.add(addrDto) + data.error = response["error"].getStr() + except Exception as e: + error "error: ", procName="fetchAddressDetails", errName = e.name, errDesription = e.msg + data.error = e.msg + self.events.emit(SIGNAL_WALLET_ACCOUNT_ADDRESS_DETAILS_FETCHED, data) + +proc handleWalletAccount(self: Service, account: WalletAccountDto, notify: bool = true) = + if account.removed: + self.updateAccountsPositions() + self.removeAccountFromLocalStoreAndNotify(account.address, notify) + else: + var localAcc = self.getAccountByAddress(account.address) + if not localAcc.isNil: + self.updateAccountInLocalStoreAndNotify(account.address, account.name, account.colorId, account.emoji, + none(bool), notify) + else: + self.addNewKeypairsAccountsToLocalStoreAndNotify(notify) + +proc handleKeypair(self: Service, keypair: KeypairDto) = + let localKp = self.getKeypairByKeyUid(keypair.keyUid) + if not localKp.isNil: + # - first remove removed accounts from the UI + for localAcc in localKp.accounts: + let accAddress = localAcc.address + if keypair.accounts.filter(a => cmpIgnoreCase(a.address, accAddress) == 0).len == 0: + self.handleWalletAccount(WalletAccountDto(address: accAddress, removed: true), notify = false) + # - second add/update new/existing accounts + for acc in keypair.accounts: + self.handleWalletAccount(acc, notify = false) + else: + self.addNewKeypairsAccountsToLocalStoreAndNotify(notify = false) + + # notify all interested parts about the keypair change + self.events.emit(SIGNAL_KEYPAIR_SYNCED, KeypairArgs(keypair: keypair)) + +proc isIncludeWatchOnlyAccount*(self: Service): bool = + return self.settingsService.isIncludeWatchOnlyAccount() + +proc toggleIncludeWatchOnlyAccount*(self: Service) = + self.settingsService.toggleIncludeWatchOnlyAccount() + +proc onFetchChainIdForUrl*(self: Service, jsonString: string) {.slot.} = + let response = parseJson(jsonString) + self.events.emit(SIGNAL_WALLET_ACCOUNT_CHAIN_ID_FOR_URL_FETCHED, ChainIdForUrlArgs( + chainId: response{"chainId"}.getInt, + success: response{"success"}.getBool, + url: response{"url"}.getStr, + )) + +proc fetchChainIdForUrl*(self: Service, url: string) = + let arg = FetchChainIdForUrlTaskArg( + tptr: cast[ByteAddress](fetchChainIdForUrlTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onFetchChainIdForUrl", + url: url + ) + self.threadpool.start(arg) + +proc getEnabledChainIds*(self: Service): seq[int] = + return self.networkService.getNetworks().filter(n => n.enabled).map(n => n.chainId) + +proc getCurrencyFormat*(self: Service, symbol: string): CurrencyFormatDto = + return self.currencyService.getCurrencyFormat(symbol) + +proc areTestNetworksEnabled*(self: Service): bool = + return self.settingsService.areTestNetworksEnabled() \ No newline at end of file diff --git a/src/app_service/service/wallet_account/service_keycard.nim b/src/app_service/service/wallet_account/service_keycard.nim new file mode 100644 index 0000000000..b874a8e1aa --- /dev/null +++ b/src/app_service/service/wallet_account/service_keycard.nim @@ -0,0 +1,184 @@ + proc addKeycardOrAccountsAsync*(self: Service, keycard: KeycardDto, accountsComingFromKeycard: bool = false) = + let arg = SaveOrUpdateKeycardTaskArg( + tptr: cast[ByteAddress](saveOrUpdateKeycardTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onKeycardAdded", + keycard: keycard, + accountsComingFromKeycard: accountsComingFromKeycard + ) + self.threadpool.start(arg) + +proc emitAddKeycardAddAccountsChange(self: Service, success: bool, keycard: KeycardDto) = + let data = KeycardArgs( + success: success, + keycard: keycard + ) + self.events.emit(SIGNAL_NEW_KEYCARD_SET, data) + +proc onKeycardAdded*(self: Service, response: string) {.slot.} = + var keycard = KeycardDto() + var success = false + try: + let responseObj = response.parseJson + discard responseObj.getProp("success", success) + var kpJson: JsonNode + if responseObj.getProp("keycard", kpJson): + keycard = kpJson.toKeycardDto() + except Exception as e: + error "error handilng migrated keycard response", errDesription=e.msg + self.emitAddKeycardAddAccountsChange(success, keycard) + +proc addKeycardOrAccounts*(self: Service, keycard: KeycardDto, accountsComingFromKeycard: bool = false): bool = + var success = false + try: + let response = backend.saveOrUpdateKeycard( + %* { + "keycard-uid": keycard.keycardUid, + "keycard-name": keycard.keycardName, + # "keycard-locked" - no need to set it here, cause it will be set to false by the status-go + "key-uid": keycard.keyUid, + "accounts-addresses": keycard.accountsAddresses, + # "position": - no need to set it here, cause it is fully maintained by the status-go + }, + accountsComingFromKeycard + ) + success = responseHasNoErrors("addKeycardOrAccounts", response) + except Exception as e: + error "error: ", procName="addKeycardOrAccounts", errName = e.name, errDesription = e.msg + self.emitAddKeycardAddAccountsChange(success = success, keycard) + return success + +proc removeMigratedAccountsForKeycard*(self: Service, keyUid: string, keycardUid: string, accountsToRemove: seq[string]) = + let arg = DeleteKeycardAccountsTaskArg( + tptr: cast[ByteAddress](deleteKeycardAccountsTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onMigratedAccountsForKeycardRemoved", + keycard: KeycardDto(keyUid: keyUid, keycardUid: keycardUid, accountsAddresses: accountsToRemove) + ) + self.threadpool.start(arg) + +proc onMigratedAccountsForKeycardRemoved*(self: Service, response: string) {.slot.} = + var data = KeycardArgs( + success: false, + ) + try: + let responseObj = response.parseJson + discard responseObj.getProp("success", data.success) + var kpJson: JsonNode + if responseObj.getProp("keycard", kpJson): + data.keycard = kpJson.toKeycardDto() + except Exception as e: + error "error handilng migrated keycard response", errDesription=e.msg + self.events.emit(SIGNAL_KEYCARD_ACCOUNTS_REMOVED, data) + +proc getAllKnownKeycards*(self: Service): seq[KeycardDto] = + try: + let response = backend.getAllKnownKeycards() + if responseHasNoErrors("getAllKnownKeycards", response): + return map(response.result.getElems(), proc(x: JsonNode): KeycardDto = toKeycardDto(x)) + except Exception as e: + error "error: ", procName="getAllKnownKeycards", errName = e.name, errDesription = e.msg + +proc getKeycardByKeycardUid*(self: Service, keycardUid: string): KeycardDto = + try: + let response = backend.getKeycardByKeycardUID(keycardUid) + if responseHasNoErrors("getKeycardByKeycardUid", response): + return response.result.toKeycardDto() + except Exception as e: + error "error: ", procName="getKeycardByKeycardUid", errName = e.name, errDesription = e.msg + +proc getKeycardsWithSameKeyUid*(self: Service, keyUid: string): seq[KeycardDto] = + try: + let response = backend.getKeycardsWithSameKeyUID(keyUid) + if responseHasNoErrors("getKeycardsWithSameKeyUid", response): + return map(response.result.getElems(), proc(x: JsonNode): KeycardDto = toKeycardDto(x)) + except Exception as e: + error "error: ", procName="getKeycardsWithSameKeyUid", errName = e.name, errDesription = e.msg + +proc isKeycardAccount*(self: Service, account: WalletAccountDto): bool = + if account.isNil or + account.keyUid.len == 0 or + account.path.len == 0 or + utils.isPathOutOfTheDefaultStatusDerivationTree(account.path): + return false + let keycards = self.getKeycardsWithSameKeyUid(account.keyUid) + return keycards.len > 0 + +proc updateKeycardName*(self: Service, keycardUid: string, name: string): bool = + var data = KeycardArgs( + success: false, + keycard: KeycardDto(keycardUid: keycardUid, keycardName: name) + ) + try: + let response = backend.setKeycardName(keycardUid, name) + data.success = responseHasNoErrors("updateKeycardName", response) + except Exception as e: + error "error: ", procName="updateKeycardName", errName = e.name, errDesription = e.msg + self.events.emit(SIGNAL_KEYCARD_NAME_CHANGED, data) + return data.success + +proc setKeycardLocked*(self: Service, keyUid: string, keycardUid: string): bool = + var data = KeycardArgs( + success: false, + keycard: KeycardDto(keyUid: keyUid, keycardUid: keycardUid) + ) + try: + let response = backend.keycardLocked(keycardUid) + data.success = responseHasNoErrors("setKeycardLocked", response) + except Exception as e: + error "error: ", procName="setKeycardLocked", errName = e.name, errDesription = e.msg + self.events.emit(SIGNAL_KEYCARD_LOCKED, data) + return data.success + +proc setKeycardUnlocked*(self: Service, keyUid: string, keycardUid: string): bool = + var data = KeycardArgs( + success: false, + keycard: KeycardDto(keyUid: keyUid, keycardUid: keycardUid) + ) + try: + let response = backend.keycardUnlocked(keycardUid) + data.success = responseHasNoErrors("setKeycardUnlocked", response) + except Exception as e: + error "error: ", procName="setKeycardUnlocked", errName = e.name, errDesription = e.msg + self.events.emit(SIGNAL_KEYCARD_UNLOCKED, data) + return data.success + +proc updateKeycardUid*(self: Service, oldKeycardUid: string, newKeycardUid: string): bool = + var data = KeycardArgs( + success: false, + oldKeycardUid: oldKeycardUid, + keycard: KeycardDto(keycardUid: newKeycardUid) + ) + try: + let response = backend.updateKeycardUID(oldKeycardUid, newKeycardUid) + data.success = responseHasNoErrors("updateKeycardUid", response) + except Exception as e: + error "error: ", procName="updateKeycardUid", errName = e.name, errDesription = e.msg + self.events.emit(SIGNAL_KEYCARD_UID_UPDATED, data) + return data.success + +proc deleteKeycard*(self: Service, keycardUid: string): bool = + var data = KeycardArgs( + success: false, + keycard: KeycardDto(keycardUid: keycardUid) + ) + try: + let response = backend.deleteKeycard(keycardUid) + data.success = responseHasNoErrors("deleteKeycard", response) + except Exception as e: + error "error: ", procName="deleteKeycard", errName = e.name, errDesription = e.msg + self.events.emit(SIGNAL_KEYCARD_DELETED, data) + return data.success + +proc deleteAllKeycardsWithKeyUid*(self: Service, keyUid: string): bool = + var data = KeycardArgs( + success: false, + keycard: KeycardDto(keyUid: keyUid) + ) + try: + let response = backend.deleteAllKeycardsWithKeyUID(keyUid) + data.success = responseHasNoErrors("deleteAllKeycardsWithKeyUid", response) + except Exception as e: + error "error: ", procName="deleteAllKeycardsWithKeyUid", errName = e.name, errDesription = e.msg + self.events.emit(SIGNAL_ALL_KEYCARDS_DELETED, data) + return data.success \ No newline at end of file diff --git a/src/app_service/service/wallet_account/service_token.nim b/src/app_service/service/wallet_account/service_token.nim new file mode 100644 index 0000000000..c9cb8bd9b8 --- /dev/null +++ b/src/app_service/service/wallet_account/service_token.nim @@ -0,0 +1,178 @@ + proc storeTokensForAccount*(self: Service, address: string, tokens: seq[WalletTokenDto], areBalancesCached: bool, areMarketValuesCached: bool) = + let acc = self.getAccountByAddress(address) + if acc.isNil: + return + self.accountsTokens[address] = tokens + acc.hasBalanceCache = areBalancesCached + acc.hasMarketValuesCache = areMarketValuesCached + +proc updateReceivedTokens*(self: Service, address: string, tokens: var seq[WalletTokenDto]) = + let acc = self.getAccountByAddress(address) + if acc.isNil or not self.accountsTokens.hasKey(address): + return + let allBalancesForAllTokensHaveError = allBalancesForAllTokensHaveError(tokens) + let allMarketValuesForAllTokensHaveError = allMarketValuesForAllTokensHaveError(tokens) + + for storedToken in self.accountsTokens[address]: + for token in tokens.mitems: + if storedToken.name == token.name: + if allBalancesForAllTokensHaveError: + token.balancesPerChain = storedToken.balancesPerChain + if allMarketValuesForAllTokensHaveError: + token.marketValuesPerCurrency = storedToken.marketValuesPerCurrency + +proc getTokensByAddress*(self: Service, address: string): seq[WalletTokenDto] = + if not self.accountsTokens.hasKey(address): + return + return self.accountsTokens[address] + +proc getTokensByAddresses*(self: Service, addresses: seq[string]): seq[WalletTokenDto] = + var tokens = initTable[string, WalletTokenDto]() + for address in addresses: + if not self.accountsTokens.hasKey(address): + continue + for token in self.accountsTokens[address]: + if not tokens.hasKey(token.symbol): + let newToken = token.copyToken() + tokens[token.symbol] = newToken + continue + + for chainId, balanceDto in token.balancesPerChain: + if not tokens[token.symbol].balancesPerChain.hasKey(chainId): + tokens[token.symbol].balancesPerChain[chainId] = balanceDto + continue + + tokens[token.symbol].balancesPerChain[chainId].balance += balanceDto.balance + + result = toSeq(tokens.values) + result.sort(priorityTokenCmp) + +proc onAllTokensBuilt*(self: Service, response: string) {.slot.} = + try: + var visibleSymbols: seq[string] + let chainIds = self.networkService.getNetworks().map(n => n.chainId) + + let responseObj = response.parseJson + var storeResult: bool + var resultObj: JsonNode + discard responseObj.getProp("storeResult", storeResult) + discard responseObj.getProp("result", resultObj) + + var data = TokensPerAccountArgs() + data.accountsTokens = initOrderedTable[string, seq[WalletTokenDto]]() + data.hasBalanceCache = false + data.hasMarketValuesCache = false + if resultObj.kind == JObject: + for wAddress, tokensDetailsObj in resultObj: + if tokensDetailsObj.kind == JArray: + var tokens: seq[WalletTokenDto] + tokens = map(tokensDetailsObj.getElems(), proc(x: JsonNode): WalletTokenDto = x.toWalletTokenDto()) + tokens.sort(priorityTokenCmp) + self.updateReceivedTokens(wAddress, tokens) + let hasBalanceCache = anyTokenHasBalanceForAnyChain(tokens) + let hasMarketValuesCache = anyTokenHasMarketValuesForAnyChain(tokens) + data.accountsTokens[wAddress] = @[] + deepCopy(data.accountsTokens[wAddress], tokens) + data.hasBalanceCache = data.hasBalanceCache or hasBalanceCache + data.hasMarketValuesCache = data.hasMarketValuesCache or hasMarketValuesCache + + # set assetsLoading to false once the tokens are loaded + self.updateAssetsLoadingState(wAddress, false) + + if storeResult: + self.storeTokensForAccount(wAddress, tokens, hasBalanceCache, hasMarketValuesCache) + self.tokenService.updateTokenPrices(tokens) # For efficiency. Will be removed when token info fetching gets moved to the tokenService + # Gather symbol for visible tokens + for token in tokens: + if token.getVisibleForNetworkWithPositiveBalance(chainIds) and find(visibleSymbols, token.symbol) == -1: + visibleSymbols.add(token.symbol) + self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT, data) + if visibleSymbols.len > 0: + discard backend.updateVisibleTokens(visibleSymbols) + except Exception as e: + error "error: ", procName="onAllTokensBuilt", errName = e.name, errDesription = e.msg + +proc buildAllTokens(self: Service, accounts: seq[string], store: bool) = + if not main_constants.WALLET_ENABLED or + accounts.len == 0: + return + + # set assetsLoading to true as the tokens are being loaded + for waddress in accounts: + self.updateAssetsLoadingState(waddress, true) + + let arg = BuildTokensTaskArg( + tptr: cast[ByteAddress](prepareTokensTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onAllTokensBuilt", + accounts: accounts, + storeResult: store + ) + self.threadpool.start(arg) + +proc checkRecentHistory*(self: Service, addresses: seq[string]) = + if(not main_constants.WALLET_ENABLED): + return + try: + let chainIds = self.networkService.getNetworks().map(a => a.chainId) + status_go_transactions.checkRecentHistory(chainIds, addresses) + except Exception as e: + let errDescription = e.msg + error "error: ", errDescription + +proc reloadAccountTokens*(self: Service) = + let addresses = self.getWalletAddresses() + self.buildAllTokens(addresses, store = true) + self.checkRecentHistory(addresses) + +proc getCurrency*(self: Service): string = + return self.settingsService.getCurrency() + +proc getCurrentCurrencyIfEmpty(self: Service, currency = ""): string = + if currency != "": + return currency + else: + return self.getCurrency() + +proc getCurrencyBalance*(self: Service, address: string, chainIds: seq[int], currency: string): float64 = + if not self.accountsTokens.hasKey(address): + return + return self.accountsTokens[address].map(t => t.getCurrencyBalance(chainIds, currency)).foldl(a + b, 0.0) + +proc getTotalCurrencyBalance*(self: Service, addresses: seq[string], currency: string = ""): float64 = + let chainIds = self.networkService.getNetworks().filter(a => a.enabled).map(a => a.chainId) + let accounts = self.getWalletAccounts().filter(w => addresses.contains(w.address)) + return accounts.map(a => self.getCurrencyBalance(a.address, chainIds, self.getCurrentCurrencyIfEmpty(currency))).foldl(a + b, 0.0) + +proc findTokenSymbolByAddress*(self: Service, address: string): string = + return self.tokenService.findTokenSymbolByAddress(address) + +proc getOrFetchBalanceForAddressInPreferredCurrency*(self: Service, address: string): tuple[balance: float64, fetched: bool] = + let acc = self.getAccountByAddress(address) + if acc.isNil: + self.buildAllTokens(@[address], store = false) + result.balance = 0.0 + result.fetched = false + return + let chainIds = self.networkService.getNetworks().map(n => n.chainId) + result.balance = self.getCurrencyBalance(acc.address, chainIds, self.getCurrentCurrencyIfEmpty()) + result.fetched = true + +proc getTokenBalanceOnChain*(self: Service, address: string, chainId: int, symbol: string): float64 = + if not self.accountsTokens.hasKey(address): + return 0.0 + for token in self.accountsTokens[address]: + if token.symbol == symbol and token.balancesPerChain.hasKey(chainId): + return token.balancesPerChain[chainId].balance + return 0.0 + +proc allAccountsTokenBalance*(self: Service, symbol: string): float64 = + var totalTokenBalance = 0.0 + for walletAccount in self.getWalletAccounts(): + if walletAccount.walletType == WalletTypeWatch or + not self.accountsTokens.hasKey(walletAccount.address): + continue + for token in self.accountsTokens[walletAccount.address]: + if token.symbol == symbol: + totalTokenBalance += token.getTotalBalanceOfSupportedChains() + return totalTokenBalance \ No newline at end of file diff --git a/src/app_service/service/wallet_account/signals_and_payloads.nim b/src/app_service/service/wallet_account/signals_and_payloads.nim new file mode 100644 index 0000000000..001229279f --- /dev/null +++ b/src/app_service/service/wallet_account/signals_and_payloads.nim @@ -0,0 +1,66 @@ +################################################# +# Signals emitted by wallet account service +################################################# + +const SIGNAL_WALLET_ACCOUNT_SAVED* = "walletAccount/accountSaved" +const SIGNAL_WALLET_ACCOUNT_DELETED* = "walletAccount/accountDeleted" +const SIGNAL_WALLET_ACCOUNT_UPDATED* = "walletAccount/walletAccountUpdated" +const SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED* = "walletAccount/networkEnabledUpdated" +const SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT* = "walletAccount/tokensRebuilt" +const SIGNAL_WALLET_ACCOUNT_TOKENS_BEING_FETCHED* = "walletAccount/tokenFetching" +const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FETCHED* = "walletAccount/derivedAddressesFetched" +const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FROM_MNEMONIC_FETCHED* = "walletAccount/derivedAddressesFromMnemonicFetched" +const SIGNAL_WALLET_ACCOUNT_ADDRESS_DETAILS_FETCHED* = "walletAccount/addressDetailsFetched" +const SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED* = "walletAccount/positionUpdated" +const SIGNAL_WALLET_ACCOUNT_OPERABILITY_UPDATED* = "walletAccount/operabilityUpdated" +const SIGNAL_WALLET_ACCOUNT_CHAIN_ID_FOR_URL_FETCHED* = "walletAccount/chainIdForUrlFetched" +const SIGNAL_WALLET_ACCOUNT_PREFERRED_SHARING_CHAINS_UPDATED* = "walletAccount/preferredSharingChainsUpdated" + +const SIGNAL_KEYPAIR_SYNCED* = "keypairSynced" +const SIGNAL_KEYPAIR_NAME_CHANGED* = "keypairNameChanged" + +const SIGNAL_NEW_KEYCARD_SET* = "newKeycardSet" +const SIGNAL_KEYCARD_REBUILD* = "keycardRebuild" +const SIGNAL_KEYCARD_DELETED* = "keycardDeleted" +const SIGNAL_ALL_KEYCARDS_DELETED* = "allKeycardsDeleted" +const SIGNAL_KEYCARD_ACCOUNTS_REMOVED* = "keycardAccountsRemoved" +const SIGNAL_KEYCARD_LOCKED* = "keycardLocked" +const SIGNAL_KEYCARD_UNLOCKED* = "keycardUnlocked" +const SIGNAL_KEYCARD_UID_UPDATED* = "keycardUidUpdated" +const SIGNAL_KEYCARD_NAME_CHANGED* = "keycardNameChanged" + +################################################# +# Payload sent via above defined signals +################################################# + +type AccountArgs* = ref object of Args + account*: WalletAccountDto + +type KeypairArgs* = ref object of Args + keypair*: KeypairDto + oldKeypairName*: string + +type KeycardArgs* = ref object of Args + success*: bool + oldKeycardUid*: string + keycard*: KeycardDto + +type DerivedAddressesArgs* = ref object of Args + uniqueId*: string + derivedAddresses*: seq[DerivedAddressDto] + error*: string + +type TokensPerAccountArgs* = ref object of Args + accountsTokens*: OrderedTable[string, seq[WalletTokenDto]] # [wallet address, list of tokens] + hasBalanceCache*: bool + hasMarketValuesCache*: bool + +type KeycardActivityArgs* = ref object of Args + success*: bool + oldKeycardUid*: string + keycard*: KeycardDto + +type ChainIdForUrlArgs* = ref object of Args + chainId*: int + success*: bool + url*: string \ No newline at end of file diff --git a/src/app_service/service/wallet_account/utils.nim b/src/app_service/service/wallet_account/utils.nim new file mode 100644 index 0000000000..11649826ff --- /dev/null +++ b/src/app_service/service/wallet_account/utils.nim @@ -0,0 +1,109 @@ +################################################# +# Common functions +################################################# + +proc priorityTokenCmp(a, b: WalletTokenDto): int = + for symbol in @["ETH", "SNT", "DAI", "STT"]: + if a.symbol == symbol: + return -1 + if b.symbol == symbol: + return 1 + cmp(a.name, b.name) + +proc walletAccountsCmp(x, y: WalletAccountDto): int = + cmp(x.position, y.position) + +proc hex2Balance*(input: string, decimals: int): string = + var value = fromHex(Stuint[256], input) + if decimals == 0: + return fmt"{value}" + var p = u256(10).pow(decimals) + var i = value.div(p) + var r = value.mod(p) + var leading_zeros = "0".repeat(decimals - ($r).len) + var d = fmt"{leading_zeros}{$r}" + result = $i + if(r > 0): result = fmt"{result}.{d}" + +proc responseHasNoErrors(procName: string, response: RpcResponse[JsonNode]): bool = + var errMsg = "" + if not response.error.isNil: + errMsg = "(" & $response.error.code & ") " & response.error.message + elif response.result.kind == JObject and response.result.contains("error"): + errMsg = response.result["error"].getStr + if(errMsg.len == 0): + return true + error "error: ", procName=procName, errDesription = errMsg + return false + +proc allBalancesForAllTokensHaveError(tokens: seq[WalletTokenDto]): bool = + for token in tokens: + for chainId, balanceDto in token.balancesPerChain: + if not balanceDto.hasError: + return false + return true + +proc anyTokenHasBalanceForAnyChain(tokens: seq[WalletTokenDto]): bool = + for token in tokens: + if len(token.balancesPerChain) > 0: + return true + return false + +proc allMarketValuesForAllTokensHaveError(tokens: seq[WalletTokenDto]): bool = + for token in tokens: + for currency, marketDto in token.marketValuesPerCurrency: + if not marketDto.hasError: + return false + return true + +proc anyTokenHasMarketValuesForAnyChain(tokens: seq[WalletTokenDto]): bool = + for token in tokens: + if len(token.marketValuesPerCurrency) > 0: + return true + return false + +################################################# +# Remote functions +################################################# + +proc getAccountsFromDb(): seq[WalletAccountDto] = + try: + let response = status_go_accounts.getAccounts() + return response.result.getElems().map( + x => x.toWalletAccountDto() + ).filter(a => not a.isChat) + except Exception as e: + error "error: ", procName="getAccounts", errName = e.name, errDesription = e.msg + +proc getWatchOnlyAccountsFromDb(): seq[WalletAccountDto] = + try: + let response = status_go_accounts.getWatchOnlyAccounts() + return response.result.getElems().map(x => x.toWalletAccountDto()) + except Exception as e: + error "error: ", procName="getWatchOnlyAccounts", errName = e.name, errDesription = e.msg + +proc getKeypairsFromDb(): seq[KeypairDto] = + try: + let response = status_go_accounts.getKeypairs() + return response.result.getElems().map(x => x.toKeypairDto()) + except Exception as e: + error "error: ", procName="getKeypairs", errName = e.name, errDesription = e.msg + +proc getKeypairByKeyUidFromDb(keyUid: string): KeypairDto = + if keyUid.len == 0: + return + try: + let response = status_go_accounts.getKeypairByKeyUid(keyUid) + if not response.error.isNil: + return + return response.result.toKeypairDto() + except Exception as e: + info "no known keypair", keyUid=keyUid, procName="getKeypairByKeyUid", errName = e.name, errDesription = e.msg + +proc getEnsName(address: string, chainId: int): string = + try: + let response = backend.getName(chainId, address) + return response.result.getStr + except Exception as e: + let errDesription = e.msg + error "error: ", errDesription \ No newline at end of file