(Fix) send tx from address book (#677)

* fix: Send funds not working when selecting receipt from addressBook

Also, this commit includes an intent to unify/simplify SendModal component

fixes #632

* Set default value to txData for custom txs

fixes #632
This commit is contained in:
Fernando 2020-03-20 17:06:27 -03:00 committed by GitHub
parent c4cc79c682
commit a0ce9683dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 122 additions and 223 deletions

View File

@ -42,8 +42,7 @@ import RenameOwnerIcon from '~/routes/safe/components/Settings/ManageOwners/asse
import type { OwnerRow } from '~/routes/safe/components/Settings/ManageOwners/dataFetcher'
import RemoveOwnerIcon from '~/routes/safe/components/Settings/assets/icons/bin.svg'
import RemoveOwnerIconDisabled from '~/routes/safe/components/Settings/assets/icons/disabled-bin.svg'
import { extendedSafeTokensSelector } from '~/routes/safe/container/selector'
import { addressBookQueryParamsSelector, safeSelector, safesListSelector } from '~/routes/safe/store/selectors'
import { addressBookQueryParamsSelector, safesListSelector } from '~/routes/safe/store/selectors'
type Props = {
classes: Object,
@ -86,10 +85,7 @@ const AddressBookTable = ({ classes }: Props) => {
}
}, [addressBook])
const safe = useSelector(safeSelector)
const safesList = useSelector(safesListSelector)
const activeTokens = useSelector(extendedSafeTokensSelector)
const { address, ethBalance, name } = safe
const newEntryModalHandler = (entry: AddressBookEntry) => {
setEditCreateEntryModalOpen(false)
@ -223,13 +219,9 @@ const AddressBookTable = ({ classes }: Props) => {
/>
<SendModal
activeScreenType="chooseTxType"
ethBalance={ethBalance}
isOpen={sendFundsModalOpen}
onClose={() => setSendFundsModalOpen(false)}
recipientAddress={selectedEntry && selectedEntry.entry ? selectedEntry.entry.address : undefined}
safeAddress={address}
safeName={name}
tokens={activeTokens}
/>
</>
)

View File

@ -10,7 +10,6 @@ import Paragraph from '~/components/layout/Paragraph'
import type { NFTAssetsState, NFTTokensState } from '~/logic/collectibles/store/reducer/collectibles'
import { nftAssetsSelector, nftTokensSelector } from '~/logic/collectibles/store/selectors'
import SendModal from '~/routes/safe/components/Balances/SendModal'
import { safeSelector } from '~/routes/safe/store/selectors'
import { fontColor, lg, screenSm, screenXs } from '~/theme/variables'
const useStyles = makeStyles({
@ -80,7 +79,6 @@ const Collectibles = () => {
const classes = useStyles()
const [selectedToken, setSelectedToken] = React.useState({})
const [sendNFTsModalOpen, setSendNFTsModalOpen] = React.useState(false)
const { address, ethBalance, name } = useSelector(safeSelector)
const nftAssets: NFTAssetsState = useSelector(nftAssetsSelector)
const nftTokens: NFTTokensState = useSelector(nftTokensSelector)
const nftAssetsKeys = Object.keys(nftAssets)
@ -124,11 +122,8 @@ const Collectibles = () => {
</div>
<SendModal
activeScreenType="sendCollectible"
ethBalance={ethBalance}
isOpen={sendNFTsModalOpen}
onClose={() => setSendNFTsModalOpen(false)}
safeAddress={address}
safeName={name}
selectedToken={selectedToken}
/>
</Card>

View File

@ -2,7 +2,6 @@
import CircularProgress from '@material-ui/core/CircularProgress'
import { makeStyles } from '@material-ui/core/styles'
import cn from 'classnames'
import { List } from 'immutable'
import React, { Suspense, useEffect, useState } from 'react'
import Modal from '~/components/Modal'
@ -33,16 +32,11 @@ type ActiveScreen =
| 'reviewCollectible'
type Props = {
onClose: () => void,
isOpen: boolean,
safeAddress: string,
safeName: string,
ethBalance: string,
tokens: List<Token>,
selectedToken?: string | NFTToken | {},
createTransaction?: Function,
activeScreenType: ActiveScreen,
isOpen: boolean,
onClose: () => void,
recipientAddress?: string,
selectedToken?: string | NFTToken | {},
}
type TxStateType =
@ -71,18 +65,7 @@ const useStyles = makeStyles({
},
})
const SendModal = ({
activeScreenType,
createTransaction,
ethBalance,
isOpen,
onClose,
recipientAddress,
safeAddress,
safeName,
selectedToken,
tokens,
}: Props) => {
const SendModal = ({ activeScreenType, isOpen, onClose, recipientAddress, selectedToken }: Props) => {
const classes = useStyles()
const [activeScreen, setActiveScreen] = useState<ActiveScreen>(activeScreenType || 'chooseTxType')
const [tx, setTx] = useState<TxStateType>({})
@ -129,50 +112,26 @@ const SendModal = ({
)}
{activeScreen === 'sendFunds' && (
<SendFunds
ethBalance={ethBalance}
initialValues={tx}
onClose={onClose}
onSubmit={handleTxCreation}
onNext={handleTxCreation}
recipientAddress={recipientAddress}
safeAddress={safeAddress}
safeName={safeName}
selectedToken={selectedToken}
tokens={tokens}
/>
)}
{activeScreen === 'reviewTx' && (
<ReviewTx
createTransaction={createTransaction}
ethBalance={ethBalance}
onClose={onClose}
safeAddress={safeAddress}
safeName={safeName}
setActiveScreen={setActiveScreen}
tokens={tokens}
tx={tx}
/>
<ReviewTx onClose={onClose} onPrev={() => setActiveScreen('sendFunds')} tx={tx} />
)}
{activeScreen === 'sendCustomTx' && (
<SendCustomTx
ethBalance={ethBalance}
initialValues={tx}
onClose={onClose}
onSubmit={handleCustomTxCreation}
onNext={handleCustomTxCreation}
recipientAddress={recipientAddress}
safeAddress={safeAddress}
safeName={safeName}
/>
)}
{activeScreen === 'reviewCustomTx' && (
<ReviewCustomTx
createTransaction={createTransaction}
ethBalance={ethBalance}
onClose={onClose}
safeAddress={safeAddress}
safeName={safeName}
setActiveScreen={setActiveScreen}
tx={tx}
/>
<ReviewCustomTx onClose={onClose} onPrev={() => setActiveScreen('sendCustomTx')} tx={tx} />
)}
{activeScreen === 'sendCollectible' && (
<SendCollectible

View File

@ -2,7 +2,6 @@
import IconButton from '@material-ui/core/IconButton'
import { makeStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import { List } from 'immutable'
import { withSnackbar } from 'notistack'
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
@ -30,7 +29,6 @@ import {
getERC721TokenContract,
getHumanFriendlyToken,
} from '~/logic/tokens/store/actions/fetchTokens'
import { type Token } from '~/logic/tokens/store/model/token'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH } from '~/logic/tokens/utils/tokenHelpers'
import { getWeb3 } from '~/logic/wallets/getWeb3'
@ -44,14 +42,11 @@ import { textShortener } from '~/utils/strings'
const useStyles = makeStyles(styles)
type Props = {
closeSnackbar: Function,
enqueueSnackbar: Function,
onClose: () => void,
onPrev: () => void,
classes: Object,
tx: Object,
tokens: List<Token>,
createTransaction: Function,
enqueueSnackbar: Function,
closeSnackbar: Function,
}
const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }: Props) => {

View File

@ -1,9 +1,10 @@
// @flow
import IconButton from '@material-ui/core/IconButton'
import { withStyles } from '@material-ui/core/styles'
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'
import ArrowDown from '../assets/arrow-down.svg'
@ -26,44 +27,37 @@ import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
import createTransaction from '~/routes/safe/store/actions/createTransaction'
import { safeSelector } from '~/routes/safe/store/selectors'
import { sm } from '~/theme/variables'
type Props = {
closeSnackbar: () => void,
enqueueSnackbar: () => void,
onClose: () => void,
setActiveScreen: Function,
classes: Object,
safeAddress: string,
safeName: string,
ethBalance: string,
onPrev: () => void,
tx: Object,
createTransaction: Function,
enqueueSnackbar: Function,
closeSnackbar: Function,
}
const ReviewCustomTx = ({
classes,
closeSnackbar,
createTransaction,
enqueueSnackbar,
ethBalance,
onClose,
safeAddress,
safeName,
setActiveScreen,
tx,
}: Props) => {
const useStyles = makeStyles(styles)
const ReviewCustomTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }: Props) => {
const classes = useStyles()
const dispatch = useDispatch()
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, tx.recipientAddress, tx.data.trim())
const estimateGas = async () => {
const { fromWei, toBN } = getWeb3().utils
const txData = tx.data ? tx.data.trim() : ''
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, tx.recipientAddress, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
@ -79,9 +73,10 @@ const ReviewCustomTx = ({
const submitTx = async () => {
const web3 = getWeb3()
const txRecipient = tx.recipientAddress
const txData = tx.data.trim()
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0
const txData = tx.data ? tx.data.trim() : ''
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : '0'
dispatch(
createTransaction({
safeAddress,
to: txRecipient,
@ -90,7 +85,9 @@ const ReviewCustomTx = ({
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
enqueueSnackbar,
closeSnackbar,
})
}),
)
onClose()
}
@ -167,7 +164,7 @@ const ReviewCustomTx = ({
</Block>
<Hairline />
<Row align="center" className={classes.buttonRow}>
<Button minWidth={140} onClick={() => setActiveScreen('sendCustomTx')}>
<Button minWidth={140} onClick={onPrev}>
Back
</Button>
<Button
@ -186,4 +183,4 @@ const ReviewCustomTx = ({
)
}
export default withStyles(styles)(withSnackbar(ReviewCustomTx))
export default withSnackbar(ReviewCustomTx)

View File

@ -1,11 +1,11 @@
// @flow
import IconButton from '@material-ui/core/IconButton'
import { withStyles } from '@material-ui/core/styles'
import { makeStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import { BigNumber } from 'bignumber.js'
import { List } from 'immutable'
import { withSnackbar } from 'notistack'
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import ArrowDown from '../assets/arrow-down.svg'
@ -24,65 +24,63 @@ import Row from '~/components/layout/Row'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
import { type Token } from '~/logic/tokens/store/model/token'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
import { extendedSafeTokensSelector } from '~/routes/safe/container/selector'
import createTransaction from '~/routes/safe/store/actions/createTransaction'
import { safeSelector } from '~/routes/safe/store/selectors'
import { sm } from '~/theme/variables'
type Props = {
onClose: () => void,
setActiveScreen: Function,
classes: Object,
safeAddress: string,
safeName: string,
ethBalance: string,
tx: Object,
tokens: List<Token>,
createTransaction: Function,
enqueueSnackbar: Function,
closeSnackbar: Function,
enqueueSnackbar: Function,
onClose: () => void,
onPrev: () => void,
tx: Object,
}
const ReviewTx = ({
classes,
closeSnackbar,
createTransaction,
enqueueSnackbar,
ethBalance,
onClose,
safeAddress,
safeName,
setActiveScreen,
tokens,
tx,
}: Props) => {
const useStyles = makeStyles(styles)
const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }: Props) => {
const classes = useStyles()
const dispatch = useDispatch()
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
const tokens = useSelector(extendedSafeTokensSelector)
const [gasCosts, setGasCosts] = useState<string>('< 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
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const { fromWei, toBN } = getWeb3().utils
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, 0).encodeABI()
txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI()
}
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, txRecipient, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
setData(txData)
}
}
@ -95,30 +93,22 @@ const ReviewTx = ({
const submitTx = async () => {
const web3 = getWeb3()
let txData = EMPTY_DATA
let txAmount = web3.utils.toWei(tx.amount, 'ether')
if (!isSendingETH) {
const HumanFriendlyToken = await getHumanFriendlyToken()
const tokenInstance = await HumanFriendlyToken.at(txToken.address)
const decimals = await tokenInstance.decimals()
txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString()
txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI()
// 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
txAmount = 0
}
const txAmount = isSendingETH ? web3.utils.toWei(tx.amount, 'ether') : '0'
dispatch(
createTransaction({
safeAddress,
to: txRecipient,
valueInWei: txAmount,
txData,
txData: data,
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
enqueueSnackbar,
closeSnackbar,
})
}),
)
onClose()
}
@ -182,13 +172,14 @@ const ReviewTx = ({
</Block>
<Hairline style={{ position: 'absolute', bottom: 85 }} />
<Row align="center" className={classes.buttonRow}>
<Button minWidth={140} onClick={() => setActiveScreen('sendFunds')}>
<Button minWidth={140} onClick={onPrev}>
Back
</Button>
<Button
className={classes.submitButton}
color="primary"
data-testid="submit-tx-btn"
disabled={!data}
minWidth={140}
onClick={submitTx}
type="submit"
@ -201,4 +192,4 @@ const ReviewTx = ({
)
}
export default withStyles(styles)(withSnackbar(ReviewTx))
export default withSnackbar(ReviewTx)

View File

@ -1,9 +1,10 @@
// @flow
import IconButton from '@material-ui/core/IconButton'
import InputAdornment from '@material-ui/core/InputAdornment'
import { withStyles } from '@material-ui/core/styles'
import { makeStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import ArrowDown from '../assets/arrow-down.svg'
@ -29,29 +30,21 @@ import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
import { safeSelector } from '~/routes/safe/store/selectors'
import { sm } from '~/theme/variables'
type Props = {
onClose: () => void,
classes: Object,
recipientAddress: string,
safeAddress: string,
safeName: string,
ethBalance: string,
onSubmit: Function,
initialValues: Object,
onClose: () => void,
onNext: any => void,
recipientAddress: string,
}
const SendCustomTx = ({
classes,
ethBalance,
initialValues,
onClose,
onSubmit,
recipientAddress,
safeAddress,
safeName,
}: Props) => {
const useStyles = makeStyles(styles)
const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Props) => {
const classes = useStyles()
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
address: recipientAddress || initialValues.recipientAddress,
@ -68,7 +61,7 @@ const SendCustomTx = ({
const handleSubmit = (values: Object) => {
if (values.data || values.value) {
onSubmit(values)
onNext(values)
}
}
@ -268,4 +261,4 @@ const SendCustomTx = ({
)
}
export default withStyles(styles)(SendCustomTx)
export default SendCustomTx

View File

@ -1,11 +1,11 @@
// @flow
import IconButton from '@material-ui/core/IconButton'
import InputAdornment from '@material-ui/core/InputAdornment'
import { withStyles } from '@material-ui/core/styles'
import { makeStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import { List } from 'immutable'
import React, { useState } from 'react'
import { OnChange } from 'react-final-form-listeners'
import { useSelector } from 'react-redux'
import ArrowDown from '../assets/arrow-down.svg'
@ -32,19 +32,16 @@ import { type Token } from '~/logic/tokens/store/model/token'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField'
import { extendedSafeTokensSelector } from '~/routes/safe/container/selector'
import { safeSelector } from '~/routes/safe/store/selectors'
import { sm } from '~/theme/variables'
type Props = {
onClose: () => void,
classes: Object,
safeAddress: string,
safeName: string,
ethBalance: string,
selectedToken: string,
tokens: List<Token>,
onSubmit: Function,
initialValues: Object,
onClose: () => void,
onNext: any => void,
recipientAddress?: string,
selectedToken: string,
}
const formMutators = {
@ -59,21 +56,15 @@ const formMutators = {
},
}
const SendFunds = ({
classes,
ethBalance,
initialValues,
onClose,
onSubmit,
recipientAddress,
safeAddress,
safeName,
selectedToken,
tokens,
}: Props) => {
const useStyles = makeStyles(styles)
const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedToken = '' }: Props) => {
const classes = useStyles()
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
const tokens: Token = useSelector(extendedSafeTokensSelector)
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
address: recipientAddress,
address: recipientAddress || initialValues.recipientAddress,
name: '',
})
const [pristine, setPristine] = useState<boolean>(true)
@ -91,7 +82,7 @@ const SendFunds = ({
if (!values.recipientAddress) {
submitValues.recipientAddress = selectedEntry.address
}
onSubmit(submitValues)
onNext(submitValues)
}
const openQrModal = () => {
@ -294,4 +285,4 @@ const SendFunds = ({
)
}
export default withStyles(styles)(SendFunds)
export default SendFunds

View File

@ -51,10 +51,10 @@ type Props = {
activeTokens: List<Token>,
blacklistedTokens: List<Token>,
classes: Object,
createTransaction: Function,
createTransaction?: Function,
currencySelected: string,
currencyValues: BalanceCurrencyType[],
ethBalance: string,
ethBalance?: string,
featuresEnabled: string[],
fetchCurrencyValues: Function,
fetchTokens: Function,
@ -156,10 +156,8 @@ class Balances extends React.Component<Props, State> {
activeTokens,
blacklistedTokens,
classes,
createTransaction,
currencySelected,
currencyValues,
ethBalance,
granted,
safeAddress,
safeName,
@ -304,14 +302,9 @@ class Balances extends React.Component<Props, State> {
{erc721Enabled && showCollectibles && <Collectibles />}
<SendModal
activeScreenType="sendFunds"
createTransaction={createTransaction}
ethBalance={ethBalance}
isOpen={sendFunds.isOpen}
onClose={this.hideSendFunds}
safeAddress={safeAddress}
safeName={safeName}
selectedToken={sendFunds.selectedToken}
tokens={activeTokens}
/>
<Modal
description="Receive Tokens Form"

View File

@ -316,10 +316,8 @@ const Layout = (props: Props) => {
activateTokensByBalance={activateTokensByBalance}
activeTokens={activeTokens}
blacklistedTokens={blacklistedTokens}
createTransaction={createTransaction}
currencySelected={currencySelected}
currencyValues={currencyValues}
ethBalance={ethBalance}
featuresEnabled={featuresEnabled}
fetchCurrencyValues={fetchCurrencyValues}
fetchTokens={fetchTokens}
@ -378,14 +376,9 @@ const Layout = (props: Props) => {
</Switch>
<SendModal
activeScreenType="chooseTxType"
createTransaction={createTransaction}
ethBalance={ethBalance}
isOpen={sendFunds.isOpen}
onClose={hideSendFunds}
safeAddress={address}
safeName={name}
selectedToken={sendFunds.selectedToken}
tokens={activeTokens}
/>
<Modal
description="Receive Tokens Form"