diff --git a/src/logic/collectibles/utils/index.ts b/src/logic/collectibles/utils/index.ts deleted file mode 100644 index af0d2e2c8c..0000000000 --- a/src/logic/collectibles/utils/index.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { _getChainId } from 'src/config' -import { CHAIN_ID } from 'src/config/chain.d' -import { getERC721TokenContract, getERC20TokenContract } from 'src/logic/tokens/store/actions/fetchTokens' -import { sameAddress } from 'src/logic/wallets/ethAddresses' -import { CollectibleTx } from 'src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible' - -// CryptoKitties Contract Addresses by network -// This is an exception made for a popular NFT that's not ERC721 standard-compatible, -// so we can allow the user to transfer the assets by using `transferFrom` instead of -// the standard `safeTransferFrom` method. -const CK_ADDRESS = { - [CHAIN_ID.ETHEREUM]: '0x06012c8cf97bead5deae237070f9587f8e7a266d', - [CHAIN_ID.RINKEBY]: '0x16baf0de678e52367adc69fd067e5edd1d33e3bf', -} - -// safeTransferFrom(address,address,uint256) -const SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH = '42842e0e' - -/** - * Returns a method identifier based on the asset specified and the current network - * @param {string} contractAddress - * @returns string - */ -export const getTransferMethodByContractAddress = (contractAddress: string): string => { - if (sameAddress(contractAddress, CK_ADDRESS[_getChainId()])) { - // on mainnet `transferFrom` seems to work fine but we can assure that `transfer` will work on both networks - // so that's the reason why we're falling back to `transfer` for CryptoKitties - return 'transfer' - } - - return `0x${SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH}` -} - -/** - * Builds the encodedABI data for the transfer of an NFT token - * @param {CollectibleTx} tx - * @param {string} safeAddress - * @returns Promise - */ -export const generateERC721TransferTxData = async ( - tx: CollectibleTx, - safeAddress: string | undefined, -): Promise => { - if (!safeAddress) { - throw new Error('Failed to build NFT transfer tx data. SafeAddress is not defined.') - } - - const methodToCall = getTransferMethodByContractAddress(tx.assetAddress) - let transferParams = [tx.recipientAddress, tx.nftTokenId] - let NFTTokenInstance - - if (methodToCall.includes(SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH)) { - // we add the `from` param for the `safeTransferFrom` method call - transferParams = [safeAddress, ...transferParams] - NFTTokenInstance = getERC721TokenContract(tx.assetAddress) - } else { - // we fallback to an ERC20 Token contract whose ABI implements the `transfer` method - NFTTokenInstance = getERC20TokenContract(tx.assetAddress) - } - - return NFTTokenInstance.methods[methodToCall](...transferParams).encodeABI() -} diff --git a/src/routes/safe/components/Balances/SendModal/index.tsx b/src/routes/safe/components/Balances/SendModal/index.tsx index d355f881f2..6ff944aa28 100644 --- a/src/routes/safe/components/Balances/SendModal/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/index.tsx @@ -1,31 +1,17 @@ import { Loader } from '@aura/safe-react-components' import { makeStyles } from '@material-ui/core/styles' -import { Suspense, useEffect, useState, lazy } from 'react' +import { Suspense, lazy, useEffect, useState } from 'react' import Modal from 'src/components/Modal' -import { CollectibleTx } from './screens/ReviewCollectible' -import { ReviewCustomTxProps } from './screens/ContractInteraction/ReviewCustomTx' -import { CustomTxProps } from './screens/ContractInteraction/SendCustomTx' -import { ReviewTxProp } from './screens/ReviewSendFundsTx' import { NFTToken } from 'src/logic/collectibles/sources/collectibles.d' -import { SendCollectibleTxInfo } from './screens/SendCollectible' +import { ReviewTxProp } from './screens/ReviewSendFundsTx' const ChooseTxType = lazy(() => import('./screens/ChooseTxType')) const SendFunds = lazy(() => import('./screens/SendFunds')) -const SendCollectible = lazy(() => import('./screens/SendCollectible')) - -const ReviewCollectible = lazy(() => import('./screens/ReviewCollectible')) - const ReviewSendFundsTx = lazy(() => import('./screens/ReviewSendFundsTx')) -const ContractInteractionReview: any = lazy(() => import('./screens/ContractInteraction/Review')) - -const SendCustomTx = lazy(() => import('./screens/ContractInteraction/SendCustomTx')) - -const ReviewCustomTx = lazy(() => import('./screens/ContractInteraction/ReviewCustomTx')) - const useStyles = makeStyles({ loaderStyle: { height: '500px', @@ -36,6 +22,16 @@ const useStyles = makeStyles({ }, }) +type SendCollectibleTxInfo = { + assetAddress: string + assetName: string + nftTokenId: string + recipientAddress?: string + recipientName?: string + amount?: number + gasLimit?: number +} + type TxType = | 'chooseTxType' | 'sendFunds' @@ -73,13 +69,11 @@ const SendModal = ({ const classes = useStyles() const [activeScreen, setActiveScreen] = useState(activeScreenType || 'chooseTxType') const [tx, setTx] = useState({}) - const [isABI, setIsABI] = useState(true) const [recipient, setRecipient] = useState(recipientAddress) useEffect(() => { setActiveScreen(activeScreenType || 'chooseTxType') - setIsABI(true) setTx({}) setRecipient(recipientAddress) }, [activeScreenType, isOpen, recipientAddress]) @@ -89,25 +83,6 @@ const SendModal = ({ setTx(txInfo) } - const handleContractInteractionCreation = (contractInteractionInfo: any, submit: boolean): void => { - setTx(contractInteractionInfo) - if (submit) setActiveScreen('contractInteractionReview') - } - - const handleCustomTxCreation = (customTxInfo: any, submit: boolean): void => { - setTx(customTxInfo) - if (submit) setActiveScreen('reviewCustomTx') - } - - const handleSendCollectible = (txInfo) => { - setActiveScreen('reviewCollectible') - setTx(txInfo) - } - - const handleSwitchMethod = (): void => { - setIsABI(!isABI) - } - const handleOnPrev = (screen: TxType) => { setRecipient((tx as ReviewTxProp).recipientAddress) setActiveScreen(screen) @@ -157,57 +132,6 @@ const SendModal = ({ tx={tx as ReviewTxProp} /> )} - - {/* {activeScreen === 'contractInteraction' && isABI && ( - - )} */} - - {activeScreen === 'contractInteractionReview' && isABI && tx && ( - handleOnPrev('contractInteraction')} tx={tx} /> - )} - - {activeScreen === 'contractInteraction' && !isABI && ( - - )} - - {activeScreen === 'reviewCustomTx' && ( - handleOnPrev('contractInteraction')} - tx={tx as ReviewCustomTxProps} - /> - )} - - {activeScreen === 'sendCollectible' && ( - - )} - - {activeScreen === 'reviewCollectible' && ( - handleOnPrev('sendCollectible')} - tx={tx as CollectibleTx} - /> - )} ) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx index b76f9b5194..eafaa68a61 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx @@ -28,25 +28,6 @@ const ChooseTxType = ({ setActiveScreen, }: ChooseTxTypeProps): React.ReactElement => { const classes = useStyles() - // const featuresEnabled = useSelector(currentSafeFeaturesEnabled) - // const erc721Enabled = featuresEnabled?.includes(FEATURES.ERC721) - // const contractInteractionEnabled = featuresEnabled?.includes(FEATURES.CONTRACT_INTERACTION) - // const [disableContractInteraction, setDisableContractInteraction] = React.useState(!!recipientAddress) - - // React.useEffect(() => { - // let isCurrent = true - // const isContract = async () => { - // if (recipientAddress && isCurrent) { - // setDisableContractInteraction(!!(await mustBeEthereumContractAddress(recipientAddress))) - // } - // } - - // isContract() - - // return () => { - // isCurrent = false - // } - // }, [recipientAddress]) return ( <> @@ -77,60 +58,11 @@ const ChooseTxType = ({ )} - {/* */} - setActiveScreen('sendFunds')}> Send funds - {/* {erc721Enabled && ( - - )} - {contractInteractionEnabled && ( - - )} */} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx deleted file mode 100644 index 1cdb398987..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useField, useFormState } from 'react-final-form' -import { Modal } from 'src/components/Modal' -import { ButtonStatus } from 'src/components/Modal/type' -import { isReadMethod } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' - -interface ButtonProps { - onClose: () => void - requiresMethod?: boolean -} - -const Buttons = ({ onClose, requiresMethod }: ButtonProps): React.ReactElement => { - const { - input: { value: method }, - } = useField('selectedMethod', { subscription: { value: true } }) - const { modifiedSinceLastSubmit, submitError, submitting, valid, validating } = useFormState({ - subscription: { - modifiedSinceLastSubmit: true, - submitError: true, - submitting: true, - valid: true, - validating: true, - }, - }) - - return ( - - - - ) -} - -export default Buttons diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx deleted file mode 100644 index df5e46d181..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useField, useForm } from 'react-final-form' -import { useRef, useEffect } from 'react' - -import { TextAreaField } from 'src/components/forms/TextAreaField' -import { mustBeEthereumAddress, mustBeEthereumContractAddress } from 'src/components/forms/validator' -import Col from 'src/components/layout/Col' -import Row from 'src/components/layout/Row' -import { getContractABI } from 'src/config' -import { extractUsefulMethods } from 'src/logic/contractInteraction/sources/ABIService' -import { parsePrefixedAddress } from 'src/utils/prefixedAddress' - -const NO_DATA = 'no data' - -const hasUsefulMethods = (abi: string): undefined | string => { - try { - const parsedABI = extractUsefulMethods(JSON.parse(abi)) - - if (parsedABI.length === 0) { - return NO_DATA - } - } catch (e) { - return NO_DATA - } -} - -const ContractABI = (): React.ReactElement => { - const { - input: { value: contractAddress }, - } = useField('contractAddress', { subscription: { value: true } }) - const { mutators } = useForm() - const setAbiValue = useRef(mutators.setAbiValue) - - useEffect(() => { - const validateAndSetAbi = async () => { - const isEthereumAddress = mustBeEthereumAddress(contractAddress) === undefined - const isEthereumContractAddress = (await mustBeEthereumContractAddress(contractAddress)) === undefined - - if (isEthereumAddress && isEthereumContractAddress) { - const { address } = parsePrefixedAddress(contractAddress) - const abi = await getContractABI(address) - const isValidABI = hasUsefulMethods(abi) === undefined - - // this check may help in scenarios where the user first pastes the ABI, - // and then sets a Proxy contract that has no useful methods - if (isValidABI) { - setAbiValue.current(abi) - } - } - } - - if (contractAddress) { - validateAndSetAbi() - } - }, [contractAddress]) - - return ( - - - - - - ) -} - -export default ContractABI diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx deleted file mode 100644 index 91e4890792..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles' -import { useState } from 'react' -import { useFormState, useField } from 'react-final-form' - -import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper' -import { ContractsAddressBookInput } from 'src/routes/safe/components/Balances/SendModal/screens/AddressBookInput' -import Field from 'src/components/forms/Field' -import TextField from 'src/components/forms/TextField' -import { - composeValidators, - mustBeEthereumAddress, - mustBeEthereumContractAddress, - required, - Validator, -} from 'src/components/forms/validator' -import Col from 'src/components/layout/Col' -import Row from 'src/components/layout/Row' -import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' - -const useStyles = makeStyles(styles) - -interface EthAddressInputProps { - isContract?: boolean - isRequired?: boolean - name: string - onScannedValue: (scannedValue: string) => void - text: string -} - -export const EthAddressInput = ({ - isContract = true, - isRequired = true, - name, - onScannedValue, - text, -}: EthAddressInputProps): React.ReactElement => { - const classes = useStyles() - const validatorsList = [ - isRequired && required, - mustBeEthereumAddress, - isContract && mustBeEthereumContractAddress, - ] as Validator[] - const validate = composeValidators(...validatorsList.filter((validator) => validator)) - const { pristine } = useFormState({ subscription: { pristine: true } }) - const { - input: { value }, - } = useField('contractAddress', { subscription: { value: true } }) - const [selectedEntry, setSelectedEntry] = useState<{ address?: string; name?: string | null } | null>({ - address: value, - name: '', - }) - - const handleScan = (value, closeQrModal) => { - let scannedAddress = value - - if (scannedAddress.startsWith('ethereum:')) { - scannedAddress = scannedAddress.replace('ethereum:', '') - } - - setSelectedEntry({ address: scannedAddress }) - onScannedValue(scannedAddress) - closeQrModal() - } - - const handleInputChange: React.ChangeEventHandler = (event) => { - const { value } = event.target - setSelectedEntry({ address: value }) - } - - return ( - <> - - - {selectedEntry?.address ? ( - - ) : ( - {}} - fieldMutator={onScannedValue} - pristine={pristine} - label="Contract address" - /> - )} - - - - - - - ) -} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx deleted file mode 100644 index 811e80f99c..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles' - -import { useFormState } from 'react-final-form' - -import Row from 'src/components/layout/Row' -import Paragraph from 'src/components/layout/Paragraph' -import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' - -const useStyles = makeStyles(styles) - -const FormErrorMessage = (): React.ReactElement | null => { - const classes = useStyles() - const { modifiedSinceLastSubmit, submitError } = useFormState({ - subscription: { modifiedSinceLastSubmit: true, submitError: true }, - }) - - const hasNewSubmitError = !!submitError && !modifiedSinceLastSubmit - return hasNewSubmitError ? ( - - - {submitError} - - - ) : null -} - -export default FormErrorMessage diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/img/check.svg b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/img/check.svg deleted file mode 100644 index 5ffc23141a..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/img/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx deleted file mode 100644 index b57b0ca6dd..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import InputBase from '@material-ui/core/InputBase' -import ListItemIcon from '@material-ui/core/ListItemIcon' -import ListItemText from '@material-ui/core/ListItemText' -import Menu from '@material-ui/core/Menu' -import MenuItem from '@material-ui/core/MenuItem' -import { MuiThemeProvider } from '@material-ui/core/styles' -import SearchIcon from '@material-ui/icons/Search' -import classNames from 'classnames' -import { ReactElement, useEffect, useState } from 'react' -import { useField, useFormState } from 'react-final-form' -import { AbiItem } from 'web3-utils' - -import Col from 'src/components/layout/Col' -import Row from 'src/components/layout/Row' -import { NO_CONTRACT } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' -import CheckIcon from '../MethodsDropdown/img/check.svg' -import { useDropdownStyles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/style' -import { DropdownListTheme } from 'src/theme/mui' -import { extractUsefulMethods, AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService' -import { Text } from '@aura/safe-react-components' -import styled from 'styled-components' - -const MENU_WIDTH = '452px' - -const StyledText = styled(Text)` - padding: 4px 0 0 8px; -` - -interface MethodsDropdownProps { - onChange: (method: AbiItem) => void -} - -export const MethodsDropdown = ({ onChange }: MethodsDropdownProps): ReactElement | null => { - const classes = useDropdownStyles({ buttonWidth: MENU_WIDTH }) - const { - input: { value: abi }, - meta: { valid }, - } = useField('abi', { subscription: { value: true, valid: true } }) - const { - initialValues: { selectedMethod: selectedMethodByDefault }, - } = useFormState({ subscription: { initialValues: true } }) - const [selectedMethod, setSelectedMethod] = useState(selectedMethodByDefault ? selectedMethodByDefault : {}) - const [methodsList, setMethodsList] = useState([]) - const [methodsListFiltered, setMethodsListFiltered] = useState([]) - - const [anchorEl, setAnchorEl] = useState(null) - const [searchParams, setSearchParams] = useState('') - - useEffect(() => { - if (abi) { - try { - setMethodsList(extractUsefulMethods(JSON.parse(abi))) - } catch (e) { - setMethodsList([]) - } - } - }, [abi]) - - useEffect(() => { - setMethodsListFiltered(methodsList.filter(({ name }) => name?.toLowerCase().includes(searchParams.toLowerCase()))) - }, [methodsList, searchParams]) - - const handleClick = (event) => { - setAnchorEl(event.currentTarget) - } - - const handleClose = () => { - setAnchorEl(null) - } - - const onMethodSelectedChanged = (chosenMethod: AbiItem) => { - setSelectedMethod(chosenMethod) - onChange(chosenMethod) - handleClose() - } - - if (!valid || !abi || abi === NO_CONTRACT) { - return null - } - - return ( - - - - <> - - - -
-
- -
- setSearchParams(event.target.value)} - placeholder="Search…" - value={searchParams} - /> -
-
-
- {methodsListFiltered.map((method) => { - const { action, name, signatureHash } = method - - return ( - onMethodSelectedChanged(method)} - value={signatureHash} - > - - - {signatureHash === (selectedMethod as Record).signatureHash ? ( - checked - ) : ( - - )} - - -
{action}
-
-
- ) - })} -
-
- -
- -
- ) -} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/style.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/style.ts deleted file mode 100644 index e357a940af..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/style.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles' - -const buttonWidth = '140px' -export const useDropdownStyles = makeStyles({ - listItem: { - maxWidth: (props: any) => (props.buttonWidth ? props.buttonWidth : buttonWidth), - boxSizing: 'border-box', - }, - listItemSearch: { - maxWidth: (props: any) => (props.buttonWidth ? props.buttonWidth : buttonWidth), - padding: '0', - boxSizing: 'border-box', - backgroundColor: 'black', - }, - localFlag: { - backgroundPosition: '50% 50%', - backgroundRepeat: 'no-repeat', - backgroundSize: 'contain', - height: '20px !important', - width: '26px !important', - }, - etherFlag: { - backgroundPosition: '50% 50%', - backgroundRepeat: 'no-repeat', - backgroundSize: 'contain', - width: '26px', - height: '26px', - }, - iconLeft: { - marginRight: '10px', - }, - iconRight: { - marginLeft: '18px', - }, - button: { - backgroundColor: 'rgba(15, 15, 15, 1)', - border: 'none', - borderRadius: '3px', - boxSizing: 'border-box', - color: 'white', - cursor: 'pointer', - fontSize: '12px', - fontWeight: 'normal', - height: '24px', - lineHeight: '1.33', - marginRight: '20px', - minWidth: (props: any) => (props.buttonWidth ? props.buttonWidth : buttonWidth), - outline: 'none', - padding: '0', - textAlign: 'left', - '&:active': { - opacity: '0.8', - }, - }, - buttonInner: { - boxSizing: 'border-box', - display: 'block', - height: '100%', - lineHeight: '24px', - padding: '0 22px 0 8px', - position: 'relative', - width: '100%', - '&::after': { - borderLeft: '5px solid transparent', - borderRight: '5px solid transparent', - borderTop: '5px solid white', - content: '""', - height: '0', - position: 'absolute', - right: '8px', - top: '9px', - width: '0', - }, - }, - openMenuButton: { - '&::after': { - borderBottom: '5px solid white', - borderLeft: '5px solid transparent', - borderRight: '5px solid transparent', - borderTop: 'none', - }, - }, - dropdownItemsScrollWrapper: { - maxHeight: '280px', - overflow: 'auto', - backgroundColor: 'rgba(15, 15, 15, 1)', - '&:hover': { - backgroundColor: 'rgba(15, 15, 15, 1)', - }, - }, - search: { - backgroundColor: 'rgba(15, 15, 15, 1)', - position: 'relative', - borderRadius: '0', - // backgroundColor: '#fff', - '&:hover': { - backgroundColor: 'rgba(15, 15, 15, 1)', - }, - marginRight: 0, - width: '100%', - }, - searchIcon: { - alignItems: 'center', - color: 'white', - display: 'flex', - height: '100%', - justifyContent: 'center', - left: '12px', - margin: '0', - pointerEvents: 'none', - position: 'absolute', - top: '50%', - transform: 'translateY(-50%)', - width: '18px', - '& path': { - fill: '#b2b5b2', - }, - }, - inputRoot: { - color: 'white', - fontSize: '14px', - fontWeight: 'normal', - lineHeight: '1.43', - width: '100%', - }, - inputInput: { - boxSizing: 'border-box', - color: 'white', - height: '44px', - padding: '12px 12px 12px 40px', - width: '100%', - }, -}) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/NativeCoinValue/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/NativeCoinValue/index.tsx deleted file mode 100644 index 2f7b505985..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/NativeCoinValue/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import InputAdornment from '@material-ui/core/InputAdornment' -import { makeStyles } from '@material-ui/core/styles' - -import { useField } from 'react-final-form' -import { useSelector } from 'react-redux' - -import Field from 'src/components/forms/Field' -import TextField from 'src/components/forms/TextField' -import { composeValidators, maxValue, mustBeFloat } from 'src/components/forms/validator' -import ButtonLink from 'src/components/layout/ButtonLink' -import Col from 'src/components/layout/Col' -import Paragraph from 'src/components/layout/Paragraph' -import Row from 'src/components/layout/Row' -import { isPayable } from 'src/logic/contractInteraction/sources/ABIService' -import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' -import { currentSafeNativeBalance } from 'src/logic/safe/store/selectors' -import { getNativeCurrency } from 'src/config' - -const useStyles = makeStyles(styles) - -interface NativeCoinValueProps { - onSetMax: (nativeCoinBalance: string) => void -} - -export const NativeCoinValue = ({ onSetMax }: NativeCoinValueProps): React.ReactElement | null => { - const classes = useStyles() - const nativeCurrency = getNativeCurrency() - const nativeBalance = useSelector(currentSafeNativeBalance) - - const { - input: { value: method }, - } = useField('selectedMethod', { subscription: { value: true } }) - const disabled = !isPayable(method) - - if (!nativeBalance) { - return null - } - - return disabled ? null : ( - <> - - - Value - - !disabled && onSetMax(nativeBalance)} - weight="bold" - > - Send max - - - - - {nativeCurrency.symbol}, - disabled, - }} - name="value" - placeholder="Value" - text="Value" - type="text" - validate={!disabled && composeValidators(mustBeFloat, maxValue(nativeBalance))} - /> - - - - ) -} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/InputComponent/ArrayTypeInput.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/InputComponent/ArrayTypeInput.tsx deleted file mode 100644 index 2d98fb37a1..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/InputComponent/ArrayTypeInput.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { TextAreaField } from 'src/components/forms/TextAreaField' -import { - isAddress, - isBoolean, - isByte, - isInt, - isUint, -} from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' - -const validator = (value: string): string | undefined => { - try { - const values = JSON.parse(value) - - if (!Array.isArray(values)) { - return 'be sure to surround value with []' - } - } catch (e) { - return 'invalid format' - } -} - -const typePlaceholder = (text: string, type: string): string => { - if (isAddress(type)) { - return `${text} E.g.: ["0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E","0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e"]` - } - - if (isBoolean(type)) { - return `${text} E.g.: [true, false, false, true]` - } - - if (isUint(type)) { - return `${text} E.g.: [1000, 212, 320000022, 23]` - } - - if (isInt(type)) { - return `${text} E.g.: [1000, -212, 1232, -1]` - } - - if (isByte(type)) { - return `${text} E.g.: ["0xc00000000000000000000000000000000000", "0xc00000000000000000000000000000000001"]` - } - - return `${text} E.g.: ["first value", "second value", "third value"]` -} - -const ArrayTypeInput = ({ name, text, type }: { name: string; text: string; type: string }): React.ReactElement => ( - -) - -export default ArrayTypeInput diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/InputComponent/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/InputComponent/index.tsx deleted file mode 100644 index 78d282e2b3..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/InputComponent/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Checkbox } from '@aura/safe-react-components' -import { ReactElement } from 'react' - -import Col from 'src/components/layout/Col' -import Field from 'src/components/forms/Field' -import TextField from 'src/components/forms/TextField' - -import { composeValidators, mustBeAddressHash, required } from 'src/components/forms/validator' -import { isArrayParameter } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' -import ArrayTypeInput from './ArrayTypeInput' - -type Props = { - type: string - keyValue: string - placeholder: string -} - -export const InputComponent = ({ type, keyValue, placeholder }: Props): ReactElement | null => { - if (!type) { - return null - } - - switch (type) { - case 'bool': { - const inputProps = { - 'data-testid': keyValue, - } - return ( - - - - ) - } - case 'address': { - return ( - - - - ) - } - default: { - return ( - - {isArrayParameter(type) ? ( - - ) : ( - - )} - - ) - } - } -} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx deleted file mode 100644 index 4ff7436310..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { ReactElement } from 'react' -import { useField } from 'react-final-form' - -import Row from 'src/components/layout/Row' - -import { InputComponent } from './InputComponent' -import { generateFormFieldKey } from '../utils' -import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService' - -export const RenderInputParams = (): ReactElement | null => { - const { - meta: { valid: validABI }, - } = useField('abi', { subscription: { valid: true, value: true } }) - const { - input: { value: method }, - }: { input: { value: AbiItemExtended } } = useField('selectedMethod', { subscription: { value: true } }) - const renderInputs = validABI && !!method && method.inputs?.length - - return !renderInputs ? null : ( - <> - {method.inputs?.map(({ name, type }, index) => { - const placeholder = name ? `${name} (${type})` : type - const key = generateFormFieldKey(type, method.signatureHash, index) - - return ( - - - - ) - })} - - ) -} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx deleted file mode 100644 index 32ffe4e9f7..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { ReactElement } from 'react' -import { useField } from 'react-final-form' -import { makeStyles } from '@material-ui/core/styles' -import TextField from 'src/components/forms/TextField' -import Col from 'src/components/layout/Col' -import Paragraph from 'src/components/layout/Paragraph' -import Row from 'src/components/layout/Row' - -const useStyles = makeStyles({ - output: { - '& > div > textarea': { - letterSpacing: '-0.5px', - lineHeight: '20px', - height: '40px', - overflowY: 'auto', - }, - }, -}) - -export const RenderOutputParams = (): ReactElement | null => { - const classes = useStyles() - const { - input: { value: method }, - }: any = useField('selectedMethod', { subscription: { value: true } }) - const { - input: { value: results }, - }: any = useField('callResults', { subscription: { value: true } }) - const multipleResults = !!method && method.outputs.length > 1 - - if (results == null || results === '') { - return null - } - - return ( - <> - - - Call result: - - - {method.outputs?.map(({ name, type }, index) => { - const placeholder = name ? `${name} (${type})` : type - const key = `methodCallResult-${method.name}_${index}_${type}` - const value = multipleResults ? results[index] : results - - return ( - - - - - - ) - })} - - ) -} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx deleted file mode 100644 index 8b279a473e..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles' -import { Fragment, useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' - -import ExecuteCheckbox from 'src/components/ExecuteCheckbox' -import PrefixedEthHashInfo from 'src/components/PrefixedEthHashInfo' -import { ReviewInfoText } from 'src/components/ReviewInfoText' -import Block from 'src/components/layout/Block' -import Col from 'src/components/layout/Col' -import Hairline from 'src/components/layout/Hairline' -import Img from 'src/components/layout/Img' -import Paragraph from 'src/components/layout/Paragraph' -import Row from 'src/components/layout/Row' -import { getExplorerInfo, getNativeCurrency } from 'src/config' -import { addressBookEntryName } from 'src/logic/addressBook/store/selectors' -import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService' -import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { useEstimationStatus } from 'src/logic/hooks/useEstimationStatus' -import { toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' -import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers' -import { extractSafeAddress } from 'src/routes/routes' -import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' -import { - generateFormFieldKey, - getValueFromTxInputs, -} from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' -import { ModalHeader } from 'src/routes/safe/components/Balances/SendModal/screens/ModalHeader' -import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' -import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters' -import { EditableTxParameters } from 'src/utils/transactionHelpers/EditableTxParameters' -import { TxParametersDetail } from 'src/utils/transactionHelpers/TxParametersDetail' - -const useStyles = makeStyles(styles) - -export type TransactionReviewType = { - abi?: string - contractAddress?: string - data?: string - value?: string - selectedMethod?: AbiItemExtended -} - -type Props = { - onClose: () => void - onPrev: () => void - onEditTxParameters: () => void - tx: TransactionReviewType - txParameters: TxParameters -} - -const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactElement => { - const explorerUrl = getExplorerInfo(tx.contractAddress as string) - const classes = useStyles() - const dispatch = useDispatch() - const safeAddress = extractSafeAddress() - const nativeCurrency = getNativeCurrency() - const [manualSafeTxGas, setManualSafeTxGas] = useState('0') - const [manualGasPrice, setManualGasPrice] = useState() - const [manualGasLimit, setManualGasLimit] = useState() - const [executionApproved, setExecutionApproved] = useState(true) - const addressName = useSelector((state) => addressBookEntryName(state, { address: tx.contractAddress as string })) - - const [txInfo, setTxInfo] = useState<{ - txRecipient: string - txData: string - txAmount: string - }>({ txData: '', txAmount: '', txRecipient: '' }) - - const { - gasLimit, - gasEstimation, - gasPriceFormatted, - gasCostFormatted, - txEstimationExecutionStatus, - isExecution, - isOffChainSignature, - isCreation, - } = useEstimateTransactionGas({ - txRecipient: txInfo?.txRecipient, - txAmount: txInfo?.txAmount, - txData: txInfo?.txData, - safeTxGas: manualSafeTxGas, - manualGasPrice, - manualGasLimit, - }) - - const doExecute = isExecution && executionApproved - const [buttonStatus] = useEstimationStatus(txEstimationExecutionStatus) - - useEffect(() => { - setTxInfo({ - txRecipient: tx.contractAddress as string, - txAmount: tx.value ? toTokenUnit(tx.value, nativeCurrency.decimals) : '0', - txData: tx.data ? tx.data.trim() : '', - }) - }, [tx.contractAddress, tx.value, tx.data, safeAddress, nativeCurrency.decimals]) - - const closeEditModalCallback = (txParameters: TxParameters) => { - const oldGasPrice = gasPriceFormatted - const newGasPrice = txParameters.ethGasPrice - const oldSafeTxGas = gasEstimation - const newSafeTxGas = txParameters.safeTxGas - - if (newGasPrice && oldGasPrice !== newGasPrice) { - setManualGasPrice(txParameters.ethGasPrice) - } - - if (txParameters.ethGasLimit && gasLimit !== txParameters.ethGasLimit) { - setManualGasLimit(txParameters.ethGasLimit) - } - - if (newSafeTxGas && oldSafeTxGas !== newSafeTxGas) { - setManualSafeTxGas(newSafeTxGas) - } - } - - return ( - - {(txParameters, toggleEditMode) => ( - <> - - - - - - Contract Address - - - - - - - - Value - - - - - Ether - - - - - {tx.value || 0} - {' ' + nativeCurrency.symbol} - - - - - - - Method - - - - - {tx.selectedMethod?.name} - - - {tx.selectedMethod?.inputs?.map(({ name, type }, index) => { - const key = generateFormFieldKey(type, tx.selectedMethod?.signatureHash || '', index) - const value: string = getValueFromTxInputs(key, type, tx) - - return ( - - - - {name} ({type}) - - - - - {value} - - - - ) - })} - - - Data (hex encoded) - - - - - - {tx.data} - - - - - {isExecution && } - - {/* Tx Parameters */} - - - - - )} - - ) -} - -export default ContractInteractionReview diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx deleted file mode 100644 index 005105194b..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles' -import { ReactElement, useState } from 'react' -import { useDispatch } from 'react-redux' - -import Divider from 'src/components/Divider' -import ExecuteCheckbox from 'src/components/ExecuteCheckbox' -import PrefixedEthHashInfo from 'src/components/PrefixedEthHashInfo' -import { ReviewInfoText } from 'src/components/ReviewInfoText' -import Block from 'src/components/layout/Block' -import Col from 'src/components/layout/Col' -import Hairline from 'src/components/layout/Hairline' -import Img from 'src/components/layout/Img' -import Paragraph from 'src/components/layout/Paragraph' -import Row from 'src/components/layout/Row' -import { getExplorerInfo, getNativeCurrency } from 'src/config' -import { EstimationStatus, useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { useEstimationStatus } from 'src/logic/hooks/useEstimationStatus' -import { toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' -import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers' -import { extractSafeAddress } from 'src/routes/routes' -import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' -import { ModalHeader } from 'src/routes/safe/components/Balances/SendModal/screens/ModalHeader' -import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' -import { EditableTxParameters } from 'src/utils/transactionHelpers/EditableTxParameters' -import { TxParametersDetail } from 'src/utils/transactionHelpers/TxParametersDetail' -import { styles } from './style' - -export type ReviewCustomTxProps = { - contractAddress: string - contractName?: string - data: string - value: string -} - -type Props = { - onClose: () => void - onPrev: () => void - tx: ReviewCustomTxProps -} - -const useStyles = makeStyles(styles) - -const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): ReactElement => { - const classes = useStyles() - const dispatch = useDispatch() - const safeAddress = extractSafeAddress() - const nativeCurrency = getNativeCurrency() - const [executionApproved, setExecutionApproved] = useState(true) - - const { - gasLimit, - gasEstimation, - gasPriceFormatted, - gasCostFormatted, - txEstimationExecutionStatus, - isExecution, - isCreation, - isOffChainSignature, - } = useEstimateTransactionGas({ - txRecipient: tx.contractAddress as string, - txData: tx.data ? tx.data.trim() : '', - txAmount: tx.value ? toTokenUnit(tx.value, nativeCurrency.decimals) : '0', - }) - - const doExecute = isExecution && executionApproved - const [buttonStatus] = useEstimationStatus(txEstimationExecutionStatus) - - // const submitTx = (txParameters: TxParameters) => { - // const txRecipient = tx.contractAddress - // const txData = tx.data ? tx.data.trim() : '' - // const txValue = tx.value ? toTokenUnit(tx.value, nativeCurrency.decimals) : '0' - - // if (safeAddress) { - // dispatch( - // createTransaction({ - // safeAddress: safeAddress, - // to: txRecipient as string, - // valueInWei: txValue, - // txData, - // txNonce: txParameters.safeNonce, - // safeTxGas: txParameters.safeTxGas, - // ethParameters: txParameters, - // notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX, - // delayExecution: !executionApproved, - // }), - // ) - // } else { - // console.error('There was an error trying to submit the transaction, the safeAddress was not found') - // } - // onClose() - // } - - return ( - - {(txParameters, toggleEditMode) => ( - <> - - - - - - - - Recipient - - - - - - - - - - - Value - - - - Ether - - {tx.value || 0} - {' ' + nativeCurrency.symbol} - - - - - Data (hex encoded) - - - - - - {tx.data} - - - - - {isExecution && } - - {/* Tx Parameters */} - - - {txEstimationExecutionStatus === EstimationStatus.LOADING ? null : ( - - )} - {/* - submitTx(txParameters), - status: buttonStatus, - text: txEstimationExecutionStatus === EstimationStatus.LOADING ? 'Estimating' : undefined, - testId: 'submit-tx-btn', - }} - /> - */} - - )} - - ) -} - -export default ReviewCustomTx diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/style.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/style.ts deleted file mode 100644 index c2e1c00087..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/style.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { border, lg, md, sm } from 'src/theme/variables' -import { createStyles } from '@material-ui/core' - -export const styles = createStyles({ - container: { - padding: `${md} ${lg}`, - }, - value: { - marginLeft: sm, - }, - outerData: { - borderRadius: '5px', - border: `1px solid ${border}`, - padding: '11px', - minHeight: '21px', - }, - data: { - wordBreak: 'break-all', - overflow: 'auto', - fontSize: '14px', - fontFamily: 'Inter !important', - maxHeight: '100px', - letterSpacing: 'normal', - fontStretch: 'normal', - lineHeight: '1.43', - }, - buttonRow: { - height: '84px', - justifyContent: 'center', - gap: '16px', - }, -}) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/SendCustomTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/SendCustomTx/index.tsx deleted file mode 100644 index 37910b4240..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/SendCustomTx/index.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { ReactElement } from 'react' -import { useSelector } from 'react-redux' -import IconButton from '@material-ui/core/IconButton' -import InputAdornment from '@material-ui/core/InputAdornment' -import { makeStyles } from '@material-ui/core/styles' -import Switch from '@material-ui/core/Switch' -import Close from '@material-ui/icons/Close' - -import Divider from 'src/components/Divider' -import Field from 'src/components/forms/Field' -import GnoForm from 'src/components/forms/GnoForm' -import { TextAreaField } from 'src/components/forms/TextAreaField' -import TextField from 'src/components/forms/TextField' -import { composeValidators, maxValue, minValue, mustBeFloat, mustBeHexData } from 'src/components/forms/validator' -import Block from 'src/components/layout/Block' -import ButtonLink from 'src/components/layout/ButtonLink' -import Col from 'src/components/layout/Col' -import Hairline from 'src/components/layout/Hairline' -import Paragraph from 'src/components/layout/Paragraph' -import Row from 'src/components/layout/Row' -import { currentSafeNativeBalance } from 'src/logic/safe/store/selectors' -import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' - -import { styles } from './style' -import { getNativeCurrency } from 'src/config' -import { EthAddressInput } from '../EthAddressInput' -import { ensResolver, formMutators } from '../utils' -import Buttons from '../Buttons' - -export interface CreatedTx { - contractAddress: string - data: string - value: string | number -} - -export type CustomTxProps = { - contractAddress?: string -} - -type Props = { - initialValues: CustomTxProps - onClose: () => void - onNext: (tx: CreatedTx, submit: boolean) => void - isABI: boolean - switchMethod: () => void -} - -const useStyles = makeStyles(styles) - -const SendCustomTx = ({ initialValues, isABI, onClose, onNext, switchMethod }: Props): ReactElement => { - const classes = useStyles() - const nativeCurrency = getNativeCurrency() - const nativeBalance = useSelector(currentSafeNativeBalance) - - const saveForm = async (values) => { - await handleSubmit(values, false) - switchMethod() - } - - const handleSubmit = ({ contractAddress, data, value, ...values }, submit = true) => { - if (data || value) { - onNext({ ...values, contractAddress, data, value }, submit) - } - } - - return ( - <> - - - Contract interaction - - 1 of 2 - - - - - - - {(submitting, validating, rest, mutators) => { - const handleClickSendMax = () => mutators.setMax(nativeBalance) - const handleToggleAbi = () => saveForm(rest.values) - return ( - <> - - - - - - - - Value - - - Send max - - - - - - {nativeCurrency.symbol}, - }} - name="value" - placeholder="Value*" - text="Value*" - type="text" - validate={composeValidators(mustBeFloat, maxValue(nativeBalance || '0'), minValue(0))} - /> - - - - - - - - - - Use custom data (hex encoded) - - - - - ) - }} - - - ) -} - -export default SendCustomTx diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/SendCustomTx/style.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/SendCustomTx/style.ts deleted file mode 100644 index 869c8dc013..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/SendCustomTx/style.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { lg, md } from 'src/theme/variables' -import { createStyles } from '@material-ui/core' - -export const styles = createStyles({ - heading: { - padding: `${md} ${lg}`, - justifyContent: 'flex-start', - boxSizing: 'border-box', - maxHeight: '74px', - }, - annotation: { - letterSpacing: '-1px', - color: '#a2a8ba', - marginRight: 'auto', - marginLeft: '20px', - }, - manage: { - fontSize: lg, - }, - closeIcon: { - height: '35px', - width: '35px', - color: 'white', - }, - qrCodeBtn: { - cursor: 'pointer', - }, - formContainer: { - padding: `${md} ${lg}`, - }, - buttonRow: { - height: '84px', - justifyContent: 'center', - gap: '16px', - }, - dataInput: { - '& TextField-root-294': { - lineHeight: 'auto', - border: 'green', - }, - }, - selectAddress: { - cursor: 'pointer', - }, -}) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx deleted file mode 100644 index 2785510d32..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import Switch from '@material-ui/core/Switch' -import { makeStyles } from '@material-ui/core/styles' -import { useMemo } from 'react' - -import Divider from 'src/components/Divider' -import GnoForm from 'src/components/forms/GnoForm' -import Block from 'src/components/layout/Block' -import Hairline from 'src/components/layout/Hairline' -import Paragraph from 'src/components/layout/Paragraph' -import { extractSafeAddress } from 'src/routes/routes' -import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' -import { ModalHeader } from '../ModalHeader' -import Buttons from './Buttons' -import ContractABI from './ContractABI' -import { EthAddressInput } from './EthAddressInput' -import FormErrorMessage from './FormErrorMessage' -import { MethodsDropdown } from './MethodsDropdown' -import { NativeCoinValue } from './NativeCoinValue' -import { RenderInputParams } from './RenderInputParams' -import { RenderOutputParams } from './RenderOutputParams' -import { TransactionReviewType } from './Review' -import { styles } from './style' -import { ensResolver, formMutators } from './utils' - -const useStyles = makeStyles(styles) - -export interface CreatedTx { - contractAddress: string - data: string - selectedMethod: TransactionReviewType - value: string | number -} - -export type ContractInteractionTx = { - contractAddress?: string -} - -export interface ContractInteractionProps { - contractAddress?: string - initialValues: ContractInteractionTx - isABI: boolean - onClose: () => void - switchMethod: () => void - onNext: (tx: CreatedTx, submit: boolean) => void -} - -const ContractInteraction: React.FC = ({ - contractAddress, - initialValues, - isABI, - onClose, - onNext, - switchMethod, -}) => { - const classes = useStyles() - const safeAddress = extractSafeAddress() - let setCallResults - - useMemo(() => { - if (contractAddress) { - initialValues.contractAddress = contractAddress - } - }, [contractAddress, initialValues]) - - const saveForm = async (values: CreatedTx): Promise => { - await handleSubmit(values, false) - switchMethod() - } - - const handleSubmit = async ( - { contractAddress, selectedMethod, value, ...values }, - submit = true, - ): Promise> => { - // if (value || (contractAddress && selectedMethod)) { - // try { - // const txObject = createTxObject(selectedMethod, contractAddress, values) - // const data = txObject.encodeABI() - // if (isReadMethod(selectedMethod) && submit) { - // const result = await txObject.call({ from: safeAddress }) - // setCallResults(result) - // // this was a read method, so we won't go to the 'review' screen - // return - // } - // onNext({ ...values, contractAddress, data, selectedMethod, value }, submit) - // } catch (error) { - // return handleSubmitError(error, values) - // } - // } - } - - return ( - <> - - - - {(submitting, validating, rest, mutators) => { - setCallResults = mutators.setCallResults - return ( - <> - - - - - - - - - - - - saveForm(rest.values)} /> - Use custom data (hex encoded) - - - - - ) - }} - - - ) -} - -export default ContractInteraction diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style.ts deleted file mode 100644 index 356e9f5a8f..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { lg, md, sm, border } from 'src/theme/variables' -import { createStyles } from '@material-ui/core' - -export const styles = createStyles({ - qrCodeBtn: { - cursor: 'pointer', - }, - formContainer: { - padding: `${md} ${lg}`, - wordBreak: 'break-word', - }, - value: { - marginLeft: sm, - }, - outerData: { - borderRadius: '5px', - border: `1px solid ${border}`, - padding: '11px', - minHeight: '21px', - }, - data: { - wordBreak: 'break-all', - overflow: 'auto', - fontSize: '14px', - fontFamily: 'Inter !important', - maxHeight: '100px', - letterSpacing: 'normal', - fontStretch: 'normal', - lineHeight: '1.43', - }, - buttonRow: { - height: '84px', - justifyContent: 'center', - gap: '16px', - }, - dataInput: { - '& TextField-root-294': { - lineHeight: 'auto', - border: 'green', - }, - }, - selectAddress: { - cursor: 'pointer', - }, - fullWidth: { - justifyContent: 'space-between', - }, -}) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts deleted file mode 100644 index 3e0f85e70b..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { FORM_ERROR, Mutator, SubmissionErrors } from 'final-form' -import createDecorator from 'final-form-calculate' - -import { BigNumber } from 'bignumber.js' -import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService' -import { isValidCryptoDomainName, isValidEnsName } from 'src/logic/wallets/ethAddresses' -import { getAddressFromDomain } from 'src/logic/wallets/getWeb3' -import { TransactionReviewType } from '../Review' - -export const NO_CONTRACT = 'no contract' - -export const ensResolver = createDecorator({ - field: 'contractAddress', - updates: { - contractAddress: async (contractAddress) => { - try { - const resolvedAddress = - (isValidEnsName(contractAddress) || isValidCryptoDomainName(contractAddress)) && - (await getAddressFromDomain(contractAddress)) - - if (resolvedAddress) { - return resolvedAddress - } - - return contractAddress - } catch (e) { - console.error(e.message) - return contractAddress - } - }, - }, -}) - -export const formMutators: Record> = { - setMax: (args, state, utils) => { - utils.changeValue(state, 'value', () => args[0]) - }, - setContractAddress: (args, state, utils) => { - utils.changeValue(state, 'contractAddress', () => args[0]) - }, - setSelectedMethod: (args, state, utils) => { - const modified = - state.lastFormState?.values.selectedMethod && state.lastFormState.values.selectedMethod.name !== args[0].name - - if (modified) { - utils.changeValue(state, 'callResults', () => '') - utils.changeValue(state, 'value', () => '') - } - - utils.changeValue(state, 'selectedMethod', () => args[0]) - }, - setCallResults: (args, state, utils) => { - utils.changeValue(state, 'callResults', () => args[0]) - }, - setAbiValue: (args, state, utils) => { - utils.changeValue(state, 'abi', () => args[0]) - }, -} - -export const isAddress = (type: string): boolean => type.indexOf('address') === 0 -export const isBoolean = (type: string): boolean => type.indexOf('bool') === 0 -export const isUint = (type: string): boolean => type.indexOf('uint') === 0 -export const isInt = (type: string): boolean => type.indexOf('int') === 0 -export const isByte = (type: string): boolean => type.indexOf('byte') === 0 - -export const isArrayParameter = (parameter: string): boolean => /(\[\d*])+$/.test(parameter) -const getParsedJSONOrArrayFromString = (parameter: string): (string | number)[] | null => { - try { - const arrayResult = JSON.parse(parameter) - return arrayResult.map((value) => { - if (Number.isInteger(value)) { - return new BigNumber(value).toString() - } - return value - }) - } catch (err) { - return null - } -} - -export const handleSubmitError = (error: SubmissionErrors, values: Record): Record => { - for (const key in values) { - if (values.hasOwnProperty(key) && error !== undefined && values[key] === error.value) { - return { [key]: error.reason } - } - } - - // .call() failed and we're logging a generic error - return { [FORM_ERROR]: error ? error.message : undefined } -} - -export const generateFormFieldKey = (type: string, signatureHash: string, index: number): string => { - const keyType = isArrayParameter(type) ? 'arrayParam' : type - return `methodInput-${signatureHash}_${index}_${keyType}` -} - -const extractMethodArgs = - (signatureHash: string, values: Record) => - ({ type }, index) => { - const key = generateFormFieldKey(type, signatureHash, index) - - return getParsedJSONOrArrayFromString(values[key]) || values[key] - } - -// export const createTxObject = ( -// method: AbiItemExtended, -// contractAddress: string, -// values: Record, -// ): ContractSendMethod => { -// const web3 = getWeb3() -// const contract = new web3.eth.Contract([method], contractAddress) -// const { inputs, name = '', signatureHash } = method -// const args = inputs?.map(extractMethodArgs(signatureHash, values)) || [] - -// return contract.methods[name](...args) -// } - -export const isReadMethod = (method: AbiItemExtended): boolean => method && method.action === 'read' - -export const getValueFromTxInputs = (key: string, type: string, tx: TransactionReviewType): string => { - if (isArrayParameter(type)) { - key = key.replace('[]', '') - } - - let value = tx[key] - - if (type === 'bool') { - value = String(value) - } - - return value -} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx deleted file mode 100644 index 41a73593cf..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles' -import { useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' - -import Divider from 'src/components/Divider' -import Block from 'src/components/layout/Block' -import Col from 'src/components/layout/Col' -import Hairline from 'src/components/layout/Hairline' -import Img from 'src/components/layout/Img' -import Paragraph from 'src/components/layout/Paragraph' -import Row from 'src/components/layout/Row' -import { getExplorerInfo } from 'src/config' -import { nftTokensSelector } from 'src/logic/collectibles/store/selectors' -import { generateERC721TransferTxData } from 'src/logic/collectibles/utils' -import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' -import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' -import { textShortener } from 'src/utils/strings' - -import ExecuteCheckbox from 'src/components/ExecuteCheckbox' -import PrefixedEthHashInfo from 'src/components/PrefixedEthHashInfo' -import { ReviewInfoText } from 'src/components/ReviewInfoText' -import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { useEstimationStatus } from 'src/logic/hooks/useEstimationStatus' -import { extractSafeAddress } from 'src/routes/routes' -import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters' -import { EditableTxParameters } from 'src/utils/transactionHelpers/EditableTxParameters' -import { TxParametersDetail } from 'src/utils/transactionHelpers/TxParametersDetail' -import { ModalHeader } from '../ModalHeader' -import { styles } from './style' - -const useStyles = makeStyles(styles) - -export type CollectibleTx = { - recipientAddress: string - recipientName?: 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 shortener = textShortener() - const dispatch = useDispatch() - const safeAddress = extractSafeAddress() - const nftTokens = useSelector(nftTokensSelector) - const [manualSafeTxGas, setManualSafeTxGas] = useState('0') - const [manualGasPrice, setManualGasPrice] = useState() - const [manualGasLimit, setManualGasLimit] = useState() - const [executionApproved, setExecutionApproved] = useState(true) - - const txToken = nftTokens.find( - ({ assetAddress, tokenId }) => assetAddress === tx.assetAddress && tokenId === tx.nftTokenId, - ) - const [data, setData] = useState('') - - const { - gasLimit, - gasEstimation, - gasPriceFormatted, - gasCostFormatted, - txEstimationExecutionStatus, - isExecution, - isOffChainSignature, - isCreation, - } = useEstimateTransactionGas({ - txData: data, - txRecipient: tx.assetAddress, - safeTxGas: manualSafeTxGas, - manualGasPrice, - manualGasLimit, - }) - - const doExecute = isExecution && executionApproved - const [buttonStatus] = useEstimationStatus(txEstimationExecutionStatus) - - useEffect(() => { - let isCurrent = true - - const calculateERC721TransferData = async () => { - try { - const txData = await generateERC721TransferTxData(tx, safeAddress) - if (isCurrent) { - setData(txData) - } - } catch (error) { - console.error('Error calculating ERC721 transfer data:', error.message) - } - } - calculateERC721TransferData() - - return () => { - isCurrent = false - } - }, [safeAddress, tx]) - - // const submitTx = (txParameters: TxParameters) => { - // try { - // if (safeAddress) { - // dispatch( - // createTransaction({ - // safeAddress, - // to: tx.assetAddress, - // valueInWei: '0', - // txData: data, - // txNonce: txParameters.safeNonce, - // safeTxGas: txParameters.safeTxGas, - // ethParameters: txParameters, - // notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX, - // delayExecution: !executionApproved, - // }), - // ) - // } 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() - // } - // } - - const closeEditModalCallback = (txParameters: TxParameters) => { - const oldGasPrice = gasPriceFormatted - const newGasPrice = txParameters.ethGasPrice - const oldSafeTxGas = gasEstimation - const newSafeTxGas = txParameters.safeTxGas - - if (newGasPrice && oldGasPrice !== newGasPrice) { - setManualGasPrice(txParameters.ethGasPrice) - } - - if (txParameters.ethGasLimit && gasLimit !== txParameters.ethGasLimit) { - setManualGasLimit(txParameters.ethGasLimit) - } - - if (newSafeTxGas && oldSafeTxGas !== newSafeTxGas) { - setManualSafeTxGas(newSafeTxGas) - } - } - - return ( - - {(txParameters, toggleEditMode) => ( - <> - - - - - - - - Recipient - - - - - - - - - - {textShortener({ charsStart: 40, charsEnd: 0 })(tx.assetName)} - - - {txToken && ( - - {txToken.name} - - {shortener(txToken.name)} (Token ID: {shortener(txToken.tokenId as string)}) - - - )} - - {isExecution && } - - {/* Tx Parameters */} - - - - {/* - submitTx(txParameters), - type: 'submit', - status: buttonStatus, - text: txEstimationExecutionStatus === EstimationStatus.LOADING ? 'Estimating' : undefined, - testId: 'submit-tx-btn', - }} - /> - */} - - )} - - ) -} - -export default ReviewCollectible diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/style.ts b/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/style.ts deleted file mode 100644 index 9b3538b73c..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/style.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { lg, md, sm } from 'src/theme/variables' -import { createStyles } from '@material-ui/core' - -export const styles = createStyles({ - container: { - padding: `${md} ${lg}`, - }, - amount: { - marginLeft: sm, - }, - buttonRow: { - height: '84px', - justifyContent: 'center', - gap: '16px', - }, -}) diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/CollectibleSelectField/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/CollectibleSelectField/index.tsx deleted file mode 100644 index 0d4781a757..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/CollectibleSelectField/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import ListItemIcon from '@material-ui/core/ListItemIcon' -import ListItemText from '@material-ui/core/ListItemText' -import MenuItem from '@material-ui/core/MenuItem' -import { makeStyles } from '@material-ui/core/styles' - -import { selectStyles, selectedTokenStyles } from './style' - -import Field from 'src/components/forms/Field' -import SelectField from 'src/components/forms/SelectField' -import { required } from 'src/components/forms/validator' -import Img from 'src/components/layout/Img' -import Paragraph from 'src/components/layout/Paragraph' -import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' -import { textShortener } from 'src/utils/strings' -import { NFTToken } from 'src/logic/collectibles/sources/collectibles.d' - -const useSelectedCollectibleStyles = makeStyles(selectedTokenStyles) - -type SelectedCollectibleProps = { - tokenId?: number | string - tokens: NFTToken[] -} - -const SelectedCollectible = ({ tokenId, tokens }: SelectedCollectibleProps): React.ReactElement => { - const classes = useSelectedCollectibleStyles() - const token = tokenId && tokens ? tokens.find(({ tokenId: id }) => tokenId === id) : null - const shortener = textShortener({ charsStart: 40, charsEnd: 0 }) - - return ( - - {token ? ( - <> - - {token.description} - - - - ) : ( - - Select a token* - - )} - - ) -} - -const useCollectibleSelectFieldStyles = makeStyles(selectStyles) - -type CollectibleSelectFieldProps = { - initialValue?: number | string - tokens: NFTToken[] -} - -export const CollectibleSelectField = ({ initialValue, tokens }: CollectibleSelectFieldProps): React.ReactElement => { - const classes = useCollectibleSelectFieldStyles() - - return ( - } - validate={required} - > - {tokens.map((token) => ( - - - {token.name} - - - - ))} - - ) -} diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/CollectibleSelectField/style.ts b/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/CollectibleSelectField/style.ts deleted file mode 100644 index 890830fa4c..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/CollectibleSelectField/style.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { sm } from 'src/theme/variables' -import { createStyles } from '@material-ui/core' - -export const selectedTokenStyles = createStyles({ - container: { - minHeight: '55px', - padding: 0, - width: '100%', - }, - tokenData: { - padding: 0, - margin: 0, - lineHeight: '14px', - }, - tokenImage: { - marginRight: sm, - }, -}) - -export const selectStyles = createStyles({ - selectMenu: { - paddingRight: 0, - }, - tokenImage: { - marginRight: sm, - }, -}) diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/TokenSelectField/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/TokenSelectField/index.tsx deleted file mode 100644 index ef87b7c695..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/TokenSelectField/index.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import ListItemIcon from '@material-ui/core/ListItemIcon' -import ListItemText from '@material-ui/core/ListItemText' -import MenuItem from '@material-ui/core/MenuItem' -import { makeStyles } from '@material-ui/core/styles' - -import { selectStyles, selectedTokenStyles } from './style' - -import Field from 'src/components/forms/Field' -import SelectField from 'src/components/forms/SelectField' -import { required } from 'src/components/forms/validator' -import Img from 'src/components/layout/Img' -import Paragraph from 'src/components/layout/Paragraph' -import { formatAmount } from 'src/logic/tokens/utils/formatAmount' -import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' -import { textShortener } from 'src/utils/strings' -import { NFTAssets } from 'src/logic/collectibles/sources/collectibles.d' - -const useSelectedTokenStyles = makeStyles(selectedTokenStyles) - -type SelectedTokenProps = { - assetAddress?: string - assets: NFTAssets -} - -const SelectedToken = ({ assetAddress, assets }: SelectedTokenProps): React.ReactElement => { - const classes = useSelectedTokenStyles() - const asset = assetAddress ? assets[assetAddress] : null - const shortener = textShortener({ charsStart: 40, charsEnd: 0 }) - - return ( - - {asset && asset.numberOfTokens ? ( - <> - - {asset.name} - - - - ) : ( - - Select an asset* - - )} - - ) -} - -const useTokenSelectFieldStyles = makeStyles(selectStyles) - -type TokenSelectFieldProps = { - assets: NFTAssets - initialValue?: string -} - -const TokenSelectField = ({ assets, initialValue }: TokenSelectFieldProps): React.ReactElement => { - const classes = useTokenSelectFieldStyles() - const tokenClasses = useSelectedTokenStyles() - const assetsAddresses = Object.keys(assets) - - return ( - } - validate={required} - > - {assetsAddresses.map((assetAddress) => { - const asset = assets[assetAddress] - - return ( - - - {asset.name} - - - - ) - })} - - ) -} - -export default TokenSelectField diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/TokenSelectField/style.ts b/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/TokenSelectField/style.ts deleted file mode 100644 index 0737ce130e..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/TokenSelectField/style.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { sm } from 'src/theme/variables' -import { createStyles } from '@material-ui/core' - -export const selectedTokenStyles = createStyles({ - container: { - minHeight: '55px', - padding: 0, - width: '100%', - }, - tokenData: { - padding: 0, - margin: 0, - lineHeight: '14px', - }, - tokenImage: { - display: 'block', - marginRight: sm, - height: 28, - width: 'auto', - }, -}) - -export const selectStyles = createStyles({ - selectMenu: { - paddingRight: 0, - }, - tokenImage: { - marginRight: sm, - }, -}) diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx deleted file mode 100644 index 32f70b2cfd..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx +++ /dev/null @@ -1,256 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles' -import { useState, useMemo } from 'react' -import { useSelector } from 'react-redux' - -import Divider from 'src/components/Divider' -import GnoForm from 'src/components/forms/GnoForm' -import Block from 'src/components/layout/Block' -import Col from 'src/components/layout/Col' -import Hairline from 'src/components/layout/Hairline' -import Paragraph from 'src/components/layout/Paragraph' -import Row from 'src/components/layout/Row' -import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper' -import { Modal } from 'src/components/Modal' -import WhenFieldChanges from 'src/components/WhenFieldChanges' -import PrefixedEthHashInfo from 'src/components/PrefixedEthHashInfo' -import { currentNetworkAddressBook } from 'src/logic/addressBook/store/selectors' -import { nftAssetsSelector, nftTokensSelector } from 'src/logic/collectibles/store/selectors' -import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' -import { AddressBookInput } from 'src/routes/safe/components/Balances/SendModal/screens/AddressBookInput' -import { NFTToken } from 'src/logic/collectibles/sources/collectibles.d' -import { getExplorerInfo } from 'src/config' -import { sameAddress } from 'src/logic/wallets/ethAddresses' -import { sameString } from 'src/utils/strings' - -import { CollectibleSelectField } from './CollectibleSelectField' -import { styles } from './style' -import TokenSelectField from './TokenSelectField' -import { Erc721Transfer } from '@gnosis.pm/safe-react-gateway-sdk' -import { ModalHeader } from '../ModalHeader' -import { mustBeEthereumAddress } from 'src/components/forms/validator' - -const formMutators = { - setMax: (args, state, utils) => { - utils.changeValue(state, 'amount', () => args[0]) - }, - onTokenChange: (args, state, utils) => { - utils.changeValue(state, 'amount', () => '') - }, - setRecipient: (args, state, utils) => { - utils.changeValue(state, 'recipientAddress', () => args[0]) - }, -} - -const useStyles = makeStyles(styles) - -type SendCollectibleProps = { - initialValues: any - onClose: () => void - onNext: (txInfo: SendCollectibleTxInfo) => void - recipientAddress?: string - selectedToken?: NFTToken | Erc721Transfer -} - -export type SendCollectibleTxInfo = { - assetAddress: string - assetName: string - nftTokenId: string - recipientAddress?: string - recipientName?: string - amount?: number - gasLimit?: number -} - -const SendCollectible = ({ - initialValues, - onClose, - onNext, - recipientAddress, - selectedToken, -}: SendCollectibleProps): React.ReactElement => { - const classes = useStyles() - const nftAssets = useSelector(nftAssetsSelector) - const nftTokens = useSelector(nftTokensSelector) - const addressBook = useSelector(currentNetworkAddressBook) - const [addressErrorMsg, setAddressErrorMsg] = useState('') - const [selectedEntry, setSelectedEntry] = useState<{ address: string; name: string } | null>(() => { - const defaultEntry = { address: recipientAddress || '', name: '' } - - // if there's nothing to lookup for, we return the default entry - if (!initialValues?.recipientAddress && !recipientAddress) { - return defaultEntry - } - - // if there's something to lookup for, `initialValues` has precedence over `recipientAddress` - const predefinedAddress = initialValues?.recipientAddress ?? recipientAddress - const addressBookEntry = addressBook.find(({ address }) => { - return sameAddress(predefinedAddress, address) - }) - - // if found in the Address Book, then we return the entry - if (addressBookEntry) { - return addressBookEntry - } - - // otherwise we return the default entry - return defaultEntry - }) - const [pristine, setPristine] = useState(true) - const [isValidAddress, setIsValidAddress] = useState(false) - - useMemo(() => { - if (selectedEntry === null && pristine) { - setPristine(false) - } - }, [selectedEntry, pristine]) - - const handleSubmit = (values: SendCollectibleTxInfo) => { - // If the input wasn't modified, there was no mutation of the recipientAddress - if (!values.recipientAddress) { - values.recipientAddress = selectedEntry?.address - } - values.recipientName = selectedEntry?.name - values.assetName = nftAssets[values.assetAddress].name - - onNext(values) - } - - return ( - <> - - - - {(...args) => { - const formState = args[2] - const mutators = args[3] - const { assetAddress } = formState.values - const selectedNFTTokens = nftTokens.filter((nftToken) => nftToken.assetAddress === assetAddress) - - const handleScan = (value, closeQrModal) => { - let scannedAddress = value - - if (scannedAddress.startsWith('ethereum:')) { - scannedAddress = scannedAddress.replace('ethereum:', '') - } - const scannedName = addressBook.find(({ address }) => { - return sameAddress(scannedAddress, address) - })?.name - const addressErrorMessage = mustBeEthereumAddress(scannedAddress) - if (!addressErrorMessage) { - mutators.setRecipient(scannedAddress) - setSelectedEntry({ - name: scannedName || '', - address: scannedAddress, - }) - setAddressErrorMsg('') - } else setAddressErrorMsg(addressErrorMessage) - - closeQrModal() - } - - let shouldDisableSubmitButton = !isValidAddress - - if (selectedEntry) { - shouldDisableSubmitButton = !selectedEntry.address - } - - return ( - <> - - - - - {selectedEntry && selectedEntry.address ? ( -
{ - if (sameString(e.key, 'Tab')) { - return - } - setSelectedEntry({ address: '', name: '' }) - }} - onClick={() => { - setSelectedEntry({ address: '', name: '' }) - }} - role="listbox" - tabIndex={0} - > - - - Recipient - - - - - - - -
- ) : ( - <> - - - - - - - - - - )} - - - - Collectible - - - - - - - - - - - - Token ID - - - - - - - - -
- - - - - ) - }} -
- - ) -} - -export default SendCollectible diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/style.ts b/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/style.ts deleted file mode 100644 index eb76004eef..0000000000 --- a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/style.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { lg, md } from 'src/theme/variables' -import { createStyles } from '@material-ui/core' - -export const styles = createStyles({ - qrCodeBtn: { - cursor: 'pointer', - }, - formContainer: { - padding: `${md} ${lg}`, - }, - buttonRow: { - height: '84px', - justifyContent: 'center', - gap: '16px', - }, -})