mirror of
https://github.com/status-im/safe-react.git
synced 2025-02-28 09:20:39 +00:00
[AddressBook v2] - Avoid using checksumAddress
inside reducer (#2355)
This commit is contained in:
parent
e58b6d6b7b
commit
fc0c450a74
@ -4,6 +4,7 @@ import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
|||||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||||
import { isFeatureEnabled } from 'src/config'
|
import { isFeatureEnabled } from 'src/config'
|
||||||
import { FEATURES } from 'src/config/networks/network.d'
|
import { FEATURES } from 'src/config/networks/network.d'
|
||||||
|
import { isValidAddress } from 'src/utils/isValidAddress'
|
||||||
|
|
||||||
type ValidatorReturnType = string | undefined
|
type ValidatorReturnType = string | undefined
|
||||||
export type GenericValidatorType = (...args: unknown[]) => ValidatorReturnType
|
export type GenericValidatorType = (...args: unknown[]) => ValidatorReturnType
|
||||||
@ -73,9 +74,7 @@ export const mustBeHexData = (data: string): ValidatorReturnType => {
|
|||||||
export const mustBeAddressHash = memoize(
|
export const mustBeAddressHash = memoize(
|
||||||
(address: string): ValidatorReturnType => {
|
(address: string): ValidatorReturnType => {
|
||||||
const errorMessage = 'Must be a valid address'
|
const errorMessage = 'Must be a valid address'
|
||||||
const startsWith0x = address?.startsWith('0x')
|
return isValidAddress(address) ? undefined : errorMessage
|
||||||
const isAddress = getWeb3().utils.isAddress(address)
|
|
||||||
return startsWith0x && isAddress ? undefined : errorMessage
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import { AddressBookEntry, AddressBookState } from 'src/logic/addressBook/model/
|
|||||||
import { ADDRESS_BOOK_ACTIONS } from 'src/logic/addressBook/store/actions'
|
import { ADDRESS_BOOK_ACTIONS } from 'src/logic/addressBook/store/actions'
|
||||||
import { getEntryIndex, isValidAddressBookName } from 'src/logic/addressBook/utils'
|
import { getEntryIndex, isValidAddressBookName } from 'src/logic/addressBook/utils'
|
||||||
import { AppReduxState } from 'src/store'
|
import { AppReduxState } from 'src/store'
|
||||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
|
||||||
|
|
||||||
export const ADDRESS_BOOK_REDUCER_ID = 'addressBook'
|
export const ADDRESS_BOOK_REDUCER_ID = 'addressBook'
|
||||||
|
|
||||||
@ -14,16 +13,13 @@ export default handleActions<AppReduxState['addressBook'], Payloads>(
|
|||||||
{
|
{
|
||||||
[ADDRESS_BOOK_ACTIONS.ADD_OR_UPDATE]: (state, action: Action<AddressBookEntry>) => {
|
[ADDRESS_BOOK_ACTIONS.ADD_OR_UPDATE]: (state, action: Action<AddressBookEntry>) => {
|
||||||
const newState = [...state]
|
const newState = [...state]
|
||||||
const { address, ...rest } = action.payload
|
const addressBookEntry = action.payload
|
||||||
|
|
||||||
if (!isValidAddressBookName(rest.name)) {
|
if (!isValidAddressBookName(addressBookEntry.name)) {
|
||||||
// prevent adding an invalid name
|
// prevent adding an invalid name
|
||||||
return newState
|
return newState
|
||||||
}
|
}
|
||||||
|
|
||||||
// always checksum the address before storing it
|
|
||||||
const addressBookEntry = { address: checksumAddress(address), ...rest }
|
|
||||||
|
|
||||||
const entryIndex = getEntryIndex(newState, addressBookEntry)
|
const entryIndex = getEntryIndex(newState, addressBookEntry)
|
||||||
|
|
||||||
// update
|
// update
|
||||||
@ -56,18 +52,14 @@ export default handleActions<AppReduxState['addressBook'], Payloads>(
|
|||||||
// exclude those entries with invalid name
|
// exclude those entries with invalid name
|
||||||
.filter(({ name }) => isValidAddressBookName(name))
|
.filter(({ name }) => isValidAddressBookName(name))
|
||||||
.forEach((addressBookEntry) => {
|
.forEach((addressBookEntry) => {
|
||||||
const { address, ...rest } = addressBookEntry
|
const entryIndex = getEntryIndex(newState, addressBookEntry)
|
||||||
|
|
||||||
// always checksum the address before storing it
|
|
||||||
const newAddressBookEntry = { address: checksumAddress(address), ...rest }
|
|
||||||
const entryIndex = getEntryIndex(newState, newAddressBookEntry)
|
|
||||||
|
|
||||||
if (entryIndex >= 0) {
|
if (entryIndex >= 0) {
|
||||||
// update
|
// update
|
||||||
newState[entryIndex] = newAddressBookEntry
|
newState[entryIndex] = addressBookEntry
|
||||||
} else {
|
} else {
|
||||||
// add
|
// add
|
||||||
newState.push(newAddressBookEntry)
|
newState.push(addressBookEntry)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import { buildSafe } from 'src/logic/safe/store/actions/fetchSafe'
|
|||||||
import { history } from 'src/store'
|
import { history } from 'src/store'
|
||||||
import { SafeRecordProps } from 'src/logic/safe/store/models/safe'
|
import { SafeRecordProps } from 'src/logic/safe/store/models/safe'
|
||||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||||
|
import { isValidAddress } from 'src/utils/isValidAddress'
|
||||||
import { networkSelector, providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors'
|
import { networkSelector, providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors'
|
||||||
import { addOrUpdateSafe } from 'src/logic/safe/store/actions/addOrUpdateSafe'
|
import { addOrUpdateSafe } from 'src/logic/safe/store/actions/addOrUpdateSafe'
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ const Load = (): ReactElement => {
|
|||||||
const onLoadSafeSubmit = async (values: LoadFormValues) => {
|
const onLoadSafeSubmit = async (values: LoadFormValues) => {
|
||||||
let safeAddress = values[FIELD_LOAD_ADDRESS]
|
let safeAddress = values[FIELD_LOAD_ADDRESS]
|
||||||
|
|
||||||
if (!safeAddress) {
|
if (!isValidAddress(safeAddress)) {
|
||||||
console.error('failed to add Safe address', JSON.stringify(values))
|
console.error('failed to add Safe address', JSON.stringify(values))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ const Open = (): ReactElement => {
|
|||||||
setShowProgress(true)
|
setShowProgress(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSafeCreated = async (safeAddress): Promise<void> => {
|
const onSafeCreated = async (safeAddress: string): Promise<void> => {
|
||||||
const pendingCreation = await loadFromStorage<LoadedSafeType>(SAFE_PENDING_CREATION_STORAGE_KEY)
|
const pendingCreation = await loadFromStorage<LoadedSafeType>(SAFE_PENDING_CREATION_STORAGE_KEY)
|
||||||
|
|
||||||
let name = ''
|
let name = ''
|
||||||
|
@ -6,6 +6,7 @@ import { Modal } from 'src/components/Modal'
|
|||||||
import { CSVReader } from 'react-papaparse'
|
import { CSVReader } from 'react-papaparse'
|
||||||
import { AddressBookEntry } from 'src/logic/addressBook/model/addressBook'
|
import { AddressBookEntry } from 'src/logic/addressBook/model/addressBook'
|
||||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||||
|
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||||
|
|
||||||
const ImportContainer = styled.div`
|
const ImportContainer = styled.div`
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -68,8 +69,8 @@ const ImportEntryModal = ({ importEntryModalHandler, isOpen, onClose }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatedList = slicedData.map((entry) => {
|
const formatedList = slicedData.map((entry) => {
|
||||||
const address = entry.data[0].toLowerCase()
|
const address = entry.data[0]
|
||||||
return { address: getWeb3().utils.toChecksumAddress(address), name: entry.data[1] }
|
return { address: checksumAddress(address), name: entry.data[1] }
|
||||||
})
|
})
|
||||||
setEntryList(formatedList)
|
setEntryList(formatedList)
|
||||||
setImportError('')
|
setImportError('')
|
||||||
|
@ -126,7 +126,7 @@ const AddressBookTable = (): ReactElement => {
|
|||||||
// close the modal
|
// close the modal
|
||||||
setEditCreateEntryModalOpen(false)
|
setEditCreateEntryModalOpen(false)
|
||||||
// update the store
|
// update the store
|
||||||
dispatch(addressBookAddOrUpdate(makeAddressBookEntry(entry)))
|
dispatch(addressBookAddOrUpdate(makeAddressBookEntry({ ...entry, address: checksumAddress(entry.address) })))
|
||||||
}
|
}
|
||||||
|
|
||||||
const editEntryModalHandler = (entry: AddressBookEntry) => {
|
const editEntryModalHandler = (entry: AddressBookEntry) => {
|
||||||
@ -135,7 +135,7 @@ const AddressBookTable = (): ReactElement => {
|
|||||||
// close the modal
|
// close the modal
|
||||||
setEditCreateEntryModalOpen(false)
|
setEditCreateEntryModalOpen(false)
|
||||||
// update the store
|
// update the store
|
||||||
dispatch(addressBookAddOrUpdate(makeAddressBookEntry(entry)))
|
dispatch(addressBookAddOrUpdate(makeAddressBookEntry({ ...entry, address: checksumAddress(entry.address) })))
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteEntryModalHandler = () => {
|
const deleteEntryModalHandler = () => {
|
||||||
@ -149,11 +149,7 @@ const AddressBookTable = (): ReactElement => {
|
|||||||
|
|
||||||
const importEntryModalHandler = (addressList: AddressBookEntry[]) => {
|
const importEntryModalHandler = (addressList: AddressBookEntry[]) => {
|
||||||
addressList.forEach((entry) => {
|
addressList.forEach((entry) => {
|
||||||
const checksumEntries = {
|
dispatch(addressBookAddOrUpdate(makeAddressBookEntry(entry)))
|
||||||
...entry,
|
|
||||||
address: checksumAddress(entry.address),
|
|
||||||
}
|
|
||||||
dispatch(addressBookAddOrUpdate(makeAddressBookEntry(checksumEntries)))
|
|
||||||
})
|
})
|
||||||
setImportEntryModalOpen(false)
|
setImportEntryModalOpen(false)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
|
|||||||
import { addressBookAddOrUpdate } from 'src/logic/addressBook/store/actions'
|
import { addressBookAddOrUpdate } from 'src/logic/addressBook/store/actions'
|
||||||
import { NOTIFICATIONS } from 'src/logic/notifications'
|
import { NOTIFICATIONS } from 'src/logic/notifications'
|
||||||
import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar'
|
import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar'
|
||||||
|
import { OwnerData } from 'src/routes/safe/components/Settings/ManageOwners/dataFetcher'
|
||||||
|
|
||||||
import { useStyles } from './style'
|
import { useStyles } from './style'
|
||||||
import { getExplorerInfo } from 'src/config'
|
import { getExplorerInfo } from 'src/config'
|
||||||
@ -27,18 +28,17 @@ export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn'
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
ownerAddress: string
|
owner: OwnerData
|
||||||
selectedOwnerName: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditOwnerModal = ({ isOpen, onClose, ownerAddress, selectedOwnerName }: OwnProps): React.ReactElement => {
|
export const EditOwnerModal = ({ isOpen, onClose, owner }: OwnProps): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const handleSubmit = ({ ownerName }: { ownerName: string }): void => {
|
const handleSubmit = ({ ownerName }: { ownerName: string }): void => {
|
||||||
// Update the value only if the ownerName really changed
|
// Update the value only if the ownerName really changed
|
||||||
if (ownerName !== selectedOwnerName) {
|
if (ownerName !== owner.name) {
|
||||||
dispatch(addressBookAddOrUpdate(makeAddressBookEntry({ address: ownerAddress, name: ownerName })))
|
dispatch(addressBookAddOrUpdate(makeAddressBookEntry({ address: owner.address, name: ownerName })))
|
||||||
dispatch(enqueueSnackbar(NOTIFICATIONS.OWNER_NAME_CHANGE_EXECUTED_MSG))
|
dispatch(enqueueSnackbar(NOTIFICATIONS.OWNER_NAME_CHANGE_EXECUTED_MSG))
|
||||||
}
|
}
|
||||||
onClose()
|
onClose()
|
||||||
@ -70,7 +70,7 @@ export const EditOwnerModal = ({ isOpen, onClose, ownerAddress, selectedOwnerNam
|
|||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
<Field
|
<Field
|
||||||
component={TextField}
|
component={TextField}
|
||||||
initialValue={selectedOwnerName}
|
initialValue={owner.name}
|
||||||
name="ownerName"
|
name="ownerName"
|
||||||
placeholder="Owner name*"
|
placeholder="Owner name*"
|
||||||
testId={RENAME_OWNER_INPUT_TEST_ID}
|
testId={RENAME_OWNER_INPUT_TEST_ID}
|
||||||
@ -82,10 +82,10 @@ export const EditOwnerModal = ({ isOpen, onClose, ownerAddress, selectedOwnerNam
|
|||||||
<Row>
|
<Row>
|
||||||
<Block justify="center">
|
<Block justify="center">
|
||||||
<EthHashInfo
|
<EthHashInfo
|
||||||
hash={ownerAddress}
|
hash={owner.address}
|
||||||
showCopyBtn
|
showCopyBtn
|
||||||
showAvatar
|
showAvatar
|
||||||
explorerUrl={getExplorerInfo(ownerAddress)}
|
explorerUrl={getExplorerInfo(owner.address)}
|
||||||
/>
|
/>
|
||||||
</Block>
|
</Block>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { OwnerData } from 'src/routes/safe/components/Settings/ManageOwners/dataFetcher'
|
||||||
|
|
||||||
import { CheckOwner } from './screens/CheckOwner'
|
import { CheckOwner } from './screens/CheckOwner'
|
||||||
import { ReviewRemoveOwnerModal } from './screens/Review'
|
import { ReviewRemoveOwnerModal } from './screens/Review'
|
||||||
@ -13,9 +14,7 @@ import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selector
|
|||||||
import { Dispatch } from 'src/logic/safe/store/actions/types.d'
|
import { Dispatch } from 'src/logic/safe/store/actions/types.d'
|
||||||
import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
|
import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
|
||||||
|
|
||||||
type OwnerValues = {
|
type OwnerValues = OwnerData & {
|
||||||
ownerAddress: string
|
|
||||||
ownerName: string
|
|
||||||
threshold: string
|
threshold: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,18 +51,12 @@ export const sendRemoveOwner = async (
|
|||||||
type RemoveOwnerProps = {
|
type RemoveOwnerProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
ownerAddress: string
|
owner: OwnerData
|
||||||
ownerName: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RemoveOwnerModal = ({
|
export const RemoveOwnerModal = ({ isOpen, onClose, owner }: RemoveOwnerProps): React.ReactElement => {
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
ownerAddress,
|
|
||||||
ownerName,
|
|
||||||
}: RemoveOwnerProps): React.ReactElement => {
|
|
||||||
const [activeScreen, setActiveScreen] = useState('checkOwner')
|
const [activeScreen, setActiveScreen] = useState('checkOwner')
|
||||||
const [values, setValues] = useState<OwnerValues>({ ownerAddress, ownerName, threshold: '' })
|
const [values, setValues] = useState<OwnerValues>({ ...owner, threshold: '' })
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||||
|
|
||||||
@ -94,7 +87,7 @@ export const RemoveOwnerModal = ({
|
|||||||
|
|
||||||
const onRemoveOwner = (txParameters: TxParameters) => {
|
const onRemoveOwner = (txParameters: TxParameters) => {
|
||||||
onClose()
|
onClose()
|
||||||
sendRemoveOwner(values, safeAddress, ownerAddress, ownerName, dispatch, txParameters)
|
sendRemoveOwner(values, safeAddress, owner.address, owner.name, dispatch, txParameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -106,9 +99,7 @@ export const RemoveOwnerModal = ({
|
|||||||
title="Remove owner from Safe"
|
title="Remove owner from Safe"
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{activeScreen === 'checkOwner' && (
|
{activeScreen === 'checkOwner' && <CheckOwner onClose={onClose} onSubmit={ownerSubmitted} owner={owner} />}
|
||||||
<CheckOwner onClose={onClose} onSubmit={ownerSubmitted} ownerAddress={ownerAddress} ownerName={ownerName} />
|
|
||||||
)}
|
|
||||||
{activeScreen === 'selectThreshold' && (
|
{activeScreen === 'selectThreshold' && (
|
||||||
<ThresholdForm
|
<ThresholdForm
|
||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
@ -122,8 +113,7 @@ export const RemoveOwnerModal = ({
|
|||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSubmit={onRemoveOwner}
|
onSubmit={onRemoveOwner}
|
||||||
ownerAddress={ownerAddress}
|
owner={owner}
|
||||||
ownerName={ownerName}
|
|
||||||
threshold={Number(values.threshold)}
|
threshold={Number(values.threshold)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -7,6 +7,7 @@ import Col from 'src/components/layout/Col'
|
|||||||
import Hairline from 'src/components/layout/Hairline'
|
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 { OwnerData } from 'src/routes/safe/components/Settings/ManageOwners/dataFetcher'
|
||||||
|
|
||||||
import { useStyles } from './style'
|
import { useStyles } from './style'
|
||||||
import { EthHashInfo } from '@gnosis.pm/safe-react-components'
|
import { EthHashInfo } from '@gnosis.pm/safe-react-components'
|
||||||
@ -18,11 +19,10 @@ export const REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID = 'remove-owner-next-btn'
|
|||||||
interface CheckOwnerProps {
|
interface CheckOwnerProps {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSubmit: () => void
|
onSubmit: () => void
|
||||||
ownerAddress: string
|
owner: OwnerData
|
||||||
ownerName: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CheckOwner = ({ onClose, onSubmit, ownerAddress, ownerName }: CheckOwnerProps): ReactElement => {
|
export const CheckOwner = ({ onClose, onSubmit, owner }: CheckOwnerProps): ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -44,11 +44,11 @@ export const CheckOwner = ({ onClose, onSubmit, ownerAddress, ownerName }: Check
|
|||||||
<Row>
|
<Row>
|
||||||
<Col align="center" xs={12}>
|
<Col align="center" xs={12}>
|
||||||
<EthHashInfo
|
<EthHashInfo
|
||||||
hash={ownerAddress}
|
hash={owner.address}
|
||||||
name={ownerName}
|
name={owner.name}
|
||||||
showCopyBtn
|
showCopyBtn
|
||||||
showAvatar
|
showAvatar
|
||||||
explorerUrl={getExplorerInfo(ownerAddress)}
|
explorerUrl={getExplorerInfo(owner.address)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -19,6 +19,7 @@ import { useSafeName } from 'src/logic/addressBook/hooks/useSafeName'
|
|||||||
import { TxParametersDetail } from 'src/routes/safe/components/Transactions/helpers/TxParametersDetail'
|
import { TxParametersDetail } from 'src/routes/safe/components/Transactions/helpers/TxParametersDetail'
|
||||||
import { EstimationStatus, useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas'
|
import { EstimationStatus, useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas'
|
||||||
import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
|
import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
|
||||||
|
import { OwnerData } from 'src/routes/safe/components/Settings/ManageOwners/dataFetcher'
|
||||||
|
|
||||||
import { useStyles } from './style'
|
import { useStyles } from './style'
|
||||||
import { Modal } from 'src/components/Modal'
|
import { Modal } from 'src/components/Modal'
|
||||||
@ -35,8 +36,7 @@ type ReviewRemoveOwnerProps = {
|
|||||||
onClickBack: () => void
|
onClickBack: () => void
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSubmit: (txParameters: TxParameters) => void
|
onSubmit: (txParameters: TxParameters) => void
|
||||||
ownerAddress: string
|
owner: OwnerData
|
||||||
ownerName: string
|
|
||||||
threshold?: number
|
threshold?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +44,7 @@ export const ReviewRemoveOwnerModal = ({
|
|||||||
onClickBack,
|
onClickBack,
|
||||||
onClose,
|
onClose,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
ownerAddress,
|
owner,
|
||||||
ownerName,
|
|
||||||
threshold = 1,
|
threshold = 1,
|
||||||
}: ReviewRemoveOwnerProps): React.ReactElement => {
|
}: ReviewRemoveOwnerProps): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
@ -91,9 +90,9 @@ export const ReviewRemoveOwnerModal = ({
|
|||||||
// the data lookup can be removed from here
|
// the data lookup can be removed from here
|
||||||
const gnosisSafe = getGnosisSafeInstanceAt(safeAddress)
|
const gnosisSafe = getGnosisSafeInstanceAt(safeAddress)
|
||||||
const safeOwners = await gnosisSafe.methods.getOwners().call()
|
const safeOwners = await gnosisSafe.methods.getOwners().call()
|
||||||
const index = safeOwners.findIndex((owner) => sameAddress(owner, ownerAddress))
|
const index = safeOwners.findIndex((ownerAddress) => sameAddress(ownerAddress, owner.address))
|
||||||
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
|
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
|
||||||
const txData = gnosisSafe.methods.removeOwner(prevAddress, ownerAddress, threshold).encodeABI()
|
const txData = gnosisSafe.methods.removeOwner(prevAddress, owner.address, threshold).encodeABI()
|
||||||
|
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
setData(txData)
|
setData(txData)
|
||||||
@ -107,7 +106,7 @@ export const ReviewRemoveOwnerModal = ({
|
|||||||
return () => {
|
return () => {
|
||||||
isCurrent = false
|
isCurrent = false
|
||||||
}
|
}
|
||||||
}, [safeAddress, ownerAddress, threshold])
|
}, [safeAddress, owner.address, threshold])
|
||||||
|
|
||||||
const closeEditModalCallback = (txParameters: TxParameters) => {
|
const closeEditModalCallback = (txParameters: TxParameters) => {
|
||||||
const oldGasPrice = Number(gasPriceFormatted)
|
const oldGasPrice = Number(gasPriceFormatted)
|
||||||
@ -186,17 +185,17 @@ export const ReviewRemoveOwnerModal = ({
|
|||||||
</Row>
|
</Row>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
{owners?.map(
|
{owners?.map(
|
||||||
(owner) =>
|
(safeOwner) =>
|
||||||
owner.address !== ownerAddress && (
|
!sameAddress(safeOwner.address, owner.address) && (
|
||||||
<React.Fragment key={owner.address}>
|
<React.Fragment key={safeOwner.address}>
|
||||||
<Row className={classes.owner}>
|
<Row className={classes.owner}>
|
||||||
<Col align="center" xs={12}>
|
<Col align="center" xs={12}>
|
||||||
<EthHashInfo
|
<EthHashInfo
|
||||||
hash={owner.address}
|
hash={safeOwner.address}
|
||||||
name={owner.name}
|
name={safeOwner.name}
|
||||||
showCopyBtn
|
showCopyBtn
|
||||||
showAvatar
|
showAvatar
|
||||||
explorerUrl={getExplorerInfo(owner.address)}
|
explorerUrl={getExplorerInfo(safeOwner.address)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -213,11 +212,11 @@ export const ReviewRemoveOwnerModal = ({
|
|||||||
<Row className={classes.selectedOwner}>
|
<Row className={classes.selectedOwner}>
|
||||||
<Col align="center" xs={12}>
|
<Col align="center" xs={12}>
|
||||||
<EthHashInfo
|
<EthHashInfo
|
||||||
hash={ownerAddress}
|
hash={owner.address}
|
||||||
name={ownerName}
|
name={owner.name}
|
||||||
showCopyBtn
|
showCopyBtn
|
||||||
showAvatar
|
showAvatar
|
||||||
explorerUrl={getExplorerInfo(ownerAddress)}
|
explorerUrl={getExplorerInfo(owner.address)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -15,14 +15,16 @@ import { Dispatch } from 'src/logic/safe/store/actions/types.d'
|
|||||||
import { OwnerForm } from 'src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm'
|
import { OwnerForm } from 'src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm'
|
||||||
import { ReviewReplaceOwnerModal } from 'src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review'
|
import { ReviewReplaceOwnerModal } from 'src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review'
|
||||||
import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
|
import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
|
||||||
|
import { isValidAddress } from 'src/utils/isValidAddress'
|
||||||
|
import { OwnerData } from 'src/routes/safe/components/Settings/ManageOwners/dataFetcher'
|
||||||
|
|
||||||
export type OwnerValues = {
|
export type OwnerValues = {
|
||||||
newOwnerAddress: string
|
address: string
|
||||||
newOwnerName: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendReplaceOwner = async (
|
export const sendReplaceOwner = async (
|
||||||
values: OwnerValues,
|
newOwner: OwnerValues,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
ownerAddressToRemove: string,
|
ownerAddressToRemove: string,
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
@ -32,7 +34,7 @@ export const sendReplaceOwner = async (
|
|||||||
const safeOwners = await gnosisSafe.methods.getOwners().call()
|
const safeOwners = await gnosisSafe.methods.getOwners().call()
|
||||||
const index = safeOwners.findIndex((ownerAddress) => sameAddress(ownerAddress, ownerAddressToRemove))
|
const index = safeOwners.findIndex((ownerAddress) => sameAddress(ownerAddress, ownerAddressToRemove))
|
||||||
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
|
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
|
||||||
const txData = gnosisSafe.methods.swapOwner(prevAddress, ownerAddressToRemove, values.newOwnerAddress).encodeABI()
|
const txData = gnosisSafe.methods.swapOwner(prevAddress, ownerAddressToRemove, newOwner.address).encodeABI()
|
||||||
|
|
||||||
const txHash = await dispatch(
|
const txHash = await dispatch(
|
||||||
createTransaction({
|
createTransaction({
|
||||||
@ -49,45 +51,26 @@ export const sendReplaceOwner = async (
|
|||||||
|
|
||||||
if (txHash) {
|
if (txHash) {
|
||||||
// update the AB
|
// update the AB
|
||||||
dispatch(
|
dispatch(addressBookAddOrUpdate(makeAddressBookEntry(newOwner)))
|
||||||
addressBookAddOrUpdate(
|
|
||||||
makeAddressBookEntry({
|
|
||||||
address: values.newOwnerAddress,
|
|
||||||
name: values.newOwnerName,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReplaceOwnerProps = {
|
type ReplaceOwnerProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
ownerAddress: string
|
owner: OwnerData
|
||||||
ownerName: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReplaceOwnerModal = ({
|
export const ReplaceOwnerModal = ({ isOpen, onClose, owner }: ReplaceOwnerProps): React.ReactElement => {
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
ownerAddress,
|
|
||||||
ownerName,
|
|
||||||
}: ReplaceOwnerProps): React.ReactElement => {
|
|
||||||
const [activeScreen, setActiveScreen] = useState('checkOwner')
|
const [activeScreen, setActiveScreen] = useState('checkOwner')
|
||||||
const [values, setValues] = useState({
|
const [newOwner, setNewOwner] = useState({ address: '', name: '' })
|
||||||
newOwnerAddress: '',
|
|
||||||
newOwnerName: '',
|
|
||||||
})
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => () => {
|
() => () => {
|
||||||
setActiveScreen('checkOwner')
|
setActiveScreen('checkOwner')
|
||||||
setValues({
|
setNewOwner({ address: '', name: '' })
|
||||||
newOwnerAddress: '',
|
|
||||||
newOwnerName: '',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
[isOpen],
|
[isOpen],
|
||||||
)
|
)
|
||||||
@ -96,22 +79,19 @@ export const ReplaceOwnerModal = ({
|
|||||||
|
|
||||||
const ownerSubmitted = (newValues) => {
|
const ownerSubmitted = (newValues) => {
|
||||||
const { ownerAddress, ownerName } = newValues
|
const { ownerAddress, ownerName } = newValues
|
||||||
|
|
||||||
|
if (isValidAddress(ownerAddress)) {
|
||||||
const checksumAddr = checksumAddress(ownerAddress)
|
const checksumAddr = checksumAddress(ownerAddress)
|
||||||
setValues({
|
setNewOwner({ address: checksumAddr, name: ownerName })
|
||||||
newOwnerAddress: checksumAddr,
|
|
||||||
newOwnerName: ownerName,
|
|
||||||
})
|
|
||||||
setActiveScreen('reviewReplaceOwner')
|
setActiveScreen('reviewReplaceOwner')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onReplaceOwner = async (txParameters: TxParameters) => {
|
const onReplaceOwner = async (txParameters: TxParameters) => {
|
||||||
onClose()
|
onClose()
|
||||||
try {
|
try {
|
||||||
await sendReplaceOwner(values, safeAddress, ownerAddress, dispatch, txParameters)
|
await sendReplaceOwner(newOwner, safeAddress, owner.address, dispatch, txParameters)
|
||||||
|
dispatch(addressBookAddOrUpdate(makeAddressBookEntry(newOwner)))
|
||||||
dispatch(
|
|
||||||
addressBookAddOrUpdate(makeAddressBookEntry({ address: values.newOwnerAddress, name: values.newOwnerName })),
|
|
||||||
)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error while removing an owner', error)
|
console.error('Error while removing an owner', error)
|
||||||
}
|
}
|
||||||
@ -127,22 +107,15 @@ export const ReplaceOwnerModal = ({
|
|||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{activeScreen === 'checkOwner' && (
|
{activeScreen === 'checkOwner' && (
|
||||||
<OwnerForm
|
<OwnerForm onClose={onClose} onSubmit={ownerSubmitted} initialValues={newOwner} owner={owner} />
|
||||||
onClose={onClose}
|
|
||||||
onSubmit={ownerSubmitted}
|
|
||||||
initialValues={values}
|
|
||||||
ownerAddress={ownerAddress}
|
|
||||||
ownerName={ownerName}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'reviewReplaceOwner' && (
|
{activeScreen === 'reviewReplaceOwner' && (
|
||||||
<ReviewReplaceOwnerModal
|
<ReviewReplaceOwnerModal
|
||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSubmit={onReplaceOwner}
|
onSubmit={onReplaceOwner}
|
||||||
ownerAddress={ownerAddress}
|
owner={owner}
|
||||||
ownerName={ownerName}
|
newOwner={newOwner}
|
||||||
values={values}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -26,6 +26,7 @@ import { Modal } from 'src/components/Modal'
|
|||||||
import { safeOwnersSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
import { safeOwnersSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
||||||
import { addressBookMapSelector } from 'src/logic/addressBook/store/selectors'
|
import { addressBookMapSelector } from 'src/logic/addressBook/store/selectors'
|
||||||
import { web3ReadOnly } from 'src/logic/wallets/getWeb3'
|
import { web3ReadOnly } from 'src/logic/wallets/getWeb3'
|
||||||
|
import { OwnerData } from 'src/routes/safe/components/Settings/ManageOwners/dataFetcher'
|
||||||
|
|
||||||
import { useStyles } from './style'
|
import { useStyles } from './style'
|
||||||
import { getExplorerInfo, getNetworkId } from 'src/config'
|
import { getExplorerInfo, getNetworkId } from 'src/config'
|
||||||
@ -59,18 +60,11 @@ type NewOwnerProps = {
|
|||||||
type OwnerFormProps = {
|
type OwnerFormProps = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSubmit: (values: NewOwnerProps) => void
|
onSubmit: (values: NewOwnerProps) => void
|
||||||
ownerAddress: string
|
owner: OwnerData
|
||||||
ownerName: string
|
|
||||||
initialValues?: OwnerValues
|
initialValues?: OwnerValues
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OwnerForm = ({
|
export const OwnerForm = ({ onClose, onSubmit, owner, initialValues }: OwnerFormProps): ReactElement => {
|
||||||
onClose,
|
|
||||||
onSubmit,
|
|
||||||
ownerAddress,
|
|
||||||
ownerName,
|
|
||||||
initialValues,
|
|
||||||
}: OwnerFormProps): ReactElement => {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const handleSubmit = (values: NewOwnerProps) => {
|
const handleSubmit = (values: NewOwnerProps) => {
|
||||||
@ -99,8 +93,8 @@ export const OwnerForm = ({
|
|||||||
formMutators={formMutators}
|
formMutators={formMutators}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
ownerName: initialValues?.newOwnerName,
|
ownerName: initialValues?.name,
|
||||||
ownerAddress: initialValues?.newOwnerAddress,
|
ownerAddress: initialValues?.address,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(...args) => {
|
{(...args) => {
|
||||||
@ -132,11 +126,11 @@ export const OwnerForm = ({
|
|||||||
<Row className={classes.owner}>
|
<Row className={classes.owner}>
|
||||||
<Col align="center" xs={12}>
|
<Col align="center" xs={12}>
|
||||||
<EthHashInfo
|
<EthHashInfo
|
||||||
hash={ownerAddress}
|
hash={owner.address}
|
||||||
name={ownerName}
|
name={owner.name}
|
||||||
showCopyBtn
|
showCopyBtn
|
||||||
showAvatar
|
showAvatar
|
||||||
explorerUrl={getExplorerInfo(ownerAddress)}
|
explorerUrl={getExplorerInfo(owner.address)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -24,6 +24,8 @@ import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionPara
|
|||||||
import { Modal } from 'src/components/Modal'
|
import { Modal } from 'src/components/Modal'
|
||||||
import { TransactionFees } from 'src/components/TransactionsFees'
|
import { TransactionFees } from 'src/components/TransactionsFees'
|
||||||
import { EditableTxParameters } from 'src/routes/safe/components/Transactions/helpers/EditableTxParameters'
|
import { EditableTxParameters } from 'src/routes/safe/components/Transactions/helpers/EditableTxParameters'
|
||||||
|
import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||||
|
import { OwnerData } from 'src/routes/safe/components/Settings/ManageOwners/dataFetcher'
|
||||||
|
|
||||||
import { useStyles } from './style'
|
import { useStyles } from './style'
|
||||||
|
|
||||||
@ -35,11 +37,10 @@ type ReplaceOwnerProps = {
|
|||||||
onClose: () => void
|
onClose: () => void
|
||||||
onClickBack: () => void
|
onClickBack: () => void
|
||||||
onSubmit: (txParameters: TxParameters) => void
|
onSubmit: (txParameters: TxParameters) => void
|
||||||
ownerAddress: string
|
owner: OwnerData
|
||||||
ownerName: string
|
newOwner: {
|
||||||
values: {
|
address: string
|
||||||
newOwnerAddress: string
|
name: string
|
||||||
newOwnerName: string
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,9 +48,8 @@ export const ReviewReplaceOwnerModal = ({
|
|||||||
onClickBack,
|
onClickBack,
|
||||||
onClose,
|
onClose,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
ownerAddress,
|
owner,
|
||||||
ownerName,
|
newOwner,
|
||||||
values,
|
|
||||||
}: ReplaceOwnerProps): React.ReactElement => {
|
}: ReplaceOwnerProps): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [data, setData] = useState('')
|
const [data, setData] = useState('')
|
||||||
@ -85,9 +85,9 @@ export const ReviewReplaceOwnerModal = ({
|
|||||||
const calculateReplaceOwnerData = async () => {
|
const calculateReplaceOwnerData = async () => {
|
||||||
const gnosisSafe = getGnosisSafeInstanceAt(safeAddress)
|
const gnosisSafe = getGnosisSafeInstanceAt(safeAddress)
|
||||||
const safeOwners = await gnosisSafe.methods.getOwners().call()
|
const safeOwners = await gnosisSafe.methods.getOwners().call()
|
||||||
const index = safeOwners.findIndex((owner) => owner.toLowerCase() === ownerAddress.toLowerCase())
|
const index = safeOwners.findIndex((ownerAddress) => sameAddress(ownerAddress, owner.address))
|
||||||
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
|
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
|
||||||
const txData = gnosisSafe.methods.swapOwner(prevAddress, ownerAddress, values.newOwnerAddress).encodeABI()
|
const txData = gnosisSafe.methods.swapOwner(prevAddress, owner.address, newOwner.address).encodeABI()
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
setData(txData)
|
setData(txData)
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ export const ReviewReplaceOwnerModal = ({
|
|||||||
return () => {
|
return () => {
|
||||||
isCurrent = false
|
isCurrent = false
|
||||||
}
|
}
|
||||||
}, [ownerAddress, safeAddress, values.newOwnerAddress])
|
}, [owner.address, safeAddress, newOwner.address])
|
||||||
|
|
||||||
const closeEditModalCallback = (txParameters: TxParameters) => {
|
const closeEditModalCallback = (txParameters: TxParameters) => {
|
||||||
const oldGasPrice = Number(gasPriceFormatted)
|
const oldGasPrice = Number(gasPriceFormatted)
|
||||||
@ -174,17 +174,17 @@ export const ReviewReplaceOwnerModal = ({
|
|||||||
</Row>
|
</Row>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
{owners?.map(
|
{owners?.map(
|
||||||
(owner) =>
|
(safeOwner) =>
|
||||||
owner.address !== ownerAddress && (
|
!sameAddress(safeOwner.address, owner.address) && (
|
||||||
<React.Fragment key={owner.address}>
|
<React.Fragment key={safeOwner.address}>
|
||||||
<Row className={classes.owner}>
|
<Row className={classes.owner}>
|
||||||
<Col align="center" xs={12}>
|
<Col align="center" xs={12}>
|
||||||
<EthHashInfo
|
<EthHashInfo
|
||||||
hash={owner.address}
|
hash={safeOwner.address}
|
||||||
name={owner.name}
|
name={safeOwner.name}
|
||||||
showCopyBtn
|
showCopyBtn
|
||||||
showAvatar
|
showAvatar
|
||||||
explorerUrl={getExplorerInfo(owner.address)}
|
explorerUrl={getExplorerInfo(safeOwner.address)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -201,11 +201,11 @@ export const ReviewReplaceOwnerModal = ({
|
|||||||
<Row className={classes.selectedOwnerRemoved}>
|
<Row className={classes.selectedOwnerRemoved}>
|
||||||
<Col align="center" xs={12}>
|
<Col align="center" xs={12}>
|
||||||
<EthHashInfo
|
<EthHashInfo
|
||||||
hash={ownerAddress}
|
hash={owner.address}
|
||||||
name={ownerName}
|
name={owner.name}
|
||||||
showCopyBtn
|
showCopyBtn
|
||||||
showAvatar
|
showAvatar
|
||||||
explorerUrl={getExplorerInfo(ownerAddress)}
|
explorerUrl={getExplorerInfo(owner.address)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -218,11 +218,11 @@ export const ReviewReplaceOwnerModal = ({
|
|||||||
<Row className={classes.selectedOwnerAdded}>
|
<Row className={classes.selectedOwnerAdded}>
|
||||||
<Col align="center" xs={12}>
|
<Col align="center" xs={12}>
|
||||||
<EthHashInfo
|
<EthHashInfo
|
||||||
hash={values.newOwnerAddress}
|
hash={newOwner.address}
|
||||||
name={values.newOwnerName}
|
name={newOwner.name}
|
||||||
showCopyBtn
|
showCopyBtn
|
||||||
showAvatar
|
showAvatar
|
||||||
explorerUrl={getExplorerInfo(values.newOwnerAddress)}
|
explorerUrl={getExplorerInfo(newOwner.address)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -7,7 +7,9 @@ export const OWNERS_TABLE_NAME_ID = 'name'
|
|||||||
export const OWNERS_TABLE_ADDRESS_ID = 'address'
|
export const OWNERS_TABLE_ADDRESS_ID = 'address'
|
||||||
export const OWNERS_TABLE_ACTIONS_ID = 'actions'
|
export const OWNERS_TABLE_ACTIONS_ID = 'actions'
|
||||||
|
|
||||||
export const getOwnerData = (owners: AddressBookState): { address: string; name: string }[] => {
|
export type OwnerData = { address: string; name: string }
|
||||||
|
|
||||||
|
export const getOwnerData = (owners: AddressBookState): OwnerData[] => {
|
||||||
return owners.map((owner) => ({
|
return owners.map((owner) => ({
|
||||||
[OWNERS_TABLE_NAME_ID]: owner.name,
|
[OWNERS_TABLE_NAME_ID]: owner.name,
|
||||||
[OWNERS_TABLE_ADDRESS_ID]: owner.address,
|
[OWNERS_TABLE_ADDRESS_ID]: owner.address,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect, ReactElement } from 'react'
|
||||||
import { EthHashInfo } from '@gnosis.pm/safe-react-components'
|
import { EthHashInfo } from '@gnosis.pm/safe-react-components'
|
||||||
import TableCell from '@material-ui/core/TableCell'
|
import TableCell from '@material-ui/core/TableCell'
|
||||||
import TableContainer from '@material-ui/core/TableContainer'
|
import TableContainer from '@material-ui/core/TableContainer'
|
||||||
@ -13,7 +13,7 @@ import { RemoveOwnerModal } from './RemoveOwnerModal'
|
|||||||
import { ReplaceOwnerModal } from './ReplaceOwnerModal'
|
import { ReplaceOwnerModal } from './ReplaceOwnerModal'
|
||||||
import RenameOwnerIcon from './assets/icons/rename-owner.svg'
|
import RenameOwnerIcon from './assets/icons/rename-owner.svg'
|
||||||
import ReplaceOwnerIcon from './assets/icons/replace-owner.svg'
|
import ReplaceOwnerIcon from './assets/icons/replace-owner.svg'
|
||||||
import { OWNERS_TABLE_ADDRESS_ID, generateColumns, getOwnerData } from './dataFetcher'
|
import { OWNERS_TABLE_ADDRESS_ID, generateColumns, getOwnerData, OwnerData } from './dataFetcher'
|
||||||
import { useStyles } from './style'
|
import { useStyles } from './style'
|
||||||
|
|
||||||
import { getExplorerInfo } from 'src/config'
|
import { getExplorerInfo } from 'src/config'
|
||||||
@ -41,12 +41,11 @@ type Props = {
|
|||||||
owners: AddressBookState
|
owners: AddressBookState
|
||||||
}
|
}
|
||||||
|
|
||||||
const ManageOwners = ({ granted, owners }: Props): React.ReactElement => {
|
const ManageOwners = ({ granted, owners }: Props): ReactElement => {
|
||||||
const { trackEvent } = useAnalytics()
|
const { trackEvent } = useAnalytics()
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const [selectedOwnerAddress, setSelectedOwnerAddress] = useState('')
|
const [selectedOwner, setSelectedOwner] = useState<OwnerData | undefined>()
|
||||||
const [selectedOwnerName, setSelectedOwnerName] = useState('')
|
|
||||||
const [modalsStatus, setModalStatus] = useState({
|
const [modalsStatus, setModalStatus] = useState({
|
||||||
showAddOwner: false,
|
showAddOwner: false,
|
||||||
showRemoveOwner: false,
|
showRemoveOwner: false,
|
||||||
@ -54,13 +53,14 @@ const ManageOwners = ({ granted, owners }: Props): React.ReactElement => {
|
|||||||
showEditOwner: false,
|
showEditOwner: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const onShow = (action, row?: any) => () => {
|
const onShow = (action, row?: OwnerData) => () => {
|
||||||
setModalStatus((prevState) => ({
|
setModalStatus((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
[`show${action}`]: !prevState[`show${action}`],
|
[`show${action}`]: !prevState[`show${action}`],
|
||||||
}))
|
}))
|
||||||
setSelectedOwnerAddress(row && row.address)
|
if (row) {
|
||||||
setSelectedOwnerName(row && row.name)
|
setSelectedOwner(row)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onHide = (action) => () => {
|
const onHide = (action) => () => {
|
||||||
@ -68,8 +68,7 @@ const ManageOwners = ({ granted, owners }: Props): React.ReactElement => {
|
|||||||
...prevState,
|
...prevState,
|
||||||
[`show${action}`]: !Boolean(prevState[`show${action}`]),
|
[`show${action}`]: !Boolean(prevState[`show${action}`]),
|
||||||
}))
|
}))
|
||||||
setSelectedOwnerAddress('')
|
setSelectedOwner(undefined)
|
||||||
setSelectedOwnerName('')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -180,24 +179,21 @@ const ManageOwners = ({ granted, owners }: Props): React.ReactElement => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<AddOwnerModal isOpen={modalsStatus.showAddOwner} onClose={onHide('AddOwner')} />
|
<AddOwnerModal isOpen={modalsStatus.showAddOwner} onClose={onHide('AddOwner')} />
|
||||||
|
{selectedOwner && (
|
||||||
|
<>
|
||||||
<RemoveOwnerModal
|
<RemoveOwnerModal
|
||||||
isOpen={modalsStatus.showRemoveOwner}
|
isOpen={modalsStatus.showRemoveOwner}
|
||||||
onClose={onHide('RemoveOwner')}
|
onClose={onHide('RemoveOwner')}
|
||||||
ownerAddress={selectedOwnerAddress}
|
owner={selectedOwner}
|
||||||
ownerName={selectedOwnerName}
|
|
||||||
/>
|
/>
|
||||||
<ReplaceOwnerModal
|
<ReplaceOwnerModal
|
||||||
isOpen={modalsStatus.showReplaceOwner}
|
isOpen={modalsStatus.showReplaceOwner}
|
||||||
onClose={onHide('ReplaceOwner')}
|
onClose={onHide('ReplaceOwner')}
|
||||||
ownerAddress={selectedOwnerAddress}
|
owner={selectedOwner}
|
||||||
ownerName={selectedOwnerName}
|
|
||||||
/>
|
|
||||||
<EditOwnerModal
|
|
||||||
isOpen={modalsStatus.showEditOwner}
|
|
||||||
onClose={onHide('EditOwner')}
|
|
||||||
ownerAddress={selectedOwnerAddress}
|
|
||||||
selectedOwnerName={selectedOwnerName}
|
|
||||||
/>
|
/>
|
||||||
|
<EditOwnerModal isOpen={modalsStatus.showEditOwner} onClose={onHide('EditOwner')} owner={selectedOwner} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
20
src/utils/__tests__/checksumAddress.test.ts
Normal file
20
src/utils/__tests__/checksumAddress.test.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { checksumAddress, isChecksumAddress } from '../checksumAddress'
|
||||||
|
|
||||||
|
describe('checksumAddress', () => {
|
||||||
|
it('Returns a checksummed address', () => {
|
||||||
|
const address = '0xbaddad0000000000000000000000000000000001'
|
||||||
|
const checksummedAddress = checksumAddress(address)
|
||||||
|
|
||||||
|
expect(checksummedAddress).toBe('0xbAddaD0000000000000000000000000000000001')
|
||||||
|
expect(isChecksumAddress(checksummedAddress)).toBeTruthy()
|
||||||
|
})
|
||||||
|
it('Throws if an invalid address was provided', () => {
|
||||||
|
const address = '0xbaddad'
|
||||||
|
|
||||||
|
try {
|
||||||
|
checksumAddress(address)
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('Given address "0xbaddad" is not a valid Ethereum address.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
19
src/utils/__tests__/isValidAddress.test.ts
Normal file
19
src/utils/__tests__/isValidAddress.test.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { isValidAddress } from '../isValidAddress'
|
||||||
|
|
||||||
|
describe('isValidAddress', () => {
|
||||||
|
it('Returns false for an empty string', () => {
|
||||||
|
expect(isValidAddress('')).toBeFalsy()
|
||||||
|
})
|
||||||
|
it('Returns false when address is `undefined`', () => {
|
||||||
|
expect(isValidAddress(undefined)).toBeFalsy()
|
||||||
|
})
|
||||||
|
it('Returns false for `0x123`', () => {
|
||||||
|
expect(isValidAddress('0x123')).toBeFalsy()
|
||||||
|
})
|
||||||
|
it('Returns false for a valid address without `0x` prefix', () => {
|
||||||
|
expect(isValidAddress('0000000000000000000000000000000000000001')).toBeFalsy()
|
||||||
|
})
|
||||||
|
it('Returns true for a valid address with `0x` prefix', () => {
|
||||||
|
expect(isValidAddress('0x0000000000000000000000000000000000000001')).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
@ -1,5 +1,11 @@
|
|||||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
import { checkAddressChecksum, toChecksumAddress } from 'web3-utils'
|
||||||
|
|
||||||
export const checksumAddress = (address: string): string => {
|
export const checksumAddress = (address: string): string => toChecksumAddress(address)
|
||||||
return getWeb3().utils.toChecksumAddress(address)
|
|
||||||
|
export const isChecksumAddress = (address?: string): boolean => {
|
||||||
|
if (address) {
|
||||||
|
return checkAddressChecksum(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
11
src/utils/isValidAddress.ts
Normal file
11
src/utils/isValidAddress.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { isAddress, isHexStrict } from 'web3-utils'
|
||||||
|
|
||||||
|
export const isValidAddress = (address?: string): boolean => {
|
||||||
|
if (address) {
|
||||||
|
// `isAddress` do not require the string to start with `0x`
|
||||||
|
// `isHexStrict` ensures the address to start with `0x` aside from being a valid hex string
|
||||||
|
return isHexStrict(address) && isAddress(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user