import React, { Fragment, PureComponent } from 'react'; import web3 from 'web3'; import EmbarkJS from 'Embark/EmbarkJS'; import { connect } from 'react-redux'; import { actions as accountActions, getDefaultAccount } from '../../reducers/accounts'; import { checkAndDispatchStatusContactCode } from '../../actions/accounts'; import { hash } from 'eth-ens-namehash'; import { isNil } from 'lodash'; import Hidden from '@material-ui/core/Hidden'; import Typography from '@material-ui/core/Typography'; import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar'; import ENSRegistry from 'Embark/contracts/ENSRegistry'; import { Button, Field, TextInput, MobileSearch, MobileButton, Card, Info, Text } from '../../ui/components' import { IconCheck } from '../../ui/icons' import { keyFromXY } from '../../utils/ecdsa'; import EditOptions from './EditOptions'; import ReleaseDomainAlert from './ReleaseDomain'; import theme from '../../ui/theme' import { withFormik } from 'formik'; import PublicResolver from 'Embark/contracts/PublicResolver'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import RegisterSubDomain from '../ens/registerSubDomain'; import StatusLogo from '../../ui/icons/components/StatusLogo' import EnsLogo from '../../ui/icons/logos/ens.png'; import { formatPrice } from '../ens/utils'; import CheckCircle from '../../ui/icons/components/baseline_check_circle_outline.png'; import Warning from '../../ui/components/Warning'; const { getPrice, getExpirationTime, getCreationTime, release } = UsernameRegistrar.methods; import NotInterested from '@material-ui/icons/NotInterested'; import Face from '@material-ui/icons/Face'; import Copy from './copy'; import IDNANormalizer from 'idna-normalize'; import { nullAddress, getResolver } from './utils/domain'; import { YOUR_CONTACT_CODE } from './constants'; import DisplayBox from './DisplayBox'; import styled from "styled-components"; import { Route } from "react-router-dom"; const normalizer = new IDNANormalizer(); const invalidSuffix = '0000000000000000000000000000000000000000' const validAddress = address => address !== nullAddress; const validStatusAddress = address => !address.includes(invalidSuffix); const formatName = domainName => domainName.includes('.') ? normalizer.normalize(domainName) : normalizer.normalize(`${domainName}.stateofus.eth`); const getDomain = fullDomain => formatName(fullDomain).split('.').slice(1).join('.'); const hashedDomain = domainName => hash(getDomain(domainName)); const registryIsOwner = address => address === UsernameRegistrar._address; const { soliditySha3, fromWei } = web3.utils; const cardStyle = { width: '100%', padding: '30px', height: '425px' } const addressStyle = { fontSize: '18px', fontWeight: 400, cursor: 'copy', wordWrap: 'break-word', } const backButton = { fontSize: '40px', color: theme.accent, cursor: 'pointer' } const validTimestamp = timestamp => Number(timestamp) > 99999999; const generatePrettyDate = timestamp => new Date(timestamp * 1000).toDateString(); const pastReleaseDate = timestamp => new Date > new Date(timestamp * 1000); const MobileAddressDisplay = ({ domainName, address, statusAccount, expirationTime, creationTime, defaultAccount, isOwner, edit, onSubmit, handleChange, values, handleSubmit }) => ( {isOwner ? : } {formatName(domainName)}
{isOwner ? edit ? 'Edit Contact Code' : 'You own this ENS name' : 'unavailable'}
Registered {validTimestamp(creationTime) && generatePrettyDate(creationTime)} {edit ? 'The contact code connects the domain with a unique Status account' : validAddress(address) ? 'to the addresses below' : 'Click \'Edit\' to add a valid address and contact code'} {edit && } {!edit && } {!edit && validStatusAddress(statusAccount) && }
) class RenderAddresses extends PureComponent { state = { copied: false, editMenu: false, editAction: false } render() { const { domainName, address, statusAccount, expirationTime, defaultAccount, ownerAddress, setStatus, registryOwnsDomain } = this.props; const { copied, editMenu, editAction, submitted } = this.state const markCopied = (v) => { this.setState({ copied: v }) } const isCopied = address => address === copied; const renderCopied = address => isCopied(address) && Copied!; const onClose = value => { this.setState({ editAction: value, editMenu: false }) } const onClickEdit = () => { validAddress(address) ? this.setState({ editMenu: true }) : this.setState({ editAction: 'edit' }) } const isOwner = defaultAccount === ownerAddress; const canBeReleased = validTimestamp(expirationTime) && pastReleaseDate(expirationTime); const closeReleaseAlert = value => { if (!isNil(value)) { this.setState({ submitted: true }); release( soliditySha3(domainName) ) .send() } else { this.setState({ editAction: null }) } } return (
{formatName(domainName)}{expirationTime && (Expires {generatePrettyDate(expirationTime)})} Resolves To: {address && Ethereum Address {renderCopied(address)}}
{address}
{validStatusAddress(statusAccount) && Status Address {renderCopied(statusAccount)}} {validStatusAddress(statusAccount) &&
{statusAccount}
}
{submitted ? : { this.setState({ submitted: true}) }}/>} {isOwner && !editAction && }
) } } const InfoHeading = styled.h2` line-height: 26px; font-size: 22px; text-align: center; letter-spacing: -0.275px; margin: 0 0 12px 0; color: #FFFFFF; `; const RegisterInfoCard = ({ formattedDomain, domainPrice, registryOwnsDomain }) => ( {formattedDomain.toLowerCase()} can be registered for {!!domainPrice && formatPrice(fromWei(domainPrice))} SNT
{formattedDomain.toLowerCase()}
available
{!!domainPrice && formatPrice(fromWei(domainPrice))} SNT to register {registryOwnsDomain ? Add your contact code to use
your name in Status chat.
: This domain is not owned by the registry'}
); const TransactionComplete = ({ type, setStatus }) => (
{Copy[type]['title']['sub']}
{Copy[type]['title']['body']}
{Copy[type]['subheading']} { setStatus(null) } } />
); class Register extends PureComponent { state = { domainPrice: null }; componentDidMount() { const { domainName } = this.props; getPrice() .call() .then((res) => { this.setState({ domainPrice: res })}); } onRegistered = (address, statusAccount) => { const { domainPrice } = this.state; const { subtractFromBalance } = this.props; subtractFromBalance(domainPrice); this.setState({ registered: { address, statusAccount } }); } render() { const { domainName, setStatus, style, registryOwnsDomain, ownerAddress, defaultAccount } = this.props; const { domainPrice, registered, submitted } = this.state; const formattedDomain = formatName(domainName); const formattedDomainArray = formattedDomain.split('.'); const isOwner = defaultAccount === ownerAddress; return (
{!registered && !submitted ? {registryOwnsDomain && { this.setState({ submitted: true }) }} registeredCallbackFn={this.onRegistered} />} : submitted && !registered ? : }
) } } const mapDispatchToProps = dispatch => ({ subtractFromBalance(amount) { dispatch(accountActions.subtractfromSntTokenBalance(amount)); }, }); const mapStateToProps = state => ({ defaultAccount: getDefaultAccount(state) }); const ConnectedRegister = connect(mapStateToProps, mapDispatchToProps)(Register); const DisplayAddress = connect(mapStateToProps)((props) => ( {validAddress(props.address) || props.defaultAccount === props.ownerAddress ? : {props.domainName} } )); class LookupForm extends React.Component { render() { const { handleSubmit, values, handleChange, isWarningDisplayed } = this.props; return (
{isWarningDisplayed && Names are made with
letters and numbers only
}
); } } const isValidDomainName = val => /^([a-z0-9]+)$/.test(val.toLowerCase()); class SearchResultsPage extends React.Component { async loadDomainInformation({setStatus, domainName}) { if (isValidDomainName(domainName)) { const { methods: { owner, resolver } } = ENSRegistry; const lookupHash = hash(formatName(domainName)); const subdomainHash = soliditySha3(domainName); const resolverContract = await getResolver(lookupHash); const { addr, pubkey } = resolverContract.methods; const address = addr(lookupHash).call(); const keys = pubkey(lookupHash).call(); const ownerAddress = owner(lookupHash).call(); const suffixOwner = owner(hash(getDomain(domainName))).call(); const expirationTime = getExpirationTime(subdomainHash).call(); const creationTime = getCreationTime(subdomainHash).call(); Promise.all([address, keys, ownerAddress, expirationTime, creationTime,suffixOwner]) .then(([ address, keys, ownerAddress, expirationTime, creationTime, suffixOwner ]) => { const statusAccount = keyFromXY(keys[0], keys[1]); const registryOwnsDomain = registryIsOwner(suffixOwner); setStatus({ address, statusAccount, expirationTime, creationTime, ownerAddress, registryOwnsDomain, domainName }); }); } else { setStatus({isInvalidDomain: true }); } } componentDidMount() { const { setValues } = this.props; const domainName = this.props.match.params.domainName; setValues({domainName: domainName}); this.loadDomainInformation({ setStatus: this.props.setStatus, domainName: domainName }); } componentWillReceiveProps(nextProps) { const { setValues, match } = this.props; const oldDomainName = match.params.domainName; const newDomainName = nextProps.match.params.domainName; if (oldDomainName !== newDomainName) { setValues({domainName: newDomainName}); this.loadDomainInformation({ setStatus: this.props.setStatus, domainName: newDomainName }); } } render() { const { values, handleChange, handleSubmit, status, setStatus, defaultAccount } = this.props; return (
{!status || !status.address ? : validAddress(status.address) || defaultAccount === status.ownerAddress ? :
}
); } } class NameLookupContainer extends React.Component { render() { const {match, status} = this.props; return (
Ens Logo {match &&
}/> }/>
}
) } } const NameLookupContainerWrapped = withFormik({ mapPropsToValues: () => ({ domainName: '' }), async handleSubmit(values, { props }) { const { domainName } = values; props.history.push(`${props.match.url}/${domainName}`); } })(NameLookupContainer); export default connect(mapStateToProps)(NameLookupContainerWrapped);