import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { debounce } from 'lodash'; import copyToClipboard from 'copy-to-clipboard/index'; import ENS from 'ethjs-ens'; import networkMap from 'ethereum-ens-network-map'; import log from 'loglevel'; import { isHexString } from 'ethereumjs-util'; import { ellipsify } from '../../send.utils'; import { isValidDomainName } from '../../../../helpers/utils/util'; import { MAINNET_NETWORK_ID } from '../../../../../shared/constants/network'; import { isBurnAddress, isValidHexAddress, } from '../../../../../shared/modules/hexstring-utils'; // Local Constants const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; const ZERO_X_ERROR_ADDRESS = '0x'; export default class EnsInput extends Component { static contextTypes = { t: PropTypes.func, }; static propTypes = { className: PropTypes.string, network: PropTypes.string, selectedAddress: PropTypes.string, selectedName: PropTypes.string, onChange: PropTypes.func, updateEnsResolution: PropTypes.func, scanQrCode: PropTypes.func, updateEnsResolutionError: PropTypes.func, onPaste: PropTypes.func, onReset: PropTypes.func, onValidAddressTyped: PropTypes.func, contact: PropTypes.object, value: PropTypes.string, internalSearch: PropTypes.bool, }; state = { input: '', toError: null, ensResolution: undefined, }; componentDidMount() { const { network, internalSearch } = this.props; const networkHasEnsSupport = getNetworkEnsSupport(network); this.setState({ ensResolution: ZERO_ADDRESS }); if (networkHasEnsSupport && !internalSearch) { const provider = global.ethereumProvider; this.ens = new ENS({ provider, network }); this.checkName = debounce(this.lookupEnsName, 200); } } componentDidUpdate(prevProps) { const { input } = this.state; const { network, value, internalSearch } = this.props; let newValue; // Set the value of our input based on QR code provided by parent const newProvidedValue = input !== value && prevProps.value !== value; if (newProvidedValue) { newValue = value; } if (prevProps.network !== network) { if (getNetworkEnsSupport(network)) { const provider = global.ethereumProvider; this.ens = new ENS({ provider, network }); this.checkName = debounce(this.lookupEnsName, 200); if (!newProvidedValue) { newValue = input; } } else { // ens is null on mount on a network that does not have ens support // this is intended to prevent accidental lookup of domains across // networks this.ens = null; this.checkName = null; } } if (newValue !== undefined) { this.onChange({ target: { value: newValue } }); } if (!internalSearch && prevProps.internalSearch) { this.resetInput(); } } resetInput = () => { const { updateEnsResolution, updateEnsResolutionError, onReset, } = this.props; this.onChange({ target: { value: '' } }); onReset(); updateEnsResolution(''); updateEnsResolutionError(''); }; lookupEnsName = (ensName) => { const { network } = this.props; const recipient = ensName.trim(); log.info(`ENS attempting to resolve name: ${recipient}`); this.ens .lookup(recipient) .then((address) => { if (address === ZERO_ADDRESS) { throw new Error(this.context.t('noAddressForName')); } if (address === ZERO_X_ERROR_ADDRESS) { throw new Error(this.context.t('ensRegistrationError')); } this.props.updateEnsResolution(address); }) .catch((reason) => { if ( isValidDomainName(recipient) && reason.message === 'ENS name not defined.' ) { this.props.updateEnsResolutionError( network === MAINNET_NETWORK_ID ? this.context.t('noAddressForName') : this.context.t('ensNotFoundOnCurrentNetwork'), ); } else { log.error(reason); this.props.updateEnsResolutionError(reason.message); } }); }; onPaste = (event) => { event.clipboardData.items[0].getAsString((text) => { if ( !isBurnAddress(text) && isValidHexAddress(text, { mixedCaseUseChecksum: true }) ) { this.props.onPaste(text); } }); }; onChange = (e) => { const { network, onChange, updateEnsResolution, updateEnsResolutionError, onValidAddressTyped, internalSearch, } = this.props; const input = e.target.value; const networkHasEnsSupport = getNetworkEnsSupport(network); this.setState({ input }, () => onChange(input)); if (internalSearch) { return null; } // Empty ENS state if input is empty // maybe scan ENS if ( !networkHasEnsSupport && !( isBurnAddress(input) === false && isValidHexAddress(input, { mixedCaseUseChecksum: true }) ) && !isHexString(input) ) { updateEnsResolution(''); updateEnsResolutionError( networkHasEnsSupport ? '' : 'Network does not support ENS', ); return null; } if (isValidDomainName(input)) { this.lookupEnsName(input); } else if ( onValidAddressTyped && !isBurnAddress(input) && isValidHexAddress(input, { mixedCaseUseChecksum: true }) ) { onValidAddressTyped(input); } else { updateEnsResolution(''); updateEnsResolutionError(''); } return null; }; render() { const { t } = this.context; const { className, selectedAddress } = this.props; const { input } = this.state; if (selectedAddress) { return this.renderSelected(); } return (