Merge pull request #118 from gnosis/load-safe-improvements
Load safe improvements
This commit is contained in:
commit
4b470d870f
|
@ -53,7 +53,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
||||||
let currentProvider: ProviderProps = await getProviderInfo()
|
let currentProvider: ProviderProps = await getProviderInfo()
|
||||||
fetchProvider(currentProvider, openSnackbar)
|
fetchProvider(currentProvider, openSnackbar)
|
||||||
|
|
||||||
this.providerListener = setInterval(async () => {
|
this.providerListener = setInterval(async () => {
|
||||||
const newProvider: ProviderProps = await getProviderInfo()
|
const newProvider: ProviderProps = await getProviderInfo()
|
||||||
if (JSON.stringify(currentProvider) !== JSON.stringify(newProvider)) {
|
if (JSON.stringify(currentProvider) !== JSON.stringify(newProvider)) {
|
||||||
fetchProvider(newProvider, openSnackbar)
|
fetchProvider(newProvider, openSnackbar)
|
||||||
|
|
|
@ -7,12 +7,13 @@ import Heading from '~/components/layout/Heading'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import ReviewInformation from '~/routes/load/components/ReviewInformation'
|
import ReviewInformation from '~/routes/load/components/ReviewInformation'
|
||||||
|
import OwnerList from '~/routes/load/components/OwnerList'
|
||||||
import DetailsForm, { safeFieldsValidation } from '~/routes/load/components/DetailsForm'
|
import DetailsForm, { safeFieldsValidation } from '~/routes/load/components/DetailsForm'
|
||||||
import { history } from '~/store'
|
import { history } from '~/store'
|
||||||
import { secondary } from '~/theme/variables'
|
import { secondary } from '~/theme/variables'
|
||||||
import { type SelectorProps } from '~/routes/load/container/selector'
|
import { type SelectorProps } from '~/routes/load/container/selector'
|
||||||
|
|
||||||
const getSteps = () => ['Details', 'Review']
|
const getSteps = () => ['Details', 'Owners', 'Review']
|
||||||
|
|
||||||
type Props = SelectorProps & {
|
type Props = SelectorProps & {
|
||||||
onLoadSafeSubmit: (values: Object) => Promise<void>,
|
onLoadSafeSubmit: (values: Object) => Promise<void>,
|
||||||
|
@ -32,6 +33,7 @@ const Layout = ({
|
||||||
provider, onLoadSafeSubmit, network, userAddress,
|
provider, onLoadSafeSubmit, network, userAddress,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const steps = getSteps()
|
const steps = getSteps()
|
||||||
|
const initialValues = {}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -43,15 +45,16 @@ 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} testId="load-safe-form">
|
<Stepper onSubmit={onLoadSafeSubmit} steps={steps} initialValues={initialValues} testId="load-safe-form">
|
||||||
<Stepper.Page validate={safeFieldsValidation}>{DetailsForm}</Stepper.Page>
|
<Stepper.Page validate={safeFieldsValidation}>{DetailsForm}</Stepper.Page>
|
||||||
|
<Stepper.Page network={network}>{OwnerList}</Stepper.Page>
|
||||||
<Stepper.Page network={network} userAddress={userAddress}>
|
<Stepper.Page network={network} userAddress={userAddress}>
|
||||||
{ReviewInformation}
|
{ReviewInformation}
|
||||||
</Stepper.Page>
|
</Stepper.Page>
|
||||||
</Stepper>
|
</Stepper>
|
||||||
</Block>
|
</Block>
|
||||||
) : (
|
) : (
|
||||||
<div>No metamask detected</div>
|
<div>No account detected</div>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import Block from '~/components/layout/Block'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import Field from '~/components/forms/Field'
|
||||||
|
import { required } from '~/components/forms/validator'
|
||||||
|
import TextField from '~/components/forms/TextField'
|
||||||
|
import OpenInNew from '@material-ui/icons/OpenInNew'
|
||||||
|
import Identicon from '~/components/Identicon'
|
||||||
|
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||||
|
import Col from '~/components/layout/Col'
|
||||||
|
import Row from '~/components/layout/Row'
|
||||||
|
import Link from '~/components/layout/Link'
|
||||||
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
|
import Hairline from '~/components/layout/Hairline'
|
||||||
|
import {
|
||||||
|
sm, md, lg, border, secondary,
|
||||||
|
} from '~/theme/variables'
|
||||||
|
import { getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields'
|
||||||
|
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
||||||
|
import { FIELD_LOAD_ADDRESS, THRESHOLD } from '~/routes/load/components/fields'
|
||||||
|
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||||
|
|
||||||
|
const openIconStyle = {
|
||||||
|
height: '16px',
|
||||||
|
color: secondary,
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
details: {
|
||||||
|
padding: lg,
|
||||||
|
borderRight: `solid 1px ${border}`,
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
owners: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
},
|
||||||
|
ownerNames: {
|
||||||
|
maxWidth: '400px',
|
||||||
|
},
|
||||||
|
ownerAddresses: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: `${sm}`,
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
paddingLeft: '6px',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
paddingLeft: sm,
|
||||||
|
width: 'auto',
|
||||||
|
'&:hover': {
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
padding: `${md} ${lg}`,
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
padding: `0 ${lg}`,
|
||||||
|
marginBottom: '12px',
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
padding: `${sm} ${lg}`,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
marginRight: `${sm}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
type LayoutProps = {
|
||||||
|
network: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = LayoutProps & {
|
||||||
|
values: Object,
|
||||||
|
classes: Object,
|
||||||
|
updateInitialProps: (initialValues: Object) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
owners: Array<string>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateSafeValues = (owners: Array<string>, threshold: Number, values: Object) => {
|
||||||
|
const initialValues = { ...values }
|
||||||
|
for (let i = 0; i < owners.length; i += 1) {
|
||||||
|
initialValues[getOwnerAddressBy(i)] = owners[i]
|
||||||
|
}
|
||||||
|
initialValues[THRESHOLD] = threshold
|
||||||
|
return initialValues
|
||||||
|
}
|
||||||
|
|
||||||
|
class OwnerListComponent extends React.PureComponent<Props, State> {
|
||||||
|
state = {
|
||||||
|
owners: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted = false
|
||||||
|
|
||||||
|
componentDidMount = async () => {
|
||||||
|
this.mounted = true
|
||||||
|
const { values, updateInitialProps } = this.props
|
||||||
|
const safeAddress = values[FIELD_LOAD_ADDRESS]
|
||||||
|
|
||||||
|
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
|
const owners = await gnosisSafe.getOwners()
|
||||||
|
const threshold = await gnosisSafe.getThreshold()
|
||||||
|
|
||||||
|
const initialValues = calculateSafeValues(owners.sort(), threshold, values)
|
||||||
|
updateInitialProps(initialValues)
|
||||||
|
|
||||||
|
if (!owners) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mounted) {
|
||||||
|
this.setState(() => ({ owners: owners.sort() }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.mounted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { network, classes } = this.props
|
||||||
|
const { owners } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Block className={classes.title}>
|
||||||
|
<Paragraph noMargin size="md" color="primary">
|
||||||
|
{`This Safe has ${owners.length} owners. Optional: Provide a name for each owner.`}
|
||||||
|
</Paragraph>
|
||||||
|
</Block>
|
||||||
|
<Hairline />
|
||||||
|
<Row className={classes.header}>
|
||||||
|
<Col xs={4}>NAME</Col>
|
||||||
|
<Col xs={8}>ADDRESS</Col>
|
||||||
|
</Row>
|
||||||
|
<Hairline />
|
||||||
|
<Block margin="md" padding="md">
|
||||||
|
{owners.map((x, index) => (
|
||||||
|
<Row key={owners[index].address} className={classes.owner}>
|
||||||
|
<Col xs={4}>
|
||||||
|
<Field
|
||||||
|
className={classes.name}
|
||||||
|
name={getOwnerNameBy(index)}
|
||||||
|
component={TextField}
|
||||||
|
type="text"
|
||||||
|
validate={required}
|
||||||
|
defaultValue={`Owner #${index + 1}`}
|
||||||
|
placeholder="Owner Name*"
|
||||||
|
text="Owner Name"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col xs={7}>
|
||||||
|
<Row className={classes.ownerAddresses}>
|
||||||
|
<Identicon address={owners[index]} diameter={32} />
|
||||||
|
<Paragraph size="md" color="disabled" noMargin className={classes.address}>
|
||||||
|
{owners[index]}
|
||||||
|
</Paragraph>
|
||||||
|
<Link className={classes.open} to={getEtherScanLink(owners[index], network)} target="_blank">
|
||||||
|
<OpenInNew style={openIconStyle} />
|
||||||
|
</Link>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)) }
|
||||||
|
</Block>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const OwnerListPage = withStyles(styles)(OwnerListComponent)
|
||||||
|
|
||||||
|
const OwnerList = ({ updateInitialProps }: Object, network: string) => (controls: React$Node, { values }: Object) => (
|
||||||
|
<React.Fragment>
|
||||||
|
<OpenPaper controls={controls} padding={false}>
|
||||||
|
<OwnerListPage
|
||||||
|
network={network}
|
||||||
|
updateInitialProps={updateInitialProps}
|
||||||
|
values={values}
|
||||||
|
/>
|
||||||
|
</OpenPaper>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default OwnerList
|
|
@ -1,20 +1,24 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
import OpenInNew from '@material-ui/icons/OpenInNew'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||||
|
import Col from '~/components/layout/Col'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Link from '~/components/layout/Link'
|
import Link from '~/components/layout/Link'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import {
|
import {
|
||||||
xs, sm, lg, border, secondary,
|
xs, sm, lg, border, secondary,
|
||||||
} from '~/theme/variables'
|
} from '~/theme/variables'
|
||||||
import { getEtherScanLink, getWeb3 } from '~/logic/wallets/getWeb3'
|
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
||||||
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '~/routes/load/components/fields'
|
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
import { getOwnerNameBy, getOwnerAddressBy, getNumOwnersFrom } from '~/routes/open/components/fields'
|
||||||
import { getGnosisSafeContract } from '~/logic/contracts/safeContracts'
|
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
||||||
|
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS, THRESHOLD } from '~/routes/load/components/fields'
|
||||||
|
|
||||||
const openIconStyle = {
|
const openIconStyle = {
|
||||||
height: '16px',
|
height: '16px',
|
||||||
|
@ -22,20 +26,31 @@ const openIconStyle = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = () => ({
|
const styles = () => ({
|
||||||
|
root: {
|
||||||
|
minHeight: '300px',
|
||||||
|
},
|
||||||
details: {
|
details: {
|
||||||
padding: lg,
|
padding: lg,
|
||||||
borderRight: `solid 1px ${border}`,
|
borderRight: `solid 1px ${border}`,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
},
|
},
|
||||||
name: {
|
owners: {
|
||||||
letterSpacing: '-0.6px',
|
padding: lg,
|
||||||
},
|
},
|
||||||
container: {
|
name: {
|
||||||
marginTop: xs,
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
userName: {
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
padding: sm,
|
||||||
|
paddingLeft: lg,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
address: {
|
user: {
|
||||||
paddingLeft: '6px',
|
justifyContent: 'left',
|
||||||
},
|
},
|
||||||
open: {
|
open: {
|
||||||
paddingLeft: sm,
|
paddingLeft: sm,
|
||||||
|
@ -44,6 +59,13 @@ const styles = () => ({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
container: {
|
||||||
|
marginTop: xs,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
paddingLeft: '6px',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
type LayoutProps = {
|
type LayoutProps = {
|
||||||
|
@ -60,77 +82,113 @@ type State = {
|
||||||
isOwner: boolean,
|
isOwner: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkUserAddressOwner = (values: Object, userAddress: string): boolean => {
|
||||||
|
let isOwner: boolean = false
|
||||||
|
|
||||||
|
for (let i = 0; i < getNumOwnersFrom(values); i += 1) {
|
||||||
|
if (values[getOwnerAddressBy(i)] === userAddress) {
|
||||||
|
isOwner = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isOwner
|
||||||
|
}
|
||||||
|
|
||||||
class ReviewComponent extends React.PureComponent<Props, State> {
|
class ReviewComponent extends React.PureComponent<Props, State> {
|
||||||
state = {
|
|
||||||
isOwner: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
mounted = false
|
|
||||||
|
|
||||||
componentDidMount = async () => {
|
|
||||||
this.mounted = true
|
|
||||||
|
|
||||||
const { values, userAddress } = this.props
|
|
||||||
const safeAddress = values[FIELD_LOAD_ADDRESS]
|
|
||||||
const web3 = getWeb3()
|
|
||||||
|
|
||||||
const GnosisSafe = getGnosisSafeContract(web3)
|
|
||||||
const gnosisSafe = await GnosisSafe.at(safeAddress)
|
|
||||||
const owners = await gnosisSafe.getOwners()
|
|
||||||
if (!owners) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const isOwner = owners.find((owner: string) => sameAddress(owner, userAddress)) !== undefined
|
|
||||||
if (this.mounted) {
|
|
||||||
this.setState(() => ({ isOwner }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.mounted = false
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { values, classes, network } = this.props
|
const {
|
||||||
const { isOwner } = this.state
|
values, classes, network, userAddress,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const isOwner = checkUserAddressOwner(values, userAddress)
|
||||||
|
const owners = getAccountsFrom(values)
|
||||||
const safeAddress = values[FIELD_LOAD_ADDRESS]
|
const safeAddress = values[FIELD_LOAD_ADDRESS]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Block className={classes.details}>
|
<Row className={classes.root}>
|
||||||
<Block margin="lg">
|
<Col xs={4} layout="column">
|
||||||
<Paragraph size="sm" color="disabled" noMargin>
|
<Block className={classes.details}>
|
||||||
Name of the Safe
|
<Block margin="lg">
|
||||||
</Paragraph>
|
<Paragraph size="lg" color="primary" noMargin>
|
||||||
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
Review details
|
||||||
{values[FIELD_LOAD_NAME]}
|
</Paragraph>
|
||||||
</Paragraph>
|
</Block>
|
||||||
</Block>
|
<Block margin="lg">
|
||||||
<Block margin="lg">
|
<Paragraph size="sm" color="disabled" noMargin>
|
||||||
<Paragraph size="sm" color="disabled" noMargin>
|
Name of the Safe
|
||||||
Safe address
|
</Paragraph>
|
||||||
</Paragraph>
|
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
||||||
<Row className={classes.container}>
|
{values[FIELD_LOAD_NAME]}
|
||||||
<Identicon address={safeAddress} diameter={32} />
|
</Paragraph>
|
||||||
<Paragraph size="md" color="disabled" noMargin className={classes.address}>
|
</Block>
|
||||||
{safeAddress}
|
<Block margin="lg">
|
||||||
|
<Paragraph size="sm" color="disabled" noMargin>
|
||||||
|
Safe address
|
||||||
|
</Paragraph>
|
||||||
|
<Row className={classes.container}>
|
||||||
|
<Identicon address={safeAddress} diameter={32} />
|
||||||
|
<Paragraph size="md" color="disabled" noMargin className={classes.address}>
|
||||||
|
{shortVersionOf(safeAddress, 4)}
|
||||||
|
</Paragraph>
|
||||||
|
<Link className={classes.open} to={getEtherScanLink(safeAddress, network)} target="_blank">
|
||||||
|
<OpenInNew style={openIconStyle} />
|
||||||
|
</Link>
|
||||||
|
</Row>
|
||||||
|
</Block>
|
||||||
|
<Block margin="lg">
|
||||||
|
<Paragraph size="sm" color="disabled" noMargin>
|
||||||
|
Connected wallet client is owner?
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
||||||
|
{isOwner ? 'Yes' : 'No (read-only)'}
|
||||||
|
</Paragraph>
|
||||||
|
</Block>
|
||||||
|
<Block margin="lg">
|
||||||
|
<Paragraph size="sm" color="disabled" noMargin>
|
||||||
|
Any transaction requires the confirmation of:
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
||||||
|
{`${values[THRESHOLD]} out of ${getNumOwnersFrom(values)} owners`}
|
||||||
|
</Paragraph>
|
||||||
|
</Block>
|
||||||
|
</Block>
|
||||||
|
</Col>
|
||||||
|
<Col xs={8} layout="column">
|
||||||
|
<Block className={classes.owners}>
|
||||||
|
<Paragraph size="lg" color="primary" noMargin>
|
||||||
|
{`${getNumOwnersFrom(values)} Safe owners`}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link className={classes.open} to={getEtherScanLink(safeAddress, network)} target="_blank">
|
</Block>
|
||||||
<OpenInNew style={openIconStyle} />
|
<Hairline />
|
||||||
</Link>
|
{owners.map((x, index) => (
|
||||||
</Row>
|
<React.Fragment key={owners[index].address}>
|
||||||
</Block>
|
<Row className={classes.owner}>
|
||||||
<Block margin="lg">
|
<Col xs={1} align="center">
|
||||||
<Paragraph size="sm" color="disabled" noMargin>
|
<Identicon address={owners[index]} diameter={32} />
|
||||||
Connected wallet client is owner?
|
</Col>
|
||||||
</Paragraph>
|
<Col xs={11}>
|
||||||
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
<Block className={classNames(classes.name, classes.userName)}>
|
||||||
{isOwner ? 'Yes' : 'No (read-only)'}
|
<Paragraph size="lg" noMargin>
|
||||||
</Paragraph>
|
{values[getOwnerNameBy(index)]}
|
||||||
</Block>
|
</Paragraph>
|
||||||
</Block>
|
<Block align="center" className={classes.user}>
|
||||||
|
<Paragraph size="md" color="disabled" noMargin>
|
||||||
|
{owners[index]}
|
||||||
|
</Paragraph>
|
||||||
|
<Link className={classes.open} to={getEtherScanLink(owners[index], network)} target="_blank">
|
||||||
|
<OpenInNew style={openIconStyle} />
|
||||||
|
</Link>
|
||||||
|
</Block>
|
||||||
|
</Block>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Hairline />
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
// @flow
|
// @flow
|
||||||
export const FIELD_LOAD_NAME: string = 'name'
|
export const FIELD_LOAD_NAME: string = 'name'
|
||||||
export const FIELD_LOAD_ADDRESS: string = 'address'
|
export const FIELD_LOAD_ADDRESS: string = 'address'
|
||||||
|
export const THRESHOLD: Number = 'threshold'
|
||||||
|
|
|
@ -10,13 +10,20 @@ import { history } from '~/store'
|
||||||
import selector, { type SelectorProps } from './selector'
|
import selector, { type SelectorProps } from './selector'
|
||||||
import actions, { type Actions } from './actions'
|
import actions, { type Actions } from './actions'
|
||||||
import Layout from '../components/Layout'
|
import Layout from '../components/Layout'
|
||||||
|
import { getNamesFrom, getOwnersFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||||
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '../components/fields'
|
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '../components/fields'
|
||||||
|
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||||
|
|
||||||
type Props = SelectorProps & Actions
|
type Props = SelectorProps & Actions
|
||||||
|
|
||||||
export const loadSafe = async (safeName: string, safeAddress: string, addSafe: Function) => {
|
export const loadSafe = async (
|
||||||
|
safeName: string,
|
||||||
|
safeAddress: string,
|
||||||
|
owners: Array,
|
||||||
|
addSafe: Function
|
||||||
|
) => {
|
||||||
const safeProps = await buildSafe(safeAddress, safeName)
|
const safeProps = await buildSafe(safeAddress, safeName)
|
||||||
|
safeProps.owners = owners
|
||||||
await addSafe(safeProps)
|
await addSafe(safeProps)
|
||||||
|
|
||||||
const storedSafes = (await loadFromStorage(SAFES_KEY)) || {}
|
const storedSafes = (await loadFromStorage(SAFES_KEY)) || {}
|
||||||
|
@ -31,8 +38,13 @@ class Load extends React.Component<Props> {
|
||||||
const { addSafe } = this.props
|
const { addSafe } = this.props
|
||||||
const safeName = values[FIELD_LOAD_NAME]
|
const safeName = values[FIELD_LOAD_NAME]
|
||||||
const safeAddress = values[FIELD_LOAD_ADDRESS]
|
const safeAddress = values[FIELD_LOAD_ADDRESS]
|
||||||
|
const ownerNames = getNamesFrom(values)
|
||||||
|
|
||||||
await loadSafe(safeName, safeAddress, addSafe)
|
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
|
const ownerAddresses = await gnosisSafe.getOwners()
|
||||||
|
const owners = getOwnersFrom(ownerNames, ownerAddresses.sort())
|
||||||
|
|
||||||
|
await loadSafe(safeName, safeAddress, owners, addSafe)
|
||||||
|
|
||||||
const url = `${SAFELIST_ADDRESS}/${safeAddress}`
|
const url = `${SAFELIST_ADDRESS}/${safeAddress}`
|
||||||
history.push(url)
|
history.push(url)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import { makeOwner } from '~/routes/safe/store/models/owner'
|
||||||
|
|
||||||
export const getAccountsFrom = (values: Object): string[] => {
|
export const getAccountsFrom = (values: Object): string[] => {
|
||||||
const accounts = Object.keys(values)
|
const accounts = Object.keys(values)
|
||||||
.sort()
|
.sort()
|
||||||
|
@ -15,6 +17,17 @@ export const getNamesFrom = (values: Object): string[] => {
|
||||||
return accounts.map(account => values[account]).slice(0, values.owners)
|
return accounts.map(account => values[account]).slice(0, values.owners)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getOwnersFrom = (
|
||||||
|
names: string[],
|
||||||
|
addresses: string[],
|
||||||
|
): Array<string, string> => {
|
||||||
|
const owners = names.map((name: string, index: number) => makeOwner(
|
||||||
|
{ name, address: addresses[index] },
|
||||||
|
))
|
||||||
|
|
||||||
|
return owners
|
||||||
|
}
|
||||||
|
|
||||||
export const getThresholdFrom = (values: Object): number => Number(values.confirmations)
|
export const getThresholdFrom = (values: Object): number => Number(values.confirmations)
|
||||||
|
|
||||||
export const getSafeNameFrom = (values: Object): string => values.name
|
export const getSafeNameFrom = (values: Object): string => values.name
|
||||||
|
|
|
@ -12,7 +12,7 @@ import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||||
import { sleep } from '~/utils/timer'
|
import { sleep } from '~/utils/timer'
|
||||||
import { history } from '~/store'
|
import { history } from '~/store'
|
||||||
import AppRoutes from '~/routes'
|
import AppRoutes from '~/routes'
|
||||||
import { SAFELIST_ADDRESS, SETTINS_ADDRESS } from '~/routes/routes'
|
import { SAFELIST_ADDRESS } from '~/routes/routes'
|
||||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||||
|
|
||||||
export const EXPAND_BALANCE_INDEX = 0
|
export const EXPAND_BALANCE_INDEX = 0
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
export * from './tokens'
|
export * from './tokens'
|
||||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -6267,11 +6267,16 @@ ejs@^2.6.1:
|
||||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
|
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
|
||||||
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
|
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
|
||||||
|
|
||||||
electron-to-chromium@^1.3.122, electron-to-chromium@^1.3.150, electron-to-chromium@^1.3.47:
|
electron-to-chromium@^1.3.122, electron-to-chromium@^1.3.47:
|
||||||
version "1.3.158"
|
version "1.3.158"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.158.tgz#5e16909dcfd25ab7cd1665114ee381083a3ee858"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.158.tgz#5e16909dcfd25ab7cd1665114ee381083a3ee858"
|
||||||
integrity sha512-wJsJaWsViNQ129XPGmyO5gGs1jPMHr9vffjHAhUje1xZbEzQcqbENdvfyRD9q8UF0TgFQFCCUbaIpJarFbvsIg==
|
integrity sha512-wJsJaWsViNQ129XPGmyO5gGs1jPMHr9vffjHAhUje1xZbEzQcqbENdvfyRD9q8UF0TgFQFCCUbaIpJarFbvsIg==
|
||||||
|
|
||||||
|
electron-to-chromium@^1.3.150:
|
||||||
|
version "1.3.155"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.155.tgz#ebf0cc8eeaffd6151d1efad60fd9e021fb45fd3a"
|
||||||
|
integrity sha512-/ci/XgZG8jkLYOgOe3mpJY1onxPPTDY17y7scldhnSjjZqV6VvREG/LvwhRuV7BJbnENFfuDWZkSqlTh4x9ZjQ==
|
||||||
|
|
||||||
elliptic@6.3.3:
|
elliptic@6.3.3:
|
||||||
version "6.3.3"
|
version "6.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f"
|
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f"
|
||||||
|
@ -10728,11 +10733,16 @@ loglevel-colored-level-prefix@^1.0.0:
|
||||||
chalk "^1.1.3"
|
chalk "^1.1.3"
|
||||||
loglevel "^1.4.1"
|
loglevel "^1.4.1"
|
||||||
|
|
||||||
loglevel@^1.4.1, loglevel@^1.6.2:
|
loglevel@^1.4.1:
|
||||||
version "1.6.3"
|
version "1.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.3.tgz#77f2eb64be55a404c9fd04ad16d57c1d6d6b1280"
|
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.3.tgz#77f2eb64be55a404c9fd04ad16d57c1d6d6b1280"
|
||||||
integrity sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==
|
integrity sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==
|
||||||
|
|
||||||
|
loglevel@^1.6.2:
|
||||||
|
version "1.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.2.tgz#668c77948a03dbd22502a3513ace1f62a80cc372"
|
||||||
|
integrity sha512-Jt2MHrCNdtIe1W6co3tF5KXGRkzF+TYffiQstfXa04mrss9IKXzAAXYWak8LbZseAQY03sH2GzMCMU0ZOUc9bg==
|
||||||
|
|
||||||
looper@^2.0.0:
|
looper@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/looper/-/looper-2.0.0.tgz#66cd0c774af3d4fedac53794f742db56da8f09ec"
|
resolved "https://registry.yarnpkg.com/looper/-/looper-2.0.0.tgz#66cd0c774af3d4fedac53794f742db56da8f09ec"
|
||||||
|
@ -13595,7 +13605,7 @@ react-transition-group@^4.0.0:
|
||||||
object-assign "^4.1.0"
|
object-assign "^4.1.0"
|
||||||
prop-types "^15.5.10"
|
prop-types "^15.5.10"
|
||||||
|
|
||||||
react@^16.8.3, react@^16.8.6:
|
react@^16.7.0, react@^16.8.3, react@^16.8.6:
|
||||||
version "16.8.6"
|
version "16.8.6"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
|
||||||
integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==
|
integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==
|
||||||
|
|
Loading…
Reference in New Issue