(Feature) #554 - Safe creation tx details (#894)

* ChecksumAddresses when add new addressBook entry
ChecksumAddresses when add new owner
ChecksumAddresses when replaces owner

* Refactor expandedTx to show creation safe data

* Export CreationTx, IncomingTx and OutgoingTx render to components
Rename components

* Renames addMockSafeCreationTx to getCreationTx

* Avoid unnecessary fetch of creation tx after the first fetch

* - Loads creationTx once the safe loads
- Adds addOrUpdateTransactions.js
- Adds fetchSafeCreationTx.js
- Removes creationTx logic from fetchTransactions.js
- Updates notificationsMiddleware.js

* Fixs getAwaitingTransactions

* Remove creationTx selector

* Merge branch 'development' of https://github.com/gnosis/safe-react into 416-contract-version-improvements

# Conflicts:
#	src/routes/safe/components/Balances/index.tsx
#	src/routes/safe/container/index.tsx

* Merge branch 'development' of https://github.com/gnosis/safe-react into 416-contract-version-improvements

# Conflicts:
#	src/routes/safe/components/Balances/index.tsx
#	src/routes/safe/container/index.tsx

* Fix date column in safe creation

* Add copy and etherscanlinks to creation safe details

* Fix hooks import
This commit is contained in:
Agustin Pane 2020-05-26 15:00:26 -03:00 committed by GitHub
parent 10df1bd876
commit de4d564955
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 291 additions and 108 deletions

View File

@ -1,6 +1,6 @@
// //
import { ensureOnce } from "src/utils/singleton" import { ensureOnce } from "src/utils/singleton"
import { ETHEREUM_NETWORK } from "src/logic/wallets/getWeb3" import { ETHEREUM_NETWORK, getWeb3 } from 'src/logic/wallets/getWeb3'
import { import {
RELAY_API_URL, RELAY_API_URL,
SIGNATURES_VIA_METAMASK, SIGNATURES_VIA_METAMASK,
@ -58,6 +58,8 @@ export const getTxServiceUriFrom = (safeAddress) =>
export const getIncomingTxServiceUriTo = (safeAddress) => export const getIncomingTxServiceUriTo = (safeAddress) =>
`safes/${safeAddress}/incoming-transfers/` `safes/${safeAddress}/incoming-transfers/`
export const getSafeCreationTxUri = (safeAddress) => `safes/${safeAddress}/creation/`
export const getRelayUrl = () => getConfig()[RELAY_API_URL] export const getRelayUrl = () => getConfig()[RELAY_API_URL]
export const signaturesViaMetamask = () => { export const signaturesViaMetamask = () => {
@ -79,3 +81,11 @@ export const getIntercomId = () =>
export const getExchangeRatesUrl = () => 'https://api.exchangeratesapi.io/latest' export const getExchangeRatesUrl = () => 'https://api.exchangeratesapi.io/latest'
export const getSafeLastVersion = () => process.env.REACT_APP_LATEST_SAFE_VERSION || '1.1.1' 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 { history } from 'src/store/index'
import { wrapInSuspense } from 'src/utils/wrapInSuspense' import { wrapInSuspense } from 'src/utils/wrapInSuspense'
import { useFetchTokens } from '../../container/hooks/useFetchTokens' import { useFetchTokens } from '../../container/hooks/useFetchTokens'
const Collectibles = React.lazy(() => import('src/routes/safe/components/Balances/Collectibles')) const Collectibles = React.lazy(() => import('src/routes/safe/components/Balances/Collectibles'))
const Coins = React.lazy(() => import('src/routes/safe/components/Balances/Coins')) 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 React, { useState } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { formatDate } from '../columns'
import ApproveTxModal from './ApproveTxModal' import ApproveTxModal from './ApproveTxModal'
import OwnersColumn from './OwnersColumn' import OwnersColumn from './OwnersColumn'
import RejectTxModal from './RejectTxModal' 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 { INCOMING_TX_TYPES } from 'src/routes/safe/store/models/incomingTransaction'
import { safeNonceSelector, safeThresholdSelector } from 'src/routes/safe/store/selectors' 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) const useStyles = makeStyles(styles as any)
@ -34,6 +35,8 @@ const ExpandedTx = ({ cancelTx, tx }) => {
const openApproveModal = () => setOpenModal('approveTx') const openApproveModal = () => setOpenModal('approveTx')
const closeModal = () => setOpenModal(null) const closeModal = () => setOpenModal(null)
const isIncomingTx = !!INCOMING_TX_TYPES[tx.type] const isIncomingTx = !!INCOMING_TX_TYPES[tx.type]
const isCreationTx = tx.type === 'creation'
const thresholdReached = !isIncomingTx && threshold <= tx.confirmations.size const thresholdReached = !isIncomingTx && threshold <= tx.confirmations.size
const canExecute = !isIncomingTx && nonce === tx.nonce const canExecute = !isIncomingTx && nonce === tx.nonce
const cancelThresholdReached = !!cancelTx && threshold <= cancelTx.confirmations.size const cancelThresholdReached = !!cancelTx && threshold <= cancelTx.confirmations.size
@ -52,63 +55,33 @@ const ExpandedTx = ({ cancelTx, tx }) => {
<Block className={classes.expandedTxBlock}> <Block className={classes.expandedTxBlock}>
<Row> <Row>
<Col layout="column" xs={6}> <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}> <Block align="left" className={classes.txData}>
<Bold className={classes.txHash}>Hash:</Bold> <Bold className={classes.txHash}>Hash:</Bold>
{tx.executionTxHash ? <EtherScanLink cut={8} type="tx" value={tx.executionTxHash} /> : 'n/a'} {tx.executionTxHash ? <EtherScanLink cut={8} type="tx" value={tx.executionTxHash} /> : 'n/a'}
</Block> </Block>
{!isIncomingTx && ( {!isIncomingTx && !isCreationTx && (
<Paragraph noMargin> <Paragraph noMargin>
<Bold>Nonce: </Bold> <Bold>Nonce: </Bold>
<Span>{tx.nonce}</Span> <Span>{tx.nonce}</Span>
</Paragraph> </Paragraph>
)} )}
<Paragraph noMargin> {!isCreationTx ? (
<Bold>Fee: </Bold> <Paragraph noMargin>
{tx.fee ? tx.fee : 'n/a'} <Bold>Fee: </Bold>
</Paragraph> {tx.fee ? tx.fee : 'n/a'}
{isIncomingTx ? ( </Paragraph>
<> ) : null}
<Paragraph noMargin> <CreationTx tx={tx} />
<Bold>Created: </Bold> <IncomingTx tx={tx} />
{formatDate(tx.executionDate)} <OutgoingTx tx={tx} />
</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>
)}
</>
)}
</Block> </Block>
<Hairline /> <Hairline />
{isIncomingTx ? <IncomingTxDescription tx={tx} /> : <TxDescription tx={tx} />} {isIncomingTx && <IncomingTxDescription tx={tx} />}
{!isIncomingTx && !isCreationTx && <TxDescription tx={tx} />}
{isCreationTx && <Block className={classes.emptyRowDataContainer} />}
</Col> </Col>
{!isIncomingTx && ( {!isIncomingTx && !isCreationTx && (
<OwnersColumn <OwnersColumn
cancelThresholdReached={cancelThresholdReached} cancelThresholdReached={cancelThresholdReached}
cancelTx={cancelTx} cancelTx={cancelTx}

View File

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

View File

@ -26,4 +26,8 @@ export const styles = () => ({
display: 'flex', display: 'flex',
justifyContent: 'flex-end', 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 fetchLatestMasterContractVersion from 'src/routes/safe/store/actions/fetchLatestMasterContractVersion'
import fetchSafe from 'src/routes/safe/store/actions/fetchSafe' import fetchSafe from 'src/routes/safe/store/actions/fetchSafe'
import fetchTransactions from 'src/routes/safe/store/actions/fetchTransactions' import fetchTransactions from 'src/routes/safe/store/actions/fetchTransactions'
import fetchSafeCreationTx from '../../store/actions/fetchSafeCreationTx'
export const useLoadSafe = (safeAddress) => { export const useLoadSafe = (safeAddress) => {
const dispatch = useDispatch() const dispatch = useDispatch()
@ -19,6 +20,7 @@ export const useLoadSafe = (safeAddress) => {
.then(() => { .then(() => {
dispatch(fetchSafeTokens(safeAddress)) dispatch(fetchSafeTokens(safeAddress))
dispatch(loadAddressBookFromStorage()) dispatch(loadAddressBookFromStorage())
dispatch(fetchSafeCreationTx(safeAddress))
return dispatch(fetchTransactions(safeAddress)) return dispatch(fetchTransactions(safeAddress))
}) })
.then(() => dispatch(addViewedSafe(safeAddress))) .then(() => dispatch(addViewedSafe(safeAddress)))

View File

@ -7,8 +7,8 @@ import Page from 'src/components/layout/Page'
import Layout from 'src/routes/safe/components/Layout' import Layout from 'src/routes/safe/components/Layout'
import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors' import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors'
import { useCheckForUpdates } from './hooks/useCheckForUpdates'
import { useLoadSafe } from './hooks/useLoadSafe' import { useLoadSafe } from './hooks/useLoadSafe'
import { useCheckForUpdates } from './hooks/useCheckForUpdates'
const INITIAL_STATE = { const INITIAL_STATE = {
sendFunds: { 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 { batch } from 'react-redux'
import { addIncomingTransactions } from './addIncomingTransactions' import { addIncomingTransactions } from './addIncomingTransactions'
import { addTransactions } from './addTransactions'
import generateBatchRequests from 'src/logic/contracts/generateBatchRequests' import generateBatchRequests from 'src/logic/contracts/generateBatchRequests'
import { decodeParamsFromSafeMethod } from 'src/logic/contracts/methodIds' 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 { addCancellationTransactions } from 'src/routes/safe/store/actions/addCancellationTransactions'
import { makeConfirmation } from 'src/routes/safe/store/models/confirmation' import { makeConfirmation } from 'src/routes/safe/store/models/confirmation'
import { makeIncomingTransaction } from 'src/routes/safe/store/models/incomingTransaction' 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 let web3
@ -144,33 +144,7 @@ export const buildTransactionFrom = async (
}) })
} }
const addMockSafeCreationTx = (safeAddress) => [ const batchRequestTxsData = (txs: any[]) => {
{
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 web3Batch = new web3.BatchRequest() const web3Batch = new web3.BatchRequest()
const txsTokenInfo = txs.map((tx) => { const txsTokenInfo = txs.map((tx) => {
@ -236,9 +210,9 @@ export const buildIncomingTransactionFrom = ([tx, symbol, decimals, fee]) => {
} }
let etagSafeTransactions = null let etagSafeTransactions = null
let etagCachedSafeIncommingTransactions = null let etagCachedSafeIncomingTransactions = null
export const loadSafeTransactions = async (safeAddress, getState) => { export const loadSafeTransactions = async (safeAddress, getState) => {
let transactions = addMockSafeCreationTx(safeAddress) let transactions = []
try { try {
const config = etagSafeTransactions const config = etagSafeTransactions
@ -288,7 +262,7 @@ export const loadSafeTransactions = async (safeAddress, getState) => {
const groupedTxs = List(txsRecord).groupBy((tx) => (tx.get('cancellationTx') ? 'cancel' : 'outgoing')) const groupedTxs = List(txsRecord).groupBy((tx) => (tx.get('cancellationTx') ? 'cancel' : 'outgoing'))
return { return {
outgoing: Map().set(safeAddress, groupedTxs.get('outgoing')), outgoing: groupedTxs.get('outgoing'),
cancel: Map().set(safeAddress, groupedTxs.get('cancel')), cancel: Map().set(safeAddress, groupedTxs.get('cancel')),
} }
} }
@ -296,10 +270,10 @@ export const loadSafeTransactions = async (safeAddress, getState) => {
export const loadSafeIncomingTransactions = async (safeAddress) => { export const loadSafeIncomingTransactions = async (safeAddress) => {
let incomingTransactions = [] let incomingTransactions = []
try { try {
const config = etagCachedSafeIncommingTransactions const config = etagCachedSafeIncomingTransactions
? { ? {
headers: { headers: {
'If-None-Match': etagCachedSafeIncommingTransactions, 'If-None-Match': etagCachedSafeIncomingTransactions,
}, },
} }
: undefined : undefined
@ -307,11 +281,11 @@ export const loadSafeIncomingTransactions = async (safeAddress) => {
const response = await axios.get(url, config) const response = await axios.get(url, config)
if (response.data.count > 0) { if (response.data.count > 0) {
incomingTransactions = response.data.results 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 // The txs are the same, we can return the cached ones
return return
} }
etagCachedSafeIncommingTransactions = response.headers.etag etagCachedSafeIncomingTransactions = response.headers.etag
} }
} catch (err) { } catch (err) {
if (err && err.response && err.response.status === 304) { if (err && err.response && err.response.status === 304) {
@ -336,7 +310,7 @@ export default (safeAddress) => async (dispatch, getState) => {
batch(() => { batch(() => {
dispatch(addCancellationTransactions(cancel)) 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 { grantedSelector } from 'src/routes/safe/container/selector'
import { ADD_INCOMING_TRANSACTIONS } from 'src/routes/safe/store/actions/addIncomingTransactions' import { ADD_INCOMING_TRANSACTIONS } from 'src/routes/safe/store/actions/addIncomingTransactions'
import { ADD_SAFE } from 'src/routes/safe/store/actions/addSafe' 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 updateSafe from 'src/routes/safe/store/actions/updateSafe'
import { safeParamAddressFromStateSelector, safesMapSelector } from 'src/routes/safe/store/selectors' import { safeParamAddressFromStateSelector, safesMapSelector } from 'src/routes/safe/store/selectors'
import { loadFromStorage, saveToStorage } from 'src/utils/storage' 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 ( const sendAwaitingTransactionNotification = async (
dispatch, dispatch,
@ -66,16 +66,16 @@ const notificationsMiddleware = (store) => (next) => async (action) => {
if (watchedActions.includes(action.type)) { if (watchedActions.includes(action.type)) {
const state = store.getState() const state = store.getState()
switch (action.type) { switch (action.type) {
case ADD_TRANSACTIONS: { case ADD_OR_UPDATE_TRANSACTIONS: {
const transactionsList = action.payload const { safeAddress, transactions } = action.payload
const userAddress = userAccountSelector(state) const userAddress: string = userAccountSelector(state)
const safeAddress = action.payload.keySeq().get(0)
const cancellationTransactions = state.cancellationTransactions.get(safeAddress) const cancellationTransactions = state.cancellationTransactions.get(safeAddress)
const cancellationTransactionsByNonce = cancellationTransactions const cancellationTransactionsByNonce = cancellationTransactions
? cancellationTransactions.reduce((acc, tx) => acc.set(tx.nonce, tx), Map()) ? cancellationTransactions.reduce((acc, tx) => acc.set(tx.nonce, tx), Map())
: Map() : Map()
const awaitingTransactions = getAwaitingTransactions( const awaitingTransactions = getAwaitingTransactions(
transactionsList, Map().set(safeAddress, transactions),
cancellationTransactionsByNonce, cancellationTransactionsByNonce,
userAddress, userAddress,
) )

View File

@ -39,4 +39,10 @@ export const makeTransaction = Record({
refundParams: null, refundParams: null,
type: 'outgoing', type: 'outgoing',
origin: null, 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 { handleActions } from 'redux-actions'
import { ADD_TRANSACTIONS } from 'src/routes/safe/store/actions/addTransactions' 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 const TRANSACTIONS_REDUCER_ID = 'transactions'
export default handleActions( export default handleActions(
{ {
[ADD_TRANSACTIONS]: (state, action) => action.payload, [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(), Map(),
) )