From dccf63bf9b497e4fbbcacc9a8112fa973573fe9e Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 12:23:09 -0400 Subject: [PATCH 01/25] Add confusable warning to SendTo --- app/components/Views/SendFlow/SendTo/index.js | 63 ++++++++++++++++--- locales/languages/en.json | 4 +- package.json | 1 + yarn.lock | 5 ++ 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js index 54a4e6adda7..d12f21e529a 100644 --- a/app/components/Views/SendFlow/SendTo/index.js +++ b/app/components/Views/SendFlow/SendTo/index.js @@ -7,7 +7,6 @@ import { StyleSheet, View, TouchableOpacity, - Text, TextInput, SafeAreaView, InteractionManager, @@ -34,6 +33,9 @@ import Analytics from '../../../../core/Analytics'; import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics'; import { allowedToBuy } from '../../../UI/FiatOrders'; import NetworkList from '../../../../util/networks'; +import { confusables } from 'unicode-confusables'; +import Text from '../../../Base/Text'; +import Icon from 'react-native-vector-icons/FontAwesome'; const { hexToBN } = util; const styles = StyleSheet.create({ @@ -125,12 +127,32 @@ const styles = StyleSheet.create({ marginBottom: 32 }, buyEth: { - ...fontStyles.bold, color: colors.black, textDecorationLine: 'underline' }, - bold: { - ...fontStyles.bold + confusabeError: { + display: 'flex', + flexDirection: 'row', + margin: 16, + padding: 16, + borderWidth: 1, + borderColor: colors.red, + backgroundColor: colors.red000, + borderRadius: 8 + }, + confusableTitle: { + marginTop: -3, + color: colors.red, + ...fontStyles.bold, + fontSize: 14 + }, + confusableMsg: { + color: colors.red, + fontSize: 12, + lineHeight: 16 + }, + warningIcon: { + paddingRight: 8 } }); @@ -210,6 +232,7 @@ class SendFlow extends PureComponent { toEnsName: undefined, addToAddressToAddressBook: false, alias: undefined, + isConfusable: false, inputWidth: { width: '99%' } }; @@ -274,7 +297,7 @@ class SendFlow extends PureComponent { const { AssetsContractController } = Engine.context; const { addressBook, network, identities, providerType } = this.props; const networkAddressBook = addressBook[network] || {}; - let addressError, toAddressName, toEnsName, errorContinue, isOnlyWarning; + let addressError, toAddressName, toEnsName, errorContinue, isOnlyWarning, isConfusable; let [addToAddressToAddressBook, toSelectedAddressReady] = [false, false]; if (isValidAddress(toSelectedAddress)) { const checksummedToSelectedAddress = toChecksumAddress(toSelectedAddress); @@ -304,7 +327,7 @@ class SendFlow extends PureComponent { addressError = ( {strings('transaction.tokenContractAddressWarning_1')} - {strings('transaction.tokenContractAddressWarning_2')} + {strings('transaction.tokenContractAddressWarning_2')} {strings('transaction.tokenContractAddressWarning_3')} ); @@ -329,6 +352,10 @@ class SendFlow extends PureComponent { */ } else if (isENS(toSelectedAddress)) { toEnsName = toSelectedAddress; + // confusables + const key = 'similarTo'; + const collection = confusables(toEnsName).filter(result => key in result); + isConfusable = !!collection.length; const resolvedAddress = await doENSLookup(toSelectedAddress, network); if (resolvedAddress) { const checksummedResolvedAddress = toChecksumAddress(resolvedAddress); @@ -352,7 +379,8 @@ class SendFlow extends PureComponent { toSelectedAddressName: toAddressName, toEnsName, errorContinue, - isOnlyWarning + isOnlyWarning, + isConfusable }); }; @@ -510,7 +538,7 @@ class SendFlow extends PureComponent { return ( <> {'\n'} - + {strings('fiat_on_ramp.buy_eth')} @@ -532,8 +560,10 @@ class SendFlow extends PureComponent { toInputHighlighted, inputWidth, errorContinue, - isOnlyWarning + isOnlyWarning, + isConfusable } = this.state; + return ( @@ -578,6 +608,21 @@ class SendFlow extends PureComponent { /> )} + {isConfusable && ( + + + + + + + {strings('transaction.confusable_title')} + + + {strings('transaction.confusable_msg')} + + + + )} {addToAddressToAddressBook && ( Date: Wed, 31 Mar 2021 13:50:46 -0400 Subject: [PATCH 02/25] Highlight confusable characters --- app/components/Base/Text.js | 10 ++++ .../Views/SendFlow/AddressInputs/index.js | 49 ++++++++++++++++--- app/components/Views/SendFlow/SendTo/index.js | 18 ++++--- 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/app/components/Base/Text.js b/app/components/Base/Text.js index 906ba541319..0663eab3287 100644 --- a/app/components/Base/Text.js +++ b/app/components/Base/Text.js @@ -16,6 +16,9 @@ const style = StyleSheet.create({ right: { textAlign: 'right' }, + red: { + color: colors.red + }, bold: fontStyles.bold, black: { color: colors.black @@ -66,6 +69,7 @@ const Text = ({ green, black, blue, + red, primary, small, upper, @@ -87,6 +91,7 @@ const Text = ({ green && style.green, black && style.black, blue && style.blue, + red && style.red, primary && style.primary, disclaimer && [style.small, style.disclaimer], small && style.small, @@ -110,6 +115,7 @@ Text.defaultProps = { green: false, black: false, blue: false, + red: false, primary: false, disclaimer: false, modal: false, @@ -150,6 +156,10 @@ Text.propTypes = { * Makes text blue */ blue: PropTypes.bool, + /** + * Makes text red + */ + red: PropTypes.bool, /** * Makes text fontPrimary color */ diff --git a/app/components/Views/SendFlow/AddressInputs/index.js b/app/components/Views/SendFlow/AddressInputs/index.js index 24e7ccf9cf7..b60b3e103b7 100644 --- a/app/components/Views/SendFlow/AddressInputs/index.js +++ b/app/components/Views/SendFlow/AddressInputs/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, View, Text, TextInput, TouchableOpacity } from 'react-native'; +import { StyleSheet, View, TextInput, TouchableOpacity } from 'react-native'; import { colors, fontStyles, baseStyles } from '../../../../styles/common'; import AntIcon from 'react-native-vector-icons/AntDesign'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; @@ -7,6 +7,7 @@ import PropTypes from 'prop-types'; import Identicon from '../../../UI/Identicon'; import { renderShortAddress } from '../../../../util/address'; import { strings } from '../../../../../locales/i18n'; +import Text from '../../../Base/Text'; const styles = StyleSheet.create({ wrapper: { @@ -119,6 +120,36 @@ const styles = StyleSheet.create({ } }); +const AddressName = ({ toAddressName, confusableCollection = [] }) => { + if (confusableCollection.length) { + const T = toAddressName.split('').map((char, index) => { + if (confusableCollection.includes(char)) { + return ( + + {char} + + ); + } + return {char}; + }); + return ( + + {T} + + ); + } + return ( + + {toAddressName} + + ); +}; + +AddressName.propTypes = { + toAddressName: PropTypes.string, + confusableCollection: PropTypes.array +}; + export const AddressTo = props => { const { addressToReady, @@ -132,7 +163,8 @@ export const AddressTo = props => { onInputFocus, onSubmit, onInputBlur, - inputWidth + inputWidth, + confusableCollection } = props; return ( @@ -176,9 +208,10 @@ export const AddressTo = props => { {toAddressName && ( - - {toAddressName} - + )} { diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js index d12f21e529a..aadb985a380 100644 --- a/app/components/Views/SendFlow/SendTo/index.js +++ b/app/components/Views/SendFlow/SendTo/index.js @@ -232,7 +232,7 @@ class SendFlow extends PureComponent { toEnsName: undefined, addToAddressToAddressBook: false, alias: undefined, - isConfusable: false, + confusableCollection: [], inputWidth: { width: '99%' } }; @@ -297,7 +297,7 @@ class SendFlow extends PureComponent { const { AssetsContractController } = Engine.context; const { addressBook, network, identities, providerType } = this.props; const networkAddressBook = addressBook[network] || {}; - let addressError, toAddressName, toEnsName, errorContinue, isOnlyWarning, isConfusable; + let addressError, toAddressName, toEnsName, errorContinue, isOnlyWarning, confusableCollection; let [addToAddressToAddressBook, toSelectedAddressReady] = [false, false]; if (isValidAddress(toSelectedAddress)) { const checksummedToSelectedAddress = toChecksumAddress(toSelectedAddress); @@ -354,8 +354,11 @@ class SendFlow extends PureComponent { toEnsName = toSelectedAddress; // confusables const key = 'similarTo'; - const collection = confusables(toEnsName).filter(result => key in result); - isConfusable = !!collection.length; + const collection = confusables(toEnsName) + .filter(result => key in result) + .map(result => result.point); + console.log({ collection }); + confusableCollection = collection; const resolvedAddress = await doENSLookup(toSelectedAddress, network); if (resolvedAddress) { const checksummedResolvedAddress = toChecksumAddress(resolvedAddress); @@ -380,7 +383,7 @@ class SendFlow extends PureComponent { toEnsName, errorContinue, isOnlyWarning, - isConfusable + confusableCollection }); }; @@ -561,7 +564,7 @@ class SendFlow extends PureComponent { inputWidth, errorContinue, isOnlyWarning, - isConfusable + confusableCollection } = this.state; return ( @@ -586,6 +589,7 @@ class SendFlow extends PureComponent { onInputBlur={this.onToInputFocus} onSubmit={this.onTransactionDirectionSet} inputWidth={inputWidth} + confusableCollection={confusableCollection} /> @@ -608,7 +612,7 @@ class SendFlow extends PureComponent { /> )} - {isConfusable && ( + {!!confusableCollection.length && ( From 4bfca9d97d25939fe7bc8ad176199a56f82728d7 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 14:04:14 -0400 Subject: [PATCH 03/25] Replace zeroWidthPoints characters with ? --- .../Views/SendFlow/AddressInputs/index.js | 14 +++++++++++++- app/components/Views/SendFlow/SendTo/index.js | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/components/Views/SendFlow/AddressInputs/index.js b/app/components/Views/SendFlow/AddressInputs/index.js index b60b3e103b7..7a7a0331315 100644 --- a/app/components/Views/SendFlow/AddressInputs/index.js +++ b/app/components/Views/SendFlow/AddressInputs/index.js @@ -120,13 +120,25 @@ const styles = StyleSheet.create({ } }); +const zeroWidthPoints = new Set([ + '\u200b', // zero width space + '\u200c', // zero width non-joiner + '\u200d', // zero width joiner + '\ufeff', // zero width no-break space + '\u2028', // line separator + '\u2029' // paragraph separator, +]); + +const checkZeroWidth = str => zeroWidthPoints.has(str) || /\s/g.test(str); + const AddressName = ({ toAddressName, confusableCollection = [] }) => { if (confusableCollection.length) { const T = toAddressName.split('').map((char, index) => { if (confusableCollection.includes(char)) { + const replacement = checkZeroWidth(char) ? '?' : char; return ( - {char} + {replacement} ); } diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js index aadb985a380..93f06445e83 100644 --- a/app/components/Views/SendFlow/SendTo/index.js +++ b/app/components/Views/SendFlow/SendTo/index.js @@ -357,7 +357,7 @@ class SendFlow extends PureComponent { const collection = confusables(toEnsName) .filter(result => key in result) .map(result => result.point); - console.log({ collection }); + confusableCollection = collection; const resolvedAddress = await doENSLookup(toSelectedAddress, network); if (resolvedAddress) { From bfc188330964fd75de71238a87473b89097834d3 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 14:18:53 -0400 Subject: [PATCH 04/25] Add some notes --- app/components/Views/SendFlow/AddressInputs/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/components/Views/SendFlow/AddressInputs/index.js b/app/components/Views/SendFlow/AddressInputs/index.js index 7a7a0331315..ccbc8b2d5e1 100644 --- a/app/components/Views/SendFlow/AddressInputs/index.js +++ b/app/components/Views/SendFlow/AddressInputs/index.js @@ -134,7 +134,9 @@ const checkZeroWidth = str => zeroWidthPoints.has(str) || /\s/g.test(str); const AddressName = ({ toAddressName, confusableCollection = [] }) => { if (confusableCollection.length) { const T = toAddressName.split('').map((char, index) => { + // if text has a confusable highlight it red if (confusableCollection.includes(char)) { + // if the confusable is zero width, replace it with `?` const replacement = checkZeroWidth(char) ? '?' : char; return ( From 29a101c8dc98a6acdeafcd38c13e39552ac751b2 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 14:47:53 -0400 Subject: [PATCH 05/25] Add confusable highlight to confirm screen --- app/components/Base/Text.js | 3 +- .../__snapshots__/index.test.js.snap | 12 +++++++ .../__snapshots__/index.test.js.snap | 6 ++++ .../__snapshots__/index.test.js.snap | 6 ++++ .../__snapshots__/index.test.js.snap | 4 +++ .../__snapshots__/TokenIcon.test.js.snap | 2 ++ .../TokenSelectButton.test.js.snap | 5 +++ .../__snapshots__/index.test.js.snap | 5 +++ .../__snapshots__/index.test.js.snap | 3 ++ .../__snapshots__/index.test.js.snap | 5 +++ .../__snapshots__/index.test.js.snap | 2 ++ .../Views/SendFlow/AddressInputs/index.js | 6 +++- .../Views/SendFlow/Confirm/index.js | 8 +++++ .../__snapshots__/index.test.js.snap | 1 + .../SendTo/__snapshots__/index.test.js.snap | 33 +++++++++++++++++++ 15 files changed, 99 insertions(+), 2 deletions(-) diff --git a/app/components/Base/Text.js b/app/components/Base/Text.js index 0663eab3287..1478cb60198 100644 --- a/app/components/Base/Text.js +++ b/app/components/Base/Text.js @@ -19,10 +19,10 @@ const style = StyleSheet.create({ red: { color: colors.red }, - bold: fontStyles.bold, black: { color: colors.black }, + bold: fontStyles.bold, blue: { color: colors.blue }, @@ -92,6 +92,7 @@ const Text = ({ black && style.black, blue && style.blue, red && style.red, + black && style.black, primary && style.primary, disclaimer && [style.small, style.disclaimer], small && style.small, diff --git a/app/components/UI/AddCustomNetwork/__snapshots__/index.test.js.snap b/app/components/UI/AddCustomNetwork/__snapshots__/index.test.js.snap index 19c45c50f29..7d82f7e0e3d 100644 --- a/app/components/UI/AddCustomNetwork/__snapshots__/index.test.js.snap +++ b/app/components/UI/AddCustomNetwork/__snapshots__/index.test.js.snap @@ -26,6 +26,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={false} small={false} @@ -55,6 +56,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={false} small={false} @@ -83,6 +85,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={false} small={false} @@ -109,6 +112,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={false} small={false} @@ -132,6 +136,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` noMargin={true} onPress={[Function]} primary={true} + red={false} reset={false} right={false} small={false} @@ -174,6 +179,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={false} small={true} @@ -199,6 +205,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={true} small={false} @@ -232,6 +239,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={false} small={true} @@ -257,6 +265,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={true} small={false} @@ -289,6 +298,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={false} small={true} @@ -314,6 +324,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={true} small={false} @@ -350,6 +361,7 @@ exports[`AddCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={false} + red={false} reset={false} right={false} small={false} diff --git a/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap b/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap index 7e4369c5da1..08125e83109 100644 --- a/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap +++ b/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap @@ -40,6 +40,7 @@ exports[`AssetActionButtons should render correctly 1`] = ` modal={false} numberOfLines={1} primary={false} + red={false} reset={false} right={false} small={false} @@ -97,6 +98,7 @@ exports[`AssetActionButtons should render type add correctly 1`] = ` modal={false} numberOfLines={1} primary={false} + red={false} reset={false} right={false} small={false} @@ -154,6 +156,7 @@ exports[`AssetActionButtons should render type information correctly 1`] = ` modal={false} numberOfLines={1} primary={false} + red={false} reset={false} right={false} small={false} @@ -211,6 +214,7 @@ exports[`AssetActionButtons should render type receive correctly 1`] = ` modal={false} numberOfLines={1} primary={false} + red={false} reset={false} right={false} small={false} @@ -268,6 +272,7 @@ exports[`AssetActionButtons should render type send correctly 1`] = ` modal={false} numberOfLines={1} primary={false} + red={false} reset={false} right={false} small={false} @@ -325,6 +330,7 @@ exports[`AssetActionButtons should render type swap correctly 1`] = ` modal={false} numberOfLines={1} primary={false} + red={false} reset={false} right={false} small={false} diff --git a/app/components/UI/CustomNonceModal/__snapshots__/index.test.js.snap b/app/components/UI/CustomNonceModal/__snapshots__/index.test.js.snap index 5177dd71ee4..79962ceefa0 100644 --- a/app/components/UI/CustomNonceModal/__snapshots__/index.test.js.snap +++ b/app/components/UI/CustomNonceModal/__snapshots__/index.test.js.snap @@ -88,6 +88,7 @@ exports[`CustomNonceModal should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -150,6 +151,7 @@ exports[`CustomNonceModal should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -176,6 +178,7 @@ exports[`CustomNonceModal should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -280,6 +283,7 @@ exports[`CustomNonceModal should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -309,6 +313,7 @@ exports[`CustomNonceModal should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -336,6 +341,7 @@ exports[`CustomNonceModal should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} diff --git a/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap b/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap index 71e670559c0..84e57f358d0 100644 --- a/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap +++ b/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap @@ -28,6 +28,7 @@ exports[`ReceiveRequest should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -70,6 +71,7 @@ exports[`ReceiveRequest should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -104,6 +106,7 @@ exports[`ReceiveRequest should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -126,6 +129,7 @@ exports[`ReceiveRequest should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={true} diff --git a/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap b/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap index db85e8e40db..2e5a01e3d1d 100644 --- a/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap +++ b/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap @@ -38,6 +38,7 @@ exports[`TokenIcon component should Render correctly 3`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -134,6 +135,7 @@ exports[`TokenIcon component should Render correctly 7`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} diff --git a/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap b/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap index 60cadcd3f84..a609ae4002a 100644 --- a/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap +++ b/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap @@ -34,6 +34,7 @@ exports[`TokenSelectButton component should Render correctly 1`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={false} @@ -96,6 +97,7 @@ exports[`TokenSelectButton component should Render correctly 2`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={false} @@ -158,6 +160,7 @@ exports[`TokenSelectButton component should Render correctly 3`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={false} @@ -221,6 +224,7 @@ exports[`TokenSelectButton component should Render correctly 4`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={false} @@ -286,6 +290,7 @@ exports[`TokenSelectButton component should Render correctly 5`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={false} diff --git a/app/components/UI/SwitchCustomNetwork/__snapshots__/index.test.js.snap b/app/components/UI/SwitchCustomNetwork/__snapshots__/index.test.js.snap index cab6e467afd..b1c7d1a29d4 100644 --- a/app/components/UI/SwitchCustomNetwork/__snapshots__/index.test.js.snap +++ b/app/components/UI/SwitchCustomNetwork/__snapshots__/index.test.js.snap @@ -24,6 +24,7 @@ exports[`SwitchCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={false} small={false} @@ -53,6 +54,7 @@ exports[`SwitchCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={false} small={false} @@ -79,6 +81,7 @@ exports[`SwitchCustomNetwork should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -97,6 +100,7 @@ exports[`SwitchCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={true} + red={false} reset={false} right={false} small={false} @@ -117,6 +121,7 @@ exports[`SwitchCustomNetwork should render correctly 1`] = ` modal={false} noMargin={true} primary={false} + red={false} reset={false} right={false} small={false} diff --git a/app/components/UI/TransactionElement/TransactionDetails/__snapshots__/index.test.js.snap b/app/components/UI/TransactionElement/TransactionDetails/__snapshots__/index.test.js.snap index 554bf0e3fc0..bd9bdbcb3fe 100644 --- a/app/components/UI/TransactionElement/TransactionDetails/__snapshots__/index.test.js.snap +++ b/app/components/UI/TransactionElement/TransactionDetails/__snapshots__/index.test.js.snap @@ -30,6 +30,7 @@ exports[`TransactionDetails should render correctly 1`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={true} @@ -58,6 +59,7 @@ exports[`TransactionDetails should render correctly 1`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={true} @@ -87,6 +89,7 @@ exports[`TransactionDetails should render correctly 1`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={true} diff --git a/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap b/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap index c4aede01781..e0a24358079 100644 --- a/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap +++ b/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap @@ -20,6 +20,7 @@ exports[`TransactionReviewFeeCard should render correctly 1`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={false} @@ -39,6 +40,7 @@ exports[`TransactionReviewFeeCard should render correctly 1`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={false} @@ -59,6 +61,7 @@ exports[`TransactionReviewFeeCard should render correctly 1`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={false} @@ -81,6 +84,7 @@ exports[`TransactionReviewFeeCard should render correctly 1`] = ` link={true} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -121,6 +125,7 @@ exports[`TransactionReviewFeeCard should render correctly 1`] = ` link={false} modal={false} primary={true} + red={false} reset={false} right={false} small={false} diff --git a/app/components/Views/OfflineMode/__snapshots__/index.test.js.snap b/app/components/Views/OfflineMode/__snapshots__/index.test.js.snap index 0305a7201aa..528c58446c4 100644 --- a/app/components/Views/OfflineMode/__snapshots__/index.test.js.snap +++ b/app/components/Views/OfflineMode/__snapshots__/index.test.js.snap @@ -50,6 +50,7 @@ exports[`OfflineMode should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} @@ -78,6 +79,7 @@ exports[`OfflineMode should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={false} diff --git a/app/components/Views/SendFlow/AddressInputs/index.js b/app/components/Views/SendFlow/AddressInputs/index.js index ccbc8b2d5e1..30cc70eed38 100644 --- a/app/components/Views/SendFlow/AddressInputs/index.js +++ b/app/components/Views/SendFlow/AddressInputs/index.js @@ -144,7 +144,11 @@ const AddressName = ({ toAddressName, confusableCollection = [] }) => { ); } - return {char}; + return ( + + {char} + + ); }); return ( diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index 573f00fb850..604d8465165 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -56,6 +56,7 @@ import { capitalize } from '../../../../util/general'; import { isMainNet, getNetworkName, getNetworkNonce } from '../../../../util/networks'; import Text from '../../../Base/Text'; import AnalyticsV2 from '../../../../util/analyticsV2'; +import { confusables } from 'unicode-confusables'; const EDIT = 'edit'; const EDIT_NONCE = 'edit_nonce'; @@ -932,6 +933,12 @@ class Confirm extends PureComponent { over } = this.state; + // confusables + const key = 'similarTo'; + const collection = confusables(transactionToName) + .filter(result => key in result) + .map(result => result.point); + const is_main_net = isMainNet(network); const errorPress = is_main_net ? this.buyEth : this.gotoFaucet; const networkName = capitalize(getNetworkName(network)); @@ -952,6 +959,7 @@ class Confirm extends PureComponent { toSelectedAddress={transactionTo} toAddressName={transactionToName} onToSelectedAddressChange={this.onToSelectedAddressChange} + confusableCollection={collection} /> diff --git a/app/components/Views/SendFlow/ErrorMessage/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/ErrorMessage/__snapshots__/index.test.js.snap index 57413a9c970..621c6cfde6f 100644 --- a/app/components/Views/SendFlow/ErrorMessage/__snapshots__/index.test.js.snap +++ b/app/components/Views/SendFlow/ErrorMessage/__snapshots__/index.test.js.snap @@ -32,6 +32,7 @@ exports[`ErrorMessage should render correctly 1`] = ` link={false} modal={false} primary={false} + red={false} reset={false} right={false} small={true} diff --git a/app/components/Views/SendFlow/SendTo/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/SendTo/__snapshots__/index.test.js.snap index d6855203729..c9965feb7e4 100644 --- a/app/components/Views/SendFlow/SendTo/__snapshots__/index.test.js.snap +++ b/app/components/Views/SendFlow/SendTo/__snapshots__/index.test.js.snap @@ -27,6 +27,7 @@ exports[`SendTo should render correctly 1`] = ` /> Add to address book Enter an alias From faa58da0d9c763a9f6ebac95b492110b3a39510d Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 15:47:42 -0400 Subject: [PATCH 06/25] Update checkZeroWidth function --- app/components/Views/SendFlow/AddressInputs/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/components/Views/SendFlow/AddressInputs/index.js b/app/components/Views/SendFlow/AddressInputs/index.js index 30cc70eed38..9d2bc9e9e98 100644 --- a/app/components/Views/SendFlow/AddressInputs/index.js +++ b/app/components/Views/SendFlow/AddressInputs/index.js @@ -129,15 +129,13 @@ const zeroWidthPoints = new Set([ '\u2029' // paragraph separator, ]); -const checkZeroWidth = str => zeroWidthPoints.has(str) || /\s/g.test(str); - const AddressName = ({ toAddressName, confusableCollection = [] }) => { if (confusableCollection.length) { const T = toAddressName.split('').map((char, index) => { // if text has a confusable highlight it red if (confusableCollection.includes(char)) { // if the confusable is zero width, replace it with `?` - const replacement = checkZeroWidth(char) ? '?' : char; + const replacement = zeroWidthPoints.has(char) ? '?' : char; return ( {replacement} From a688d780ae0cc9fa5f70f4a06ce10564d0fbc621 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 16:21:25 -0400 Subject: [PATCH 07/25] Add exclamation mark to Confirm --- .../Views/SendFlow/AddressInputs/index.js | 24 ++++++++++++++++--- .../Views/SendFlow/Confirm/index.js | 1 + 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/components/Views/SendFlow/AddressInputs/index.js b/app/components/Views/SendFlow/AddressInputs/index.js index 9d2bc9e9e98..88d693289a7 100644 --- a/app/components/Views/SendFlow/AddressInputs/index.js +++ b/app/components/Views/SendFlow/AddressInputs/index.js @@ -46,7 +46,15 @@ const styles = StyleSheet.create({ addressToInformation: { flex: 1, flexDirection: 'row', - alignItems: 'center' + alignItems: 'center', + position: 'relative' + }, + exclamation: { + backgroundColor: colors.white, + borderRadius: 12, + position: 'absolute', + bottom: 8, + left: 20 }, address: { flexDirection: 'column', @@ -180,7 +188,8 @@ export const AddressTo = props => { onSubmit, onInputBlur, inputWidth, - confusableCollection + confusableCollection, + displayExclamation } = props; return ( @@ -221,6 +230,11 @@ export const AddressTo = props => { + {displayExclamation && ( + + + + )} {toAddressName && ( @@ -311,7 +325,11 @@ AddressTo.propTypes = { /** * Array of confusables */ - confusableCollection: PropTypes.array + confusableCollection: PropTypes.array, + /** + * Display Exclamation Icon + */ + displayExclamation: PropTypes.bool }; export const AddressFrom = props => { diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index 604d8465165..c2fda378ca5 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -960,6 +960,7 @@ class Confirm extends PureComponent { toAddressName={transactionToName} onToSelectedAddressChange={this.onToSelectedAddressChange} confusableCollection={collection} + displayExclamation={!!collection.length} /> From 38a4db95a876a1c14e4cd583ba8ae78fe7e49020 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 19:12:39 -0400 Subject: [PATCH 08/25] Add handleConfusables method --- .../Confirm/__snapshots__/index.test.js.snap | 2 ++ .../Views/SendFlow/Confirm/index.js | 24 ++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap index 30049b78f87..20e1a6ec866 100644 --- a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap +++ b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap @@ -26,6 +26,8 @@ exports[`Confirm should render correctly 1`] = ` /> diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index c2fda378ca5..2f7d8fc791e 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -314,6 +314,7 @@ class Confirm extends PureComponent { }; state = { + confusableCollection: [], gasSpeedSelected: 'average', gasEstimationReady: false, customGas: undefined, @@ -369,6 +370,17 @@ class Confirm extends PureComponent { } }; + handleConfusables = async () => { + const { transactionToName } = this.props.transactionState; + const key = 'similarTo'; + const confusableCollection = + transactionToName && + confusables(transactionToName) + .filter(result => key in result) + .map(result => result.point); + await this.setState({ confusableCollection }); + }; + componentDidMount = async () => { // For analytics AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SEND_TRANSACTION_STARTED, this.getAnalyticsParams()); @@ -376,6 +388,7 @@ class Confirm extends PureComponent { const { showCustomNonce, navigation, providerType } = this.props; await this.handleFetchBasicEstimates(); showCustomNonce && (await this.setNetworkNonce()); + await this.handleConfusables(); navigation.setParams({ providerType }); this.parseTransactionData(); this.prepareTransaction(); @@ -929,16 +942,11 @@ class Confirm extends PureComponent { errorMessage, transactionConfirmed, warningGasPriceHigh, + confusableCollection, mode, over } = this.state; - // confusables - const key = 'similarTo'; - const collection = confusables(transactionToName) - .filter(result => key in result) - .map(result => result.point); - const is_main_net = isMainNet(network); const errorPress = is_main_net ? this.buyEth : this.gotoFaucet; const networkName = capitalize(getNetworkName(network)); @@ -959,8 +967,8 @@ class Confirm extends PureComponent { toSelectedAddress={transactionTo} toAddressName={transactionToName} onToSelectedAddressChange={this.onToSelectedAddressChange} - confusableCollection={collection} - displayExclamation={!!collection.length} + confusableCollection={confusableCollection} + displayExclamation={!!confusableCollection.length} /> From 13debfa78f4edd06408a855e2535009475c521b0 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 19:40:19 -0400 Subject: [PATCH 09/25] Move this into one spot --- app/components/Views/SendFlow/Confirm/index.js | 9 ++------- app/components/Views/SendFlow/SendTo/index.js | 10 ++-------- app/util/validators.js | 9 +++++++++ 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index 2f7d8fc791e..b63bc4fb5ad 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -57,6 +57,7 @@ import { isMainNet, getNetworkName, getNetworkNonce } from '../../../../util/net import Text from '../../../Base/Text'; import AnalyticsV2 from '../../../../util/analyticsV2'; import { confusables } from 'unicode-confusables'; +import { collectConfusables } from '../../../../util/validators'; const EDIT = 'edit'; const EDIT_NONCE = 'edit_nonce'; @@ -372,13 +373,7 @@ class Confirm extends PureComponent { handleConfusables = async () => { const { transactionToName } = this.props.transactionState; - const key = 'similarTo'; - const confusableCollection = - transactionToName && - confusables(transactionToName) - .filter(result => key in result) - .map(result => result.point); - await this.setState({ confusableCollection }); + await this.setState({ confusableCollection: collectConfusables(transactionToName) }); }; componentDidMount = async () => { diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js index 93f06445e83..e5be4a718af 100644 --- a/app/components/Views/SendFlow/SendTo/index.js +++ b/app/components/Views/SendFlow/SendTo/index.js @@ -33,9 +33,9 @@ import Analytics from '../../../../core/Analytics'; import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics'; import { allowedToBuy } from '../../../UI/FiatOrders'; import NetworkList from '../../../../util/networks'; -import { confusables } from 'unicode-confusables'; import Text from '../../../Base/Text'; import Icon from 'react-native-vector-icons/FontAwesome'; +import { collectConfusables } from '../../../../util/validators'; const { hexToBN } = util; const styles = StyleSheet.create({ @@ -352,13 +352,7 @@ class SendFlow extends PureComponent { */ } else if (isENS(toSelectedAddress)) { toEnsName = toSelectedAddress; - // confusables - const key = 'similarTo'; - const collection = confusables(toEnsName) - .filter(result => key in result) - .map(result => result.point); - - confusableCollection = collection; + confusableCollection = collectConfusables(toEnsName); const resolvedAddress = await doENSLookup(toSelectedAddress, network); if (resolvedAddress) { const checksummedResolvedAddress = toChecksumAddress(resolvedAddress); diff --git a/app/util/validators.js b/app/util/validators.js index 25195c8e272..ff74f5c33b1 100644 --- a/app/util/validators.js +++ b/app/util/validators.js @@ -1,4 +1,5 @@ import { ethers } from 'ethers'; +import { confusables } from 'unicode-confusables'; export const failedSeedPhraseRequirements = seed => { const wordCount = seed.split(/\s/u).length; @@ -13,3 +14,11 @@ export const parseSeedPhrase = seedPhrase => ?.join(' ') || ''; export const { isValidMnemonic } = ethers.utils; + +export const collectConfusables = ensName => { + const key = 'similarTo'; + const collection = confusables(ensName) + .filter(result => key in result) + .map(result => result.point); + return collection; +}; From a619a5d638d7512f25450ef9fd356b00d4200b16 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 20:16:11 -0400 Subject: [PATCH 10/25] Add hasZeroWidthPoints --- app/components/Views/SendFlow/AddressInputs/index.js | 12 ++---------- app/util/validators.js | 11 +++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/components/Views/SendFlow/AddressInputs/index.js b/app/components/Views/SendFlow/AddressInputs/index.js index 88d693289a7..f539b11bd82 100644 --- a/app/components/Views/SendFlow/AddressInputs/index.js +++ b/app/components/Views/SendFlow/AddressInputs/index.js @@ -8,6 +8,7 @@ import Identicon from '../../../UI/Identicon'; import { renderShortAddress } from '../../../../util/address'; import { strings } from '../../../../../locales/i18n'; import Text from '../../../Base/Text'; +import { hasZeroWidthPoints } from '../../../../util/validators'; const styles = StyleSheet.create({ wrapper: { @@ -128,22 +129,13 @@ const styles = StyleSheet.create({ } }); -const zeroWidthPoints = new Set([ - '\u200b', // zero width space - '\u200c', // zero width non-joiner - '\u200d', // zero width joiner - '\ufeff', // zero width no-break space - '\u2028', // line separator - '\u2029' // paragraph separator, -]); - const AddressName = ({ toAddressName, confusableCollection = [] }) => { if (confusableCollection.length) { const T = toAddressName.split('').map((char, index) => { // if text has a confusable highlight it red if (confusableCollection.includes(char)) { // if the confusable is zero width, replace it with `?` - const replacement = zeroWidthPoints.has(char) ? '?' : char; + const replacement = hasZeroWidthPoints(char) ? '?' : char; return ( {replacement} diff --git a/app/util/validators.js b/app/util/validators.js index ff74f5c33b1..cc54cd1940d 100644 --- a/app/util/validators.js +++ b/app/util/validators.js @@ -22,3 +22,14 @@ export const collectConfusables = ensName => { .map(result => result.point); return collection; }; + +const zeroWidthPoints = new Set([ + '\u200b', // zero width space + '\u200c', // zero width non-joiner + '\u200d', // zero width joiner + '\ufeff', // zero width no-break space + '\u2028', // line separator + '\u2029' // paragraph separator, +]); + +export const hasZeroWidthPoints = char => zeroWidthPoints.has(char); From e99798eae66e67641e85eb41943222042dad7c4a Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 20:17:47 -0400 Subject: [PATCH 11/25] Rename T to Texts --- app/components/Views/SendFlow/AddressInputs/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/Views/SendFlow/AddressInputs/index.js b/app/components/Views/SendFlow/AddressInputs/index.js index f539b11bd82..0ff94b2c56f 100644 --- a/app/components/Views/SendFlow/AddressInputs/index.js +++ b/app/components/Views/SendFlow/AddressInputs/index.js @@ -131,7 +131,7 @@ const styles = StyleSheet.create({ const AddressName = ({ toAddressName, confusableCollection = [] }) => { if (confusableCollection.length) { - const T = toAddressName.split('').map((char, index) => { + const Texts = toAddressName.split('').map((char, index) => { // if text has a confusable highlight it red if (confusableCollection.includes(char)) { // if the confusable is zero width, replace it with `?` @@ -150,7 +150,7 @@ const AddressName = ({ toAddressName, confusableCollection = [] }) => { }); return ( - {T} + {Texts} ); } From d9cebaccc12b2d6d6881f5bf29010413a7179344 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Wed, 31 Mar 2021 22:07:13 -0400 Subject: [PATCH 12/25] Use reduce --- app/util/validators.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/util/validators.js b/app/util/validators.js index cc54cd1940d..1eb1f24207e 100644 --- a/app/util/validators.js +++ b/app/util/validators.js @@ -17,9 +17,10 @@ export const { isValidMnemonic } = ethers.utils; export const collectConfusables = ensName => { const key = 'similarTo'; - const collection = confusables(ensName) - .filter(result => key in result) - .map(result => result.point); + const collection = confusables(ensName).reduce( + (total, current) => (key in current ? [...total, current.point] : total), + [] + ); return collection; }; From b0296fb7f8b9c44a1d368cebb958fbffbfd4e366 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Thu, 1 Apr 2021 11:12:47 -0400 Subject: [PATCH 13/25] Add homoglyphic tests --- app/util/validators.test.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/util/validators.test.js b/app/util/validators.test.js index a9f8b7c90e6..0f676b55953 100644 --- a/app/util/validators.test.js +++ b/app/util/validators.test.js @@ -1,4 +1,4 @@ -import { failedSeedPhraseRequirements, parseSeedPhrase } from './validators'; +import { failedSeedPhraseRequirements, parseSeedPhrase, hasZeroWidthPoints, collectConfusables } from './validators'; const VALID_24 = 'verb middle giant soon wage common wide tool gentle garlic issue nut retreat until album recall expire bronze bundle live accident expect dry cook'; @@ -37,3 +37,23 @@ describe('parseSeedPhrase', () => { expect(parseSeedPhrase(` ${String(VALID_12).toUpperCase()}`)).toEqual(VALID_12); }); }); + +describe('hasZeroWidthPoints', () => { + it('should detect zero-width unicode', () => { + expect('vita‍lik.eth'.split('').some(hasZeroWidthPoints)).toEqual(true); + }); + it('should not detect zero-width unicode', () => { + expect('vitalik.eth'.split('').some(hasZeroWidthPoints)).toEqual(false); + }); +}); + +describe('collectConfusables', () => { + it('should detect homoglyphic unicode points', () => { + expect(collectConfusables('vita‍lik.eth')).toHaveLength(1); + expect(collectConfusables('faceboоk.eth')).toHaveLength(1); + }); + + it('should detect multiple homoglyphic unicode points', () => { + expect(collectConfusables('ѕсоре.eth')).toHaveLength(5); + }); +}); From e7dfa57f415bb6e0c946646b10b7913dabcdd51d Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Thu, 1 Apr 2021 12:10:05 -0400 Subject: [PATCH 14/25] Add Modal for confusable on confirm screen --- .../Views/SendFlow/Confirm/index.js | 82 +++++++++++++++++-- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index b63bc4fb5ad..5f918de08a0 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -58,6 +58,7 @@ import Text from '../../../Base/Text'; import AnalyticsV2 from '../../../../util/analyticsV2'; import { confusables } from 'unicode-confusables'; import { collectConfusables } from '../../../../util/validators'; +import Text from '../../../Base/Text'; const EDIT = 'edit'; const EDIT_NONCE = 'edit_nonce'; @@ -213,6 +214,30 @@ const styles = StyleSheet.create({ over: { color: colors.red, ...fontStyles.bold + }, + modal: { + margin: 0, + width: '100%' + }, + confusableWarning: { + alignSelf: 'center', + backgroundColor: colors.white, + width: '80%', + borderRadius: 6, + minHeight: 200, + padding: 15 + }, + confusableTitle: { + color: colors.black, + ...fontStyles.bold, + fontSize: 16, + lineHeight: 18 + }, + confusableMsg: { + marginTop: 10, + color: colors.black, + fontSize: 14, + lineHeight: 18 } }); @@ -334,6 +359,9 @@ class Confirm extends PureComponent { transactionTotalAmountFiat: undefined, errorMessage: undefined, fromAccountModalVisible: false, + paymentChannelBalance: this.props.selectedAsset.assetBalance, + paymentChannelReady: false, + warningModalVisible: false, mode: REVIEW, over: false }; @@ -376,6 +404,8 @@ class Confirm extends PureComponent { await this.setState({ confusableCollection: collectConfusables(transactionToName) }); }; + toggleWarningModal = () => this.setState(state => ({ warningModalVisible: !state.warningModalVisible })); + componentDidMount = async () => { // For analytics AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SEND_TRANSACTION_STARTED, this.getAnalyticsParams()); @@ -939,9 +969,30 @@ class Confirm extends PureComponent { warningGasPriceHigh, confusableCollection, mode, - over + over, + warningModalVisible } = this.state; + const AdressToComponent = () => ( + + ); + + const AdressToComponentWrap = () => + confusableCollection.length ? ( + + + + ) : ( + + ); + const is_main_net = isMainNet(network); const errorPress = is_main_net ? this.buyEth : this.gotoFaucet; const networkName = capitalize(getNetworkName(network)); @@ -957,16 +1008,29 @@ class Confirm extends PureComponent { fromAccountName={fromAccountName} fromAccountBalance={fromAccountBalance} /> - + + + + {strings('transaction.confusable_title')} + {strings('transaction.confusable_msg')} + + + {!selectedAsset.tokenId ? ( From 7f4ba867a084c8519f1a32d5430866bdeb342fea Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Thu, 1 Apr 2021 12:11:28 -0400 Subject: [PATCH 15/25] Update snapshot --- .../Confirm/__snapshots__/index.test.js.snap | 130 +++++++++++++++++- .../Views/SendFlow/Confirm/index.js | 1 - 2 files changed, 124 insertions(+), 7 deletions(-) diff --git a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap index 20e1a6ec866..51c8f6cfab7 100644 --- a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap +++ b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap @@ -24,13 +24,124 @@ exports[`Confirm should render correctly 1`] = ` fromAccountAddress="0x1" onPressIcon={null} /> - + + + + + Check the recipient address + + + We have detected a confusable character in the ENS name. Check the ENS name to avoid a potential scam. + + + Date: Thu, 1 Apr 2021 13:42:43 -0400 Subject: [PATCH 16/25] Use Swaps InfoModal --- .../Confirm/__snapshots__/index.test.js.snap | 103 ++---------------- .../Views/SendFlow/Confirm/index.js | 49 +-------- 2 files changed, 13 insertions(+), 139 deletions(-) diff --git a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap index 51c8f6cfab7..1e0f1bd9668 100644 --- a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap +++ b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap @@ -26,63 +26,8 @@ exports[`Confirm should render correctly 1`] = ` /> - - + - Check the recipient address - - We have detected a confusable character in the ENS name. Check the ENS name to avoid a potential scam. - - + } + isVisible={false} + title="Check the recipient address" + toggleModal={[Function]} + /> - - - {strings('transaction.confusable_title')} - {strings('transaction.confusable_msg')} - - + toggleModal={this.toggleWarningModal} + title={strings('transaction.confusable_title')} + body={{strings('transaction.confusable_msg')}} + /> {!selectedAsset.tokenId ? ( From 1211a92ec653cfbcb9f632094a37ace90e406b7c Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Thu, 1 Apr 2021 14:23:26 -0400 Subject: [PATCH 17/25] Increase lineheight on modals --- app/components/UI/Swaps/QuotesView.js | 9 ++++++--- app/components/Views/SendFlow/Confirm/index.js | 5 ++++- app/components/Views/SendFlow/SendTo/index.js | 4 +++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index d578eadd56c..66e66ce4e8e 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -199,6 +199,9 @@ const styles = StyleSheet.create({ termsButton: { marginTop: 10, marginBottom: 6 + }, + text: { + lineHeight: 20 } }); @@ -1303,20 +1306,20 @@ function SwapsQuotesView({ isVisible={isUpdateModalVisible} toggleModal={toggleUpdateModal} title={strings('swaps.quotes_update_often')} - body={{strings('swaps.quotes_update_often_text')}} + body={{strings('swaps.quotes_update_often_text')}} /> {strings('swaps.price_difference_body')}} + body={{strings('swaps.price_difference_body')}} /> + {strings('swaps.fee_text.get_the')} {strings('swaps.fee_text.best_price')}{' '} {strings('swaps.fee_text.from_the')} {strings('swaps.fee_text.top_liquidity')}{' '} {strings('swaps.fee_text.fee_is_applied', { diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index e963cac7314..03a9352357b 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -213,6 +213,9 @@ const styles = StyleSheet.create({ over: { color: colors.red, ...fontStyles.bold + }, + text: { + lineHeight: 20 } }); @@ -990,7 +993,7 @@ class Confirm extends PureComponent { isVisible={warningModalVisible} toggleModal={this.toggleWarningModal} title={strings('transaction.confusable_title')} - body={{strings('transaction.confusable_msg')}} + body={{strings('transaction.confusable_msg')}} /> diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js index e5be4a718af..fb96148d539 100644 --- a/app/components/Views/SendFlow/SendTo/index.js +++ b/app/components/Views/SendFlow/SendTo/index.js @@ -561,6 +561,8 @@ class SendFlow extends PureComponent { confusableCollection } = this.state; + const displayConfusableWarning = confusableCollection && !!confusableCollection.length; + return ( @@ -606,7 +608,7 @@ class SendFlow extends PureComponent { /> )} - {!!confusableCollection.length && ( + {displayConfusableWarning && ( From 39e7a463795e3ce783a5df57398f2b0083c117be Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Thu, 1 Apr 2021 14:50:08 -0400 Subject: [PATCH 18/25] Only display warning if address is not in addressBook --- app/components/Views/SendFlow/SendTo/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js index fb96148d539..c457df771c4 100644 --- a/app/components/Views/SendFlow/SendTo/index.js +++ b/app/components/Views/SendFlow/SendTo/index.js @@ -544,6 +544,7 @@ class SendFlow extends PureComponent { render = () => { const { ticker } = this.props; + const { addressBook, network } = this.props; const { fromSelectedAddress, fromAccountName, @@ -561,7 +562,9 @@ class SendFlow extends PureComponent { confusableCollection } = this.state; - const displayConfusableWarning = confusableCollection && !!confusableCollection.length; + const checksummedAddress = toSelectedAddress && toChecksumAddress(toSelectedAddress); + const existingContact = checksummedAddress && addressBook[network] && addressBook[network][checksummedAddress]; + const displayConfusableWarning = !existingContact && confusableCollection && !!confusableCollection.length; return ( From f65dd5afe63a2aa7c8121cb5653674190bf7e392 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Thu, 1 Apr 2021 15:04:12 -0400 Subject: [PATCH 19/25] Update snapshot --- .../Views/SendFlow/Confirm/__snapshots__/index.test.js.snap | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap index 1e0f1bd9668..9ef917d8156 100644 --- a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap +++ b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap @@ -43,6 +43,11 @@ exports[`Confirm should render correctly 1`] = ` right={false} small={false} strikethrough={false} + style={ + Object { + "lineHeight": 20, + } + } underline={false} upper={false} > From 89284fc37f727c334b915f3140b7eee204db5912 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Thu, 8 Apr 2021 13:39:39 -0400 Subject: [PATCH 20/25] Make texts lowercase --- app/components/Views/SendFlow/AddressInputs/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/Views/SendFlow/AddressInputs/index.js b/app/components/Views/SendFlow/AddressInputs/index.js index 0ff94b2c56f..7a59d533301 100644 --- a/app/components/Views/SendFlow/AddressInputs/index.js +++ b/app/components/Views/SendFlow/AddressInputs/index.js @@ -131,7 +131,7 @@ const styles = StyleSheet.create({ const AddressName = ({ toAddressName, confusableCollection = [] }) => { if (confusableCollection.length) { - const Texts = toAddressName.split('').map((char, index) => { + const texts = toAddressName.split('').map((char, index) => { // if text has a confusable highlight it red if (confusableCollection.includes(char)) { // if the confusable is zero width, replace it with `?` @@ -150,7 +150,7 @@ const AddressName = ({ toAddressName, confusableCollection = [] }) => { }); return ( - {Texts} + {texts} ); } From def68ea2441fe7189d42c7716fbfb925fd42dbf6 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Thu, 8 Apr 2021 13:40:17 -0400 Subject: [PATCH 21/25] Remove unused state --- app/components/Views/SendFlow/Confirm/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index 03a9352357b..5cb2903b948 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -337,8 +337,6 @@ class Confirm extends PureComponent { transactionTotalAmountFiat: undefined, errorMessage: undefined, fromAccountModalVisible: false, - paymentChannelBalance: this.props.selectedAsset.assetBalance, - paymentChannelReady: false, warningModalVisible: false, mode: REVIEW, over: false From e7d73352f500552137f9daa29d3a1e2235d492f0 Mon Sep 17 00:00:00 2001 From: andrepimenta Date: Wed, 14 Apr 2021 17:40:46 +0100 Subject: [PATCH 22/25] Add patch --- patches/unicode-confusables+0.1.1.patch | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 patches/unicode-confusables+0.1.1.patch diff --git a/patches/unicode-confusables+0.1.1.patch b/patches/unicode-confusables+0.1.1.patch new file mode 100644 index 00000000000..9b90e6b4c18 --- /dev/null +++ b/patches/unicode-confusables+0.1.1.patch @@ -0,0 +1,15 @@ +diff --git a/node_modules/unicode-confusables/data/confusables.json b/node_modules/unicode-confusables/data/confusables.json +index 855e49c..b0b8a0b 100644 +--- a/node_modules/unicode-confusables/data/confusables.json ++++ b/node_modules/unicode-confusables/data/confusables.json +@@ -157,8 +157,8 @@ + "໊": "๊", + "໋": "๋", + "꙯": "⃩", +- "
": " ", +- "
": " ", ++ "\u2028": " ", ++ "\u2029": " ", + " ": " ", + " ": " ", + " ": " ", From 12ca98ddd10d66831e0cdaf371bbb8f18b3ca776 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Mon, 26 Apr 2021 13:55:41 -0400 Subject: [PATCH 23/25] Display as warning in yelllow when not zero width --- app/components/Views/SendFlow/SendTo/index.js | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js index c457df771c4..cc2013280e7 100644 --- a/app/components/Views/SendFlow/SendTo/index.js +++ b/app/components/Views/SendFlow/SendTo/index.js @@ -35,7 +35,7 @@ import { allowedToBuy } from '../../../UI/FiatOrders'; import NetworkList from '../../../../util/networks'; import Text from '../../../Base/Text'; import Icon from 'react-native-vector-icons/FontAwesome'; -import { collectConfusables } from '../../../../util/validators'; +import { collectConfusables, hasZeroWidthPoints } from '../../../../util/validators'; const { hexToBN } = util; const styles = StyleSheet.create({ @@ -133,6 +133,7 @@ const styles = StyleSheet.create({ confusabeError: { display: 'flex', flexDirection: 'row', + justifyContent: 'space-between', margin: 16, padding: 16, borderWidth: 1, @@ -140,6 +141,10 @@ const styles = StyleSheet.create({ backgroundColor: colors.red000, borderRadius: 8 }, + confusabeWarning: { + borderColor: colors.yellow, + backgroundColor: colors.yellow100 + }, confusableTitle: { marginTop: -3, color: colors.red, @@ -149,10 +154,14 @@ const styles = StyleSheet.create({ confusableMsg: { color: colors.red, fontSize: 12, - lineHeight: 16 + lineHeight: 16, + paddingRight: 10 + }, + black: { + color: colors.black }, warningIcon: { - paddingRight: 8 + marginRight: 8 } }); @@ -565,6 +574,8 @@ class SendFlow extends PureComponent { const checksummedAddress = toSelectedAddress && toChecksumAddress(toSelectedAddress); const existingContact = checksummedAddress && addressBook[network] && addressBook[network][checksummedAddress]; const displayConfusableWarning = !existingContact && confusableCollection && !!confusableCollection.length; + const displayAsWarning = + confusableCollection && confusableCollection.length && !confusableCollection.some(hasZeroWidthPoints); return ( @@ -612,15 +623,19 @@ class SendFlow extends PureComponent { )} {displayConfusableWarning && ( - + - + - + {strings('transaction.confusable_title')} - + {strings('transaction.confusable_msg')} From 192bba58073fe65e3f937ea7bab094efa4d7a523 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Mon, 26 Apr 2021 15:52:22 -0400 Subject: [PATCH 24/25] Only display confusables warnings if the user is not in addressbook --- app/components/Views/SendFlow/Confirm/index.js | 18 ++++++++++++++---- app/components/Views/SendFlow/SendTo/index.js | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index 5cb2903b948..5ec24a98e07 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -58,6 +58,7 @@ import Text from '../../../Base/Text'; import AnalyticsV2 from '../../../../util/analyticsV2'; import { collectConfusables } from '../../../../util/validators'; import InfoModal from '../../../UI/Swaps/components/InfoModal'; +import { toChecksumAddress } from 'ethereumjs-util'; const EDIT = 'edit'; const EDIT_NONCE = 'edit_nonce'; @@ -235,6 +236,10 @@ class Confirm extends PureComponent { * Map of accounts to information objects including balances */ accounts: PropTypes.object, + /** + * Map representing the address book + */ + addressBook: PropTypes.object, /** * Object containing token balances in the format address => balance */ @@ -926,7 +931,7 @@ class Confirm extends PureComponent { render = () => { const { transactionToName, selectedAsset, paymentRequest } = this.props.transactionState; - const { showHexData, showCustomNonce, primaryCurrency, network, chainId } = this.props; + const { addressBook, showHexData, showCustomNonce, primaryCurrency, network, chainId } = this.props; const { nonce } = this.props.transaction; const { gasEstimationReady, @@ -949,19 +954,23 @@ class Confirm extends PureComponent { warningModalVisible } = this.state; + const checksummedAddress = transactionTo && toChecksumAddress(transactionTo); + const existingContact = checksummedAddress && addressBook[network] && addressBook[network][checksummedAddress]; + const displayExclamation = !existingContact && !!confusableCollection.length; + const AdressToComponent = () => ( ); const AdressToComponentWrap = () => - confusableCollection.length ? ( + !existingContact && confusableCollection.length ? ( @@ -1085,6 +1094,7 @@ class Confirm extends PureComponent { const mapStateToProps = state => ({ accounts: state.engine.backgroundState.AccountTrackerController.accounts, + addressBook: state.engine.backgroundState.AddressBookController.addressBook, contractBalances: state.engine.backgroundState.TokenBalancesController.contractBalances, contractExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates, currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency, diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js index cc2013280e7..bd0f0b7bd42 100644 --- a/app/components/Views/SendFlow/SendTo/index.js +++ b/app/components/Views/SendFlow/SendTo/index.js @@ -599,7 +599,7 @@ class SendFlow extends PureComponent { onInputBlur={this.onToInputFocus} onSubmit={this.onTransactionDirectionSet} inputWidth={inputWidth} - confusableCollection={confusableCollection} + confusableCollection={(!existingContact && confusableCollection) || []} /> From a7237a2d13e6bc75e6f2e9a5ba530817b8790ed7 Mon Sep 17 00:00:00 2001 From: Ricky Miller Date: Mon, 26 Apr 2021 18:47:38 -0400 Subject: [PATCH 25/25] Add optional chaining for addressBook --- app/components/Views/SendFlow/Confirm/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index 5ec24a98e07..ba9afcd178e 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -1094,7 +1094,7 @@ class Confirm extends PureComponent { const mapStateToProps = state => ({ accounts: state.engine.backgroundState.AccountTrackerController.accounts, - addressBook: state.engine.backgroundState.AddressBookController.addressBook, + addressBook: state.engine.backgroundState.AddressBookController?.addressBook, contractBalances: state.engine.backgroundState.TokenBalancesController.contractBalances, contractExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates, currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency,