diff --git a/src/app/modules/shared_modules/keycard_popup/controller.nim b/src/app/modules/shared_modules/keycard_popup/controller.nim index 9ad7274041..3939e22a7b 100644 --- a/src/app/modules/shared_modules/keycard_popup/controller.nim +++ b/src/app/modules/shared_modules/keycard_popup/controller.nim @@ -684,12 +684,12 @@ proc updateKeycardUid*(self: Controller, keyUid: string, keycardUid: string) = self.tmpKeycardUid = keycardUid info "update keycard uid failed", oldKeycardUid=self.tmpKeycardUid, newKeycardUid=keycardUid -proc addWalletAccount*(self: Controller, name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, accountType, - color, emoji: string): bool = +proc addWalletAccount*(self: Controller, name, keyPairName, address, path: string, lastUsedDerivationIndex: int, + rootWalletMasterKey, publicKey, keyUid, accountType, color, emoji: string): bool = if not serviceApplicable(self.walletAccountService): return false - let err = self.walletAccountService.addWalletAccount(name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, - accountType, color, emoji) + let err = self.walletAccountService.addWalletAccount(password = "", doPasswordHashing = false, name, keyPairName, + address, path, lastUsedDerivationIndex, rootWalletMasterKey, publicKey, keyUid, accountType, color, emoji) if err.len > 0: info "adding wallet account failed", name=name, path=path return false diff --git a/src/app/modules/shared_modules/keycard_popup/internal/creating_account_new_seed_phrase_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/creating_account_new_seed_phrase_state.nim index c810f0e18f..7d3b51ae0a 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/creating_account_new_seed_phrase_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/creating_account_new_seed_phrase_state.nim @@ -43,17 +43,21 @@ proc resolveAddresses(self: CreatingAccountNewSeedPhraseState, controller: Contr proc addAccountsToWallet(self: CreatingAccountNewSeedPhraseState, controller: Controller): bool = let kpForProcessing = controller.getKeyPairForProcessing() + var index = 0 for account in kpForProcessing.getAccountsModel().getItems(): if not controller.addWalletAccount(name = account.getName(), + keyPairName = kpForProcessing.getName(), address = account.getAddress(), path = account.getPath(), - addressAccountIsDerivedFrom = kpForProcessing.getDerivedFrom(), + lastUsedDerivationIndex = index, + rootWalletMasterKey = kpForProcessing.getDerivedFrom(), publicKey = account.getPubKey(), keyUid = kpForProcessing.getKeyUid(), - accountType = if account.getPath() == PATH_DEFAULT_WALLET: SEED else: GENERATED, + accountType = SEED, color = account.getColor(), emoji = account.getEmoji()): return false + index.inc return true proc doMigration(self: CreatingAccountNewSeedPhraseState, controller: Controller) = diff --git a/src/app/modules/shared_modules/keycard_popup/internal/creating_account_old_seed_phrase_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/creating_account_old_seed_phrase_state.nim index d4fc0707b8..7c9f6d5b54 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/creating_account_old_seed_phrase_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/creating_account_old_seed_phrase_state.nim @@ -43,17 +43,21 @@ proc resolveAddresses(self: CreatingAccountOldSeedPhraseState, controller: Contr proc addAccountsToWallet(self: CreatingAccountOldSeedPhraseState, controller: Controller): bool = let kpForProcessing = controller.getKeyPairForProcessing() + var index = 0 for account in kpForProcessing.getAccountsModel().getItems(): if not controller.addWalletAccount(name = account.getName(), + keyPairName = kpForProcessing.getName(), address = account.getAddress(), path = account.getPath(), - addressAccountIsDerivedFrom = kpForProcessing.getDerivedFrom(), + lastUsedDerivationIndex = index, + rootWalletMasterKey = kpForProcessing.getDerivedFrom(), publicKey = account.getPubKey(), keyUid = kpForProcessing.getKeyUid(), - accountType = if account.getPath() == PATH_DEFAULT_WALLET: SEED else: GENERATED, + accountType = SEED, color = account.getColor(), emoji = account.getEmoji()): return false + index.inc return true proc doMigration(self: CreatingAccountOldSeedPhraseState, controller: Controller) = diff --git a/src/app/modules/shared_modules/keycard_popup/internal/importing_from_keycard_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/importing_from_keycard_state.nim index e0249c2114..0f765cc641 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/importing_from_keycard_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/importing_from_keycard_state.nim @@ -13,18 +13,22 @@ proc delete*(self: ImportingFromKeycardState) = proc addAccountsToWallet(self: ImportingFromKeycardState, controller: Controller): bool = let kpForProcessing = controller.getKeyPairForProcessing() let kpHelper = controller.getKeyPairHelper() + var index = 0 for account in kpForProcessing.getAccountsModel().getItems(): self.addresses.add(account.getAddress()) if not controller.addWalletAccount(name = account.getName(), + keyPairName = kpForProcessing.getName(), address = account.getAddress(), path = account.getPath(), - addressAccountIsDerivedFrom = kpForProcessing.getDerivedFrom(), + lastUsedDerivationIndex = index, + rootWalletMasterKey = kpForProcessing.getDerivedFrom(), publicKey = account.getPubKey(), keyUid = kpForProcessing.getKeyUid(), - accountType = if account.getPath() == PATH_DEFAULT_WALLET: SEED else: GENERATED, + accountType = SEED, color = account.getColor(), emoji = account.getEmoji()): return false + index.inc return true proc doMigration(self: ImportingFromKeycardState, controller: Controller) = diff --git a/src/app_service/service/accounts/async_tasks.nim b/src/app_service/service/accounts/async_tasks.nim index e4e29d30d5..bd6e3cf051 100644 --- a/src/app_service/service/accounts/async_tasks.nim +++ b/src/app_service/service/accounts/async_tasks.nim @@ -17,4 +17,27 @@ const convertToKeycardAccountTask*: Task = proc(argEncoded: string) {.gcsafe, ni arg.finish(response) except Exception as e: error "error converting profile keypair: ", message = e.msg - arg.finish("") \ No newline at end of file + arg.finish("") + + +################################################# +# Async load derived addreses +################################################# + +type + FetchAddressesFromNotImportedMnemonicArg* = ref object of QObjectTaskArg + mnemonic: string + paths: seq[string] + +const fetchAddressesFromNotImportedMnemonicTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[FetchAddressesFromNotImportedMnemonicArg](argEncoded) + var output = %*{ + "derivedAddress": "", + "error": "" + } + try: + let response = status_account.createAccountFromMnemonicAndDeriveAccountsForPaths(arg.mnemonic, arg.paths) + output["derivedAddresses"] = response.result + except Exception as e: + output["error"] = %* fmt"Error fetching address from not imported mnemonic: {e.msg}" + arg.finish(output) \ No newline at end of file diff --git a/src/app_service/service/accounts/service.nim b/src/app_service/service/accounts/service.nim index b0f21d7c55..609c4dc700 100644 --- a/src/app_service/service/accounts/service.nim +++ b/src/app_service/service/accounts/service.nim @@ -1,4 +1,4 @@ -import NimQml, os, json, sequtils, strutils, uuids, times +import NimQml, Tables, os, json, strformat, sequtils, strutils, uuids, times import json_serialization, chronicles import ../../../app/global/global_singleton @@ -35,10 +35,15 @@ const KDF_ITERATIONS* {.intdefine.} = 256_000 let TEST_PEER_ENR = getEnv("TEST_PEER_ENR").string const SIGNAL_CONVERTING_PROFILE_KEYPAIR* = "convertingProfileKeypair" +const SIGNAL_DERIVED_ADDRESSES_FROM_NOT_IMPORTED_MNEMONIC_FETCHED* = "derivedAddressesFromNotImportedMnemonicFetched" type ResultArgs* = ref object of Args success*: bool +type DerivedAddressesFromNotImportedMnemonicArgs* = ref object of Args + error*: string + derivations*: Table[string, DerivedAccountDetails] + include utils include async_tasks @@ -496,7 +501,20 @@ QtObject: except Exception as e: error "error: ", procName="setupAccount", errName = e.name, errDesription = e.msg + proc createAccountFromPrivateKey*(self: Service, privateKey: string): GeneratedAccountDto = + if privateKey.len == 0: + error "empty private key" + return + try: + let response = status_account.createAccountFromPrivateKey(privateKey) + return toGeneratedAccountDto(response.result) + except Exception as e: + error "error: ", procName="createAccountFromPrivateKey", errName = e.name, errDesription = e.msg + proc createAccountFromMnemonic*(self: Service, mnemonic: string, paths: seq[string]): GeneratedAccountDto = + if mnemonic.len == 0: + error "empty mnemonic" + return try: let response = status_account.createAccountFromMnemonicAndDeriveAccountsForPaths(mnemonic, paths) return toGeneratedAccountDto(response.result) @@ -505,9 +523,6 @@ QtObject: proc createAccountFromMnemonic*(self: Service, mnemonic: string, includeEncryption = false, includeWhisper = false, includeRoot = false, includeDefaultWallet = false, includeEip1581 = false): GeneratedAccountDto = - if mnemonic.len == 0: - error "empty mnemonic" - return var paths: seq[string] if includeEncryption: paths.add(PATH_ENCRYPTION) @@ -519,7 +534,29 @@ QtObject: paths.add(PATH_DEFAULT_WALLET) if includeEip1581: paths.add(PATH_EIP_1581) - return self.createAccountFromMnemonic(mnemonic, paths) + return self.createAccountFromMnemonic(mnemonic, paths) + + proc fetchAddressesFromNotImportedMnemonic*(self: Service, mnemonic: string, paths: seq[string])= + let arg = FetchAddressesFromNotImportedMnemonicArg( + mnemonic: mnemonic, + paths: paths, + tptr: cast[ByteAddress](fetchAddressesFromNotImportedMnemonicTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onAddressesFromNotImportedMnemonicFetched", + ) + self.threadpool.start(arg) + + proc onAddressesFromNotImportedMnemonicFetched*(self: Service, jsonString: string) {.slot.} = + var data = DerivedAddressesFromNotImportedMnemonicArgs() + try: + let response = parseJson(jsonString) + data.error = response["error"].getStr() + if data.error.len == 0: + data.derivations = toGeneratedAccountDto(response["derivedAddresses"]).derivedAccounts.derivations + except Exception as e: + error "error: ", procName="fetchAddressesFromNotImportedMnemonic", errName = e.name, errDesription = e.msg + data.error = e.msg + self.events.emit(SIGNAL_DERIVED_ADDRESSES_FROM_NOT_IMPORTED_MNEMONIC_FETCHED, data) proc importMnemonic*(self: Service, mnemonic: string): string = if mnemonic.len == 0: diff --git a/src/app_service/service/wallet_account/async_tasks.nim b/src/app_service/service/wallet_account/async_tasks.nim index 975f2ea368..7b0c3cfa7d 100644 --- a/src/app_service/service/wallet_account/async_tasks.nim +++ b/src/app_service/service/wallet_account/async_tasks.nim @@ -2,108 +2,65 @@ # Async load derivedAddreses ################################################# type - GetDerivedAddressTaskArg* = ref object of QObjectTaskArg + FetchAddressesArg* = ref object of QObjectTaskArg + paths: seq[string] + +type + FetchDerivedAddressesTaskArg* = ref object of FetchAddressesArg password: string derivedFrom: string - path: string -const getDerivedAddressTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[GetDerivedAddressTaskArg](argEncoded) +const fetchDerivedAddressesTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[FetchDerivedAddressesTaskArg](argEncoded) var output = %*{ "derivedAddress": "", "error": "" } try: - let response = status_go_accounts.getDerivedAddress(arg.password, arg.derivedFrom, arg.path) + let response = status_go_accounts.getDerivedAddresses(arg.password, arg.derivedFrom, arg.paths) output["derivedAddresses"] = response.result except Exception as e: - output["error"] = %* fmt"Error getting derived address list: {e.msg}" + output["error"] = %* fmt"Error fetching derived address: {e.msg}" arg.finish(output) type - GetDerivedAddressesTaskArg* = ref object of GetDerivedAddressTaskArg - pageSize: int - pageNumber: int - -const getDerivedAddressesTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[GetDerivedAddressesTaskArg](argEncoded) - try: - let response = status_go_accounts.getDerivedAddressList(arg.password, arg.derivedFrom, arg.path, arg.pageSize, arg.pageNumber) - - let output = %*{ - "derivedAddresses": response.result, - "error": "" - } - arg.finish(output) - except Exception as e: - let output = %* { - "derivedAddresses": "", - "error": fmt"Error getting derived address list: {e.msg}" - } - arg.finish(output) - -type - GetDerivedAddressesForMnemonicTaskArg* = ref object of QObjectTaskArg + FetchDerivedAddressesForMnemonicTaskArg* = ref object of FetchAddressesArg mnemonic: string - path: string - pageSize: int - pageNumber: int -const getDerivedAddressesForMnemonicTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[GetDerivedAddressesForMnemonicTaskArg](argEncoded) - try: - let response = status_go_accounts.getDerivedAddressListForMnemonic(arg.mnemonic, arg.path, arg.pageSize, arg.pageNumber) - - let output = %*{ - "derivedAddresses": response.result, - "error": "" - } - arg.finish(output) - except Exception as e: - let output = %* { - "derivedAddresses": "", - "error": fmt"Error getting derived address list for mnemonic: {e.msg}" - } - arg.finish(output) - -type - GetDerivedAddressForPrivateKeyTaskArg* = ref object of QObjectTaskArg - privateKey: string - -const getDerivedAddressForPrivateKeyTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[GetDerivedAddressForPrivateKeyTaskArg](argEncoded) - try: - let response = status_go_accounts.getDerivedAddressForPrivateKey(arg.privateKey) - - let output = %*{ - "derivedAddresses": response.result, - "error": "" - } - arg.finish(output) - except Exception as e: - let output = %* { - "derivedAddresses": "", - "error": fmt"Error getting derived address list for private key: {e.msg}" - } - arg.finish(output) - -type - FetchDerivedAddressDetailsTaskArg* = ref object of QObjectTaskArg - address: string - -const fetchDerivedAddressDetailsTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[FetchDerivedAddressDetailsTaskArg](argEncoded) - var data = %* { - "details": "", +const fetchDerivedAddressesForMnemonicTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[FetchDerivedAddressesForMnemonicTaskArg](argEncoded) + var output = %*{ + "derivedAddress": "", "error": "" } try: - let response = status_go_accounts.getDerivedAddressDetails(arg.address) - data["details"] = response.result + let response = status_go_accounts.getDerivedAddressesForMnemonic(arg.mnemonic, arg.paths) + output["derivedAddresses"] = response.result except Exception as e: - let err = fmt"Error getting details for an address: {e.msg}" - data["error"] = %* err - arg.finish(data) + output["error"] = %* fmt"Error fetching derived address for mnemonic: {e.msg}" + arg.finish(output) + +type + FetchDetailsForAddressesTaskArg* = ref object of QObjectTaskArg + uniqueId: string + addresses: seq[string] + +const fetchDetailsForAddressesTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[FetchDetailsForAddressesTaskArg](argEncoded) + for address in arg.addresses: + var data = %* { + "uniqueId": arg.uniqueId, + "details": "", + "error": "" + } + try: + let response = status_go_accounts.getAddressDetails(address) + sleep(250) + data["details"] = response.result + except Exception as e: + let err = fmt"Error fetching details for an address: {e.msg}" + data["error"] = %* err + arg.finish(data) ################################################# # Async building token diff --git a/src/app_service/service/wallet_account/dto.nim b/src/app_service/service/wallet_account/dto.nim index 66fb99c5c6..a977bb1a6b 100644 --- a/src/app_service/service/wallet_account/dto.nim +++ b/src/app_service/service/wallet_account/dto.nim @@ -95,6 +95,8 @@ type relatedAccounts*: seq[WalletAccountDto] ens*: string assetsLoading*: bool + keypairName*: string + lastUsedDerivationIndex*: int proc newDto*( name: string, @@ -137,6 +139,8 @@ proc toWalletAccountDto*(jsonObj: JsonNode): WalletAccountDto = discard jsonObj.getProp("type", result.walletType) discard jsonObj.getProp("emoji", result.emoji) discard jsonObj.getProp("derived-from", result.derivedfrom) + discard jsonObj.getProp("keypair-name", result.keypairName) + discard jsonObj.getProp("last-used-derivation-index", result.lastUsedDerivationIndex) result.assetsLoading = true proc getCurrencyBalance*(self: BalanceDto, currencyPrice: float64): float64 = diff --git a/src/app_service/service/wallet_account/service.nim b/src/app_service/service/wallet_account/service.nim index e7c3611bab..4d9ca7ad20 100644 --- a/src/app_service/service/wallet_account/service.nim +++ b/src/app_service/service/wallet_account/service.nim @@ -30,9 +30,11 @@ const SIGNAL_WALLET_ACCOUNT_DELETED* = "walletAccount/accountDeleted" const SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED* = "walletAccount/currencyUpdated" const SIGNAL_WALLET_ACCOUNT_UPDATED* = "walletAccount/walletAccountUpdated" const SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED* = "walletAccount/networkEnabledUpdated" -const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY* = "walletAccount/derivedAddressesReady" const SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT* = "walletAccount/tokensRebuilt" -const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_DETAILS_FETCHED* = "walletAccount/derivedAddressDetailsFetched" +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_KEYCARDS_SYNCHRONIZED* = "keycardsSynchronized" const SIGNAL_NEW_KEYCARD_SET* = "newKeycardSet" @@ -83,6 +85,7 @@ type WalletAccountUpdated* = ref object of Args account*: WalletAccountDto type DerivedAddressesArgs* = ref object of Args + uniqueId*: string derivedAddresses*: seq[DerivedAddressDto] error*: string @@ -264,7 +267,7 @@ QtObject: proc getIndex*(self: Service, address: string): int = let accounts = self.getWalletAccounts() - for i in 0..accounts.len: + for i in 0 ..< accounts.len: if(accounts[i].address == address): return i @@ -308,101 +311,77 @@ QtObject: self.buildAllTokens(@[newAccount.address], store = true) self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountSaved(account: newAccount)) - proc addOrReplaceWalletAccount(self: Service, name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, accountType, - color, emoji: string, walletDefaultAccount = false, chatDefaultAccount = false): string = + ## if password is not provided local keystore file won't be created + proc addWalletAccount*(self: Service, password: string, doPasswordHashing: bool, name, keyPairName, address, path: string, + lastUsedDerivationIndex: int, rootWalletMasterKey, publicKey, keyUid, accountType, color, emoji: string): string = try: - let response = status_go_accounts.saveAccount(name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, - accountType, color, emoji, walletDefaultAccount, chatDefaultAccount) + var response: RpcResponse[JsonNode] + if password.len == 0: + response = status_go_accounts.addAccountWithoutKeystoreFileCreation(name, keyPairName, address, path, lastUsedDerivationIndex, + rootWalletMasterKey, publicKey, keyUid, accountType, color, emoji) + else: + var finalPassword = password + if doPasswordHashing: + finalPassword = utils.hashPassword(password) + response = status_go_accounts.addAccount(finalPassword, name, keyPairName, address, path, + lastUsedDerivationIndex, rootWalletMasterKey, publicKey, keyUid, accountType, color, emoji) if not response.error.isNil: - return "(" & $response.error.code & ") " & response.error.message + error "status-go error", procName="addWalletAccount", errCode=response.error.code, errDesription=response.error.message + return response.error.message + self.addNewAccountToLocalStore() + return "" except Exception as e: - error "error: ", procName="addWalletAccount", errName = e.name, errDesription = e.msg - return "error: " & e.msg + error "error: ", procName="addWalletAccount", errName=e.name, errDesription=e.msg + return e.msg - proc generateNewAccount*(self: Service, password: string, accountName: string, color: string, emoji: string, - path: string, derivedFrom: string, skipPasswordVerification: bool): string = + proc addNewPrivateKeyAccount*(self: Service, privateKey, password: string, doPasswordHashing: bool, name, keyPairName, address, path: string, + lastUsedDerivationIndex: int, rootWalletMasterKey, publicKey, keyUid, accountType, color, emoji: string): 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: - if skipPasswordVerification: - discard backend.generateAccountWithDerivedPathPasswordVerified( - password, - accountName, - color, - emoji, - path, - derivedFrom) - else: - discard backend.generateAccountWithDerivedPath( - utils.hashPassword(password), - accountName, - color, - emoji, - path, - derivedFrom) + let response = status_go_accounts.importPrivateKey(privateKey, finalPassword) + if not response.error.isNil: + error "status-go error importing private key", procName="addNewPrivateKeyAccount", errCode=response.error.code, errDesription=response.error.message + return response.error.message + return self.addWalletAccount(password, doPasswordHashing, name, keyPairName, address, path, lastUsedDerivationIndex, rootWalletMasterKey, publicKey, + keyUid, accountType, color, emoji) except Exception as e: - return fmt"Error generating new account: {e.msg}" + error "error: ", procName="addNewPrivateKeyAccount", errName=e.name, errDesription=e.msg + return e.msg - self.addNewAccountToLocalStore() - - proc addAccountsFromPrivateKey*(self: Service, privateKey: string, password: string, accountName: string, color: string, - emoji: string, skipPasswordVerification: bool): string = + proc addNewSeedPhraseAccount*(self: Service, seedPhrase, password: string, doPasswordHashing: bool, name, keyPairName, address, path: string, + lastUsedDerivationIndex: int, rootWalletMasterKey, publicKey, keyUid, accountType, color, emoji: string): string = + if password.len == 0: + error "for adding new seed phrase account, password must be provided" + return + var finalPassword = password + if doPasswordHashing: + finalPassword = utils.hashPassword(password) try: - if skipPasswordVerification: - discard backend.addAccountWithPrivateKeyPasswordVerified( - privateKey, - password, - accountName, - color, - emoji) - else: - discard backend.addAccountWithPrivateKey( - privateKey, - utils.hashPassword(password), - accountName, - color, - emoji) + let response = status_go_accounts.importMnemonic(seedPhrase, finalPassword) + if not response.error.isNil: + error "status-go error importing private key", procName="addNewSeedPhraseAccount", errCode=response.error.code, errDesription=response.error.message + return response.error.message + return self.addWalletAccount(password, doPasswordHashing, name, keyPairName, address, path, lastUsedDerivationIndex, rootWalletMasterKey, publicKey, + keyUid, accountType, color, emoji) except Exception as e: - return fmt"Error adding account with private key: {e.msg}" + error "error: ", procName="addNewSeedPhraseAccount", errName=e.name, errDesription=e.msg + return e.msg - self.addNewAccountToLocalStore() - - proc addAccountsFromSeed*(self: Service, mnemonic: string, password: string, accountName: string, color: string, - emoji: string, path: string, skipPasswordVerification: bool): string = + proc getRandomMnemonic*(self: Service): string = try: - if skipPasswordVerification: - discard backend.addAccountWithMnemonicAndPathPasswordVerified( - mnemonic, - password, - accountName, - color, - emoji, - path - ) - else: - discard backend.addAccountWithMnemonicAndPath( - mnemonic, - utils.hashPassword(password), - accountName, - color, - emoji, - path - ) + 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: - return fmt"Error adding account with mnemonic: {e.msg}" - - self.addNewAccountToLocalStore() - - proc addWatchOnlyAccount*(self: Service, address: string, accountName: string, color: string, emoji: string): string = - try: - discard backend.addAccountWatch( - address, - accountName, - color, - emoji - ) - except Exception as e: - return fmt"Error adding account with mnemonic: {e.msg}" - - self.addNewAccountToLocalStore() + error "error: ", procName="getRandomMnemonic", errName=e.name, errDesription=e.msg + return "" proc deleteAccount*(self: Service, address: string, password = "") = try: @@ -437,102 +416,83 @@ QtObject: if not self.walletAccountsContainsAddress(address): error "account's address is not among known addresses: ", address=address return - var account = self.getAccountByAddress(address) - let res = self.addOrReplaceWalletAccount(accountName, account.address, account.path, account.derivedfrom, - account.publicKey, account.keyUid, account.walletType, color, emoji, account.isWallet, account.isChat) - if res.len == 0: + try: + var account = self.getAccountByAddress(address) + let response = status_go_accounts.updateAccount(accountName, account.keyPairName, account.address, account.path, account.lastUsedDerivationIndex, + account.derivedfrom, account.publicKey, account.keyUid, account.walletType, color, emoji, account.isWallet, account.isChat) + if not response.error.isNil: + error "status-go error", procName="updateWalletAccount", errCode=response.error.code, errDesription=response.error.message + return account.name = accountName account.color = color account.emoji = emoji self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, WalletAccountUpdated(account: account)) + except Exception as e: + error "error: ", procName="updateWalletAccount", errName=e.name, errDesription=e.msg - proc getDerivedAddress*(self: Service, password: string, derivedFrom: string, path: string, hashPassword: bool)= - let arg = GetDerivedAddressTaskArg( + 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, - path: path, - tptr: cast[ByteAddress](getDerivedAddressTask), + paths: paths, + tptr: cast[ByteAddress](fetchDerivedAddressesTask), vptr: cast[ByteAddress](self.vptr), - slot: "setDerivedAddress", + slot: "onDerivedAddressesFetched", ) self.threadpool.start(arg) - proc getDerivedAddressList*(self: Service, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int, hashPassword: bool)= - let arg = GetDerivedAddressesTaskArg( - password: if hashPassword: utils.hashPassword(password) else: password, - derivedFrom: derivedFrom, - path: path, - pageSize: pageSize, - pageNumber: pageNumber, - tptr: cast[ByteAddress](getDerivedAddressesTask), - vptr: cast[ByteAddress](self.vptr), - slot: "setDerivedAddresses", - ) - self.threadpool.start(arg) - - proc getDerivedAddressListForMnemonic*(self: Service, mnemonic: string, path: string, pageSize: int, pageNumber: int) = - let arg = GetDerivedAddressesForMnemonicTaskArg( - mnemonic: mnemonic, - path: path, - pageSize: pageSize, - pageNumber: pageNumber, - tptr: cast[ByteAddress](getDerivedAddressesForMnemonicTask), - vptr: cast[ByteAddress](self.vptr), - slot: "setDerivedAddresses", - ) - self.threadpool.start(arg) - - proc getDerivedAddressForPrivateKey*(self: Service, privateKey: string) = - let arg = GetDerivedAddressForPrivateKeyTaskArg( - privateKey: privateKey, - tptr: cast[ByteAddress](getDerivedAddressForPrivateKeyTask), - vptr: cast[ByteAddress](self.vptr), - slot: "setDerivedAddresses", - ) - self.threadpool.start(arg) - - proc setDerivedAddresses*(self: Service, derivedAddressesJson: string) {.slot.} = - let response = parseJson(derivedAddressesJson) + 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() - - # emit event - self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY, DerivedAddressesArgs( + self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FETCHED, DerivedAddressesArgs( derivedAddresses: derivedAddress, error: error )) - proc setDerivedAddress*(self: Service, derivedAddressesJson: string) {.slot.} = - let response = parseJson(derivedAddressesJson) - let derivedAddress = response["derivedAddresses"].toDerivedAddressDto() - let error = response["error"].getStr() - # emit event - self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY, DerivedAddressesArgs( - derivedAddresses: @[derivedAddress], - error: error - )) - - proc fetchDerivedAddressDetails*(self: Service, address: string) = - let arg = FetchDerivedAddressDetailsTaskArg( - address: address, - tptr: cast[ByteAddress](fetchDerivedAddressDetailsTask), + 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: "onDerivedAddressDetailsFetched", + slot: "onDerivedAddressesForMnemonicFetched", ) self.threadpool.start(arg) - proc onDerivedAddressDetailsFetched*(self: Service, jsonString: string) {.slot.} = + 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 arg = FetchDetailsForAddressesTaskArg( + uniqueId: uniqueId, + 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="getDerivedAddressDetails", errName = e.name, errDesription = e.msg + error "error: ", procName="fetchAddressDetails", errName = e.name, errDesription = e.msg data.error = e.msg - self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_DETAILS_FETCHED, data) + self.events.emit(SIGNAL_WALLET_ACCOUNT_ADDRESS_DETAILS_FETCHED, data) proc updateAssetsLoadingState(self: Service, wAddress: string, loading: bool) = withLock self.walletAccountsLock: @@ -809,13 +769,6 @@ QtObject: error "error: ", procName="deleteKeycard", errName = e.name, errDesription = e.msg return false - proc addWalletAccount*(self: Service, name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, accountType, - color, emoji: string): string = - result = self.addOrReplaceWalletAccount(name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, - accountType, color, emoji) - if result.len == 0: - self.addNewAccountToLocalStore() - proc handleKeycardActions(self: Service, keycardActions: seq[KeycardActionDto]) = if keycardActions.len == 0: return diff --git a/src/backend/accounts.nim b/src/backend/accounts.nim index 4d871b4cc2..aec1a5d948 100644 --- a/src/backend/accounts.nim +++ b/src/backend/accounts.nim @@ -1,5 +1,6 @@ import json, json_serialization, chronicles, strutils import ./core, ../app_service/common/utils +import ../app_service/common/account_constants import ./response_type import status_go @@ -25,22 +26,64 @@ proc deleteAccount*(address: string, password: string): RpcResponse[JsonNode] {. let payload = %* [address, password] return core.callPrivateRPC("accounts_deleteAccount", payload) -proc saveAccount*(name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, accountType, color, emoji: string, - walletDefaultAccount: bool, chatDefaultAccount: bool): +## Adds a new account and creates a Keystore file if password is provided, otherwise it only creates a new account +proc addAccount*(password, name, keyPairName, address, path: string, lastUsedDerivationIndex: int, rootWalletMasterKey, publicKey, + keyUid, accountType, color, emoji: string): + RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [ + password, + { + "address": address, + "key-uid": keyUid, + "wallet": false, #this refers to the default wallet account and it's set at the moment of Status chat account creation, cannot be changed later + "chat": false, #this refers to Status chat account, set when the Status account is created, cannot be changed later + "type": accountType, + #"storage" present on the status-go side, but we don't use it + "path": path, + "public-key": publicKey, + "name": name, + "emoji": emoji, + "color": color, + #"hidden" present on the status-go side, but we don't use it + "derived-from": rootWalletMasterKey, + #"clock" we leave this empty, if needed should be set on the status-go side + #"removed" present on the status-go side, used for synchronization, no need to set it here + "keypair-name": keyPairName, + "last-used-derivation-index": lastUsedDerivationIndex + } + ] + return core.callPrivateRPC("accounts_addAccount", payload) + +## Adds a new account without creating a Keystore file +proc addAccountWithoutKeystoreFileCreation*(name, keyPairName, address, path: string, lastUsedDerivationIndex: int, rootWalletMasterKey, publicKey, + keyUid, accountType, color, emoji: string): + RpcResponse[JsonNode] {.raises: [Exception].} = + return addAccount(password = "", name, keyPairName, address, path, lastUsedDerivationIndex, rootWalletMasterKey, publicKey, + keyUid, accountType, color, emoji) + +## Updates either regular or keycard account, without interaction to a Keystore file +proc updateAccount*(name, keyPairName, address, path: string, lastUsedDerivationIndex: int, rootWalletMasterKey, publicKey, + keyUid, accountType, color, emoji: string, walletDefaultAccount: bool, chatDefaultAccount: bool): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* [ [{ - "name": name, "address": address, - "path": path, - "derived-from": addressAccountIsDerivedFrom, - "public-key": publicKey, "key-uid": keyUid, - "type": accountType, - "color": color, - "emoji": emoji, "wallet": walletDefaultAccount, - "chat": chatDefaultAccount + "chat": chatDefaultAccount, + "type": accountType, + #"storage" present on the status-go side, but we don't use it + "path": path, + "public-key": publicKey, + "name": name, + "emoji": emoji, + "color": color, + #"hidden" present on the status-go side, but we don't use it + "derived-from": rootWalletMasterKey, + #"clock" we leave this empty, if needed should be set on the status-go side + #"removed" present on the status-go side, used for synchronization, no need to set it here + "keypair-name": keyPairName, + "last-used-derivation-index": lastUsedDerivationIndex }] ] return core.callPrivateRPC("accounts_saveAccounts", payload) @@ -130,6 +173,10 @@ proc isAlias*(value: string): bool = let r = Json.decode(response, JsonNode) return r["result"].getBool() +proc getRandomMnemonic*(): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [] + return core.callPrivateRPC("accounts_getRandomMnemonic", payload) + proc multiAccountImportMnemonic*(mnemonic: string): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* { "mnemonicPhrase": mnemonic, @@ -144,6 +191,12 @@ proc multiAccountImportMnemonic*(mnemonic: string): RpcResponse[JsonNode] {.rais error "error doing rpc request", methodName = "multiAccountImportMnemonic", exception=e.msg raise newException(RpcException, e.msg) +## Imports a new mnemonic and creates local keystore file. +proc importMnemonic*(mnemonic, password: string): + RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [mnemonic, password] + return core.callPrivateRPC("accounts_importMnemonic", payload) + proc createAccountFromMnemonicAndDeriveAccountsForPaths*(mnemonic: string, paths: seq[string]): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* { "mnemonicPhrase": mnemonic, @@ -158,6 +211,21 @@ proc createAccountFromMnemonicAndDeriveAccountsForPaths*(mnemonic: string, paths error "error doing rpc request", methodName = "createAccountFromMnemonicAndDeriveAccountsForPaths", exception=e.msg raise newException(RpcException, e.msg) +## Imports a new private key and creates local keystore file. +proc importPrivateKey*(privateKey, password: string): + RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [privateKey, password] + return core.callPrivateRPC("accounts_importPrivateKey", payload) + +proc createAccountFromPrivateKey*(privateKey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* {"privateKey": privateKey} + try: + let response = status_go.createAccountFromPrivateKey($payload) + result.result = Json.decode(response, JsonNode) + except RpcException as e: + error "error doing rpc request", methodName = "createAccountFromPrivateKey", exception=e.msg + raise newException(RpcException, e.msg) + proc deriveAccounts*(accountId: string, paths: seq[string]): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* { "accountID": accountId, @@ -323,25 +391,17 @@ proc setDisplayName*(displayName: string): RpcResponse[JsonNode] {.raises: [Exce let payload = %* [displayName] result = core.callPrivateRPC("setDisplayName".prefix, payload) -proc getDerivedAddress*(password: string, derivedFrom: string, path: string): RpcResponse[JsonNode] {.raises: [Exception].} = - let payload = %* [password, derivedFrom, path] - result = core.callPrivateRPC("wallet_getDerivedAddressForPath", payload) +proc getDerivedAddresses*(password: string, derivedFrom: string, paths: seq[string]): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [password, derivedFrom, paths] + result = core.callPrivateRPC("wallet_getDerivedAddresses", payload) -proc getDerivedAddressList*(password: string, derivedFrom: string, path: string, pageSize: int = 0, pageNumber: int = 6,): RpcResponse[JsonNode] {.raises: [Exception].} = - let payload = %* [password, derivedFrom, path, pageSize, pageNumber ] - result = core.callPrivateRPC("wallet_getDerivedAddressesForPath", payload) +proc getDerivedAddressesForMnemonic*(mnemonic: string, paths: seq[string]): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [mnemonic, paths] + result = core.callPrivateRPC("wallet_getDerivedAddressesForMnemonic", payload) -proc getDerivedAddressListForMnemonic*(mnemonic: string, path: string, pageSize: int = 0, pageNumber: int = 6,): RpcResponse[JsonNode] {.raises: [Exception].} = - let payload = %* [mnemonic, path, pageSize, pageNumber ] - result = core.callPrivateRPC("wallet_getDerivedAddressesForMnemonicWithPath", payload) - -proc getDerivedAddressForPrivateKey*(privateKey: string,): RpcResponse[JsonNode] {.raises: [Exception].} = - let payload = %* [privateKey] - result = core.callPrivateRPC("wallet_getDerivedAddressForPrivateKey", payload) - -proc getDerivedAddressDetails*(address: string,): RpcResponse[JsonNode] {.raises: [Exception].} = +proc getAddressDetails*(address: string,): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* [address] - result = core.callPrivateRPC("wallet_getDerivedAddressDetails", payload) + result = core.callPrivateRPC("wallet_getAddressDetails", payload) proc verifyPassword*(password: string): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* [password] diff --git a/src/backend/backend.nim b/src/backend/backend.nim index bc94724d05..1c1366cfa2 100644 --- a/src/backend/backend.nim +++ b/src/backend/backend.nim @@ -113,58 +113,6 @@ rpc(fetchPrices, "wallet"): symbols: seq[string] currencies: seq[string] -rpc(generateAccountWithDerivedPath, "accounts"): - password: string - name: string - color: string - emoji: string - path: string - derivedFrom: string - -rpc(generateAccountWithDerivedPathPasswordVerified, "accounts"): - password: string - name: string - color: string - emoji: string - path: string - derivedFrom: string - -rpc(addAccountWithMnemonicAndPath, "accounts"): - mnemonic: string - password: string - name: string - color: string - emoji: string - path: string - -rpc(addAccountWithMnemonicAndPathPasswordVerified, "accounts"): - mnemonic: string - password: string - name: string - color: string - emoji: string - path: string - -rpc(addAccountWithPrivateKey, "accounts"): - privateKey: string - password: string - name: string - color: string - emoji: string - -rpc(addAccountWithPrivateKeyPasswordVerified, "accounts"): - privateKey: string - password: string - name: string - color: string - emoji: string - -rpc(addAccountWatch, "accounts"): - address: string - name: string - color: string - emoji: string - rpc(activityCenterNotifications, "wakuext"): request: ActivityCenterNotificationsRequest diff --git a/vendor/nim-status-go b/vendor/nim-status-go index 73fe79616c..534c04dc73 160000 --- a/vendor/nim-status-go +++ b/vendor/nim-status-go @@ -1 +1 @@ -Subproject commit 73fe79616ce6c7a6e5e79f6425d20cd74b788ffd +Subproject commit 534c04dc737f681ef49207e1f183e9fc53647fd5 diff --git a/vendor/status-go b/vendor/status-go index 8c85a62e10..e9482e3974 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 8c85a62e108d9c54f6124117cf7a196fb3731732 +Subproject commit e9482e3974a2f82bd6a23f0c9e861b5bca23f472