diff --git a/.metamaskrc.dist b/.metamaskrc.dist index 8d7507e86..29dbcb2f7 100644 --- a/.metamaskrc.dist +++ b/.metamaskrc.dist @@ -5,3 +5,4 @@ SEGMENT_WRITE_KEY= ONBOARDING_V2= SWAPS_USE_DEV_APIS= COLLECTIBLES_V1= +TOKEN_DETECTION_V2= diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 3b36455be..371e04242 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -3542,9 +3542,15 @@ "tokenDetails": { "message": "Token details" }, + "tokenDetection": { + "message": "Token detection" + }, "tokenDetectionAnnouncement": { "message": "New! Improved token detection is available on Ethereum Mainnet as an experimental feature. $1" }, + "tokenDetectionToggleDescription": { + "message": "ConsenSys’ token API aggregates a list of tokens from various third party token lists. Turning it off will stop detecting new tokens added to your wallet, but will keep the option to search for tokens to import." + }, "tokenId": { "message": "Token ID" }, diff --git a/development/build/scripts.js b/development/build/scripts.js index 063fc5a7e..e02c81519 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -35,6 +35,7 @@ const metamaskrc = require('rc')('metamask', { ONBOARDING_V2: process.env.ONBOARDING_V2, COLLECTIBLES_V1: process.env.COLLECTIBLES_V1, DARK_MODE_V1: process.env.DARK_MODE_V1, + TOKEN_DETECTION_V2: process.env.TOKEN_DETECTION_V2, SEGMENT_HOST: process.env.SEGMENT_HOST, SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY, SEGMENT_BETA_WRITE_KEY: process.env.SEGMENT_BETA_WRITE_KEY, @@ -812,6 +813,7 @@ function getEnvironmentVariables({ buildType, devMode, testing, version }) { ONBOARDING_V2: metamaskrc.ONBOARDING_V2 === '1', COLLECTIBLES_V1: metamaskrc.COLLECTIBLES_V1 === '1', DARK_MODE_V1: metamaskrc.DARK_MODE_V1 === '1', + TOKEN_DETECTION_V2: metamaskrc.TOKEN_DETECTION_V2 === '1', }; } diff --git a/ui/helpers/utils/settings-search.js b/ui/helpers/utils/settings-search.js index 31550eb5c..c9ca6376a 100644 --- a/ui/helpers/utils/settings-search.js +++ b/ui/helpers/utils/settings-search.js @@ -22,7 +22,7 @@ function showHideSettings(t, settings) { } export function getSettingsRoutes(t) { - const settingsRoutesList = [ + let settingsRoutesList = [ { tab: t('general'), section: t('currencyConversion'), @@ -271,14 +271,6 @@ export function getSettingsRoutes(t) { image: 'network-icon.svg', id: 31, }, - { - tab: t('experimental'), - section: t('useTokenDetection'), - description: t('useTokenDetectionDescription'), - route: `${EXPERIMENTAL_ROUTE}#token-description`, - image: 'experimental-icon.svg', - id: 32, - }, { tab: t('experimental'), section: t('enableOpenSeaAPI'), @@ -366,6 +358,34 @@ export function getSettingsRoutes(t) { }, ]; + if (process.env.TOKEN_DETECTION_V2) { + settingsRoutesList = [ + ...settingsRoutesList, + { + /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */ + tab: t('advanced'), + section: t('tokenDetection'), + description: t('tokenDetectionToggleDescription'), + route: `${ADVANCED_ROUTE}#token-description`, + image: 'advanced-icon.svg', + id: 32, + }, + ]; + } else { + settingsRoutesList = [ + ...settingsRoutesList, + { + /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */ + tab: t('experimental'), + section: t('useTokenDetection'), + description: t('useTokenDetectionDescription'), + route: `${EXPERIMENTAL_ROUTE}#token-description`, + image: 'experimental-icon.svg', + id: 32, + }, + ]; + } + // TODO: write to json file? return showHideSettings(t, settingsRoutesList); } diff --git a/ui/helpers/utils/settings-search.test.js b/ui/helpers/utils/settings-search.test.js index c41eaf757..b7249c6af 100644 --- a/ui/helpers/utils/settings-search.test.js +++ b/ui/helpers/utils/settings-search.test.js @@ -111,10 +111,15 @@ const t = (key) => { return 'Localhost 8545'; case 'experimental': return 'Experimental'; + /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */ case 'useTokenDetection': return 'Use Token Detection'; case 'useTokenDetectionDescription': return 'We use third-party APIs to detect and display new tokens sent to your wallet. Turn off if you don’t want MetaMask to pull data from those services.'; + case 'tokenDetection': + return 'Token detection'; + case 'tokenDetectionToggleDescription': + return 'ConsenSys’ token API aggregates a list of tokens from various third party token lists. Turning it off will stop detecting new tokens added to your wallet, but will keep the option to search for tokens to import.'; case 'enableOpenSeaAPI': return 'Enable OpenSea API'; case 'enableOpenSeaAPIDescription': @@ -416,15 +421,6 @@ describe('Settings Search Utils', () => { section: 'Localhost 8545', tab: 'Networks', }, - { - description: - 'We use third-party APIs to detect and display new tokens sent to your wallet. Turn off if you don’t want MetaMask to pull data from those services.', - id: 32, - image: 'experimental-icon.svg', - route: '/settings/experimental#token-description', - section: 'Use Token Detection', - tab: 'Experimental', - }, { description: 'MetaMask is designed and built around the world.', id: 35, @@ -489,6 +485,16 @@ describe('Settings Search Utils', () => { section: 'Contact us', tab: 'About', }, + { + /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */ + description: + 'We use third-party APIs to detect and display new tokens sent to your wallet. Turn off if you don’t want MetaMask to pull data from those services.', + id: 32, + image: 'experimental-icon.svg', + route: '/settings/experimental#token-description', + section: 'Use Token Detection', + tab: 'Experimental', + }, ]; expect(getSettingsRoutes(t)).toStrictEqual(settingsListExcepted); }); diff --git a/ui/pages/settings/advanced-tab/advanced-tab.component.js b/ui/pages/settings/advanced-tab/advanced-tab.component.js index 60bae67da..c0bea1a3f 100644 --- a/ui/pages/settings/advanced-tab/advanced-tab.component.js +++ b/ui/pages/settings/advanced-tab/advanced-tab.component.js @@ -55,6 +55,8 @@ export default class AdvancedTab extends PureComponent { setDismissSeedBackUpReminder: PropTypes.func.isRequired, dismissSeedBackUpReminder: PropTypes.bool.isRequired, userHasALedgerAccount: PropTypes.bool.isRequired, + useTokenDetection: PropTypes.bool.isRequired, + setUseTokenDetection: PropTypes.func.isRequired, }; state = { @@ -669,6 +671,49 @@ export default class AdvancedTab extends PureComponent { ); } + renderTokenDetectionToggle() { + if (!process.env.TOKEN_DETECTION_V2) { + return null; + } + + const { t } = this.context; + const { useTokenDetection, setUseTokenDetection } = this.props; + + return ( +
+
+ {t('tokenDetection')} +
+ {t('tokenDetectionToggleDescription')} +
+
+
+
+ { + this.context.metricsEvent({ + eventOpts: { + category: 'Settings', + action: 'Token Detection', + name: 'Token Detection', + }, + }); + setUseTokenDetection(!value); + }} + offLabel={t('off')} + onLabel={t('on')} + /> +
+
+
+ ); + } + render() { const { warning } = this.props; @@ -681,6 +726,7 @@ export default class AdvancedTab extends PureComponent { {this.renderMobileSync()} {this.renderResetAccount()} {this.renderAdvancedGasInputInline()} + {this.renderTokenDetectionToggle()} {this.renderHexDataOptIn()} {this.renderShowConversionInTestnets()} {this.renderToggleTestNetworks()} diff --git a/ui/pages/settings/advanced-tab/advanced-tab.component.test.js b/ui/pages/settings/advanced-tab/advanced-tab.component.test.js index 0ea5954f5..d3f11735b 100644 --- a/ui/pages/settings/advanced-tab/advanced-tab.component.test.js +++ b/ui/pages/settings/advanced-tab/advanced-tab.component.test.js @@ -10,6 +10,7 @@ describe('AdvancedTab Component', () => { let component; let setAutoLockTimeLimitSpy = sinon.spy(); const toggleTestnet = sinon.spy(); + const toggleTokenDetection = sinon.spy(); beforeAll(() => { component = shallow( @@ -27,6 +28,8 @@ describe('AdvancedTab Component', () => { setLedgerTransportPreference={() => undefined} setDismissSeedBackUpReminder={() => undefined} dismissSeedBackUpReminder={false} + useTokenDetection + setUseTokenDetection={toggleTokenDetection} />, { context: { @@ -80,4 +83,71 @@ describe('AdvancedTab Component', () => { toggleButton.first().simulate('toggle'); expect(toggleTestnet.calledOnce).toStrictEqual(true); }); + + it('should toggle token detection', () => { + process.env.TOKEN_DETECTION_V2 = true; + component = shallow( + undefined} + setShowFiatConversionOnTestnetsPreference={() => undefined} + setThreeBoxSyncingPermission={() => undefined} + setShowTestNetworks={toggleTestnet} + showTestNetworks={false} + threeBoxDisabled + threeBoxSyncingAllowed={false} + ledgerTransportType={LEDGER_TRANSPORT_TYPES.U2F} + setLedgerTransportPreference={() => undefined} + setDismissSeedBackUpReminder={() => undefined} + dismissSeedBackUpReminder={false} + useTokenDetection + setUseTokenDetection={toggleTokenDetection} + />, + { + context: { + metricsEvent: () => undefined, + t: (s) => `_${s}`, + }, + }, + ); + const useTokenDetection = component + .find('.settings-page__content-row') + .at(4); + const toggleButton = useTokenDetection.find(ToggleButton); + toggleButton.first().simulate('toggle'); + expect(toggleTokenDetection.calledOnce).toStrictEqual(true); + }); + + /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */ + it('should not show token detection toggle', () => { + process.env.TOKEN_DETECTION_V2 = false; + component = shallow( + undefined} + setShowFiatConversionOnTestnetsPreference={() => undefined} + setThreeBoxSyncingPermission={() => undefined} + setShowTestNetworks={toggleTestnet} + showTestNetworks={false} + threeBoxDisabled + threeBoxSyncingAllowed={false} + ledgerTransportType={LEDGER_TRANSPORT_TYPES.U2F} + setLedgerTransportPreference={() => undefined} + setDismissSeedBackUpReminder={() => undefined} + dismissSeedBackUpReminder={false} + useTokenDetection + setUseTokenDetection={toggleTokenDetection} + />, + { + context: { + metricsEvent: () => undefined, + t: (s) => `_${s}`, + }, + }, + ); + const tokenDetectionText = component.find({ text: 'Token detection' }); + expect(tokenDetectionText).toHaveLength(0); + }); }); diff --git a/ui/pages/settings/advanced-tab/advanced-tab.container.js b/ui/pages/settings/advanced-tab/advanced-tab.container.js index ed888571a..e545cf327 100644 --- a/ui/pages/settings/advanced-tab/advanced-tab.container.js +++ b/ui/pages/settings/advanced-tab/advanced-tab.container.js @@ -14,6 +14,7 @@ import { setIpfsGateway, setLedgerTransportPreference, setDismissSeedBackUpReminder, + setUseTokenDetection, } from '../../../store/actions'; import { getPreferences } from '../../../selectors'; import { doesUserHaveALedgerAccount } from '../../../ducks/metamask/metamask'; @@ -32,6 +33,7 @@ export const mapStateToProps = (state) => { ipfsGateway, ledgerTransportType, dismissSeedBackUpReminder, + useTokenDetection, } = metamask; const { showFiatInTestnets, @@ -55,6 +57,7 @@ export const mapStateToProps = (state) => { ledgerTransportType, dismissSeedBackUpReminder, userHasALedgerAccount, + useTokenDetection, }; }; @@ -93,6 +96,9 @@ export const mapDispatchToProps = (dispatch) => { setDismissSeedBackUpReminder: (value) => { return dispatch(setDismissSeedBackUpReminder(value)); }, + setUseTokenDetection: (value) => { + return dispatch(setUseTokenDetection(value)); + }, }; }; diff --git a/ui/pages/settings/experimental-tab/experimental-tab.component.js b/ui/pages/settings/experimental-tab/experimental-tab.component.js index 5961432e9..c520ed02b 100644 --- a/ui/pages/settings/experimental-tab/experimental-tab.component.js +++ b/ui/pages/settings/experimental-tab/experimental-tab.component.js @@ -271,7 +271,10 @@ export default class ExperimentalTab extends PureComponent { render() { return (
- {this.renderTokenDetectionToggle()} + {/* TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */} + {process.env.TOKEN_DETECTION_V2 + ? null + : this.renderTokenDetectionToggle()} {this.renderOpenSeaEnabledToggle()} {this.renderCollectibleDetectionToggle()} {this.renderEIP1559V2EnabledToggle()} diff --git a/ui/pages/settings/experimental-tab/experimental-tab.component.test.js b/ui/pages/settings/experimental-tab/experimental-tab.component.test.js index 158e6f76e..12f518012 100644 --- a/ui/pages/settings/experimental-tab/experimental-tab.component.test.js +++ b/ui/pages/settings/experimental-tab/experimental-tab.component.test.js @@ -11,18 +11,28 @@ describe('Experimental Tab', () => { setUseTokenDetection: sinon.spy(), }; - beforeEach(() => { + it('toggles Use Token detection', () => { wrapper = mount(, { context: { t: (str) => str, metricsEvent: () => undefined, }, }); - }); - - it('toggles Use Token detection', () => { const useTokenDetection = wrapper.find({ type: 'checkbox' }).at(0); useTokenDetection.simulate('click'); expect(props.setUseTokenDetection.calledOnce).toStrictEqual(true); }); + + /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */ + it('should not show use token detection toggle', () => { + process.env.TOKEN_DETECTION_V2 = true; + wrapper = mount(, { + context: { + t: (str) => str, + metricsEvent: () => undefined, + }, + }); + const useTokenDetectionText = wrapper.find({ text: 'Use Token Detection' }); + expect(useTokenDetectionText).toHaveLength(0); + }); }); diff --git a/ui/pages/settings/experimental-tab/experimental-tab.container.js b/ui/pages/settings/experimental-tab/experimental-tab.container.js index c9a6d3b74..1fb4124ee 100644 --- a/ui/pages/settings/experimental-tab/experimental-tab.container.js +++ b/ui/pages/settings/experimental-tab/experimental-tab.container.js @@ -19,7 +19,9 @@ import ExperimentalTab from './experimental-tab.component'; const mapStateToProps = (state) => { return { - useTokenDetection: getUseTokenDetection(state), + useTokenDetection: getUseTokenDetection( + state, + ) /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */, useCollectibleDetection: getUseCollectibleDetection(state), openSeaEnabled: getOpenSeaEnabled(state), eip1559V2Enabled: getEIP1559V2Enabled(state), @@ -29,7 +31,10 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return { - setUseTokenDetection: (val) => dispatch(setUseTokenDetection(val)), + setUseTokenDetection: (val) => + dispatch( + setUseTokenDetection(val), + ) /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */, setUseCollectibleDetection: (val) => dispatch(setUseCollectibleDetection(val)), setOpenSeaEnabled: (val) => dispatch(setOpenSeaEnabled(val)),