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 { ellipsify } from '../../send.utils'; import { isValidDomainName, isValidAddress, isValidAddressHead, } from '../../../../helpers/utils/util'; import { MAINNET_NETWORK_ID } from '../../../../../../shared/constants/network'; // 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) { const provider = global.ethereumProvider; this.ens = new ENS({ provider, network }); if (!newProvidedValue) { newValue = input; } } 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 (isValidAddress(text)) { 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 && !isValidAddress(input) && !isValidAddressHead(input) ) { updateEnsResolution(''); updateEnsResolutionError( networkHasEnsSupport ? '' : 'Network does not support ENS', ); return null; } if (isValidDomainName(input)) { this.lookupEnsName(input); } else if (onValidAddressTyped && isValidAddress(input)) { 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 (
); } renderSelected() { const { t } = this.context; const { className, selectedAddress, selectedName, contact = {}, } = this.props; const name = contact.name || selectedName; return (
{name || ellipsify(selectedAddress)}
{name && (
{selectedAddress}
)}
); } ensIcon(recipient) { const { hoverText } = this.state; return ( {this.ensIconContents(recipient)} ); } ensIconContents() { const { loadingEns, ensFailure, ensResolution, toError } = this.state; if (toError) { return null; } if (loadingEns) { return ( ); } if (ensFailure) { return ; } if (ensResolution && ensResolution !== ZERO_ADDRESS) { return ( { event.preventDefault(); event.stopPropagation(); copyToClipboard(ensResolution); }} /> ); } return null; } } function getNetworkEnsSupport(network) { return Boolean(networkMap[network]); }