mirror of
https://github.com/status-im/safe-react.git
synced 2025-02-04 22:03:41 +00:00
Merge branch 'development' into fix/v3.1.1
This commit is contained in:
commit
883e6f4391
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 => {
|
||||||
|
59
src/logic/hooks/useEstimateSafeCreationGas.tsx
Normal file
59
src/logic/hooks/useEstimateSafeCreationGas.tsx
Normal 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
|
||||||
|
}
|
@ -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}>
|
||||||
|
@ -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)
|
||||||
|
@ -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 />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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're about to create a new Safe and will have to confirm a transaction with your currently connected
|
You'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}>
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user