(Feature) Proper rejections (#2096)

* remove `isCancelTransaction` utility function in favor of `txInfo.isCancellation` flag provided by client-gateway

* replace "cancel" concept in favor of "reject"

* add circle-cross-red icon to "On-chain rejection" transaction info

Adjust owner's list text color

* identify queued on-chain rejection

* apply styles to on-chain rejection type identifier

* update awaiting messages wording

* fix styles on styles to on-chain rejection

* replace local svg with SRC `Icon` component wherever is possible
This commit is contained in:
Fernando 2021-04-07 07:40:33 -03:00 committed by GitHub
parent e48891ba2b
commit c96e3192ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 134 additions and 106 deletions

View File

@ -161,7 +161,7 @@
"@gnosis.pm/safe-apps-sdk": "1.0.3", "@gnosis.pm/safe-apps-sdk": "1.0.3",
"@gnosis.pm/safe-apps-sdk-v1": "npm:@gnosis.pm/safe-apps-sdk@0.4.2", "@gnosis.pm/safe-apps-sdk-v1": "npm:@gnosis.pm/safe-apps-sdk@0.4.2",
"@gnosis.pm/safe-contracts": "1.1.1-dev.2", "@gnosis.pm/safe-contracts": "1.1.1-dev.2",
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#a68a67e", "@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#7ebc414",
"@gnosis.pm/util-contracts": "2.0.6", "@gnosis.pm/util-contracts": "2.0.6",
"@ledgerhq/hw-transport-node-hid-singleton": "5.45.0", "@ledgerhq/hw-transport-node-hid-singleton": "5.45.0",
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",

View File

@ -1,5 +1,5 @@
import { Text } from '@gnosis.pm/safe-react-components' import { Text } from '@gnosis.pm/safe-react-components'
import React from 'react' import React, { ReactElement } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
const Wrapper = styled.div` const Wrapper = styled.div`
@ -12,11 +12,9 @@ const Icon = styled.img`
margin-right: 9px; margin-right: 9px;
` `
const CustomIconText = ({ iconUrl, text }: { iconUrl: string; text?: string }) => ( export const CustomIconText = ({ iconUrl, text }: { iconUrl: string; text?: string }): ReactElement => (
<Wrapper> <Wrapper>
<Icon alt={text} src={iconUrl} /> <Icon alt={text} src={iconUrl} />
{text && <Text size="xl">{text}</Text>} {text && <Text size="xl">{text}</Text>}
</Wrapper> </Wrapper>
) )
export default CustomIconText

View File

@ -41,7 +41,7 @@ export const TransactionFailText = ({
if (isExecution) { if (isExecution) {
errorMessage = errorMessage =
threshold && threshold > 1 threshold && threshold > 1
? `To save gas costs, cancel this transaction` ? `To save gas costs, reject this transaction`
: `To save gas costs, avoid executing the transaction.` : `To save gas costs, avoid executing the transaction.`
} }

View File

@ -236,7 +236,7 @@ type MultiSigExecutionDetails = {
type DetailedExecutionInfo = ModuleExecutionDetails | MultiSigExecutionDetails type DetailedExecutionInfo = ModuleExecutionDetails | MultiSigExecutionDetails
type ExpandedTxDetails = { type ExpandedTxDetails = {
executedAt: number executedAt: number | null
txStatus: TransactionStatus txStatus: TransactionStatus
txInfo: TransactionInfo txInfo: TransactionInfo
txData: TransactionData | null txData: TransactionData | null

View File

@ -4,7 +4,7 @@ import CircularProgress from '@material-ui/core/CircularProgress'
import React, { ReactElement, useContext, useRef } from 'react' import React, { ReactElement, useContext, useRef } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import CustomIconText from 'src/components/CustomIconText' import { CustomIconText } from 'src/components/CustomIconText'
import { import {
isCustomTxInfo, isCustomTxInfo,
isMultiSendTxInfo, isMultiSendTxInfo,
@ -24,6 +24,7 @@ import { TokenTransferAmount } from './TokenTransferAmount'
import { TxsInfiniteScrollContext } from './TxsInfiniteScroll' import { TxsInfiniteScrollContext } from './TxsInfiniteScroll'
import { TxLocationContext } from './TxLocationProvider' import { TxLocationContext } from './TxLocationProvider'
import { CalculatedVotes } from './TxQueueCollapsed' import { CalculatedVotes } from './TxQueueCollapsed'
import { isCancelTxDetails } from './utils'
const TxInfo = ({ info }: { info: AssetInfo }) => { const TxInfo = ({ info }: { info: AssetInfo }) => {
if (isTokenTransferAsset(info)) { if (isTokenTransferAsset(info)) {
@ -116,6 +117,8 @@ export const TxCollapsed = ({
const { ref, lastItemId } = useContext(TxsInfiniteScrollContext) const { ref, lastItemId } = useContext(TxsInfiniteScrollContext)
const willBeReplaced = transaction?.txStatus === 'WILL_BE_REPLACED' ? ' will-be-replaced' : '' const willBeReplaced = transaction?.txStatus === 'WILL_BE_REPLACED' ? ' will-be-replaced' : ''
const onChainRejection =
isCancelTxDetails(transaction.txInfo) && txLocation !== 'history' ? ' on-chain-rejection' : ''
const txCollapsedNonce = ( const txCollapsedNonce = (
<div className={'tx-nonce' + willBeReplaced}> <div className={'tx-nonce' + willBeReplaced}>
@ -124,7 +127,7 @@ export const TxCollapsed = ({
) )
const txCollapsedType = ( const txCollapsedType = (
<div className={'tx-type' + willBeReplaced}> <div className={'tx-type' + willBeReplaced + onChainRejection}>
<CustomIconText iconUrl={type.icon} text={type.text} /> <CustomIconText iconUrl={type.icon} text={type.text} />
</div> </div>
) )

View File

@ -46,7 +46,7 @@ export const TxCollapsedActions = ({ transaction }: TxCollapsedActionsProps): Re
</span> </span>
</Tooltip> </Tooltip>
{canCancel && ( {canCancel && (
<Tooltip title="Cancel" placement="top"> <Tooltip title="Reject" placement="top">
<span> <span>
<IconButton size="small" type="button" onClick={handleCancelButtonClick} disabled={isPending}> <IconButton size="small" type="button" onClick={handleCancelButtonClick} disabled={isPending}>
<Icon type="circleCross" color="error" size="sm" /> <Icon type="circleCross" color="error" size="sm" />

View File

@ -1,7 +1,6 @@
import { Icon, Link, Loader, Text } from '@gnosis.pm/safe-react-components' import { Icon, Link, Loader, Text } from '@gnosis.pm/safe-react-components'
import cn from 'classnames' import cn from 'classnames'
import React, { ReactElement, useContext } from 'react' import React, { ReactElement, useContext } from 'react'
import { useSelector } from 'react-redux'
import styled from 'styled-components' import styled from 'styled-components'
import { import {
@ -12,7 +11,6 @@ import {
MultiSigExecutionDetails, MultiSigExecutionDetails,
Transaction, Transaction,
} from 'src/logic/safe/store/models/types/gateway.d' } from 'src/logic/safe/store/models/types/gateway.d'
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import { TransactionActions } from './hooks/useTransactionActions' import { TransactionActions } from './hooks/useTransactionActions'
import { useTransactionDetails } from './hooks/useTransactionDetails' import { useTransactionDetails } from './hooks/useTransactionDetails'
import { TxDetailsContainer, Centered, AlignItemsWithMargin } from './styled' import { TxDetailsContainer, Centered, AlignItemsWithMargin } from './styled'
@ -30,35 +28,45 @@ const NormalBreakingText = styled(Text)`
` `
const TxDataGroup = ({ txDetails }: { txDetails: ExpandedTxDetails }): ReactElement | null => { const TxDataGroup = ({ txDetails }: { txDetails: ExpandedTxDetails }): ReactElement | null => {
const safeAddress = useSelector(safeParamAddressFromStateSelector)
if (isTransferTxInfo(txDetails.txInfo) || isSettingsChangeTxInfo(txDetails.txInfo)) { if (isTransferTxInfo(txDetails.txInfo) || isSettingsChangeTxInfo(txDetails.txInfo)) {
return <TxInfo txInfo={txDetails.txInfo} /> return <TxInfo txInfo={txDetails.txInfo} />
} }
if (isCancelTxDetails({ executedAt: txDetails.executedAt, txInfo: txDetails.txInfo, safeAddress })) { if (isCancelTxDetails(txDetails.txInfo)) {
const txNonce = `${(txDetails.detailedExecutionInfo as MultiSigExecutionDetails).nonce ?? NOT_AVAILABLE}`
const isTxExecuted = txDetails.executedAt
// executed rejection transaction
let message = `This is an on-chain rejection that didn't send any funds.
This on-chain rejection replaced all transactions with nonce ${txNonce}.`
if (!isTxExecuted) {
// queued rejection transaction
message = `This is an on-chain rejection that doesn't send any funds.
Executing this on-chain rejection will replace all currently awaiting transactions with nonce ${txNonce}.`
}
return ( return (
<> <>
<NormalBreakingText size="xl"> <NormalBreakingText size="xl">{message}</NormalBreakingText>
{`This is an empty cancelling transaction that doesn't send any funds. {!isTxExecuted && (
Executing this transaction will replace all currently awaiting transactions with nonce ${ <>
(txDetails.detailedExecutionInfo as MultiSigExecutionDetails).nonce ?? NOT_AVAILABLE <br />
}.`}
</NormalBreakingText>
<Link <Link
href="https://help.gnosis-safe.io/en/articles/4738501-why-do-i-need-to-pay-for-cancelling-a-transaction" href="https://help.gnosis-safe.io/en/articles/4738501-why-do-i-need-to-pay-for-cancelling-a-transaction"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
title="Why do I need to pay for cancelling a transaction?" title="Why do I need to pay for rejecting a transaction?"
> >
<AlignItemsWithMargin> <AlignItemsWithMargin>
<Text size="xl" as="span" color="primary"> <Text size="xl" as="span" color="primary">
Why do I need to pay for cancelling a transaction? Why do I need to pay for rejecting a transaction?
</Text> </Text>
<Icon size="sm" type="externalLink" color="primary" /> <Icon size="sm" type="externalLink" color="primary" />
</AlignItemsWithMargin> </AlignItemsWithMargin>
</Link> </Link>
</> </>
)}
</>
) )
} }
@ -116,7 +124,7 @@ export const TxDetails = ({ transaction, actions }: TxDetailsProps): ReactElemen
'will-be-replaced': transaction.txStatus === 'WILL_BE_REPLACED', 'will-be-replaced': transaction.txStatus === 'WILL_BE_REPLACED',
})} })}
> >
<TxOwners detailedExecutionInfo={data.detailedExecutionInfo} /> <TxOwners txDetails={data} />
</div> </div>
{!data.executedAt && txLocation !== 'history' && actions?.isUserAnOwner && ( {!data.executedAt && txLocation !== 'history' && actions?.isUserAnOwner && (
<div className={cn('tx-details-actions', { 'will-be-replaced': transaction.txStatus === 'WILL_BE_REPLACED' })}> <div className={cn('tx-details-actions', { 'will-be-replaced': transaction.txStatus === 'WILL_BE_REPLACED' })}>

View File

@ -41,7 +41,7 @@ export const TxExpandedActions = ({ transaction }: TxExpandedActionsProps): Reac
</Button> </Button>
{canCancel && ( {canCancel && (
<Button size="md" color="error" onClick={handleCancelButtonClick} className="error" disabled={isPending}> <Button size="md" color="error" onClick={handleCancelButtonClick} className="error" disabled={isPending}>
Cancel Reject
</Button> </Button>
)} )}
</> </>

View File

@ -1,4 +1,4 @@
import { Text } from '@gnosis.pm/safe-react-components' import { Text, Icon } from '@gnosis.pm/safe-react-components'
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -6,32 +6,39 @@ import Img from 'src/components/layout/Img'
import { ExpandedTxDetails, isModuleExecutionDetails } from 'src/logic/safe/store/models/types/gateway.d' import { ExpandedTxDetails, isModuleExecutionDetails } from 'src/logic/safe/store/models/types/gateway.d'
import TransactionListActive from './assets/transactions-list-active.svg' import TransactionListActive from './assets/transactions-list-active.svg'
import TransactionListInactive from './assets/transactions-list-inactive.svg' import TransactionListInactive from './assets/transactions-list-inactive.svg'
import CheckCircleGreen from './assets/check-circle-green.svg'
import PlusCircleGreen from './assets/plus-circle-green.svg'
import { OwnerRow } from './OwnerRow' import { OwnerRow } from './OwnerRow'
import { OwnerList, OwnerListItem } from './styled' import { OwnerList, OwnerListItem } from './styled'
import { isCancelTxDetails } from './utils'
type TxOwnersProps = {
detailedExecutionInfo: ExpandedTxDetails['detailedExecutionInfo']
}
const StyledImg = styled(Img)` const StyledImg = styled(Img)`
background-color: transparent; background-color: transparent;
border-radius: 50%; border-radius: 50%;
` `
export const TxOwners = ({ detailedExecutionInfo }: TxOwnersProps): ReactElement | null => { export const TxOwners = ({ txDetails }: { txDetails: ExpandedTxDetails }): ReactElement | null => {
const { txInfo, detailedExecutionInfo } = txDetails
if (!detailedExecutionInfo || isModuleExecutionDetails(detailedExecutionInfo)) { if (!detailedExecutionInfo || isModuleExecutionDetails(detailedExecutionInfo)) {
return null return null
} }
const confirmationsNeeded = detailedExecutionInfo.confirmationsRequired - detailedExecutionInfo.confirmations.length const confirmationsNeeded = detailedExecutionInfo.confirmationsRequired - detailedExecutionInfo.confirmations.length
return ( const CreationNode = isCancelTxDetails(txInfo) ? (
<OwnerList>
<OwnerListItem> <OwnerListItem>
<span className="icon"> <span className="icon">
<StyledImg alt="" src={PlusCircleGreen} /> <Icon size="sm" type="circleCross" color="error" />
</span>
<div className="legend">
<Text color="error" size="xl" strong>
On-chain rejection created
</Text>
</div>
</OwnerListItem>
) : (
<OwnerListItem>
<span className="icon">
<Icon size="sm" type="add" color="primary" />
</span> </span>
<div className="legend"> <div className="legend">
<Text color="primary" size="xl" strong> <Text color="primary" size="xl" strong>
@ -39,10 +46,15 @@ export const TxOwners = ({ detailedExecutionInfo }: TxOwnersProps): ReactElement
</Text> </Text>
</div> </div>
</OwnerListItem> </OwnerListItem>
)
return (
<OwnerList>
{CreationNode}
{detailedExecutionInfo.confirmations.map(({ signer }) => ( {detailedExecutionInfo.confirmations.map(({ signer }) => (
<OwnerListItem key={signer}> <OwnerListItem key={signer}>
<span className="icon"> <span className="icon">
<StyledImg alt="" src={CheckCircleGreen} /> <Icon size="sm" type="circleCheck" color="primary" />
</span> </span>
<div className="legend"> <div className="legend">
<Text color="primary" size="xl" strong> <Text color="primary" size="xl" strong>
@ -55,7 +67,11 @@ export const TxOwners = ({ detailedExecutionInfo }: TxOwnersProps): ReactElement
{confirmationsNeeded <= 0 ? ( {confirmationsNeeded <= 0 ? (
<OwnerListItem> <OwnerListItem>
<span className="icon"> <span className="icon">
<StyledImg alt="" src={detailedExecutionInfo.executor ? CheckCircleGreen : TransactionListActive} /> {detailedExecutionInfo.executor ? (
<Icon type="circleCheck" size="sm" color="primary" />
) : (
<StyledImg alt="" src={TransactionListActive} />
)}
</span> </span>
<div className="legend"> <div className="legend">
<Text color="primary" size="xl" strong> <Text color="primary" size="xl" strong>

View File

@ -27,7 +27,7 @@ export const TxSummary = ({ txDetails }: { txDetails: ExpandedTxDetails }): Reac
</Text> </Text>
)} )}
</div> </div>
{nonce && ( {nonce !== undefined && (
<div className="tx-nonce"> <div className="tx-nonce">
<Text size="xl" strong as="span"> <Text size="xl" strong as="span">
Nonce:{' '} Nonce:{' '}

View File

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">
<g>
<g>
<path d="M0 0H16V16H0z" transform="translate(-273 -201) translate(273 201)"/>
<path fill="#F02525" fill-rule="nonzero" d="M8 15c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm0-2c-2.761 0-5-2.239-5-5s2.239-5 5-5 5 2.239 5 5-2.239 5-5 5z" transform="translate(-273 -201) translate(273 201)"/>
<path fill="#F02525" d="M9.414 8l1.414 1.414c.391.39.391 1.024 0 1.414-.39.391-1.023.391-1.414 0L8 9.414l-1.414 1.414c-.39.391-1.024.391-1.414 0-.391-.39-.391-1.023 0-1.414L6.586 8 5.172 6.586c-.391-.39-.391-1.024 0-1.414.39-.391 1.023-.391 1.414 0L8 6.586l1.414-1.414c.39-.391 1.024-.391 1.414 0 .391.39.391 1.023 0 1.414L9.414 8z" transform="translate(-273 -201) translate(273 201)"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 936 B

View File

@ -7,7 +7,6 @@ import { getQueuedTransactionsByNonce } from 'src/logic/safe/store/selectors/gat
import { sameAddress } from 'src/logic/wallets/ethAddresses' import { sameAddress } from 'src/logic/wallets/ethAddresses'
import { userAccountSelector } from 'src/logic/wallets/store/selectors' import { userAccountSelector } from 'src/logic/wallets/store/selectors'
import { TxLocationContext } from 'src/routes/safe/components/Transactions/TxList/TxLocationProvider' import { TxLocationContext } from 'src/routes/safe/components/Transactions/TxList/TxLocationProvider'
import { isCancelTransaction } from 'src/routes/safe/components/Transactions/TxList/utils'
import { grantedSelector } from 'src/routes/safe/container/selector' import { grantedSelector } from 'src/routes/safe/container/selector'
import { AppReduxState } from 'src/store' import { AppReduxState } from 'src/store'
@ -60,14 +59,7 @@ export const useTransactionActions = (transaction: Transaction): TransactionActi
canConfirm, canConfirm,
canConfirmThenExecute: txLocation === 'queued.next' && canConfirm && oneToGo, canConfirmThenExecute: txLocation === 'queued.next' && canConfirm && oneToGo,
canExecute: txLocation === 'queued.next' && thresholdReached, canExecute: txLocation === 'queued.next' && thresholdReached,
canCancel: !transactionsByNonce.some( canCancel: !transactionsByNonce.some(({ txInfo }) => isCustomTxInfo(txInfo) && txInfo.isCancellation),
({ txInfo }) =>
isCustomTxInfo(txInfo) &&
isCancelTransaction({
txInfo,
safeAddress,
}),
),
isUserAnOwner, isUserAnOwner,
oneToGo, oneToGo,
}) })

View File

@ -37,10 +37,10 @@ export const useTransactionStatus = (transaction: Transaction): TransactionStatu
switch (transaction.txStatus) { switch (transaction.txStatus) {
case 'AWAITING_CONFIRMATIONS': case 'AWAITING_CONFIRMATIONS':
text = signaturePending(currentUser) ? 'Awaiting your confirmation' : 'Awaiting confirmations' text = signaturePending(currentUser) ? 'Needs your confirmation' : 'Needs confirmations'
break break
case 'AWAITING_EXECUTION': case 'AWAITING_EXECUTION':
text = 'Awaiting execution' text = 'Needs execution'
break break
case 'PENDING': case 'PENDING':
case 'PENDING_FAILED': case 'PENDING_FAILED':

View File

@ -4,10 +4,10 @@ import { useSelector } from 'react-redux'
import { Transaction } from 'src/logic/safe/store/models/types/gateway.d' import { Transaction } from 'src/logic/safe/store/models/types/gateway.d'
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import CustomTxIcon from 'src/routes/safe/components/Transactions/TxList/assets/custom.svg' import CustomTxIcon from 'src/routes/safe/components/Transactions/TxList/assets/custom.svg'
import CircleCrossRed from 'src/routes/safe/components/Transactions/TxList/assets/circle-cross-red.svg'
import IncomingTxIcon from 'src/routes/safe/components/Transactions/TxList/assets/incoming.svg' import IncomingTxIcon from 'src/routes/safe/components/Transactions/TxList/assets/incoming.svg'
import OutgoingTxIcon from 'src/routes/safe/components/Transactions/TxList/assets/outgoing.svg' import OutgoingTxIcon from 'src/routes/safe/components/Transactions/TxList/assets/outgoing.svg'
import SettingsTxIcon from 'src/routes/safe/components/Transactions/TxList/assets/settings.svg' import SettingsTxIcon from 'src/routes/safe/components/Transactions/TxList/assets/settings.svg'
import { isCancelTransaction } from 'src/routes/safe/components/Transactions/TxList/utils'
export type TxTypeProps = { export type TxTypeProps = {
icon: string icon: string
@ -40,20 +40,8 @@ export const useTransactionType = (tx: Transaction): TxTypeProps => {
break break
} }
// TODO: isCancel if (tx.txInfo.isCancellation) {
// there are two 'cancelling' tx identification setType({ icon: CircleCrossRed, text: 'On-chain rejection' })
// this one is the candidate to remain when the client gateway implements
// https://github.com/gnosis/safe-client-gateway/issues/255
if (typeof tx.txInfo.isCancellation === 'boolean' && tx.txInfo.isCancellation) {
setType({ icon: CustomTxIcon, text: 'Cancelling transaction' })
break
}
// TODO: isCancel
// remove the following condition when issue#255 is implemented
// also remove `isCancelTransaction` function
if (isCancelTransaction({ txInfo: tx.txInfo, safeAddress })) {
setType({ icon: CustomTxIcon, text: 'Cancelling transaction' })
break break
} }

View File

@ -163,6 +163,32 @@ const failedTransaction = css`
} }
` `
const onChainRejection = css`
&.on-chain-rejection {
background-color: ${({ theme }) => theme.colors.errorTooltip};
border-left: 4px solid ${({ theme }) => theme.colors.error};
border-radius: 4px;
padding-left: 7px;
height: 22px;
max-width: 165px;
> div {
height: 17px;
align-items: center;
padding-top: 3px;
}
p {
font-size: 11px;
line-height: 16px;
letter-spacing: 1px;
font-weight: bold;
text-transform: uppercase;
margin-left: -2px;
}
}
`
export const StyledTransaction = styled.div` export const StyledTransaction = styled.div`
${willBeReplaced}; ${willBeReplaced};
${failedTransaction}; ${failedTransaction};
@ -175,6 +201,10 @@ export const StyledTransaction = styled.div`
align-self: center; align-self: center;
} }
.tx-type {
${onChainRejection};
}
.tx-votes { .tx-votes {
justify-self: center; justify-self: center;
} }

View File

@ -2,7 +2,6 @@ import { BigNumber } from 'bignumber.js'
import { getNetworkInfo } from 'src/config' import { getNetworkInfo } from 'src/config'
import { import {
Custom,
isCustomTxInfo, isCustomTxInfo,
isTransferTxInfo, isTransferTxInfo,
Transaction, Transaction,
@ -12,7 +11,6 @@ import {
import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
import { sameAddress } from 'src/logic/wallets/ethAddresses' import { sameAddress } from 'src/logic/wallets/ethAddresses'
import { sameString } from 'src/utils/strings'
export const NOT_AVAILABLE = 'n/a' export const NOT_AVAILABLE = 'n/a'
@ -90,27 +88,11 @@ export const getTxTokenData = (txInfo: Transfer): txTokenData => {
} }
} }
// TODO: isCancel export const isCancelTxDetails = (txInfo: Transaction['txInfo']): boolean =>
// how can we be sure that it's a cancel tx without asking for tx-details?
// can the client-gateway service provide info about the tx, Like: `isCancelTransaction: boolean`?
// it will be solved as part of: https://github.com/gnosis/safe-client-gateway/issues/255
export const isCancelTransaction = ({ txInfo, safeAddress }: { txInfo: Custom; safeAddress: string }): boolean =>
sameAddress(txInfo.to, safeAddress) &&
sameString(txInfo.dataSize, '0') &&
sameString(txInfo.value, '0') &&
txInfo.methodName === null
type IsCancelTxDetailsProps = {
executedAt: number | null
txInfo: Transaction['txInfo']
safeAddress: string
}
export const isCancelTxDetails = ({ executedAt, txInfo, safeAddress }: IsCancelTxDetailsProps): boolean =>
!executedAt &&
// custom transaction // custom transaction
isCustomTxInfo(txInfo) && isCustomTxInfo(txInfo) &&
// verify that it's a cancel tx based on it's info // flag-based identification
isCancelTransaction({ safeAddress, txInfo }) txInfo.isCancellation
export const addressInList = (list: string[] = []) => (address: string): boolean => export const addressInList = (list: string[] = []) => (address: string): boolean =>
list.some((ownerAddress) => sameAddress(ownerAddress, address)) list.some((ownerAddress) => sameAddress(ownerAddress, address))

View File

@ -1596,9 +1596,9 @@
solc "0.5.14" solc "0.5.14"
truffle "^5.1.21" truffle "^5.1.21"
"@gnosis.pm/safe-react-components@https://github.com/gnosis/safe-react-components.git#a68a67e": "@gnosis.pm/safe-react-components@https://github.com/gnosis/safe-react-components.git#7ebc414":
version "0.5.0" version "0.5.0"
resolved "https://github.com/gnosis/safe-react-components.git#a68a67e634d0be091856ebba9f6874eebb767cd7" resolved "https://github.com/gnosis/safe-react-components.git#7ebc414ae975d60846704c5a8db5e61c30348d12"
dependencies: dependencies:
classnames "^2.2.6" classnames "^2.2.6"
react-media "^1.10.0" react-media "^1.10.0"