diff --git a/src/app/wallet/view.nim b/src/app/wallet/view.nim index a868b9339c..25791150b1 100644 --- a/src/app/wallet/view.nim +++ b/src/app/wallet/view.nim @@ -5,6 +5,7 @@ import ../../status/libstatus/wallet as status_wallet import ../../status/libstatus/tokens import ../../status/libstatus/types import ../../status/libstatus/utils as status_utils +import ../../status/ens as status_ens import views/[asset_list, account_list, account_item, token_list, transaction_list, collectibles_list] QtObject: @@ -527,3 +528,11 @@ QtObject: proc wei2Token*(self: WalletView, wei: string, decimals: int): string {.slot.} = return status_utils.wei2Token(wei, decimals) + proc resolveENS*(self: WalletView, ens: string) {.slot.} = + spawnAndSend(self, "ensResolved") do: + status_ens.owner(ens) + + proc ensWasResolved*(self: WalletView, resolvedPubKey: string) {.signal.} + + proc ensResolved(self: WalletView, pubKey: string) {.slot.} = + self.ensWasResolved(pubKey) diff --git a/ui/app/AppLayouts/Wallet/SendModal.qml b/ui/app/AppLayouts/Wallet/SendModal.qml index cde4c8ad6a..dca884f7ec 100644 --- a/ui/app/AppLayouts/Wallet/SendModal.qml +++ b/ui/app/AppLayouts/Wallet/SendModal.qml @@ -236,11 +236,10 @@ ModalPopup { id: btnNext anchors.right: parent.right label: qsTr("Next") - disabled: !stack.currentGroup.isValid + disabled: !stack.currentGroup.isValid || stack.currentGroup.isPending onClicked: { - const isValid = stack.currentGroup.validate() - - if (stack.currentGroup.validate()) { + const validity = stack.currentGroup.validate() + if (validity.isValid && !validity.isPending) { if (stack.isLastGroup) { return root.sendTransaction() } diff --git a/ui/imports/Utils.qml b/ui/imports/Utils.qml index 1ec6078e8b..e1baaa8ef9 100644 --- a/ui/imports/Utils.qml +++ b/ui/imports/Utils.qml @@ -70,6 +70,13 @@ QtObject { return /^0x[a-fA-F0-9]{40}$/.test(inputValue) } + function isValidEns(inputValue) { + const isEmail = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(inputValue) + const isDomain = /(?:(?:(?[\w\-]*)(?:\.))?(?[\w\-]*))\.(?[\w\-]*)/.test(inputValue) + return isEmail || isDomain || inputValue.startsWith("@") + } + + /** * Removes trailing zeros from a string-representation of a number. Throws * if parameter is not a string diff --git a/ui/shared/AddressInput.qml b/ui/shared/AddressInput.qml index 8cbd50e0d9..c330d34fbe 100644 --- a/ui/shared/AddressInput.qml +++ b/ui/shared/AddressInput.qml @@ -3,13 +3,16 @@ import QtQuick.Controls 2.13 import QtQuick.Layouts 1.13 import QtGraphicalEffects 1.13 import "../imports" +import "../shared" Item { id: root property string validationError: "Error" + property string ensAsyncValidationError: qsTr("ENS Username not found") property alias label: inpAddress.label property string selectedAddress property var isValid: false + property bool isPending: false height: inpAddress.height @@ -20,24 +23,37 @@ Item { isValid = false } - function isValidEns(inputValue) { - // TODO: Check if the entered value resolves to an address. Long operation. - // Issue tracked: https://github.com/status-im/nim-status-client/issues/718 - const isEmail = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(inputValue) - const isDomain = /(?:(?:(?[\w\-]*)(?:\.))?(?[\w\-]*))\.(?[\w\-]*)/.test(inputValue) - return isEmail || isDomain - } - function validate(inputValue) { if (!inputValue) inputValue = selectedAddress let isValid = - (inputValue && inputValue.startsWith("0x") && Utils.isValidAddress(inputValue)) || - isValidEns(inputValue) + (inputValue && inputValue.startsWith("0x") && Utils.isValidAddress(inputValue) || Utils.isValidEns(inputValue)) inpAddress.validationError = isValid ? "" : validationError root.isValid = isValid return isValid } + property var validateAsync: Backpressure.debounce(inpAddress, 300, function (inputValue) { + root.isPending = true + var name = inputValue.startsWith("@") ? inputValue.substring(1) : inputValue + walletModel.resolveENS(name) + }); + + + Connections { + target: walletModel + onEnsWasResolved: { + root.isPending = false + if (resolvedPubKey === ""){ + inpAddress.validationError = root.ensAsyncValidationError + root.isValid = false + } else { + root.isValid = true + root.selectedAddress = resolvedPubKey + inpAddress.validationError = "" + } + } + } + Input { id: inpAddress //% "eg. 0x1234 or ENS" @@ -64,7 +80,11 @@ Item { metrics.text = text const isValid = root.validate(inputValue) if (isValid) { + if (Utils.isValidAddress(inputValue)) { root.selectedAddress = inputValue + } else { + Qt.callLater(root.validateAsync, inputValue) + } } } TextMetrics { @@ -86,6 +106,22 @@ Item { } } } + + Loader { + sourceComponent: loadingIndicator + anchors.top: inpAddress.bottom + anchors.right: inpAddress.right + anchors.topMargin: Style.current.halfPadding + active: root.isPending + } + + Component { + id: loadingIndicator + LoadingImage { + width: 12 + height: 12 + } + } } diff --git a/ui/shared/FormGroup.qml b/ui/shared/FormGroup.qml index eb540b1cdf..a2451a81c5 100644 --- a/ui/shared/FormGroup.qml +++ b/ui/shared/FormGroup.qml @@ -5,6 +5,7 @@ import "../imports" Rectangle { id: root property var isValid: true + property var isPending: false property var validate: function() { let isValid = true for (let i=0; i