From 7ca838d0af61e0ffd1fb2425827d13f6c5f7c99b Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 25 Aug 2020 17:28:29 -0300 Subject: [PATCH 01/19] WIP: first attempt to send funds using spending limit module --- .../SendModal/screens/ReviewTx/index.tsx | 97 ++++++++++++------ .../SendModal/screens/SendFunds/index.tsx | 98 ++++++++++++++++++- 2 files changed, 160 insertions(+), 35 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx index b6de2579c6..0846db3a96 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx @@ -1,15 +1,11 @@ +import StandardToken from '@gnosis.pm/util-contracts/build/contracts/GnosisStandardToken.json' import IconButton from '@material-ui/core/IconButton' import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' -import { BigNumber } from 'bignumber.js' import { withSnackbar } from 'notistack' import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import ArrowDown from '../assets/arrow-down.svg' - -import { styles } from './style' - import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' @@ -20,19 +16,26 @@ import Hairline from 'src/components/layout/Hairline' import Img from 'src/components/layout/Img' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import { getSpendingLimitContract } from 'src/logic/contracts/safeContracts' +import createTransaction from 'src/logic/safe/store/actions/createTransaction' +import { safeSelector } from 'src/logic/safe/store/selectors' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' -import { getHumanFriendlyToken } from 'src/logic/tokens/store/actions/fetchTokens' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { ETH_ADDRESS } from 'src/logic/tokens/utils/tokenHelpers' +import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' import { getWeb3 } from 'src/logic/wallets/getWeb3' import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' +import { toTokenUnit } from 'src/routes/safe/components/Settings/SpendingLimit/utils' import { extendedSafeTokensSelector } from 'src/routes/safe/container/selector' -import createTransaction from 'src/logic/safe/store/actions/createTransaction' -import { safeSelector } from 'src/logic/safe/store/selectors' import { sm } from 'src/theme/variables' +import { AbiItem } from 'web3-utils' + +import ArrowDown from '../assets/arrow-down.svg' + +import { styles } from './style' const useStyles = makeStyles(styles as any) @@ -44,9 +47,13 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { const [gasCosts, setGasCosts] = useState('< 0.001') const [data, setData] = useState('') - const txToken = tokens.find((token) => token.address === tx.token) - const isSendingETH = txToken.address === ETH_ADDRESS - const txRecipient = isSendingETH ? tx.recipientAddress : txToken.address + const txToken = React.useMemo(() => tokens.find((token) => token.address === tx.token), [tokens, tx.token]) + const isSendingETH = React.useMemo(() => txToken.address === ETH_ADDRESS, [txToken.address]) + const txRecipient = React.useMemo(() => (isSendingETH ? tx.recipientAddress : txToken.address), [ + isSendingETH, + tx.recipientAddress, + txToken.address, + ]) useEffect(() => { let isCurrent = true @@ -56,13 +63,12 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { let txData = EMPTY_DATA - if (!isSendingETH) { - const StandardToken = await getHumanFriendlyToken() - const tokenInstance = await StandardToken.at(txToken.address) - const decimals = await tokenInstance.decimals() - const txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString() - - txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI() + if (!isSendingETH && txToken) { + const web3 = getWeb3() + const ERC20Instance = new web3.eth.Contract(StandardToken.abi as AbiItem[], txToken.address) + txData = ERC20Instance.methods + .transfer(tx.recipientAddress, toTokenUnit(tx.amount, txToken.decimals)) + .encodeABI() } const estimatedGasCosts = await estimateTxGasCosts(safeAddress, txRecipient, txData) @@ -80,26 +86,57 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { return () => { isCurrent = false } - }, [isSendingETH, safeAddress, tx.amount, tx.recipientAddress, txRecipient, txToken.address]) + }, [isSendingETH, safeAddress, tx.amount, tx.recipientAddress, txRecipient, txToken]) const submitTx = async () => { + const isSpendingLimit = tx.txType === 'spendingLimit' const web3 = getWeb3() // txAmount should be 0 if we send tokens // the real value is encoded in txData and will be used by the contract // if txAmount > 0 it would send ETH from the Safe const txAmount = isSendingETH ? web3.utils.toWei(tx.amount, 'ether') : '0' - dispatch( - createTransaction({ - safeAddress, - to: txRecipient, - valueInWei: txAmount, - txData: data, - notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX, - enqueueSnackbar, - closeSnackbar, - } as any), - ) + if (isSpendingLimit) { + const spendingLimit = getSpendingLimitContract() + const transferHash = await spendingLimit.methods + .generateTransferHash( + safeAddress, + txToken.address === ETH_ADDRESS ? ZERO_ADDRESS : txToken.address, + tx.recipientAddress, + toTokenUnit(tx.amount, txToken.decimals), + ZERO_ADDRESS, + 0, + tx.tokenSpendingLimit.nonce, + ) + .call() + const signature = await web3.eth.sign(transferHash, tx.tokenSpendingLimit.delegate) + await spendingLimit.methods + .executeAllowanceTransfer( + safeAddress, + txToken.address === ETH_ADDRESS ? ZERO_ADDRESS : txToken.address, + tx.recipientAddress, + toTokenUnit(tx.amount, txToken.decimals), + ZERO_ADDRESS, + 0, + tx.tokenSpendingLimit.delegate, + signature, + ) + .send({ from: tx.tokenSpendingLimit.delegate }) + .then(console.log) + .catch(console.error) + } else { + dispatch( + createTransaction({ + safeAddress, + to: txRecipient, + valueInWei: txAmount, + txData: data, + notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX, + enqueueSnackbar, + closeSnackbar, + } as any), + ) + } onClose() } diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx index 8b78fc79b4..02a0e1a245 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx @@ -1,3 +1,4 @@ +import { RadioButtons, Text } from '@gnosis.pm/safe-react-components' import IconButton from '@material-ui/core/IconButton' import InputAdornment from '@material-ui/core/InputAdornment' import { makeStyles } from '@material-ui/core/styles' @@ -5,6 +6,12 @@ import Close from '@material-ui/icons/Close' import React, { useState } from 'react' import { OnChange } from 'react-final-form-listeners' import { useSelector } from 'react-redux' +import { safeSpendingLimitsSelector } from 'src/logic/safe/store/selectors' +import { ETH_ADDRESS } from 'src/logic/tokens/utils/tokenHelpers' +import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' +import { userAccountSelector } from 'src/logic/wallets/store/selectors' +import { fromTokenUnit } from 'src/routes/safe/components/Settings/SpendingLimit/utils' +import styled from 'styled-components' import ArrowDown from '../assets/arrow-down.svg' @@ -33,6 +40,7 @@ import AddressBookInput from 'src/routes/safe/components/Balances/SendModal/scre import TokenSelectField from 'src/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField' import { extendedSafeTokensSelector } from 'src/routes/safe/container/selector' import { sm } from 'src/theme/variables' +import { BigNumber } from 'bignumber.js' const formMutators = { setMax: (args, state, utils) => { @@ -44,8 +52,20 @@ const formMutators = { setRecipient: (args, state, utils) => { utils.changeValue(state, 'recipientAddress', () => args[0]) }, + setTxType: (args, state, utils) => { + utils.changeValue(state, 'txType', () => args[0]) + }, } +// const txTypeDecorator + +// TODO: propose refactor in safe-react-components based on this requirements +const SpendingLimitRadioButtons = styled(RadioButtons)` + & .MuiRadio-colorPrimary.Mui-checked { + color: ${({ theme }) => theme.colors.primary}; + } +` + const useStyles = makeStyles(styles as any) const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedToken = '' }): React.ReactElement => { @@ -60,21 +80,25 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT const [pristine, setPristine] = useState(true) const [isValidAddress, setIsValidAddress] = useState(true) - React.useMemo(() => { + React.useEffect(() => { if (selectedEntry === null && pristine) { setPristine(false) } }, [selectedEntry, pristine]) + let tokenSpendingLimit const handleSubmit = (values) => { const submitValues = values // If the input wasn't modified, there was no mutation of the recipientAddress if (!values.recipientAddress) { submitValues.recipientAddress = selectedEntry.address } - onNext(submitValues) + onNext({ ...submitValues, tokenSpendingLimit }) } + const spendingLimits = useSelector(safeSpendingLimitsSelector) + const currentUser = useSelector(userAccountSelector) + return ( <> @@ -91,8 +115,16 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT {(...args) => { const formState = args[2] const mutators = args[3] - const { token: tokenAddress } = formState.values + const { token: tokenAddress, txType } = formState.values const selectedTokenRecord = tokens.find((token) => token.address === tokenAddress) + tokenSpendingLimit = + selectedTokenRecord && + spendingLimits.find( + ({ delegate, token }) => + delegate.toLowerCase() === currentUser.toLowerCase() && + (token === ZERO_ADDRESS ? ETH_ADDRESS : token.toLowerCase()) === + selectedTokenRecord.address.toLowerCase(), + ) const handleScan = (value, closeQrModal) => { let scannedAddress = value @@ -198,13 +230,55 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT /> + {tokenSpendingLimit && ( + + + Send as + + {({ input: { name, value } }) => ( + + )} + + + + )} Amount mutators.setMax(selectedTokenRecord.balance)} + onClick={() => + mutators.setMax( + tokenSpendingLimit && txType === 'spendingLimit' + ? new BigNumber(selectedTokenRecord.balance).gt( + fromTokenUnit( + new BigNumber(tokenSpendingLimit.amount).minus(tokenSpendingLimit.spent).toString(), + selectedTokenRecord.decimals, + ), + ) + ? fromTokenUnit( + new BigNumber(tokenSpendingLimit.amount).minus(tokenSpendingLimit.spent).toString(), + selectedTokenRecord.decimals, + ) + : selectedTokenRecord.balance + : selectedTokenRecord.balance, + ) + } weight="bold" testId="send-max-btn" > @@ -230,7 +304,21 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT required, mustBeFloat, minValue(0, false), - maxValue(selectedTokenRecord?.balance), + maxValue( + tokenSpendingLimit && txType === 'spendingLimit' + ? new BigNumber(selectedTokenRecord.balance).gt( + fromTokenUnit( + new BigNumber(tokenSpendingLimit.amount).minus(tokenSpendingLimit.spent).toString(), + selectedTokenRecord.decimals, + ), + ) + ? fromTokenUnit( + new BigNumber(tokenSpendingLimit.amount).minus(tokenSpendingLimit.spent).toString(), + selectedTokenRecord.decimals, + ) + : selectedTokenRecord.balance + : selectedTokenRecord?.balance, + ), )} /> From bbacc8b450dc0b474c1be4665b1f067d4b21d305 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 26 Aug 2020 15:23:28 -0300 Subject: [PATCH 02/19] fix: avoid signature request --- .../Balances/SendModal/screens/ReviewTx/index.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx index 0846db3a96..9a6e04ed4f 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx @@ -98,18 +98,6 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { if (isSpendingLimit) { const spendingLimit = getSpendingLimitContract() - const transferHash = await spendingLimit.methods - .generateTransferHash( - safeAddress, - txToken.address === ETH_ADDRESS ? ZERO_ADDRESS : txToken.address, - tx.recipientAddress, - toTokenUnit(tx.amount, txToken.decimals), - ZERO_ADDRESS, - 0, - tx.tokenSpendingLimit.nonce, - ) - .call() - const signature = await web3.eth.sign(transferHash, tx.tokenSpendingLimit.delegate) await spendingLimit.methods .executeAllowanceTransfer( safeAddress, @@ -119,7 +107,7 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { ZERO_ADDRESS, 0, tx.tokenSpendingLimit.delegate, - signature, + EMPTY_DATA, ) .send({ from: tx.tokenSpendingLimit.delegate }) .then(console.log) From d228c03d8be9d14ce1b0abded55db439d80c3ecd Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 26 Aug 2020 15:29:52 -0300 Subject: [PATCH 03/19] refactor: remove duplicated type file --- src/logic/contracts/methodIds.ts | 2 +- .../transactions/utils/transactionHelpers.ts | 2 +- .../safe/store/models/types/transaction.ts | 2 +- .../TxDescription/CustomDescription.tsx | 2 +- .../TxDescription/SettingsDescription.tsx | 2 +- .../ExpandedTx/TxDescription/utils.ts | 2 +- .../utils/newTransactionHelpers.ts | 2 +- .../transactions/utils/transferDetails.ts | 2 +- .../safe/store/models/types/transactions.d.ts | 254 ------------------ 9 files changed, 8 insertions(+), 262 deletions(-) delete mode 100644 src/routes/safe/store/models/types/transactions.d.ts diff --git a/src/logic/contracts/methodIds.ts b/src/logic/contracts/methodIds.ts index e3ab182633..c07bbe5b2a 100644 --- a/src/logic/contracts/methodIds.ts +++ b/src/logic/contracts/methodIds.ts @@ -1,5 +1,5 @@ import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3' -import { DataDecoded, METHOD_TO_ID } from 'src/routes/safe/store/models/types/transactions.d' +import { DataDecoded, METHOD_TO_ID } from 'src/logic/safe/store/models/types/transactions.d' export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => { const [methodId, params] = [data.slice(0, 10) as keyof typeof METHOD_TO_ID | string, data.slice(10)] diff --git a/src/logic/safe/store/actions/transactions/utils/transactionHelpers.ts b/src/logic/safe/store/actions/transactions/utils/transactionHelpers.ts index 5349d7b669..25a5bb8a74 100644 --- a/src/logic/safe/store/actions/transactions/utils/transactionHelpers.ts +++ b/src/logic/safe/store/actions/transactions/utils/transactionHelpers.ts @@ -34,7 +34,7 @@ import { TypedDataUtils } from 'eth-sig-util' import { Token } from 'src/logic/tokens/store/model/token' import { ProviderRecord } from 'src/logic/wallets/store/model/provider' import { SafeRecord } from 'src/logic/safe/store/models/safe' -import { DecodedParams } from 'src/routes/safe/store/models/types/transactions.d' +import { DecodedParams } from 'src/logic/safe/store/models/types/transactions.d' export const isEmptyData = (data?: string | null): boolean => { return !data || data === EMPTY_DATA diff --git a/src/logic/safe/store/models/types/transaction.ts b/src/logic/safe/store/models/types/transaction.ts index 150233b818..2bcae6c647 100644 --- a/src/logic/safe/store/models/types/transaction.ts +++ b/src/logic/safe/store/models/types/transaction.ts @@ -2,7 +2,7 @@ import { List, Map, RecordOf } from 'immutable' import { Confirmation } from './confirmation' import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d' import { DataDecoded, Transfer } from './transactions' -import { DecodedParams } from 'src/routes/safe/store/models/types/transactions' +import { DecodedParams } from 'src/logic/safe/store/models/types/transactions' export enum TransactionTypes { INCOMING = 'incoming', diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx index 9433f695cc..2917091bb1 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx @@ -27,7 +27,7 @@ import Paragraph from 'src/components/layout/Paragraph' import LinkWithRef from 'src/components/layout/Link' import { shortVersionOf } from 'src/logic/wallets/ethAddresses' import { Transaction } from 'src/logic/safe/store/models/types/transaction' -import { DataDecoded } from 'src/routes/safe/store/models/types/transactions.d' +import { DataDecoded } from 'src/logic/safe/store/models/types/transactions.d' import DividerLine from 'src/components/DividerLine' import { decodeMethods, isSetAllowanceMethod } from 'src/logic/contracts/methodIds' diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx index 974382b91e..c810267f6e 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx @@ -7,7 +7,7 @@ import Bold from 'src/components/layout/Bold' import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' import EtherscanLink from 'src/components/EtherscanLink' import Paragraph from 'src/components/layout/Paragraph' -import { SAFE_METHODS_NAMES, SafeMethods } from 'src/routes/safe/store/models/types/transactions.d' +import { SAFE_METHODS_NAMES, SafeMethods } from 'src/logic/safe/store/models/types/transactions.d' export const TRANSACTIONS_DESC_ADD_OWNER_TEST_ID = 'tx-description-add-owner' export const TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID = 'tx-description-remove-owner' diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/utils.ts b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/utils.ts index 15dabaaa70..97784c2404 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/utils.ts +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/utils.ts @@ -1,5 +1,5 @@ import { Transaction } from 'src/logic/safe/store/models/types/transaction' -import { SAFE_METHODS_NAMES } from 'src/routes/safe/store/models/types/transactions.d' +import { SAFE_METHODS_NAMES } from 'src/logic/safe/store/models/types/transactions.d' const getSafeVersion = (data) => { const contractAddress = data.substr(340, 40).toLowerCase() diff --git a/src/routes/safe/store/actions/transactions/utils/newTransactionHelpers.ts b/src/routes/safe/store/actions/transactions/utils/newTransactionHelpers.ts index d10f801a98..6766d0502c 100644 --- a/src/routes/safe/store/actions/transactions/utils/newTransactionHelpers.ts +++ b/src/routes/safe/store/actions/transactions/utils/newTransactionHelpers.ts @@ -6,7 +6,7 @@ import { Parameter, Transaction, TxType, -} from 'src/routes/safe/store/models/types/transactions.d' +} from 'src/logic/safe/store/models/types/transactions.d' export const isMultiSigTx = (tx: Transaction): tx is MultiSigTransaction => { return TxType[tx.txType] === TxType.MULTISIG_TRANSACTION diff --git a/src/routes/safe/store/actions/transactions/utils/transferDetails.ts b/src/routes/safe/store/actions/transactions/utils/transferDetails.ts index 6b00126f12..7c540df9c4 100644 --- a/src/routes/safe/store/actions/transactions/utils/transferDetails.ts +++ b/src/routes/safe/store/actions/transactions/utils/transferDetails.ts @@ -1,4 +1,4 @@ -import { Transfer, TxConstants } from 'src/routes/safe/store/models/types/transactions.d' +import { Transfer, TxConstants } from 'src/logic/safe/store/models/types/transactions.d' import { sameAddress } from 'src/logic/wallets/ethAddresses' import { store } from 'src/store' import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' diff --git a/src/routes/safe/store/models/types/transactions.d.ts b/src/routes/safe/store/models/types/transactions.d.ts deleted file mode 100644 index c1f88e7744..0000000000 --- a/src/routes/safe/store/models/types/transactions.d.ts +++ /dev/null @@ -1,254 +0,0 @@ -export enum TxConstants { - MULTI_SEND = 'multiSend', - UNKNOWN = 'UNKNOWN', -} - -export enum Operation { - CALL = 'CALL', - DELEGATE_CALL = 'DELEGATE_CALL', - CREATE = 'CREATE', -} - -// types comes from: https://github.com/gnosis/safe-client-gateway/blob/752e76b6d1d475791dbd7917b174bb41d2d9d8be/src/utils.rs -export enum TransferMethods { - TRANSFER = 'transfer', - TRANSFER_FROM = 'transferFrom', - SAFE_TRANSFER_FROM = 'safeTransferFrom', -} - -export enum SettingsChangeMethods { - SETUP = 'setup', - SET_FALLBACK_HANDLER = 'setFallbackHandler', - ADD_OWNER_WITH_THRESHOLD = 'addOwnerWithThreshold', - REMOVE_OWNER = 'removeOwner', - REMOVE_OWNER_WITH_THRESHOLD = 'removeOwnerWithThreshold', - SWAP_OWNER = 'swapOwner', - CHANGE_THRESHOLD = 'changeThreshold', - CHANGE_MASTER_COPY = 'changeMasterCopy', - ENABLE_MODULE = 'enableModule', - DISABLE_MODULE = 'disableModule', - EXEC_TRANSACTION_FROM_MODULE = 'execTransactionFromModule', - APPROVE_HASH = 'approveHash', - EXEC_TRANSACTION = 'execTransaction', -} - -// note: this extends SAFE_METHODS_NAMES in /logic/contracts/methodIds.ts, we need to figure out which one we are going to use -export type DataDecodedMethod = TransferMethods | SettingsChangeMethods | string - -export interface DecodedValue { - operation: Operation - to: string - value: number - data: string - decodedData: DataDecoded -} - -export interface SingleTransactionMethodParameter { - name: string - type: string - value: string -} - -export interface MultiSendMethodParameter extends SingleTransactionMethodParameter { - decodedValue: DecodedValue[] -} - -export type Parameter = MultiSendMethodParameter | SingleTransactionMethodParameter - -export interface DataDecoded { - method: DataDecodedMethod - parameters: Parameter[] -} - -export enum ConfirmationType { - CONFIRMATION = 'CONFIRMATION', - EXECUTION = 'EXECUTION', -} - -export enum SignatureType { - CONTRACT_SIGNATURE = 'CONTRACT_SIGNATURE', - APPROVED_HASH = 'APPROVED_HASH', - EOA = 'EOA', - ETH_SIGN = 'ETH_SIGN', -} - -export interface Confirmation { - owner: string - submissionDate: string - transactionHash: string | null - confirmationType: ConfirmationType - signature: string - signatureType: SignatureType -} - -export enum TokenType { - ERC20 = 'ERC20', - ERC721 = 'ERC721', - OTHER = 'OTHER', -} - -export interface TokenInfo { - type: TokenType - address: string - name: string - symbol: string - decimals: number - logoUri: string -} - -export enum TransferType { - ETHER_TRANSFER = 'ETHER_TRANSFER', - ERC20_TRANSFER = 'ERC20_TRANSFER', - ERC721_TRANSFER = 'ERC721_TRANSFER', - UNKNOWN = 'UNKNOWN', -} - -export interface Transfer { - type: TransferType - executionDate: string - blockNumber: number - transactionHash: string | null - to: string - value: string | null - tokenId: string | null - tokenAddress: string - tokenInfo: TokenInfo | null - from: string -} - -export enum TxType { - MULTISIG_TRANSACTION = 'MULTISIG_TRANSACTION', - ETHEREUM_TRANSACTION = 'ETHEREUM_TRANSACTION', - MODULE_TRANSACTION = 'MODULE_TRANSACTION', -} - -export interface MultiSigTransaction { - safe: string - to: string - value: string - data: string | null - operation: number - gasToken: string - safeTxGas: number - baseGas: number - gasPrice: string - refundReceiver: string - nonce: number - executionDate: string | null - submissionDate: string - modified: string - blockNumber: number | null - transactionHash: string | null - safeTxHash: string - executor: string | null - isExecuted: boolean - isSuccessful: boolean | null - ethGasPrice: string | null - gasUsed: number | null - fee: string | null - origin: string | null - dataDecoded: DataDecoded | null - confirmationsRequired: number | null - confirmations: Confirmation[] - signatures: string | null - transfers: Transfer[] - txType: TxType.MULTISIG_TRANSACTION -} - -export interface ModuleTransaction { - created: string - executionDate: string - blockNumber: number - transactionHash: string - safe: string - module: string - to: string - value: string - data: string - operation: Operation - transfers: Transfer[] - txType: TxType.MODULE_TRANSACTION -} - -export interface EthereumTransaction { - executionDate: string - to: string - data: string | null - txHash: string - blockNumber: number - transfers: Transfer[] - txType: TxType.ETHEREUM_TRANSACTION - from: string -} - -export type Transaction = MultiSigTransaction | ModuleTransaction | EthereumTransaction - -// SAFE METHODS TO ITS ID -// https://github.com/gnosis/safe-contracts/blob/development/test/safeMethodNaming.js -// https://github.com/gnosis/safe-contracts/blob/development/contracts/GnosisSafe.sol -// [ -// { name: "addOwnerWithThreshold", id: "0x0d582f13" }, -// { name: "DOMAIN_SEPARATOR_TYPEHASH", id: "0x1db61b54" }, -// { name: "isOwner", id: "0x2f54bf6e" }, -// { name: "execTransactionFromModule", id: "0x468721a7" }, -// { name: "signedMessages", id: "0x5ae6bd37" }, -// { name: "enableModule", id: "0x610b5925" }, -// { name: "changeThreshold", id: "0x694e80c3" }, -// { name: "approvedHashes", id: "0x7d832974" }, -// { name: "changeMasterCopy", id: "0x7de7edef" }, -// { name: "SENTINEL_MODULES", id: "0x85e332cd" }, -// { name: "SENTINEL_OWNERS", id: "0x8cff6355" }, -// { name: "getOwners", id: "0xa0e67e2b" }, -// { name: "NAME", id: "0xa3f4df7e" }, -// { name: "nonce", id: "0xaffed0e0" }, -// { name: "getModules", id: "0xb2494df3" }, -// { name: "SAFE_MSG_TYPEHASH", id: "0xc0856ffc" }, -// { name: "SAFE_TX_TYPEHASH", id: "0xccafc387" }, -// { name: "disableModule", id: "0xe009cfde" }, -// { name: "swapOwner", id: "0xe318b52b" }, -// { name: "getThreshold", id: "0xe75235b8" }, -// { name: "domainSeparator", id: "0xf698da25" }, -// { name: "removeOwner", id: "0xf8dc5dd9" }, -// { name: "VERSION", id: "0xffa1ad74" }, -// { name: "setup", id: "0xa97ab18a" }, -// { name: "execTransaction", id: "0x6a761202" }, -// { name: "requiredTxGas", id: "0xc4ca3a9c" }, -// { name: "approveHash", id: "0xd4d9bdcd" }, -// { name: "signMessage", id: "0x85a5affe" }, -// { name: "isValidSignature", id: "0x20c13b0b" }, -// { name: "getMessageHash", id: "0x0a1028c4" }, -// { name: "encodeTransactionData", id: "0xe86637db" }, -// { name: "getTransactionHash", id: "0xd8d11f78" } -// ] - -export const SAFE_METHODS_NAMES = { - ADD_OWNER_WITH_THRESHOLD: 'addOwnerWithThreshold', - CHANGE_THRESHOLD: 'changeThreshold', - REMOVE_OWNER: 'removeOwner', - SWAP_OWNER: 'swapOwner', - ENABLE_MODULE: 'enableModule', - DISABLE_MODULE: 'disableModule', -} - -export const METHOD_TO_ID = { - '0xe318b52b': SAFE_METHODS_NAMES.SWAP_OWNER, - '0x0d582f13': SAFE_METHODS_NAMES.ADD_OWNER_WITH_THRESHOLD, - '0xf8dc5dd9': SAFE_METHODS_NAMES.REMOVE_OWNER, - '0x694e80c3': SAFE_METHODS_NAMES.CHANGE_THRESHOLD, - '0x610b5925': SAFE_METHODS_NAMES.ENABLE_MODULE, - '0xe009cfde': SAFE_METHODS_NAMES.DISABLE_MODULE, -} - -export type SafeMethods = typeof SAFE_METHODS_NAMES[keyof typeof SAFE_METHODS_NAMES] - -type TokenMethods = 'transfer' | 'transferFrom' | 'safeTransferFrom' - -type SafeDecodedParams = { - [key in SafeMethods]?: Record -} - -type TokenDecodedParams = { - [key in TokenMethods]?: Record -} - -export type DecodedParams = SafeDecodedParams | TokenDecodedParams | null From 3380816b4405ae0afd59b3cbe981e44fa3ef1798 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 26 Aug 2020 15:47:27 -0300 Subject: [PATCH 04/19] add missing type to parameters --- src/logic/contracts/methodIds.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/logic/contracts/methodIds.ts b/src/logic/contracts/methodIds.ts index c07bbe5b2a..61a26222c5 100644 --- a/src/logic/contracts/methodIds.ts +++ b/src/logic/contracts/methodIds.ts @@ -180,8 +180,8 @@ export const decodeMethods = (data: string): DataDecoded | null => { return { method: 'transfer', parameters: [ - { name: 'to', type: '', value: decodeParameters[0] }, - { name: 'value', type: '', value: decodeParameters[1] }, + { name: 'to', type: 'address', value: decodeParameters[0] }, + { name: 'value', type: 'uint', value: decodeParameters[1] }, ], } } @@ -192,9 +192,9 @@ export const decodeMethods = (data: string): DataDecoded | null => { return { method: 'transferFrom', parameters: [ - { name: 'from', type: '', value: decodeParameters[0] }, - { name: 'to', type: '', value: decodeParameters[1] }, - { name: 'value', type: '', value: decodeParameters[2] }, + { name: 'from', type: 'address', value: decodeParameters[0] }, + { name: 'to', type: 'address', value: decodeParameters[1] }, + { name: 'value', type: 'uint', value: decodeParameters[2] }, ], } } @@ -205,9 +205,9 @@ export const decodeMethods = (data: string): DataDecoded | null => { return { method: 'safeTransferFrom', parameters: [ - { name: 'from', type: '', value: decodedParameters[0] }, - { name: 'to', type: '', value: decodedParameters[1] }, - { name: 'value', type: '', value: decodedParameters[2] }, + { name: 'from', type: 'address', value: decodedParameters[0] }, + { name: 'to', type: 'address', value: decodedParameters[1] }, + { name: 'value', type: 'uint', value: decodedParameters[2] }, ], } } From 96d0776f2ba51c74ad98a871e121e0bf3e41c789 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 26 Aug 2020 16:33:28 -0300 Subject: [PATCH 05/19] load module transactions in the store --- src/config/index.ts | 3 ++ .../store/actions/addModuleTransactions.ts | 13 ++++++ .../fetchTransactions/fetchTransactions.ts | 12 ++++- .../transactions/fetchTransactions/index.ts | 8 ++++ .../loadModuleTransactions.ts | 44 +++++++++++++++++++ .../safe/store/models/types/transaction.ts | 1 + .../safe/store/reducer/moduleTransactions.ts | 31 +++++++++++++ .../safe/transactions/moduleTxHistory.ts | 10 +++++ .../Transactions/TxsTable/TxType/index.tsx | 1 + src/store/index.ts | 6 +++ 10 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/logic/safe/store/actions/addModuleTransactions.ts create mode 100644 src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts create mode 100644 src/logic/safe/store/reducer/moduleTransactions.ts create mode 100644 src/logic/safe/transactions/moduleTxHistory.ts diff --git a/src/config/index.ts b/src/config/index.ts index 929cbf1b38..314f5cf0a1 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -56,6 +56,9 @@ export const getTxServiceHost = () => { export const getTxServiceUriFrom = (safeAddress) => `safes/${safeAddress}/transactions/` +export const getModuleTxServiceUriFrom = (safeAddress) => + `safes/${safeAddress}/module-transactions/` + export const getIncomingTxServiceUriTo = (safeAddress) => `safes/${safeAddress}/incoming-transfers/` diff --git a/src/logic/safe/store/actions/addModuleTransactions.ts b/src/logic/safe/store/actions/addModuleTransactions.ts new file mode 100644 index 0000000000..6c26cb5971 --- /dev/null +++ b/src/logic/safe/store/actions/addModuleTransactions.ts @@ -0,0 +1,13 @@ +import { createAction } from 'redux-actions' +import { ModuleTxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions' + +export const ADD_MODULE_TRANSACTIONS = 'ADD_MODULE_TRANSACTIONS' + +export type AddModuleTransactionsAction = { + payload: { + safeAddress: string + modules: ModuleTxServiceModel[] + } +} + +export const addModuleTransactions = createAction(ADD_MODULE_TRANSACTIONS) diff --git a/src/logic/safe/store/actions/transactions/fetchTransactions/fetchTransactions.ts b/src/logic/safe/store/actions/transactions/fetchTransactions/fetchTransactions.ts index d413879c4f..59236c9949 100644 --- a/src/logic/safe/store/actions/transactions/fetchTransactions/fetchTransactions.ts +++ b/src/logic/safe/store/actions/transactions/fetchTransactions/fetchTransactions.ts @@ -2,17 +2,25 @@ import axios from 'axios' import { buildTxServiceUrl } from 'src/logic/safe/transactions' import { buildIncomingTxServiceUrl } from 'src/logic/safe/transactions/incomingTxHistory' +import { buildModuleTxServiceUrl } from 'src/logic/safe/transactions/moduleTxHistory' import { TxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions' import { IncomingTxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadIncomingTransactions' import { TransactionTypes } from 'src/logic/safe/store/models/types/transaction' +import { ModuleTxServiceModel } from './loadModuleTransactions' const getServiceUrl = (txType: string, safeAddress: string): string => { return { [TransactionTypes.INCOMING]: buildIncomingTxServiceUrl, [TransactionTypes.OUTGOING]: buildTxServiceUrl, + [TransactionTypes.MODULE]: buildModuleTxServiceUrl, }[txType](safeAddress) } +async function fetchTransactions( + txType: TransactionTypes.MODULE, + safeAddress: string, + eTag: string | null, +): Promise<{ eTag: string | null; results: ModuleTxServiceModel[] }> async function fetchTransactions( txType: TransactionTypes.INCOMING, safeAddress: string, @@ -24,10 +32,10 @@ async function fetchTransactions( eTag: string | null, ): Promise<{ eTag: string | null; results: TxServiceModel[] }> async function fetchTransactions( - txType: TransactionTypes.INCOMING | TransactionTypes.OUTGOING, + txType: TransactionTypes.MODULE | TransactionTypes.INCOMING | TransactionTypes.OUTGOING, safeAddress: string, eTag: string | null, -): Promise<{ eTag: string; results: TxServiceModel[] | IncomingTxServiceModel[] }> { +): Promise<{ eTag: string; results: ModuleTxServiceModel[] | TxServiceModel[] | IncomingTxServiceModel[] }> { try { const url = getServiceUrl(txType, safeAddress) const response = await axios.get(url, eTag ? { headers: { 'If-None-Match': eTag } } : undefined) diff --git a/src/logic/safe/store/actions/transactions/fetchTransactions/index.ts b/src/logic/safe/store/actions/transactions/fetchTransactions/index.ts index 07161d800e..a383f9a611 100644 --- a/src/logic/safe/store/actions/transactions/fetchTransactions/index.ts +++ b/src/logic/safe/store/actions/transactions/fetchTransactions/index.ts @@ -6,10 +6,12 @@ import { backOff } from 'exponential-backoff' import { addIncomingTransactions } from '../../addIncomingTransactions' import { loadIncomingTransactions } from './loadIncomingTransactions' +import { loadModuleTransactions } from './loadModuleTransactions' import { loadOutgoingTransactions } from './loadOutgoingTransactions' import { addOrUpdateCancellationTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateCancellationTransactions' import { addOrUpdateTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateTransactions' +import { addModuleTransactions } from 'src/logic/safe/store/actions/addModuleTransactions' import { AppReduxState } from 'src/store' const noFunc = () => {} @@ -43,6 +45,12 @@ export default (safeAddress: string): ThunkAction, AppReduxState, if (incomingTransactions.get(safeAddress).size) { dispatch(addIncomingTransactions(incomingTransactions)) } + + const moduleTransactions = await loadModuleTransactions(safeAddress) + + if (moduleTransactions) { + dispatch(addModuleTransactions({ modules: moduleTransactions, safeAddress })) + } } catch (error) { console.log('Error fetching transactions:', error) } diff --git a/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts b/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts new file mode 100644 index 0000000000..89b4c05423 --- /dev/null +++ b/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts @@ -0,0 +1,44 @@ +import { SAFE_REDUCER_ID } from 'src/logic/safe/store/reducer/safe' +import { store } from 'src/store' +import fetchTransactions from 'src/logic/safe/store/actions/transactions/fetchTransactions/fetchTransactions' +import { TransactionTypes } from 'src/logic/safe/store/models/types/transaction' +import { DataDecoded, Operation } from 'src/logic/safe/store/models/types/transactions.d' + +export type ModuleTxServiceModel = { + created: string + executionDate: string + blockNumber: number + transactionHash: string + safe: string + module: string + to: string + value: string + data: string + operation: Operation + dataDecoded: DataDecoded +} + +let previousETag = null +export const loadModuleTransactions = async (safeAddress: string): Promise => { + const defaultResponse = [] + const state = store.getState() + + if (!safeAddress) { + return defaultResponse + } + + const safe = state[SAFE_REDUCER_ID].getIn(['safes', safeAddress]) + + if (!safe) { + return defaultResponse + } + + const { eTag, results }: { eTag: string | null; results: ModuleTxServiceModel[] } = await fetchTransactions( + TransactionTypes.MODULE, + safeAddress, + previousETag, + ) + previousETag = eTag + + return results +} diff --git a/src/logic/safe/store/models/types/transaction.ts b/src/logic/safe/store/models/types/transaction.ts index 2bcae6c647..eeed5fd181 100644 --- a/src/logic/safe/store/models/types/transaction.ts +++ b/src/logic/safe/store/models/types/transaction.ts @@ -14,6 +14,7 @@ export enum TransactionTypes { UPGRADE = 'upgrade', TOKEN = 'token', COLLECTIBLE = 'collectible', + MODULE = 'module', } export type TransactionTypeValues = typeof TransactionTypes[keyof typeof TransactionTypes] diff --git a/src/logic/safe/store/reducer/moduleTransactions.ts b/src/logic/safe/store/reducer/moduleTransactions.ts new file mode 100644 index 0000000000..b01fb71cc7 --- /dev/null +++ b/src/logic/safe/store/reducer/moduleTransactions.ts @@ -0,0 +1,31 @@ +import { handleActions } from 'redux-actions' + +import { + ADD_MODULE_TRANSACTIONS, + AddModuleTransactionsAction, +} from 'src/logic/safe/store/actions/addModuleTransactions' +import { ModuleTxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions' + +export const MODULE_TRANSACTIONS_REDUCER_ID = 'moduleTransactions' + +export interface ModuleTransactionsState { + [safeAddress: string]: ModuleTxServiceModel[] +} + +export default handleActions( + { + [ADD_MODULE_TRANSACTIONS]: (state: ModuleTransactionsState, action: AddModuleTransactionsAction) => { + const { modules, safeAddress } = action.payload + const oldState = state[safeAddress] + + return { + ...state, + [safeAddress]: { + ...oldState, + ...modules, + }, + } + }, + }, + {}, +) diff --git a/src/logic/safe/transactions/moduleTxHistory.ts b/src/logic/safe/transactions/moduleTxHistory.ts new file mode 100644 index 0000000000..2f8f52c494 --- /dev/null +++ b/src/logic/safe/transactions/moduleTxHistory.ts @@ -0,0 +1,10 @@ +import { getModuleTxServiceUriFrom, getTxServiceHost } from 'src/config' +import { checksumAddress } from 'src/utils/checksumAddress' + +export const buildModuleTxServiceUrl = (safeAddress: string): string => { + const host = getTxServiceHost() + const address = checksumAddress(safeAddress) + const base = getModuleTxServiceUriFrom(address) + + return `${host}${base}` +} diff --git a/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx b/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx index fbe5a86079..a92d5a566d 100644 --- a/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx @@ -32,6 +32,7 @@ const typeToLabel = { creation: 'Safe created', cancellation: 'Cancellation transaction', upgrade: 'Contract Upgrade', + module: 'Module transaction', } interface TxTypeProps { diff --git a/src/store/index.ts b/src/store/index.ts index 8fc2340868..e3dc4491c9 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -38,6 +38,10 @@ import transactions, { TRANSACTIONS_REDUCER_ID } from 'src/logic/safe/store/redu import { NFTAssets, NFTTokens } from 'src/logic/collectibles/sources/OpenSea' import { SafeReducerMap } from 'src/routes/safe/store/reducer/types/safe' import allTransactions, { TRANSACTIONS, TransactionsState } from '../logic/safe/store/reducer/allTransactions' +import moduleTransactions, { + MODULE_TRANSACTIONS_REDUCER_ID, + ModuleTransactionsState, +} from 'src/logic/safe/store/reducer/moduleTransactions' export const history = createHashHistory() @@ -65,6 +69,7 @@ const reducers = combineReducers({ [TRANSACTIONS_REDUCER_ID]: transactions, [CANCELLATION_TRANSACTIONS_REDUCER_ID]: cancellationTransactions, [INCOMING_TRANSACTIONS_REDUCER_ID]: incomingTransactions, + [MODULE_TRANSACTIONS_REDUCER_ID]: moduleTransactions, [NOTIFICATIONS_REDUCER_ID]: notifications, [CURRENCY_VALUES_KEY]: currencyValues, [COOKIES_REDUCER_ID]: cookies, @@ -82,6 +87,7 @@ export type AppReduxState = CombinedState<{ [TRANSACTIONS_REDUCER_ID]: Map [CANCELLATION_TRANSACTIONS_REDUCER_ID]: CancellationTxState [INCOMING_TRANSACTIONS_REDUCER_ID]: Map + [MODULE_TRANSACTIONS_REDUCER_ID]: ModuleTransactionsState [NOTIFICATIONS_REDUCER_ID]: Map [CURRENCY_VALUES_KEY]: CurrencyValuesState [COOKIES_REDUCER_ID]: Map From 6411d5d2a75c6c2f9dfe31c2686f77aaab87d166 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 28 Aug 2020 17:00:56 -0300 Subject: [PATCH 06/19] refactor: move logic and types from `routes` to `logic` --- .../transactions/utils/multiSendDecodedDetails.ts | 4 ++-- .../transactions/utils/newTransactionHelpers.ts | 0 .../transactions/utils/newTransactionsHelpers.ts | 11 ----------- .../actions/transactions/utils/transferDetails.d.ts | 0 .../actions/transactions/utils/transferDetails.ts | 2 +- .../ExpandedTx/TxDescription/CustomDescription.tsx | 2 +- 6 files changed, 4 insertions(+), 15 deletions(-) rename src/{routes => logic}/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts (91%) rename src/{routes => logic}/safe/store/actions/transactions/utils/newTransactionHelpers.ts (100%) delete mode 100644 src/logic/safe/store/actions/transactions/utils/newTransactionsHelpers.ts rename src/{routes => logic}/safe/store/actions/transactions/utils/transferDetails.d.ts (100%) rename src/{routes => logic}/safe/store/actions/transactions/utils/transferDetails.ts (97%) diff --git a/src/routes/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts b/src/logic/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts similarity index 91% rename from src/routes/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts rename to src/logic/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts index a0799b2253..e9c0e19e2d 100644 --- a/src/routes/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts +++ b/src/logic/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts @@ -12,8 +12,8 @@ import { extractERC721TransferDetails, extractETHTransferDetails, extractUnknownTransferDetails, -} from './transferDetails' -import { isMultiSendParameter } from './newTransactionHelpers' +} from 'src/logic/safe/store/actions/transactions/utils/transferDetails' +import { isMultiSendParameter } from 'src/logic/safe/store/actions/transactions/utils/newTransactionHelpers' import { Transaction } from 'src/logic/safe/store/models/types/transaction' export type MultiSendDetails = { diff --git a/src/routes/safe/store/actions/transactions/utils/newTransactionHelpers.ts b/src/logic/safe/store/actions/transactions/utils/newTransactionHelpers.ts similarity index 100% rename from src/routes/safe/store/actions/transactions/utils/newTransactionHelpers.ts rename to src/logic/safe/store/actions/transactions/utils/newTransactionHelpers.ts diff --git a/src/logic/safe/store/actions/transactions/utils/newTransactionsHelpers.ts b/src/logic/safe/store/actions/transactions/utils/newTransactionsHelpers.ts deleted file mode 100644 index dbf8c096f4..0000000000 --- a/src/logic/safe/store/actions/transactions/utils/newTransactionsHelpers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Transaction, TxType } from 'src/logic/safe/store/models/types/transactions' - -export const isMultiSigTx = (tx: Transaction): boolean => { - return TxType[tx.txType] === TxType.MULTISIG_TRANSACTION -} -export const isModuleTx = (tx: Transaction): boolean => { - return TxType[tx.txType] === TxType.MODULE_TRANSACTION -} -export const isEthereumTx = (tx: Transaction): boolean => { - return TxType[tx.txType] === TxType.ETHEREUM_TRANSACTION -} diff --git a/src/routes/safe/store/actions/transactions/utils/transferDetails.d.ts b/src/logic/safe/store/actions/transactions/utils/transferDetails.d.ts similarity index 100% rename from src/routes/safe/store/actions/transactions/utils/transferDetails.d.ts rename to src/logic/safe/store/actions/transactions/utils/transferDetails.d.ts diff --git a/src/routes/safe/store/actions/transactions/utils/transferDetails.ts b/src/logic/safe/store/actions/transactions/utils/transferDetails.ts similarity index 97% rename from src/routes/safe/store/actions/transactions/utils/transferDetails.ts rename to src/logic/safe/store/actions/transactions/utils/transferDetails.ts index 7c540df9c4..05424e51eb 100644 --- a/src/routes/safe/store/actions/transactions/utils/transferDetails.ts +++ b/src/logic/safe/store/actions/transactions/utils/transferDetails.ts @@ -7,7 +7,7 @@ import { ERC721TransferDetails, ETHTransferDetails, UnknownTransferDetails, -} from './transferDetails.d' +} from 'src/logic/safe/store/actions/transactions/utils/transferDetails.d' import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue' const isIncomingTransfer = (transfer: Transfer): boolean => { diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx index 2917091bb1..c038cf113a 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx @@ -15,7 +15,7 @@ import Block from 'src/components/layout/Block' import { extractMultiSendDecodedData, MultiSendDetails, -} from 'src/routes/safe/store/actions/transactions/utils/multiSendDecodedDetails' +} from 'src/logic/safe/store/actions/transactions/utils/multiSendDecodedDetails' import Bold from 'src/components/layout/Bold' import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' import EtherscanLink from 'src/components/EtherscanLink' From e1008767dc1048b995e1fbd01bcfb655a06d1387 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 28 Aug 2020 17:12:22 -0300 Subject: [PATCH 07/19] update module's reducer to only add new txs - also set a default value for oldTxs --- src/logic/safe/store/reducer/moduleTransactions.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/logic/safe/store/reducer/moduleTransactions.ts b/src/logic/safe/store/reducer/moduleTransactions.ts index b01fb71cc7..e5de69c58c 100644 --- a/src/logic/safe/store/reducer/moduleTransactions.ts +++ b/src/logic/safe/store/reducer/moduleTransactions.ts @@ -16,14 +16,15 @@ export default handleActions( { [ADD_MODULE_TRANSACTIONS]: (state: ModuleTransactionsState, action: AddModuleTransactionsAction) => { const { modules, safeAddress } = action.payload - const oldState = state[safeAddress] + const oldModuleTxs = state[safeAddress] ?? [] + const oldModuleTxsHashes = oldModuleTxs.map(({ transactionHash }) => transactionHash) + // filtering in this level happens, because backend is returning the whole list of txs + // so, to avoid re-storing all the txs, those already stored are filtered out + const newModuleTxs = modules.filter((moduleTx) => !oldModuleTxsHashes.includes(moduleTx.transactionHash)) return { ...state, - [safeAddress]: { - ...oldState, - ...modules, - }, + [safeAddress]: [...oldModuleTxs, ...newModuleTxs], } }, }, From 7d5cd59d3448d456dc32c0179290c797d947a281 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 28 Aug 2020 18:33:55 -0300 Subject: [PATCH 08/19] define type for SafeModuleTransaction --- src/logic/safe/store/models/types/transaction.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/logic/safe/store/models/types/transaction.ts b/src/logic/safe/store/models/types/transaction.ts index eeed5fd181..41cbb468c4 100644 --- a/src/logic/safe/store/models/types/transaction.ts +++ b/src/logic/safe/store/models/types/transaction.ts @@ -1,4 +1,6 @@ import { List, Map, RecordOf } from 'immutable' +import { ModuleTxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions' +import { Token } from 'src/logic/tokens/store/model/token' import { Confirmation } from './confirmation' import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d' import { DataDecoded, Transfer } from './transactions' @@ -15,6 +17,7 @@ export enum TransactionTypes { TOKEN = 'token', COLLECTIBLE = 'collectible', MODULE = 'module', + SPENDING_LIMIT = 'spendingLimit', } export type TransactionTypeValues = typeof TransactionTypes[keyof typeof TransactionTypes] @@ -101,3 +104,13 @@ export type TxArgs = { to: string valueInWei: string } + +export type SafeModuleTransaction = ModuleTxServiceModel & { + nonce?: string // not required for this tx: added for compatibility + fee?: number // not required for this tx: added for compatibility + executionTxHash?: string // not required for this tx: added for compatibility + safeTxHash: string // table uses this key as a unique row identifier, added for compatibility + status: TransactionStatus + type: TransactionTypes + tokenInfo?: Token +} From f7f3dc2bb1eda5bb9a903058d2c5df84b7ac3a61 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 28 Aug 2020 18:35:15 -0300 Subject: [PATCH 09/19] add moduleTxs to the list of txs - create moduleTxSelector and fill the required data to build txTable --- src/logic/safe/store/selectors/index.ts | 53 +++++++++++++++++-- .../safe/store/selectors/transactions.ts | 12 +++-- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/logic/safe/store/selectors/index.ts b/src/logic/safe/store/selectors/index.ts index f133288d46..f12140eef7 100644 --- a/src/logic/safe/store/selectors/index.ts +++ b/src/logic/safe/store/selectors/index.ts @@ -1,20 +1,28 @@ import { List, Map, Set } from 'immutable' import { matchPath, RouteComponentProps } from 'react-router-dom' import { createSelector } from 'reselect' -import { SAFELIST_ADDRESS, SAFE_PARAM_ADDRESS } from 'src/routes/routes' +import makeSafe, { SafeRecord, SafeRecordProps } from 'src/logic/safe/store/models/safe' +import { + SafeModuleTransaction, + TransactionStatus, + TransactionTypes, +} from 'src/logic/safe/store/models/types/transaction' import { CANCELLATION_TRANSACTIONS_REDUCER_ID, CancellationTransactions, } from 'src/logic/safe/store/reducer/cancellationTransactions' import { INCOMING_TRANSACTIONS_REDUCER_ID } from 'src/logic/safe/store/reducer/incomingTransactions' +import { MODULE_TRANSACTIONS_REDUCER_ID } from 'src/logic/safe/store/reducer/moduleTransactions' import { SAFE_REDUCER_ID } from 'src/logic/safe/store/reducer/safe' import { TRANSACTIONS_REDUCER_ID } from 'src/logic/safe/store/reducer/transactions' +import { tokenListSelector } from 'src/logic/tokens/store/selectors' +import { sameAddress } from 'src/logic/wallets/ethAddresses' +import { SAFE_PARAM_ADDRESS, SAFELIST_ADDRESS } from 'src/routes/routes' +import { SafesMap } from 'src/routes/safe/store/reducer/types/safe' import { AppReduxState } from 'src/store' - import { checksumAddress } from 'src/utils/checksumAddress' -import makeSafe, { SafeRecord, SafeRecordProps } from '../models/safe' -import { SafesMap } from 'src/routes/safe/store/reducer/types/safe' +import { SPENDING_LIMIT_MODULE_ADDRESS } from 'src/utils/constants' const safesStateSelector = (state: AppReduxState) => state[SAFE_REDUCER_ID] @@ -36,6 +44,8 @@ const cancellationTransactionsSelector = (state: AppReduxState) => state[CANCELL const incomingTransactionsSelector = (state: AppReduxState) => state[INCOMING_TRANSACTIONS_REDUCER_ID] +const moduleTransactionsSelector = (state: AppReduxState) => state[MODULE_TRANSACTIONS_REDUCER_ID] + export const safeParamAddressFromStateSelector = (state: AppReduxState): string | null => { const match = matchPath<{ safeAddress: string }>(state.router.location.pathname, { path: `${SAFELIST_ADDRESS}/:safeAddress`, @@ -114,6 +124,41 @@ export const safeIncomingTransactionsSelector = createSelector( }, ) +export const safeModuleTransactionsSelector = createSelector( + tokenListSelector, + moduleTransactionsSelector, + safeParamAddressFromStateSelector, + (tokens, moduleTransactions, safeAddress): SafeModuleTransaction[] => { + // no module tx for the current safe so far + if (!moduleTransactions || !safeAddress || !moduleTransactions[safeAddress]) { + return [] + } + + return moduleTransactions[safeAddress]?.map((moduleTx) => { + // if not spendingLimit module tx, then it's an generic module tx + const type = sameAddress(moduleTx.module, SPENDING_LIMIT_MODULE_ADDRESS) + ? TransactionTypes.SPENDING_LIMIT + : TransactionTypes.MODULE + + // TODO: this is strictly attached to Spending Limit Module. + // This has to be moved nearest the module info rendering. + // add token info to the model, so data can be properly displayed in the UI + let tokenInfo + if (type === TransactionTypes.SPENDING_LIMIT) { + tokenInfo = tokens.find(({ address }) => sameAddress(address, moduleTx.to)) + } + + return { + ...moduleTx, + safeTxHash: moduleTx.transactionHash, + status: TransactionStatus.SUCCESS, + tokenInfo, + type, + } + }) + }, +) + export const safeSelector = createSelector( safesMapSelector, safeParamAddressFromStateSelector, diff --git a/src/logic/safe/store/selectors/transactions.ts b/src/logic/safe/store/selectors/transactions.ts index 123f360edc..6a6c275f45 100644 --- a/src/logic/safe/store/selectors/transactions.ts +++ b/src/logic/safe/store/selectors/transactions.ts @@ -1,11 +1,17 @@ import { List } from 'immutable' import { createSelector } from 'reselect' -import { safeIncomingTransactionsSelector, safeTransactionsSelector } from 'src/logic/safe/store/selectors' -import { Transaction } from 'src/logic/safe/store/models/types/transaction' +import { + safeIncomingTransactionsSelector, + safeTransactionsSelector, + safeModuleTransactionsSelector, +} from 'src/logic/safe/store/selectors' +import { Transaction, SafeModuleTransaction } from 'src/logic/safe/store/models/types/transaction' export const extendedTransactionsSelector = createSelector( safeTransactionsSelector, safeIncomingTransactionsSelector, - (transactions, incomingTransactions): List => List([...transactions, ...incomingTransactions]), + safeModuleTransactionsSelector, + (transactions, incomingTransactions, moduleTransactions): List => + List([...transactions, ...incomingTransactions, ...moduleTransactions]), ) From 0aa49d9b4798eaf52b5e2df60fc71ba0ccc7d0f4 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 28 Aug 2020 18:53:24 -0300 Subject: [PATCH 10/19] add ExpandedModuleTx component - also, renamed the `ExpandedTx` directory to `ExpandedTxRow` - the component exported from `ExpandedTxRow` now decides whether to display `ExpandedTx` or `ExpandedModuleTx` --- .../ApproveTxModal/index.tsx | 0 .../ApproveTxModal/style.ts | 0 .../CreationTx/index.tsx | 0 .../IncomingTx/index.tsx | 0 .../IncomingTxDescription/index.tsx | 0 .../OutgoingTx/index.tsx | 0 .../OwnersColumn/OwnerComponent.tsx | 0 .../OwnersColumn/OwnersList.tsx | 0 .../assets/cancel-small-filled.svg | 0 .../assets/check-large-filled-green.svg | 0 .../assets/check-large-filled-red.svg | 0 .../assets/confirm-large-green.svg | 0 .../assets/confirm-large-grey.svg | 0 .../OwnersColumn/assets/confirm-large-red.svg | 0 .../assets/confirm-small-filled.svg | 0 .../assets/confirm-small-green.svg | 0 .../assets/confirm-small-grey.svg | 0 .../OwnersColumn/assets/confirm-small-red.svg | 0 .../assets/confirm-small-yellow.svg | 0 .../OwnersColumn/index.tsx | 0 .../OwnersColumn/style.ts | 0 .../RejectTxModal/index.tsx | 0 .../RejectTxModal/style.ts | 0 .../TxDescription/CustomDescription.tsx | 22 +++++--- .../TxDescription/SettingsDescription.tsx | 0 .../TxDescription/TransferDescription.tsx | 0 .../TxDescription/Value.tsx | 0 .../TxDescription/index.tsx | 0 .../TxDescription/styles.ts | 0 .../TxDescription/utils.ts | 0 .../{ExpandedTx => ExpandedTxRow}/index.tsx | 54 ++++++++++++++++++- .../{ExpandedTx => ExpandedTxRow}/style.ts | 0 .../Transactions/TxsTable/TxType/index.tsx | 3 ++ .../Transactions/TxsTable/columns.tsx | 44 ++++++++++++--- .../Transactions/TxsTable/index.tsx | 26 +++++---- src/test/safe.dom.funds.thresholdGt1.ts | 4 +- .../transactions/transactionList.helper.ts | 4 +- 37 files changed, 121 insertions(+), 36 deletions(-) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/ApproveTxModal/index.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/ApproveTxModal/style.ts (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/CreationTx/index.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/IncomingTx/index.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/IncomingTxDescription/index.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OutgoingTx/index.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/OwnerComponent.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/OwnersList.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/cancel-small-filled.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/check-large-filled-green.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/check-large-filled-red.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/confirm-large-green.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/confirm-large-grey.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/confirm-large-red.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/confirm-small-filled.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/confirm-small-green.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/confirm-small-grey.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/confirm-small-red.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/assets/confirm-small-yellow.svg (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/index.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/OwnersColumn/style.ts (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/RejectTxModal/index.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/RejectTxModal/style.ts (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/TxDescription/CustomDescription.tsx (93%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/TxDescription/SettingsDescription.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/TxDescription/TransferDescription.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/TxDescription/Value.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/TxDescription/index.tsx (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/TxDescription/styles.ts (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/TxDescription/utils.ts (100%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/index.tsx (72%) rename src/routes/safe/components/Transactions/TxsTable/{ExpandedTx => ExpandedTxRow}/style.ts (100%) diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/ApproveTxModal/index.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/ApproveTxModal/index.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/style.ts b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/ApproveTxModal/style.ts similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/style.ts rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/ApproveTxModal/style.ts diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/CreationTx/index.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/CreationTx/index.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/IncomingTx/index.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTx/index.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/IncomingTx/index.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/IncomingTxDescription/index.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/IncomingTxDescription/index.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OutgoingTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OutgoingTx/index.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OutgoingTx/index.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OutgoingTx/index.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/OwnerComponent.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/OwnerComponent.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/OwnerComponent.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/OwnerComponent.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/OwnersList.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/OwnersList.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/OwnersList.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/OwnersList.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/cancel-small-filled.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/cancel-small-filled.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/cancel-small-filled.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/cancel-small-filled.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/check-large-filled-green.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/check-large-filled-green.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/check-large-filled-green.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/check-large-filled-green.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/check-large-filled-red.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/check-large-filled-red.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/check-large-filled-red.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/check-large-filled-red.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-large-green.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-large-green.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-large-green.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-large-green.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-large-grey.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-large-grey.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-large-grey.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-large-grey.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-large-red.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-large-red.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-large-red.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-large-red.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-small-filled.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-small-filled.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-small-filled.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-small-filled.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-small-green.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-small-green.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-small-green.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-small-green.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-small-grey.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-small-grey.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-small-grey.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-small-grey.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-small-red.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-small-red.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-small-red.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-small-red.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-small-yellow.svg b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-small-yellow.svg similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/assets/confirm-small-yellow.svg rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/assets/confirm-small-yellow.svg diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/index.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/index.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/index.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/style.ts b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/style.ts similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/style.ts rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/style.ts diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/RejectTxModal/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/RejectTxModal/index.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/RejectTxModal/index.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/RejectTxModal/index.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/RejectTxModal/style.ts b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/RejectTxModal/style.ts similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/RejectTxModal/style.ts rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/RejectTxModal/style.ts diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/CustomDescription.tsx similarity index 93% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/CustomDescription.tsx index c038cf113a..2476bfb8a6 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/CustomDescription.tsx @@ -26,7 +26,7 @@ import { getNameFromAddressBook } from 'src/logic/addressBook/store/selectors' import Paragraph from 'src/components/layout/Paragraph' import LinkWithRef from 'src/components/layout/Link' import { shortVersionOf } from 'src/logic/wallets/ethAddresses' -import { Transaction } from 'src/logic/safe/store/models/types/transaction' +import { Transaction, SafeModuleTransaction } from 'src/logic/safe/store/models/types/transaction' import { DataDecoded } from 'src/logic/safe/store/models/types/transactions.d' import DividerLine from 'src/components/DividerLine' import { decodeMethods, isSetAllowanceMethod } from 'src/logic/contracts/methodIds' @@ -80,7 +80,7 @@ interface NewSpendingLimitDetailsProps { data: DataDecoded } -const NewSpendingLimitDetails = ({ data }: NewSpendingLimitDetailsProps): React.ReactElement => { +const ModifySpendingLimitDetails = ({ data }: NewSpendingLimitDetailsProps): React.ReactElement => { const [beneficiary, tokenAddress, amount, resetTimeMin] = React.useMemo( () => data.parameters.map(({ value }) => value), [data.parameters], @@ -96,7 +96,7 @@ const NewSpendingLimitDetails = ({ data }: NewSpendingLimitDetailsProps): React. return ( <> - New Spending Limit: + Modify Spending Limit: @@ -129,7 +129,7 @@ const MultiSendCustomDataAction = ({ tx, order }: { tx: MultiSendDetails; order: > {isNewSpendingLimit ? ( - + ) : ( @@ -201,7 +201,7 @@ const TxData = ({ data }: { data: string }): React.ReactElement => { ) } -const TxActionData = ({ dataDecoded }: { dataDecoded: DataDecoded }): React.ReactElement => { +export const TxActionData = ({ dataDecoded }: { dataDecoded: DataDecoded }): React.ReactElement => { const classes = useStyles() return ( @@ -237,16 +237,22 @@ interface GenericCustomDataProps { amount?: string data: string recipient: string - storedTx: Transaction + storedTx: Transaction | SafeModuleTransaction } -const GenericCustomData = ({ amount = '0', data, recipient, storedTx }: GenericCustomDataProps): React.ReactElement => { +export const GenericCustomData = ({ + amount = '0', + data, + recipient, + storedTx, +}: GenericCustomDataProps): React.ReactElement => { + debugger const recipientName = useSelector((state) => getNameFromAddressBook(state, recipient)) const txData = storedTx?.dataDecoded ?? decodeMethods(data) const isNewSpendingLimit = isSetAllowanceMethod(data || '') return isNewSpendingLimit ? ( - + ) : ( diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/SettingsDescription.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/SettingsDescription.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/TransferDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/TransferDescription.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/TransferDescription.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/TransferDescription.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/Value.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/Value.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/index.tsx similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/index.tsx diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/styles.ts b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/styles.ts similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/styles.ts rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/styles.ts diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/utils.ts b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/utils.ts similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/utils.ts rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/utils.ts diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx similarity index 72% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx index 8298fb3c02..36457ff208 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx @@ -3,6 +3,12 @@ import cn from 'classnames' import React, { useState } from 'react' import { useSelector } from 'react-redux' import { EthHashInfo } from '@gnosis.pm/safe-react-components' +import { + getModuleAmount, + TableData, + TX_TABLE_RAW_CANCEL_TX_ID, + TX_TABLE_RAW_TX_ID, +} from 'src/routes/safe/components/Transactions/TxsTable/columns' import ApproveTxModal from './ApproveTxModal' import OwnersColumn from './OwnersColumn' @@ -23,11 +29,45 @@ import Row from 'src/components/layout/Row' import Span from 'src/components/layout/Span' import { INCOMING_TX_TYPES } from 'src/logic/safe/store/models/incomingTransaction' import { safeNonceSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors' -import { Transaction, TransactionTypes } from 'src/logic/safe/store/models/types/transaction' +import { Transaction, TransactionTypes, SafeModuleTransaction } from 'src/logic/safe/store/models/types/transaction' import IncomingTxDescription from './IncomingTxDescription' +import TransferDescription from './TxDescription/TransferDescription' const useStyles = makeStyles(styles as any) +const ExpandedModuleTx = ({ tx }: { tx: SafeModuleTransaction }): React.ReactElement => { + const classes = useStyles() + + return ( + + + + +
+ Hash: + {tx.executionTxHash ? ( + + ) : ( + 'n/a' + )} +
+
+ + + + + +
+
+ ) +} + interface ExpandedTxProps { cancelTx: Transaction tx: Transaction @@ -136,4 +176,14 @@ const ExpandedTx = ({ cancelTx, tx }: ExpandedTxProps): React.ReactElement => { ) } -export default ExpandedTx +const ExpandedTxRow = ({ row }: { row: TableData }): React.ReactElement => { + const isModuleTx = [TransactionTypes.SPENDING_LIMIT, TransactionTypes.MODULE].includes(row.tx.type) + + if (isModuleTx) { + return + } + + return +} + +export default ExpandedTxRow diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/style.ts b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/style.ts similarity index 100% rename from src/routes/safe/components/Transactions/TxsTable/ExpandedTx/style.ts rename to src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/style.ts diff --git a/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx b/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx index a92d5a566d..5112b11d68 100644 --- a/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx @@ -20,6 +20,8 @@ const typeToIcon = { creation: SettingsTxIcon, cancellation: SettingsTxIcon, upgrade: SettingsTxIcon, + module: SettingsTxIcon, + spendingLimit: SettingsTxIcon, } const typeToLabel = { @@ -33,6 +35,7 @@ const typeToLabel = { cancellation: 'Cancellation transaction', upgrade: 'Contract Upgrade', module: 'Module transaction', + spendingLimit: 'Spending Limit', } interface TxTypeProps { diff --git a/src/routes/safe/components/Transactions/TxsTable/columns.tsx b/src/routes/safe/components/Transactions/TxsTable/columns.tsx index 4f385bf644..3f3bd8756b 100644 --- a/src/routes/safe/components/Transactions/TxsTable/columns.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/columns.tsx @@ -9,10 +9,10 @@ import TxType from './TxType' import { buildOrderFieldFrom } from 'src/components/Table/sorting' import { TableColumn } from 'src/components/Table/types.d' -import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { INCOMING_TX_TYPES } from 'src/logic/safe/store/models/incomingTransaction' -import { Transaction } from 'src/logic/safe/store/models/types/transaction' +import { Transaction, TransactionTypes, SafeModuleTransaction } from 'src/logic/safe/store/models/types/transaction' import { CancellationTransactions } from 'src/logic/safe/store/reducer/cancellationTransactions' +import { formatAmount } from 'src/logic/tokens/utils/formatAmount' export const TX_TABLE_ID = 'id' export const TX_TABLE_TYPE_ID = 'type' @@ -68,17 +68,38 @@ export const getTxAmount = (tx: Transaction, formatted = true): string => { return getAmountWithSymbol({ decimals, symbol, value }, formatted) } -interface TableData { +export const getModuleAmount = (tx: SafeModuleTransaction, formatted = true): string => { + if (tx.type === TransactionTypes.SPENDING_LIMIT && tx.tokenInfo) { + const { decimals, symbol } = tx.tokenInfo + const [, amount] = tx.dataDecoded.parameters + const { value } = amount + return getAmountWithSymbol({ decimals, symbol, value }, formatted) + } + + return NOT_AVAILABLE +} + +export interface TableData { amount: string cancelTx?: Transaction date: string dateOrder?: number id: string status: string - tx?: Transaction + tx?: Transaction | SafeModuleTransaction type: any } +const getModuleTxTableData = (tx: SafeModuleTransaction): TableData => ({ + [TX_TABLE_ID]: tx.blockNumber?.toString() ?? '', + [TX_TABLE_TYPE_ID]: , + [TX_TABLE_DATE_ID]: formatDate(tx.executionDate), + [buildOrderFieldFrom(TX_TABLE_DATE_ID)]: getTime(parseISO(tx.executionDate)), + [TX_TABLE_AMOUNT_ID]: getModuleAmount(tx), + [TX_TABLE_STATUS_ID]: tx.status, + [TX_TABLE_RAW_TX_ID]: tx, +}) + const getIncomingTxTableData = (tx: Transaction): TableData => ({ [TX_TABLE_ID]: tx.blockNumber?.toString() ?? '', [TX_TABLE_TYPE_ID]: , @@ -105,15 +126,22 @@ const getTransactionTableData = (tx: Transaction, cancelTx: Transaction): TableD } export const getTxTableData = ( - transactions: List, + transactions: List, cancelTxs: CancellationTransactions, ): List => { return transactions.map((tx) => { - if (INCOMING_TX_TYPES[tx.type] !== undefined) { - return getIncomingTxTableData(tx) + const isModuleTx = [TransactionTypes.SPENDING_LIMIT, TransactionTypes.MODULE].includes(tx.type) + const isIncomingTx = INCOMING_TX_TYPES[tx.type] !== undefined + + if (isModuleTx) { + return getModuleTxTableData(tx as SafeModuleTransaction) + } + + if (isIncomingTx) { + return getIncomingTxTableData(tx as Transaction) } - return getTransactionTableData(tx, cancelTxs.get(`${tx.nonce}`)) + return getTransactionTableData(tx as Transaction, cancelTxs.get(`${tx.nonce}`)) }) } diff --git a/src/routes/safe/components/Transactions/TxsTable/index.tsx b/src/routes/safe/components/Transactions/TxsTable/index.tsx index 57033ba43e..73e9983881 100644 --- a/src/routes/safe/components/Transactions/TxsTable/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/index.tsx @@ -1,27 +1,27 @@ import Collapse from '@material-ui/core/Collapse' import IconButton from '@material-ui/core/IconButton' +import { withStyles } from '@material-ui/core/styles' import TableCell from '@material-ui/core/TableCell' import TableContainer from '@material-ui/core/TableContainer' import TableRow from '@material-ui/core/TableRow' -import { withStyles } from '@material-ui/core/styles' import ExpandLess from '@material-ui/icons/ExpandLess' import ExpandMore from '@material-ui/icons/ExpandMore' import cn from 'classnames' import React, { useState } from 'react' import { useSelector } from 'react-redux' -import ExpandedTxComponent from './ExpandedTx' -import Status from './Status' -import { TX_TABLE_ID, TX_TABLE_RAW_CANCEL_TX_ID, TX_TABLE_RAW_TX_ID, generateColumns, getTxTableData } from './columns' -import { styles } from './style' - -import Table from 'src/components/Table' -import { cellWidth } from 'src/components/Table/TableHead' import Block from 'src/components/layout/Block' import Row from 'src/components/layout/Row' +import Table from 'src/components/Table' +import { cellWidth } from 'src/components/Table/TableHead' import { safeCancellationTransactionsSelector } from 'src/logic/safe/store/selectors' import { extendedTransactionsSelector } from 'src/logic/safe/store/selectors/transactions' +import { generateColumns, getTxTableData, TX_TABLE_ID } from './columns' +import ExpandedTxRow from './ExpandedTxRow' +import Status from './Status' +import { styles } from './style' + export const TRANSACTION_ROW_TEST_ID = 'transaction-row' const TxsTable = ({ classes }) => { @@ -38,8 +38,8 @@ const TxsTable = ({ classes }) => { const filteredData = getTxTableData(transactions, cancellationTransactions) .sort((tx1, tx2) => { // First order by nonce - const aNonce = tx1.tx.nonce - const bNonce = tx1.tx.nonce + const aNonce = Number(tx1.tx.nonce) + const bNonce = Number(tx1.tx.nonce) if (aNonce && bNonce) { const difference = aNonce - bNonce if (difference !== 0) { @@ -77,7 +77,7 @@ const TxsTable = ({ classes }) => { > {(sortedData) => sortedData.map((row, index) => ( - + { style={{ paddingBottom: 0, paddingTop: 0 }} > ( - - )} + component={() => } in={expandedTx === row.tx.safeTxHash} timeout="auto" unmountOnExit diff --git a/src/test/safe.dom.funds.thresholdGt1.ts b/src/test/safe.dom.funds.thresholdGt1.ts index 23c484f975..0ab98af706 100644 --- a/src/test/safe.dom.funds.thresholdGt1.ts +++ b/src/test/safe.dom.funds.thresholdGt1.ts @@ -1,4 +1,4 @@ -// +// import { fireEvent, waitForElement } from '@testing-library/react' import { aNewStore } from 'src/store' import { aMinedSafe } from 'src/test/builder/safe.redux.builder' @@ -13,7 +13,7 @@ import { TRANSACTIONS_TAB_BTN_TEST_ID } from 'src/routes/safe/components/Layout/ import { TRANSACTION_ROW_TEST_ID } from 'src/routes/safe/components/Transactions/TxsTable' import { useTestAccountAt, resetTestAccount } from './utils/accounts' //import { CONFIRM_TX_BTN_TEST_ID } from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/ButtonRow' -import { APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID } from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal' +import { APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID } from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/ApproveTxModal' afterEach(resetTestAccount) diff --git a/src/test/utils/transactions/transactionList.helper.ts b/src/test/utils/transactions/transactionList.helper.ts index 74b7860709..4059dd607e 100644 --- a/src/test/utils/transactions/transactionList.helper.ts +++ b/src/test/utils/transactions/transactionList.helper.ts @@ -4,11 +4,11 @@ import { shortVersionOf } from 'src/logic/wallets/ethAddresses' import { TRANSACTION_ROW_TEST_ID } from 'src/routes/safe/components/Transactions/TxsTable' import { TRANSACTIONS_DESC_SEND_TEST_ID, -} from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription' +} from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription' import { TRANSACTIONS_DESC_ADD_OWNER_TEST_ID, TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID, -} from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription' +} from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/SettingsDescription' import { TRANSACTIONS_TAB_BTN_TEST_ID } from 'src/routes/safe/components/Layout/Tabs' export const getLastTransaction = async (SafeDom) => { From a7b5953d4643d3dbe9c17f821878ca4e105b96b7 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 28 Aug 2020 19:16:23 -0300 Subject: [PATCH 11/19] identify spendingLimitTxs --- src/logic/safe/store/selectors/index.ts | 1 + .../ExpandedTxRow/TxDescription/CustomDescription.tsx | 1 - .../safe/components/Transactions/TxsTable/TxType/index.tsx | 1 + .../safe/components/Transactions/TxsTable/columns.tsx | 7 ++++++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/logic/safe/store/selectors/index.ts b/src/logic/safe/store/selectors/index.ts index f12140eef7..cd60280a6a 100644 --- a/src/logic/safe/store/selectors/index.ts +++ b/src/logic/safe/store/selectors/index.ts @@ -151,6 +151,7 @@ export const safeModuleTransactionsSelector = createSelector( return { ...moduleTx, safeTxHash: moduleTx.transactionHash, + executionTxHash: moduleTx.transactionHash, status: TransactionStatus.SUCCESS, tokenInfo, type, diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/CustomDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/CustomDescription.tsx index 2476bfb8a6..18b626ab23 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/CustomDescription.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/TxDescription/CustomDescription.tsx @@ -246,7 +246,6 @@ export const GenericCustomData = ({ recipient, storedTx, }: GenericCustomDataProps): React.ReactElement => { - debugger const recipientName = useSelector((state) => getNameFromAddressBook(state, recipient)) const txData = storedTx?.dataDecoded ?? decodeMethods(data) const isNewSpendingLimit = isSetAllowanceMethod(data || '') diff --git a/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx b/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx index 5112b11d68..8cece1a4dc 100644 --- a/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/TxType/index.tsx @@ -77,4 +77,5 @@ const TxType = ({ origin, txType }: TxTypeProps): React.ReactElement => { return loading ? : } + export default TxType diff --git a/src/routes/safe/components/Transactions/TxsTable/columns.tsx b/src/routes/safe/components/Transactions/TxsTable/columns.tsx index 3f3bd8756b..ea40c2fff4 100644 --- a/src/routes/safe/components/Transactions/TxsTable/columns.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/columns.tsx @@ -112,10 +112,15 @@ const getIncomingTxTableData = (tx: Transaction): TableData => ({ const getTransactionTableData = (tx: Transaction, cancelTx: Transaction): TableData => { const txDate = tx.submissionDate + // given that setAllowance will always be part of + // a spendingLimit related tx (as of now, until removeDelegate is implemented) + // we can use this method to identify an SpendingLimit tx + const setAllowanceHash = 'beaeb388' + const txType = tx.data?.includes(setAllowanceHash) ? TransactionTypes.SPENDING_LIMIT : tx.type return { [TX_TABLE_ID]: tx.blockNumber?.toString() ?? '', - [TX_TABLE_TYPE_ID]: , + [TX_TABLE_TYPE_ID]: , [TX_TABLE_DATE_ID]: txDate ? formatDate(txDate) : '', [buildOrderFieldFrom(TX_TABLE_DATE_ID)]: txDate ? getTime(parseISO(txDate)) : null, [TX_TABLE_AMOUNT_ID]: getTxAmount(tx), From 80d0948598255df4db6cb2fe2ff497ebeb33039c Mon Sep 17 00:00:00 2001 From: fernandomg Date: Thu, 3 Sep 2020 10:41:53 -0300 Subject: [PATCH 12/19] fix: properly verify existence of moduleTransactions - also reorganized imports --- .../transactions/fetchTransactions/index.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/logic/safe/store/actions/transactions/fetchTransactions/index.ts b/src/logic/safe/store/actions/transactions/fetchTransactions/index.ts index a383f9a611..07aeec4d78 100644 --- a/src/logic/safe/store/actions/transactions/fetchTransactions/index.ts +++ b/src/logic/safe/store/actions/transactions/fetchTransactions/index.ts @@ -1,19 +1,18 @@ +import { backOff } from 'exponential-backoff' import { batch } from 'react-redux' -import { ThunkAction, ThunkDispatch } from 'redux-thunk' import { AnyAction } from 'redux' -import { backOff } from 'exponential-backoff' +import { ThunkAction, ThunkDispatch } from 'redux-thunk' -import { addIncomingTransactions } from '../../addIncomingTransactions' +import { addIncomingTransactions } from 'src/logic/safe/store/actions/addIncomingTransactions' +import { addModuleTransactions } from 'src/logic/safe/store/actions/addModuleTransactions' +import { addOrUpdateCancellationTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateCancellationTransactions' +import { addOrUpdateTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateTransactions' +import { AppReduxState } from 'src/store' import { loadIncomingTransactions } from './loadIncomingTransactions' import { loadModuleTransactions } from './loadModuleTransactions' import { loadOutgoingTransactions } from './loadOutgoingTransactions' -import { addOrUpdateCancellationTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateCancellationTransactions' -import { addOrUpdateTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateTransactions' -import { addModuleTransactions } from 'src/logic/safe/store/actions/addModuleTransactions' -import { AppReduxState } from 'src/store' - const noFunc = () => {} export default (safeAddress: string): ThunkAction, AppReduxState, undefined, AnyAction> => async ( @@ -48,7 +47,7 @@ export default (safeAddress: string): ThunkAction, AppReduxState, const moduleTransactions = await loadModuleTransactions(safeAddress) - if (moduleTransactions) { + if (moduleTransactions.length) { dispatch(addModuleTransactions({ modules: moduleTransactions, safeAddress })) } } catch (error) { From c141d52b2f174ded451da4a822c06c53e9abfd37 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Thu, 3 Sep 2020 11:24:40 -0300 Subject: [PATCH 13/19] fix: remove unnecessary state access from within `loadModuleTransactions` --- .../fetchTransactions/loadModuleTransactions.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts b/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts index 89b4c05423..bb75be320a 100644 --- a/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts +++ b/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts @@ -1,5 +1,3 @@ -import { SAFE_REDUCER_ID } from 'src/logic/safe/store/reducer/safe' -import { store } from 'src/store' import fetchTransactions from 'src/logic/safe/store/actions/transactions/fetchTransactions/fetchTransactions' import { TransactionTypes } from 'src/logic/safe/store/models/types/transaction' import { DataDecoded, Operation } from 'src/logic/safe/store/models/types/transactions.d' @@ -20,17 +18,8 @@ export type ModuleTxServiceModel = { let previousETag = null export const loadModuleTransactions = async (safeAddress: string): Promise => { - const defaultResponse = [] - const state = store.getState() - if (!safeAddress) { - return defaultResponse - } - - const safe = state[SAFE_REDUCER_ID].getIn(['safes', safeAddress]) - - if (!safe) { - return defaultResponse + return [] } const { eTag, results }: { eTag: string | null; results: ModuleTxServiceModel[] } = await fetchTransactions( From a549155a7a9d0a31719f88a47124edf45c439163 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Thu, 3 Sep 2020 13:57:33 -0300 Subject: [PATCH 14/19] fix: rewrite explanation why there's a filter in the module's tx reducer --- src/logic/safe/store/reducer/moduleTransactions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logic/safe/store/reducer/moduleTransactions.ts b/src/logic/safe/store/reducer/moduleTransactions.ts index e5de69c58c..27b310b237 100644 --- a/src/logic/safe/store/reducer/moduleTransactions.ts +++ b/src/logic/safe/store/reducer/moduleTransactions.ts @@ -18,8 +18,8 @@ export default handleActions( const { modules, safeAddress } = action.payload const oldModuleTxs = state[safeAddress] ?? [] const oldModuleTxsHashes = oldModuleTxs.map(({ transactionHash }) => transactionHash) - // filtering in this level happens, because backend is returning the whole list of txs - // so, to avoid re-storing all the txs, those already stored are filtered out + // As backend is returning the whole list of txs on every request, + // to avoid duplicates, filtering happens in this level. const newModuleTxs = modules.filter((moduleTx) => !oldModuleTxsHashes.includes(moduleTx.transactionHash)) return { From f2668fac97e0148b4e28d057fa8107d29e53153e Mon Sep 17 00:00:00 2001 From: fernandomg Date: Thu, 3 Sep 2020 14:02:56 -0300 Subject: [PATCH 15/19] fix: remove `as any` and unnecessary params in createTransaction action call --- .../Balances/SendModal/screens/ReviewTx/index.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx index 0db49ac7ee..7cca45ee09 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx @@ -2,7 +2,6 @@ import StandardToken from '@gnosis.pm/util-contracts/build/contracts/GnosisStand import IconButton from '@material-ui/core/IconButton' import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' -import { withSnackbar } from 'notistack' import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' @@ -39,7 +38,7 @@ import { styles } from './style' const useStyles = makeStyles(styles) -const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { +const ReviewTx = ({ onClose, onPrev, tx }) => { const classes = useStyles() const dispatch = useDispatch() const { address: safeAddress } = useSelector(safeSelector) @@ -94,7 +93,7 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { // txAmount should be 0 if we send tokens // the real value is encoded in txData and will be used by the contract // if txAmount > 0 it would send ETH from the Safe - const txAmount = isSendingETH ? web3.utils.toWei(tx.amount, 'ether') : '0' + const txAmount = isSendingETH ? web3.utils.toWei(tx.amount, 'ether').toString() : '0' if (isSpendingLimit) { const spendingLimit = getSpendingLimitContract() @@ -120,9 +119,7 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { valueInWei: txAmount, txData: data, notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX, - enqueueSnackbar, - closeSnackbar, - } as any), + }), ) onClose() } @@ -213,4 +210,4 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { ) } -export default withSnackbar(ReviewTx) +export default ReviewTx From b138c4d5be69536542fa25c2885c02c55024af56 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Thu, 3 Sep 2020 14:27:19 -0300 Subject: [PATCH 16/19] fix: remove index from key --- src/routes/safe/components/Transactions/TxsTable/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/safe/components/Transactions/TxsTable/index.tsx b/src/routes/safe/components/Transactions/TxsTable/index.tsx index 73e9983881..fe1a7ef8b1 100644 --- a/src/routes/safe/components/Transactions/TxsTable/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/index.tsx @@ -76,8 +76,8 @@ const TxsTable = ({ classes }) => { size={filteredData.size} > {(sortedData) => - sortedData.map((row, index) => ( - + sortedData.map((row) => ( + Date: Thu, 3 Sep 2020 15:02:49 -0300 Subject: [PATCH 17/19] refactor: rename constants --- src/logic/contracts/methodIds.ts | 49 ++++++++----------- .../safe/store/models/types/transactions.d.ts | 14 +++++- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/logic/contracts/methodIds.ts b/src/logic/contracts/methodIds.ts index 763c80ac93..f134138aba 100644 --- a/src/logic/contracts/methodIds.ts +++ b/src/logic/contracts/methodIds.ts @@ -1,15 +1,20 @@ import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3' -import { DataDecoded, METHOD_TO_ID } from 'src/logic/safe/store/models/types/transactions.d' +import { + DataDecoded, + SAFE_METHOD_ID_TO_NAME, + SPENDING_LIMIT_METHOD_ID_TO_NAME, + SPENDING_LIMIT_METHODS_NAMES, +} from 'src/logic/safe/store/models/types/transactions.d' export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => { - const [methodId, params] = [data.slice(0, 10) as keyof typeof METHOD_TO_ID | string, data.slice(10)] + const [methodId, params] = [data.slice(0, 10) as keyof typeof SAFE_METHOD_ID_TO_NAME | string, data.slice(10)] switch (methodId) { // swapOwner case '0xe318b52b': { const decodedParameters = web3.eth.abi.decodeParameters(['uint', 'address', 'address'], params) as string[] return { - method: METHOD_TO_ID[methodId], + method: SAFE_METHOD_ID_TO_NAME[methodId], parameters: [ { name: 'oldOwner', type: 'address', value: decodedParameters[1] }, { name: 'newOwner', type: 'address', value: decodedParameters[2] }, @@ -21,7 +26,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => case '0x0d582f13': { const decodedParameters = web3.eth.abi.decodeParameters(['address', 'uint'], params) return { - method: METHOD_TO_ID[methodId], + method: SAFE_METHOD_ID_TO_NAME[methodId], parameters: [ { name: 'owner', type: 'address', value: decodedParameters[0] }, { name: '_threshold', type: 'uint', value: decodedParameters[1] }, @@ -33,7 +38,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => case '0xf8dc5dd9': { const decodedParameters = web3.eth.abi.decodeParameters(['address', 'address', 'uint'], params) return { - method: METHOD_TO_ID[methodId], + method: SAFE_METHOD_ID_TO_NAME[methodId], parameters: [ { name: 'oldOwner', type: 'address', value: decodedParameters[1] }, { name: '_threshold', type: 'uint', value: decodedParameters[2] }, @@ -45,7 +50,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => case '0x694e80c3': { const decodedParameters = web3.eth.abi.decodeParameters(['uint'], params) return { - method: METHOD_TO_ID[methodId], + method: SAFE_METHOD_ID_TO_NAME[methodId], parameters: [ { name: '_threshold', type: 'uint', value: decodedParameters[0] }, ], @@ -56,7 +61,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => case '0x610b5925': { const decodedParameters = web3.eth.abi.decodeParameters(['address'], params) return { - method: METHOD_TO_ID[methodId], + method: SAFE_METHOD_ID_TO_NAME[methodId], parameters: [ { name: 'module', type: 'address', value: decodedParameters[0] }, ], @@ -67,7 +72,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => case '0xe009cfde': { const decodedParameters = web3.eth.abi.decodeParameters(['address', 'address'], params) return { - method: METHOD_TO_ID[methodId], + method: SAFE_METHOD_ID_TO_NAME[methodId], parameters: [ { name: 'prevModule', type: 'address', value: decodedParameters[0] }, { name: 'module', type: 'address', value: decodedParameters[1] }, @@ -80,32 +85,20 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => } } -export const SPENDING_LIMIT_METHODS_NAMES = { - ADD_DELEGATE: 'addDelegate', - SET_ALLOWANCE: 'setAllowance', - EXECUTE_ALLOWANCE_TRANSFER: 'executeAllowanceTransfer', -} - -export const SPENDING_LIMIT_METHOD_TO_ID = { - '0xe71bdf41': SPENDING_LIMIT_METHODS_NAMES.ADD_DELEGATE, - '0xbeaeb388': SPENDING_LIMIT_METHODS_NAMES.SET_ALLOWANCE, - '0x4515641a': SPENDING_LIMIT_METHODS_NAMES.EXECUTE_ALLOWANCE_TRANSFER, -} - export const isSetAllowanceMethod = (data: string): boolean => { - const methodId = data.slice(0, 10) as keyof typeof SPENDING_LIMIT_METHOD_TO_ID - return SPENDING_LIMIT_METHOD_TO_ID[methodId] === SPENDING_LIMIT_METHODS_NAMES.SET_ALLOWANCE + const methodId = data.slice(0, 10) as keyof typeof SPENDING_LIMIT_METHOD_ID_TO_NAME + return SPENDING_LIMIT_METHOD_ID_TO_NAME[methodId] === SPENDING_LIMIT_METHODS_NAMES.SET_ALLOWANCE } export const decodeParamsFromSpendingLimit = (data: string): DataDecoded | null => { - const [methodId, params] = [data.slice(0, 10) as keyof typeof SPENDING_LIMIT_METHOD_TO_ID | string, data.slice(10)] + const [methodId, params] = [data.slice(0, 10) as keyof typeof SPENDING_LIMIT_METHOD_ID_TO_NAME | string, data.slice(10)] switch (methodId) { // addDelegate case '0xe71bdf41': { const decodedParameter = (web3.eth.abi.decodeParameter('address', params) as unknown) as string return { - method: SPENDING_LIMIT_METHOD_TO_ID[methodId], + method: SPENDING_LIMIT_METHOD_ID_TO_NAME[methodId], parameters: [ { name: 'delegate', type: 'address', value: decodedParameter }, ], @@ -116,7 +109,7 @@ export const decodeParamsFromSpendingLimit = (data: string): DataDecoded | null case '0xbeaeb388': { const decodedParameters = web3.eth.abi.decodeParameters(['address', 'address', 'uint96', 'uint16', 'uint32'], params) return { - method: SPENDING_LIMIT_METHOD_TO_ID[methodId], + method: SPENDING_LIMIT_METHOD_ID_TO_NAME[methodId], parameters: [ { name: 'delegate', type: 'address', value: decodedParameters[0] }, { name: 'token', type: 'address', value: decodedParameters[1] }, @@ -131,7 +124,7 @@ export const decodeParamsFromSpendingLimit = (data: string): DataDecoded | null case '0x4515641a': { const decodedParameters = web3.eth.abi.decodeParameters(['address', 'address', 'address', 'uint96', 'address', 'uint96', 'address', 'bytes'], params) return { - method: SPENDING_LIMIT_METHOD_TO_ID[methodId], + method: SPENDING_LIMIT_METHOD_ID_TO_NAME[methodId], parameters: [ { name: "safe", type: "address", value: decodedParameters[0] }, { name: "token", type: "address", value: decodedParameters[1] }, @@ -151,11 +144,11 @@ export const decodeParamsFromSpendingLimit = (data: string): DataDecoded | null } const isSafeMethod = (methodId: string): boolean => { - return !!METHOD_TO_ID[methodId] + return !!SAFE_METHOD_ID_TO_NAME[methodId] } const isSpendingLimitMethod = (methodId: string): boolean => { - return !!SPENDING_LIMIT_METHOD_TO_ID[methodId] + return !!SPENDING_LIMIT_METHOD_ID_TO_NAME[methodId] } export const decodeMethods = (data: string): DataDecoded | null => { diff --git a/src/logic/safe/store/models/types/transactions.d.ts b/src/logic/safe/store/models/types/transactions.d.ts index a9fcc709ce..723e68eb3a 100644 --- a/src/logic/safe/store/models/types/transactions.d.ts +++ b/src/logic/safe/store/models/types/transactions.d.ts @@ -230,7 +230,7 @@ export const SAFE_METHODS_NAMES = { DISABLE_MODULE: 'disableModule', } -export const METHOD_TO_ID = { +export const SAFE_METHOD_ID_TO_NAME = { '0xe318b52b': SAFE_METHODS_NAMES.SWAP_OWNER, '0x0d582f13': SAFE_METHODS_NAMES.ADD_OWNER_WITH_THRESHOLD, '0xf8dc5dd9': SAFE_METHODS_NAMES.REMOVE_OWNER, @@ -239,6 +239,18 @@ export const METHOD_TO_ID = { '0xe009cfde': SAFE_METHODS_NAMES.DISABLE_MODULE, } +export const SPENDING_LIMIT_METHODS_NAMES = { + ADD_DELEGATE: 'addDelegate', + SET_ALLOWANCE: 'setAllowance', + EXECUTE_ALLOWANCE_TRANSFER: 'executeAllowanceTransfer', +} + +export const SPENDING_LIMIT_METHOD_ID_TO_NAME = { + '0xe71bdf41': SPENDING_LIMIT_METHODS_NAMES.ADD_DELEGATE, + '0xbeaeb388': SPENDING_LIMIT_METHODS_NAMES.SET_ALLOWANCE, + '0x4515641a': SPENDING_LIMIT_METHODS_NAMES.EXECUTE_ALLOWANCE_TRANSFER, +} + export type SafeMethods = typeof SAFE_METHODS_NAMES[keyof typeof SAFE_METHODS_NAMES] type TokenMethods = 'transfer' | 'transferFrom' | 'safeTransferFrom' From ce681b812d527e7bc628cb440092991ef9c833b3 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 8 Sep 2020 15:21:36 -0300 Subject: [PATCH 18/19] fix: support ETH for spending limit transactions --- src/logic/safe/store/selectors/index.ts | 10 +++++++++- .../TxsTable/ExpandedTxRow/index.tsx | 20 ++++++++++++++++--- .../Transactions/TxsTable/columns.tsx | 14 +++++++++++-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/logic/safe/store/selectors/index.ts b/src/logic/safe/store/selectors/index.ts index cd60280a6a..cec5fd4aa2 100644 --- a/src/logic/safe/store/selectors/index.ts +++ b/src/logic/safe/store/selectors/index.ts @@ -23,6 +23,7 @@ import { SafesMap } from 'src/routes/safe/store/reducer/types/safe' import { AppReduxState } from 'src/store' import { checksumAddress } from 'src/utils/checksumAddress' import { SPENDING_LIMIT_MODULE_ADDRESS } from 'src/utils/constants' +import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers' const safesStateSelector = (state: AppReduxState) => state[SAFE_REDUCER_ID] @@ -145,7 +146,14 @@ export const safeModuleTransactionsSelector = createSelector( // add token info to the model, so data can be properly displayed in the UI let tokenInfo if (type === TransactionTypes.SPENDING_LIMIT) { - tokenInfo = tokens.find(({ address }) => sameAddress(address, moduleTx.to)) + if (moduleTx.data) { + // if `data` is defined, then it's a token transfer + tokenInfo = tokens.find(({ address }) => sameAddress(address, moduleTx.to)) + } else { + // if `data` is not defined, then it's an ETH transfer + // ETH does not exist in the list of tokens, so we recreate the record here + tokenInfo = getEthAsToken(0) + } } return { diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx index 36457ff208..c68d952570 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx @@ -38,6 +38,18 @@ const useStyles = makeStyles(styles as any) const ExpandedModuleTx = ({ tx }: { tx: SafeModuleTransaction }): React.ReactElement => { const classes = useStyles() + const recipient = React.useMemo(() => { + if (tx.type === TransactionTypes.SPENDING_LIMIT) { + if (tx.dataDecoded) { + // if `dataDecoded` is defined, then it's a token transfer + return tx.dataDecoded?.parameters[0].value + } else { + // if `data` is not defined, then it's an ETH transfer + return tx.to + } + } + }, [tx.dataDecoded, tx.to, tx.type]) + return ( @@ -59,9 +71,11 @@ const ExpandedModuleTx = ({ tx }: { tx: SafeModuleTransaction }): React.ReactEle - - - + {recipient && ( + + + + )}
diff --git a/src/routes/safe/components/Transactions/TxsTable/columns.tsx b/src/routes/safe/components/Transactions/TxsTable/columns.tsx index ea40c2fff4..0dd4967cf1 100644 --- a/src/routes/safe/components/Transactions/TxsTable/columns.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/columns.tsx @@ -71,8 +71,18 @@ export const getTxAmount = (tx: Transaction, formatted = true): string => { export const getModuleAmount = (tx: SafeModuleTransaction, formatted = true): string => { if (tx.type === TransactionTypes.SPENDING_LIMIT && tx.tokenInfo) { const { decimals, symbol } = tx.tokenInfo - const [, amount] = tx.dataDecoded.parameters - const { value } = amount + + let value + + if (tx.dataDecoded) { + // if `dataDecoded` is defined, then it's a token transfer + const [, amount] = tx.dataDecoded.parameters + value = amount.value + } else { + // if `dataDecoded` is not defined, then it's an ETH transfer + value = tx.value + } + return getAmountWithSymbol({ decimals, symbol, value }, formatted) } From b58b6482dc70db573ea920cf0633ac9ae8f50de5 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 8 Sep 2020 18:49:37 -0300 Subject: [PATCH 19/19] fix types after merge --- .../loadModuleTransactions.ts | 6 ++- .../SendModal/screens/ReviewTx/index.tsx | 54 ++++++++++--------- .../ExpandedTxRow/OwnersColumn/index.tsx | 2 +- .../TxsTable/ExpandedTxRow/index.tsx | 17 +++--- .../TxsTable/ExpandedTxRow/style.ts | 45 ++++++++-------- .../Transactions/TxsTable/columns.tsx | 6 +-- 6 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts b/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts index bb75be320a..7dd1b7b7d3 100644 --- a/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts +++ b/src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions.ts @@ -16,13 +16,15 @@ export type ModuleTxServiceModel = { dataDecoded: DataDecoded } -let previousETag = null +type ETag = string | null + +let previousETag: ETag = null export const loadModuleTransactions = async (safeAddress: string): Promise => { if (!safeAddress) { return [] } - const { eTag, results }: { eTag: string | null; results: ModuleTxServiceModel[] } = await fetchTransactions( + const { eTag, results }: { eTag: ETag; results: ModuleTxServiceModel[] } = await fetchTransactions( TransactionTypes.MODULE, safeAddress, previousETag, diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx index 22f483184b..6c1eb8f6e4 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx @@ -94,33 +94,35 @@ const ReviewTx = ({ onClose, onPrev, tx }) => { // if txAmount > 0 it would send ETH from the Safe const txAmount = isSendingETH ? web3.utils.toWei(tx.amount, 'ether').toString() : '0' - if (isSpendingLimit && txToken) { - const spendingLimit = getSpendingLimitContract() - spendingLimit.methods - .executeAllowanceTransfer( - safeAddress, - txToken.address === ETH_ADDRESS ? ZERO_ADDRESS : txToken.address, - tx.recipientAddress, - toTokenUnit(tx.amount, txToken.decimals), - ZERO_ADDRESS, - 0, - tx.tokenSpendingLimit.delegate, - EMPTY_DATA, + if (safeAddress) { + if (isSpendingLimit && txToken) { + const spendingLimit = getSpendingLimitContract() + spendingLimit.methods + .executeAllowanceTransfer( + safeAddress, + txToken.address === ETH_ADDRESS ? ZERO_ADDRESS : txToken.address, + tx.recipientAddress, + toTokenUnit(tx.amount, txToken.decimals), + ZERO_ADDRESS, + 0, + tx.tokenSpendingLimit.delegate, + EMPTY_DATA, + ) + .send({ from: tx.tokenSpendingLimit.delegate }) + .on('transactionHash', () => onClose()) + .catch(console.error) + } else { + dispatch( + createTransaction({ + safeAddress, + to: txRecipient, + valueInWei: txAmount, + txData: data, + notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX, + }), ) - .send({ from: tx.tokenSpendingLimit.delegate }) - .on('transactionHash', () => onClose()) - .catch(console.error) - } else { - dispatch( - createTransaction({ - safeAddress, - to: txRecipient, - valueInWei: txAmount, - txData: data, - notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX, - }), - ) - onClose() + onClose() + } } } diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/index.tsx index 03cfea3f24..9c7a7b43f0 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/OwnersColumn/index.tsx @@ -91,7 +91,7 @@ const useStyles = makeStyles(styles) type ownersColumnProps = { tx: Transaction - cancelTx: Transaction + cancelTx?: Transaction thresholdReached: boolean cancelThresholdReached: boolean onTxConfirm: () => void diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx index 5999ee0b48..1a64f0467a 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTxRow/index.tsx @@ -1,4 +1,3 @@ -import { makeStyles } from '@material-ui/core/styles' import cn from 'classnames' import React, { useState } from 'react' import { useSelector } from 'react-redux' @@ -17,7 +16,7 @@ import TxDescription from './TxDescription' import { IncomingTx } from './IncomingTx' import { CreationTx } from './CreationTx' import { OutgoingTx } from './OutgoingTx' -import { styles } from './style' +import useStyles from './style' import { getNetwork } from 'src/config' import Block from 'src/components/layout/Block' @@ -34,8 +33,6 @@ import { Transaction, TransactionTypes, SafeModuleTransaction } from 'src/logic/ import IncomingTxDescription from './IncomingTxDescription' import TransferDescription from './TxDescription/TransferDescription' -const useStyles = makeStyles(styles as any) - const ExpandedModuleTx = ({ tx }: { tx: SafeModuleTransaction }): React.ReactElement => { const classes = useStyles() @@ -72,11 +69,9 @@ const ExpandedModuleTx = ({ tx }: { tx: SafeModuleTransaction }): React.ReactEle - {recipient && ( - - - - )} + + {recipient && } + @@ -84,7 +79,7 @@ const ExpandedModuleTx = ({ tx }: { tx: SafeModuleTransaction }): React.ReactEle } interface ExpandedTxProps { - cancelTx: Transaction + cancelTx?: Transaction tx: Transaction } @@ -179,7 +174,7 @@ const ExpandedTx = ({ cancelTx, tx }: ExpandedTxProps): React.ReactElement => { /> )} {openModal === 'rejectTx' && } - {openModal === 'executeRejectTx' && ( + {openModal === 'executeRejectTx' && cancelTx && ( cssStyles +export default makeStyles( + createStyles({ + expandedTxBlock: { + borderBottom: `2px solid ${border}`, + }, + txDataContainer: { + padding: `${lg} ${md}`, + }, + txHash: { + paddingRight: '3px', + }, + 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/columns.tsx b/src/routes/safe/components/Transactions/TxsTable/columns.tsx index f9dbbfb321..6a5b285347 100644 --- a/src/routes/safe/components/Transactions/TxsTable/columns.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/columns.tsx @@ -99,7 +99,7 @@ export interface TableData { dateOrder?: number id: string status: string - tx?: Transaction | SafeModuleTransaction + tx: Transaction | SafeModuleTransaction type: any } @@ -119,7 +119,7 @@ const getIncomingTxTableData = (tx: Transaction): TableData => ({ [TX_TABLE_DATE_ID]: formatDate(tx.executionDate || '0'), [buildOrderFieldFrom(TX_TABLE_DATE_ID)]: getTime(parseISO(tx.executionDate || '0')), [TX_TABLE_AMOUNT_ID]: getIncomingTxAmount(tx), - [TX_TABLE_STATUS_ID]: tx.status, + [TX_TABLE_STATUS_ID]: tx.status ?? '', [TX_TABLE_RAW_TX_ID]: tx, }) @@ -137,7 +137,7 @@ const getTransactionTableData = (tx: Transaction, cancelTx?: Transaction): Table [TX_TABLE_DATE_ID]: txDate ? formatDate(txDate) : '', [buildOrderFieldFrom(TX_TABLE_DATE_ID)]: txDate ? getTime(parseISO(txDate)) : null, [TX_TABLE_AMOUNT_ID]: getTxAmount(tx), - [TX_TABLE_STATUS_ID]: tx.status, + [TX_TABLE_STATUS_ID]: tx.status ?? '', [TX_TABLE_RAW_TX_ID]: tx, [TX_TABLE_RAW_CANCEL_TX_ID]: cancelTx, }