v1.7.3 (#611)
* Fix #597: USD value not load (#609) * Converts all the addresses to checksum values * Fix for empty address * fix the order of transactions, change updateSafe to upgradeSafe to avoid naming confusion with the updateSafe action (#610) * Fix #596: Notification when safe is already updated (#599) * Fix notification of update if the safe is already updated * Makes the notification clickable Displays the notification for owners only * Identify upgrade tx * Add red badge to Settings tab * Fixs Padding Removes the red dot if the user is not an owner Co-authored-by: Fernando <fernando.greco@gmail.com> Co-authored-by: Fernando <fernando.greco@gmail.com> Co-authored-by: Agustin Pane <agustin.pane@gmail.com>
This commit is contained in:
parent
5bd173bbca
commit
8f0f28b7ba
|
@ -238,7 +238,7 @@ export const NOTIFICATIONS: Notifications = {
|
||||||
|
|
||||||
// Safe Version
|
// Safe Version
|
||||||
SAFE_NEW_VERSION_AVAILABLE: {
|
SAFE_NEW_VERSION_AVAILABLE: {
|
||||||
message: 'There is a new version available for this Safe',
|
message: 'There is a new version available for this Safe. Update now!',
|
||||||
options: { variant: WARNING, persist: false, preventDuplicate: true },
|
options: { variant: WARNING, persist: false, preventDuplicate: true },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type { MultiSendTransactionInstanceType } from '~/logic/contracts/safeCon
|
||||||
import { DELEGATE_CALL } from '~/logic/safe/transactions'
|
import { DELEGATE_CALL } from '~/logic/safe/transactions'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
|
||||||
export const upgradeSafeToLastVersion = async (safeAddress: string, createTransaction: Function) => {
|
export const upgradeSafeToLatestVersion = async (safeAddress: string, createTransaction: Function) => {
|
||||||
const sendTransactions = async (txs: Array<MultiSendTransactionInstanceType>) => {
|
const sendTransactions = async (txs: Array<MultiSendTransactionInstanceType>) => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const encodeMultiSendCallData = getEncodedMultiSendCallData(txs, web3)
|
const encodeMultiSendCallData = getEncodedMultiSendCallData(txs, web3)
|
||||||
|
@ -35,13 +35,13 @@ export const upgradeSafeToLastVersion = async (safeAddress: string, createTransa
|
||||||
operation: 0,
|
operation: 0,
|
||||||
to: safeAddress,
|
to: safeAddress,
|
||||||
value: 0,
|
value: 0,
|
||||||
data: fallbackHandlerTxData,
|
data: updateSafeTxData,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
operation: 0,
|
operation: 0,
|
||||||
to: safeAddress,
|
to: safeAddress,
|
||||||
value: 0,
|
value: 0,
|
||||||
data: updateSafeTxData,
|
data: fallbackHandlerTxData,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
return sendTransactions(txs)
|
return sendTransactions(txs)
|
|
@ -53,3 +53,11 @@ export const isAddressAToken = async (tokenAddress: string) => {
|
||||||
|
|
||||||
export const isTokenTransfer = (data: string, value: number): boolean =>
|
export const isTokenTransfer = (data: string, value: number): boolean =>
|
||||||
!!data && data.substring(0, 10) === '0xa9059cbb' && value === 0
|
!!data && data.substring(0, 10) === '0xa9059cbb' && value === 0
|
||||||
|
|
||||||
|
export const isMultisendTransaction = (data: string, value: number): boolean =>
|
||||||
|
!!data && data.substring(0, 10) === '0x8d80ff0a' && value === 0
|
||||||
|
|
||||||
|
// f08a0323 - setFallbackHandler (308, 8)
|
||||||
|
// 7de7edef - changeMasterCopy (550, 8)
|
||||||
|
export const isUpgradeTransaction = (data: string) =>
|
||||||
|
!!data && data.substr(308, 8) === 'f08a0323' && data.substr(550, 8) === '7de7edef'
|
||||||
|
|
|
@ -4,6 +4,7 @@ import classNames from 'classnames/bind'
|
||||||
import { Switch, Redirect, Route, withRouter } from 'react-router-dom'
|
import { Switch, Redirect, Route, withRouter } from 'react-router-dom'
|
||||||
import Tabs from '@material-ui/core/Tabs'
|
import Tabs from '@material-ui/core/Tabs'
|
||||||
import Tab from '@material-ui/core/Tab'
|
import Tab from '@material-ui/core/Tab'
|
||||||
|
import Badge from '@material-ui/core/Badge'
|
||||||
import CallMade from '@material-ui/icons/CallMade'
|
import CallMade from '@material-ui/icons/CallMade'
|
||||||
import CallReceived from '@material-ui/icons/CallReceived'
|
import CallReceived from '@material-ui/icons/CallReceived'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
@ -35,6 +36,7 @@ import { AddressBookIcon } from './assets/AddressBookIcon'
|
||||||
import { TransactionsIcon } from './assets/TransactionsIcon'
|
import { TransactionsIcon } from './assets/TransactionsIcon'
|
||||||
import { BalancesIcon } from './assets/BalancesIcon'
|
import { BalancesIcon } from './assets/BalancesIcon'
|
||||||
import { AppsIcon } from './assets/AppsIcon'
|
import { AppsIcon } from './assets/AppsIcon'
|
||||||
|
import { getSafeVersion } from '~/logic/safe/utils/safeVersion'
|
||||||
|
|
||||||
export const BALANCES_TAB_BTN_TEST_ID = 'balances-tab-btn'
|
export const BALANCES_TAB_BTN_TEST_ID = 'balances-tab-btn'
|
||||||
export const SETTINGS_TAB_BTN_TEST_ID = 'settings-tab-btn'
|
export const SETTINGS_TAB_BTN_TEST_ID = 'settings-tab-btn'
|
||||||
|
@ -102,6 +104,23 @@ const Layout = (props: Props) => {
|
||||||
onClose: null,
|
onClose: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [needUpdate, setNeedUpdate] = useState(false)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const checkUpdateRequirement = async () => {
|
||||||
|
let safeVersion = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
safeVersion = await getSafeVersion(safe.address)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('failed to check version', e)
|
||||||
|
}
|
||||||
|
setNeedUpdate(safeVersion.needUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkUpdateRequirement()
|
||||||
|
}, [safe && safe.address])
|
||||||
|
|
||||||
const handleCallToRouter = (_, value) => {
|
const handleCallToRouter = (_, value) => {
|
||||||
const { history } = props
|
const { history } = props
|
||||||
|
|
||||||
|
@ -147,11 +166,19 @@ const Layout = (props: Props) => {
|
||||||
Apps
|
Apps
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const labelSettings = (
|
const labelSettings = (
|
||||||
<>
|
<>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
Settings
|
<Badge
|
||||||
|
badgeContent=""
|
||||||
|
variant="dot"
|
||||||
|
invisible={!needUpdate || !granted}
|
||||||
|
color="error"
|
||||||
|
style={{ paddingRight: '10px' }}
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</Badge>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
const labelBalances = (
|
const labelBalances = (
|
||||||
|
|
|
@ -10,7 +10,7 @@ import GnoForm from '~/components/forms/GnoForm'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
import { upgradeSafeToLastVersion } from '~/logic/safe/utils/updateSafe'
|
import { upgradeSafeToLatestVersion } from '~/logic/safe/utils/upgradeSafe'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: Function,
|
onClose: Function,
|
||||||
|
@ -22,7 +22,7 @@ type Props = {
|
||||||
const UpdateSafeModal = ({ onClose, classes, safeAddress, createTransaction }: Props) => {
|
const UpdateSafeModal = ({ onClose, classes, safeAddress, createTransaction }: Props) => {
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
// Call the update safe method
|
// Call the update safe method
|
||||||
await upgradeSafeToLastVersion(safeAddress, createTransaction)
|
await upgradeSafeToLatestVersion(safeAddress, createTransaction)
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
import Badge from '@material-ui/core/Badge'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import { OwnersIcon } from './assets/icons/OwnersIcon'
|
import { OwnersIcon } from './assets/icons/OwnersIcon'
|
||||||
|
@ -25,12 +26,14 @@ import { styles } from './style'
|
||||||
import RemoveSafeIcon from './assets/icons/bin.svg'
|
import RemoveSafeIcon from './assets/icons/bin.svg'
|
||||||
import type { Safe } from '~/routes/safe/store/models/safe'
|
import type { Safe } from '~/routes/safe/store/models/safe'
|
||||||
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
|
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
|
||||||
|
import { getSafeVersion } from '~/logic/safe/utils/safeVersion'
|
||||||
|
|
||||||
export const OWNERS_SETTINGS_TAB_TEST_ID = 'owner-settings-tab'
|
export const OWNERS_SETTINGS_TAB_TEST_ID = 'owner-settings-tab'
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
showRemoveSafe: boolean,
|
showRemoveSafe: boolean,
|
||||||
menuOptionIndex: number,
|
menuOptionIndex: number,
|
||||||
|
needUpdate: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = Actions & {
|
type Props = Actions & {
|
||||||
|
@ -63,9 +66,25 @@ class Settings extends React.Component<Props, State> {
|
||||||
this.state = {
|
this.state = {
|
||||||
showRemoveSafe: false,
|
showRemoveSafe: false,
|
||||||
menuOptionIndex: 1,
|
menuOptionIndex: 1,
|
||||||
|
needUpdate: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
const checkUpdateRequirement = async () => {
|
||||||
|
let safeVersion = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
safeVersion = await getSafeVersion(this.props.safe.address)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('failed to check version', e)
|
||||||
|
}
|
||||||
|
this.setState({ needUpdate: safeVersion.needUpdate })
|
||||||
|
}
|
||||||
|
|
||||||
|
checkUpdateRequirement()
|
||||||
|
}
|
||||||
|
|
||||||
handleChange = menuOptionIndex => () => {
|
handleChange = menuOptionIndex => () => {
|
||||||
this.setState({ menuOptionIndex })
|
this.setState({ menuOptionIndex })
|
||||||
}
|
}
|
||||||
|
@ -124,7 +143,9 @@ class Settings extends React.Component<Props, State> {
|
||||||
onClick={this.handleChange(1)}
|
onClick={this.handleChange(1)}
|
||||||
>
|
>
|
||||||
<SafeDetailsIcon />
|
<SafeDetailsIcon />
|
||||||
Safe details
|
<Badge badgeContent=" " variant="dot" invisible={!this.state.needUpdate || !granted} color="error" style={{paddingRight: '10px'}}>
|
||||||
|
Safe details
|
||||||
|
</Badge>
|
||||||
</Row>
|
</Row>
|
||||||
<Hairline className={classes.hairline} />
|
<Hairline className={classes.hairline} />
|
||||||
<Row
|
<Row
|
||||||
|
|
|
@ -171,6 +171,7 @@ const TxDescription = ({ tx, classes }: Props) => {
|
||||||
cancellationTx,
|
cancellationTx,
|
||||||
customTx,
|
customTx,
|
||||||
creationTx,
|
creationTx,
|
||||||
|
upgradeTx,
|
||||||
data,
|
data,
|
||||||
} = getTxData(tx)
|
} = getTxData(tx)
|
||||||
const amount = getTxAmount(tx)
|
const amount = getTxAmount(tx)
|
||||||
|
@ -179,8 +180,11 @@ const TxDescription = ({ tx, classes }: Props) => {
|
||||||
{modifySettingsTx && (
|
{modifySettingsTx && (
|
||||||
<SettingsDescription removedOwner={removedOwner} newThreshold={newThreshold} addedOwner={addedOwner} />
|
<SettingsDescription removedOwner={removedOwner} newThreshold={newThreshold} addedOwner={addedOwner} />
|
||||||
)}
|
)}
|
||||||
{customTx && <CustomDescription data={data} amount={amount} recipient={recipient} classes={classes} />}
|
{!upgradeTx && customTx && (
|
||||||
{!cancellationTx && !modifySettingsTx && !customTx && !creationTx && (
|
<CustomDescription data={data} amount={amount} recipient={recipient} classes={classes} />
|
||||||
|
)}
|
||||||
|
{upgradeTx && <div>{data}</div>}
|
||||||
|
{!cancellationTx && !modifySettingsTx && !customTx && !creationTx && !upgradeTx && (
|
||||||
<TransferDescription amount={amount} recipient={recipient} />
|
<TransferDescription amount={amount} recipient={recipient} />
|
||||||
)}
|
)}
|
||||||
</Block>
|
</Block>
|
||||||
|
|
|
@ -15,6 +15,16 @@ type DecodedTxData = {
|
||||||
data: string,
|
data: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSafeVersion = (data: string) => {
|
||||||
|
const contractAddress = data.substr(582, 40).toLowerCase()
|
||||||
|
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
'34cfac646f301356faa8b21e94227e3583fe3f5f': '1.1.1',
|
||||||
|
}[contractAddress] || 'X.x.x'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const getTxData = (tx: Transaction): DecodedTxData => {
|
export const getTxData = (tx: Transaction): DecodedTxData => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const { toBN, fromWei } = web3.utils
|
const { toBN, fromWei } = web3.utils
|
||||||
|
@ -57,6 +67,9 @@ export const getTxData = (tx: Transaction): DecodedTxData => {
|
||||||
txData.cancellationTx = true
|
txData.cancellationTx = true
|
||||||
} else if (tx.creationTx) {
|
} else if (tx.creationTx) {
|
||||||
txData.creationTx = true
|
txData.creationTx = true
|
||||||
|
} else if (tx.upgradeTx) {
|
||||||
|
txData.upgradeTx = true
|
||||||
|
txData.data = `The contract of this Safe is upgraded to Version ${getSafeVersion(tx.data)}`
|
||||||
} else {
|
} else {
|
||||||
txData.recipient = tx.recipient
|
txData.recipient = tx.recipient
|
||||||
txData.value = 0
|
txData.value = 0
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Paragraph from '~/components/layout/Paragraph/'
|
import Paragraph from '~/components/layout/Paragraph/'
|
||||||
import Img from '~/components/layout/Img'
|
import Img from '~/components/layout/Img'
|
||||||
|
@ -12,7 +12,6 @@ import SettingsTxIcon from './assets/settings.svg'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
classes: Object,
|
|
||||||
txType: TransactionType,
|
txType: TransactionType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +22,7 @@ const typeToIcon = {
|
||||||
settings: SettingsTxIcon,
|
settings: SettingsTxIcon,
|
||||||
creation: SettingsTxIcon,
|
creation: SettingsTxIcon,
|
||||||
cancellation: SettingsTxIcon,
|
cancellation: SettingsTxIcon,
|
||||||
|
upgrade: SettingsTxIcon,
|
||||||
}
|
}
|
||||||
|
|
||||||
const typeToLabel = {
|
const typeToLabel = {
|
||||||
|
@ -32,15 +32,22 @@ const typeToLabel = {
|
||||||
settings: 'Modify settings',
|
settings: 'Modify settings',
|
||||||
creation: 'Safe created',
|
creation: 'Safe created',
|
||||||
cancellation: 'Cancellation transaction',
|
cancellation: 'Cancellation transaction',
|
||||||
|
upgrade: 'Contract Upgrade',
|
||||||
}
|
}
|
||||||
|
|
||||||
const TxType = ({ classes, txType }: Props) => (
|
const useStyles = makeStyles(styles)
|
||||||
<Block className={classes.container}>
|
|
||||||
<Img src={typeToIcon[txType]} alt={typeToLabel[txType]} className={classes.img} />
|
|
||||||
<Paragraph className={classes.type} noMargin>
|
|
||||||
{typeToLabel[txType]}
|
|
||||||
</Paragraph>
|
|
||||||
</Block>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default withStyles(styles)(TxType)
|
const TxType = ({ txType }: Props) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Block className={classes.container}>
|
||||||
|
<Img src={typeToIcon[txType]} alt={typeToLabel[txType]} className={classes.img} />
|
||||||
|
<Paragraph className={classes.type} noMargin>
|
||||||
|
{typeToLabel[txType]}
|
||||||
|
</Paragraph>
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TxType
|
||||||
|
|
|
@ -76,6 +76,8 @@ const getTransactionTableData = (tx: Transaction, cancelTx: ?Transaction): Trans
|
||||||
txType = 'custom'
|
txType = 'custom'
|
||||||
} else if (tx.creationTx) {
|
} else if (tx.creationTx) {
|
||||||
txType = 'creation'
|
txType = 'creation'
|
||||||
|
} else if (tx.upgradeTx) {
|
||||||
|
txType = 'upgrade'
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type { SafeProps } from '~/routes/safe/store/models/safe'
|
||||||
import addSafe from '~/routes/safe/store/actions/addSafe'
|
import addSafe from '~/routes/safe/store/actions/addSafe'
|
||||||
import { getSafeName, getLocalSafe } from '~/logic/safe/utils'
|
import { getSafeName, getLocalSafe } from '~/logic/safe/utils'
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||||
import { getBalanceInEtherOf } from '~/logic/wallets/getWeb3'
|
import { getBalanceInEtherOf, getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||||
import removeSafeOwner from '~/routes/safe/store/actions/removeSafeOwner'
|
import removeSafeOwner from '~/routes/safe/store/actions/removeSafeOwner'
|
||||||
import addSafeOwner from '~/routes/safe/store/actions/addSafeOwner'
|
import addSafeOwner from '~/routes/safe/store/actions/addSafeOwner'
|
||||||
|
@ -33,7 +33,8 @@ const buildOwnersFrom = (
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
export const buildSafe = async (safeAddress: string, safeName: string) => {
|
export const buildSafe = async (safeAdd: string, safeName: string) => {
|
||||||
|
const safeAddress = getWeb3().utils.toChecksumAddress(safeAdd)
|
||||||
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
const ethBalance = await getBalanceInEtherOf(safeAddress)
|
const ethBalance = await getBalanceInEtherOf(safeAddress)
|
||||||
|
|
||||||
|
@ -53,7 +54,8 @@ export const buildSafe = async (safeAddress: string, safeName: string) => {
|
||||||
return safe
|
return safe
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkAndUpdateSafe = (safeAddress: string) => async (dispatch: ReduxDispatch<*>) => {
|
export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: ReduxDispatch<*>) => {
|
||||||
|
const safeAddress = getWeb3().utils.toChecksumAddress(safeAdd)
|
||||||
// Check if the owner's safe did change and update them
|
// Check if the owner's safe did change and update them
|
||||||
const [gnosisSafe, localSafe] = await Promise.all([getGnosisSafeInstanceAt(safeAddress), getLocalSafe(safeAddress)])
|
const [gnosisSafe, localSafe] = await Promise.all([getGnosisSafeInstanceAt(safeAddress), getLocalSafe(safeAddress)])
|
||||||
|
|
||||||
|
@ -90,8 +92,9 @@ export const checkAndUpdateSafe = (safeAddress: string) => async (dispatch: Redu
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
export default (safeAdd: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||||
try {
|
try {
|
||||||
|
const safeAddress = getWeb3().utils.toChecksumAddress(safeAdd)
|
||||||
const safeName = (await getSafeName(safeAddress)) || 'LOADED SAFE'
|
const safeName = (await getSafeName(safeAddress)) || 'LOADED SAFE'
|
||||||
const safeProps: SafeProps = await buildSafe(safeAddress, safeName)
|
const safeProps: SafeProps = await buildSafe(safeAddress, safeName)
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { getLocalSafe } from '~/logic/safe/utils'
|
||||||
import { addTransactions } from './addTransactions'
|
import { addTransactions } from './addTransactions'
|
||||||
import { addIncomingTransactions } from './addIncomingTransactions'
|
import { addIncomingTransactions } from './addIncomingTransactions'
|
||||||
import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
|
import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
|
||||||
import { isTokenTransfer } from '~/logic/tokens/utils/tokenHelpers'
|
import { isMultisendTransaction, isTokenTransfer, isUpgradeTransaction } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds'
|
import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds'
|
||||||
import { ALTERNATIVE_TOKEN_ABI } from '~/logic/tokens/utils/alternativeAbi'
|
import { ALTERNATIVE_TOKEN_ABI } from '~/logic/tokens/utils/alternativeAbi'
|
||||||
import type { TransactionProps } from '~/routes/safe/store/models/transaction'
|
import type { TransactionProps } from '~/routes/safe/store/models/transaction'
|
||||||
|
@ -89,7 +89,9 @@ export const buildTransactionFrom = async (safeAddress: string, tx: TxServiceMod
|
||||||
const modifySettingsTx = sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 && !!tx.data
|
const modifySettingsTx = sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 && !!tx.data
|
||||||
const cancellationTx = sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 && !tx.data
|
const cancellationTx = sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 && !tx.data
|
||||||
const isSendTokenTx = isTokenTransfer(tx.data, Number(tx.value))
|
const isSendTokenTx = isTokenTransfer(tx.data, Number(tx.value))
|
||||||
const customTx = !sameAddress(tx.to, safeAddress) && !!tx.data && !isSendTokenTx
|
const isMultiSendTx = isMultisendTransaction(tx.data, Number(tx.value))
|
||||||
|
const customTx = !sameAddress(tx.to, safeAddress) && !!tx.data && !isSendTokenTx && !isMultiSendTx
|
||||||
|
const isUpgradeTx = isMultiSendTx && isUpgradeTransaction(tx.data)
|
||||||
|
|
||||||
let refundParams = null
|
let refundParams = null
|
||||||
if (tx.gasPrice > 0) {
|
if (tx.gasPrice > 0) {
|
||||||
|
@ -165,6 +167,8 @@ export const buildTransactionFrom = async (safeAddress: string, tx: TxServiceMod
|
||||||
executionTxHash: tx.transactionHash,
|
executionTxHash: tx.transactionHash,
|
||||||
safeTxHash: tx.safeTxHash,
|
safeTxHash: tx.safeTxHash,
|
||||||
isTokenTransfer: isSendTokenTx,
|
isTokenTransfer: isSendTokenTx,
|
||||||
|
multiSendTx: isMultiSendTx,
|
||||||
|
upgradeTx: isUpgradeTx,
|
||||||
decodedParams,
|
decodedParams,
|
||||||
modifySettingsTx,
|
modifySettingsTx,
|
||||||
customTx,
|
customTx,
|
||||||
|
|
|
@ -3,12 +3,14 @@ import type { Dispatch as ReduxDispatch } from 'redux'
|
||||||
import { type GlobalState } from '~/store/index'
|
import { type GlobalState } from '~/store/index'
|
||||||
import { getDefaultSafe } from '~/logic/safe/utils'
|
import { getDefaultSafe } from '~/logic/safe/utils'
|
||||||
import setDefaultSafe from './setDefaultSafe'
|
import setDefaultSafe from './setDefaultSafe'
|
||||||
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
|
||||||
const loadDefaultSafe = () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
const loadDefaultSafe = () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||||
try {
|
try {
|
||||||
const defaultSafe: string = await getDefaultSafe()
|
const defaultSafe: string = await getDefaultSafe()
|
||||||
|
const checksumed =
|
||||||
dispatch(setDefaultSafe(defaultSafe))
|
defaultSafe && defaultSafe.length > 0 ? getWeb3().utils.toChecksumAddress(defaultSafe) : defaultSafe
|
||||||
|
dispatch(setDefaultSafe(checksumed))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.error('Error while getting default Safe from storage:', err)
|
console.error('Error while getting default Safe from storage:', err)
|
||||||
|
|
|
@ -12,10 +12,11 @@ import { enhanceSnackbarForAction, NOTIFICATIONS } from '~/logic/notifications'
|
||||||
import closeSnackbarAction from '~/logic/notifications/store/actions/closeSnackbar'
|
import closeSnackbarAction from '~/logic/notifications/store/actions/closeSnackbar'
|
||||||
import { getIncomingTxAmount } from '~/routes/safe/components/Transactions/TxsTable/columns'
|
import { getIncomingTxAmount } from '~/routes/safe/components/Transactions/TxsTable/columns'
|
||||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||||
import { safesMapSelector } from '~/routes/safe/store/selectors'
|
import { safeParamAddressFromStateSelector, safesMapSelector } from '~/routes/safe/store/selectors'
|
||||||
import { isUserOwner } from '~/logic/wallets/ethAddresses'
|
import { isUserOwner } from '~/logic/wallets/ethAddresses'
|
||||||
import { ADD_SAFE } from '~/routes/safe/store/actions/addSafe'
|
import { ADD_SAFE } from '~/routes/safe/store/actions/addSafe'
|
||||||
import { getSafeVersion } from '~/logic/safe/utils/safeVersion'
|
import { getSafeVersion } from '~/logic/safe/utils/safeVersion'
|
||||||
|
import { grantedSelector } from '~/routes/safe/container/selector'
|
||||||
|
|
||||||
const watchedActions = [ADD_TRANSACTIONS, ADD_INCOMING_TRANSACTIONS, ADD_SAFE]
|
const watchedActions = [ADD_TRANSACTIONS, ADD_INCOMING_TRANSACTIONS, ADD_SAFE]
|
||||||
|
|
||||||
|
@ -106,12 +107,27 @@ const notificationsMiddleware = (store: Store<GlobalState>) => (next: Function)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case ADD_SAFE: {
|
case ADD_SAFE: {
|
||||||
const { safe } = action.payload
|
const state: GlobalState = store.getState()
|
||||||
const { needUpdate } = await getSafeVersion(safe.address)
|
const currentSafeAddress = safeParamAddressFromStateSelector(state)
|
||||||
|
const isUserOwner = grantedSelector(state)
|
||||||
|
const { needUpdate } = await getSafeVersion(currentSafeAddress)
|
||||||
|
|
||||||
const notificationKey = `${safe.address}`
|
const notificationKey = `${currentSafeAddress}`
|
||||||
if (needUpdate) {
|
const onNotificationClicked = () => {
|
||||||
dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.SAFE_NEW_VERSION_AVAILABLE, notificationKey)))
|
dispatch(closeSnackbarAction({ key: notificationKey }))
|
||||||
|
dispatch(push(`/safes/${currentSafeAddress}/settings`))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needUpdate && isUserOwner) {
|
||||||
|
dispatch(
|
||||||
|
enqueueSnackbar(
|
||||||
|
enhanceSnackbarForAction(
|
||||||
|
NOTIFICATIONS.SAFE_NEW_VERSION_AVAILABLE,
|
||||||
|
notificationKey,
|
||||||
|
onNotificationClicked,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,8 @@ export type TransactionProps = {
|
||||||
cancellationTx: boolean,
|
cancellationTx: boolean,
|
||||||
customTx: boolean,
|
customTx: boolean,
|
||||||
creationTx: boolean,
|
creationTx: boolean,
|
||||||
|
multiSendTx: boolean,
|
||||||
|
upgradeTx: boolean,
|
||||||
safeTxHash: string,
|
safeTxHash: string,
|
||||||
executor: string,
|
executor: string,
|
||||||
executionTxHash?: ?string,
|
executionTxHash?: ?string,
|
||||||
|
@ -74,6 +76,8 @@ export const makeTransaction: RecordFactory<TransactionProps> = Record({
|
||||||
cancellationTx: false,
|
cancellationTx: false,
|
||||||
customTx: false,
|
customTx: false,
|
||||||
creationTx: false,
|
creationTx: false,
|
||||||
|
multiSendTx: false,
|
||||||
|
upgradeTx: false,
|
||||||
status: 'awaiting',
|
status: 'awaiting',
|
||||||
decimals: 18,
|
decimals: 18,
|
||||||
isTokenTransfer: false,
|
isTokenTransfer: false,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { REPLACE_SAFE_OWNER } from '~/routes/safe/store/actions/replaceSafeOwner
|
||||||
import { EDIT_SAFE_OWNER } from '~/routes/safe/store/actions/editSafeOwner'
|
import { EDIT_SAFE_OWNER } from '~/routes/safe/store/actions/editSafeOwner'
|
||||||
import { SET_DEFAULT_SAFE } from '~/routes/safe/store/actions/setDefaultSafe'
|
import { SET_DEFAULT_SAFE } from '~/routes/safe/store/actions/setDefaultSafe'
|
||||||
import { UPDATE_SAFE_THRESHOLD } from '~/routes/safe/store/actions/updateSafeThreshold'
|
import { UPDATE_SAFE_THRESHOLD } from '~/routes/safe/store/actions/updateSafeThreshold'
|
||||||
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
|
||||||
export const SAFE_REDUCER_ID = 'safes'
|
export const SAFE_REDUCER_ID = 'safes'
|
||||||
|
|
||||||
|
@ -20,7 +21,10 @@ export type SafeReducerState = Map<string, *>
|
||||||
|
|
||||||
export const buildSafe = (storedSafe: SafeProps) => {
|
export const buildSafe = (storedSafe: SafeProps) => {
|
||||||
const names = storedSafe.owners.map((owner: OwnerProps) => owner.name)
|
const names = storedSafe.owners.map((owner: OwnerProps) => owner.name)
|
||||||
const addresses = storedSafe.owners.map((owner: OwnerProps) => owner.address)
|
const addresses = storedSafe.owners.map((owner: OwnerProps) => {
|
||||||
|
const checksumed = getWeb3().utils.toChecksumAddress(owner.address)
|
||||||
|
return checksumed
|
||||||
|
})
|
||||||
const owners = buildOwnersFrom(Array.from(names), Array.from(addresses))
|
const owners = buildOwnersFrom(Array.from(names), Array.from(addresses))
|
||||||
const activeTokens = Set(storedSafe.activeTokens)
|
const activeTokens = Set(storedSafe.activeTokens)
|
||||||
const blacklistedTokens = Set(storedSafe.blacklistedTokens)
|
const blacklistedTokens = Set(storedSafe.blacklistedTokens)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
||||||
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
||||||
import type { IncomingTransaction } from '~/routes/safe/store/models/incomingTransaction'
|
import type { IncomingTransaction } from '~/routes/safe/store/models/incomingTransaction'
|
||||||
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
|
||||||
export type RouterProps = {
|
export type RouterProps = {
|
||||||
match: Match,
|
match: Match,
|
||||||
|
@ -60,8 +61,10 @@ const incomingTransactionsSelector = (state: GlobalState): IncomingTransactionsS
|
||||||
|
|
||||||
const oneTransactionSelector = (state: GlobalState, props: TransactionProps) => props.transaction
|
const oneTransactionSelector = (state: GlobalState, props: TransactionProps) => props.transaction
|
||||||
|
|
||||||
export const safeParamAddressSelector = (state: GlobalState, props: RouterProps) =>
|
export const safeParamAddressSelector = (state: GlobalState, props: RouterProps) => {
|
||||||
props.match.params[SAFE_PARAM_ADDRESS] || ''
|
const urlAdd = props.match.params[SAFE_PARAM_ADDRESS]
|
||||||
|
return urlAdd ? getWeb3().utils.toChecksumAddress(urlAdd) : ''
|
||||||
|
}
|
||||||
|
|
||||||
type TxSelectorType = Selector<GlobalState, RouterProps, List<Transaction>>
|
type TxSelectorType = Selector<GlobalState, RouterProps, List<Transaction>>
|
||||||
|
|
||||||
|
@ -156,8 +159,8 @@ export const safeSelector: Selector<GlobalState, RouterProps, SafeSelectorProps>
|
||||||
if (!address) {
|
if (!address) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
const checksumed = getWeb3().utils.toChecksumAddress(address)
|
||||||
const safe = safes.get(address)
|
const safe = safes.get(checksumed)
|
||||||
|
|
||||||
return safe
|
return safe
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,16 +21,16 @@ describe('Upgrade a Safe', () => {
|
||||||
operation: 0,
|
operation: 0,
|
||||||
to: safeAddress,
|
to: safeAddress,
|
||||||
value: 0,
|
value: 0,
|
||||||
data: fallbackHandlerTxData,
|
data: updateSafeTxData,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
operation: 0,
|
operation: 0,
|
||||||
to: safeAddress,
|
to: safeAddress,
|
||||||
value: 0,
|
value: 0,
|
||||||
data: updateSafeTxData,
|
data: fallbackHandlerTxData,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const expectedEncodedData = '0x8d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f08a0323000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf44000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247de7edef00000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f0000000000000000000000000000'
|
const expectedEncodedData = '0x8d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247de7edef00000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f08a0323000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf440000000000000000000000000000'
|
||||||
const multiSendTxData = getEncodedMultiSendCallData(txs, web3)
|
const multiSendTxData = getEncodedMultiSendCallData(txs, web3)
|
||||||
expect(multiSendTxData).toEqual(expectedEncodedData)
|
expect(multiSendTxData).toEqual(expectedEncodedData)
|
||||||
})
|
})
|
Loading…
Reference in New Issue