* Types * Adds tokenAddress to getTxData for tokenTransfer transactions * Adds sendModalOpenHandler to EllipsisTransactionDetails * Adds getRawTxAmount util * Add isTokenTransfer fix for ether in getTxData * Uses sendFund modal for retry outgoing transfer transactions * Adds ether address in getTxData result for outgoig transfers * Uses nativeCoin * Remove fragmnet * Fix decimals for native coin * Fix decimals usage in tx transfer amount Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>
This commit is contained in:
parent
19e6df725a
commit
294ba47142
|
@ -8,7 +8,7 @@ import CopyBtn from 'src/components/CopyBtn'
|
|||
import Block from 'src/components/layout/Block'
|
||||
import Span from 'src/components/layout/Span'
|
||||
import { shortVersionOf } from 'src/logic/wallets/ethAddresses'
|
||||
import EllipsisTransactionDetails from 'src/routes/safe/components/AddressBook/EllipsisTransactionDetails'
|
||||
import { EllipsisTransactionDetails } from 'src/routes/safe/components/AddressBook/EllipsisTransactionDetails'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
|
||||
|
@ -19,9 +19,16 @@ interface EtherscanLinkProps {
|
|||
cut?: number
|
||||
knownAddress?: boolean
|
||||
value: string
|
||||
sendModalOpenHandler?: () => void
|
||||
}
|
||||
|
||||
export const EtherscanLink = ({ className, cut, knownAddress, value }: EtherscanLinkProps): React.ReactElement => {
|
||||
export const EtherscanLink = ({
|
||||
className,
|
||||
cut,
|
||||
knownAddress,
|
||||
value,
|
||||
sendModalOpenHandler,
|
||||
}: EtherscanLinkProps): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
|
@ -31,7 +38,13 @@ export const EtherscanLink = ({ className, cut, knownAddress, value }: Etherscan
|
|||
</Span>
|
||||
<CopyBtn className={cn(classes.button, classes.firstButton)} content={value} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(value)} />
|
||||
{knownAddress !== undefined ? <EllipsisTransactionDetails address={value} knownAddress={knownAddress} /> : null}
|
||||
{knownAddress !== undefined ? (
|
||||
<EllipsisTransactionDetails
|
||||
address={value}
|
||||
knownAddress={knownAddress}
|
||||
sendModalOpenHandler={sendModalOpenHandler}
|
||||
/>
|
||||
) : null}
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ClickAwayListener, Divider } from '@material-ui/core'
|
||||
import { ClickAwayListener, createStyles, Divider } from '@material-ui/core'
|
||||
import Menu from '@material-ui/core/Menu'
|
||||
import MenuItem from '@material-ui/core/MenuItem'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
|
@ -11,26 +11,38 @@ import { SAFELIST_ADDRESS } from 'src/routes/routes'
|
|||
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
||||
import { xs } from 'src/theme/variables'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
container: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
margin: `0 ${xs}`,
|
||||
borderRadius: '50%',
|
||||
transition: 'background-color .2s ease-in-out',
|
||||
'&:hover': {
|
||||
backgroundColor: '#F0EFEE',
|
||||
const useStyles = makeStyles(
|
||||
createStyles({
|
||||
container: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
margin: `0 ${xs}`,
|
||||
borderRadius: '50%',
|
||||
transition: 'background-color .2s ease-in-out',
|
||||
'&:hover': {
|
||||
backgroundColor: '#F0EFEE',
|
||||
},
|
||||
outline: 'none',
|
||||
},
|
||||
outline: 'none',
|
||||
},
|
||||
increasedPopperZindex: {
|
||||
zIndex: 2001,
|
||||
},
|
||||
})
|
||||
increasedPopperZindex: {
|
||||
zIndex: 2001,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
const EllipsisTransactionDetails = ({ address, knownAddress }) => {
|
||||
type EllipsisTransactionDetailsProps = {
|
||||
address: string
|
||||
knownAddress?: boolean
|
||||
sendModalOpenHandler?: () => void
|
||||
}
|
||||
|
||||
export const EllipsisTransactionDetails = ({
|
||||
address,
|
||||
knownAddress,
|
||||
sendModalOpenHandler,
|
||||
}: EllipsisTransactionDetailsProps): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
const [anchorEl, setAnchorEl] = React.useState(null)
|
||||
|
||||
|
@ -51,10 +63,12 @@ const EllipsisTransactionDetails = ({ address, knownAddress }) => {
|
|||
<div className={classes.container} role="menu" tabIndex={0}>
|
||||
<MoreHorizIcon onClick={handleClick} onKeyDown={handleClick} />
|
||||
<Menu anchorEl={anchorEl} id="simple-menu" keepMounted onClose={closeMenuHandler} open={Boolean(anchorEl)}>
|
||||
<MenuItem disabled onClick={closeMenuHandler}>
|
||||
Send Again
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
{sendModalOpenHandler ? (
|
||||
<>
|
||||
<MenuItem onClick={sendModalOpenHandler}>Send Again</MenuItem>
|
||||
<Divider />
|
||||
</>
|
||||
) : null}
|
||||
{knownAddress ? (
|
||||
<MenuItem onClick={addOrEditEntryHandler}>Edit Address book Entry</MenuItem>
|
||||
) : (
|
||||
|
@ -65,5 +79,3 @@ const EllipsisTransactionDetails = ({ address, knownAddress }) => {
|
|||
</ClickAwayListener>
|
||||
)
|
||||
}
|
||||
|
||||
export default EllipsisTransactionDetails
|
||||
|
|
|
@ -6,7 +6,6 @@ import React, { Suspense, useEffect, useState } from 'react'
|
|||
import Modal from 'src/components/Modal'
|
||||
import { CollectibleTx } from './screens/ReviewCollectible'
|
||||
import { CustomTx } from './screens/ContractInteraction/ReviewCustomTx'
|
||||
import { SendFundsTx } from './screens/SendFunds'
|
||||
import { ContractInteractionTx } from './screens/ContractInteraction'
|
||||
import { CustomTxProps } from './screens/ContractInteraction/SendCustomTx'
|
||||
import { ReviewTxProp } from './screens/ReviewTx'
|
||||
|
@ -53,6 +52,7 @@ type Props = {
|
|||
onClose: () => void
|
||||
recipientAddress?: string
|
||||
selectedToken?: string | NFTToken
|
||||
tokenAmount?: string
|
||||
}
|
||||
|
||||
const SendModal = ({
|
||||
|
@ -61,6 +61,7 @@ const SendModal = ({
|
|||
onClose,
|
||||
recipientAddress,
|
||||
selectedToken,
|
||||
tokenAmount,
|
||||
}: Props): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
const [activeScreen, setActiveScreen] = useState(activeScreenType || 'chooseTxType')
|
||||
|
@ -119,11 +120,11 @@ const SendModal = ({
|
|||
)}
|
||||
{activeScreen === 'sendFunds' && (
|
||||
<SendFunds
|
||||
initialValues={tx as SendFundsTx}
|
||||
onClose={onClose}
|
||||
onNext={handleTxCreation}
|
||||
recipientAddress={recipientAddress}
|
||||
selectedToken={selectedToken as string}
|
||||
amount={tokenAmount}
|
||||
/>
|
||||
)}
|
||||
{activeScreen === 'reviewTx' && (
|
||||
|
|
|
@ -48,44 +48,36 @@ const formMutators = {
|
|||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
export type SendFundsTx = {
|
||||
amount?: string
|
||||
recipientAddress?: string
|
||||
token?: string
|
||||
}
|
||||
|
||||
type SendFundsProps = {
|
||||
initialValues: SendFundsTx
|
||||
onClose: () => void
|
||||
onNext: (txInfo: unknown) => void
|
||||
recipientAddress?: string
|
||||
selectedToken?: string
|
||||
amount?: string
|
||||
}
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
const SendFunds = ({
|
||||
initialValues,
|
||||
onClose,
|
||||
onNext,
|
||||
recipientAddress,
|
||||
selectedToken = '',
|
||||
amount,
|
||||
}: SendFundsProps): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
const tokens = useSelector(extendedSafeTokensSelector)
|
||||
const addressBook = useSelector(addressBookSelector)
|
||||
const [selectedEntry, setSelectedEntry] = useState<{ address: string; name: string } | null>(() => {
|
||||
const defaultEntry = { address: '', name: '' }
|
||||
const defaultEntry = { address: recipientAddress || '', name: '' }
|
||||
|
||||
// if there's nothing to lookup for, we return the default entry
|
||||
if (!initialValues?.recipientAddress && !recipientAddress) {
|
||||
if (!recipientAddress) {
|
||||
return defaultEntry
|
||||
}
|
||||
|
||||
// if there's something to lookup for, `initialValues` has precedence over `recipientAddress`
|
||||
const predefinedAddress = initialValues?.recipientAddress ?? recipientAddress
|
||||
const addressBookEntry = addressBook.find(({ address }) => {
|
||||
return sameAddress(predefinedAddress, address)
|
||||
return sameAddress(recipientAddress, address)
|
||||
})
|
||||
|
||||
// if found in the Address Book, then we return the entry
|
||||
|
@ -126,7 +118,11 @@ const SendFunds = ({
|
|||
</IconButton>
|
||||
</Row>
|
||||
<Hairline />
|
||||
<GnoForm formMutators={formMutators} initialValues={initialValues} onSubmit={handleSubmit}>
|
||||
<GnoForm
|
||||
formMutators={formMutators}
|
||||
initialValues={{ amount, recipientAddress, token: selectedToken }}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{(...args) => {
|
||||
const formState = args[2]
|
||||
const mutators = args[3]
|
||||
|
|
|
@ -13,10 +13,11 @@ type OwnerAddressTableCellProps = {
|
|||
knownAddress?: boolean
|
||||
showLinks: boolean
|
||||
userName?: string
|
||||
sendModalOpenHandler?: () => void
|
||||
}
|
||||
|
||||
const OwnerAddressTableCell = (props: OwnerAddressTableCellProps): React.ReactElement => {
|
||||
const { address, knownAddress, showLinks, userName } = props
|
||||
const { address, knownAddress, showLinks, userName, sendModalOpenHandler } = props
|
||||
const [cut, setCut] = useState(0)
|
||||
const { width } = useWindowDimensions()
|
||||
|
||||
|
@ -36,7 +37,12 @@ const OwnerAddressTableCell = (props: OwnerAddressTableCellProps): React.ReactEl
|
|||
{showLinks ? (
|
||||
<div style={{ marginLeft: 10, flexShrink: 1, minWidth: 0 }}>
|
||||
{userName && getValidAddressBookName(userName)}
|
||||
<EtherscanLink knownAddress={knownAddress} value={address} cut={cut} />
|
||||
<EtherscanLink
|
||||
knownAddress={knownAddress}
|
||||
value={address}
|
||||
cut={cut}
|
||||
sendModalOpenHandler={sendModalOpenHandler}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Paragraph style={{ marginLeft: 10 }}>{address}</Paragraph>
|
||||
|
|
|
@ -7,23 +7,59 @@ import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/sele
|
|||
import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell'
|
||||
|
||||
import { TRANSACTIONS_DESC_SEND_TEST_ID } from './index'
|
||||
import SendModal from 'src/routes/safe/components/Balances/SendModal'
|
||||
|
||||
interface TransferDescriptionProps {
|
||||
amount: string
|
||||
amountWithSymbol: string
|
||||
recipient: string
|
||||
tokenAddress?: string
|
||||
rawAmount?: string
|
||||
isTokenTransfer: boolean
|
||||
}
|
||||
|
||||
const TransferDescription = ({ amount = '', recipient }: TransferDescriptionProps): React.ReactElement => {
|
||||
const TransferDescription = ({
|
||||
amountWithSymbol = '',
|
||||
recipient,
|
||||
tokenAddress,
|
||||
rawAmount,
|
||||
isTokenTransfer,
|
||||
}: TransferDescriptionProps): React.ReactElement => {
|
||||
const recipientName = useSelector((state) => getNameFromAddressBookSelector(state, recipient))
|
||||
const [sendModalOpen, setSendModalOpen] = React.useState(false)
|
||||
|
||||
const sendModalOpenHandler = () => {
|
||||
setSendModalOpen(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<Block data-testid={TRANSACTIONS_DESC_SEND_TEST_ID}>
|
||||
<Bold>Send {amount} to:</Bold>
|
||||
{recipientName ? (
|
||||
<OwnerAddressTableCell address={recipient} knownAddress showLinks userName={recipientName} />
|
||||
) : (
|
||||
<EtherscanLink knownAddress={false} value={recipient} />
|
||||
)}
|
||||
</Block>
|
||||
<>
|
||||
<Block data-testid={TRANSACTIONS_DESC_SEND_TEST_ID}>
|
||||
<Bold>Send {amountWithSymbol} to:</Bold>
|
||||
{recipientName ? (
|
||||
<OwnerAddressTableCell
|
||||
address={recipient}
|
||||
knownAddress
|
||||
showLinks
|
||||
userName={recipientName}
|
||||
sendModalOpenHandler={isTokenTransfer ? sendModalOpenHandler : undefined}
|
||||
/>
|
||||
) : (
|
||||
<EtherscanLink
|
||||
knownAddress={false}
|
||||
value={recipient}
|
||||
sendModalOpenHandler={isTokenTransfer ? sendModalOpenHandler : undefined}
|
||||
/>
|
||||
)}
|
||||
</Block>
|
||||
<SendModal
|
||||
activeScreenType="sendFunds"
|
||||
isOpen={sendModalOpen}
|
||||
onClose={() => setSendModalOpen(false)}
|
||||
recipientAddress={recipient}
|
||||
selectedToken={tokenAddress}
|
||||
tokenAmount={rawAmount}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import SettingsDescription from './SettingsDescription'
|
|||
import CustomDescription from './CustomDescription'
|
||||
import TransferDescription from './TransferDescription'
|
||||
|
||||
import { getTxAmount } from 'src/routes/safe/components/Transactions/TxsTable/columns'
|
||||
import { getRawTxAmount, getTxAmount } from 'src/routes/safe/components/Transactions/TxsTable/columns'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import { Transaction } from 'src/logic/safe/store/models/types/transaction'
|
||||
|
||||
|
@ -30,8 +30,12 @@ const TxDescription = ({ tx }: { tx: Transaction }): React.ReactElement => {
|
|||
recipient,
|
||||
removedOwner,
|
||||
upgradeTx,
|
||||
tokenAddress,
|
||||
isTokenTransfer,
|
||||
}: any = getTxData(tx)
|
||||
const amount = getTxAmount(tx, false)
|
||||
|
||||
const amountWithSymbol = getTxAmount(tx, false)
|
||||
const amount = getRawTxAmount(tx)
|
||||
return (
|
||||
<Block className={classes.txDataContainer}>
|
||||
{modifySettingsTx && action && (
|
||||
|
@ -43,10 +47,18 @@ const TxDescription = ({ tx }: { tx: Transaction }): React.ReactElement => {
|
|||
module={module}
|
||||
/>
|
||||
)}
|
||||
{!upgradeTx && customTx && <CustomDescription amount={amount} data={data} recipient={recipient} storedTx={tx} />}
|
||||
{!upgradeTx && customTx && (
|
||||
<CustomDescription amount={amountWithSymbol} data={data} recipient={recipient} storedTx={tx} />
|
||||
)}
|
||||
{upgradeTx && <div>{data}</div>}
|
||||
{!cancellationTx && !modifySettingsTx && !customTx && !creationTx && !upgradeTx && (
|
||||
<TransferDescription amount={amount} recipient={recipient} />
|
||||
<TransferDescription
|
||||
amountWithSymbol={amountWithSymbol}
|
||||
recipient={recipient}
|
||||
tokenAddress={tokenAddress}
|
||||
rawAmount={amount}
|
||||
isTokenTransfer={isTokenTransfer}
|
||||
/>
|
||||
)}
|
||||
</Block>
|
||||
)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { Transaction } from 'src/logic/safe/store/models/types/transaction'
|
||||
import { SAFE_METHODS_NAMES } from 'src/routes/safe/store/models/types/transactions.d'
|
||||
import { sameString } from 'src/utils/strings'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
|
||||
const getSafeVersion = (data) => {
|
||||
const contractAddress = data.substr(340, 40).toLowerCase()
|
||||
|
@ -27,6 +29,7 @@ interface TxData {
|
|||
cancellationTx?: boolean
|
||||
creationTx?: boolean
|
||||
upgradeTx?: boolean
|
||||
tokenAddress?: string
|
||||
}
|
||||
|
||||
const getTxDataForModifySettingsTxs = (tx: Transaction): TxData => {
|
||||
|
@ -97,6 +100,7 @@ const getTxDataForTxsWithDecodedParams = (tx: Transaction): TxData => {
|
|||
const { to } = tx.decodedParams.transfer || {}
|
||||
txData.recipient = to
|
||||
txData.isTokenTransfer = true
|
||||
txData.tokenAddress = tx.recipient
|
||||
return txData
|
||||
}
|
||||
|
||||
|
@ -133,6 +137,12 @@ const getTxDataForTxsWithDecodedParams = (tx: Transaction): TxData => {
|
|||
export const getTxData = (tx: Transaction): TxData => {
|
||||
const txData: TxData = {}
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
if (sameString(tx.type, 'outgoing') && tx.symbol && sameString(tx.symbol, nativeCoin.symbol)) {
|
||||
txData.isTokenTransfer = true
|
||||
txData.tokenAddress = nativeCoin.address
|
||||
}
|
||||
|
||||
if (tx.decodedParams) {
|
||||
return getTxDataForTxsWithDecodedParams(tx)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ 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 { CancellationTransactions } from 'src/logic/safe/store/reducer/cancellationTransactions'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
|
||||
export const TX_TABLE_ID = 'id'
|
||||
export const TX_TABLE_TYPE_ID = 'type'
|
||||
|
@ -71,6 +72,25 @@ export const getTxAmount = (tx: Transaction, formatted = true): string => {
|
|||
return getAmountWithSymbol({ decimals: decimals as string, symbol: symbol as string, value }, formatted)
|
||||
}
|
||||
|
||||
export const getRawTxAmount = (tx: Transaction): string => {
|
||||
const { decimals, decodedParams, isTokenTransfer } = tx
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
const { value } = isTokenTransfer && !!decodedParams?.transfer ? decodedParams.transfer : tx
|
||||
|
||||
if (tx.isCollectibleTransfer) {
|
||||
return '1'
|
||||
}
|
||||
|
||||
if (!isTokenTransfer && !(Number(value) > 0)) {
|
||||
return NOT_AVAILABLE
|
||||
}
|
||||
|
||||
const tokenDecimals = decimals ?? nativeCoin.decimals
|
||||
const finalValue = new BigNumber(value).times(`1e-${tokenDecimals}`).toFixed()
|
||||
|
||||
return finalValue === 'NaN' ? NOT_AVAILABLE : finalValue
|
||||
}
|
||||
|
||||
export interface TableData {
|
||||
amount: string
|
||||
cancelTx?: Transaction
|
||||
|
|
Loading…
Reference in New Issue