From 653f68b09c4473f4970693896a1a66eb00849afe Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 27 Apr 2020 12:15:33 -0300 Subject: [PATCH] (Feature) Tx Table amount notations (#812) --- src/logic/tokens/store/reducer/tokens.js | 2 +- src/logic/tokens/utils/formatAmount.js | 2 +- .../IncomingTxDescription/index.jsx | 2 +- .../ExpandedTx/TxDescription/index.jsx | 2 +- .../TxsTable/ExpandedTx/index.jsx | 25 +++++----- .../Transactions/TxsTable/columns.js | 48 +++++++++++-------- .../safe/store/actions/fetchTransactions.js | 28 ++++++----- .../safe/store/models/incomingTransaction.js | 7 ++- 8 files changed, 69 insertions(+), 47 deletions(-) diff --git a/src/logic/tokens/store/reducer/tokens.js b/src/logic/tokens/store/reducer/tokens.js index 3d9d1916..fa4f47a2 100644 --- a/src/logic/tokens/store/reducer/tokens.js +++ b/src/logic/tokens/store/reducer/tokens.js @@ -9,7 +9,7 @@ import { type Token, makeToken } from '~/logic/tokens/store/model/token' export const TOKEN_REDUCER_ID = 'tokens' -export type State = Map> +export type State = Map export default handleActions( { diff --git a/src/logic/tokens/utils/formatAmount.js b/src/logic/tokens/utils/formatAmount.js index 6c4221af..a6b5e628 100644 --- a/src/logic/tokens/utils/formatAmount.js +++ b/src/logic/tokens/utils/formatAmount.js @@ -18,7 +18,7 @@ export const formatAmount = (number: string | number) => { let numberFloat = parseFloat(number) if (numberFloat === 0) { - numberFloat = '0.000' + numberFloat = '0' } else if (numberFloat < 0.001) { numberFloat = '< 0.001' } else if (numberFloat < 1000) { diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.jsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.jsx index a7107233..b169866c 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.jsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.jsx @@ -49,7 +49,7 @@ const IncomingTxDescription = ({ tx }: Props) => { const txFromName = getNameFromAddressBook(tx.from) return ( - + ) } diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.jsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.jsx index dd09fcec..54ac5a2a 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.jsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.jsx @@ -222,7 +222,7 @@ const TxDescription = ({ classes, tx }: Props) => { removedOwner, upgradeTx, } = getTxData(tx) - const amount = getTxAmount(tx) + const amount = getTxAmount(tx, false) return ( {modifySettingsTx && action && ( diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.jsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.jsx index fdaaeee3..f3e09260 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.jsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.jsx @@ -41,8 +41,9 @@ const ExpandedTx = ({ cancelTx, tx }: Props) => { const [openModal, setOpenModal] = useState(null) const openApproveModal = () => setOpenModal('approveTx') const closeModal = () => setOpenModal(null) - const thresholdReached = !INCOMING_TX_TYPES.includes(tx.type) && threshold <= tx.confirmations.size - const canExecute = !INCOMING_TX_TYPES.includes(tx.type) && nonce === tx.nonce + const isIncomingTx = !!INCOMING_TX_TYPES[tx.type] + const thresholdReached = !isIncomingTx && threshold <= tx.confirmations.size + const canExecute = !isIncomingTx && nonce === tx.nonce const cancelThresholdReached = !!cancelTx && threshold <= cancelTx.confirmations.size const canExecuteCancel = nonce === tx.nonce @@ -59,22 +60,22 @@ const ExpandedTx = ({ cancelTx, tx }: Props) => { - + Hash: {tx.executionTxHash ? : 'n/a'} - - Nonce: - {tx.nonce} - + {!isIncomingTx && ( + + Nonce: + {tx.nonce} + + )} Fee: {tx.fee ? tx.fee : 'n/a'} - {INCOMING_TX_TYPES.includes(tx.type) ? ( + {isIncomingTx ? ( <> Created: @@ -113,9 +114,9 @@ const ExpandedTx = ({ cancelTx, tx }: Props) => { )} - {INCOMING_TX_TYPES.includes(tx.type) ? : } + {isIncomingTx ? : } - {!INCOMING_TX_TYPES.includes(tx.type) && ( + {!isIncomingTx && ( format(parseISO(date), 'MMM d, yyyy - HH:mm:ss') -export const getIncomingTxAmount = (tx: IncomingTransaction) => { - const txAmount = tx.value ? `${new BigNumber(tx.value).div(`1e${tx.decimals}`).toFixed()}` : 'n/a' - return `${txAmount} ${tx.symbol || 'n/a'}` +type TxValues = { + value?: string | number, + decimals?: string | number, + symbol?: string, } -export const getTxAmount = (tx: Transaction) => { - const web3 = getWeb3() - const { fromWei, toBN } = web3.utils +const NOT_AVAILABLE = 'n/a' - let txAmount = 'n/a' +const getAmountWithSymbol = ({ decimals = 0, symbol = NOT_AVAILABLE, value }: TxValues, formatted = false) => { + const nonFormattedValue = BigNumber(value).times(`1e-${decimals}`).toFixed() + const finalValue = formatted ? formatAmount(nonFormattedValue).toString() : nonFormattedValue + const txAmount = finalValue === 'NaN' ? NOT_AVAILABLE : finalValue - if (tx.isTokenTransfer && tx.decodedParams) { - const tokenDecimals = tx.decimals.toNumber ? tx.decimals.toNumber() : tx.decimals - txAmount = `${formatAmount( - BigNumber(tx.decodedParams.value) - .div(10 ** tokenDecimals) - .toString(), - )} ${tx.symbol}` - } else if (Number(tx.value) > 0) { - txAmount = `${fromWei(toBN(tx.value), 'ether')} ${tx.symbol}` + return `${txAmount} ${symbol}` +} + +export const getIncomingTxAmount = (tx: IncomingTransaction, formatted: boolean = true) => { + // simple workaround to avoid displaying unexpected values for incoming NFT transfer + if (INCOMING_TX_TYPES[tx.type] === INCOMING_TX_TYPES.ERC721_TRANSFER) { + return `1 ${tx.symbol}` } - return txAmount + return getAmountWithSymbol(tx, formatted) +} + +export const getTxAmount = (tx: Transaction, formatted: boolean = true) => { + const { decimals = 18, decodedParams, isTokenTransfer, symbol } = tx + const { value } = isTokenTransfer && decodedParams && decodedParams.value ? decodedParams : tx + + if (!isTokenTransfer && !(Number(value) > 0)) { + return NOT_AVAILABLE + } + + return getAmountWithSymbol({ decimals, symbol, value }, formatted) } export type TransactionRow = SortRow @@ -106,7 +116,7 @@ export const getTxTableData = ( const cancelTxsByNonce = cancelTxs.reduce((acc, tx) => acc.set(tx.nonce, tx), Map()) return transactions.map((tx) => { - if (INCOMING_TX_TYPES.includes(tx.type)) { + if (INCOMING_TX_TYPES[tx.type]) { return getIncomingTxTableData(tx) } diff --git a/src/routes/safe/store/actions/fetchTransactions.js b/src/routes/safe/store/actions/fetchTransactions.js index c9ef0d11..267b5c3c 100644 --- a/src/routes/safe/store/actions/fetchTransactions.js +++ b/src/routes/safe/store/actions/fetchTransactions.js @@ -76,12 +76,12 @@ type IncomingTxServiceModel = { export const buildTransactionFrom = async ( safeAddress: string, - tx: TxServiceModel, knownTokens, + tx: TxServiceModel, + txTokenCode, txTokenDecimals, - txTokenSymbol, txTokenName, - code, + txTokenSymbol, ): Promise => { const localSafe = await getLocalSafe(safeAddress) @@ -108,7 +108,7 @@ export const buildTransactionFrom = async ( const modifySettingsTx = sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 && !!tx.data const cancellationTx = sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 && !tx.data const isERC721Token = - (code && code.includes(SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH)) || + (txTokenCode && txTokenCode.includes(SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH)) || (isTokenTransfer(tx.data, Number(tx.value)) && !knownTokens.get(tx.to) && txTokenDecimals !== null) let isSendTokenTx = !isERC721Token && isTokenTransfer(tx.data, Number(tx.value)) const isMultiSendTx = isMultisendTransaction(tx.data, Number(tx.value)) @@ -118,7 +118,7 @@ export const buildTransactionFrom = async ( let refundParams = null if (tx.gasPrice > 0) { const refundSymbol = txTokenSymbol || 'ETH' - const decimals = txTokenName || 18 + const decimals = txTokenDecimals || 18 const feeString = (tx.gasPrice * (tx.baseGas + tx.safeTxGas)).toString().padStart(decimals, 0) const whole = feeString.slice(0, feeString.length - decimals) || '0' const fraction = feeString.slice(feeString.length - decimals) @@ -230,8 +230,8 @@ const addMockSafeCreationTx = (safeAddress): Array => [ const batchRequestTxsData = (txs: any[]) => { const web3Batch = new web3.BatchRequest() - const whenTxsValues = txs.map((tx) => { - const methods = ['decimals', { method: 'getCode', type: 'eth', args: [tx.to] }, 'symbol', 'name'] + const txsTokenInfo = txs.map((tx) => { + const methods = [{ method: 'getCode', type: 'eth', args: [tx.to] }, 'decimals', 'name', 'symbol'] return generateBatchRequests({ abi: ERC20Detailed.abi, address: tx.to, @@ -243,7 +243,7 @@ const batchRequestTxsData = (txs: any[]) => { web3Batch.execute() - return Promise.all(whenTxsValues) + return Promise.all(txsTokenInfo) } const batchRequestIncomingTxsData = (txs: IncomingTxServiceModel[]) => { @@ -341,9 +341,15 @@ export const loadSafeTransactions = async (safeAddress: string, getState: GetSta const txsWithData = await batchRequestTxsData(transactions) // In case that the etags don't match, we parse the new transactions and save them to the cache const txsRecord: Array> = await Promise.all( - txsWithData.map(([tx: TxServiceModel, decimals, code, symbol, name]) => - buildTransactionFrom(safeAddress, tx, knownTokens, decimals, symbol, name, code), - ), + txsWithData.map(([tx: TxServiceModel, code, decimals, name, symbol]) => { + const knownToken = knownTokens.get(tx.to) + + if (knownToken) { + ;({ decimals, name, symbol } = knownToken) + } + + return buildTransactionFrom(safeAddress, knownTokens, tx, code, decimals, name, symbol) + }), ) const groupedTxs = List(txsRecord).groupBy((tx) => (tx.get('cancellationTx') ? 'cancel' : 'outgoing')) diff --git a/src/routes/safe/store/models/incomingTransaction.js b/src/routes/safe/store/models/incomingTransaction.js index ebe5d7f5..d88405b0 100644 --- a/src/routes/safe/store/models/incomingTransaction.js +++ b/src/routes/safe/store/models/incomingTransaction.js @@ -2,7 +2,12 @@ import { Record } from 'immutable' import type { RecordFactory, RecordOf } from 'immutable' -export const INCOMING_TX_TYPES = ['INCOMING', 'ERC721_TRANSFER', 'ERC20_TRANSFER', 'ETHER_TRANSFER'] +export const INCOMING_TX_TYPES = { + INCOMING: 'INCOMING', + ERC721_TRANSFER: 'ERC721_TRANSFER', + ERC20_TRANSFER: 'ERC20_TRANSFER', + ETHER_TRANSFER: 'ETHER_TRANSFER', +} export type IncomingTransactionProps = { blockNumber: number,