BridgeDestinationInput.tsx
1 import { t, Trans } from '@lingui/macro'; 2 import { 3 CircularProgress, 4 FormControlLabel, 5 InputBase, 6 Stack, 7 Switch, 8 Typography, 9 } from '@mui/material'; 10 import { isAddress } from 'ethers/lib/utils'; 11 import { useEffect, useState } from 'react'; 12 import { useIsContractAddress } from 'src/hooks/useIsContractAddress'; 13 import { getENSProvider } from 'src/utils/marketsAndNetworksConfig'; 14 15 export const BridgeDestinationInput = ({ 16 connectedAccount, 17 onInputValid, 18 onInputError, 19 sourceChainId, 20 }: { 21 connectedAccount: string; 22 onInputValid: (destinationAccount: string) => void; 23 onInputError: () => void; 24 sourceChainId: number; 25 }) => { 26 const { data: isContractAddress, isFetching: fetchingIsContractAddress } = useIsContractAddress( 27 connectedAccount, 28 sourceChainId 29 ); 30 31 const [useConnectedAccount, setUseConnectedAccount] = useState(true); 32 const [destinationAccount, setDestinationAccount] = useState(''); 33 const [validatingENS, setValidatingENS] = useState(false); 34 35 useEffect(() => { 36 if (isContractAddress === undefined) { 37 return; 38 } 39 40 if (isContractAddress) { 41 setUseConnectedAccount(false); 42 setDestinationAccount(''); 43 } else { 44 setUseConnectedAccount(true); 45 setDestinationAccount(connectedAccount); 46 } 47 }, [connectedAccount, isContractAddress]); 48 49 useEffect(() => { 50 const checkENS = async () => { 51 setValidatingENS(true); 52 const resolvedAddress = await getENSProvider().resolveName(destinationAccount); 53 if (resolvedAddress) { 54 setDestinationAccount(resolvedAddress.toLowerCase()); 55 } 56 setValidatingENS(false); 57 }; 58 59 if (destinationAccount.slice(-4) === '.eth') { 60 checkENS(); 61 } 62 }, [destinationAccount]); 63 64 useEffect(() => { 65 const validAddress = isAddress(destinationAccount); 66 if (validAddress) { 67 onInputValid(destinationAccount); 68 } else { 69 onInputError(); 70 } 71 }, [destinationAccount, onInputError, onInputValid]); 72 73 const showWarning = !useConnectedAccount && !isAddress(destinationAccount); 74 75 return ( 76 <Stack direction="column" gap={1} width="100%"> 77 <Stack direction="row" justifyContent="space-between" alignItems="flex-end"> 78 <Typography color="text.secondary"> 79 <Trans>To</Trans> 80 </Typography> 81 <Stack direction="row" alignItems="center" sx={{ mb: -1 }}> 82 <FormControlLabel 83 sx={{ mx: 0 }} 84 control={ 85 <Switch 86 disableRipple 87 checked={useConnectedAccount} 88 onClick={() => { 89 const newValue = !useConnectedAccount; 90 if (newValue) { 91 setDestinationAccount(connectedAccount); 92 onInputValid(connectedAccount); 93 } else { 94 setDestinationAccount(''); 95 onInputError(); 96 } 97 setUseConnectedAccount(newValue); 98 }} 99 /> 100 } 101 labelPlacement="start" 102 label={ 103 <Typography sx={{ fontSize: '0.75rem' }} color="text.secondary"> 104 <Trans>Use connected account</Trans> 105 </Typography> 106 } 107 /> 108 </Stack> 109 </Stack> 110 <InputBase 111 fullWidth 112 value={destinationAccount} 113 disabled={useConnectedAccount || fetchingIsContractAddress} 114 onChange={(e) => setDestinationAccount(e.target.value)} 115 placeholder={t`Enter ETH address or ENS`} 116 sx={(theme) => ({ 117 height: '44px', 118 px: 2, 119 border: `1px solid ${theme.palette.divider}`, 120 borderRadius: '6px', 121 overflow: 'hidden', 122 })} 123 endAdornment={ 124 validatingENS || fetchingIsContractAddress ? ( 125 <CircularProgress color="inherit" size="16px" /> 126 ) : null 127 } 128 /> 129 <Typography 130 sx={{ 131 visibility: 132 useConnectedAccount || fetchingIsContractAddress 133 ? 'hidden' 134 : showWarning 135 ? 'visible' 136 : 'hidden', 137 }} 138 variant="helperText" 139 color="error.main" 140 > 141 <Trans>Enter a valid address</Trans> 142 </Typography> 143 </Stack> 144 ); 145 };