(Feature) Tx Table amount notations (#812)
This commit is contained in:
parent
5014e86c3a
commit
653f68b09c
|
@ -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, *>(
|
||||||
{
|
{
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 && (
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue