From 26e9c1cb67261bb838d2a37b9c1dd979854954e7 Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Fri, 22 May 2020 13:56:53 +0400 Subject: [PATCH 1/5] add transparent bg to stepper root in mui theme --- src/theme/mui.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/theme/mui.ts b/src/theme/mui.ts index 4dc10638..bee2a175 100644 --- a/src/theme/mui.ts +++ b/src/theme/mui.ts @@ -108,6 +108,7 @@ const theme = createMuiTheme({ MuiStepper: { root: { padding: `${lg} 0 0 15px`, + background: 'transparent', }, }, MuiIconButton: { From 14d9a3a78fbd6f7e913f7c855c34d86da16d73e7 Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Fri, 22 May 2020 14:33:08 +0400 Subject: [PATCH 2/5] fix required validator --- src/components/forms/validator.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/forms/validator.ts b/src/components/forms/validator.ts index a5bd54e6..93db7da9 100644 --- a/src/components/forms/validator.ts +++ b/src/components/forms/validator.ts @@ -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}` } From c8e9ef9a3aeb7a24f3a070bff1db8668bb1b039c Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Mon, 25 May 2020 15:43:00 +0400 Subject: [PATCH 3/5] fix hooks import casing --- src/routes/safe/container/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/safe/container/index.jsx b/src/routes/safe/container/index.jsx index 927aff1c..92efece0 100644 --- a/src/routes/safe/container/index.jsx +++ b/src/routes/safe/container/index.jsx @@ -5,8 +5,8 @@ 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 { 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' const INITIAL_STATE = { From 5803ca0ea48b2a34de737ed78a2ba80200837ea4 Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Mon, 25 May 2020 16:07:40 +0400 Subject: [PATCH 4/5] fix hooks import casing --- src/routes/safe/components/Balances/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/safe/components/Balances/index.tsx b/src/routes/safe/components/Balances/index.tsx index 551b73d5..105404e6 100644 --- a/src/routes/safe/components/Balances/index.tsx +++ b/src/routes/safe/components/Balances/index.tsx @@ -18,7 +18,7 @@ import CurrencyDropdown from 'src/routes/safe/components/CurrencyDropdown' import { safeFeaturesEnabledSelector, safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors' import { history } from 'src/store/index' 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 Coins = React.lazy(() => import('src/routes/safe/components/Balances/Coins')) From de4d5649551f76e104d51855c3f6474b70a51486 Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Tue, 26 May 2020 15:00:26 -0300 Subject: [PATCH 5/5] (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 --- src/config/index.ts | 14 +++- src/routes/safe/components/Balances/index.tsx | 1 + .../TxsTable/ExpandedTx/CreationTx/index.tsx | 52 ++++++++++++++ .../TxsTable/ExpandedTx/IncomingTx/index.tsx | 19 ++++++ .../TxsTable/ExpandedTx/OutgoingTx/index.tsx | 40 +++++++++++ .../TxsTable/ExpandedTx/index.tsx | 67 ++++++------------- .../Transactions/TxsTable/ExpandedTx/style.ts | 6 ++ .../Transactions/TxsTable/index.tsx | 37 ++++++---- .../components/Transactions/TxsTable/style.ts | 4 ++ .../safe/container/hooks/useLoadSafe.tsx | 2 + src/routes/safe/container/index.tsx | 2 +- .../safe/store/actions/fetchSafeCreationTx.ts | 49 ++++++++++++++ .../safe/store/actions/fetchTransactions.ts | 48 +++---------- .../transactions/addOrUpdateTransactions.ts | 6 ++ .../middleware/notificationsMiddleware.ts | 14 ++-- src/routes/safe/store/models/transaction.ts | 6 ++ src/routes/safe/store/reducer/transactions.ts | 32 ++++++++- 17 files changed, 291 insertions(+), 108 deletions(-) create mode 100644 src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx create mode 100644 src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTx/index.tsx create mode 100644 src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OutgoingTx/index.tsx create mode 100644 src/routes/safe/store/actions/fetchSafeCreationTx.ts create mode 100644 src/routes/safe/store/actions/transactions/addOrUpdateTransactions.ts diff --git a/src/config/index.ts b/src/config/index.ts index 4ad19f8b..bdcdb6dc 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -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}` +} \ No newline at end of file diff --git a/src/routes/safe/components/Balances/index.tsx b/src/routes/safe/components/Balances/index.tsx index 105404e6..1b3b8d72 100644 --- a/src/routes/safe/components/Balances/index.tsx +++ b/src/routes/safe/components/Balances/index.tsx @@ -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')) diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx new file mode 100644 index 00000000..8305985b --- /dev/null +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx @@ -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 : ( + <> + + Created: + {formatDate(tx.created)} + + + Creator: + {tx.creator ? : 'n/a'} + + + Factory: + {tx.factoryAddress ? : 'n/a'} + + + Mastercopy: + {tx.masterCopy ? : 'n/a'} + + + ) +} diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTx/index.tsx new file mode 100644 index 00000000..d0f9d383 --- /dev/null +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTx/index.tsx @@ -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 : ( + <> + + Created: + {formatDate(tx.executionDate)} + + + ) +} diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OutgoingTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OutgoingTx/index.tsx new file mode 100644 index 00000000..1fafd089 --- /dev/null +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OutgoingTx/index.tsx @@ -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 ( + <> + + Created: + {formatDate(tx.submissionDate)} + + {tx.executionDate && ( + + Executed: + {formatDate(tx.executionDate)} + + )} + {tx.refundParams && ( + + Refund: + max. {tx.refundParams.fee} {tx.refundParams.symbol} + + )} + {tx.operation === 1 && ( + + Delegate Call + + )} + {tx.operation === 2 && ( + + Contract Creation + + )} + + ) +} diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx index 447d6c69..ca1f1cef 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx @@ -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 }) => { - + Hash: {tx.executionTxHash ? : 'n/a'} - {!isIncomingTx && ( + {!isIncomingTx && !isCreationTx && ( Nonce: {tx.nonce} )} - - Fee: - {tx.fee ? tx.fee : 'n/a'} - - {isIncomingTx ? ( - <> - - Created: - {formatDate(tx.executionDate)} - - - ) : ( - <> - - Created: - {formatDate(tx.submissionDate)} - - {tx.executionDate && ( - - Executed: - {formatDate(tx.executionDate)} - - )} - {tx.refundParams && ( - - Refund: - max. {tx.refundParams.fee} {tx.refundParams.symbol} - - )} - {tx.operation === 1 && ( - - Delegate Call - - )} - {tx.operation === 2 && ( - - Contract Creation - - )} - - )} + {!isCreationTx ? ( + + Fee: + {tx.fee ? tx.fee : 'n/a'} + + ) : null} + + + - {isIncomingTx ? : } + {isIncomingTx && } + {!isIncomingTx && !isCreationTx && } + {isCreationTx && } - {!isIncomingTx && ( + {!isIncomingTx && !isCreationTx && ( ({ incomingTxBlock: { borderRight: '2px solid rgb(232, 231, 230)', }, + emptyRowDataContainer: { + paddingTop: lg, + paddingLeft: md, + paddingBottom: md, + borderRight: '2px solid rgb(232, 231, 230)', + }, }) diff --git a/src/routes/safe/components/Transactions/TxsTable/index.tsx b/src/routes/safe/components/Transactions/TxsTable/index.tsx index e5dbd6bc..59c31371 100644 --- a/src/routes/safe/components/Transactions/TxsTable/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/index.tsx @@ -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 }) => { - + {!row.tx.creationTx && ( {expandedTx === row.safeTxHash ? : } @@ -122,12 +115,30 @@ const TxsTable = ({ classes }) => { colSpan={6} style={{ paddingBottom: 0, paddingTop: 0 }} > - ( + + )} + in={expandedTx === row.tx.safeTxHash} + timeout="auto" + unmountOnExit + /> + + + )} + {row.tx.creationTx && ( + + + ( + + )} in={expandedTx === row.tx.safeTxHash} timeout="auto" - tx={row[TX_TABLE_RAW_TX_ID]} unmountOnExit /> diff --git a/src/routes/safe/components/Transactions/TxsTable/style.ts b/src/routes/safe/components/Transactions/TxsTable/style.ts index 25cf4b6d..815ab694 100644 --- a/src/routes/safe/components/Transactions/TxsTable/style.ts +++ b/src/routes/safe/components/Transactions/TxsTable/style.ts @@ -26,4 +26,8 @@ export const styles = () => ({ display: 'flex', justifyContent: 'flex-end', }, + expandCellStyle: { + paddingLeft: 0, + paddingRight: 15, + }, }) diff --git a/src/routes/safe/container/hooks/useLoadSafe.tsx b/src/routes/safe/container/hooks/useLoadSafe.tsx index a6601f10..d80d149e 100644 --- a/src/routes/safe/container/hooks/useLoadSafe.tsx +++ b/src/routes/safe/container/hooks/useLoadSafe.tsx @@ -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))) diff --git a/src/routes/safe/container/index.tsx b/src/routes/safe/container/index.tsx index eac833df..e2533b89 100644 --- a/src/routes/safe/container/index.tsx +++ b/src/routes/safe/container/index.tsx @@ -7,8 +7,8 @@ import Page from 'src/components/layout/Page' import Layout from 'src/routes/safe/components/Layout' import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors' -import { useCheckForUpdates } from './hooks/useCheckForUpdates' import { useLoadSafe } from './hooks/useLoadSafe' +import { useCheckForUpdates } from './hooks/useCheckForUpdates' const INITIAL_STATE = { sendFunds: { diff --git a/src/routes/safe/store/actions/fetchSafeCreationTx.ts b/src/routes/safe/store/actions/fetchSafeCreationTx.ts new file mode 100644 index 00000000..99a72a8b --- /dev/null +++ b/src/routes/safe/store/actions/fetchSafeCreationTx.ts @@ -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 diff --git a/src/routes/safe/store/actions/fetchTransactions.ts b/src/routes/safe/store/actions/fetchTransactions.ts index f7fb37de..6f1a0879 100644 --- a/src/routes/safe/store/actions/fetchTransactions.ts +++ b/src/routes/safe/store/actions/fetchTransactions.ts @@ -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 })) }) } diff --git a/src/routes/safe/store/actions/transactions/addOrUpdateTransactions.ts b/src/routes/safe/store/actions/transactions/addOrUpdateTransactions.ts new file mode 100644 index 00000000..ce167f40 --- /dev/null +++ b/src/routes/safe/store/actions/transactions/addOrUpdateTransactions.ts @@ -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) diff --git a/src/routes/safe/store/middleware/notificationsMiddleware.ts b/src/routes/safe/store/middleware/notificationsMiddleware.ts index bea90187..90cc66bc 100644 --- a/src/routes/safe/store/middleware/notificationsMiddleware.ts +++ b/src/routes/safe/store/middleware/notificationsMiddleware.ts @@ -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, ) diff --git a/src/routes/safe/store/models/transaction.ts b/src/routes/safe/store/models/transaction.ts index abe71a6c..577a6322 100644 --- a/src/routes/safe/store/models/transaction.ts +++ b/src/routes/safe/store/models/transaction.ts @@ -39,4 +39,10 @@ export const makeTransaction = Record({ refundParams: null, type: 'outgoing', origin: null, + created: false, + creator: '', + factoryAddress: '', + masterCopy: '', + setupData: '', + transactionHash: '', }) diff --git a/src/routes/safe/store/reducer/transactions.ts b/src/routes/safe/store/reducer/transactions.ts index a8b4f8fa..7b2472e8 100644 --- a/src/routes/safe/store/reducer/transactions.ts +++ b/src/routes/safe/store/reducer/transactions.ts @@ -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(), )