(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:
parent
c4cc79c682
commit
a0ce9683dd
|
@ -42,8 +42,7 @@ import RenameOwnerIcon from '~/routes/safe/components/Settings/ManageOwners/asse
|
||||||
import type { OwnerRow } from '~/routes/safe/components/Settings/ManageOwners/dataFetcher'
|
import type { OwnerRow } from '~/routes/safe/components/Settings/ManageOwners/dataFetcher'
|
||||||
import RemoveOwnerIcon from '~/routes/safe/components/Settings/assets/icons/bin.svg'
|
import RemoveOwnerIcon from '~/routes/safe/components/Settings/assets/icons/bin.svg'
|
||||||
import RemoveOwnerIconDisabled from '~/routes/safe/components/Settings/assets/icons/disabled-bin.svg'
|
import RemoveOwnerIconDisabled from '~/routes/safe/components/Settings/assets/icons/disabled-bin.svg'
|
||||||
import { extendedSafeTokensSelector } from '~/routes/safe/container/selector'
|
import { addressBookQueryParamsSelector, safesListSelector } from '~/routes/safe/store/selectors'
|
||||||
import { addressBookQueryParamsSelector, safeSelector, safesListSelector } from '~/routes/safe/store/selectors'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
@ -86,10 +85,7 @@ const AddressBookTable = ({ classes }: Props) => {
|
||||||
}
|
}
|
||||||
}, [addressBook])
|
}, [addressBook])
|
||||||
|
|
||||||
const safe = useSelector(safeSelector)
|
|
||||||
const safesList = useSelector(safesListSelector)
|
const safesList = useSelector(safesListSelector)
|
||||||
const activeTokens = useSelector(extendedSafeTokensSelector)
|
|
||||||
const { address, ethBalance, name } = safe
|
|
||||||
|
|
||||||
const newEntryModalHandler = (entry: AddressBookEntry) => {
|
const newEntryModalHandler = (entry: AddressBookEntry) => {
|
||||||
setEditCreateEntryModalOpen(false)
|
setEditCreateEntryModalOpen(false)
|
||||||
|
@ -223,13 +219,9 @@ const AddressBookTable = ({ classes }: Props) => {
|
||||||
/>
|
/>
|
||||||
<SendModal
|
<SendModal
|
||||||
activeScreenType="chooseTxType"
|
activeScreenType="chooseTxType"
|
||||||
ethBalance={ethBalance}
|
|
||||||
isOpen={sendFundsModalOpen}
|
isOpen={sendFundsModalOpen}
|
||||||
onClose={() => setSendFundsModalOpen(false)}
|
onClose={() => setSendFundsModalOpen(false)}
|
||||||
recipientAddress={selectedEntry && selectedEntry.entry ? selectedEntry.entry.address : undefined}
|
recipientAddress={selectedEntry && selectedEntry.entry ? selectedEntry.entry.address : undefined}
|
||||||
safeAddress={address}
|
|
||||||
safeName={name}
|
|
||||||
tokens={activeTokens}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,7 +10,6 @@ import Paragraph from '~/components/layout/Paragraph'
|
||||||
import type { NFTAssetsState, NFTTokensState } from '~/logic/collectibles/store/reducer/collectibles'
|
import type { NFTAssetsState, NFTTokensState } from '~/logic/collectibles/store/reducer/collectibles'
|
||||||
import { nftAssetsSelector, nftTokensSelector } from '~/logic/collectibles/store/selectors'
|
import { nftAssetsSelector, nftTokensSelector } from '~/logic/collectibles/store/selectors'
|
||||||
import SendModal from '~/routes/safe/components/Balances/SendModal'
|
import SendModal from '~/routes/safe/components/Balances/SendModal'
|
||||||
import { safeSelector } from '~/routes/safe/store/selectors'
|
|
||||||
import { fontColor, lg, screenSm, screenXs } from '~/theme/variables'
|
import { fontColor, lg, screenSm, screenXs } from '~/theme/variables'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
|
@ -80,7 +79,6 @@ const Collectibles = () => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [selectedToken, setSelectedToken] = React.useState({})
|
const [selectedToken, setSelectedToken] = React.useState({})
|
||||||
const [sendNFTsModalOpen, setSendNFTsModalOpen] = React.useState(false)
|
const [sendNFTsModalOpen, setSendNFTsModalOpen] = React.useState(false)
|
||||||
const { address, ethBalance, name } = useSelector(safeSelector)
|
|
||||||
const nftAssets: NFTAssetsState = useSelector(nftAssetsSelector)
|
const nftAssets: NFTAssetsState = useSelector(nftAssetsSelector)
|
||||||
const nftTokens: NFTTokensState = useSelector(nftTokensSelector)
|
const nftTokens: NFTTokensState = useSelector(nftTokensSelector)
|
||||||
const nftAssetsKeys = Object.keys(nftAssets)
|
const nftAssetsKeys = Object.keys(nftAssets)
|
||||||
|
@ -124,11 +122,8 @@ const Collectibles = () => {
|
||||||
</div>
|
</div>
|
||||||
<SendModal
|
<SendModal
|
||||||
activeScreenType="sendCollectible"
|
activeScreenType="sendCollectible"
|
||||||
ethBalance={ethBalance}
|
|
||||||
isOpen={sendNFTsModalOpen}
|
isOpen={sendNFTsModalOpen}
|
||||||
onClose={() => setSendNFTsModalOpen(false)}
|
onClose={() => setSendNFTsModalOpen(false)}
|
||||||
safeAddress={address}
|
|
||||||
safeName={name}
|
|
||||||
selectedToken={selectedToken}
|
selectedToken={selectedToken}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { List } from 'immutable'
|
|
||||||
import React, { Suspense, useEffect, useState } from 'react'
|
import React, { Suspense, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import Modal from '~/components/Modal'
|
import Modal from '~/components/Modal'
|
||||||
|
@ -33,16 +32,11 @@ type ActiveScreen =
|
||||||
| 'reviewCollectible'
|
| 'reviewCollectible'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
|
||||||
isOpen: boolean,
|
|
||||||
safeAddress: string,
|
|
||||||
safeName: string,
|
|
||||||
ethBalance: string,
|
|
||||||
tokens: List<Token>,
|
|
||||||
selectedToken?: string | NFTToken | {},
|
|
||||||
createTransaction?: Function,
|
|
||||||
activeScreenType: ActiveScreen,
|
activeScreenType: ActiveScreen,
|
||||||
|
isOpen: boolean,
|
||||||
|
onClose: () => void,
|
||||||
recipientAddress?: string,
|
recipientAddress?: string,
|
||||||
|
selectedToken?: string | NFTToken | {},
|
||||||
}
|
}
|
||||||
|
|
||||||
type TxStateType =
|
type TxStateType =
|
||||||
|
@ -71,18 +65,7 @@ const useStyles = makeStyles({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const SendModal = ({
|
const SendModal = ({ activeScreenType, isOpen, onClose, recipientAddress, selectedToken }: Props) => {
|
||||||
activeScreenType,
|
|
||||||
createTransaction,
|
|
||||||
ethBalance,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
recipientAddress,
|
|
||||||
safeAddress,
|
|
||||||
safeName,
|
|
||||||
selectedToken,
|
|
||||||
tokens,
|
|
||||||
}: Props) => {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [activeScreen, setActiveScreen] = useState<ActiveScreen>(activeScreenType || 'chooseTxType')
|
const [activeScreen, setActiveScreen] = useState<ActiveScreen>(activeScreenType || 'chooseTxType')
|
||||||
const [tx, setTx] = useState<TxStateType>({})
|
const [tx, setTx] = useState<TxStateType>({})
|
||||||
|
@ -129,50 +112,26 @@ const SendModal = ({
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'sendFunds' && (
|
{activeScreen === 'sendFunds' && (
|
||||||
<SendFunds
|
<SendFunds
|
||||||
ethBalance={ethBalance}
|
|
||||||
initialValues={tx}
|
initialValues={tx}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSubmit={handleTxCreation}
|
onNext={handleTxCreation}
|
||||||
recipientAddress={recipientAddress}
|
recipientAddress={recipientAddress}
|
||||||
safeAddress={safeAddress}
|
|
||||||
safeName={safeName}
|
|
||||||
selectedToken={selectedToken}
|
selectedToken={selectedToken}
|
||||||
tokens={tokens}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'reviewTx' && (
|
{activeScreen === 'reviewTx' && (
|
||||||
<ReviewTx
|
<ReviewTx onClose={onClose} onPrev={() => setActiveScreen('sendFunds')} tx={tx} />
|
||||||
createTransaction={createTransaction}
|
|
||||||
ethBalance={ethBalance}
|
|
||||||
onClose={onClose}
|
|
||||||
safeAddress={safeAddress}
|
|
||||||
safeName={safeName}
|
|
||||||
setActiveScreen={setActiveScreen}
|
|
||||||
tokens={tokens}
|
|
||||||
tx={tx}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'sendCustomTx' && (
|
{activeScreen === 'sendCustomTx' && (
|
||||||
<SendCustomTx
|
<SendCustomTx
|
||||||
ethBalance={ethBalance}
|
|
||||||
initialValues={tx}
|
initialValues={tx}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSubmit={handleCustomTxCreation}
|
onNext={handleCustomTxCreation}
|
||||||
recipientAddress={recipientAddress}
|
recipientAddress={recipientAddress}
|
||||||
safeAddress={safeAddress}
|
|
||||||
safeName={safeName}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'reviewCustomTx' && (
|
{activeScreen === 'reviewCustomTx' && (
|
||||||
<ReviewCustomTx
|
<ReviewCustomTx onClose={onClose} onPrev={() => setActiveScreen('sendCustomTx')} tx={tx} />
|
||||||
createTransaction={createTransaction}
|
|
||||||
ethBalance={ethBalance}
|
|
||||||
onClose={onClose}
|
|
||||||
safeAddress={safeAddress}
|
|
||||||
safeName={safeName}
|
|
||||||
setActiveScreen={setActiveScreen}
|
|
||||||
tx={tx}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'sendCollectible' && (
|
{activeScreen === 'sendCollectible' && (
|
||||||
<SendCollectible
|
<SendCollectible
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import { List } from 'immutable'
|
|
||||||
import { withSnackbar } from 'notistack'
|
import { withSnackbar } from 'notistack'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
@ -30,7 +29,6 @@ import {
|
||||||
getERC721TokenContract,
|
getERC721TokenContract,
|
||||||
getHumanFriendlyToken,
|
getHumanFriendlyToken,
|
||||||
} from '~/logic/tokens/store/actions/fetchTokens'
|
} from '~/logic/tokens/store/actions/fetchTokens'
|
||||||
import { type Token } from '~/logic/tokens/store/model/token'
|
|
||||||
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
||||||
import { SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH } from '~/logic/tokens/utils/tokenHelpers'
|
import { SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
@ -44,14 +42,11 @@ import { textShortener } from '~/utils/strings'
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
closeSnackbar: Function,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
onPrev: () => void,
|
onPrev: () => void,
|
||||||
classes: Object,
|
|
||||||
tx: Object,
|
tx: Object,
|
||||||
tokens: List<Token>,
|
|
||||||
createTransaction: Function,
|
|
||||||
enqueueSnackbar: Function,
|
|
||||||
closeSnackbar: Function,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }: Props) => {
|
const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }: Props) => {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
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 Close from '@material-ui/icons/Close'
|
||||||
import { withSnackbar } from 'notistack'
|
import { withSnackbar } from 'notistack'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
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 { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
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'
|
import { sm } from '~/theme/variables'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
closeSnackbar: () => void,
|
||||||
|
enqueueSnackbar: () => void,
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
setActiveScreen: Function,
|
onPrev: () => void,
|
||||||
classes: Object,
|
|
||||||
safeAddress: string,
|
|
||||||
safeName: string,
|
|
||||||
ethBalance: string,
|
|
||||||
tx: Object,
|
tx: Object,
|
||||||
createTransaction: Function,
|
|
||||||
enqueueSnackbar: Function,
|
|
||||||
closeSnackbar: Function,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReviewCustomTx = ({
|
const useStyles = makeStyles(styles)
|
||||||
classes,
|
|
||||||
closeSnackbar,
|
const ReviewCustomTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }: Props) => {
|
||||||
createTransaction,
|
const classes = useStyles()
|
||||||
enqueueSnackbar,
|
const dispatch = useDispatch()
|
||||||
ethBalance,
|
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
|
||||||
onClose,
|
|
||||||
safeAddress,
|
|
||||||
safeName,
|
|
||||||
setActiveScreen,
|
|
||||||
tx,
|
|
||||||
}: Props) => {
|
|
||||||
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
|
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isCurrent = true
|
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 gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
||||||
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
||||||
|
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
setGasCosts(formattedGasCosts)
|
setGasCosts(formattedGasCosts)
|
||||||
}
|
}
|
||||||
|
@ -79,18 +73,21 @@ const ReviewCustomTx = ({
|
||||||
const submitTx = async () => {
|
const submitTx = async () => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const txRecipient = tx.recipientAddress
|
const txRecipient = tx.recipientAddress
|
||||||
const txData = tx.data.trim()
|
const txData = tx.data ? tx.data.trim() : ''
|
||||||
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0
|
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : '0'
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
createTransaction({
|
||||||
|
safeAddress,
|
||||||
|
to: txRecipient,
|
||||||
|
valueInWei: txValue,
|
||||||
|
txData,
|
||||||
|
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
createTransaction({
|
|
||||||
safeAddress,
|
|
||||||
to: txRecipient,
|
|
||||||
valueInWei: txValue,
|
|
||||||
txData,
|
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
|
||||||
enqueueSnackbar,
|
|
||||||
closeSnackbar,
|
|
||||||
})
|
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +164,7 @@ const ReviewCustomTx = ({
|
||||||
</Block>
|
</Block>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
<Row align="center" className={classes.buttonRow}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Button minWidth={140} onClick={() => setActiveScreen('sendCustomTx')}>
|
<Button minWidth={140} onClick={onPrev}>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
@ -186,4 +183,4 @@ const ReviewCustomTx = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(ReviewCustomTx))
|
export default withSnackbar(ReviewCustomTx)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
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 Close from '@material-ui/icons/Close'
|
||||||
import { BigNumber } from 'bignumber.js'
|
import { BigNumber } from 'bignumber.js'
|
||||||
import { List } from 'immutable'
|
|
||||||
import { withSnackbar } from 'notistack'
|
import { withSnackbar } from 'notistack'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
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 { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
|
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
|
||||||
import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
|
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 { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
||||||
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
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'
|
import { sm } from '~/theme/variables'
|
||||||
|
|
||||||
type Props = {
|
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,
|
closeSnackbar: Function,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
onClose: () => void,
|
||||||
|
onPrev: () => void,
|
||||||
|
tx: Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReviewTx = ({
|
const useStyles = makeStyles(styles)
|
||||||
classes,
|
|
||||||
closeSnackbar,
|
const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }: Props) => {
|
||||||
createTransaction,
|
const classes = useStyles()
|
||||||
enqueueSnackbar,
|
const dispatch = useDispatch()
|
||||||
ethBalance,
|
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
|
||||||
onClose,
|
const tokens = useSelector(extendedSafeTokensSelector)
|
||||||
safeAddress,
|
|
||||||
safeName,
|
|
||||||
setActiveScreen,
|
|
||||||
tokens,
|
|
||||||
tx,
|
|
||||||
}: Props) => {
|
|
||||||
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
|
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
|
||||||
|
const [data, setData] = useState('')
|
||||||
|
|
||||||
const txToken = tokens.find(token => token.address === tx.token)
|
const txToken = tokens.find(token => token.address === tx.token)
|
||||||
const isSendingETH = txToken.address === ETH_ADDRESS
|
const isSendingETH = txToken.address === ETH_ADDRESS
|
||||||
const txRecipient = isSendingETH ? tx.recipientAddress : txToken.address
|
const txRecipient = isSendingETH ? tx.recipientAddress : txToken.address
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isCurrent = true
|
let isCurrent = true
|
||||||
|
|
||||||
const estimateGas = async () => {
|
const estimateGas = async () => {
|
||||||
const web3 = getWeb3()
|
const { fromWei, toBN } = getWeb3().utils
|
||||||
const { fromWei, toBN } = web3.utils
|
|
||||||
let txData = EMPTY_DATA
|
let txData = EMPTY_DATA
|
||||||
|
|
||||||
if (!isSendingETH) {
|
if (!isSendingETH) {
|
||||||
const StandardToken = await getHumanFriendlyToken()
|
const StandardToken = await getHumanFriendlyToken()
|
||||||
const tokenInstance = await StandardToken.at(txToken.address)
|
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 estimatedGasCosts = await estimateTxGasCosts(safeAddress, txRecipient, txData)
|
||||||
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
||||||
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
||||||
|
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
setGasCosts(formattedGasCosts)
|
setGasCosts(formattedGasCosts)
|
||||||
|
setData(txData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,30 +93,22 @@ const ReviewTx = ({
|
||||||
|
|
||||||
const submitTx = async () => {
|
const submitTx = async () => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
let txData = EMPTY_DATA
|
// txAmount should be 0 if we send tokens
|
||||||
let txAmount = web3.utils.toWei(tx.amount, 'ether')
|
// the real value is encoded in txData and will be used by the contract
|
||||||
if (!isSendingETH) {
|
// if txAmount > 0 it would send ETH from the Safe
|
||||||
const HumanFriendlyToken = await getHumanFriendlyToken()
|
const txAmount = isSendingETH ? web3.utils.toWei(tx.amount, 'ether') : '0'
|
||||||
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()
|
dispatch(
|
||||||
// txAmount should be 0 if we send tokens
|
createTransaction({
|
||||||
// the real value is encoded in txData and will be used by the contract
|
safeAddress,
|
||||||
// if txAmount > 0 it would send ETH from the Safe
|
to: txRecipient,
|
||||||
txAmount = 0
|
valueInWei: txAmount,
|
||||||
}
|
txData: data,
|
||||||
|
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||||
createTransaction({
|
enqueueSnackbar,
|
||||||
safeAddress,
|
closeSnackbar,
|
||||||
to: txRecipient,
|
}),
|
||||||
valueInWei: txAmount,
|
)
|
||||||
txData,
|
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
|
||||||
enqueueSnackbar,
|
|
||||||
closeSnackbar,
|
|
||||||
})
|
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,13 +172,14 @@ const ReviewTx = ({
|
||||||
</Block>
|
</Block>
|
||||||
<Hairline style={{ position: 'absolute', bottom: 85 }} />
|
<Hairline style={{ position: 'absolute', bottom: 85 }} />
|
||||||
<Row align="center" className={classes.buttonRow}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Button minWidth={140} onClick={() => setActiveScreen('sendFunds')}>
|
<Button minWidth={140} onClick={onPrev}>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className={classes.submitButton}
|
className={classes.submitButton}
|
||||||
color="primary"
|
color="primary"
|
||||||
data-testid="submit-tx-btn"
|
data-testid="submit-tx-btn"
|
||||||
|
disabled={!data}
|
||||||
minWidth={140}
|
minWidth={140}
|
||||||
onClick={submitTx}
|
onClick={submitTx}
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -201,4 +192,4 @@ const ReviewTx = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(ReviewTx))
|
export default withSnackbar(ReviewTx)
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import InputAdornment from '@material-ui/core/InputAdornment'
|
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 Close from '@material-ui/icons/Close'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
import ArrowDown from '../assets/arrow-down.svg'
|
||||||
|
|
||||||
|
@ -29,29 +30,21 @@ import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
|
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
|
||||||
|
import { safeSelector } from '~/routes/safe/store/selectors'
|
||||||
import { sm } from '~/theme/variables'
|
import { sm } from '~/theme/variables'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
|
||||||
classes: Object,
|
|
||||||
recipientAddress: string,
|
|
||||||
safeAddress: string,
|
|
||||||
safeName: string,
|
|
||||||
ethBalance: string,
|
|
||||||
onSubmit: Function,
|
|
||||||
initialValues: Object,
|
initialValues: Object,
|
||||||
|
onClose: () => void,
|
||||||
|
onNext: any => void,
|
||||||
|
recipientAddress: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SendCustomTx = ({
|
const useStyles = makeStyles(styles)
|
||||||
classes,
|
|
||||||
ethBalance,
|
const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Props) => {
|
||||||
initialValues,
|
const classes = useStyles()
|
||||||
onClose,
|
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
|
||||||
onSubmit,
|
|
||||||
recipientAddress,
|
|
||||||
safeAddress,
|
|
||||||
safeName,
|
|
||||||
}: Props) => {
|
|
||||||
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
||||||
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
|
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
|
||||||
address: recipientAddress || initialValues.recipientAddress,
|
address: recipientAddress || initialValues.recipientAddress,
|
||||||
|
@ -68,7 +61,7 @@ const SendCustomTx = ({
|
||||||
|
|
||||||
const handleSubmit = (values: Object) => {
|
const handleSubmit = (values: Object) => {
|
||||||
if (values.data || values.value) {
|
if (values.data || values.value) {
|
||||||
onSubmit(values)
|
onNext(values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,4 +261,4 @@ const SendCustomTx = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(SendCustomTx)
|
export default SendCustomTx
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import InputAdornment from '@material-ui/core/InputAdornment'
|
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 Close from '@material-ui/icons/Close'
|
||||||
import { List } from 'immutable'
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { OnChange } from 'react-final-form-listeners'
|
import { OnChange } from 'react-final-form-listeners'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
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 SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
|
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
|
||||||
import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField'
|
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'
|
import { sm } from '~/theme/variables'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
|
||||||
classes: Object,
|
|
||||||
safeAddress: string,
|
|
||||||
safeName: string,
|
|
||||||
ethBalance: string,
|
|
||||||
selectedToken: string,
|
|
||||||
tokens: List<Token>,
|
|
||||||
onSubmit: Function,
|
|
||||||
initialValues: Object,
|
initialValues: Object,
|
||||||
|
onClose: () => void,
|
||||||
|
onNext: any => void,
|
||||||
recipientAddress?: string,
|
recipientAddress?: string,
|
||||||
|
selectedToken: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const formMutators = {
|
const formMutators = {
|
||||||
|
@ -59,21 +56,15 @@ const formMutators = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const SendFunds = ({
|
const useStyles = makeStyles(styles)
|
||||||
classes,
|
|
||||||
ethBalance,
|
const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedToken = '' }: Props) => {
|
||||||
initialValues,
|
const classes = useStyles()
|
||||||
onClose,
|
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
|
||||||
onSubmit,
|
const tokens: Token = useSelector(extendedSafeTokensSelector)
|
||||||
recipientAddress,
|
|
||||||
safeAddress,
|
|
||||||
safeName,
|
|
||||||
selectedToken,
|
|
||||||
tokens,
|
|
||||||
}: Props) => {
|
|
||||||
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
||||||
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
|
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
|
||||||
address: recipientAddress,
|
address: recipientAddress || initialValues.recipientAddress,
|
||||||
name: '',
|
name: '',
|
||||||
})
|
})
|
||||||
const [pristine, setPristine] = useState<boolean>(true)
|
const [pristine, setPristine] = useState<boolean>(true)
|
||||||
|
@ -91,7 +82,7 @@ const SendFunds = ({
|
||||||
if (!values.recipientAddress) {
|
if (!values.recipientAddress) {
|
||||||
submitValues.recipientAddress = selectedEntry.address
|
submitValues.recipientAddress = selectedEntry.address
|
||||||
}
|
}
|
||||||
onSubmit(submitValues)
|
onNext(submitValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
const openQrModal = () => {
|
const openQrModal = () => {
|
||||||
|
@ -294,4 +285,4 @@ const SendFunds = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(SendFunds)
|
export default SendFunds
|
||||||
|
|
|
@ -51,10 +51,10 @@ type Props = {
|
||||||
activeTokens: List<Token>,
|
activeTokens: List<Token>,
|
||||||
blacklistedTokens: List<Token>,
|
blacklistedTokens: List<Token>,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
createTransaction: Function,
|
createTransaction?: Function,
|
||||||
currencySelected: string,
|
currencySelected: string,
|
||||||
currencyValues: BalanceCurrencyType[],
|
currencyValues: BalanceCurrencyType[],
|
||||||
ethBalance: string,
|
ethBalance?: string,
|
||||||
featuresEnabled: string[],
|
featuresEnabled: string[],
|
||||||
fetchCurrencyValues: Function,
|
fetchCurrencyValues: Function,
|
||||||
fetchTokens: Function,
|
fetchTokens: Function,
|
||||||
|
@ -156,10 +156,8 @@ class Balances extends React.Component<Props, State> {
|
||||||
activeTokens,
|
activeTokens,
|
||||||
blacklistedTokens,
|
blacklistedTokens,
|
||||||
classes,
|
classes,
|
||||||
createTransaction,
|
|
||||||
currencySelected,
|
currencySelected,
|
||||||
currencyValues,
|
currencyValues,
|
||||||
ethBalance,
|
|
||||||
granted,
|
granted,
|
||||||
safeAddress,
|
safeAddress,
|
||||||
safeName,
|
safeName,
|
||||||
|
@ -304,14 +302,9 @@ class Balances extends React.Component<Props, State> {
|
||||||
{erc721Enabled && showCollectibles && <Collectibles />}
|
{erc721Enabled && showCollectibles && <Collectibles />}
|
||||||
<SendModal
|
<SendModal
|
||||||
activeScreenType="sendFunds"
|
activeScreenType="sendFunds"
|
||||||
createTransaction={createTransaction}
|
|
||||||
ethBalance={ethBalance}
|
|
||||||
isOpen={sendFunds.isOpen}
|
isOpen={sendFunds.isOpen}
|
||||||
onClose={this.hideSendFunds}
|
onClose={this.hideSendFunds}
|
||||||
safeAddress={safeAddress}
|
|
||||||
safeName={safeName}
|
|
||||||
selectedToken={sendFunds.selectedToken}
|
selectedToken={sendFunds.selectedToken}
|
||||||
tokens={activeTokens}
|
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
description="Receive Tokens Form"
|
description="Receive Tokens Form"
|
||||||
|
|
|
@ -316,10 +316,8 @@ const Layout = (props: Props) => {
|
||||||
activateTokensByBalance={activateTokensByBalance}
|
activateTokensByBalance={activateTokensByBalance}
|
||||||
activeTokens={activeTokens}
|
activeTokens={activeTokens}
|
||||||
blacklistedTokens={blacklistedTokens}
|
blacklistedTokens={blacklistedTokens}
|
||||||
createTransaction={createTransaction}
|
|
||||||
currencySelected={currencySelected}
|
currencySelected={currencySelected}
|
||||||
currencyValues={currencyValues}
|
currencyValues={currencyValues}
|
||||||
ethBalance={ethBalance}
|
|
||||||
featuresEnabled={featuresEnabled}
|
featuresEnabled={featuresEnabled}
|
||||||
fetchCurrencyValues={fetchCurrencyValues}
|
fetchCurrencyValues={fetchCurrencyValues}
|
||||||
fetchTokens={fetchTokens}
|
fetchTokens={fetchTokens}
|
||||||
|
@ -378,14 +376,9 @@ const Layout = (props: Props) => {
|
||||||
</Switch>
|
</Switch>
|
||||||
<SendModal
|
<SendModal
|
||||||
activeScreenType="chooseTxType"
|
activeScreenType="chooseTxType"
|
||||||
createTransaction={createTransaction}
|
|
||||||
ethBalance={ethBalance}
|
|
||||||
isOpen={sendFunds.isOpen}
|
isOpen={sendFunds.isOpen}
|
||||||
onClose={hideSendFunds}
|
onClose={hideSendFunds}
|
||||||
safeAddress={address}
|
|
||||||
safeName={name}
|
|
||||||
selectedToken={sendFunds.selectedToken}
|
selectedToken={sendFunds.selectedToken}
|
||||||
tokens={activeTokens}
|
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
description="Receive Tokens Form"
|
description="Receive Tokens Form"
|
||||||
|
|
Loading…
Reference in New Issue