Fix merge

This commit is contained in:
Mati Dastugue 2020-05-27 21:08:20 -03:00
commit bf5a24d762
19 changed files with 306 additions and 122 deletions

View File

@ -13,14 +13,14 @@ export const simpleMemoize = (fn) => {
}
}
export const required = (value) => (value ? undefined : 'Required')
export const required = (value?: string) => (value && value.trim() !== '' ? undefined : 'Required')
export const mustBeInteger = (value) =>
export const mustBeInteger = (value: string) =>
!Number.isInteger(Number(value)) || value.includes('.') ? 'Must be an integer' : undefined
export const mustBeFloat = (value) => (value && Number.isNaN(Number(value)) ? 'Must be a number' : undefined)
export const mustBeFloat = (value: string) => (value && Number.isNaN(Number(value)) ? 'Must be a number' : undefined)
export const greaterThan = (min) => (value) => {
export const greaterThan = (min: number | string) => (value: string) => {
if (Number.isNaN(Number(value)) || Number.parseFloat(value) > Number(min)) {
return undefined
}
@ -30,7 +30,7 @@ export const greaterThan = (min) => (value) => {
const regexQuery = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i
const url = new RegExp(regexQuery)
export const mustBeUrl = (value) => {
export const mustBeUrl = (value: string) => {
if (url.test(value)) {
return undefined
}
@ -38,7 +38,7 @@ export const mustBeUrl = (value) => {
return 'Please, provide a valid url'
}
export const minValue = (min) => (value) => {
export const minValue = (min: number | string) => (value: string) => {
if (Number.isNaN(Number(value)) || Number.parseFloat(value) >= Number(min)) {
return undefined
}
@ -46,8 +46,8 @@ export const minValue = (min) => (value) => {
return `Should be at least ${min}`
}
export const maxValue = (max) => (value) => {
if (Number.isNaN(Number(value)) || parseFloat(value) <= parseFloat(max)) {
export const maxValue = (max: number | string) => (value: string) => {
if (Number.isNaN(Number(value)) || parseFloat(value) <= parseFloat(max.toString())) {
return undefined
}
@ -56,14 +56,14 @@ export const maxValue = (max) => (value) => {
export const ok = () => undefined
export const mustBeEthereumAddress = simpleMemoize((address) => {
export const mustBeEthereumAddress = simpleMemoize((address: string) => {
const startsWith0x = address.startsWith('0x')
const isAddress = getWeb3().utils.isAddress(address)
return startsWith0x && isAddress ? undefined : 'Address should be a valid Ethereum address or ENS name'
})
export const mustBeEthereumContractAddress = simpleMemoize(async (address) => {
export const mustBeEthereumContractAddress = simpleMemoize(async (address: string) => {
const contractCode = await getWeb3().eth.getCode(address)
return !contractCode || contractCode.replace('0x', '').replace(/0/g, '') === ''
@ -76,8 +76,8 @@ export const minMaxLength = (minLen, maxLen) => (value) =>
export const ADDRESS_REPEATED_ERROR = 'Address already introduced'
export const uniqueAddress = (addresses) =>
simpleMemoize((value) => {
export const uniqueAddress = (addresses: string[]) =>
simpleMemoize((value: string[]) => {
const addressAlreadyExists = addresses.some((address) => sameAddress(value, address))
return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined
})
@ -95,7 +95,7 @@ export const inLimit = (limit, base, baseText, symbol = 'ETH') => (value) => {
return `Should not exceed ${max} ${symbol} (amount to reach ${baseText})`
}
export const differentFrom = (diffValue) => (value) => {
export const differentFrom = (diffValue: number | string) => (value: string) => {
if (value === diffValue.toString()) {
return `Value should be different than ${value}`
}

View File

@ -1,6 +1,6 @@
//
import { ensureOnce } from "src/utils/singleton"
import { ETHEREUM_NETWORK } from "src/logic/wallets/getWeb3"
import { ETHEREUM_NETWORK, getWeb3 } from 'src/logic/wallets/getWeb3'
import {
RELAY_API_URL,
SIGNATURES_VIA_METAMASK,
@ -58,6 +58,8 @@ export const getTxServiceUriFrom = (safeAddress) =>
export const getIncomingTxServiceUriTo = (safeAddress) =>
`safes/${safeAddress}/incoming-transfers/`
export const getSafeCreationTxUri = (safeAddress) => `safes/${safeAddress}/creation/`
export const getRelayUrl = () => getConfig()[RELAY_API_URL]
export const signaturesViaMetamask = () => {
@ -79,3 +81,11 @@ export const getIntercomId = () =>
export const getExchangeRatesUrl = () => 'https://api.exchangeratesapi.io/latest'
export const getSafeLastVersion = () => process.env.REACT_APP_LATEST_SAFE_VERSION || '1.1.1'
export const buildSafeCreationTxUrl = (safeAddress) => {
const host = getTxServiceHost()
const address = getWeb3().utils.toChecksumAddress(safeAddress)
const base = getSafeCreationTxUri(address)
return `${host}${base}`
}

View File

@ -19,6 +19,7 @@ import { safeFeaturesEnabledSelector, safeParamAddressFromStateSelector } from '
import { history } from 'src/store/index'
import { wrapInSuspense } from 'src/utils/wrapInSuspense'
import { useFetchTokens } from '../../container/hooks/useFetchTokens'
const Collectibles = React.lazy(() => import('src/routes/safe/components/Balances/Collectibles'))
const Coins = React.lazy(() => import('src/routes/safe/components/Balances/Coins'))

View File

@ -0,0 +1,52 @@
// @flow
import React from 'react'
import { formatDate } from '../../columns'
import Bold from '../../../../../../../components/layout/Bold'
import Paragraph from '../../../../../../../components/layout/Paragraph'
import EtherscanLink from '../../../../../../../components/EtherscanLink'
import { makeStyles } from '@material-ui/core/styles'
import Block from '../../../../../../../components/layout/Block'
const useStyles = makeStyles({
address: {
height: '20px',
},
txData: {
alignItems: 'center',
display: 'flex',
lineHeight: '1.6',
},
txHash: {
paddingRight: '3px',
},
})
export const CreationTx = (props) => {
const { tx } = props
const classes = useStyles()
if (!tx) return null
const isCreationTx = tx.type === 'creation'
console.log('Classes', classes)
return !isCreationTx ? null : (
<>
<Paragraph noMargin>
<Bold>Created: </Bold>
{formatDate(tx.created)}
</Paragraph>
<Block align="left" className={classes.txData}>
<Bold className={classes.txHash}>Creator:</Bold>
{tx.creator ? <EtherscanLink cut={8} type="address" value={tx.creator} /> : 'n/a'}
</Block>
<Block align="left" className={classes.txData}>
<Bold className={classes.txHash}>Factory:</Bold>
{tx.factoryAddress ? <EtherscanLink cut={8} type="address" value={tx.factoryAddress} /> : 'n/a'}
</Block>
<Block align="left" className={classes.txData}>
<Bold className={classes.txHash}>Mastercopy:</Bold>
{tx.masterCopy ? <EtherscanLink cut={8} type="address" value={tx.masterCopy} /> : 'n/a'}
</Block>
</>
)
}

View File

@ -0,0 +1,19 @@
import React from 'react'
import { INCOMING_TX_TYPES } from '../../../../../store/models/incomingTransaction'
import { formatDate } from '../../columns'
import Bold from '../../../../../../../components/layout/Bold'
import Paragraph from '../../../../../../../components/layout/Paragraph'
export const IncomingTx = (props) => {
const { tx } = props
if (!tx) return null
const isIncomingTx = !!INCOMING_TX_TYPES[tx.type]
return !isIncomingTx ? null : (
<>
<Paragraph noMargin>
<Bold>Created: </Bold>
{formatDate(tx.executionDate)}
</Paragraph>
</>
)
}

View File

@ -0,0 +1,40 @@
// @flow
import React from 'react'
import { formatDate } from '../../columns'
import Bold from '../../../../../../../components/layout/Bold'
import Paragraph from '../../../../../../../components/layout/Paragraph'
export const OutgoingTx = (props) => {
const { tx } = props
if (!tx || !(tx.type === 'outgoing')) return null
return (
<>
<Paragraph noMargin>
<Bold>Created: </Bold>
{formatDate(tx.submissionDate)}
</Paragraph>
{tx.executionDate && (
<Paragraph noMargin>
<Bold>Executed: </Bold>
{formatDate(tx.executionDate)}
</Paragraph>
)}
{tx.refundParams && (
<Paragraph noMargin>
<Bold>Refund: </Bold>
max. {tx.refundParams.fee} {tx.refundParams.symbol}
</Paragraph>
)}
{tx.operation === 1 && (
<Paragraph noMargin>
<Bold>Delegate Call</Bold>
</Paragraph>
)}
{tx.operation === 2 && (
<Paragraph noMargin>
<Bold>Contract Creation</Bold>
</Paragraph>
)}
</>
)
}

View File

@ -3,8 +3,6 @@ import cn from 'classnames'
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import { formatDate } from '../columns'
import ApproveTxModal from './ApproveTxModal'
import OwnersColumn from './OwnersColumn'
import RejectTxModal from './RejectTxModal'
@ -23,6 +21,9 @@ import IncomingTxDescription from 'src/routes/safe/components/Transactions/TxsTa
import { INCOMING_TX_TYPES } from 'src/routes/safe/store/models/incomingTransaction'
import { safeNonceSelector, safeThresholdSelector } from 'src/routes/safe/store/selectors'
import { IncomingTx } from './IncomingTx'
import { CreationTx } from './CreationTx'
import { OutgoingTx } from './OutgoingTx'
const useStyles = makeStyles(styles as any)
@ -34,6 +35,8 @@ const ExpandedTx = ({ cancelTx, tx }) => {
const openApproveModal = () => setOpenModal('approveTx')
const closeModal = () => setOpenModal(null)
const isIncomingTx = !!INCOMING_TX_TYPES[tx.type]
const isCreationTx = tx.type === 'creation'
const thresholdReached = !isIncomingTx && threshold <= tx.confirmations.size
const canExecute = !isIncomingTx && nonce === tx.nonce
const cancelThresholdReached = !!cancelTx && threshold <= cancelTx.confirmations.size
@ -52,63 +55,33 @@ const ExpandedTx = ({ cancelTx, tx }) => {
<Block className={classes.expandedTxBlock}>
<Row>
<Col layout="column" xs={6}>
<Block className={cn(classes.txDataContainer, isIncomingTx && classes.incomingTxBlock)}>
<Block className={cn(classes.txDataContainer, (isIncomingTx || isCreationTx) && classes.incomingTxBlock)}>
<Block align="left" className={classes.txData}>
<Bold className={classes.txHash}>Hash:</Bold>
{tx.executionTxHash ? <EtherScanLink cut={8} type="tx" value={tx.executionTxHash} /> : 'n/a'}
</Block>
{!isIncomingTx && (
{!isIncomingTx && !isCreationTx && (
<Paragraph noMargin>
<Bold>Nonce: </Bold>
<Span>{tx.nonce}</Span>
</Paragraph>
)}
{!isCreationTx ? (
<Paragraph noMargin>
<Bold>Fee: </Bold>
{tx.fee ? tx.fee : 'n/a'}
</Paragraph>
{isIncomingTx ? (
<>
<Paragraph noMargin>
<Bold>Created: </Bold>
{formatDate(tx.executionDate)}
</Paragraph>
</>
) : (
<>
<Paragraph noMargin>
<Bold>Created: </Bold>
{formatDate(tx.submissionDate)}
</Paragraph>
{tx.executionDate && (
<Paragraph noMargin>
<Bold>Executed: </Bold>
{formatDate(tx.executionDate)}
</Paragraph>
)}
{tx.refundParams && (
<Paragraph noMargin>
<Bold>Refund: </Bold>
max. {tx.refundParams.fee} {tx.refundParams.symbol}
</Paragraph>
)}
{tx.operation === 1 && (
<Paragraph noMargin>
<Bold>Delegate Call</Bold>
</Paragraph>
)}
{tx.operation === 2 && (
<Paragraph noMargin>
<Bold>Contract Creation</Bold>
</Paragraph>
)}
</>
)}
) : null}
<CreationTx tx={tx} />
<IncomingTx tx={tx} />
<OutgoingTx tx={tx} />
</Block>
<Hairline />
{isIncomingTx ? <IncomingTxDescription tx={tx} /> : <TxDescription tx={tx} />}
{isIncomingTx && <IncomingTxDescription tx={tx} />}
{!isIncomingTx && !isCreationTx && <TxDescription tx={tx} />}
{isCreationTx && <Block className={classes.emptyRowDataContainer} />}
</Col>
{!isIncomingTx && (
{!isIncomingTx && !isCreationTx && (
<OwnersColumn
cancelThresholdReached={cancelThresholdReached}
cancelTx={cancelTx}

View File

@ -33,4 +33,10 @@ export const styles = () => ({
incomingTxBlock: {
borderRight: '2px solid rgb(232, 231, 230)',
},
emptyRowDataContainer: {
paddingTop: lg,
paddingLeft: md,
paddingBottom: md,
borderRight: '2px solid rgb(232, 231, 230)',
},
})

View File

@ -1,4 +1,3 @@
import Collapse from '@material-ui/core/Collapse'
import IconButton from '@material-ui/core/IconButton'
import TableCell from '@material-ui/core/TableCell'
import TableContainer from '@material-ui/core/TableContainer'
@ -21,16 +20,10 @@ import Block from 'src/components/layout/Block'
import Row from 'src/components/layout/Row'
import { extendedTransactionsSelector } from 'src/routes/safe/container/selector'
import { safeCancellationTransactionsSelector } from 'src/routes/safe/store/selectors'
import { Collapse } from '@material-ui/core'
export const TRANSACTION_ROW_TEST_ID = 'transaction-row'
const CollapseAux: any = Collapse
const expandCellStyle = {
paddingLeft: 0,
paddingRight: 15,
}
const TxsTable = ({ classes }) => {
const [expandedTx, setExpandedTx] = useState(null)
const cancellationTransactions = useSelector(safeCancellationTransactionsSelector)
@ -107,7 +100,7 @@ const TxsTable = ({ classes }) => {
<Status status={row.status} />
</Row>
</TableCell>
<TableCell style={expandCellStyle}>
<TableCell className={classes.expandCellStyle}>
{!row.tx.creationTx && (
<IconButton disableRipple>
{expandedTx === row.safeTxHash ? <ExpandLess /> : <ExpandMore />}
@ -122,12 +115,30 @@ const TxsTable = ({ classes }) => {
colSpan={6}
style={{ paddingBottom: 0, paddingTop: 0 }}
>
<CollapseAux
cancelTx={row[TX_TABLE_RAW_CANCEL_TX_ID]}
component={ExpandedTxComponent}
<Collapse
component={() => (
<ExpandedTxComponent cancelTx={row[TX_TABLE_RAW_CANCEL_TX_ID]} tx={row[TX_TABLE_RAW_TX_ID]} />
)}
in={expandedTx === row.tx.safeTxHash}
timeout="auto"
unmountOnExit
/>
</TableCell>
</TableRow>
)}
{row.tx.creationTx && (
<TableRow>
<TableCell
className={classes.extendedTxContainer}
colSpan={6}
style={{ paddingBottom: 0, paddingTop: 0 }}
>
<Collapse
component={() => (
<ExpandedTxComponent cancelTx={row[TX_TABLE_RAW_CANCEL_TX_ID]} tx={row[TX_TABLE_RAW_TX_ID]} />
)}
in={expandedTx === row.tx.safeTxHash}
timeout="auto"
tx={row[TX_TABLE_RAW_TX_ID]}
unmountOnExit
/>
</TableCell>

View File

@ -26,4 +26,8 @@ export const styles = () => ({
display: 'flex',
justifyContent: 'flex-end',
},
expandCellStyle: {
paddingLeft: 0,
paddingRight: 15,
},
})

View File

@ -7,6 +7,7 @@ import fetchSafeTokens from 'src/logic/tokens/store/actions/fetchSafeTokens'
import fetchLatestMasterContractVersion from 'src/routes/safe/store/actions/fetchLatestMasterContractVersion'
import fetchSafe from 'src/routes/safe/store/actions/fetchSafe'
import fetchTransactions from 'src/routes/safe/store/actions/fetchTransactions'
import fetchSafeCreationTx from '../../store/actions/fetchSafeCreationTx'
export const useLoadSafe = (safeAddress) => {
const dispatch = useDispatch()
@ -19,6 +20,7 @@ export const useLoadSafe = (safeAddress) => {
.then(() => {
dispatch(fetchSafeTokens(safeAddress))
dispatch(loadAddressBookFromStorage())
dispatch(fetchSafeCreationTx(safeAddress))
return dispatch(fetchTransactions(safeAddress))
})
.then(() => dispatch(addViewedSafe(safeAddress)))

View File

@ -5,9 +5,9 @@ import { useSelector } from 'react-redux'
import Page from 'src/components/layout/Page'
import Layout from 'src/routes/safe/components/Layout'
import { useCheckForUpdates } from 'src/routes/safe/container/hooks/useCheckForUpdates'
import { useLoadSafe } from 'src/routes/safe/container/hooks/useLoadSafe'
import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors'
import { useLoadSafe } from './hooks/useLoadSafe'
import { useCheckForUpdates } from './hooks/useCheckForUpdates'
const INITIAL_STATE = {
sendFunds: {

View File

@ -0,0 +1,49 @@
// @flow
import axios from 'axios'
import { List } from 'immutable'
import { buildSafeCreationTxUrl } from '../../../../config'
import { addOrUpdateTransactions } from './transactions/addOrUpdateTransactions'
import { makeTransaction } from '../models/transaction'
const getCreationTx = async (safeAddress) => {
const url = buildSafeCreationTxUrl(safeAddress)
const response = await axios.get(url)
return {
...response.data,
creationTx: true,
nonce: null,
}
}
const fetchSafeCreationTx = (safeAddress) => async (dispatch) => {
if (!safeAddress) return
const creationTxFetched = await getCreationTx(safeAddress)
const {
created,
creationTx,
creator,
factoryAddress,
masterCopy,
setupData,
transactionHash,
type,
} = creationTxFetched
const txType = type || 'creation'
const creationTxAsRecord = makeTransaction({
created,
creator,
factoryAddress,
masterCopy,
setupData,
creationTx,
executionTxHash: transactionHash,
type: txType,
submissionDate: created,
})
dispatch(addOrUpdateTransactions({ safeAddress, transactions: List([creationTxAsRecord]) }))
}
export default fetchSafeCreationTx

View File

@ -5,7 +5,6 @@ import { List, Map } from 'immutable'
import { batch } from 'react-redux'
import { addIncomingTransactions } from './addIncomingTransactions'
import { addTransactions } from './addTransactions'
import generateBatchRequests from 'src/logic/contracts/generateBatchRequests'
import { decodeParamsFromSafeMethod } from 'src/logic/contracts/methodIds'
@ -25,7 +24,8 @@ import { getWeb3 } from 'src/logic/wallets/getWeb3'
import { addCancellationTransactions } from 'src/routes/safe/store/actions/addCancellationTransactions'
import { makeConfirmation } from 'src/routes/safe/store/models/confirmation'
import { makeIncomingTransaction } from 'src/routes/safe/store/models/incomingTransaction'
import { makeTransaction } from 'src/routes/safe/store/models/transaction'
import { addOrUpdateTransactions } from './transactions/addOrUpdateTransactions'
import { makeTransaction } from '../models/transaction'
let web3
@ -144,33 +144,7 @@ export const buildTransactionFrom = async (
})
}
const addMockSafeCreationTx = (safeAddress) => [
{
blockNumber: null,
baseGas: 0,
confirmations: [],
data: null,
executionDate: null,
gasPrice: 0,
gasToken: '0x0000000000000000000000000000000000000000',
isExecuted: true,
nonce: null,
operation: 0,
refundReceiver: '0x0000000000000000000000000000000000000000',
safe: safeAddress,
safeTxGas: 0,
safeTxHash: '',
signatures: null,
submissionDate: null,
executor: '',
to: '',
transactionHash: null,
value: 0,
creationTx: true,
},
]
const batchRequestTxsData = (txs) => {
const batchRequestTxsData = (txs: any[]) => {
const web3Batch = new web3.BatchRequest()
const txsTokenInfo = txs.map((tx) => {
@ -236,9 +210,9 @@ export const buildIncomingTransactionFrom = ([tx, symbol, decimals, fee]) => {
}
let etagSafeTransactions = null
let etagCachedSafeIncommingTransactions = null
let etagCachedSafeIncomingTransactions = null
export const loadSafeTransactions = async (safeAddress, getState) => {
let transactions = addMockSafeCreationTx(safeAddress)
let transactions = []
try {
const config = etagSafeTransactions
@ -288,7 +262,7 @@ export const loadSafeTransactions = async (safeAddress, getState) => {
const groupedTxs = List(txsRecord).groupBy((tx) => (tx.get('cancellationTx') ? 'cancel' : 'outgoing'))
return {
outgoing: Map().set(safeAddress, groupedTxs.get('outgoing')),
outgoing: groupedTxs.get('outgoing'),
cancel: Map().set(safeAddress, groupedTxs.get('cancel')),
}
}
@ -296,10 +270,10 @@ export const loadSafeTransactions = async (safeAddress, getState) => {
export const loadSafeIncomingTransactions = async (safeAddress) => {
let incomingTransactions = []
try {
const config = etagCachedSafeIncommingTransactions
const config = etagCachedSafeIncomingTransactions
? {
headers: {
'If-None-Match': etagCachedSafeIncommingTransactions,
'If-None-Match': etagCachedSafeIncomingTransactions,
},
}
: undefined
@ -307,11 +281,11 @@ export const loadSafeIncomingTransactions = async (safeAddress) => {
const response = await axios.get(url, config)
if (response.data.count > 0) {
incomingTransactions = response.data.results
if (etagCachedSafeIncommingTransactions === response.headers.etag) {
if (etagCachedSafeIncomingTransactions === response.headers.etag) {
// The txs are the same, we can return the cached ones
return
}
etagCachedSafeIncommingTransactions = response.headers.etag
etagCachedSafeIncomingTransactions = response.headers.etag
}
} catch (err) {
if (err && err.response && err.response.status === 304) {
@ -336,7 +310,7 @@ export default (safeAddress) => async (dispatch, getState) => {
batch(() => {
dispatch(addCancellationTransactions(cancel))
dispatch(addTransactions(outgoing))
dispatch(addOrUpdateTransactions({ safeAddress, transactions: outgoing }))
})
}

View File

@ -0,0 +1,6 @@
// @flow
import { createAction } from 'redux-actions'
export const ADD_OR_UPDATE_TRANSACTIONS = 'ADD_OR_UPDATE_TRANSACTIONS'
export const addOrUpdateTransactions = createAction(ADD_OR_UPDATE_TRANSACTIONS)

View File

@ -12,13 +12,13 @@ import { getIncomingTxAmount } from 'src/routes/safe/components/Transactions/Txs
import { grantedSelector } from 'src/routes/safe/container/selector'
import { ADD_INCOMING_TRANSACTIONS } from 'src/routes/safe/store/actions/addIncomingTransactions'
import { ADD_SAFE } from 'src/routes/safe/store/actions/addSafe'
import { ADD_TRANSACTIONS } from 'src/routes/safe/store/actions/addTransactions'
import updateSafe from 'src/routes/safe/store/actions/updateSafe'
import { safeParamAddressFromStateSelector, safesMapSelector } from 'src/routes/safe/store/selectors'
import { loadFromStorage, saveToStorage } from 'src/utils/storage'
import { ADD_OR_UPDATE_TRANSACTIONS } from '../actions/transactions/addOrUpdateTransactions'
const watchedActions = [ADD_TRANSACTIONS, ADD_INCOMING_TRANSACTIONS, ADD_SAFE]
const watchedActions = [ADD_OR_UPDATE_TRANSACTIONS, ADD_INCOMING_TRANSACTIONS, ADD_SAFE]
const sendAwaitingTransactionNotification = async (
dispatch,
@ -66,16 +66,16 @@ const notificationsMiddleware = (store) => (next) => async (action) => {
if (watchedActions.includes(action.type)) {
const state = store.getState()
switch (action.type) {
case ADD_TRANSACTIONS: {
const transactionsList = action.payload
const userAddress = userAccountSelector(state)
const safeAddress = action.payload.keySeq().get(0)
case ADD_OR_UPDATE_TRANSACTIONS: {
const { safeAddress, transactions } = action.payload
const userAddress: string = userAccountSelector(state)
const cancellationTransactions = state.cancellationTransactions.get(safeAddress)
const cancellationTransactionsByNonce = cancellationTransactions
? cancellationTransactions.reduce((acc, tx) => acc.set(tx.nonce, tx), Map())
: Map()
const awaitingTransactions = getAwaitingTransactions(
transactionsList,
Map().set(safeAddress, transactions),
cancellationTransactionsByNonce,
userAddress,
)

View File

@ -39,4 +39,10 @@ export const makeTransaction = Record({
refundParams: null,
type: 'outgoing',
origin: null,
created: false,
creator: '',
factoryAddress: '',
masterCopy: '',
setupData: '',
transactionHash: '',
})

View File

@ -1,13 +1,43 @@
import { Map } from 'immutable'
import { List, Map } from 'immutable'
import { handleActions } from 'redux-actions'
import { ADD_TRANSACTIONS } from 'src/routes/safe/store/actions/addTransactions'
import { ADD_OR_UPDATE_TRANSACTIONS } from '../actions/transactions/addOrUpdateTransactions'
export const TRANSACTIONS_REDUCER_ID = 'transactions'
export default handleActions(
{
[ADD_TRANSACTIONS]: (state, action) => action.payload,
[ADD_OR_UPDATE_TRANSACTIONS]: (state, action) => {
const { safeAddress, transactions } = action.payload
if (!safeAddress || !transactions) {
return state
}
const newState = state.withMutations((map) => {
const stateTransactionsList = map.get(safeAddress)
if (stateTransactionsList) {
let newTxList
transactions.forEach((updateTx) => {
const txIndex = stateTransactionsList.findIndex((txIterator) => txIterator.nonce === updateTx.nonce)
if (txIndex !== -1) {
// Update
newTxList = stateTransactionsList.update(txIndex, (oldTx) => oldTx.merge(updateTx))
map.set(safeAddress, newTxList)
} else {
// Add new
map.update(safeAddress, (oldTxList) => oldTxList.merge(List([updateTx])))
}
})
} else {
map.set(safeAddress, transactions)
}
})
return newState
},
},
Map(),
)

View File

@ -108,6 +108,7 @@ const theme = createMuiTheme({
MuiStepper: {
root: {
padding: `${lg} 0 0 15px`,
background: 'transparent',
},
},
MuiIconButton: {