(Feature) - Send erc721 collectible with proxy contact (#1508)
* Removed containsMethodByHash condition check, now we always expect that safeTransferFrom is defined on the erc721 contract * Types * More types * Add try catch on estimateGas * Add try catch on submit transaction * More types * More types * More types * ReviewTx modal props * Fix SendCollectible modal types * Add guard for safeAddress * Move some imports * Fix DispatchReturn types * Fix import of Dispatch * Remove console log * Adds logs * Fix import Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>
This commit is contained in:
parent
95d102d337
commit
058fec3dbc
|
@ -42,7 +42,7 @@ import { Transaction, TransactionStatus, TxArgs } from 'src/logic/safe/store/mod
|
||||||
import { AnyAction } from 'redux'
|
import { AnyAction } from 'redux'
|
||||||
import { PayableTx } from 'src/types/contracts/types.d'
|
import { PayableTx } from 'src/types/contracts/types.d'
|
||||||
import { AppReduxState } from 'src/store'
|
import { AppReduxState } from 'src/store'
|
||||||
import { Dispatch } from './types'
|
import { Dispatch, DispatchReturn } from './types'
|
||||||
|
|
||||||
export const removeTxFromStore = (
|
export const removeTxFromStore = (
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
|
@ -110,7 +110,7 @@ interface CreateTransactionArgs {
|
||||||
safeTxGas?: number
|
safeTxGas?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateTransactionAction = ThunkAction<Promise<void>, AppReduxState, undefined, AnyAction>
|
type CreateTransactionAction = ThunkAction<Promise<void | string>, AppReduxState, DispatchReturn, AnyAction>
|
||||||
type ConfirmEventHandler = (safeTxHash: string) => void
|
type ConfirmEventHandler = (safeTxHash: string) => void
|
||||||
type ErrorEventHandler = () => void
|
type ErrorEventHandler = () => void
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ const createTransaction = (
|
||||||
}: CreateTransactionArgs,
|
}: CreateTransactionArgs,
|
||||||
onUserConfirm?: ConfirmEventHandler,
|
onUserConfirm?: ConfirmEventHandler,
|
||||||
onError?: ErrorEventHandler,
|
onError?: ErrorEventHandler,
|
||||||
): CreateTransactionAction => async (dispatch: Dispatch, getState: () => AppReduxState): Promise<void> => {
|
): CreateTransactionAction => async (dispatch: Dispatch, getState: () => AppReduxState): Promise<DispatchReturn> => {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
|
|
||||||
if (navigateToTransactionsTab) {
|
if (navigateToTransactionsTab) {
|
||||||
|
|
|
@ -3,4 +3,6 @@ import { AnyAction } from 'redux'
|
||||||
|
|
||||||
import { AppReduxState } from 'src/store'
|
import { AppReduxState } from 'src/store'
|
||||||
|
|
||||||
export type Dispatch = ThunkDispatch<AppReduxState, undefined, AnyAction>
|
export type DispatchReturn = string | undefined
|
||||||
|
|
||||||
|
export type Dispatch = ThunkDispatch<AppReduxState, DispatchReturn, AnyAction>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import Card from '@material-ui/core/Card'
|
import Card from '@material-ui/core/Card'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
import Item from './components/Item'
|
import Item from './components/Item'
|
||||||
|
@ -8,11 +8,12 @@ import Item from './components/Item'
|
||||||
import Paragraph from 'src/components/layout/Paragraph'
|
import Paragraph from 'src/components/layout/Paragraph'
|
||||||
import { activeNftAssetsListSelector, nftTokensSelector } from 'src/logic/collectibles/store/selectors'
|
import { activeNftAssetsListSelector, nftTokensSelector } from 'src/logic/collectibles/store/selectors'
|
||||||
import SendModal from 'src/routes/safe/components/Balances/SendModal'
|
import SendModal from 'src/routes/safe/components/Balances/SendModal'
|
||||||
import { safeSelector } from 'src/logic/safe/store/selectors'
|
|
||||||
import { fontColor, lg, screenSm, screenXs } from 'src/theme/variables'
|
import { fontColor, lg, screenSm, screenXs } from 'src/theme/variables'
|
||||||
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
|
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
|
||||||
|
import { NFTToken } from 'src/logic/collectibles/sources/collectibles.d'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles(
|
||||||
|
createStyles({
|
||||||
cardInner: {
|
cardInner: {
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
|
@ -65,7 +66,7 @@ const useStyles = makeStyles({
|
||||||
},
|
},
|
||||||
titleFiller: {
|
titleFiller: {
|
||||||
backgroundColor: '#e8e7e6',
|
backgroundColor: '#e8e7e6',
|
||||||
flexGrow: '1',
|
flexGrow: 1,
|
||||||
height: '2px',
|
height: '2px',
|
||||||
marginLeft: '40px',
|
marginLeft: '40px',
|
||||||
},
|
},
|
||||||
|
@ -73,13 +74,13 @@ const useStyles = makeStyles({
|
||||||
fontSize: lg,
|
fontSize: lg,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
} as any)
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
const Collectibles = (): React.ReactElement => {
|
const Collectibles = (): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [selectedToken, setSelectedToken] = React.useState({})
|
const [selectedToken, setSelectedToken] = React.useState<NFTToken | undefined>()
|
||||||
const [sendNFTsModalOpen, setSendNFTsModalOpen] = React.useState(false)
|
const [sendNFTsModalOpen, setSendNFTsModalOpen] = React.useState(false)
|
||||||
const { address, ethBalance, name } = useSelector(safeSelector) || {}
|
|
||||||
const nftTokens = useSelector(nftTokensSelector)
|
const nftTokens = useSelector(nftTokensSelector)
|
||||||
const activeAssetsList = useSelector(activeNftAssetsListSelector)
|
const activeAssetsList = useSelector(activeNftAssetsListSelector)
|
||||||
const { trackEvent } = useAnalytics()
|
const { trackEvent } = useAnalytics()
|
||||||
|
@ -88,7 +89,7 @@ const Collectibles = (): React.ReactElement => {
|
||||||
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Collectibles' })
|
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Collectibles' })
|
||||||
}, [trackEvent])
|
}, [trackEvent])
|
||||||
|
|
||||||
const handleItemSend = (nftToken) => {
|
const handleItemSend = (nftToken: NFTToken) => {
|
||||||
setSelectedToken(nftToken)
|
setSelectedToken(nftToken)
|
||||||
setSendNFTsModalOpen(true)
|
setSendNFTsModalOpen(true)
|
||||||
}
|
}
|
||||||
|
@ -125,11 +126,8 @@ const Collectibles = (): React.ReactElement => {
|
||||||
</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>
|
||||||
|
|
|
@ -4,6 +4,14 @@ import cn from 'classnames'
|
||||||
import React, { Suspense, useEffect, useState } from 'react'
|
import React, { Suspense, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import Modal from 'src/components/Modal'
|
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'
|
||||||
|
import { NFTToken } from 'src/logic/collectibles/sources/collectibles'
|
||||||
|
import { SendCollectibleTxInfo } from './screens/SendCollectible'
|
||||||
|
|
||||||
const ChooseTxType = React.lazy(() => import('./screens/ChooseTxType'))
|
const ChooseTxType = React.lazy(() => import('./screens/ChooseTxType'))
|
||||||
|
|
||||||
|
@ -39,10 +47,24 @@ const useStyles = makeStyles({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const SendModal = ({ activeScreenType, isOpen, onClose, recipientAddress, selectedToken }: any) => {
|
type Props = {
|
||||||
|
activeScreenType: string
|
||||||
|
isOpen: boolean
|
||||||
|
onClose: () => void
|
||||||
|
recipientAddress?: string
|
||||||
|
selectedToken?: string | NFTToken
|
||||||
|
}
|
||||||
|
|
||||||
|
const SendModal = ({
|
||||||
|
activeScreenType,
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
recipientAddress,
|
||||||
|
selectedToken,
|
||||||
|
}: Props): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [activeScreen, setActiveScreen] = useState(activeScreenType || 'chooseTxType')
|
const [activeScreen, setActiveScreen] = useState(activeScreenType || 'chooseTxType')
|
||||||
const [tx, setTx] = useState({})
|
const [tx, setTx] = useState<unknown>({})
|
||||||
const [isABI, setIsABI] = useState(true)
|
const [isABI, setIsABI] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -53,7 +75,7 @@ const SendModal = ({ activeScreenType, isOpen, onClose, recipientAddress, select
|
||||||
|
|
||||||
const scalableModalSize = activeScreen === 'chooseTxType'
|
const scalableModalSize = activeScreen === 'chooseTxType'
|
||||||
|
|
||||||
const handleTxCreation = (txInfo) => {
|
const handleTxCreation = (txInfo: SendCollectibleTxInfo) => {
|
||||||
setActiveScreen('reviewTx')
|
setActiveScreen('reviewTx')
|
||||||
setTx(txInfo)
|
setTx(txInfo)
|
||||||
}
|
}
|
||||||
|
@ -97,22 +119,22 @@ const SendModal = ({ activeScreenType, isOpen, onClose, recipientAddress, select
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'sendFunds' && (
|
{activeScreen === 'sendFunds' && (
|
||||||
<SendFunds
|
<SendFunds
|
||||||
initialValues={tx}
|
initialValues={tx as SendFundsTx}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onNext={handleTxCreation}
|
onNext={handleTxCreation}
|
||||||
recipientAddress={recipientAddress}
|
recipientAddress={recipientAddress}
|
||||||
selectedToken={selectedToken}
|
selectedToken={selectedToken as string}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'reviewTx' && (
|
{activeScreen === 'reviewTx' && (
|
||||||
<ReviewTx onClose={onClose} onPrev={() => setActiveScreen('sendFunds')} tx={tx} />
|
<ReviewTx onClose={onClose} onPrev={() => setActiveScreen('sendFunds')} tx={tx as ReviewTxProp} />
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'contractInteraction' && isABI && (
|
{activeScreen === 'contractInteraction' && isABI && (
|
||||||
<ContractInteraction
|
<ContractInteraction
|
||||||
isABI={isABI}
|
isABI={isABI}
|
||||||
switchMethod={handleSwitchMethod}
|
switchMethod={handleSwitchMethod}
|
||||||
contractAddress={recipientAddress}
|
contractAddress={recipientAddress}
|
||||||
initialValues={tx}
|
initialValues={tx as ContractInteractionTx}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onNext={handleContractInteractionCreation}
|
onNext={handleContractInteractionCreation}
|
||||||
/>
|
/>
|
||||||
|
@ -122,7 +144,7 @@ const SendModal = ({ activeScreenType, isOpen, onClose, recipientAddress, select
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'contractInteraction' && !isABI && (
|
{activeScreen === 'contractInteraction' && !isABI && (
|
||||||
<SendCustomTx
|
<SendCustomTx
|
||||||
initialValues={tx}
|
initialValues={tx as CustomTxProps}
|
||||||
isABI={isABI}
|
isABI={isABI}
|
||||||
switchMethod={handleSwitchMethod}
|
switchMethod={handleSwitchMethod}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
@ -131,7 +153,7 @@ const SendModal = ({ activeScreenType, isOpen, onClose, recipientAddress, select
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'reviewCustomTx' && (
|
{activeScreen === 'reviewCustomTx' && (
|
||||||
<ReviewCustomTx onClose={onClose} onPrev={() => setActiveScreen('contractInteraction')} tx={tx} />
|
<ReviewCustomTx onClose={onClose} onPrev={() => setActiveScreen('contractInteraction')} tx={tx as CustomTx} />
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'sendCollectible' && (
|
{activeScreen === 'sendCollectible' && (
|
||||||
<SendCollectible
|
<SendCollectible
|
||||||
|
@ -139,11 +161,15 @@ const SendModal = ({ activeScreenType, isOpen, onClose, recipientAddress, select
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onNext={handleSendCollectible}
|
onNext={handleSendCollectible}
|
||||||
recipientAddress={recipientAddress}
|
recipientAddress={recipientAddress}
|
||||||
selectedToken={selectedToken}
|
selectedToken={selectedToken as NFTToken}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'reviewCollectible' && (
|
{activeScreen === 'reviewCollectible' && (
|
||||||
<ReviewCollectible onClose={onClose} onPrev={() => setActiveScreen('sendCollectible')} tx={tx} />
|
<ReviewCollectible
|
||||||
|
onClose={onClose}
|
||||||
|
onPrev={() => setActiveScreen('sendCollectible')}
|
||||||
|
tx={tx as CollectibleTx}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -23,7 +23,7 @@ type ActiveScreen = 'sendFunds' | 'sendCollectible' | 'contractInteraction'
|
||||||
|
|
||||||
interface ChooseTxTypeProps {
|
interface ChooseTxTypeProps {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
recipientAddress: string
|
recipientAddress?: string
|
||||||
setActiveScreen: React.Dispatch<React.SetStateAction<ActiveScreen>>
|
setActiveScreen: React.Dispatch<React.SetStateAction<ActiveScreen>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
|
||||||
import { useSnackbar } from 'notistack'
|
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { fromTokenUnit, toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
|
||||||
import { getNetworkInfo } from 'src/config'
|
import { getNetworkInfo } from 'src/config'
|
||||||
|
import { fromTokenUnit, toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||||
import AddressInfo from 'src/components/AddressInfo'
|
import AddressInfo from 'src/components/AddressInfo'
|
||||||
import Block from 'src/components/layout/Block'
|
import Block from 'src/components/layout/Block'
|
||||||
import Button from 'src/components/layout/Button'
|
import Button from 'src/components/layout/Button'
|
||||||
|
@ -43,7 +43,6 @@ type Props = {
|
||||||
const { nativeCoin } = getNetworkInfo()
|
const { nativeCoin } = getNetworkInfo()
|
||||||
|
|
||||||
const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
||||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { address: safeAddress } = useSelector(safeSelector) || {}
|
const { address: safeAddress } = useSelector(safeSelector) || {}
|
||||||
|
@ -74,18 +73,19 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactE
|
||||||
const txRecipient = tx.contractAddress
|
const txRecipient = tx.contractAddress
|
||||||
const txData = tx.data ? tx.data.trim() : ''
|
const txData = tx.data ? tx.data.trim() : ''
|
||||||
const txValue = tx.value ? toTokenUnit(tx.value, nativeCoin.decimals) : '0'
|
const txValue = tx.value ? toTokenUnit(tx.value, nativeCoin.decimals) : '0'
|
||||||
|
if (safeAddress) {
|
||||||
dispatch(
|
dispatch(
|
||||||
createTransaction({
|
createTransaction({
|
||||||
safeAddress,
|
safeAddress,
|
||||||
to: txRecipient,
|
to: txRecipient as string,
|
||||||
valueInWei: txValue,
|
valueInWei: txValue,
|
||||||
txData,
|
txData,
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||||
enqueueSnackbar,
|
}),
|
||||||
closeSnackbar,
|
|
||||||
} as any),
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
console.error('There was an error trying to submit the transaction, the safeAddress was not found')
|
||||||
|
}
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
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 React, { useEffect, useState } from 'react'
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import { fromTokenUnit, toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
|
||||||
import { getNetworkInfo } from 'src/config'
|
|
||||||
|
|
||||||
|
import { getNetworkInfo } from 'src/config'
|
||||||
|
import { fromTokenUnit, toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||||
import CopyBtn from 'src/components/CopyBtn'
|
import CopyBtn from 'src/components/CopyBtn'
|
||||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||||
import Identicon from 'src/components/Identicon'
|
import Identicon from 'src/components/Identicon'
|
||||||
|
@ -30,10 +30,16 @@ import ArrowDown from '../../assets/arrow-down.svg'
|
||||||
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
|
export type CustomTx = {
|
||||||
|
contractAddress?: string
|
||||||
|
data?: string
|
||||||
|
value?: string
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onPrev: () => void
|
onPrev: () => void
|
||||||
tx: { contractAddress?: string; data?: string; value?: string }
|
tx: CustomTx
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
@ -72,15 +78,19 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
||||||
const txData = tx.data ? tx.data.trim() : ''
|
const txData = tx.data ? tx.data.trim() : ''
|
||||||
const txValue = tx.value ? toTokenUnit(tx.value, nativeCoin.decimals) : '0'
|
const txValue = tx.value ? toTokenUnit(tx.value, nativeCoin.decimals) : '0'
|
||||||
|
|
||||||
|
if (safeAddress) {
|
||||||
dispatch(
|
dispatch(
|
||||||
createTransaction({
|
createTransaction({
|
||||||
safeAddress: safeAddress as string,
|
safeAddress: safeAddress,
|
||||||
to: txRecipient as string,
|
to: txRecipient as string,
|
||||||
valueInWei: txValue,
|
valueInWei: txValue,
|
||||||
txData,
|
txData,
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
console.error('There was an error trying to submit the transaction, the safeAddress was not found')
|
||||||
|
}
|
||||||
|
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
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 { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import Switch from '@material-ui/core/Switch'
|
import Switch from '@material-ui/core/Switch'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import React, { useState } from 'react'
|
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
import QRIcon from 'src/assets/icons/qrcode.svg'
|
import QRIcon from 'src/assets/icons/qrcode.svg'
|
||||||
import CopyBtn from 'src/components/CopyBtn'
|
import CopyBtn from 'src/components/CopyBtn'
|
||||||
|
@ -40,13 +40,17 @@ export interface CreatedTx {
|
||||||
value: string | number
|
value: string | number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CustomTxProps = {
|
||||||
|
contractAddress?: string
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialValues: { contractAddress?: string }
|
initialValues: CustomTxProps
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onNext: (tx: CreatedTx, submit: boolean) => void
|
onNext: (tx: CreatedTx, submit: boolean) => void
|
||||||
isABI: boolean
|
isABI: boolean
|
||||||
switchMethod: () => void
|
switchMethod: () => void
|
||||||
contractAddress: string
|
contractAddress?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
|
@ -31,9 +31,13 @@ export interface CreatedTx {
|
||||||
value: string | number
|
value: string | number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ContractInteractionTx = {
|
||||||
|
contractAddress?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface ContractInteractionProps {
|
export interface ContractInteractionProps {
|
||||||
contractAddress: string
|
contractAddress?: string
|
||||||
initialValues: { contractAddress?: string }
|
initialValues: ContractInteractionTx
|
||||||
isABI: boolean
|
isABI: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
switchMethod: () => void
|
switchMethod: () => void
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
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 { withSnackbar } from 'notistack'
|
|
||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||||
import { getNetworkInfo } from 'src/config'
|
import { getNetworkInfo } from 'src/config'
|
||||||
import CopyBtn from 'src/components/CopyBtn'
|
import CopyBtn from 'src/components/CopyBtn'
|
||||||
|
@ -21,11 +21,7 @@ import createTransaction from 'src/logic/safe/store/actions/createTransaction'
|
||||||
import { safeSelector } from 'src/logic/safe/store/selectors'
|
import { safeSelector } from 'src/logic/safe/store/selectors'
|
||||||
import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
|
import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
|
||||||
import { estimateTxGasCosts } from 'src/logic/safe/transactions/gas'
|
import { estimateTxGasCosts } from 'src/logic/safe/transactions/gas'
|
||||||
import {
|
import { getERC721TokenContract } from 'src/logic/tokens/store/actions/fetchTokens'
|
||||||
containsMethodByHash,
|
|
||||||
getERC721TokenContract,
|
|
||||||
getHumanFriendlyToken,
|
|
||||||
} from 'src/logic/tokens/store/actions/fetchTokens'
|
|
||||||
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
|
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
|
||||||
import { SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH } from 'src/logic/tokens/utils/tokenHelpers'
|
import { SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH } from 'src/logic/tokens/utils/tokenHelpers'
|
||||||
import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo'
|
import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
|
@ -39,9 +35,22 @@ import { styles } from './style'
|
||||||
|
|
||||||
const { nativeCoin } = getNetworkInfo()
|
const { nativeCoin } = getNetworkInfo()
|
||||||
|
|
||||||
const useStyles = makeStyles(styles as any)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => {
|
export type CollectibleTx = {
|
||||||
|
recipientAddress: string
|
||||||
|
assetAddress: string
|
||||||
|
assetName: string
|
||||||
|
nftTokenId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => void
|
||||||
|
onPrev: () => void
|
||||||
|
tx: CollectibleTx
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReviewCollectible = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const shortener = textShortener()
|
const shortener = textShortener()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
@ -57,12 +66,11 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx
|
||||||
let isCurrent = true
|
let isCurrent = true
|
||||||
|
|
||||||
const estimateGas = async () => {
|
const estimateGas = async () => {
|
||||||
const supportsSafeTransfer = await containsMethodByHash(tx.assetAddress, SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH)
|
try {
|
||||||
const methodToCall = supportsSafeTransfer ? `0x${SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH}` : 'transfer'
|
const methodToCall = `0x${SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH}`
|
||||||
const transferParams = [tx.recipientAddress, tx.nftTokenId]
|
const transferParams = [tx.recipientAddress, tx.nftTokenId]
|
||||||
const params = methodToCall === 'transfer' ? transferParams : [safeAddress, ...transferParams]
|
const params = [safeAddress, ...transferParams]
|
||||||
|
const ERC721Token = await getERC721TokenContract()
|
||||||
const ERC721Token = methodToCall === 'transfer' ? await getHumanFriendlyToken() : await getERC721TokenContract()
|
|
||||||
const tokenInstance = await ERC721Token.at(tx.assetAddress)
|
const tokenInstance = await ERC721Token.at(tx.assetAddress)
|
||||||
const txData = tokenInstance.contract.methods[methodToCall](...params).encodeABI()
|
const txData = tokenInstance.contract.methods[methodToCall](...params).encodeABI()
|
||||||
|
|
||||||
|
@ -74,6 +82,9 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx
|
||||||
setGasCosts(formattedGasCosts)
|
setGasCosts(formattedGasCosts)
|
||||||
setData(txData)
|
setData(txData)
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error while calculating estimated gas:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
estimateGas()
|
estimateGas()
|
||||||
|
@ -84,6 +95,8 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx
|
||||||
}, [safeAddress, tx.assetAddress, tx.nftTokenId, tx.recipientAddress])
|
}, [safeAddress, tx.assetAddress, tx.nftTokenId, tx.recipientAddress])
|
||||||
|
|
||||||
const submitTx = async () => {
|
const submitTx = async () => {
|
||||||
|
try {
|
||||||
|
if (safeAddress) {
|
||||||
dispatch(
|
dispatch(
|
||||||
createTransaction({
|
createTransaction({
|
||||||
safeAddress,
|
safeAddress,
|
||||||
|
@ -91,12 +104,17 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx
|
||||||
valueInWei: '0',
|
valueInWei: '0',
|
||||||
txData: data,
|
txData: data,
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||||
enqueueSnackbar,
|
}),
|
||||||
closeSnackbar,
|
|
||||||
} as any),
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
console.error('There was an error trying to submit the transaction, the safeAddress was not found')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating sendCollectible Tx:', error)
|
||||||
|
} finally {
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -180,4 +198,4 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withSnackbar(ReviewCollectible)
|
export default ReviewCollectible
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { lg, md, secondaryText, sm } from 'src/theme/variables'
|
import { lg, md, secondaryText, sm } from 'src/theme/variables'
|
||||||
|
import { createStyles } from '@material-ui/core'
|
||||||
|
|
||||||
export const styles = () => ({
|
export const styles = createStyles({
|
||||||
heading: {
|
heading: {
|
||||||
padding: `${md} ${lg}`,
|
padding: `${md} ${lg}`,
|
||||||
justifyContent: 'flex-start',
|
justifyContent: 'flex-start',
|
||||||
|
|
|
@ -2,7 +2,6 @@ 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 { BigNumber } from 'bignumber.js'
|
import { BigNumber } from 'bignumber.js'
|
||||||
import { withSnackbar } from 'notistack'
|
|
||||||
import React, { useEffect, useMemo, useState } from 'react'
|
import React, { useEffect, useMemo, useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { toTokenUnit, fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
import { toTokenUnit, fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||||
|
@ -34,11 +33,24 @@ import ArrowDown from '../assets/arrow-down.svg'
|
||||||
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles as any)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const { nativeCoin } = getNetworkInfo()
|
const { nativeCoin } = getNetworkInfo()
|
||||||
|
|
||||||
const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => {
|
export type ReviewTxProp = {
|
||||||
|
recipientAddress: string
|
||||||
|
amount: string
|
||||||
|
txRecipient: string
|
||||||
|
token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReviewTxProps = {
|
||||||
|
onClose: () => void
|
||||||
|
onPrev: () => void
|
||||||
|
tx: ReviewTxProp
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReviewTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { address: safeAddress } = useSelector(safeSelector) || {}
|
const { address: safeAddress } = useSelector(safeSelector) || {}
|
||||||
|
@ -69,7 +81,7 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => {
|
||||||
txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI()
|
txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI()
|
||||||
}
|
}
|
||||||
|
|
||||||
const estimatedGasCosts = await estimateTxGasCosts(safeAddress as string, txRecipient, txData)
|
const estimatedGasCosts = await estimateTxGasCosts(safeAddress as string, txRecipient as string, txData)
|
||||||
const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals)
|
const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals)
|
||||||
const formattedGasCosts = formatAmount(gasCosts)
|
const formattedGasCosts = formatAmount(gasCosts)
|
||||||
|
|
||||||
|
@ -92,17 +104,19 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => {
|
||||||
// if txAmount > 0 it would send ETH from the Safe
|
// if txAmount > 0 it would send ETH from the Safe
|
||||||
const txAmount = isSendingETH ? toTokenUnit(tx.amount, nativeCoin.decimals) : '0'
|
const txAmount = isSendingETH ? toTokenUnit(tx.amount, nativeCoin.decimals) : '0'
|
||||||
|
|
||||||
|
if (safeAddress) {
|
||||||
dispatch(
|
dispatch(
|
||||||
createTransaction({
|
createTransaction({
|
||||||
safeAddress,
|
safeAddress: safeAddress,
|
||||||
to: txRecipient,
|
to: txRecipient as string,
|
||||||
valueInWei: txAmount,
|
valueInWei: txAmount,
|
||||||
txData: data,
|
txData: data,
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||||
enqueueSnackbar,
|
}),
|
||||||
closeSnackbar,
|
|
||||||
} as any),
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
console.error('There was an error trying to submit the transaction, the safeAddress was not found')
|
||||||
|
}
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,4 +210,4 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withSnackbar(ReviewTx)
|
export default ReviewTx
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { lg, md, secondaryText, sm } from 'src/theme/variables'
|
import { lg, md, secondaryText, sm } from 'src/theme/variables'
|
||||||
|
import { createStyles } from '@material-ui/core'
|
||||||
|
|
||||||
export const styles = () => ({
|
export const styles = createStyles({
|
||||||
heading: {
|
heading: {
|
||||||
padding: `${md} ${lg}`,
|
padding: `${md} ${lg}`,
|
||||||
justifyContent: 'flex-start',
|
justifyContent: 'flex-start',
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { sm } from 'src/theme/variables'
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
import ArrowDown from '../assets/arrow-down.svg'
|
||||||
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
import { NFTToken } from 'src/logic/collectibles/sources/collectibles'
|
||||||
|
|
||||||
const formMutators = {
|
const formMutators = {
|
||||||
setMax: (args, state, utils) => {
|
setMax: (args, state, utils) => {
|
||||||
|
@ -43,13 +44,28 @@ const formMutators = {
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
type SendCollectibleProps = {
|
||||||
|
initialValues: any
|
||||||
|
onClose: () => void
|
||||||
|
onNext: (txInfo: SendCollectibleTxInfo) => void
|
||||||
|
recipientAddress?: string
|
||||||
|
selectedToken: NFTToken
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SendCollectibleTxInfo = {
|
||||||
|
assetAddress: string
|
||||||
|
assetName: string
|
||||||
|
nftTokenId: string
|
||||||
|
recipientAddress?: string
|
||||||
|
}
|
||||||
|
|
||||||
const SendCollectible = ({
|
const SendCollectible = ({
|
||||||
initialValues,
|
initialValues,
|
||||||
onClose,
|
onClose,
|
||||||
onNext,
|
onNext,
|
||||||
recipientAddress,
|
recipientAddress,
|
||||||
selectedToken = {},
|
selectedToken,
|
||||||
}): React.ReactElement => {
|
}: SendCollectibleProps): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const nftAssets = useSelector(safeActiveSelectorMap)
|
const nftAssets = useSelector(safeActiveSelectorMap)
|
||||||
const nftTokens = useSelector(nftTokensSelector)
|
const nftTokens = useSelector(nftTokensSelector)
|
||||||
|
@ -67,7 +83,7 @@ const SendCollectible = ({
|
||||||
}
|
}
|
||||||
}, [selectedEntry, pristine])
|
}, [selectedEntry, pristine])
|
||||||
|
|
||||||
const handleSubmit = (values) => {
|
const handleSubmit = (values: SendCollectibleTxInfo) => {
|
||||||
// If the input wasn't modified, there was no mutation of the recipientAddress
|
// If the input wasn't modified, there was no mutation of the recipientAddress
|
||||||
if (!values.recipientAddress) {
|
if (!values.recipientAddress) {
|
||||||
values.recipientAddress = selectedEntry?.address
|
values.recipientAddress = selectedEntry?.address
|
||||||
|
|
|
@ -47,18 +47,20 @@ const formMutators = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(styles as any)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
type SendFundsProps = {
|
export type SendFundsTx = {
|
||||||
initialValues: {
|
|
||||||
amount?: string
|
amount?: string
|
||||||
recipientAddress?: string
|
recipientAddress?: string
|
||||||
token?: string
|
token?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SendFundsProps = {
|
||||||
|
initialValues: SendFundsTx
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onNext: (txInfo: unknown) => void
|
onNext: (txInfo: unknown) => void
|
||||||
recipientAddress: string
|
recipientAddress?: string
|
||||||
selectedToken: string
|
selectedToken?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const { nativeCoin } = getNetworkInfo()
|
const { nativeCoin } = getNetworkInfo()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { lg, md, secondaryText } from 'src/theme/variables'
|
import { lg, md, secondaryText } from 'src/theme/variables'
|
||||||
|
import { createStyles } from '@material-ui/core'
|
||||||
|
|
||||||
export const styles = () => ({
|
export const styles = createStyles({
|
||||||
heading: {
|
heading: {
|
||||||
padding: `${md} ${lg}`,
|
padding: `${md} ${lg}`,
|
||||||
justifyContent: 'flex-start',
|
justifyContent: 'flex-start',
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
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'
|
||||||
|
|
||||||
|
@ -14,11 +13,12 @@ import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
|
||||||
import addSafeOwner from 'src/logic/safe/store/actions/addSafeOwner'
|
import addSafeOwner from 'src/logic/safe/store/actions/addSafeOwner'
|
||||||
import createTransaction from 'src/logic/safe/store/actions/createTransaction'
|
import createTransaction from 'src/logic/safe/store/actions/createTransaction'
|
||||||
|
|
||||||
import { safeOwnersSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
||||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||||
import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
|
import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
|
||||||
|
import { Dispatch } from 'src/logic/safe/store/actions/types'
|
||||||
|
|
||||||
const styles = () => ({
|
const styles = createStyles({
|
||||||
biggerModalWindow: {
|
biggerModalWindow: {
|
||||||
width: '775px',
|
width: '775px',
|
||||||
minHeight: '500px',
|
minHeight: '500px',
|
||||||
|
@ -26,7 +26,15 @@ const styles = () => ({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const sendAddOwner = async (values, safeAddress, ownersOld, enqueueSnackbar, closeSnackbar, dispatch) => {
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
type OwnerValues = {
|
||||||
|
ownerAddress: string
|
||||||
|
ownerName: string
|
||||||
|
threshold: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendAddOwner = async (values: OwnerValues, safeAddress: string, dispatch: Dispatch): Promise<void> => {
|
||||||
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
const txData = gnosisSafe.methods.addOwnerWithThreshold(values.ownerAddress, values.threshold).encodeABI()
|
const txData = gnosisSafe.methods.addOwnerWithThreshold(values.ownerAddress, values.threshold).encodeABI()
|
||||||
|
|
||||||
|
@ -37,9 +45,7 @@ export const sendAddOwner = async (values, safeAddress, ownersOld, enqueueSnackb
|
||||||
valueInWei: '0',
|
valueInWei: '0',
|
||||||
txData,
|
txData,
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
|
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
|
||||||
enqueueSnackbar,
|
}),
|
||||||
closeSnackbar,
|
|
||||||
} as any),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (txHash) {
|
if (txHash) {
|
||||||
|
@ -47,12 +53,17 @@ export const sendAddOwner = async (values, safeAddress, ownersOld, enqueueSnackb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose }) => {
|
type Props = {
|
||||||
|
isOpen: boolean
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddOwner = ({ isOpen, onClose }: Props): React.ReactElement => {
|
||||||
|
const classes = useStyles()
|
||||||
const [activeScreen, setActiveScreen] = useState('selectOwner')
|
const [activeScreen, setActiveScreen] = useState('selectOwner')
|
||||||
const [values, setValues] = useState<any>({})
|
const [values, setValues] = useState<any>({})
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||||
const owners = useSelector(safeOwnersSelector)
|
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => () => {
|
() => () => {
|
||||||
|
@ -91,7 +102,7 @@ const AddOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose })
|
||||||
onClose()
|
onClose()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendAddOwner(values, safeAddress, owners, enqueueSnackbar, closeSnackbar, dispatch)
|
await sendAddOwner(values, safeAddress, dispatch)
|
||||||
dispatch(
|
dispatch(
|
||||||
addOrUpdateAddressBookEntry(makeAddressBookEntry({ name: values.ownerName, address: values.ownerAddress })),
|
addOrUpdateAddressBookEntry(makeAddressBookEntry({ name: values.ownerName, address: values.ownerAddress })),
|
||||||
)
|
)
|
||||||
|
@ -121,4 +132,4 @@ const AddOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles as any)(withSnackbar(AddOwner))
|
export default AddOwner
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
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'
|
||||||
|
|
||||||
|
@ -13,13 +12,10 @@ import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
|
||||||
import createTransaction from 'src/logic/safe/store/actions/createTransaction'
|
import createTransaction from 'src/logic/safe/store/actions/createTransaction'
|
||||||
import removeSafeOwner from 'src/logic/safe/store/actions/removeSafeOwner'
|
import removeSafeOwner from 'src/logic/safe/store/actions/removeSafeOwner'
|
||||||
|
|
||||||
import {
|
import { safeParamAddressFromStateSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors'
|
||||||
safeOwnersSelector,
|
import { Dispatch } from 'src/logic/safe/store/actions/types'
|
||||||
safeParamAddressFromStateSelector,
|
|
||||||
safeThresholdSelector,
|
|
||||||
} from 'src/logic/safe/store/selectors'
|
|
||||||
|
|
||||||
const styles = () => ({
|
const styles = createStyles({
|
||||||
biggerModalWindow: {
|
biggerModalWindow: {
|
||||||
width: '775px',
|
width: '775px',
|
||||||
minHeight: '500px',
|
minHeight: '500px',
|
||||||
|
@ -27,17 +23,22 @@ const styles = () => ({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
type OwnerValues = {
|
||||||
|
ownerAddress: string
|
||||||
|
ownerName: string
|
||||||
|
threshold: string
|
||||||
|
}
|
||||||
|
|
||||||
export const sendRemoveOwner = async (
|
export const sendRemoveOwner = async (
|
||||||
values,
|
values: OwnerValues,
|
||||||
safeAddress,
|
safeAddress: string,
|
||||||
ownerAddressToRemove,
|
ownerAddressToRemove: string,
|
||||||
ownerNameToRemove,
|
ownerNameToRemove: string,
|
||||||
ownersOld,
|
dispatch: Dispatch,
|
||||||
enqueueSnackbar,
|
threshold?: number,
|
||||||
closeSnackbar,
|
): Promise<void> => {
|
||||||
threshold,
|
|
||||||
dispatch,
|
|
||||||
) => {
|
|
||||||
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
const safeOwners = await gnosisSafe.methods.getOwners().call()
|
const safeOwners = await gnosisSafe.methods.getOwners().call()
|
||||||
const index = safeOwners.findIndex(
|
const index = safeOwners.findIndex(
|
||||||
|
@ -53,9 +54,7 @@ export const sendRemoveOwner = async (
|
||||||
valueInWei: '0',
|
valueInWei: '0',
|
||||||
txData,
|
txData,
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
|
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
|
||||||
enqueueSnackbar,
|
}),
|
||||||
closeSnackbar,
|
|
||||||
} as any),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (txHash && threshold === 1) {
|
if (txHash && threshold === 1) {
|
||||||
|
@ -63,11 +62,18 @@ export const sendRemoveOwner = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const RemoveOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose, ownerAddress, ownerName }) => {
|
type RemoveOwnerProps = {
|
||||||
|
isOpen: boolean
|
||||||
|
onClose: () => void
|
||||||
|
ownerAddress: string
|
||||||
|
ownerName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const RemoveOwner = ({ isOpen, onClose, ownerAddress, ownerName }: RemoveOwnerProps): React.ReactElement => {
|
||||||
|
const classes = useStyles()
|
||||||
const [activeScreen, setActiveScreen] = useState('checkOwner')
|
const [activeScreen, setActiveScreen] = useState('checkOwner')
|
||||||
const [values, setValues] = useState<any>({})
|
const [values, setValues] = useState<any>({})
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const owners = useSelector(safeOwnersSelector)
|
|
||||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||||
const threshold = useSelector(safeThresholdSelector)
|
const threshold = useSelector(safeThresholdSelector)
|
||||||
|
|
||||||
|
@ -99,17 +105,7 @@ const RemoveOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose,
|
||||||
|
|
||||||
const onRemoveOwner = () => {
|
const onRemoveOwner = () => {
|
||||||
onClose()
|
onClose()
|
||||||
sendRemoveOwner(
|
sendRemoveOwner(values, safeAddress, ownerAddress, ownerName, dispatch, threshold)
|
||||||
values,
|
|
||||||
safeAddress,
|
|
||||||
ownerAddress,
|
|
||||||
ownerName,
|
|
||||||
owners,
|
|
||||||
enqueueSnackbar,
|
|
||||||
closeSnackbar,
|
|
||||||
threshold,
|
|
||||||
dispatch,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -142,4 +138,4 @@ const RemoveOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles as any)(withSnackbar(RemoveOwner))
|
export default RemoveOwner
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
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'
|
||||||
|
|
||||||
|
@ -15,8 +14,10 @@ import replaceSafeOwner from 'src/logic/safe/store/actions/replaceSafeOwner'
|
||||||
import { safeParamAddressFromStateSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors'
|
import { safeParamAddressFromStateSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors'
|
||||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||||
import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
|
import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
|
||||||
|
import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||||
|
import { Dispatch } from 'src/logic/safe/store/actions/types'
|
||||||
|
|
||||||
const styles = () => ({
|
const styles = createStyles({
|
||||||
biggerModalWindow: {
|
biggerModalWindow: {
|
||||||
width: '775px',
|
width: '775px',
|
||||||
minHeight: '500px',
|
minHeight: '500px',
|
||||||
|
@ -24,20 +25,24 @@ const styles = () => ({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
type OwnerValues = {
|
||||||
|
ownerAddress: string
|
||||||
|
ownerName: string
|
||||||
|
threshold: string
|
||||||
|
}
|
||||||
|
|
||||||
export const sendReplaceOwner = async (
|
export const sendReplaceOwner = async (
|
||||||
values,
|
values: OwnerValues,
|
||||||
safeAddress,
|
safeAddress: string,
|
||||||
ownerAddressToRemove,
|
ownerAddressToRemove: string,
|
||||||
enqueueSnackbar,
|
dispatch: Dispatch,
|
||||||
closeSnackbar,
|
threshold?: number,
|
||||||
threshold,
|
): Promise<void> => {
|
||||||
dispatch,
|
|
||||||
) => {
|
|
||||||
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
const safeOwners = await gnosisSafe.methods.getOwners().call()
|
const safeOwners = await gnosisSafe.methods.getOwners().call()
|
||||||
const index = safeOwners.findIndex(
|
const index = safeOwners.findIndex((ownerAddress) => sameAddress(ownerAddress, ownerAddressToRemove))
|
||||||
(ownerAddress) => ownerAddress.toLowerCase() === ownerAddressToRemove.toLowerCase(),
|
|
||||||
)
|
|
||||||
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
|
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
|
||||||
const txData = gnosisSafe.methods.swapOwner(prevAddress, ownerAddressToRemove, values.ownerAddress).encodeABI()
|
const txData = gnosisSafe.methods.swapOwner(prevAddress, ownerAddressToRemove, values.ownerAddress).encodeABI()
|
||||||
|
|
||||||
|
@ -48,9 +53,7 @@ export const sendReplaceOwner = async (
|
||||||
valueInWei: '0',
|
valueInWei: '0',
|
||||||
txData,
|
txData,
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
|
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
|
||||||
enqueueSnackbar,
|
}),
|
||||||
closeSnackbar,
|
|
||||||
} as any),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (txHash && threshold === 1) {
|
if (txHash && threshold === 1) {
|
||||||
|
@ -65,7 +68,15 @@ export const sendReplaceOwner = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReplaceOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose, ownerAddress, ownerName }) => {
|
type ReplaceOwnerProps = {
|
||||||
|
isOpen: boolean
|
||||||
|
onClose: () => void
|
||||||
|
ownerAddress: string
|
||||||
|
ownerName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReplaceOwner = ({ isOpen, onClose, ownerAddress, ownerName }: ReplaceOwnerProps): React.ReactElement => {
|
||||||
|
const classes = useStyles()
|
||||||
const [activeScreen, setActiveScreen] = useState('checkOwner')
|
const [activeScreen, setActiveScreen] = useState('checkOwner')
|
||||||
const [values, setValues] = useState<any>({})
|
const [values, setValues] = useState<any>({})
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
@ -94,7 +105,7 @@ const ReplaceOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose
|
||||||
const onReplaceOwner = async () => {
|
const onReplaceOwner = async () => {
|
||||||
onClose()
|
onClose()
|
||||||
try {
|
try {
|
||||||
await sendReplaceOwner(values, safeAddress, ownerAddress, enqueueSnackbar, closeSnackbar, threshold, dispatch)
|
await sendReplaceOwner(values, safeAddress, ownerAddress, dispatch, threshold)
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
addOrUpdateAddressBookEntry(makeAddressBookEntry({ address: values.ownerAddress, name: values.ownerName })),
|
addOrUpdateAddressBookEntry(makeAddressBookEntry({ address: values.ownerAddress, name: values.ownerName })),
|
||||||
|
@ -131,4 +142,4 @@ const ReplaceOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles as any)(withSnackbar(ReplaceOwner))
|
export default ReplaceOwner
|
||||||
|
|
Loading…
Reference in New Issue