Merge branch 'development' into fix/v3.1.1

This commit is contained in:
Daniel Sanchez 2021-03-04 16:11:04 +01:00 committed by GitHub
commit 883e6f4391
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 200 additions and 97 deletions

View File

@ -11,6 +11,7 @@ import Controls from './Controls'
import GnoForm from 'src/components/forms/GnoForm' import GnoForm from 'src/components/forms/GnoForm'
import Hairline from 'src/components/layout/Hairline' import Hairline from 'src/components/layout/Hairline'
import { history } from 'src/store' import { history } from 'src/store'
import { LoadFormValues } from 'src/routes/load/container/Load'
const transitionProps = { const transitionProps = {
timeout: { timeout: {
@ -20,7 +21,7 @@ const transitionProps = {
} }
export interface StepperPageFormProps { export interface StepperPageFormProps {
values: Record<string, string> values: LoadFormValues
errors: Record<string, string> errors: Record<string, string>
form: FormApi form: FormApi
} }

View File

@ -5,7 +5,7 @@ import Web3 from 'web3'
import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' import { ETHEREUM_NETWORK } from 'src/config/networks/network.d'
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
import { calculateGasOf, calculateGasPrice } from 'src/logic/wallets/ethTransactions' import { calculateGasOf, EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
import { getWeb3, getNetworkIdFrom } from 'src/logic/wallets/getWeb3' import { getWeb3, getNetworkIdFrom } from 'src/logic/wallets/getWeb3'
import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d' import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d'
import { GnosisSafeProxyFactory } from 'src/types/contracts/GnosisSafeProxyFactory.d' import { GnosisSafeProxyFactory } from 'src/types/contracts/GnosisSafeProxyFactory.d'
@ -99,7 +99,7 @@ export const getSafeDeploymentTransaction = (
safeAccounts, safeAccounts,
numConfirmations, numConfirmations,
ZERO_ADDRESS, ZERO_ADDRESS,
'0x', EMPTY_DATA,
DEFAULT_FALLBACK_HANDLER_ADDRESS, DEFAULT_FALLBACK_HANDLER_ADDRESS,
ZERO_ADDRESS, ZERO_ADDRESS,
0, 0,
@ -120,7 +120,7 @@ export const estimateGasForDeployingSafe = async (
safeAccounts, safeAccounts,
numConfirmations, numConfirmations,
ZERO_ADDRESS, ZERO_ADDRESS,
'0x', EMPTY_DATA,
DEFAULT_FALLBACK_HANDLER_ADDRESS, DEFAULT_FALLBACK_HANDLER_ADDRESS,
ZERO_ADDRESS, ZERO_ADDRESS,
0, 0,
@ -130,14 +130,11 @@ export const estimateGasForDeployingSafe = async (
const proxyFactoryData = proxyFactoryMaster.methods const proxyFactoryData = proxyFactoryMaster.methods
.createProxyWithNonce(safeMaster.options.address, gnosisSafeData, safeCreationSalt) .createProxyWithNonce(safeMaster.options.address, gnosisSafeData, safeCreationSalt)
.encodeABI() .encodeABI()
const gas = await calculateGasOf({ return calculateGasOf({
data: proxyFactoryData, data: proxyFactoryData,
from: userAccount, from: userAccount,
to: proxyFactoryMaster.options.address, to: proxyFactoryMaster.options.address,
}) })
const gasPrice = await calculateGasPrice()
return gas * parseInt(gasPrice, 10)
} }
export const getGnosisSafeInstanceAt = (safeAddress: string): GnosisSafe => { export const getGnosisSafeInstanceAt = (safeAddress: string): GnosisSafe => {

View File

@ -0,0 +1,59 @@
import { useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { userAccountSelector } from '../wallets/store/selectors'
import { estimateGasForDeployingSafe } from 'src/logic/contracts/safeContracts'
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
import { getNetworkInfo } from 'src/config'
import { calculateGasPrice } from 'src/logic/wallets/ethTransactions'
type EstimateSafeCreationGasProps = {
addresses: string[]
numOwners: number
safeCreationSalt: number
}
type SafeCreationEstimationResult = {
gasEstimation: number // Amount of gas needed for execute or approve the transaction
gasCostFormatted: string // Cost of gas in format '< | > 100'
gasLimit: number // Minimum gas requited to execute the Tx
}
const { nativeCoin } = getNetworkInfo()
export const useEstimateSafeCreationGas = ({
addresses,
numOwners,
safeCreationSalt,
}: EstimateSafeCreationGasProps): SafeCreationEstimationResult => {
const [gasEstimation, setGasEstimation] = useState<SafeCreationEstimationResult>({
gasEstimation: 0,
gasCostFormatted: '< 0.001',
gasLimit: 0,
})
const userAccount = useSelector(userAccountSelector)
useEffect(() => {
const estimateGas = async () => {
if (!addresses.length || !numOwners || !userAccount) {
return
}
const gasEstimation = await estimateGasForDeployingSafe(addresses, numOwners, userAccount, safeCreationSalt)
const gasPrice = await calculateGasPrice()
const estimatedGasCosts = gasEstimation * parseInt(gasPrice, 10)
const gasCost = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals)
const gasCostFormatted = formatAmount(gasCost)
setGasEstimation({
gasEstimation,
gasCostFormatted,
gasLimit: gasEstimation,
})
}
estimateGas()
}, [numOwners, userAccount, safeCreationSalt, addresses])
return gasEstimation
}

View File

@ -17,8 +17,9 @@ import { getAccountsFrom } from 'src/routes/open/utils/safeDataExtractor'
import { useStyles } from './styles' import { useStyles } from './styles'
import { getExplorerInfo } from 'src/config' import { getExplorerInfo } from 'src/config'
import { ExplorerButton } from '@gnosis.pm/safe-react-components' import { ExplorerButton } from '@gnosis.pm/safe-react-components'
import { LoadFormValues } from 'src/routes/load/container/Load'
const checkIfUserAddressIsAnOwner = (values: Record<string, string>, userAddress: string): boolean => { const checkIfUserAddressIsAnOwner = (values: LoadFormValues, userAddress: string): boolean => {
let isOwner = false let isOwner = false
for (let i = 0; i < getNumOwnersFrom(values); i += 1) { for (let i = 0; i < getNumOwnersFrom(values); i += 1) {
@ -33,7 +34,7 @@ const checkIfUserAddressIsAnOwner = (values: Record<string, string>, userAddress
interface Props { interface Props {
userAddress: string userAddress: string
values: Record<string, string> values: LoadFormValues
} }
const ReviewComponent = ({ userAddress, values }: Props): React.ReactElement => { const ReviewComponent = ({ userAddress, values }: Props): React.ReactElement => {
@ -138,7 +139,7 @@ const ReviewComponent = ({ userAddress, values }: Props): React.ReactElement =>
} }
const Review = ({ userAddress }: { userAddress: string }) => const Review = ({ userAddress }: { userAddress: string }) =>
function ReviewPage(controls: React.ReactNode, { values }: { values: Record<string, string> }): React.ReactElement { function ReviewPage(controls: React.ReactNode, { values }: { values: LoadFormValues }): React.ReactElement {
return ( return (
<> <>
<OpenPaper controls={controls} padding={false}> <OpenPaper controls={controls} padding={false}>

View File

@ -35,12 +35,24 @@ export const loadSafe = async (
await addSafe(safeProps) await addSafe(safeProps)
} }
export interface LoadFormValues { interface ReviewSafeCreationValues {
confirmations: string
name: string
owner0Address: string
owner0Name: string
safeCreationSalt: number
}
interface LoadForm {
name: string name: string
address: string address: string
threshold: string threshold: string
owner0Address: string
owner0Name: string
} }
export type LoadFormValues = ReviewSafeCreationValues | LoadForm
const Load = (): React.ReactElement => { const Load = (): React.ReactElement => {
const dispatch = useDispatch() const dispatch = useDispatch()
const provider = useSelector(providerNameSelector) const provider = useSelector(providerNameSelector)

View File

@ -20,7 +20,7 @@ import {
import { WelcomeLayout } from 'src/routes/welcome/components/index' import { WelcomeLayout } from 'src/routes/welcome/components/index'
import { history } from 'src/store' import { history } from 'src/store'
import { secondary, sm } from 'src/theme/variables' import { secondary, sm } from 'src/theme/variables'
import { networkSelector, providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors' import { providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { addressBookSelector } from 'src/logic/addressBook/store/selectors' import { addressBookSelector } from 'src/logic/addressBook/store/selectors'
import { getNameFromAddressBook } from 'src/logic/addressBook/utils' import { getNameFromAddressBook } from 'src/logic/addressBook/utils'
@ -94,7 +94,6 @@ export const Layout = (props: LayoutProps): React.ReactElement => {
const { onCallSafeContractSubmit, safeProps } = props const { onCallSafeContractSubmit, safeProps } = props
const provider = useSelector(providerNameSelector) const provider = useSelector(providerNameSelector)
const network = useSelector(networkSelector)
const userAccount = useSelector(userAccountSelector) const userAccount = useSelector(userAccountSelector)
useEffect(() => { useEffect(() => {
@ -107,33 +106,31 @@ export const Layout = (props: LayoutProps): React.ReactElement => {
const initialValues = useInitialValuesFrom(userAccount, safeProps) const initialValues = useInitialValuesFrom(userAccount, safeProps)
if (!provider) {
return <WelcomeLayout isOldMultisigMigration />
}
return ( return (
<> <Block>
{provider ? ( <Row align="center">
<Block> <IconButton disableRipple onClick={back} style={iconStyle}>
<Row align="center"> <ChevronLeft />
<IconButton disableRipple onClick={back} style={iconStyle}> </IconButton>
<ChevronLeft /> <Heading tag="h2" testId="create-safe-form-title">
</IconButton> Create New Safe
<Heading tag="h2" testId="create-safe-form-title"> </Heading>
Create New Safe </Row>
</Heading> <Stepper
</Row> initialValues={initialValues}
<Stepper mutators={formMutators}
initialValues={initialValues} onSubmit={onCallSafeContractSubmit}
mutators={formMutators} steps={steps}
onSubmit={onCallSafeContractSubmit} testId="create-safe-form"
steps={steps} >
testId="create-safe-form" <StepperPage component={SafeNameField} />
> <StepperPage component={SafeOwnersPage} validate={validateOwnersForm} />
<StepperPage component={SafeNameField} /> <StepperPage component={Review} />
<StepperPage component={SafeOwnersPage} validate={validateOwnersForm} /> </Stepper>
<StepperPage network={network} userAccount={userAccount} component={Review} /> </Block>
</Stepper>
</Block>
) : (
<WelcomeLayout isOldMultisigMigration />
)}
</>
) )
} }

View File

@ -1,7 +1,6 @@
import TableContainer from '@material-ui/core/TableContainer' import TableContainer from '@material-ui/core/TableContainer'
import classNames from 'classnames' import classNames from 'classnames'
import React, { useEffect, useState } from 'react' import React, { ReactElement, useEffect, useMemo } from 'react'
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
import { getExplorerInfo, getNetworkInfo } from 'src/config' import { getExplorerInfo, getNetworkInfo } from 'src/config'
import CopyBtn from 'src/components/CopyBtn' import CopyBtn from 'src/components/CopyBtn'
import Identicon from 'src/components/Identicon' import Identicon from 'src/components/Identicon'
@ -11,45 +10,41 @@ import Hairline from 'src/components/layout/Hairline'
import Paragraph from 'src/components/layout/Paragraph' import Paragraph from 'src/components/layout/Paragraph'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
import OpenPaper from 'src/components/Stepper/OpenPaper' import OpenPaper from 'src/components/Stepper/OpenPaper'
import { estimateGasForDeployingSafe } from 'src/logic/contracts/safeContracts' import {
import { formatAmount } from 'src/logic/tokens/utils/formatAmount' CreateSafeValues,
import { getAccountsFrom, getNamesFrom, getSafeCreationSaltFrom } from 'src/routes/open/utils/safeDataExtractor' getAccountsFrom,
getNamesFrom,
getSafeCreationSaltFrom,
} from 'src/routes/open/utils/safeDataExtractor'
import { FIELD_CONFIRMATIONS, FIELD_NAME, getNumOwnersFrom } from '../fields' import { FIELD_CONFIRMATIONS, FIELD_NAME, getNumOwnersFrom } from '../fields'
import { useStyles } from './styles' import { useStyles } from './styles'
import { ExplorerButton } from '@gnosis.pm/safe-react-components' import { ExplorerButton } from '@gnosis.pm/safe-react-components'
import { useEstimateSafeCreationGas } from 'src/logic/hooks/useEstimateSafeCreationGas'
import { FormApi } from 'final-form'
import { StepperPageFormProps } from 'src/components/Stepper'
import { LoadFormValues } from 'src/routes/load/container/Load'
type ReviewComponentProps = { type ReviewComponentProps = {
userAccount: string values: LoadFormValues
values: any form: FormApi
} }
const { nativeCoin } = getNetworkInfo() const { nativeCoin } = getNetworkInfo()
const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => { const ReviewComponent = ({ values, form }: ReviewComponentProps): ReactElement => {
const classes = useStyles() const classes = useStyles()
const [gasCosts, setGasCosts] = useState('< 0.001')
const names = getNamesFrom(values) const names = getNamesFrom(values)
const addresses = getAccountsFrom(values) const addresses = useMemo(() => getAccountsFrom(values), [values])
const numOwners = getNumOwnersFrom(values) const numOwners = getNumOwnersFrom(values)
const safeCreationSalt = getSafeCreationSaltFrom(values) const safeCreationSalt = getSafeCreationSaltFrom(values as CreateSafeValues)
const { gasCostFormatted, gasLimit } = useEstimateSafeCreationGas({ addresses, numOwners, safeCreationSalt })
useEffect(() => { useEffect(() => {
const estimateGas = async () => { form.mutators.setValue('gasLimit', gasLimit)
if (!addresses.length || !numOwners || !userAccount) { }, [gasLimit, form.mutators])
return
}
const estimatedGasCosts = (
await estimateGasForDeployingSafe(addresses, numOwners, userAccount, safeCreationSalt)
).toString()
const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals)
const formattedGasCosts = formatAmount(gasCosts)
setGasCosts(formattedGasCosts)
}
estimateGas()
}, [addresses, numOwners, safeCreationSalt, userAccount])
return ( return (
<> <>
@ -135,8 +130,8 @@ const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => {
<Row align="center" className={classes.info}> <Row align="center" className={classes.info}>
<Paragraph color="primary" noMargin size="md"> <Paragraph color="primary" noMargin size="md">
You&apos;re about to create a new Safe and will have to confirm a transaction with your currently connected You&apos;re about to create a new Safe and will have to confirm a transaction with your currently connected
wallet. The creation will cost approximately {gasCosts} {nativeCoin.name}. The exact amount will be determined wallet. The creation will cost approximately {gasCostFormatted} {nativeCoin.name}. The exact amount will be
by your wallet. determined by your wallet.
</Paragraph> </Paragraph>
</Row> </Row>
</> </>
@ -144,7 +139,7 @@ const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => {
} }
export const Review = () => export const Review = () =>
function ReviewPage(controls, props): React.ReactElement { function ReviewPage(controls: React.ReactNode, props: StepperPageFormProps): React.ReactElement {
return ( return (
<> <>
<OpenPaper controls={controls} padding={false}> <OpenPaper controls={controls} padding={false}>

View File

@ -4,13 +4,17 @@ export const FIELD_OWNERS = 'owners'
export const FIELD_SAFE_NAME = 'safeName' export const FIELD_SAFE_NAME = 'safeName'
export const FIELD_CREATION_PROXY_SALT = 'safeCreationSalt' export const FIELD_CREATION_PROXY_SALT = 'safeCreationSalt'
export const getOwnerNameBy = (index) => `owner${index}Name` export const getOwnerNameBy = (index: number): string => `owner${index}Name`
export const getOwnerAddressBy = (index) => `owner${index}Address` export const getOwnerAddressBy = (index: number): string => `owner${index}Address`
export const getNumOwnersFrom = (values) => { export const getNumOwnersFrom = (values) => {
const accounts = Object.keys(values) const accounts = Object.keys(values)
.sort() .sort()
.filter((key) => /^owner\d+Address$/.test(key) && !!values[key]) .filter((key) => {
const res = /^owner\d+Address$/.test(key)
return res && !!values[key]
})
return accounts.length return accounts.length
} }

View File

@ -6,11 +6,12 @@ import { useLocation } from 'react-router-dom'
import { PromiEvent, TransactionReceipt } from 'web3-core' import { PromiEvent, TransactionReceipt } from 'web3-core'
import { SafeDeployment } from 'src/routes/opening' import { SafeDeployment } from 'src/routes/opening'
import { InitialValuesForm, Layout } from 'src/routes/open/components/Layout' import { Layout } from 'src/routes/open/components/Layout'
import Page from 'src/components/layout/Page' import Page from 'src/components/layout/Page'
import { getSafeDeploymentTransaction } from 'src/logic/contracts/safeContracts' import { getSafeDeploymentTransaction } from 'src/logic/contracts/safeContracts'
import { checkReceiptStatus } from 'src/logic/wallets/ethTransactions' import { checkReceiptStatus } from 'src/logic/wallets/ethTransactions'
import { import {
CreateSafeValues,
getAccountsFrom, getAccountsFrom,
getNamesFrom, getNamesFrom,
getOwnersFrom, getOwnersFrom,
@ -29,6 +30,8 @@ import { useAnalytics } from 'src/utils/googleAnalytics'
const SAFE_PENDING_CREATION_STORAGE_KEY = 'SAFE_PENDING_CREATION_STORAGE_KEY' const SAFE_PENDING_CREATION_STORAGE_KEY = 'SAFE_PENDING_CREATION_STORAGE_KEY'
type LoadedSafeType = CreateSafeValues & { txHash: string }
interface SafeCreationQueryParams { interface SafeCreationQueryParams {
ownerAddresses: string | string[] | null ownerAddresses: string | string[] | null
ownerNames: string | string[] | null ownerNames: string | string[] | null
@ -85,7 +88,7 @@ export const getSafeProps = async (
return safeProps return safeProps
} }
export const createSafe = (values: InitialValuesForm, userAccount: string): PromiEvent<TransactionReceipt> => { export const createSafe = (values: CreateSafeValues, userAccount: string): PromiEvent<TransactionReceipt> => {
const confirmations = getThresholdFrom(values) const confirmations = getThresholdFrom(values)
const name = getSafeNameFrom(values) const name = getSafeNameFrom(values)
const ownersNames = getNamesFrom(values) const ownersNames = getNamesFrom(values)
@ -93,7 +96,10 @@ export const createSafe = (values: InitialValuesForm, userAccount: string): Prom
const safeCreationSalt = getSafeCreationSaltFrom(values) const safeCreationSalt = getSafeCreationSaltFrom(values)
const deploymentTx = getSafeDeploymentTransaction(ownerAddresses, confirmations, safeCreationSalt) const deploymentTx = getSafeDeploymentTransaction(ownerAddresses, confirmations, safeCreationSalt)
const promiEvent = deploymentTx.send({ from: userAccount }) const promiEvent = deploymentTx.send({
from: userAccount,
gas: values?.gasLimit,
})
promiEvent promiEvent
.once('transactionHash', (txHash) => { .once('transactionHash', (txHash) => {
@ -155,28 +161,28 @@ const Open = (): React.ReactElement => {
load() load()
}, []) }, [])
const createSafeProxy = async (formValues?: InitialValuesForm) => { const createSafeProxy = async (formValues?: CreateSafeValues) => {
let values = formValues let values = formValues
// save form values, used when the user rejects the TX and wants to retry // save form values, used when the user rejects the TX and wants to retry
if (formValues) { if (values) {
const copy = { ...formValues } const copy = { ...values }
saveToStorage(SAFE_PENDING_CREATION_STORAGE_KEY, copy) saveToStorage(SAFE_PENDING_CREATION_STORAGE_KEY, copy)
} else { } else {
values = await loadFromStorage(SAFE_PENDING_CREATION_STORAGE_KEY) values = (await loadFromStorage(SAFE_PENDING_CREATION_STORAGE_KEY)) as CreateSafeValues
} }
const promiEvent = createSafe(values as InitialValuesForm, userAccount) const promiEvent = createSafe(values, userAccount)
setCreationTxPromise(promiEvent) setCreationTxPromise(promiEvent)
setShowProgress(true) setShowProgress(true)
} }
const onSafeCreated = async (safeAddress): Promise<void> => { const onSafeCreated = async (safeAddress): Promise<void> => {
const pendingCreation = await loadFromStorage<{ txHash: string }>(SAFE_PENDING_CREATION_STORAGE_KEY) const pendingCreation = await loadFromStorage<LoadedSafeType>(SAFE_PENDING_CREATION_STORAGE_KEY)
const name = getSafeNameFrom(pendingCreation) const name = pendingCreation ? getSafeNameFrom(pendingCreation) : ''
const ownersNames = getNamesFrom(pendingCreation) const ownersNames = getNamesFrom(pendingCreation as CreateSafeValues)
const ownerAddresses = getAccountsFrom(pendingCreation) const ownerAddresses = pendingCreation ? getAccountsFrom(pendingCreation) : []
const safeProps = await getSafeProps(safeAddress, name, ownersNames, ownerAddresses) const safeProps = await getSafeProps(safeAddress, name, ownersNames, ownerAddresses)
await dispatch(addOrUpdateSafe(safeProps)) await dispatch(addOrUpdateSafe(safeProps))

View File

@ -8,6 +8,9 @@ describe('Test JS', () => {
owner1Address: 'bar', owner1Address: 'bar',
owner2Address: 'baz', owner2Address: 'baz',
owners: 3, owners: 3,
confirmations: '0',
name: '',
safeCreationSalt: 0,
} }
expect(getAccountsFrom(safe)).toEqual(['foo', 'bar', 'baz']) expect(getAccountsFrom(safe)).toEqual(['foo', 'bar', 'baz'])
@ -15,9 +18,15 @@ describe('Test JS', () => {
it('return the names of owners', () => { it('return the names of owners', () => {
const safe = { const safe = {
owner0Name: 'foo', owner0Name: 'foo',
owner0Address: '0x',
owner1Name: 'bar', owner1Name: 'bar',
owner1Address: '0x',
owner2Name: 'baz', owner2Name: 'baz',
owner2Address: '0x',
owners: 3, owners: 3,
confirmations: '0',
name: '',
safeCreationSalt: 0,
} }
expect(getNamesFrom(safe)).toEqual(['foo', 'bar', 'baz']) expect(getNamesFrom(safe)).toEqual(['foo', 'bar', 'baz'])
@ -31,12 +40,15 @@ describe('Test JS', () => {
owner2Name: 'bazName', owner2Name: 'bazName',
owner2Address: 'bazAddress', owner2Address: 'bazAddress',
owners: 1, owners: 1,
confirmations: '0',
name: '',
safeCreationSalt: 0,
} }
expect(getNamesFrom(safe)).toEqual(['fooName']) expect(getNamesFrom(safe)).toEqual(['fooName', 'barName', 'bazName'])
expect(getAccountsFrom(safe)).toEqual(['fooAddress']) expect(getAccountsFrom(safe)).toEqual(['fooAddress', 'barAddress', 'bazAddress'])
}) })
it('return name and address ordered alphabetically', () => { it('return name and address keys ordered alphabetically', () => {
const safe = { const safe = {
owner1Name: 'barName', owner1Name: 'barName',
owner1Address: 'barAddress', owner1Address: 'barAddress',
@ -45,14 +57,19 @@ describe('Test JS', () => {
owner2Address: 'bazAddress', owner2Address: 'bazAddress',
owner0Address: 'fooAddress', owner0Address: 'fooAddress',
owners: 1, owners: 1,
confirmations: '0',
name: '',
safeCreationSalt: 0,
} }
expect(getNamesFrom(safe)).toEqual(['fooName']) expect(getNamesFrom(safe)).toEqual(['fooName', 'barName', 'bazName'])
expect(getAccountsFrom(safe)).toEqual(['fooAddress']) expect(getAccountsFrom(safe)).toEqual(['fooAddress', 'barAddress', 'bazAddress'])
}) })
it('return the number of required confirmations', () => { it('return the number of required confirmations', () => {
const safe = { const safe = {
confirmations: '1', confirmations: '1',
name: '',
safeCreationSalt: 0,
} }
expect(getThresholdFrom(safe)).toEqual(1) expect(getThresholdFrom(safe)).toEqual(1)

View File

@ -2,31 +2,45 @@ import { List } from 'immutable'
import { makeOwner } from 'src/logic/safe/store/models/owner' import { makeOwner } from 'src/logic/safe/store/models/owner'
import { SafeOwner } from 'src/logic/safe/store/models/safe' import { SafeOwner } from 'src/logic/safe/store/models/safe'
import { LoadFormValues } from 'src/routes/load/container/Load'
import { getNumOwnersFrom } from 'src/routes/open/components/fields'
export const getAccountsFrom = (values) => { export type CreateSafeValues = {
confirmations: string
name: string
owner0Address?: string
owner0Name?: string
safeCreationSalt: number
gasLimit?: number
owners?: number | string
}
export const getAccountsFrom = (values: CreateSafeValues | LoadFormValues): string[] => {
const accounts = Object.keys(values) const accounts = Object.keys(values)
.sort() .sort()
.filter((key) => /^owner\d+Address$/.test(key)) .filter((key) => /^owner\d+Address$/.test(key))
return accounts.map((account) => values[account]).slice(0, values.owners) const numOwners = getNumOwnersFrom(values)
return accounts.map((account) => values[account]).slice(0, numOwners)
} }
export const getNamesFrom = (values) => { export const getNamesFrom = (values: CreateSafeValues | LoadFormValues): string[] => {
const accounts = Object.keys(values) const accounts = Object.keys(values)
.sort() .sort()
.filter((key) => /^owner\d+Name$/.test(key)) .filter((key) => /^owner\d+Name$/.test(key))
return accounts.map((account) => values[account]).slice(0, values.owners) const numOwners = getNumOwnersFrom(values)
return accounts.map((account) => values[account]).slice(0, numOwners)
} }
export const getOwnersFrom = (names, addresses): List<SafeOwner> => { export const getOwnersFrom = (names: string[], addresses: string[]): List<SafeOwner> => {
const owners = names.map((name, index) => makeOwner({ name, address: addresses[index] })) const owners = names.map((name, index) => makeOwner({ name, address: addresses[index] }))
return List(owners) return List(owners)
} }
export const getThresholdFrom = (values) => Number(values.confirmations) export const getThresholdFrom = (values: CreateSafeValues): number => Number(values.confirmations)
export const getSafeNameFrom = (values) => values.name export const getSafeNameFrom = (values: CreateSafeValues): string => values.name
export const getSafeCreationSaltFrom = (values) => values.safeCreationSalt export const getSafeCreationSaltFrom = (values: CreateSafeValues): number => values.safeCreationSalt