feat(TransactionModal): introduce async validation for ENS names
This commit extends the AddressInput to perform ENS lookups when valid ENS values are entered. The lookup happens asynchronously, so we show a loading indicator as the request is happening. Closes #790
This commit is contained in:
parent
d64446f868
commit
729a2781f0
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 = /(?:(?:(?<thld>[\w\-]*)(?:\.))?(?<sld>[\w\-]*))\.(?<tld>[\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
|
||||
|
|
|
@ -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 = /(?:(?:(?<thld>[\w\-]*)(?:\.))?(?<sld>[\w\-]*))\.(?<tld>[\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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<children.length; i++) {
|
||||
|
@ -12,9 +13,13 @@ Rectangle {
|
|||
if (component.hasOwnProperty("validate") && typeof component.validate === "function") {
|
||||
isValid = component.validate()
|
||||
}
|
||||
if (component.hasOwnProperty("isPending")) {
|
||||
isPending = component.isPending
|
||||
}
|
||||
}
|
||||
root.isValid = isValid
|
||||
return isValid
|
||||
root.isPending = isPending
|
||||
return { isValid, isPending }
|
||||
}
|
||||
color: Style.current.background
|
||||
function reset() {
|
||||
|
@ -41,19 +46,28 @@ Rectangle {
|
|||
for (let i=0; i<children.length; i++) {
|
||||
const component = children[i]
|
||||
if (component.hasOwnProperty("isValid")) {
|
||||
component.isValidChanged.connect(updateGroupValidity)
|
||||
component.isValidChanged.connect(updateGroupValidityAndPendingStatus)
|
||||
root.isValid = root.isValid && component.isValid // set the initial state
|
||||
}
|
||||
if (component.hasOwnProperty("isPending")) {
|
||||
component.isPendingChanged.connect(updateGroupValidityAndPendingStatus)
|
||||
root.isPending = component.isPending
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateGroupValidity() {
|
||||
function updateGroupValidityAndPendingStatus() {
|
||||
let isValid = true
|
||||
let isPending = false
|
||||
for (let i=0; i<children.length; i++) {
|
||||
const component = children[i]
|
||||
if (component.hasOwnProperty("isValid")) {
|
||||
isValid = isValid && component.isValid
|
||||
}
|
||||
if (component.hasOwnProperty("isPending")) {
|
||||
isPending = component.isPending
|
||||
}
|
||||
}
|
||||
root.isValid = isValid
|
||||
root.isPending = isPending
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue