diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2579da87d..46fbdc1a7 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -265,6 +265,9 @@ "encryptNewDen": { "message": "Encrypt your new DEN" }, + "ensNameNotFound": { + "message": "ENS name not found" + }, "enterPassword": { "message": "Enter password" }, diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index aff4b6ef6..292dcdde6 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -12,6 +12,7 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const connect = require('react-redux').connect const ToAutoComplete = require('./send/to-autocomplete') const log = require('loglevel') +const { isValidENSAddress } = require('../util') EnsInput.contextTypes = { t: PropTypes.func, @@ -25,31 +26,34 @@ function EnsInput () { Component.call(this) } -EnsInput.prototype.render = function () { - const props = this.props - const opts = extend(props, { - list: 'addresses', - onChange: (recipient) => { - const network = this.props.network - const networkHasEnsSupport = getNetworkEnsSupport(network) +EnsInput.prototype.onChange = function (recipient) { + const network = this.props.network + const networkHasEnsSupport = getNetworkEnsSupport(network) - props.onChange(recipient) + this.props.onChange({ toAddress: recipient }) - if (!networkHasEnsSupport) return + if (!networkHasEnsSupport) return - if (recipient.match(ensRE) === null) { - return this.setState({ - loadingEns: false, - ensResolution: null, - ensFailure: null, - }) - } + if (recipient.match(ensRE) === null) { + return this.setState({ + loadingEns: false, + ensResolution: null, + ensFailure: null, + toError: null, + }) + } - this.setState({ - loadingEns: true, - }) - this.checkName(recipient) - }, + this.setState({ + loadingEns: true, + }) + this.checkName(recipient) +} + +EnsInput.prototype.render = function () { + const props = this.props + const opts = extend(props, { + list: 'addresses', + onChange: this.onChange.bind(this), }) return h('div', { style: { width: '100%', position: 'relative' }, @@ -85,17 +89,27 @@ EnsInput.prototype.lookupEnsName = function (recipient) { nickname: recipient.trim(), hoverText: address + '\n' + this.context.t('clickCopy'), ensFailure: false, + toError: null, }) } }) .catch((reason) => { - log.error(reason) - return this.setState({ + const setStateObj = { loadingEns: false, - ensResolution: ZERO_ADDRESS, + ensResolution: recipient, ensFailure: true, - hoverText: reason.message, - }) + toError: null, + } + if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') { + setStateObj.hoverText = this.context.t('ensNameNotFound') + setStateObj.toError = 'ensNameNotFound' + setStateObj.ensFailure = false + } else { + log.error(reason) + setStateObj.hoverText = reason.message + } + + return this.setState(setStateObj) }) } @@ -105,9 +119,14 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) { // If an address is sent without a nickname, meaning not from ENS or from // the user's own accounts, a default of a one-space string is used. const nickname = state.nickname || ' ' + if (prevProps.network !== this.props.network) { + const provider = global.ethereumProvider + this.ens = new ENS({ provider, network: this.props.network }) + this.onChange(ensResolution) + } if (prevState && ensResolution && this.props.onChange && ensResolution !== prevState.ensResolution) { - this.props.onChange(ensResolution, nickname) + this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError }) } } @@ -124,7 +143,9 @@ EnsInput.prototype.ensIcon = function (recipient) { } EnsInput.prototype.ensIconContents = function (recipient) { - const { loadingEns, ensFailure, ensResolution } = this.state || { ensResolution: ZERO_ADDRESS} + const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS } + + if (toError) return if (loadingEns) { return h('img', { diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js index 0a83186a5..1c2ecdf9c 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -19,9 +19,9 @@ export default class SendToRow extends Component { updateSendToError: PropTypes.func, }; - handleToChange (to, nickname = '') { + handleToChange (to, nickname = '', toError) { const { updateSendTo, updateSendToError, updateGas } = this.props - const toErrorObject = getToErrorObject(to) + const toErrorObject = getToErrorObject(to, toError) updateSendTo(to, nickname) updateSendToError(toErrorObject) if (toErrorObject.to === null) { @@ -53,7 +53,7 @@ export default class SendToRow extends Component { inError={inError} name={'address'} network={network} - onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)} + onChange={({ toAddress, nickname, toError }) => this.handleToChange(toAddress, nickname, toError)} openDropdown={() => openToDropdown()} placeholder={this.context.t('recipientAddress')} to={to} diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js index cea51ee20..6b90a9f09 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js @@ -4,12 +4,10 @@ const { } = require('../../send.constants') const { isValidAddress } = require('../../../../util') -function getToErrorObject (to) { - let toError = null - +function getToErrorObject (to, toError = null) { if (!to) { toError = REQUIRED_ERROR - } else if (!isValidAddress(to)) { + } else if (!isValidAddress(to) && !toError) { toError = INVALID_RECIPIENT_ADDRESS_ERROR } diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js index 58fe51dcf..781371004 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js @@ -6,8 +6,8 @@ import proxyquire from 'proxyquire' const SendToRow = proxyquire('../send-to-row.component.js', { './send-to-row.utils.js': { - getToErrorObject: (to) => ({ - to: to === false ? null : `mockToErrorObject:${to}`, + getToErrorObject: (to, toError) => ({ + to: to === false ? null : `mockToErrorObject:${to}${toError}`, }), }, }).default @@ -67,11 +67,11 @@ describe('SendToRow Component', function () { it('should call updateSendToError', () => { assert.equal(propsMethodSpies.updateSendToError.callCount, 0) - instance.handleToChange('mockTo2') + instance.handleToChange('mockTo2', '', 'mockToError') assert.equal(propsMethodSpies.updateSendToError.callCount, 1) assert.deepEqual( propsMethodSpies.updateSendToError.getCall(0).args, - [{ to: 'mockToErrorObject:mockTo2' }] + [{ to: 'mockToErrorObject:mockTo2mockToError' }] ) }) @@ -138,11 +138,11 @@ describe('SendToRow Component', function () { openDropdown() assert.equal(propsMethodSpies.openToDropdown.callCount, 1) assert.equal(SendToRow.prototype.handleToChange.callCount, 0) - onChange('mockNewTo', 'mockNewNickname') + onChange({ toAddress: 'mockNewTo', nickname: 'mockNewNickname', toError: 'mockToError' }) assert.equal(SendToRow.prototype.handleToChange.callCount, 1) assert.deepEqual( SendToRow.prototype.handleToChange.getCall(0).args, - ['mockNewTo', 'mockNewNickname'] + ['mockNewTo', 'mockNewNickname', 'mockToError'] ) }) }) diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js index 615c9581b..4d2447c32 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js @@ -40,6 +40,12 @@ describe('send-to-row utils', () => { to: null, }) }) + + it('should return the passed error if to is truthy but invalid if to is truthy and valid', () => { + assert.deepEqual(getToErrorObject('invalid #$ 345878', 'someExplicitError'), { + to: 'someExplicitError', + }) + }) }) }) diff --git a/ui/app/util.js b/ui/app/util.js index 1ccd17ba7..8c85c5926 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -36,6 +36,7 @@ module.exports = { miniAddressSummary: miniAddressSummary, isAllOneCase: isAllOneCase, isValidAddress: isValidAddress, + isValidENSAddress, numericBalance: numericBalance, parseBalance: parseBalance, formatBalance: formatBalance, @@ -87,6 +88,10 @@ function isValidAddress (address) { return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) } +function isValidENSAddress (address) { + return address.match(/^.{7,}\.(eth|test)$/) +} + function isInvalidChecksumAddress (address) { var prefixed = ethUtil.addHexPrefix(address) if (address === '0x0000000000000000000000000000000000000000') return false