Resolve ENS name onChange, add ENS support to load safe route
This commit is contained in:
parent
6f92662e03
commit
2acd0e17bf
|
@ -1,13 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { Field } from 'react-final-form'
|
import { Field } from 'react-final-form'
|
||||||
|
import { OnChange } from 'react-final-form-listeners'
|
||||||
import TextField from '~/components/forms/TextField'
|
import TextField from '~/components/forms/TextField'
|
||||||
import {
|
import {
|
||||||
composeValidators,
|
composeValidators,
|
||||||
required,
|
required,
|
||||||
mustBeEthereumAddress,
|
mustBeEthereumAddress,
|
||||||
ifElseValidator,
|
|
||||||
ensResolverHasAddress,
|
|
||||||
} from '~/components/forms/validator'
|
} from '~/components/forms/validator'
|
||||||
import { getAddressFromENS } from '~/logic/wallets/getWeb3'
|
import { getAddressFromENS } from '~/logic/wallets/getWeb3'
|
||||||
|
|
||||||
|
@ -22,9 +21,7 @@ type Props = {
|
||||||
inputAdornment?: React.Element,
|
inputAdornment?: React.Element,
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValidEnsName = name => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
|
const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
|
||||||
|
|
||||||
const { useState, useEffect } = React
|
|
||||||
|
|
||||||
// an idea for second field was taken from here
|
// an idea for second field was taken from here
|
||||||
// https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js
|
// https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js
|
||||||
|
@ -46,7 +43,7 @@ const AddressInput = ({
|
||||||
type="text"
|
type="text"
|
||||||
validate={composeValidators(
|
validate={composeValidators(
|
||||||
required,
|
required,
|
||||||
ifElseValidator(isValidEnsName, ensResolverHasAddress, mustBeEthereumAddress),
|
mustBeEthereumAddress,
|
||||||
...validators,
|
...validators,
|
||||||
)}
|
)}
|
||||||
inputAdornment={inputAdornment}
|
inputAdornment={inputAdornment}
|
||||||
|
@ -55,7 +52,21 @@ const AddressInput = ({
|
||||||
className={className}
|
className={className}
|
||||||
testId={testId}
|
testId={testId}
|
||||||
/>
|
/>
|
||||||
<Field
|
<OnChange name={name}>
|
||||||
|
{async (value) => {
|
||||||
|
if (isValidEnsName(value)) {
|
||||||
|
try {
|
||||||
|
const resolverAddr = await getAddressFromENS(value)
|
||||||
|
fieldMutator(resolverAddr)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to resolve address for ENS name: ', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</OnChange>
|
||||||
|
{/* onBlur - didn't work because of the complex validation
|
||||||
|
(if you submit before it gets the address, breaks everything) */}
|
||||||
|
{/* <Field
|
||||||
name={name}
|
name={name}
|
||||||
subscription={{ active: true, value: true }}
|
subscription={{ active: true, value: true }}
|
||||||
render={({ meta, input }) => {
|
render={({ meta, input }) => {
|
||||||
|
@ -82,7 +93,7 @@ const AddressInput = ({
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { type FieldValidator } from 'final-form'
|
import { type FieldValidator } from 'final-form'
|
||||||
import { getWeb3, getAddressFromENS } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
|
||||||
export const simpleMemoize = (fn: Function) => {
|
export const simpleMemoize = (fn: Function) => {
|
||||||
let lastArg
|
let lastArg
|
||||||
|
@ -61,7 +61,7 @@ export const ok = () => undefined
|
||||||
export const mustBeEthereumAddress = simpleMemoize((address: Field) => {
|
export const mustBeEthereumAddress = simpleMemoize((address: Field) => {
|
||||||
const isAddress: boolean = getWeb3().utils.isAddress(address)
|
const isAddress: boolean = getWeb3().utils.isAddress(address)
|
||||||
|
|
||||||
return isAddress ? undefined : 'Address should be a valid Ethereum address or ENS domain'
|
return isAddress ? undefined : 'Address should be a valid Ethereum address or ENS name'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const minMaxLength = (minLen: string | number, maxLen: string | number) => (value: string) => (value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols`)
|
export const minMaxLength = (minLen: string | number, maxLen: string | number) => (value: string) => (value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols`)
|
||||||
|
@ -90,24 +90,4 @@ export const differentFrom = (diffValue: string) => (value: string) => {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ensResolverHasAddress = async (value: string) => {
|
|
||||||
let error
|
|
||||||
|
|
||||||
try {
|
|
||||||
await getAddressFromENS(value)
|
|
||||||
} catch {
|
|
||||||
error = 'Couldn\'t resolve the address'
|
|
||||||
}
|
|
||||||
|
|
||||||
return error
|
|
||||||
}
|
|
||||||
|
|
||||||
export const noErrorsOn = (name: string, errors: Object) => errors[name] === undefined
|
export const noErrorsOn = (name: string, errors: Object) => errors[name] === undefined
|
||||||
|
|
||||||
export const ifElseValidator = (ifFunc: Function, thenFunc: Function, elseFunc: Function) => (value: string) => {
|
|
||||||
if (ifFunc(value)) {
|
|
||||||
return thenFunc(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return elseFunc(value)
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/Proxy.json'
|
||||||
import InputAdornment from '@material-ui/core/InputAdornment'
|
import InputAdornment from '@material-ui/core/InputAdornment'
|
||||||
import CheckCircle from '@material-ui/icons/CheckCircle'
|
import CheckCircle from '@material-ui/icons/CheckCircle'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
|
import AddressInput from '~/components/forms/AddressInput'
|
||||||
import {
|
import {
|
||||||
composeValidators, required, noErrorsOn, mustBeEthereumAddress,
|
composeValidators, required, noErrorsOn, mustBeEthereumAddress,
|
||||||
} from '~/components/forms/validator'
|
} from '~/components/forms/validator'
|
||||||
|
@ -19,6 +20,7 @@ import { getSafeMasterContract } from '~/logic/contracts/safeContracts'
|
||||||
type Props = {
|
type Props = {
|
||||||
classes: Object,
|
classes: Object,
|
||||||
errors: Object,
|
errors: Object,
|
||||||
|
form: Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = () => ({
|
const styles = () => ({
|
||||||
|
@ -80,8 +82,8 @@ export const safeFieldsValidation = async (values: Object) => {
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
const Details = ({ classes, errors }: Props) => (
|
const Details = ({ classes, errors, form }: Props) => (
|
||||||
<React.Fragment>
|
<>
|
||||||
<Block margin="sm">
|
<Block margin="sm">
|
||||||
<Paragraph noMargin size="md" color="primary">
|
<Paragraph noMargin size="md" color="primary">
|
||||||
Adding an existing Safe only requires the Safe address. Optionally you can give it a name. In case your
|
Adding an existing Safe only requires the Safe address. Optionally you can give it a name. In case your
|
||||||
|
@ -99,9 +101,12 @@ const Details = ({ classes, errors }: Props) => (
|
||||||
/>
|
/>
|
||||||
</Block>
|
</Block>
|
||||||
<Block margin="lg" className={classes.root}>
|
<Block margin="lg" className={classes.root}>
|
||||||
<Field
|
<AddressInput
|
||||||
name={FIELD_LOAD_ADDRESS}
|
name={FIELD_LOAD_ADDRESS}
|
||||||
component={TextField}
|
component={TextField}
|
||||||
|
fieldMutator={(val) => {
|
||||||
|
form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
|
||||||
|
}}
|
||||||
inputAdornment={
|
inputAdornment={
|
||||||
noErrorsOn(FIELD_LOAD_ADDRESS, errors) && {
|
noErrorsOn(FIELD_LOAD_ADDRESS, errors) && {
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
|
@ -117,17 +122,17 @@ const Details = ({ classes, errors }: Props) => (
|
||||||
text="Safe Address"
|
text="Safe Address"
|
||||||
/>
|
/>
|
||||||
</Block>
|
</Block>
|
||||||
</React.Fragment>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const DetailsForm = withStyles(styles)(Details)
|
const DetailsForm = withStyles(styles)(Details)
|
||||||
|
|
||||||
const DetailsPage = () => (controls: React.Node, { errors }: Object) => (
|
const DetailsPage = () => (controls: React.Node, { errors, form }: Object) => (
|
||||||
<React.Fragment>
|
<>
|
||||||
<OpenPaper controls={controls} container={605}>
|
<OpenPaper controls={controls} container={605}>
|
||||||
<DetailsForm errors={errors} />
|
<DetailsForm errors={errors} form={form} />
|
||||||
</OpenPaper>
|
</OpenPaper>
|
||||||
</React.Fragment>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default DetailsPage
|
export default DetailsPage
|
||||||
|
|
|
@ -29,6 +29,12 @@ const back = () => {
|
||||||
history.goBack()
|
history.goBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formMutators = {
|
||||||
|
setValue: ([field, value], state, { changeValue }) => {
|
||||||
|
changeValue(state, field, () => value)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const Layout = ({
|
const Layout = ({
|
||||||
provider, onLoadSafeSubmit, network, userAddress,
|
provider, onLoadSafeSubmit, network, userAddress,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
@ -36,7 +42,7 @@ const Layout = ({
|
||||||
const initialValues = {}
|
const initialValues = {}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
{provider ? (
|
{provider ? (
|
||||||
<Block>
|
<Block>
|
||||||
<Row align="center">
|
<Row align="center">
|
||||||
|
@ -45,7 +51,13 @@ const Layout = ({
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Heading tag="h2">Load existing Safe</Heading>
|
<Heading tag="h2">Load existing Safe</Heading>
|
||||||
</Row>
|
</Row>
|
||||||
<Stepper onSubmit={onLoadSafeSubmit} steps={steps} initialValues={initialValues} testId="load-safe-form">
|
<Stepper
|
||||||
|
onSubmit={onLoadSafeSubmit}
|
||||||
|
steps={steps}
|
||||||
|
initialValues={initialValues}
|
||||||
|
mutators={formMutators}
|
||||||
|
testId="load-safe-form"
|
||||||
|
>
|
||||||
<StepperPage validate={safeFieldsValidation}>{DetailsForm}</StepperPage>
|
<StepperPage validate={safeFieldsValidation}>{DetailsForm}</StepperPage>
|
||||||
<StepperPage network={network}>{OwnerList}</StepperPage>
|
<StepperPage network={network}>{OwnerList}</StepperPage>
|
||||||
<StepperPage network={network} userAddress={userAddress}>
|
<StepperPage network={network} userAddress={userAddress}>
|
||||||
|
@ -56,7 +68,7 @@ const Layout = ({
|
||||||
) : (
|
) : (
|
||||||
<div>No account detected</div>
|
<div>No account detected</div>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ const OwnerListComponent = (props: Props) => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
<Block className={classes.title}>
|
<Block className={classes.title}>
|
||||||
<Paragraph noMargin size="md" color="primary">
|
<Paragraph noMargin size="md" color="primary">
|
||||||
{`This Safe has ${owners.length} owners. Optional: Provide a name for each owner.`}
|
{`This Safe has ${owners.length} owners. Optional: Provide a name for each owner.`}
|
||||||
|
@ -159,18 +159,18 @@ const OwnerListComponent = (props: Props) => {
|
||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
||||||
</Block>
|
</Block>
|
||||||
</React.Fragment>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const OwnerListPage = withStyles(styles)(OwnerListComponent)
|
const OwnerListPage = withStyles(styles)(OwnerListComponent)
|
||||||
|
|
||||||
const OwnerList = ({ updateInitialProps }: Object, network: string) => (controls: React$Node, { values }: Object) => (
|
const OwnerList = ({ updateInitialProps }: Object, network: string) => (controls: React$Node, { values }: Object) => (
|
||||||
<React.Fragment>
|
<>
|
||||||
<OpenPaper controls={controls} padding={false}>
|
<OpenPaper controls={controls} padding={false}>
|
||||||
<OwnerListPage network={network} updateInitialProps={updateInitialProps} values={values} />
|
<OwnerListPage network={network} updateInitialProps={updateInitialProps} values={values} />
|
||||||
</OpenPaper>
|
</OpenPaper>
|
||||||
</React.Fragment>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default OwnerList
|
export default OwnerList
|
||||||
|
|
|
@ -51,7 +51,7 @@ const Layout = ({
|
||||||
const initialValues = initialValuesFrom(userAccount)
|
const initialValues = initialValuesFrom(userAccount)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
{provider ? (
|
{provider ? (
|
||||||
<Block>
|
<Block>
|
||||||
<Row align="center">
|
<Row align="center">
|
||||||
|
@ -75,7 +75,7 @@ const Layout = ({
|
||||||
) : (
|
) : (
|
||||||
<div>No web3 provider detected</div>
|
<div>No web3 provider detected</div>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ const SafeOwners = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
<Block className={classes.title}>
|
<Block className={classes.title}>
|
||||||
<Paragraph noMargin size="md" color="primary">
|
<Paragraph noMargin size="md" color="primary">
|
||||||
Specify the owners of the Safe.
|
Specify the owners of the Safe.
|
||||||
|
@ -212,14 +212,14 @@ owner(s)
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />}
|
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />}
|
||||||
</React.Fragment>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SafeOwnersForm = withStyles(styles)(SafeOwners)
|
const SafeOwnersForm = withStyles(styles)(SafeOwners)
|
||||||
|
|
||||||
const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node, { values, errors, form }: Object) => (
|
const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node, { values, errors, form }: Object) => (
|
||||||
<React.Fragment>
|
<>
|
||||||
<OpenPaper controls={controls} padding={false}>
|
<OpenPaper controls={controls} padding={false}>
|
||||||
<SafeOwnersForm
|
<SafeOwnersForm
|
||||||
otherAccounts={getAccountsFrom(values)}
|
otherAccounts={getAccountsFrom(values)}
|
||||||
|
@ -229,7 +229,7 @@ const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node
|
||||||
values={values}
|
values={values}
|
||||||
/>
|
/>
|
||||||
</OpenPaper>
|
</OpenPaper>
|
||||||
</React.Fragment>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default SafeOwnersPage
|
export default SafeOwnersPage
|
||||||
|
|
|
@ -19,11 +19,7 @@ import Field from '~/components/forms/Field'
|
||||||
import TextField from '~/components/forms/TextField'
|
import TextField from '~/components/forms/TextField'
|
||||||
import { type Token } from '~/logic/tokens/store/model/token'
|
import { type Token } from '~/logic/tokens/store/model/token'
|
||||||
import {
|
import {
|
||||||
composeValidators,
|
composeValidators, required, mustBeFloat, maxValue, greaterThan,
|
||||||
required,
|
|
||||||
mustBeFloat,
|
|
||||||
maxValue,
|
|
||||||
greaterThan,
|
|
||||||
} from '~/components/forms/validator'
|
} from '~/components/forms/validator'
|
||||||
import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField'
|
import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField'
|
||||||
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
|
@ -74,7 +70,7 @@ const SendFunds = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
<Row align="center" grow className={classes.heading}>
|
<Row align="center" grow className={classes.heading}>
|
||||||
<Paragraph weight="bolder" className={classes.manage} noMargin>
|
<Paragraph weight="bolder" className={classes.manage} noMargin>
|
||||||
Send Funds
|
Send Funds
|
||||||
|
@ -102,7 +98,7 @@ const SendFunds = ({
|
||||||
const { token } = formState.values
|
const { token } = formState.values
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
<Col xs={12}>
|
<Col xs={12}>
|
||||||
<AddressInput
|
<AddressInput
|
||||||
|
@ -174,12 +170,12 @@ const SendFunds = ({
|
||||||
Review
|
Review
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
</React.Fragment>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</GnoForm>
|
</GnoForm>
|
||||||
</Block>
|
</Block>
|
||||||
</React.Fragment>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue