(Feature) Tx Table amount notations (#812)

This commit is contained in:
Fernando 2020-04-27 12:15:33 -03:00 committed by GitHub
parent 5014e86c3a
commit 653f68b09c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 69 additions and 47 deletions

View File

@ -9,7 +9,7 @@ import { type Token, makeToken } from '~/logic/tokens/store/model/token'
export const TOKEN_REDUCER_ID = 'tokens' export const TOKEN_REDUCER_ID = 'tokens'
export type State = Map<string, Map<string, Token>> export type State = Map<string, Token>
export default handleActions<State, *>( export default handleActions<State, *>(
{ {

View File

@ -18,7 +18,7 @@ export const formatAmount = (number: string | number) => {
let numberFloat = parseFloat(number) let numberFloat = parseFloat(number)
if (numberFloat === 0) { if (numberFloat === 0) {
numberFloat = '0.000' numberFloat = '0'
} else if (numberFloat < 0.001) { } else if (numberFloat < 0.001) {
numberFloat = '< 0.001' numberFloat = '< 0.001'
} else if (numberFloat < 1000) { } else if (numberFloat < 1000) {

View File

@ -49,7 +49,7 @@ const IncomingTxDescription = ({ tx }: Props) => {
const txFromName = getNameFromAddressBook(tx.from) const txFromName = getNameFromAddressBook(tx.from)
return ( return (
<Block className={classes.txDataContainer}> <Block className={classes.txDataContainer}>
<TransferDescription from={tx.from} txFromName={txFromName} value={getIncomingTxAmount(tx)} /> <TransferDescription from={tx.from} txFromName={txFromName} value={getIncomingTxAmount(tx, false)} />
</Block> </Block>
) )
} }

View File

@ -222,7 +222,7 @@ const TxDescription = ({ classes, tx }: Props) => {
removedOwner, removedOwner,
upgradeTx, upgradeTx,
} = getTxData(tx) } = getTxData(tx)
const amount = getTxAmount(tx) const amount = getTxAmount(tx, false)
return ( return (
<Block className={classes.txDataContainer}> <Block className={classes.txDataContainer}>
{modifySettingsTx && action && ( {modifySettingsTx && action && (

View File

@ -41,8 +41,9 @@ const ExpandedTx = ({ cancelTx, tx }: Props) => {
const [openModal, setOpenModal] = useState<OpenModal>(null) const [openModal, setOpenModal] = useState<OpenModal>(null)
const openApproveModal = () => setOpenModal('approveTx') const openApproveModal = () => setOpenModal('approveTx')
const closeModal = () => setOpenModal(null) const closeModal = () => setOpenModal(null)
const thresholdReached = !INCOMING_TX_TYPES.includes(tx.type) && threshold <= tx.confirmations.size const isIncomingTx = !!INCOMING_TX_TYPES[tx.type]
const canExecute = !INCOMING_TX_TYPES.includes(tx.type) && nonce === tx.nonce const thresholdReached = !isIncomingTx && threshold <= tx.confirmations.size
const canExecute = !isIncomingTx && nonce === tx.nonce
const cancelThresholdReached = !!cancelTx && threshold <= cancelTx.confirmations.size const cancelThresholdReached = !!cancelTx && threshold <= cancelTx.confirmations.size
const canExecuteCancel = nonce === tx.nonce const canExecuteCancel = nonce === tx.nonce
@ -59,22 +60,22 @@ const ExpandedTx = ({ cancelTx, tx }: Props) => {
<Block className={classes.expandedTxBlock}> <Block className={classes.expandedTxBlock}>
<Row> <Row>
<Col layout="column" xs={6}> <Col layout="column" xs={6}>
<Block <Block className={cn(classes.txDataContainer, isIncomingTx && classes.incomingTxBlock)}>
className={cn(classes.txDataContainer, INCOMING_TX_TYPES.includes(tx.type) && classes.incomingTxBlock)}
>
<Block align="left" className={classes.txData}> <Block align="left" className={classes.txData}>
<Bold className={classes.txHash}>Hash:</Bold> <Bold className={classes.txHash}>Hash:</Bold>
{tx.executionTxHash ? <EtherScanLink cut={8} type="tx" value={tx.executionTxHash} /> : 'n/a'} {tx.executionTxHash ? <EtherScanLink cut={8} type="tx" value={tx.executionTxHash} /> : 'n/a'}
</Block> </Block>
<Paragraph noMargin> {!isIncomingTx && (
<Bold>Nonce: </Bold> <Paragraph noMargin>
<Span>{tx.nonce}</Span> <Bold>Nonce: </Bold>
</Paragraph> <Span>{tx.nonce}</Span>
</Paragraph>
)}
<Paragraph noMargin> <Paragraph noMargin>
<Bold>Fee: </Bold> <Bold>Fee: </Bold>
{tx.fee ? tx.fee : 'n/a'} {tx.fee ? tx.fee : 'n/a'}
</Paragraph> </Paragraph>
{INCOMING_TX_TYPES.includes(tx.type) ? ( {isIncomingTx ? (
<> <>
<Paragraph noMargin> <Paragraph noMargin>
<Bold>Created: </Bold> <Bold>Created: </Bold>
@ -113,9 +114,9 @@ const ExpandedTx = ({ cancelTx, tx }: Props) => {
)} )}
</Block> </Block>
<Hairline /> <Hairline />
{INCOMING_TX_TYPES.includes(tx.type) ? <IncomingTxDescription tx={tx} /> : <TxDescription tx={tx} />} {isIncomingTx ? <IncomingTxDescription tx={tx} /> : <TxDescription tx={tx} />}
</Col> </Col>
{!INCOMING_TX_TYPES.includes(tx.type) && ( {!isIncomingTx && (
<OwnersColumn <OwnersColumn
cancelThresholdReached={cancelThresholdReached} cancelThresholdReached={cancelThresholdReached}
cancelTx={cancelTx} cancelTx={cancelTx}

View File

@ -9,7 +9,6 @@ import TxType from './TxType'
import { type Column } from '~/components/Table/TableHead' import { type Column } from '~/components/Table/TableHead'
import { type SortRow, buildOrderFieldFrom } from '~/components/Table/sorting' import { type SortRow, buildOrderFieldFrom } from '~/components/Table/sorting'
import { formatAmount } from '~/logic/tokens/utils/formatAmount' import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { INCOMING_TX_TYPES, type IncomingTransaction } from '~/routes/safe/store/models/incomingTransaction' import { INCOMING_TX_TYPES, type IncomingTransaction } from '~/routes/safe/store/models/incomingTransaction'
import { type Transaction } from '~/routes/safe/store/models/transaction' import { type Transaction } from '~/routes/safe/store/models/transaction'
@ -34,29 +33,40 @@ type TxData = {
export const formatDate = (date: string): string => format(parseISO(date), 'MMM d, yyyy - HH:mm:ss') export const formatDate = (date: string): string => format(parseISO(date), 'MMM d, yyyy - HH:mm:ss')
export const getIncomingTxAmount = (tx: IncomingTransaction) => { type TxValues = {
const txAmount = tx.value ? `${new BigNumber(tx.value).div(`1e${tx.decimals}`).toFixed()}` : 'n/a' value?: string | number,
return `${txAmount} ${tx.symbol || 'n/a'}` decimals?: string | number,
symbol?: string,
} }
export const getTxAmount = (tx: Transaction) => { const NOT_AVAILABLE = 'n/a'
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
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) { return `${txAmount} ${symbol}`
const tokenDecimals = tx.decimals.toNumber ? tx.decimals.toNumber() : tx.decimals }
txAmount = `${formatAmount(
BigNumber(tx.decodedParams.value) export const getIncomingTxAmount = (tx: IncomingTransaction, formatted: boolean = true) => {
.div(10 ** tokenDecimals) // simple workaround to avoid displaying unexpected values for incoming NFT transfer
.toString(), if (INCOMING_TX_TYPES[tx.type] === INCOMING_TX_TYPES.ERC721_TRANSFER) {
)} ${tx.symbol}` return `1 ${tx.symbol}`
} else if (Number(tx.value) > 0) {
txAmount = `${fromWei(toBN(tx.value), 'ether')} ${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<TxData> export type TransactionRow = SortRow<TxData>
@ -106,7 +116,7 @@ export const getTxTableData = (
const cancelTxsByNonce = cancelTxs.reduce((acc, tx) => acc.set(tx.nonce, tx), Map()) const cancelTxsByNonce = cancelTxs.reduce((acc, tx) => acc.set(tx.nonce, tx), Map())
return transactions.map((tx) => { return transactions.map((tx) => {
if (INCOMING_TX_TYPES.includes(tx.type)) { if (INCOMING_TX_TYPES[tx.type]) {
return getIncomingTxTableData(tx) return getIncomingTxTableData(tx)
} }

View File

@ -76,12 +76,12 @@ type IncomingTxServiceModel = {
export const buildTransactionFrom = async ( export const buildTransactionFrom = async (
safeAddress: string, safeAddress: string,
tx: TxServiceModel,
knownTokens, knownTokens,
tx: TxServiceModel,
txTokenCode,
txTokenDecimals, txTokenDecimals,
txTokenSymbol,
txTokenName, txTokenName,
code, txTokenSymbol,
): Promise<Transaction> => { ): Promise<Transaction> => {
const localSafe = await getLocalSafe(safeAddress) 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 modifySettingsTx = sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 && !!tx.data
const cancellationTx = sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 && !tx.data const cancellationTx = sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 && !tx.data
const isERC721Token = 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) (isTokenTransfer(tx.data, Number(tx.value)) && !knownTokens.get(tx.to) && txTokenDecimals !== null)
let isSendTokenTx = !isERC721Token && isTokenTransfer(tx.data, Number(tx.value)) let isSendTokenTx = !isERC721Token && isTokenTransfer(tx.data, Number(tx.value))
const isMultiSendTx = isMultisendTransaction(tx.data, Number(tx.value)) const isMultiSendTx = isMultisendTransaction(tx.data, Number(tx.value))
@ -118,7 +118,7 @@ export const buildTransactionFrom = async (
let refundParams = null let refundParams = null
if (tx.gasPrice > 0) { if (tx.gasPrice > 0) {
const refundSymbol = txTokenSymbol || 'ETH' 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 feeString = (tx.gasPrice * (tx.baseGas + tx.safeTxGas)).toString().padStart(decimals, 0)
const whole = feeString.slice(0, feeString.length - decimals) || '0' const whole = feeString.slice(0, feeString.length - decimals) || '0'
const fraction = feeString.slice(feeString.length - decimals) const fraction = feeString.slice(feeString.length - decimals)
@ -230,8 +230,8 @@ const addMockSafeCreationTx = (safeAddress): Array<TxServiceModel> => [
const batchRequestTxsData = (txs: any[]) => { const batchRequestTxsData = (txs: any[]) => {
const web3Batch = new web3.BatchRequest() const web3Batch = new web3.BatchRequest()
const whenTxsValues = txs.map((tx) => { const txsTokenInfo = txs.map((tx) => {
const methods = ['decimals', { method: 'getCode', type: 'eth', args: [tx.to] }, 'symbol', 'name'] const methods = [{ method: 'getCode', type: 'eth', args: [tx.to] }, 'decimals', 'name', 'symbol']
return generateBatchRequests({ return generateBatchRequests({
abi: ERC20Detailed.abi, abi: ERC20Detailed.abi,
address: tx.to, address: tx.to,
@ -243,7 +243,7 @@ const batchRequestTxsData = (txs: any[]) => {
web3Batch.execute() web3Batch.execute()
return Promise.all(whenTxsValues) return Promise.all(txsTokenInfo)
} }
const batchRequestIncomingTxsData = (txs: IncomingTxServiceModel[]) => { const batchRequestIncomingTxsData = (txs: IncomingTxServiceModel[]) => {
@ -341,9 +341,15 @@ export const loadSafeTransactions = async (safeAddress: string, getState: GetSta
const txsWithData = await batchRequestTxsData(transactions) const txsWithData = await batchRequestTxsData(transactions)
// In case that the etags don't match, we parse the new transactions and save them to the cache // In case that the etags don't match, we parse the new transactions and save them to the cache
const txsRecord: Array<RecordInstance<TransactionProps>> = await Promise.all( const txsRecord: Array<RecordInstance<TransactionProps>> = await Promise.all(
txsWithData.map(([tx: TxServiceModel, decimals, code, symbol, name]) => txsWithData.map(([tx: TxServiceModel, code, decimals, name, symbol]) => {
buildTransactionFrom(safeAddress, tx, knownTokens, decimals, symbol, name, code), 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')) const groupedTxs = List(txsRecord).groupBy((tx) => (tx.get('cancellationTx') ? 'cancel' : 'outgoing'))

View File

@ -2,7 +2,12 @@
import { Record } from 'immutable' import { Record } from 'immutable'
import type { RecordFactory, RecordOf } 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 = { export type IncomingTransactionProps = {
blockNumber: number, blockNumber: number,