|
| 1 | +import { useCallback, useEffect, useState } from 'react'; |
| 2 | +import { useSelector } from 'react-redux'; |
| 3 | + |
| 4 | +import { useRoute, RouteProp } from '@react-navigation/native'; |
| 5 | +import { checkAddressCheckSum, toChecksumAddress } from 'web3-utils'; |
| 6 | +import { G } from '@mobily/ts-belt'; |
| 7 | + |
| 8 | +import { SendStackParamList, SendStackRoutes } from '@suite-native/navigation'; |
| 9 | +import { Translation } from '@suite-native/intl'; |
| 10 | +import { selectAccountTokenSymbol, TokensRootState } from '@suite-native/tokens'; |
| 11 | +import { useAlert } from '@suite-native/alerts'; |
| 12 | +import { useFormContext } from '@suite-native/forms'; |
| 13 | +import { isAddressValid } from '@suite-common/wallet-utils'; |
| 14 | +import { AccountsRootState, selectAccountNetworkSymbol } from '@suite-common/wallet-core'; |
| 15 | +import TrezorConnect from '@trezor/connect'; |
| 16 | +import { Link } from '@suite-native/link'; |
| 17 | + |
| 18 | +import { getOutputFieldName } from '../utils'; |
| 19 | +import { TokenOfNetworkAlertBody } from '../components/TokenOfNetworkAlertContent'; |
| 20 | + |
| 21 | +type UseAddressValidationAlertsArgs = { |
| 22 | + inputIndex: number; |
| 23 | +}; |
| 24 | + |
| 25 | +const CHECKSUM_LINK_URL = 'https://trezor.io/learn/a/evm-address-checksum-in-trezor-suite'; |
| 26 | + |
| 27 | +export const useAddressValidationAlerts = ({ inputIndex }: UseAddressValidationAlertsArgs) => { |
| 28 | + const { |
| 29 | + params: { tokenContract, accountKey }, |
| 30 | + } = useRoute<RouteProp<SendStackParamList, SendStackRoutes.SendOutputs>>(); |
| 31 | + const [wasAddressChecksummed, setWasAddressChecksummed] = useState(false); |
| 32 | + const [wasTokenAlertDisplayed, setWasTokenAlertDisplayed] = useState( |
| 33 | + G.isNullable(tokenContract), |
| 34 | + ); |
| 35 | + const { showAlert } = useAlert(); |
| 36 | + |
| 37 | + const tokenSymbol = useSelector((state: TokensRootState) => |
| 38 | + selectAccountTokenSymbol(state, accountKey, tokenContract), |
| 39 | + ); |
| 40 | + const networkSymbol = useSelector((state: AccountsRootState) => |
| 41 | + selectAccountNetworkSymbol(state, accountKey), |
| 42 | + ); |
| 43 | + |
| 44 | + const { watch, setValue } = useFormContext(); |
| 45 | + |
| 46 | + const addressFieldName = getOutputFieldName(inputIndex, 'address'); |
| 47 | + const addressValue = watch(addressFieldName); |
| 48 | + |
| 49 | + const isFilledValidAddress = |
| 50 | + addressValue && networkSymbol && isAddressValid(addressValue, networkSymbol); |
| 51 | + |
| 52 | + const convertAddressToChecksum = useCallback(() => { |
| 53 | + setValue(addressFieldName, toChecksumAddress(addressValue), { |
| 54 | + shouldValidate: true, |
| 55 | + }); |
| 56 | + setWasAddressChecksummed(true); |
| 57 | + }, [addressFieldName, addressValue, setValue]); |
| 58 | + |
| 59 | + const handleAddressChecksum = useCallback(async () => { |
| 60 | + if (isFilledValidAddress && !checkAddressCheckSum(addressValue)) { |
| 61 | + const params = { |
| 62 | + descriptor: addressValue, |
| 63 | + coin: networkSymbol, |
| 64 | + }; |
| 65 | + |
| 66 | + const addressInfo = await TrezorConnect.getAccountInfo(params); |
| 67 | + |
| 68 | + if (addressInfo.success) { |
| 69 | + // Already used addresses are checksumed without displaying the alert. |
| 70 | + const isUsedAddress = addressInfo.payload.history.total !== 0; |
| 71 | + if (isUsedAddress) { |
| 72 | + convertAddressToChecksum(); |
| 73 | + |
| 74 | + return; |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + showAlert({ |
| 79 | + title: <Translation id="moduleSend.outputs.recipients.checksum.alert.title" />, |
| 80 | + description: ( |
| 81 | + <Translation |
| 82 | + id="moduleSend.outputs.recipients.checksum.alert.body" |
| 83 | + values={{ |
| 84 | + link: linkChunk => ( |
| 85 | + <Link |
| 86 | + href={CHECKSUM_LINK_URL} |
| 87 | + label={linkChunk} |
| 88 | + isUnderlined |
| 89 | + textColor="textSubdued" |
| 90 | + /> |
| 91 | + ), |
| 92 | + }} |
| 93 | + /> |
| 94 | + ), |
| 95 | + primaryButtonTitle: ( |
| 96 | + <Translation id="moduleSend.outputs.recipients.checksum.alert.primaryButton" /> |
| 97 | + ), |
| 98 | + onPressPrimaryButton: convertAddressToChecksum, |
| 99 | + }); |
| 100 | + } |
| 101 | + }, [addressValue, isFilledValidAddress, networkSymbol, showAlert, convertAddressToChecksum]); |
| 102 | + |
| 103 | + useEffect(() => { |
| 104 | + const shouldShowTokenAlert = |
| 105 | + tokenContract && isFilledValidAddress && !wasTokenAlertDisplayed; |
| 106 | + const shouldChecksumAddress = |
| 107 | + !wasAddressChecksummed && isFilledValidAddress && wasTokenAlertDisplayed; |
| 108 | + |
| 109 | + if (shouldShowTokenAlert) { |
| 110 | + showAlert({ |
| 111 | + appendix: ( |
| 112 | + <TokenOfNetworkAlertBody |
| 113 | + accountKey={accountKey} |
| 114 | + tokenContract={tokenContract} |
| 115 | + /> |
| 116 | + ), |
| 117 | + primaryButtonTitle: <Translation id="generic.buttons.gotIt" />, |
| 118 | + onPressPrimaryButton: () => setWasTokenAlertDisplayed(true), |
| 119 | + }); |
| 120 | + } else if (shouldChecksumAddress) handleAddressChecksum(); |
| 121 | + // TODO: add path for contract address alert: https://github.com/trezor/trezor-suite/issues/14936. |
| 122 | + else if (!isFilledValidAddress) { |
| 123 | + setWasTokenAlertDisplayed(false); |
| 124 | + setWasAddressChecksummed(false); |
| 125 | + } |
| 126 | + }, [ |
| 127 | + isFilledValidAddress, |
| 128 | + showAlert, |
| 129 | + tokenContract, |
| 130 | + tokenSymbol, |
| 131 | + accountKey, |
| 132 | + wasAddressChecksummed, |
| 133 | + handleAddressChecksum, |
| 134 | + wasTokenAlertDisplayed, |
| 135 | + ]); |
| 136 | + |
| 137 | + return { wasAddressChecksummed }; |
| 138 | +}; |
0 commit comments