diff --git a/src/app/modules/main/wallet_section/saved_addresses/controller.nim b/src/app/modules/main/wallet_section/saved_addresses/controller.nim index ddc362d836..2652ddaa2c 100644 --- a/src/app/modules/main/wallet_section/saved_addresses/controller.nim +++ b/src/app/modules/main/wallet_section/saved_addresses/controller.nim @@ -27,11 +27,14 @@ proc init*(self: Controller) = self.events.on(SIGNAL_SAVED_ADDRESS_UPDATED) do(e:Args): let args = SavedAddressArgs(e) - self.delegate.savedAddressUpdated(args.name, args.address, args.ens, args.errorMsg) + self.delegate.savedAddressUpdated(args.name, args.address, args.errorMsg) self.events.on(SIGNAL_SAVED_ADDRESS_DELETED) do(e:Args): let args = SavedAddressArgs(e) - self.delegate.savedAddressDeleted(args.address, args.ens, args.errorMsg) + self.delegate.savedAddressDeleted(args.address, args.errorMsg) + +proc areTestNetworksEnabled*(self: Controller): bool = + return self.savedAddressService.areTestNetworksEnabled() proc getSavedAddresses*(self: Controller): seq[saved_address_service.SavedAddressDto] = return self.savedAddressService.getSavedAddresses() @@ -40,5 +43,5 @@ proc createOrUpdateSavedAddress*(self: Controller, name: string, address: string chainShortNames: string) = self.savedAddressService.createOrUpdateSavedAddress(name, address, ens, colorId, chainShortNames) -proc deleteSavedAddress*(self: Controller, address: string, ens: string) = - self.savedAddressService.deleteSavedAddress(address, ens) +proc deleteSavedAddress*(self: Controller, address: string) = + self.savedAddressService.deleteSavedAddress(address) diff --git a/src/app/modules/main/wallet_section/saved_addresses/io_interface.nim b/src/app/modules/main/wallet_section/saved_addresses/io_interface.nim index 2deef205f9..9a167ff181 100644 --- a/src/app/modules/main/wallet_section/saved_addresses/io_interface.nim +++ b/src/app/modules/main/wallet_section/saved_addresses/io_interface.nim @@ -21,13 +21,13 @@ method createOrUpdateSavedAddress*(self: AccessInterface, name: string, address: chainShortNames: string) {.base.} = raise newException(ValueError, "No implementation available") -method deleteSavedAddress*(self: AccessInterface, address: string, ens: string) {.base.} = +method deleteSavedAddress*(self: AccessInterface, address: string) {.base.} = raise newException(ValueError, "No implementation available") -method savedAddressUpdated*(self: AccessInterface, name: string, address: string, ens: string, errorMsg: string) {.base.} = +method savedAddressUpdated*(self: AccessInterface, name: string, address: string, errorMsg: string) {.base.} = raise newException(ValueError, "No implementation available") -method savedAddressDeleted*(self: AccessInterface, address: string, ens: string, errorMsg: string) {.base.} = +method savedAddressDeleted*(self: AccessInterface, address: string, errorMsg: string) {.base.} = raise newException(ValueError, "No implementation available") method savedAddressNameExists*(self: AccessInterface, name: string): bool {.base.} = diff --git a/src/app/modules/main/wallet_section/saved_addresses/model.nim b/src/app/modules/main/wallet_section/saved_addresses/model.nim index d930840caa..771bf07940 100644 --- a/src/app/modules/main/wallet_section/saved_addresses/model.nim +++ b/src/app/modules/main/wallet_section/saved_addresses/model.nim @@ -102,25 +102,17 @@ QtObject: for item in items: self.itemChanged(item.getAddress()) - proc getItemByAddress*(self: Model, address: string): Item = + proc getItemByAddress*(self: Model, address: string, isTest: bool): Item = if address.len == 0 or address == ZERO_ADDRESS: return for item in self.items: - if cmpIgnoreCase(item.getAddress(), address) == 0: - return item - - proc getItemByEnsOrAddress*(self: Model, addrOrEns: string): Item = - if addrOrEns.len == 0: - return - for item in self.items: - if item.getEns().len > 0: - if item.getEns() == addrOrEns: + if cmpIgnoreCase(item.getAddress(), address) == 0 and + (item.getIsTest() == isTest): return item - if addrOrEns != ZERO_ADDRESS and cmpIgnoreCase(item.getAddress(), addrOrEns) == 0: - return item - proc nameExists*(self: Model, name: string): bool = + proc nameExists*(self: Model, name: string, isTest: bool): bool = for item in self.items: - if item.getName() == name: - return true + if item.getName() == name and + (item.getIsTest() == isTest): + return true return false \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/saved_addresses/module.nim b/src/app/modules/main/wallet_section/saved_addresses/module.nim index 06f0f36ea1..186f8cd7d8 100644 --- a/src/app/modules/main/wallet_section/saved_addresses/module.nim +++ b/src/app/modules/main/wallet_section/saved_addresses/module.nim @@ -61,28 +61,24 @@ method createOrUpdateSavedAddress*(self: Module, name: string, address: string, chainShortNames: string) = self.controller.createOrUpdateSavedAddress(name, address, ens, colorId, chainShortNames) -method deleteSavedAddress*(self: Module, address: string, ens: string) = - self.controller.deleteSavedAddress(address, ens) +method deleteSavedAddress*(self: Module, address: string) = + self.controller.deleteSavedAddress(address) -method savedAddressUpdated*(self: Module, name: string, address: string, ens: string, errorMsg: string) = - var item = self.view.getModel().getItemByEnsOrAddress(address) - if item.isEmpty(): - item = self.view.getModel().getItemByEnsOrAddress(ens) +method savedAddressUpdated*(self: Module, name: string, address: string, errorMsg: string) = + let item = self.view.getModel().getItemByAddress(address, self.controller.areTestNetworksEnabled()) self.loadSavedAddresses() - self.view.savedAddressAddedOrUpdated(item.isEmpty(), name, address, ens, errorMsg) + self.view.savedAddressAddedOrUpdated(item.isEmpty(), name, address, errorMsg) -method savedAddressDeleted*(self: Module, address: string, ens: string, errorMsg: string) = - var item = self.view.getModel().getItemByEnsOrAddress(address) - if item.isEmpty(): - item = self.view.getModel().getItemByEnsOrAddress(ens) +method savedAddressDeleted*(self: Module, address: string, errorMsg: string) = + let item = self.view.getModel().getItemByAddress(address, self.controller.areTestNetworksEnabled()) self.loadSavedAddresses() - self.view.savedAddressDeleted(item.getName(), address, ens, errorMsg) + self.view.savedAddressDeleted(item.getName(), address, errorMsg) method savedAddressNameExists*(self: Module, name: string): bool = - return self.view.getModel().nameExists(name) + return self.view.getModel().nameExists(name, self.controller.areTestNetworksEnabled()) method getSavedAddressAsJson*(self: Module, address: string): string = - let item = self.view.getModel().getItemByAddress(address) + let item = self.view.getModel().getItemByAddress(address, self.controller.areTestNetworksEnabled()) let jsonObj = %* { "name": item.getName(), "address": item.getAddress(), diff --git a/src/app/modules/main/wallet_section/saved_addresses/view.nim b/src/app/modules/main/wallet_section/saved_addresses/view.nim index e153a9576d..361a695cce 100644 --- a/src/app/modules/main/wallet_section/saved_addresses/view.nim +++ b/src/app/modules/main/wallet_section/saved_addresses/view.nim @@ -40,16 +40,16 @@ QtObject: proc setItems*(self: View, items: seq[Item]) = self.model.setItems(items) - proc savedAddressAddedOrUpdated*(self: View, added: bool, name: string, address: string, ens: string, errorMsg: string) {.signal.} + proc savedAddressAddedOrUpdated*(self: View, added: bool, name: string, address: string, errorMsg: string) {.signal.} proc createOrUpdateSavedAddress*(self: View, name: string, address: string, ens: string, colorId: string, chainShortNames: string) {.slot.} = self.delegate.createOrUpdateSavedAddress(name, address, ens, colorId, chainShortNames) - proc savedAddressDeleted*(self: View, name: string, address: string, ens: string, errorMsg: string) {.signal.} + proc savedAddressDeleted*(self: View, name: string, address: string, errorMsg: string) {.signal.} - proc deleteSavedAddress*(self: View, address: string, ens: string) {.slot.} = - self.delegate.deleteSavedAddress(address, ens) + proc deleteSavedAddress*(self: View, address: string) {.slot.} = + self.delegate.deleteSavedAddress(address) proc savedAddressNameExists*(self: View, name: string): bool {.slot.} = return self.delegate.savedAddressNameExists(name) diff --git a/src/app_service/service/saved_address/async_tasks.nim b/src/app_service/service/saved_address/async_tasks.nim index fc7a1e74a7..63736e522e 100644 --- a/src/app_service/service/saved_address/async_tasks.nim +++ b/src/app_service/service/saved_address/async_tasks.nim @@ -4,7 +4,10 @@ include app/core/tasks/common import backend/backend type - SavedAddressTaskArg = ref object of QObjectTaskArg + SavedAddressesTaskArg = ref object of QObjectTaskArg + chainId*: int + + SavedAddressTaskArg = ref object of SavedAddressesTaskArg name: string address: string colorId: string @@ -12,6 +15,54 @@ type ens: string isTestAddress: bool + UpdateCriteria {.pure.} = enum + AlwaysUpdate + OnlyIfDifferent + +proc isValidChainId(chainId: int): bool = + return chainId == Mainnet or chainId == Goerli or chainId == Sepolia + +proc checkForEnsNameAndUpdate(chainId: int, savedAddress: var SavedAddressDto, updateCriteria: UpdateCriteria): RpcResponse[JsonNode] {.raises: [RpcException].} = + if savedAddress.isTest and chainId == Mainnet or + not savedAddress.isTest and chainId != Mainnet: + return + try: + try: + let ensResponse = backend.getName(chainId, savedAddress.address) + if updateCriteria == UpdateCriteria.OnlyIfDifferent and savedAddress.ens == ensResponse.result.getStr(): + return + savedAddress.ens = ensResponse.result.getStr() + except: + savedAddress.ens = "" + return backend.upsertSavedAddress(savedAddress) + except Exception as e: + raise newException(RpcException, e.msg) + +const fetchSavedAddressesAndResolveEnsNamesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[SavedAddressesTaskArg](argEncoded) + var response = %* { + "response": [], + "error": "", + } + try: + if not isValidChainId(arg.chainId): + raise newException(CatchableError, "invalid chainId: " & $arg.chainId) + let rpcResponse = backend.getSavedAddresses() + if not rpcResponse.error.isNil: + raise newException(CatchableError, rpcResponse.error.message) + for saJson in rpcResponse.result.getElems(): + if saJson.kind != JObject or not saJson.hasKey("address"): + continue + var savedAddress = saJson.toSavedAddressDto() + try: + discard checkForEnsNameAndUpdate(arg.chainId, savedAddress, UpdateCriteria.OnlyIfDifferent) + except: + discard + response["response"].add(savedAddress.toJsonNode()) + except Exception as e: + response["error"] = %* e.msg + arg.finish(response) + const upsertSavedAddressTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[SavedAddressTaskArg](argEncoded) var response = %* { @@ -22,14 +73,16 @@ const upsertSavedAddressTask: Task = proc(argEncoded: string) {.gcsafe, nimcall. "error": "", } try: - let rpcResponse = backend.upsertSavedAddress(SavedAddressDto( + if not isValidChainId(arg.chainId): + raise newException(CatchableError, "invalid chainId: " & $arg.chainId) + var savedAddress = SavedAddressDto( name: arg.name, address: arg.address, colorId: arg.colorId, chainShortNames: arg.chainShortNames, ens: arg.ens, isTest: arg.isTestAddress) - ) + let rpcResponse = checkForEnsNameAndUpdate(arg.chainId, savedAddress, UpdateCriteria.AlwaysUpdate) if not rpcResponse.error.isNil: raise newException(CatchableError, rpcResponse.error.message) response["response"] = %* "ok" @@ -42,11 +95,10 @@ const deleteSavedAddressTask: Task = proc(argEncoded: string) {.gcsafe, nimcall. var response = %* { "response": "", "address": %* arg.address, - "ens": %* arg.ens, "error": "", } try: - let rpcResponse = backend.deleteSavedAddress(arg.address, arg.ens, arg.isTestAddress) + let rpcResponse = backend.deleteSavedAddress(arg.address, arg.isTestAddress) if not rpcResponse.error.isNil: raise newException(CatchableError, rpcResponse.error.message) response["response"] = %* "ok" diff --git a/src/app_service/service/saved_address/dto.nim b/src/app_service/service/saved_address/dto.nim index 03bb706701..bca0e382dc 100644 --- a/src/app_service/service/saved_address/dto.nim +++ b/src/app_service/service/saved_address/dto.nim @@ -21,4 +21,15 @@ proc toSavedAddressDto*(jsonObj: JsonNode): SavedAddressDto = result.colorId = result.colorId.toUpper() # to match `preDefinedWalletAccountColors` on the qml side discard jsonObj.getProp("chainShortNames", result.chainShortNames) discard jsonObj.getProp("isTest", result.isTest) - discard jsonObj.getProp("createdAt", result.createdAt) \ No newline at end of file + discard jsonObj.getProp("createdAt", result.createdAt) + +proc toJsonNode*(self: SavedAddressDto): JsonNode = + result = %* { + "name": self.name, + "address": self.address, + "ens": self.ens, + "colorId": self.colorId, + "chainShortNames": self.chainShortNames, + "isTest": self.isTest, + "createdAt": self.createdAt, + } \ No newline at end of file diff --git a/src/app_service/service/saved_address/service.nim b/src/app_service/service/saved_address/service.nim index f3872ae637..7cb6073f54 100644 --- a/src/app_service/service/saved_address/service.nim +++ b/src/app_service/service/saved_address/service.nim @@ -26,7 +26,6 @@ type SavedAddressArgs* = ref object of Args name*: string address*: string - ens*: string errorMsg*: string QtObject: @@ -49,57 +48,74 @@ QtObject: result.networkService = networkService result.settingsService = settingsService - proc fetchAddresses(self: Service) = - try: - let response = backend.getSavedAddresses() - self.savedAddresses = map( - response.result.getElems(), - proc(x: JsonNode): SavedAddressDto = toSavedAddressDto(x) - ) - let chainId = self.networkService.getAppNetwork().chainId - for savedAddress in self.savedAddresses: - if savedAddress.ens != "": - try: - let nameResponse = backend.getName(chainId, savedAddress.address) - savedAddress.ens = nameResponse.result.getStr - except: - continue - - except Exception as e: - error "error: ", procName="fetchAddress", errName = e.name, errDesription = e.msg - - proc updateAddresses(self: Service) = - self.fetchAddresses() - self.events.emit(SIGNAL_SAVED_ADDRESSES_UPDATED, Args()) + ## Forward declaration + proc fetchSavedAddressesAndResolveEnsNames(self: Service) + proc updateAddresses(self: Service, signal: string, arg: Args) proc init*(self: Service) = - # Subscribe to sync events and check for changes self.events.on(SignalType.Message.event) do(e:Args): var data = MessageSignal(e) if(len(data.savedAddresses) > 0): - self.updateAddresses() + self.updateAddresses(SIGNAL_SAVED_ADDRESSES_UPDATED, Args()) - self.fetchAddresses() + self.fetchSavedAddressesAndResolveEnsNames() + + proc areTestNetworksEnabled*(self: Service): bool = + return self.settingsService.areTestNetworksEnabled() + + proc getAddresses(self: Service): seq[SavedAddressDto] = + try: + let response = backend.getSavedAddresses() + return map(response.result.getElems(), proc(x: JsonNode): SavedAddressDto = toSavedAddressDto(x)) + except Exception as e: + error "error: ", procName="fetchAddress", errName = e.name, errDesription = e.msg proc getSavedAddresses*(self: Service): seq[SavedAddressDto] = return self.savedAddresses + proc updateAddresses(self: Service, signal: string, arg: Args) = + self.savedAddresses = self.getAddresses() + self.events.emit(signal, arg) + + proc fetchSavedAddressesAndResolveEnsNames(self: Service) = + let arg = SavedAddressTaskArg( + chainId: self.networkService.getAppNetwork().chainId, + tptr: cast[ByteAddress](fetchSavedAddressesAndResolveEnsNamesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onSavedAddressesFetched", + ) + self.threadpool.start(arg) + + proc onSavedAddressesFetched(self: Service, rpcResponse: string) {.slot.} = + try: + let rpcResponseObj = rpcResponse.parseJson + if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "": + raise newException(CatchableError, rpcResponseObj{"error"}.getStr) + if rpcResponseObj{"response"}.kind != JArray: + raise newException(CatchableError, "invalid response") + + self.savedAddresses = map(rpcResponseObj{"response"}.getElems(), proc(x: JsonNode): SavedAddressDto = toSavedAddressDto(x)) + except Exception as e: + error "onSavedAddressesFetched", msg = e.msg + self.events.emit(SIGNAL_SAVED_ADDRESSES_UPDATED, Args()) + proc createOrUpdateSavedAddress*(self: Service, name: string, address: string, ens: string, colorId: string, chainShortNames: string) = let arg = SavedAddressTaskArg( + chainId: self.networkService.getAppNetwork().chainId, name: name, address: address, ens: ens, colorId: colorId, chainShortNames: chainShortNames, - isTestAddress: self.settingsService.areTestNetworksEnabled(), + isTestAddress: self.areTestNetworksEnabled(), tptr: cast[ByteAddress](upsertSavedAddressTask), vptr: cast[ByteAddress](self.vptr), slot: "onSavedAddressCreatedOrUpdated", ) self.threadpool.start(arg) - proc onSavedAddressCreatedOrUpdated*(self: Service, rpcResponse: string) {.slot.} = + proc onSavedAddressCreatedOrUpdated(self: Service, rpcResponse: string) {.slot.} = var arg = SavedAddressArgs() try: let rpcResponseObj = rpcResponse.parseJson @@ -110,25 +126,22 @@ QtObject: arg.name = rpcResponseObj{"name"}.getStr arg.address = rpcResponseObj{"address"}.getStr - arg.ens = rpcResponseObj{"ens"}.getStr except Exception as e: error "onSavedAddressCreatedOrUpdated", msg = e.msg arg.errorMsg = e.msg - self.fetchAddresses() - self.events.emit(SIGNAL_SAVED_ADDRESS_UPDATED, arg) + self.updateAddresses(SIGNAL_SAVED_ADDRESS_UPDATED, arg) - proc deleteSavedAddress*(self: Service, address: string, ens: string) = + proc deleteSavedAddress*(self: Service, address: string) = let arg = SavedAddressTaskArg( address: address, - ens: ens, - isTestAddress: self.settingsService.areTestNetworksEnabled(), + isTestAddress: self.areTestNetworksEnabled(), tptr: cast[ByteAddress](deleteSavedAddressTask), vptr: cast[ByteAddress](self.vptr), slot: "onDeleteSavedAddress", ) self.threadpool.start(arg) - proc onDeleteSavedAddress*(self: Service, rpcResponse: string) {.slot.} = + proc onDeleteSavedAddress(self: Service, rpcResponse: string) {.slot.} = var arg = SavedAddressArgs() try: let rpcResponseObj = rpcResponse.parseJson @@ -138,9 +151,7 @@ QtObject: raise newException(CatchableError, "invalid response") arg.address = rpcResponseObj{"address"}.getStr - arg.ens = rpcResponseObj{"ens"}.getStr except Exception as e: error "onDeleteSavedAddress", msg = e.msg arg.errorMsg = e.msg - self.fetchAddresses() - self.events.emit(SIGNAL_SAVED_ADDRESS_DELETED, arg) \ No newline at end of file + self.updateAddresses(SIGNAL_SAVED_ADDRESS_DELETED, arg) \ No newline at end of file diff --git a/src/backend/backend.nim b/src/backend/backend.nim index 458d4c7123..1af8e46ecf 100644 --- a/src/backend/backend.nim +++ b/src/backend/backend.nim @@ -91,10 +91,9 @@ rpc(upsertSavedAddress, "wakuext"): rpc(deleteSavedAddress, "wakuext"): address: string - ens: string isTest: bool -rpc(getSavedAddresses, "wallet"): +rpc(getSavedAddresses, "wakuext"): discard rpc(checkConnected, "wallet"): diff --git a/test/ui-test/src/drivers/elements/base_element.py b/test/ui-test/src/drivers/elements/base_element.py index 3e74717d0c..0b2670705f 100644 --- a/test/ui-test/src/drivers/elements/base_element.py +++ b/test/ui-test/src/drivers/elements/base_element.py @@ -108,3 +108,7 @@ class BaseElement: def wait_until_hidden(self, timeout_msec: int = configs.squish.UI_LOAD_TIMEOUT_MSEC): assert squish.waitFor(lambda: not self.is_visible, timeout_msec), f'Object {self} is not hidden' + + def wait_until_enabled(self, timeout_msec: int = configs.squish.UI_LOAD_TIMEOUT_MSEC): + assert squish.waitFor(lambda: self.is_enabled, timeout_msec), f'Object {self} is not enabled' + return self diff --git a/test/ui-test/src/screens/components/saved_address_popup.py b/test/ui-test/src/screens/components/saved_address_popup.py index 8ac164842e..e9d762d7ff 100644 --- a/test/ui-test/src/screens/components/saved_address_popup.py +++ b/test/ui-test/src/screens/components/saved_address_popup.py @@ -62,7 +62,7 @@ class AddSavedAddressPopup(SavedAddressPopup): self.verify_ethereum_mainnet_network_tag_present() self.verify_otimism_mainnet_network_tag_present() self.verify_arbitrum_mainnet_network_tag_present(), - self._save_add_address_button.click() + self._save_add_address_button.wait_until_enabled().click() self.wait_until_hidden() diff --git a/ui/app/AppLayouts/Wallet/controls/SavedAddressesDelegate.qml b/ui/app/AppLayouts/Wallet/controls/SavedAddressesDelegate.qml index 573a8b528c..f463ea14d3 100644 --- a/ui/app/AppLayouts/Wallet/controls/SavedAddressesDelegate.qml +++ b/ui/app/AppLayouts/Wallet/controls/SavedAddressesDelegate.qml @@ -87,7 +87,7 @@ StatusListItem { radius: 8 icon.name: "more" onClicked: { - menu.openMenu(this, x - menu.width - statusListItemComponentsSlot.spacing, y + height + Style.current.halfPadding, + menu.openMenu(this, x + width - menu.width - statusListItemComponentsSlot.spacing, y + height + Style.current.halfPadding, { name: root.name, address: root.address, diff --git a/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml b/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml index a766387437..47d629fa75 100644 --- a/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml +++ b/ui/app/AppLayouts/Wallet/popups/AddEditSavedAddressPopup.qml @@ -98,6 +98,13 @@ StatusDialog { readonly property var chainPrefixRegexPattern: /[^:]+\:?|:/g readonly property bool addressInputIsENS: !!d.ens + property bool addressAlreadyAdded: false + function checkIfAddressIsAlreadyAddded(address) { + let details = RootStore.getSavedAddress(address) + d.addressAlreadyAdded = !!details.address + return !d.addressAlreadyAdded + } + /// Ensures that the \c root.address and \c root.chainShortNames are not reset when the initial text is set property bool initialized: false @@ -121,6 +128,13 @@ StatusDialog { RootStore.createOrUpdateSavedAddress(d.name, d.address, d.ens, d.colorId, d.chainShortNames) root.close() } + + property bool resolvingEnsName: false + readonly property string uuid: Utils.uuid() + readonly property var validateEnsAsync: Backpressure.debounce(root, 500, function (value) { + var name = value.startsWith("@") ? value.substring(1) : value + mainModule.resolveENS(name, d.uuid) + }); } Column { @@ -182,13 +196,61 @@ StatusDialog { validators: [ StatusMinLengthValidator { minLength: 1 - errorMessage: qsTr("Address must not be blank") + errorMessage: qsTr("Please enter an ethereum address") }, StatusValidator { - errorMessage: addressInput.plainText ? qsTr("Please enter a valid address or ENS name.") : "" - validate: function (t) { - return t !== Constants.zeroAddress && (Utils.isValidAddressWithChainPrefix(t) || Utils.isValidEns(t)) - ? true : { actual: t } + errorMessage: d.addressAlreadyAdded? qsTr("This address is already saved") : qsTr("Ethereum address invalid") + validate: function (value) { + if (value !== Constants.zeroAddress) { + if (Utils.isValidEns(value)) { + return true + } + if (Utils.isValidAddressWithChainPrefix(value)) { + if (d.editMode) { + return true + } + const prefixAndAddress = Utils.splitToChainPrefixAndAddress(value) + return d.checkIfAddressIsAlreadyAddded(prefixAndAddress.address) + } + } + + return false + } + } + ] + asyncValidators: [ + StatusAsyncValidator { + id: resolvingEnsName + name: "resolving-ens-name" + errorMessage: d.addressAlreadyAdded? qsTr("This address is already saved") : qsTr("Ethereum address invalid") + asyncOperation: (value) => { + if (!Utils.isValidEns(value)) { + resolvingEnsName.asyncComplete("not-ens") + return + } + d.resolvingEnsName = true + d.validateEnsAsync(value) + } + validate: (value) => { + if (d.editMode || value === "not-ens") { + return true + } + if (!!value) { + return d.checkIfAddressIsAlreadyAddded(value) + } + return false + } + + Connections { + target: mainModule + function onResolvedENS(resolvedPubKey: string, resolvedAddress: string, uuid: string) { + if (uuid !== d.uuid) { + return + } + d.resolvingEnsName = false + d.address = resolvedAddress + resolvingEnsName.asyncComplete(resolvedAddress) + } } } ] @@ -207,6 +269,7 @@ StatusDialog { if (skipTextUpdate || !d.initialized) return + d.addressAlreadyAdded = false plainText = input.edit.getText(0, text.length) if (input.edit.previousText != plainText) { @@ -228,11 +291,13 @@ StatusDialog { // Update root values if (Utils.isLikelyEnsName(plainText)) { + d.resolvingEnsName = true d.ens = plainText d.address = Constants.zeroAddress d.chainShortNames = "" } else { + d.resolvingEnsName = false d.ens = "" d.address = prefixAndAddress.address d.chainShortNames = prefixAndAddress.prefix @@ -409,7 +474,8 @@ StatusDialog { rightButtons: ObjectModel { StatusButton { text: d.editMode? qsTr("Save") : qsTr("Add address") - enabled: d.valid && d.dirty + enabled: d.valid && d.dirty && !d.resolvingEnsName + loading: d.resolvingEnsName onClicked: { d.submit() } diff --git a/ui/app/AppLayouts/Wallet/popups/RemoveSavedAddressPopup.qml b/ui/app/AppLayouts/Wallet/popups/RemoveSavedAddressPopup.qml index 276beae4d2..e2dc8eac0b 100644 --- a/ui/app/AppLayouts/Wallet/popups/RemoveSavedAddressPopup.qml +++ b/ui/app/AppLayouts/Wallet/popups/RemoveSavedAddressPopup.qml @@ -24,7 +24,7 @@ StatusDialog { property string colorId property string chainShortNames - signal removeSavedAddress(string address, string ens) + signal removeSavedAddress(string address) width: 521 focus: visible @@ -36,7 +36,7 @@ StatusDialog { readonly property real lineHeight: 1.2 function confirm() { - root.removeSavedAddress(root.address, root.ens) + root.removeSavedAddress(root.address) } } diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index 98546e3687..c97ab80473 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -18,7 +18,6 @@ QtObject { readonly property bool showAllAccounts: !root.showSavedAddresses && !root.selectedAddress property var lastCreatedSavedAddress - property var lastDeletedSavedAddress property bool addingSavedAddress: false property bool deletingSavedAddress: false @@ -354,9 +353,9 @@ QtObject { walletSectionSavedAddresses.createOrUpdateSavedAddress(name, address, ens, colorId, chainShortNames) } - function deleteSavedAddress(address, ens) { + function deleteSavedAddress(address) { root.deletingSavedAddress = true - walletSectionSavedAddresses.deleteSavedAddress(address, ens) + walletSectionSavedAddresses.deleteSavedAddress(address) } function savedAddressNameExists(name) { diff --git a/ui/app/AppLayouts/Wallet/views/SavedAddresses.qml b/ui/app/AppLayouts/Wallet/views/SavedAddresses.qml index 1bed2e1fab..c373d27cd7 100644 --- a/ui/app/AppLayouts/Wallet/views/SavedAddresses.qml +++ b/ui/app/AppLayouts/Wallet/views/SavedAddresses.qml @@ -99,8 +99,7 @@ ColumnLayout { State { name: "highlighted" when: RootStore.lastCreatedSavedAddress ? (!RootStore.lastCreatedSavedAddress.error && - RootStore.lastCreatedSavedAddress.address.toLowerCase() === address.toLowerCase() && - RootStore.lastCreatedSavedAddress.ens === ens) : false + RootStore.lastCreatedSavedAddress.address.toLowerCase() === address.toLowerCase()) : false PropertyChanges { target: savedAddressDelegate; color: Theme.palette.baseColor2 } StateChangeScript { script: Qt.callLater(d.reset) diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 2af9128233..6ef9dd404b 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -1715,9 +1715,9 @@ Item { Connections { target: WalletStore.RootStore.walletSectionSavedAddressesInst - function onSavedAddressAddedOrUpdated(added: bool, name: string, address: string, ens: string, errorMsg: string) { + function onSavedAddressAddedOrUpdated(added: bool, name: string, address: string, errorMsg: string) { WalletStore.RootStore.addingSavedAddress = false - WalletStore.RootStore.lastCreatedSavedAddress = { address: address, ens: ens, error: errorMsg } + WalletStore.RootStore.lastCreatedSavedAddress = { address: address, error: errorMsg } if (!!errorMsg) { let mode = qsTr("adding") @@ -1783,7 +1783,7 @@ Item { } onRemoveSavedAddress: { - WalletStore.RootStore.deleteSavedAddress(address, ens) + WalletStore.RootStore.deleteSavedAddress(address) close() } } @@ -1791,9 +1791,8 @@ Item { Connections { target: WalletStore.RootStore.walletSectionSavedAddressesInst - function onSavedAddressDeleted(name: string, address: string, ens: string, errorMsg: string) { + function onSavedAddressDeleted(name: string, address: string, errorMsg: string) { WalletStore.RootStore.deletingSavedAddress = false - WalletStore.RootStore.lastDeletedSavedAddress = { address: address, ens: ens, error: errorMsg } if (!!errorMsg) {