From 3feccf94de3b275342fec10f6e3a3fc678793ff5 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Fri, 21 Nov 2025 16:27:32 +0530 Subject: [PATCH 01/30] added pna-25 banner --- app/_locales/en/messages.json | 8 ++- app/_locales/en_GB/messages.json | 8 ++- .../controllers/app-state-controller.ts | 28 ++++++++++ ui/components/app/toast-master/selectors.ts | 52 +++++++++++++++++++ .../app/toast-master/toast-master.js | 36 +++++++++++++ ui/components/app/toast-master/utils.ts | 8 +++ 6 files changed, 138 insertions(+), 2 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index d9ceae9d7efc..896ec3fd48ee 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -4145,7 +4145,13 @@ "message": "Read more" }, "newPrivacyPolicyTitle": { - "message": "We’ve updated our privacy policy" + "message": "We've updated our privacy policy" + }, + "pna25BannerActionButton": { + "message": "Learn more" + }, + "pna25BannerTitle": { + "message": "We're updating MetaMetrics. We'll associate some in-app activity to on-chain data to better understand usage. You can opt out via Settings." }, "newRpcUrl": { "message": "New RPC URL" diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index d9ceae9d7efc..896ec3fd48ee 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -4145,7 +4145,13 @@ "message": "Read more" }, "newPrivacyPolicyTitle": { - "message": "We’ve updated our privacy policy" + "message": "We've updated our privacy policy" + }, + "pna25BannerActionButton": { + "message": "Learn more" + }, + "pna25BannerTitle": { + "message": "We're updating MetaMetrics. We'll associate some in-app activity to on-chain data to better understand usage. You can opt out via Settings." }, "newRpcUrl": { "message": "New RPC URL" diff --git a/app/scripts/controllers/app-state-controller.ts b/app/scripts/controllers/app-state-controller.ts index 960a1b81e005..21e22bbc158e 100644 --- a/app/scripts/controllers/app-state-controller.ts +++ b/app/scripts/controllers/app-state-controller.ts @@ -101,6 +101,8 @@ export type AppStateControllerState = { networkConnectionBanner: NetworkConnectionBanner; newPrivacyPolicyToastClickedOrClosed: boolean | null; newPrivacyPolicyToastShownDate: number | null; + pna25BannerClickedOrClosed: boolean | null; + pna25BannerDismissedDate: number | null; nftsDetectionNoticeDismissed: boolean; nftsDropdownState: Json; notificationGasPollTokens: string[]; @@ -269,6 +271,8 @@ const getDefaultAppStateControllerState = (): AppStateControllerState => ({ lastViewedUserSurvey: null, newPrivacyPolicyToastClickedOrClosed: null, newPrivacyPolicyToastShownDate: null, + pna25BannerClickedOrClosed: null, + pna25BannerDismissedDate: null, nftsDetectionNoticeDismissed: false, notificationGasPollTokens: [], onboardingDate: null, @@ -466,6 +470,18 @@ const controllerMetadata: StateMetadata = { includeInDebugSnapshot: true, usedInUi: true, }, + pna25BannerClickedOrClosed: { + includeInStateLogs: true, + persist: true, + includeInDebugSnapshot: true, + usedInUi: true, + }, + pna25BannerDismissedDate: { + includeInStateLogs: true, + persist: true, + includeInDebugSnapshot: true, + usedInUi: true, + }, nftsDetectionNoticeDismissed: { includeInStateLogs: true, persist: true, @@ -874,6 +890,18 @@ export class AppStateController extends BaseController< }); } + setPna25BannerClickedOrClosed(): void { + this.update((state) => { + state.pna25BannerClickedOrClosed = true; + }); + } + + setPna25BannerDismissedDate(time: number): void { + this.update((state) => { + state.pna25BannerDismissedDate = time; + }); + } + setShieldPausedToastLastClickedOrClosed(time: number): void { this.update((state) => { state.shieldPausedToastLastClickedOrClosed = time; diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index 671e4e61ad88..d3018b8d79ff 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -17,6 +17,7 @@ import { getPermissions, isSolanaAccount, } from '../../../selectors'; +import { getRemoteFeatureFlags } from '../../../selectors/remote-feature-flags'; import { MetaMaskReduxState } from '../../../store/store'; import { PasswordChangeToastType, @@ -47,6 +48,9 @@ type State = { | 'surveyLinkLastClickedOrClosed' | 'shieldEndingToastLastClickedOrClosed' | 'shieldPausedToastLastClickedOrClosed' + | 'pna25BannerClickedOrClosed' + | 'participateInMetaMetrics' + | 'remoteFeatureFlags' > >; }; @@ -234,3 +238,51 @@ export function selectShowShieldEndingToast( ): boolean { return !state.metamask.shieldEndingToastLastClickedOrClosed; } + +/** + * Determines if the PNA25 banner should be shown based on: + * - LaunchDarkly feature flag (extension-ux-pna25) is enabled + * - User has opted into metrics (participateInMetaMetrics === true) + * - User onboarded before the PNA25 release date + * - User hasn't dismissed the banner + * + * @param state - The application state containing PNA25 banner data. + * @returns Object with showPna25Banner boolean and pna25BannerShownDate + */ +export function selectShowPna25Banner(state: Pick): boolean { + const { + pna25BannerClickedOrClosed, + onboardingDate, + participateInMetaMetrics, + } = state.metamask || {}; + + // Get the feature flag from LaunchDarkly + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const remoteFeatureFlags = getRemoteFeatureFlags(state as any); + const pna25Timestamp = remoteFeatureFlags?.['extension-ux-pna25']; + + const currentDate = Date.now(); + + // Check all conditions + if (!pna25Timestamp || typeof pna25Timestamp !== 'number') { + return false; // LD flag not set or invalid + } + + if (participateInMetaMetrics !== true) { + return false; // User hasn't opted into metrics + } + + if (pna25BannerClickedOrClosed) { + return false; // User already dismissed banner + } + + if (currentDate < pna25Timestamp) { + return false; // PNA25 date hasn't passed yet + } + + if (!onboardingDate || onboardingDate >= pna25Timestamp) { + return false; // User onboarded after PNA25 or no onboarding date + } + + return true; +} diff --git a/ui/components/app/toast-master/toast-master.js b/ui/components/app/toast-master/toast-master.js index 268b90621069..3a1d731ea81f 100644 --- a/ui/components/app/toast-master/toast-master.js +++ b/ui/components/app/toast-master/toast-master.js @@ -94,6 +94,7 @@ import { selectClaimSubmitToast, selectShowShieldPausedToast, selectShowShieldEndingToast, + selectShowPna25Banner, } from './selectors'; import { setNewPrivacyPolicyToastClickedOrClosed, @@ -106,6 +107,8 @@ import { setShowClaimSubmitToast, setShieldPausedToastLastClickedOrClosed, setShieldEndingToastLastClickedOrClosed, + setPna25BannerClickedOrClosed, + setPna25BannerDismissedDate, } from './utils'; export function ToastMaster({ location } = {}) { @@ -129,6 +132,7 @@ export function ToastMaster({ location } = {}) { )} + @@ -749,3 +753,35 @@ function ShieldEndingToast() { ) ); } + +function Pna25Banner() { + const t = useI18nContext(); + + const showPna25Banner = useSelector(selectShowPna25Banner); + + const handleDismiss = () => { + setPna25BannerClickedOrClosed(); + setPna25BannerDismissedDate(Date.now()); + }; + + return ( + showPna25Banner && ( + + } + text={t('pna25BannerTitle')} + actionText={t('pna25BannerActionButton')} + onActionClick={() => { + global.platform.openTab({ + url: PRIVACY_POLICY_LINK, + }); + handleDismiss(); + }} + onClose={handleDismiss} + /> + ) + ); +} diff --git a/ui/components/app/toast-master/utils.ts b/ui/components/app/toast-master/utils.ts index e5a905f6bdac..a9b288b7ccc7 100644 --- a/ui/components/app/toast-master/utils.ts +++ b/ui/components/app/toast-master/utils.ts @@ -117,3 +117,11 @@ export function setShieldEndingToastLastClickedOrClosed(time: number) { time, ]); } + +export function setPna25BannerClickedOrClosed() { + submitRequestToBackgroundAndCatch('setPna25BannerClickedOrClosed'); +} + +export function setPna25BannerDismissedDate(time: number) { + submitRequestToBackgroundAndCatch('setPna25BannerDismissedDate', [time]); +} From 85cc83d18d04e5697f7dc0d84fde85b039684c11 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Mon, 24 Nov 2025 17:13:33 +0530 Subject: [PATCH 02/30] added pna25 banner to use boolean logic --- app/_locales/en/messages.json | 5 ++- app/_locales/en_GB/messages.json | 5 ++- .../controllers/app-state-controller.ts | 14 +++++++ app/scripts/metamask-controller.js | 4 ++ ui/components/app/toast-master/selectors.ts | 41 +++++++------------ .../app/toast-master/toast-master.js | 26 ++++++------ ui/components/app/toast-master/utils.ts | 8 +--- .../metametrics/metametrics.js | 28 ++++++++++++- 8 files changed, 84 insertions(+), 47 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 896ec3fd48ee..2d023bd5cbc5 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -4540,7 +4540,10 @@ "message": "Terms of Use" }, "onboardingMetametricCheckboxDescriptionOne": { - "message": "We’ll collect basic product usage data like general location, clicks, and views. No other information will be stored." + "message": "We'll collect basic product usage data like general location, clicks, and views. No other information will be stored." + }, + "onboardingMetametricCheckboxDescriptionOneUpdated": { + "message": "We'll collect basic product usage data. We may associate this information with on-chain data." }, "onboardingMetametricCheckboxDescriptionTwo": { "message": "We’ll use this data to learn how you interact with our marketing communications. We may share relevant news (like product features)." diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 896ec3fd48ee..2d023bd5cbc5 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -4540,7 +4540,10 @@ "message": "Terms of Use" }, "onboardingMetametricCheckboxDescriptionOne": { - "message": "We’ll collect basic product usage data like general location, clicks, and views. No other information will be stored." + "message": "We'll collect basic product usage data like general location, clicks, and views. No other information will be stored." + }, + "onboardingMetametricCheckboxDescriptionOneUpdated": { + "message": "We'll collect basic product usage data. We may associate this information with on-chain data." }, "onboardingMetametricCheckboxDescriptionTwo": { "message": "We’ll use this data to learn how you interact with our marketing communications. We may share relevant news (like product features)." diff --git a/app/scripts/controllers/app-state-controller.ts b/app/scripts/controllers/app-state-controller.ts index 21e22bbc158e..cd158fcd9950 100644 --- a/app/scripts/controllers/app-state-controller.ts +++ b/app/scripts/controllers/app-state-controller.ts @@ -103,6 +103,7 @@ export type AppStateControllerState = { newPrivacyPolicyToastShownDate: number | null; pna25BannerClickedOrClosed: boolean | null; pna25BannerDismissedDate: number | null; + pna25Acknowledged: boolean | null; nftsDetectionNoticeDismissed: boolean; nftsDropdownState: Json; notificationGasPollTokens: string[]; @@ -273,6 +274,7 @@ const getDefaultAppStateControllerState = (): AppStateControllerState => ({ newPrivacyPolicyToastShownDate: null, pna25BannerClickedOrClosed: null, pna25BannerDismissedDate: null, + pna25Acknowledged: null, nftsDetectionNoticeDismissed: false, notificationGasPollTokens: [], onboardingDate: null, @@ -482,6 +484,12 @@ const controllerMetadata: StateMetadata = { includeInDebugSnapshot: true, usedInUi: true, }, + pna25Acknowledged: { + includeInStateLogs: true, + persist: true, + includeInDebugSnapshot: true, + usedInUi: true, + }, nftsDetectionNoticeDismissed: { includeInStateLogs: true, persist: true, @@ -902,6 +910,12 @@ export class AppStateController extends BaseController< }); } + setPna25Acknowledged(acknowledged: boolean): void { + this.update((state) => { + state.pna25Acknowledged = acknowledged; + }); + } + setShieldPausedToastLastClickedOrClosed(time: number): void { this.update((state) => { state.shieldPausedToastLastClickedOrClosed = time; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 09ff4713591c..0125bb92bd11 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2955,6 +2955,10 @@ export default class MetamaskController extends EventEmitter { appStateController.setShieldEndingToastLastClickedOrClosed.bind( appStateController, ), + setPna25Acknowledged: + appStateController.setPna25Acknowledged.bind( + appStateController, + ), setAppActiveTab: appStateController.setAppActiveTab.bind(appStateController), setDefaultSubscriptionPaymentOptions: diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index d3018b8d79ff..1f82143134c2 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -48,9 +48,9 @@ type State = { | 'surveyLinkLastClickedOrClosed' | 'shieldEndingToastLastClickedOrClosed' | 'shieldPausedToastLastClickedOrClosed' - | 'pna25BannerClickedOrClosed' | 'participateInMetaMetrics' | 'remoteFeatureFlags' + | 'pna25Acknowledged' > >; }; @@ -241,48 +241,37 @@ export function selectShowShieldEndingToast( /** * Determines if the PNA25 banner should be shown based on: - * - LaunchDarkly feature flag (extension-ux-pna25) is enabled + * - LaunchDarkly feature flag (extension-ux-pna25) is enabled (boolean) * - User has opted into metrics (participateInMetaMetrics === true) - * - User onboarded before the PNA25 release date - * - User hasn't dismissed the banner + * - User hasn't acknowledged the new feature yet (pna25Acknowledged !== true) * - * @param state - The application state containing PNA25 banner data. - * @returns Object with showPna25Banner boolean and pna25BannerShownDate + * @param state - The application state containing the banner data. + * @returns Boolean indicating whether to show the banner */ export function selectShowPna25Banner(state: Pick): boolean { - const { - pna25BannerClickedOrClosed, - onboardingDate, - participateInMetaMetrics, - } = state.metamask || {}; + const { participateInMetaMetrics, pna25Acknowledged } = state.metamask || {}; // Get the feature flag from LaunchDarkly + // extension-ux-pna25 is now a boolean, not a timestamp // eslint-disable-next-line @typescript-eslint/no-explicit-any const remoteFeatureFlags = getRemoteFeatureFlags(state as any); - const pna25Timestamp = remoteFeatureFlags?.['extension-ux-pna25']; - - const currentDate = Date.now(); + const isMetametricsOnchainDataEnabled = Boolean( + remoteFeatureFlags?.['extension-ux-pna25'], + ); // Check all conditions - if (!pna25Timestamp || typeof pna25Timestamp !== 'number') { - return false; // LD flag not set or invalid + if (!isMetametricsOnchainDataEnabled) { + return false; // LD flag not enabled } if (participateInMetaMetrics !== true) { return false; // User hasn't opted into metrics } - if (pna25BannerClickedOrClosed) { - return false; // User already dismissed banner - } - - if (currentDate < pna25Timestamp) { - return false; // PNA25 date hasn't passed yet - } - - if (!onboardingDate || onboardingDate >= pna25Timestamp) { - return false; // User onboarded after PNA25 or no onboarding date + if (pna25Acknowledged === true) { + return false; // User already acknowledged } + // Show banner if user opted in before but hasn't acknowledged the new policy return true; } diff --git a/ui/components/app/toast-master/toast-master.js b/ui/components/app/toast-master/toast-master.js index 3a1d731ea81f..f7e758f851d7 100644 --- a/ui/components/app/toast-master/toast-master.js +++ b/ui/components/app/toast-master/toast-master.js @@ -107,8 +107,7 @@ import { setShowClaimSubmitToast, setShieldPausedToastLastClickedOrClosed, setShieldEndingToastLastClickedOrClosed, - setPna25BannerClickedOrClosed, - setPna25BannerDismissedDate, + setPna25Acknowledged, } from './utils'; export function ToastMaster({ location } = {}) { @@ -759,9 +758,17 @@ function Pna25Banner() { const showPna25Banner = useSelector(selectShowPna25Banner); - const handleDismiss = () => { - setPna25BannerClickedOrClosed(); - setPna25BannerDismissedDate(Date.now()); + const handleLearnMore = () => { + // Open privacy policy link and acknowledge + global.platform.openTab({ + url: PRIVACY_POLICY_LINK, + }); + setPna25Acknowledged(true); + }; + + const handleClose = () => { + // Just acknowledge without opening link + setPna25Acknowledged(true); }; return ( @@ -774,13 +781,8 @@ function Pna25Banner() { } text={t('pna25BannerTitle')} actionText={t('pna25BannerActionButton')} - onActionClick={() => { - global.platform.openTab({ - url: PRIVACY_POLICY_LINK, - }); - handleDismiss(); - }} - onClose={handleDismiss} + onActionClick={handleLearnMore} + onClose={handleClose} /> ) ); diff --git a/ui/components/app/toast-master/utils.ts b/ui/components/app/toast-master/utils.ts index a9b288b7ccc7..a8ad631849dc 100644 --- a/ui/components/app/toast-master/utils.ts +++ b/ui/components/app/toast-master/utils.ts @@ -118,10 +118,6 @@ export function setShieldEndingToastLastClickedOrClosed(time: number) { ]); } -export function setPna25BannerClickedOrClosed() { - submitRequestToBackgroundAndCatch('setPna25BannerClickedOrClosed'); -} - -export function setPna25BannerDismissedDate(time: number) { - submitRequestToBackgroundAndCatch('setPna25BannerDismissedDate', [time]); +export function setPna25Acknowledged(acknowledged: boolean) { + submitRequestToBackgroundAndCatch('setPna25Acknowledged', [acknowledged]); } diff --git a/ui/pages/onboarding-flow/metametrics/metametrics.js b/ui/pages/onboarding-flow/metametrics/metametrics.js index 915e2fb04bae..1b4bd761ef6a 100644 --- a/ui/pages/onboarding-flow/metametrics/metametrics.js +++ b/ui/pages/onboarding-flow/metametrics/metametrics.js @@ -29,6 +29,7 @@ import { getIsParticipateInMetaMetricsSet, getParticipateInMetaMetrics, } from '../../../selectors'; +import { getRemoteFeatureFlags } from '../../../selectors/remote-feature-flags'; import { MetaMetricsEventCategory, @@ -50,6 +51,7 @@ import { } from '../../../components/component-library'; import { FirstTimeFlowType } from '../../../../shared/constants/onboarding'; import { getBrowserName } from '../../../../shared/modules/browser-runtime.utils'; +import { submitRequestToBackgroundAndCatch } from '../../../components/app/toast-master/utils'; const isFirefox = getBrowserName() === PLATFORM_FIREFOX; @@ -65,6 +67,13 @@ export default function OnboardingMetametrics() { ); const participateInMetaMetrics = useSelector(getParticipateInMetaMetrics); const dataCollectionForMarketing = useSelector(getDataCollectionForMarketing); + const remoteFeatureFlags = useSelector(getRemoteFeatureFlags); + + // Check if the MetaMetrics on-chain data collection feature is enabled + // extension-ux-pna25 is now a boolean, not a timestamp + const isMetametricsOnchainDataEnabled = Boolean( + remoteFeatureFlags?.['extension-ux-pna25'], + ); const [ isParticipateInMetaMetricsChecked, @@ -118,6 +127,14 @@ export default function OnboardingMetametrics() { ); dispatch(setParticipateInMetaMetrics(true)); + // If LD flag is enabled, set pna25Acknowledged to true + // This means they saw the updated policy during onboarding + if (isMetametricsOnchainDataEnabled) { + await submitRequestToBackgroundAndCatch('setPna25Acknowledged', [ + true, + ]); + } + await trackEvent({ category: MetaMetricsEventCategory.Onboarding, event: MetaMetricsEventName.AppInstalled, @@ -134,6 +151,13 @@ export default function OnboardingMetametrics() { } else { dispatch(setParticipateInMetaMetrics(false)); dispatch(setDataCollectionForMarketing(false)); + + // If user opts out, set to false (no need to show banner later) + if (isMetametricsOnchainDataEnabled) { + await submitRequestToBackgroundAndCatch('setPna25Acknowledged', [ + false, + ]); + } } } catch (error) { log.error('onConfirm::error', error); @@ -229,7 +253,9 @@ export default function OnboardingMetametrics() { color={TextColor.textAlternative} textAlign={TextAlign.Left} > - {t('onboardingMetametricCheckboxDescriptionOne')} + {isMetametricsOnchainDataEnabled + ? t('onboardingMetametricCheckboxDescriptionOneUpdated') + : t('onboardingMetametricCheckboxDescriptionOne')} From 26e9ed265de2220f8a8905897442bef5aacd4db0 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Mon, 24 Nov 2025 17:17:31 +0530 Subject: [PATCH 03/30] updated selector --- .../controllers/app-state-controller.ts | 28 ------------------- ui/components/app/toast-master/selectors.ts | 8 ++---- .../metametrics/metametrics.js | 14 ++++------ 3 files changed, 9 insertions(+), 41 deletions(-) diff --git a/app/scripts/controllers/app-state-controller.ts b/app/scripts/controllers/app-state-controller.ts index cd158fcd9950..64223dbbe80e 100644 --- a/app/scripts/controllers/app-state-controller.ts +++ b/app/scripts/controllers/app-state-controller.ts @@ -101,8 +101,6 @@ export type AppStateControllerState = { networkConnectionBanner: NetworkConnectionBanner; newPrivacyPolicyToastClickedOrClosed: boolean | null; newPrivacyPolicyToastShownDate: number | null; - pna25BannerClickedOrClosed: boolean | null; - pna25BannerDismissedDate: number | null; pna25Acknowledged: boolean | null; nftsDetectionNoticeDismissed: boolean; nftsDropdownState: Json; @@ -272,8 +270,6 @@ const getDefaultAppStateControllerState = (): AppStateControllerState => ({ lastViewedUserSurvey: null, newPrivacyPolicyToastClickedOrClosed: null, newPrivacyPolicyToastShownDate: null, - pna25BannerClickedOrClosed: null, - pna25BannerDismissedDate: null, pna25Acknowledged: null, nftsDetectionNoticeDismissed: false, notificationGasPollTokens: [], @@ -472,18 +468,6 @@ const controllerMetadata: StateMetadata = { includeInDebugSnapshot: true, usedInUi: true, }, - pna25BannerClickedOrClosed: { - includeInStateLogs: true, - persist: true, - includeInDebugSnapshot: true, - usedInUi: true, - }, - pna25BannerDismissedDate: { - includeInStateLogs: true, - persist: true, - includeInDebugSnapshot: true, - usedInUi: true, - }, pna25Acknowledged: { includeInStateLogs: true, persist: true, @@ -898,18 +882,6 @@ export class AppStateController extends BaseController< }); } - setPna25BannerClickedOrClosed(): void { - this.update((state) => { - state.pna25BannerClickedOrClosed = true; - }); - } - - setPna25BannerDismissedDate(time: number): void { - this.update((state) => { - state.pna25BannerDismissedDate = time; - }); - } - setPna25Acknowledged(acknowledged: boolean): void { this.update((state) => { state.pna25Acknowledged = acknowledged; diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index 1f82143134c2..e1e23dbf0a81 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -252,15 +252,13 @@ export function selectShowPna25Banner(state: Pick): boolean { const { participateInMetaMetrics, pna25Acknowledged } = state.metamask || {}; // Get the feature flag from LaunchDarkly - // extension-ux-pna25 is now a boolean, not a timestamp + // extension-ux-pna25 is a boolean flag // eslint-disable-next-line @typescript-eslint/no-explicit-any const remoteFeatureFlags = getRemoteFeatureFlags(state as any); - const isMetametricsOnchainDataEnabled = Boolean( - remoteFeatureFlags?.['extension-ux-pna25'], - ); + const isPna25Enabled = Boolean(remoteFeatureFlags?.['extension-ux-pna25']); // Check all conditions - if (!isMetametricsOnchainDataEnabled) { + if (!isPna25Enabled) { return false; // LD flag not enabled } diff --git a/ui/pages/onboarding-flow/metametrics/metametrics.js b/ui/pages/onboarding-flow/metametrics/metametrics.js index 1b4bd761ef6a..b519ae707fa9 100644 --- a/ui/pages/onboarding-flow/metametrics/metametrics.js +++ b/ui/pages/onboarding-flow/metametrics/metametrics.js @@ -69,11 +69,9 @@ export default function OnboardingMetametrics() { const dataCollectionForMarketing = useSelector(getDataCollectionForMarketing); const remoteFeatureFlags = useSelector(getRemoteFeatureFlags); - // Check if the MetaMetrics on-chain data collection feature is enabled - // extension-ux-pna25 is now a boolean, not a timestamp - const isMetametricsOnchainDataEnabled = Boolean( - remoteFeatureFlags?.['extension-ux-pna25'], - ); + // Check if the PNA25 feature is enabled + // extension-ux-pna25 is a boolean LaunchDarkly flag + const isPna25Enabled = Boolean(remoteFeatureFlags?.['extension-ux-pna25']); const [ isParticipateInMetaMetricsChecked, @@ -129,7 +127,7 @@ export default function OnboardingMetametrics() { // If LD flag is enabled, set pna25Acknowledged to true // This means they saw the updated policy during onboarding - if (isMetametricsOnchainDataEnabled) { + if (isPna25Enabled) { await submitRequestToBackgroundAndCatch('setPna25Acknowledged', [ true, ]); @@ -153,7 +151,7 @@ export default function OnboardingMetametrics() { dispatch(setDataCollectionForMarketing(false)); // If user opts out, set to false (no need to show banner later) - if (isMetametricsOnchainDataEnabled) { + if (isPna25Enabled) { await submitRequestToBackgroundAndCatch('setPna25Acknowledged', [ false, ]); @@ -253,7 +251,7 @@ export default function OnboardingMetametrics() { color={TextColor.textAlternative} textAlign={TextAlign.Left} > - {isMetametricsOnchainDataEnabled + {isPna25Enabled ? t('onboardingMetametricCheckboxDescriptionOneUpdated') : t('onboardingMetametricCheckboxDescriptionOne')} From 1bfb72b68839576bc411448f5480ecc8ffa56ebc Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Mon, 24 Nov 2025 17:30:30 +0530 Subject: [PATCH 04/30] show banner --- ui/components/app/toast-master/selectors.ts | 17 ++++++++++++---- .../metametrics/metametrics.js | 20 ++++++------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index e1e23dbf0a81..ca43b037e6fa 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -51,6 +51,7 @@ type State = { | 'participateInMetaMetrics' | 'remoteFeatureFlags' | 'pna25Acknowledged' + | 'completedOnboarding' > >; }; @@ -241,17 +242,25 @@ export function selectShowShieldEndingToast( /** * Determines if the PNA25 banner should be shown based on: + * - User has completed onboarding (completedOnboarding === true) * - LaunchDarkly feature flag (extension-ux-pna25) is enabled (boolean) * - User has opted into metrics (participateInMetaMetrics === true) - * - User hasn't acknowledged the new feature yet (pna25Acknowledged !== true) + * - User is an EXISTING user (pna25Acknowledged === null) + * New users will have pna25Acknowledged = true (opted in) or false (opted out) + * Existing users will have pna25Acknowledged = null (state didn't exist when they onboarded) * * @param state - The application state containing the banner data. * @returns Boolean indicating whether to show the banner */ export function selectShowPna25Banner(state: Pick): boolean { - const { participateInMetaMetrics, pna25Acknowledged } = state.metamask || {}; + const { completedOnboarding, participateInMetaMetrics, pna25Acknowledged } = + state.metamask || {}; + + // Only show to users who have completed onboarding + if (!completedOnboarding) { + return false; // User hasn't completed onboarding yet + } - // Get the feature flag from LaunchDarkly // extension-ux-pna25 is a boolean flag // eslint-disable-next-line @typescript-eslint/no-explicit-any const remoteFeatureFlags = getRemoteFeatureFlags(state as any); @@ -270,6 +279,6 @@ export function selectShowPna25Banner(state: Pick): boolean { return false; // User already acknowledged } - // Show banner if user opted in before but hasn't acknowledged the new policy + // Show banner only for existing users who opted in before this feature return true; } diff --git a/ui/pages/onboarding-flow/metametrics/metametrics.js b/ui/pages/onboarding-flow/metametrics/metametrics.js index b519ae707fa9..3e96a40c544f 100644 --- a/ui/pages/onboarding-flow/metametrics/metametrics.js +++ b/ui/pages/onboarding-flow/metametrics/metametrics.js @@ -125,14 +125,6 @@ export default function OnboardingMetametrics() { ); dispatch(setParticipateInMetaMetrics(true)); - // If LD flag is enabled, set pna25Acknowledged to true - // This means they saw the updated policy during onboarding - if (isPna25Enabled) { - await submitRequestToBackgroundAndCatch('setPna25Acknowledged', [ - true, - ]); - } - await trackEvent({ category: MetaMetricsEventCategory.Onboarding, event: MetaMetricsEventName.AppInstalled, @@ -149,13 +141,13 @@ export default function OnboardingMetametrics() { } else { dispatch(setParticipateInMetaMetrics(false)); dispatch(setDataCollectionForMarketing(false)); + } - // If user opts out, set to false (no need to show banner later) - if (isPna25Enabled) { - await submitRequestToBackgroundAndCatch('setPna25Acknowledged', [ - false, - ]); - } + // If LD flag is enabled, set pna25Acknowledged to true + // This means they saw the updated policy during onboarding (whether they opted in or out) + // No need to show banner to new users who already saw the updated message + if (isPna25Enabled) { + await submitRequestToBackgroundAndCatch('setPna25Acknowledged', [true]); } } catch (error) { log.error('onConfirm::error', error); From 82adc6d6127a9b4ebc6d4788452a33594a567da6 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Mon, 24 Nov 2025 17:37:22 +0530 Subject: [PATCH 05/30] nit fix --- ui/components/app/toast-master/selectors.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index ca43b037e6fa..ad3a2875e8d5 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -261,10 +261,9 @@ export function selectShowPna25Banner(state: Pick): boolean { return false; // User hasn't completed onboarding yet } - // extension-ux-pna25 is a boolean flag // eslint-disable-next-line @typescript-eslint/no-explicit-any const remoteFeatureFlags = getRemoteFeatureFlags(state as any); - const isPna25Enabled = Boolean(remoteFeatureFlags?.['extension-ux-pna25']); + const isPna25Enabled = remoteFeatureFlags?.extensionUxPna25; // Check all conditions if (!isPna25Enabled) { From 330dfdbfd943a71e27316183cbab9c79c1d48b05 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Mon, 24 Nov 2025 17:41:58 +0530 Subject: [PATCH 06/30] locale update --- app/_locales/en/messages.json | 15 ++++++--------- app/_locales/en_GB/messages.json | 11 ++++------- ui/components/app/toast-master/toast-master.js | 2 +- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2d023bd5cbc5..0d0093de28cf 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -4145,13 +4145,7 @@ "message": "Read more" }, "newPrivacyPolicyTitle": { - "message": "We've updated our privacy policy" - }, - "pna25BannerActionButton": { - "message": "Learn more" - }, - "pna25BannerTitle": { - "message": "We're updating MetaMetrics. We'll associate some in-app activity to on-chain data to better understand usage. You can opt out via Settings." + "message": "We’ve updated our privacy policy" }, "newRpcUrl": { "message": "New RPC URL" @@ -4540,10 +4534,10 @@ "message": "Terms of Use" }, "onboardingMetametricCheckboxDescriptionOne": { - "message": "We'll collect basic product usage data like general location, clicks, and views. No other information will be stored." + "message": "We’ll collect basic product usage data like general location, clicks, and views. No other information will be stored." }, "onboardingMetametricCheckboxDescriptionOneUpdated": { - "message": "We'll collect basic product usage data. We may associate this information with on-chain data." + "message": "We’ll collect basic product usage data. We may associate this information with on-chain data." }, "onboardingMetametricCheckboxDescriptionTwo": { "message": "We’ll use this data to learn how you interact with our marketing communications. We may share relevant news (like product features)." @@ -5063,6 +5057,9 @@ "message": "+ $1 more", "description": "$1 is a number of additional but unshown items in a list- this message will be shown in place of those items" }, + "pna25BannerTitle": { + "message": "We’re updating MetaMetrics. We'll associate some in-app activity to on-chain data to better understand usage. You can opt out via Settings." + }, "popularNetworkAddToolTip": { "message": "Some of these networks rely on third parties. The connections may be less reliable or enable third-parties to track activity.", "description": "Learn more link" diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 2d023bd5cbc5..5c7a646eb1b2 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -4145,13 +4145,10 @@ "message": "Read more" }, "newPrivacyPolicyTitle": { - "message": "We've updated our privacy policy" - }, - "pna25BannerActionButton": { - "message": "Learn more" + "message": "We’ve updated our privacy policy" }, "pna25BannerTitle": { - "message": "We're updating MetaMetrics. We'll associate some in-app activity to on-chain data to better understand usage. You can opt out via Settings." + "message": "We’re updating MetaMetrics. We'll associate some in-app activity to on-chain data to better understand usage. You can opt out via Settings." }, "newRpcUrl": { "message": "New RPC URL" @@ -4540,10 +4537,10 @@ "message": "Terms of Use" }, "onboardingMetametricCheckboxDescriptionOne": { - "message": "We'll collect basic product usage data like general location, clicks, and views. No other information will be stored." + "message": "We’ll collect basic product usage data like general location, clicks, and views. No other information will be stored." }, "onboardingMetametricCheckboxDescriptionOneUpdated": { - "message": "We'll collect basic product usage data. We may associate this information with on-chain data." + "message": "We’ll collect basic product usage data. We may associate this information with on-chain data." }, "onboardingMetametricCheckboxDescriptionTwo": { "message": "We’ll use this data to learn how you interact with our marketing communications. We may share relevant news (like product features)." diff --git a/ui/components/app/toast-master/toast-master.js b/ui/components/app/toast-master/toast-master.js index f7e758f851d7..5c09abfb76f4 100644 --- a/ui/components/app/toast-master/toast-master.js +++ b/ui/components/app/toast-master/toast-master.js @@ -780,7 +780,7 @@ function Pna25Banner() { } text={t('pna25BannerTitle')} - actionText={t('pna25BannerActionButton')} + actionText={t('learnMoreUpperCase')} onActionClick={handleLearnMore} onClose={handleClose} /> From d270e720c0712315c66599dd4a19054344e72777 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Mon, 24 Nov 2025 17:46:10 +0530 Subject: [PATCH 07/30] updated metametrics link --- shared/lib/ui-utils.js | 2 ++ ui/components/app/toast-master/toast-master.js | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/shared/lib/ui-utils.js b/shared/lib/ui-utils.js index 19766c9618e4..e8a82a53282c 100644 --- a/shared/lib/ui-utils.js +++ b/shared/lib/ui-utils.js @@ -5,6 +5,8 @@ export const SUPPORT_LINK = process.env.SUPPORT_LINK; export const COINGECKO_LINK = 'https://www.coingecko.com/'; export const CRYPTOCOMPARE_LINK = 'https://www.cryptocompare.com/'; export const PRIVACY_POLICY_LINK = 'https://consensys.io/privacy-policy/'; +export const METAMETRICS_SETTINGS_LINK = + 'https://support.metamask.io/configure/privacy/how-to-manage-your-metametrics-settings/'; export const SURVEY_LINK = 'https://www.getfeedback.com/r/Oczu1vP0'; // TODO make sure these links are correct diff --git a/ui/components/app/toast-master/toast-master.js b/ui/components/app/toast-master/toast-master.js index 5c09abfb76f4..6ae0dd8b3d8a 100644 --- a/ui/components/app/toast-master/toast-master.js +++ b/ui/components/app/toast-master/toast-master.js @@ -11,6 +11,7 @@ import { MILLISECOND, SECOND } from '../../../../shared/constants/time'; import { PRIVACY_POLICY_LINK, SURVEY_LINK, + METAMETRICS_SETTINGS_LINK, } from '../../../../shared/lib/ui-utils'; import { BorderColor, @@ -759,9 +760,9 @@ function Pna25Banner() { const showPna25Banner = useSelector(selectShowPna25Banner); const handleLearnMore = () => { - // Open privacy policy link and acknowledge + // Open MetaMetrics settings help page and acknowledge global.platform.openTab({ - url: PRIVACY_POLICY_LINK, + url: METAMETRICS_SETTINGS_LINK, }); setPna25Acknowledged(true); }; From 3ff5436c32857b1015febff224a6d25c470e9b58 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Tue, 25 Nov 2025 11:57:17 +0530 Subject: [PATCH 08/30] nit fix --- app/_locales/en_GB/messages.json | 6 +++--- ui/components/app/toast-master/selectors.ts | 13 +++++++------ ui/components/app/toast-master/toast-master.js | 1 + ui/pages/onboarding-flow/metametrics/metametrics.js | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 5c7a646eb1b2..0d0093de28cf 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -4147,9 +4147,6 @@ "newPrivacyPolicyTitle": { "message": "We’ve updated our privacy policy" }, - "pna25BannerTitle": { - "message": "We’re updating MetaMetrics. We'll associate some in-app activity to on-chain data to better understand usage. You can opt out via Settings." - }, "newRpcUrl": { "message": "New RPC URL" }, @@ -5060,6 +5057,9 @@ "message": "+ $1 more", "description": "$1 is a number of additional but unshown items in a list- this message will be shown in place of those items" }, + "pna25BannerTitle": { + "message": "We’re updating MetaMetrics. We'll associate some in-app activity to on-chain data to better understand usage. You can opt out via Settings." + }, "popularNetworkAddToolTip": { "message": "Some of these networks rely on third parties. The connections may be less reliable or enable third-parties to track activity.", "description": "Learn more link" diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index ad3a2875e8d5..45152714f0a5 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -17,7 +17,6 @@ import { getPermissions, isSolanaAccount, } from '../../../selectors'; -import { getRemoteFeatureFlags } from '../../../selectors/remote-feature-flags'; import { MetaMaskReduxState } from '../../../store/store'; import { PasswordChangeToastType, @@ -253,17 +252,19 @@ export function selectShowShieldEndingToast( * @returns Boolean indicating whether to show the banner */ export function selectShowPna25Banner(state: Pick): boolean { - const { completedOnboarding, participateInMetaMetrics, pna25Acknowledged } = - state.metamask || {}; + const { + completedOnboarding, + participateInMetaMetrics, + pna25Acknowledged, + remoteFeatureFlags, + } = state.metamask || {}; // Only show to users who have completed onboarding if (!completedOnboarding) { return false; // User hasn't completed onboarding yet } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const remoteFeatureFlags = getRemoteFeatureFlags(state as any); - const isPna25Enabled = remoteFeatureFlags?.extensionUxPna25; + const isPna25Enabled = remoteFeatureFlags?.['extension-ux-pna25']; // Check all conditions if (!isPna25Enabled) { diff --git a/ui/components/app/toast-master/toast-master.js b/ui/components/app/toast-master/toast-master.js index 6ae0dd8b3d8a..cd3bafdebf75 100644 --- a/ui/components/app/toast-master/toast-master.js +++ b/ui/components/app/toast-master/toast-master.js @@ -781,6 +781,7 @@ function Pna25Banner() { } text={t('pna25BannerTitle')} + textVariant={TextVariant.bodySm} actionText={t('learnMoreUpperCase')} onActionClick={handleLearnMore} onClose={handleClose} diff --git a/ui/pages/onboarding-flow/metametrics/metametrics.js b/ui/pages/onboarding-flow/metametrics/metametrics.js index 3e96a40c544f..46bbeb411195 100644 --- a/ui/pages/onboarding-flow/metametrics/metametrics.js +++ b/ui/pages/onboarding-flow/metametrics/metametrics.js @@ -71,7 +71,7 @@ export default function OnboardingMetametrics() { // Check if the PNA25 feature is enabled // extension-ux-pna25 is a boolean LaunchDarkly flag - const isPna25Enabled = Boolean(remoteFeatureFlags?.['extension-ux-pna25']); + const isPna25Enabled = remoteFeatureFlags?.extensionUxPna25; const [ isParticipateInMetaMetricsChecked, From 8e6603eda6c3b28b94c75e2e8d27dc51139294a5 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Tue, 25 Nov 2025 13:30:28 +0530 Subject: [PATCH 09/30] jest update --- app/scripts/controllers/app-state-controller.test.ts | 4 ++++ .../errors-after-init-opt-in-background-state.json | 1 + .../errors-before-init-opt-in-background-state.json | 1 + 3 files changed, 6 insertions(+) diff --git a/app/scripts/controllers/app-state-controller.test.ts b/app/scripts/controllers/app-state-controller.test.ts index 3ab39cc8b334..23163e24f2d1 100644 --- a/app/scripts/controllers/app-state-controller.test.ts +++ b/app/scripts/controllers/app-state-controller.test.ts @@ -792,6 +792,7 @@ describe('AppStateController', () => { "outdatedBrowserWarningLastShown": null, "pendingShieldCohort": null, "pendingShieldCohortTxType": null, + "pna25Acknowledged": null, "popupGasPollTokens": [], "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": false, @@ -883,6 +884,7 @@ describe('AppStateController', () => { "outdatedBrowserWarningLastShown": null, "pendingShieldCohort": null, "pendingShieldCohortTxType": null, + "pna25Acknowledged": null, "popupGasPollTokens": [], "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": false, @@ -963,6 +965,7 @@ describe('AppStateController', () => { "onboardingDate": null, "outdatedBrowserWarningLastShown": null, "pendingShieldCohortTxType": null, + "pna25Acknowledged": null, "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": false, "recoveryPhraseReminderLastShown": 1000, @@ -1053,6 +1056,7 @@ describe('AppStateController', () => { "outdatedBrowserWarningLastShown": null, "pendingShieldCohort": null, "pendingShieldCohortTxType": null, + "pna25Acknowledged": null, "popupGasPollTokens": [], "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": false, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index e06735afb191..1e20d1d5edf5 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -52,6 +52,7 @@ "outdatedBrowserWarningLastShown": "object", "pendingShieldCohort": null, "pendingShieldCohortTxType": null, + "pna25Acknowledged": null, "popupGasPollTokens": "object", "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": true, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json index b41f2ad3e1fb..baa79556c4d7 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json @@ -53,6 +53,7 @@ "showShieldEntryModalOnce": "boolean", "pendingShieldCohort": null, "pendingShieldCohortTxType": null, + "pna25Acknowledged": null, "appActiveTab": "object" }, "BridgeController": {}, From 82a81c849d3ad6a4e63e016f0fafca085159c839 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Tue, 25 Nov 2025 14:16:16 +0530 Subject: [PATCH 10/30] added sentry state --- test/e2e/default-fixture.js | 1 + .../state-snapshots/errors-after-init-opt-in-ui-state.json | 1 + .../state-snapshots/errors-before-init-opt-in-ui-state.json | 3 ++- test/e2e/tests/settings/state-logs.json | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/e2e/default-fixture.js b/test/e2e/default-fixture.js index 479ce42a5ff1..1ee357c730d3 100644 --- a/test/e2e/default-fixture.js +++ b/test/e2e/default-fixture.js @@ -113,6 +113,7 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { notificationGasPollTokens: [], popupGasPollTokens: [], recoveryPhraseReminderHasBeenShown: true, + pna25Acknowledged: null, recoveryPhraseReminderLastShown: '__FIXTURE_SUBSTITUTION__currentDateInMilliseconds', showTestnetMessageInDropdown: true, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index fdbaa1d1c4fe..cc46f1f9283a 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -14,6 +14,7 @@ "modal": "object", "alertOpen": "boolean", "alertMessage": null, + "pna25Acknowledged": null, "qrCodeData": null, "networkDropdownOpen": "boolean", "importNftsModal": "object", diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json index 3d497689c46f..4a7f00e39856 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -53,7 +53,8 @@ "showShieldEntryModalOnce": "boolean", "pendingShieldCohort": null, "pendingShieldCohortTxType": null, - "appActiveTab": "object" + "appActiveTab": "object", + "pna25Acknowledged": null }, "BridgeController": {}, "CurrencyController": { diff --git a/test/e2e/tests/settings/state-logs.json b/test/e2e/tests/settings/state-logs.json index c88d8be6d247..0efae3a22c4f 100644 --- a/test/e2e/tests/settings/state-logs.json +++ b/test/e2e/tests/settings/state-logs.json @@ -69,6 +69,7 @@ "onboardedInThisUISession": "boolean", "openMetaMaskTabs": {}, "pendingTokens": {}, + "pna25Acknowledged": null, "qrCodeData": "null", "removeNftMessage": "string", "requestAccountTabs": {}, From 26821c2cbcbafd75b32f6b83d4e039da9746b330 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Tue, 25 Nov 2025 14:45:23 +0530 Subject: [PATCH 11/30] sentry update --- app/scripts/constants/sentry-state.ts | 1 + ui/ducks/app/app.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index 390081fac860..ffc42603dff6 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -418,6 +418,7 @@ export const SENTRY_UI_STATE = { welcomeScreenSeen: true, slides: false, confirmationExchangeRates: true, + pna25Acknowledged: true, }, metamask: { ...flattenedBackgroundStateMask, diff --git a/ui/ducks/app/app.ts b/ui/ducks/app/app.ts index c3be8d5c3051..562a5ffd8a86 100644 --- a/ui/ducks/app/app.ts +++ b/ui/ducks/app/app.ts @@ -42,6 +42,7 @@ type AppState = { }; alertOpen: boolean; alertMessage: string | null; + pna25Acknowledged: boolean | null; qrCodeData: { type?: string | null; values?: { address?: string | null }; @@ -174,6 +175,7 @@ const initialState: AppState = { }, alertOpen: false, alertMessage: null, + pna25Acknowledged: null, qrCodeData: null, networkDropdownOpen: false, importNftsModal: { open: false }, From c787954baaed02b1ab06a26f587af34998856e52 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Tue, 25 Nov 2025 15:22:58 +0530 Subject: [PATCH 12/30] sentry update --- app/scripts/constants/sentry-state.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index ffc42603dff6..aa1d2ef52783 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -93,6 +93,7 @@ export const SENTRY_BACKGROUND_STATE = { shieldEndingToastLastClickedOrClosed: true, shieldPausedToastLastClickedOrClosed: true, isWalletResetInProgress: false, + pna25Acknowledged: false, }, MultichainBalancesController: { balances: false, From ba6bbab3778b21238d594f5e8700f19e9f90a2e9 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Tue, 25 Nov 2025 15:33:02 +0530 Subject: [PATCH 13/30] lint fix --- app/scripts/metamask-controller.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0125bb92bd11..5f3f5fe6b546 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2956,9 +2956,7 @@ export default class MetamaskController extends EventEmitter { appStateController, ), setPna25Acknowledged: - appStateController.setPna25Acknowledged.bind( - appStateController, - ), + appStateController.setPna25Acknowledged.bind(appStateController), setAppActiveTab: appStateController.setAppActiveTab.bind(appStateController), setDefaultSubscriptionPaymentOptions: From 79d389297cee477fbd366a9b1f834f76c8f2f2c2 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Tue, 25 Nov 2025 17:17:13 +0530 Subject: [PATCH 14/30] nit fix --- shared/types/background.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/types/background.ts b/shared/types/background.ts index 84c313ed471b..49bca449c3d3 100644 --- a/shared/types/background.ts +++ b/shared/types/background.ts @@ -117,6 +117,7 @@ export type ControllerStatePropertiesEnumerated = { isRampCardClosed: AppStateControllerState['isRampCardClosed']; newPrivacyPolicyToastClickedOrClosed: AppStateControllerState['newPrivacyPolicyToastClickedOrClosed']; newPrivacyPolicyToastShownDate: AppStateControllerState['newPrivacyPolicyToastShownDate']; + pna25Acknowledged: AppStateControllerState['pna25Acknowledged']; // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 // eslint-disable-next-line @typescript-eslint/naming-convention hadAdvancedGasFeesSetPriorToMigration92_3: AppStateControllerState['hadAdvancedGasFeesSetPriorToMigration92_3']; From 9aa85c115b31a5880253f2fc64ba33eecb892d14 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Tue, 25 Nov 2025 19:25:12 +0530 Subject: [PATCH 15/30] metrics update --- .../errors-after-init-opt-in-ui-state.json | 209 ++++++------ .../errors-before-init-opt-in-ui-state.json | 311 +++++++++--------- 2 files changed, 261 insertions(+), 259 deletions(-) diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index cc46f1f9283a..bca3f50f0c87 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -1,88 +1,7 @@ { - "DNS": "object", - "activeTab": "object", - "appState": { - "customNonceValue": "", - "isAccountMenuOpen": false, - "isNetworkMenuOpen": false, - "nextNonce": null, - "pendingTokens": "object", - "welcomeScreenSeen": false, - "confirmationExchangeRates": {}, - "shouldClose": "boolean", - "menuOpen": "boolean", - "modal": "object", - "alertOpen": "boolean", - "alertMessage": null, - "pna25Acknowledged": null, - "qrCodeData": null, - "networkDropdownOpen": "boolean", - "importNftsModal": "object", - "showPermittedNetworkToastOpen": "boolean", - "showIpfsModalOpen": "boolean", - "showBasicFunctionalityModal": "boolean", - "externalServicesOnboardingToggleState": "boolean", - "keyringRemovalSnapModal": "object", - "showKeyringRemovalSnapModal": "boolean", - "importTokensModalOpen": "boolean", - "deprecatedNetworkModalOpen": "boolean", - "accountDetail": "object", - "isLoading": "boolean", - "isNftStillFetchingIndication": "boolean", - "showNftDetectionEnablementToast": "boolean", - "loadingMessage": "undefined", - "warning": "undefined", - "buyView": "object", - "defaultHdPaths": "object", - "networksTabSelectedRpcUrl": "string", - "requestAccountTabs": "object", - "openMetaMaskTabs": "object", - "currentWindowTab": "object", - "showWhatsNewPopup": "boolean", - "showTermsOfUsePopup": "boolean", - "singleExceptions": "object", - "gasLoadingAnimationIsShowing": "boolean", - "smartTransactionsError": null, - "smartTransactionsErrorMessageDismissed": "boolean", - "ledgerWebHidConnectedStatus": "string", - "ledgerTransportStatus": "string", - "newNftAddedMessage": "string", - "removeNftMessage": "string", - "newNetworkAddedName": "string", - "editedNetwork": "undefined", - "newNetworkAddedConfigurationId": "string", - "selectedNetworkConfigurationId": "string", - "sendInputCurrencySwitched": "boolean", - "newTokensImported": "string", - "newTokensImportedError": "string", - "onboardedInThisUISession": "boolean", - "customTokenAmount": "string", - "scrollToBottom": "boolean", - "txId": null, - "accountDetailsAddress": "string", - "showDeleteMetaMetricsDataModal": "boolean", - "showDataDeletionErrorModal": "boolean", - "snapsInstallPrivacyWarningShown": "boolean", - "isAddingNewNetwork": "boolean", - "isMultiRpcOnboarding": "boolean", - "isAccessedFromDappConnectedSitePopover": "boolean", - "errorInSettings": null, - "showNewSrpAddedToast": "boolean", - "showPasswordChangeToast": null, - "showCopyAddressToast": "boolean", - "showClaimSubmitToast": null, - "showSupportDataConsentModal": "boolean" - }, - "bridge": "object", - "confirmAlerts": "object", - "confirmTransaction": "object", - "gas": { "customData": { "price": null, "limit": null } }, - "history": { - "mostRecentOverviewPage": "/", - "redirectAfterDefaultPage": null - }, "invalidCustomNetwork": "object", - "localeMessages": "object", + "unconnectedAccount": { "state": "CLOSED" }, + "activeTab": "object", "metamask": { "isInitialized": true, "isUnlocked": true, @@ -115,8 +34,6 @@ "shouldShowAggregatedBalancePopover": "boolean" }, "firstTimeFlowType": "import", - "claims": "object", - "claimsConfigurations": "object", "completedOnboarding": true, "knownMethodData": "object", "use4ByteResolution": true, @@ -135,6 +52,7 @@ } }, "throttledOrigins": "object", + "isSeedlessOnboardingUserAuthenticated": "boolean", "activeQrCodeScanRequest": null, "appActiveTab": "object", "browserEnvironment": { "os": "string", "browser": "string" }, @@ -146,12 +64,14 @@ "enforcedSimulationsSlippageForTransactions": "object", "fullScreenGasPollTokens": "object", "hadAdvancedGasFeesSetPriorToMigration92_3": false, + "canTrackWalletFundsObtained": false, "isRampCardClosed": false, "isUpdateAvailable": false, "lastUpdatedAt": null, "lastViewedUserSurvey": null, "newPrivacyPolicyToastClickedOrClosed": "boolean", "newPrivacyPolicyToastShownDate": "number", + "pna25Acknowledged": null, "nftsDetectionNoticeDismissed": false, "notificationGasPollTokens": "object", "onboardingDate": null, @@ -174,14 +94,17 @@ "updateModalLastDismissedAt": null, "hasShownMultichainAccountsIntroModal": "boolean", "showShieldEntryModalOnce": "boolean", + "pendingShieldCohort": null, + "pendingShieldCohortTxType": null, + "isWalletResetInProgress": "boolean", + "dappSwapComparisonData": "object", "addressSecurityAlertResponses": "object", "currentExtensionPopupId": "number", - "dappSwapComparisonData": "object", "nftsDropdownState": {}, "signatureSecurityAlertResponses": "object", "networkConnectionBanner": "object", - "termsOfUseLastAgreed": "number", "snapsInstallPrivacyWarningShown": true, + "termsOfUseLastAgreed": "number", "currentAppVersion": "string", "previousAppVersion": "", "previousMigrationVersion": 0, @@ -215,7 +138,6 @@ "identities": "object", "ipfsGateway": "string", "isIpfsGatewayEnabled": "boolean", - "isMultiAccountBalancesEnabled": "boolean", "ledgerTransportType": "webhid", "lostIdentities": "object", "manageInstitutionalWallets": "boolean", @@ -230,6 +152,7 @@ "useCurrencyRateCheck": true, "useExternalNameSources": "boolean", "useExternalServices": "boolean", + "isMultiAccountBalancesEnabled": "boolean", "useMultiAccountBalanceChecker": true, "useNftDetection": false, "usePhishDetect": true, @@ -254,7 +177,6 @@ "seedPhraseBackedUp": true, "onboardingTabs": "object", "socialBackupsMetadata": "object", - "isSeedlessOnboardingUserAuthenticated": "boolean", "subscriptions": "object", "trialedProducts": "object", "subjects": "object", @@ -329,7 +251,6 @@ "isUpdatingMetamaskNotifications": "boolean", "isFetchingMetamaskNotifications": "boolean", "isUpdatingMetamaskNotificationsAccount": "object", - "isWalletResetInProgress": "boolean", "isCheckingAccountsPresence": "boolean", "isPushEnabled": "boolean", "fcmToken": "string", @@ -340,12 +261,6 @@ "feature3": { "name": "groupC", "value": "valueC" }, "sendRedesign": { "enabled": false } }, - "rewardsAccounts": "object", - "rewardsActiveAccount": null, - "rewardsSeasonStatuses": "object", - "rewardsSeasons": "object", - "rewardsSubscriptionTokens": "object", - "rewardsSubscriptions": "object", "cacheTimestamp": "number", "allDeFiPositions": "object", "allDeFiPositionsCount": "object", @@ -353,6 +268,8 @@ "tokenScanCache": "object", "coverageResults": "object", "orderedTransactionHistory": "object", + "claimsConfigurations": "object", + "claims": "object", "accountsByChainId": "object", "unapprovedDecryptMsgs": "object", "unapprovedDecryptMsgCount": 0, @@ -406,9 +323,6 @@ "ensEntries": "object", "ensResolutionsByAddress": "object", "pendingApprovals": "object", - "pendingRevocations": "object", - "pendingShieldCohort": null, - "pendingShieldCohortTxType": null, "pendingApprovalCount": "number", "approvalFlows": "object", "storageMetadata": {}, @@ -421,18 +335,105 @@ "hasAccountTreeSyncingSyncedAtLeastOnce": "boolean", "accountGroupsMetadata": "object", "accountWalletsMetadata": "object", - "canTrackWalletFundsObtained": false, "delegations": "object", "isGatorPermissionsEnabled": "boolean", "gatorPermissionsMapSerialized": "string", "isFetchingGatorPermissions": "boolean", "gatorPermissionsProviderSnapId": "string", + "pendingRevocations": "object", + "rewardsActiveAccount": null, + "rewardsAccounts": "object", + "rewardsSubscriptions": "object", + "rewardsSeasons": "object", + "rewardsSeasonStatuses": "object", + "rewardsSubscriptionTokens": "object", "srpSessionData": "object" }, - "ramps": "object", - "rewards": "object", + "appState": { + "customNonceValue": "", + "isAccountMenuOpen": false, + "isNetworkMenuOpen": false, + "nextNonce": null, + "pendingTokens": "object", + "welcomeScreenSeen": false, + "confirmationExchangeRates": {}, + "shouldClose": "boolean", + "menuOpen": "boolean", + "modal": "object", + "alertOpen": "boolean", + "alertMessage": null, + "pna25Acknowledged": null, + "qrCodeData": null, + "networkDropdownOpen": "boolean", + "importNftsModal": "object", + "showPermittedNetworkToastOpen": "boolean", + "showIpfsModalOpen": "boolean", + "showBasicFunctionalityModal": "boolean", + "externalServicesOnboardingToggleState": "boolean", + "keyringRemovalSnapModal": "object", + "showKeyringRemovalSnapModal": "boolean", + "importTokensModalOpen": "boolean", + "deprecatedNetworkModalOpen": "boolean", + "accountDetail": "object", + "isLoading": "boolean", + "isNftStillFetchingIndication": "boolean", + "showNftDetectionEnablementToast": "boolean", + "loadingMessage": "undefined", + "warning": "undefined", + "buyView": "object", + "defaultHdPaths": "object", + "networksTabSelectedRpcUrl": "string", + "requestAccountTabs": "object", + "openMetaMaskTabs": "object", + "currentWindowTab": "object", + "showWhatsNewPopup": "boolean", + "showTermsOfUsePopup": "boolean", + "singleExceptions": "object", + "gasLoadingAnimationIsShowing": "boolean", + "smartTransactionsError": null, + "smartTransactionsErrorMessageDismissed": "boolean", + "ledgerWebHidConnectedStatus": "string", + "ledgerTransportStatus": "string", + "newNftAddedMessage": "string", + "removeNftMessage": "string", + "newNetworkAddedName": "string", + "editedNetwork": "undefined", + "newNetworkAddedConfigurationId": "string", + "selectedNetworkConfigurationId": "string", + "sendInputCurrencySwitched": "boolean", + "newTokensImported": "string", + "newTokensImportedError": "string", + "onboardedInThisUISession": "boolean", + "customTokenAmount": "string", + "scrollToBottom": "boolean", + "txId": null, + "accountDetailsAddress": "string", + "showDeleteMetaMetricsDataModal": "boolean", + "showDataDeletionErrorModal": "boolean", + "snapsInstallPrivacyWarningShown": "boolean", + "isAddingNewNetwork": "boolean", + "isMultiRpcOnboarding": "boolean", + "isAccessedFromDappConnectedSitePopover": "boolean", + "errorInSettings": null, + "showNewSrpAddedToast": "boolean", + "showPasswordChangeToast": null, + "showCopyAddressToast": "boolean", + "showClaimSubmitToast": null, + "showSupportDataConsentModal": "boolean" + }, + "DNS": "object", + "history": { + "mostRecentOverviewPage": "/", + "redirectAfterDefaultPage": null + }, "send": "object", - "smartAccounts": "object", + "confirmAlerts": "object", + "confirmTransaction": "object", "swaps": "object", - "unconnectedAccount": { "state": "CLOSED" } + "ramps": "object", + "bridge": "object", + "gas": { "customData": { "price": null, "limit": null } }, + "localeMessages": "object", + "smartAccounts": "object", + "rewards": "object" } diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json index abe11e2d1490..18ecd85c429e 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -1,42 +1,42 @@ { "data": { - "AuthenticationController": { "isSignedIn": "boolean" }, - "NotificationServicesController": { - "subscriptionAccountsSeen": "object", - "isMetamaskNotificationsFeatureSeen": "boolean", - "isNotificationServicesEnabled": "boolean", - "isFeatureAnnouncementsEnabled": "boolean", - "metamaskNotificationsList": "object", - "metamaskNotificationsReadList": "object" + "AccountOrderController": { + "hiddenAccountList": {}, + "pinnedAccountList": {} + }, + "AccountTracker": { "accountsByChainId": "object" }, + "AccountTreeController": { + "accountGroupsMetadata": "object", + "accountWalletsMetadata": "object", + "hasAccountTreeSyncingSyncedAtLeastOnce": "boolean" }, "AccountsController": { "internalAccounts": { "accounts": "object", "selectedAccount": "string" } }, + "AddressBookController": { "addressBook": "object" }, "AlertController": { "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, "unconnectedAccountAlertShownOrigins": "object", "web3ShimUsageOrigins": "object" }, "AnnouncementController": { "announcements": "object" }, - "NetworkOrderController": { - "orderedNetworkList": { "0": "object", "1": "object", "2": "object" } - }, - "NetworkEnablementController": { - "enabledNetworkMap": { "eip155": "object", "solana": "object" } - }, - "AccountOrderController": { - "pinnedAccountList": {}, - "hiddenAccountList": {} + "AppMetadataController": { + "currentAppVersion": "string", + "currentMigrationVersion": "number", + "previousAppVersion": "", + "previousMigrationVersion": 0 }, "AppStateController": { - "browserEnvironment": { "os": "string", "browser": "string" }, + "browserEnvironment": { "browser": "string", "os": "string" }, + "canTrackWalletFundsObtained": false, "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, "enableEnforcedSimulations": true, "enforcedSimulationsSlippage": "number", "hadAdvancedGasFeesSetPriorToMigration92_3": false, - "canTrackWalletFundsObtained": false, + "hasShownMultichainAccountsIntroModal": "boolean", "isRampCardClosed": false, + "isWalletResetInProgress": "boolean", "lastUpdatedAt": null, "lastViewedUserSurvey": null, "newPrivacyPolicyToastClickedOrClosed": "boolean", @@ -44,31 +44,33 @@ "nftsDetectionNoticeDismissed": false, "onboardingDate": null, "outdatedBrowserWarningLastShown": "object", + "pendingShieldCohortTxType": null, + "pna25Acknowledged": null, "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": true, "recoveryPhraseReminderLastShown": "number", + "shieldEndingToastLastClickedOrClosed": "object", + "shieldPausedToastLastClickedOrClosed": "object", "showAccountBanner": true, "showBetaHeader": false, "showDownloadMobileAppSlide": "boolean", "showNetworkBanner": true, "showPermissionsTour": true, + "showShieldEntryModalOnce": "boolean", "showTestnetMessageInDropdown": true, "slides": "object", + "snapsInstallPrivacyWarningShown": true, "surveyLinkLastClickedOrClosed": "object", - "shieldEndingToastLastClickedOrClosed": "object", - "shieldPausedToastLastClickedOrClosed": "object", - "trezorModel": null, - "updateModalLastDismissedAt": null, - "hasShownMultichainAccountsIntroModal": "boolean", - "showShieldEntryModalOnce": "boolean", - "pendingShieldCohortTxType": null, - "isWalletResetInProgress": "boolean", "termsOfUseLastAgreed": "number", - "snapsInstallPrivacyWarningShown": true + "trezorModel": null, + "updateModalLastDismissedAt": null }, + "ApprovalController": {}, + "AuthenticationController": { "isSignedIn": "boolean" }, "BridgeController": {}, + "BridgeStatusController": { "txHistory": "object" }, + "ClaimsController": "object", "CurrencyController": { - "currentCurrency": "usd", "currencyRates": { "ETH": { "conversionDate": "number", @@ -80,44 +82,103 @@ "conversionRate": 0.2, "usdConversionRate": 0.2 } - } + }, + "currentCurrency": "usd" + }, + "DeFiPositionsController": "object", + "DecryptMessageController": {}, + "DelegationController": "object", + "EncryptionPublicKeyController": {}, + "EnsController": { + "ensEntries": "object", + "ensResolutionsByAddress": "object" }, "GasFeeController": { - "gasFeeEstimatesByChainId": {}, - "gasFeeEstimates": {}, "estimatedGasFeeTimeBounds": {}, "gasEstimateType": "none", + "gasFeeEstimates": {}, + "gasFeeEstimatesByChainId": {}, "nonRPCGasFeeApisDisabled": "boolean" }, + "GatorPermissionsController": "object", "KeyringController": { "vault": "string" }, + "LoggingController": { "logs": "object" }, "MetaMetricsController": { - "participateInMetaMetrics": true, - "metaMetricsId": "0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420", "dataCollectionForMarketing": "boolean", - "marketingCampaignCookieId": null, "eventsBeforeMetricsOptIn": "object", - "tracesBeforeMetricsOptIn": "object", - "traits": "object", "fragments": "object", - "segmentApiCalls": "object" + "marketingCampaignCookieId": null, + "metaMetricsId": "0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420", + "participateInMetaMetrics": true, + "segmentApiCalls": "object", + "tracesBeforeMetricsOptIn": "object", + "traits": "object" }, "MetaMetricsDataDeletionController": { "metaMetricsDataDeletionId": null, "metaMetricsDataDeletionTimestamp": 0 }, + "MultichainAccountService": "object", + "MultichainAssetsController": { + "accountsAssets": "object", + "allIgnoredAssets": "object", + "assetsMetadata": "object" + }, + "MultichainAssetsRatesController": { "conversionRates": "object" }, + "MultichainBalancesController": { "balances": "object" }, + "MultichainNetworkController": "object", + "MultichainRatesController": { + "cryptocurrencies": ["btc", "sol"], + "fiatCurrency": "usd", + "rates": { + "btc": { "conversionDate": 0, "conversionRate": 0 }, + "sol": { "conversionDate": 0, "conversionRate": 0 } + } + }, + "MultichainTransactionsController": "object", + "NameController": { "nameSources": "object", "names": "object" }, "NetworkController": { - "selectedNetworkClientId": "string", + "networkConfigurationsByChainId": "object", "networksMetadata": { "networkConfigurationId": { "EIPS": {}, "status": "unknown" } }, - "networkConfigurationsByChainId": "object" + "selectedNetworkClientId": "string" + }, + "NetworkEnablementController": { + "enabledNetworkMap": { "eip155": "object", "solana": "object" } + }, + "NetworkOrderController": { + "orderedNetworkList": { "0": "object", "1": "object", "2": "object" } + }, + "NftController": { + "allNftContracts": "object", + "allNfts": "object", + "ignoredNfts": "object" + }, + "NotificationServicesController": { + "isFeatureAnnouncementsEnabled": "boolean", + "isMetamaskNotificationsFeatureSeen": "boolean", + "isNotificationServicesEnabled": "boolean", + "metamaskNotificationsList": "object", + "metamaskNotificationsReadList": "object", + "subscriptionAccountsSeen": "object" + }, + "NotificationServicesPushController": { + "fcmToken": "string", + "isPushEnabled": "boolean" }, "OnboardingController": { - "seedPhraseBackedUp": true, + "completedOnboarding": true, "firstTimeFlowType": "import", - "completedOnboarding": true + "seedPhraseBackedUp": true }, + "PPOMController": { "storageMetadata": {} }, "PermissionController": { "subjects": "object" }, + "PermissionLogController": { "permissionHistory": "object" }, + "PhishingController": { + "tokenScanCache": "object", + "urlScanCache": "object" + }, "PreferencesController": { "addSnapAccountEnabled": "boolean", "advancedGasFee": {}, @@ -129,6 +190,7 @@ "identities": "object", "ipfsGateway": "string", "isIpfsGatewayEnabled": "boolean", + "isMultiAccountBalancesEnabled": "boolean", "knownMethodData": "object", "ledgerTransportType": "webhid", "lostIdentities": "object", @@ -141,6 +203,7 @@ "hideZeroBalanceTokens": false, "petnamesEnabled": "boolean", "privacyMode": "boolean", + "shouldShowAggregatedBalancePopover": "boolean", "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showMultiRpcModal": "boolean", @@ -148,14 +211,14 @@ "showTestNetworks": false, "skipDeepLinkInterstitial": "boolean", "smartAccountOptIn": "boolean", - "smartTransactionsOptInStatus": true, "smartTransactionsMigrationApplied": "boolean", + "smartTransactionsOptInStatus": true, "tokenNetworkFilter": {}, "tokenSortConfig": "object", "useNativeCurrencyAsPrimaryCurrency": "boolean", - "useSidePanelAsDefault": "boolean", - "shouldShowAggregatedBalancePopover": "boolean" + "useSidePanelAsDefault": "boolean" }, + "referrals": "object", "securityAlertsEnabled": "boolean", "selectedAddress": "string", "snapRegistryList": "object", @@ -167,139 +230,77 @@ "useCurrencyRateCheck": true, "useExternalNameSources": "boolean", "useExternalServices": "boolean", - "isMultiAccountBalancesEnabled": "boolean", "useMultiAccountBalanceChecker": true, "useNftDetection": false, "usePhishDetect": true, "useSafeChainsListValidation": "boolean", "useTokenDetection": true, "useTransactionSimulations": true, - "watchEthereumAccountEnabled": "boolean", - "referrals": "object" - }, - "SelectedNetworkController": { "domains": "object" }, - "SmartTransactionsController": {}, - "SubjectMetadataController": { "subjectMetadata": "object" }, - "TokensController": { - "allTokens": {}, - "allIgnoredTokens": {}, - "allDetectedTokens": {} - }, - "MultichainAccountService": "object", - "TransactionController": { - "methodData": "object", - "transactions": "object", - "transactionBatches": "object", - "lastFetchedBlockNumbers": "object", - "submitHistory": "object" - }, - "config": "object", - "firstTimeInfo": "object", - "NameController": { "names": "object", "nameSources": "object" }, - "UserStorageController": { - "isBackupAndSyncEnabled": true, - "isAccountSyncingEnabled": true, - "isContactSyncingEnabled": true - }, - "AppMetadataController": { - "currentAppVersion": "string", - "previousAppVersion": "", - "previousMigrationVersion": 0, - "currentMigrationVersion": "number" - }, - "AddressBookController": { "addressBook": "object" }, - "MultichainNetworkController": "object", - "SeedlessOnboardingController": "object", - "PermissionLogController": { "permissionHistory": "object" }, - "GatorPermissionsController": "object", - "TokenListController": { - "tokensChainsCache": {}, - "preventPollingOnNetworkRestart": false - }, - "TokenBalancesController": { "tokenBalances": "object" }, - "NftController": { - "allNftContracts": "object", - "allNfts": "object", - "ignoredNfts": "object" - }, - "PhishingController": { - "urlScanCache": "object", - "tokenScanCache": "object" - }, - "LoggingController": { "logs": "object" }, - "MultichainRatesController": { - "fiatCurrency": "usd", - "rates": { - "btc": { "conversionDate": 0, "conversionRate": 0 }, - "sol": { "conversionDate": 0, "conversionRate": 0 } - }, - "cryptocurrencies": ["btc", "sol"] - }, - "UserOperationController": { "userOperations": "object" }, - "NotificationServicesPushController": { - "isPushEnabled": "boolean", - "fcmToken": "string" + "watchEthereumAccountEnabled": "boolean" }, "RemoteFeatureFlagController": { + "cacheTimestamp": "number", "remoteFeatureFlags": { "feature1": true, "feature2": false, "feature3": { "name": "groupC", "value": "valueC" }, "sendRedesign": { "enabled": false } - }, - "cacheTimestamp": "number" - }, - "DeFiPositionsController": "object", - "AccountTracker": { "accountsByChainId": "object" }, - "TokenRatesController": { "marketData": "object" }, - "DecryptMessageController": {}, - "EncryptionPublicKeyController": {}, - "SignatureController": {}, - "SwapsController": {}, - "BridgeStatusController": { "txHistory": "object" }, - "EnsController": { - "ensEntries": "object", - "ensResolutionsByAddress": "object" + } }, - "ApprovalController": {}, - "SnapsRegistry": { - "database": "object", - "lastUpdated": "number", - "databaseUnavailable": "boolean" + "RewardsController": { + "rewardsAccounts": "object", + "rewardsActiveAccount": null, + "rewardsSeasonStatuses": "object", + "rewardsSeasons": "object", + "rewardsSubscriptionTokens": "object", + "rewardsSubscriptions": "object" }, + "SeedlessOnboardingController": "object", + "SelectedNetworkController": { "domains": "object" }, + "ShieldController": "object", + "SignatureController": {}, + "SmartTransactionsController": {}, "SnapController": { - "snaps": "object", "snapStates": "object", + "snaps": "object", "unencryptedSnapStates": "object" }, "SnapInsightsController": {}, "SnapInterfaceController": { "interfaces": "object" }, - "PPOMController": { "storageMetadata": {} }, - "AccountTreeController": { - "hasAccountTreeSyncingSyncedAtLeastOnce": "boolean", - "accountGroupsMetadata": "object", - "accountWalletsMetadata": "object" - }, - "MultichainAssetsController": { - "accountsAssets": "object", - "assetsMetadata": "object", - "allIgnoredAssets": "object" + "SnapsRegistry": { + "database": "object", + "databaseUnavailable": "boolean", + "lastUpdated": "number" }, - "MultichainAssetsRatesController": { "conversionRates": "object" }, - "MultichainBalancesController": { "balances": "object" }, - "MultichainTransactionsController": "object", - "DelegationController": "object", + "SubjectMetadataController": { "subjectMetadata": "object" }, "SubscriptionController": "object", - "ShieldController": "object", - "ClaimsController": "object", - "RewardsController": { - "rewardsActiveAccount": null, - "rewardsAccounts": "object", - "rewardsSubscriptions": "object", - "rewardsSeasons": "object", - "rewardsSeasonStatuses": "object", - "rewardsSubscriptionTokens": "object" - } + "SwapsController": {}, + "TokenBalancesController": { "tokenBalances": "object" }, + "TokenListController": { + "preventPollingOnNetworkRestart": false, + "tokensChainsCache": {} + }, + "TokenRatesController": { "marketData": "object" }, + "TokensController": { + "allDetectedTokens": {}, + "allIgnoredTokens": {}, + "allTokens": {} + }, + "TransactionController": { + "lastFetchedBlockNumbers": "object", + "methodData": "object", + "submitHistory": "object", + "transactionBatches": "object", + "transactions": "object" + }, + "UserOperationController": { "userOperations": "object" }, + "UserStorageController": { + "isAccountSyncingEnabled": true, + "isBackupAndSyncEnabled": true, + "isContactSyncingEnabled": true + }, + "config": "object", + "firstTimeInfo": "object" }, "meta": { "version": 183 } } From eed9b2a76c00961cdea6d42fc7c883418cd2beef Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Tue, 25 Nov 2025 19:42:03 +0530 Subject: [PATCH 16/30] lint fix --- .../errors-after-init-opt-in-ui-state.json | 174 +++++++++--------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index bca3f50f0c87..ae583df65e9f 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -1,7 +1,88 @@ { - "invalidCustomNetwork": "object", - "unconnectedAccount": { "state": "CLOSED" }, + "DNS": "object", "activeTab": "object", + "appState": { + "customNonceValue": "", + "isAccountMenuOpen": false, + "isNetworkMenuOpen": false, + "nextNonce": null, + "pendingTokens": "object", + "welcomeScreenSeen": false, + "confirmationExchangeRates": {}, + "shouldClose": "boolean", + "menuOpen": "boolean", + "modal": "object", + "alertOpen": "boolean", + "alertMessage": null, + "pna25Acknowledged": null, + "qrCodeData": null, + "networkDropdownOpen": "boolean", + "importNftsModal": "object", + "showPermittedNetworkToastOpen": "boolean", + "showIpfsModalOpen": "boolean", + "showBasicFunctionalityModal": "boolean", + "externalServicesOnboardingToggleState": "boolean", + "keyringRemovalSnapModal": "object", + "showKeyringRemovalSnapModal": "boolean", + "importTokensModalOpen": "boolean", + "deprecatedNetworkModalOpen": "boolean", + "accountDetail": "object", + "isLoading": "boolean", + "isNftStillFetchingIndication": "boolean", + "showNftDetectionEnablementToast": "boolean", + "loadingMessage": "undefined", + "warning": "undefined", + "buyView": "object", + "defaultHdPaths": "object", + "networksTabSelectedRpcUrl": "string", + "requestAccountTabs": "object", + "openMetaMaskTabs": "object", + "currentWindowTab": "object", + "showWhatsNewPopup": "boolean", + "showTermsOfUsePopup": "boolean", + "singleExceptions": "object", + "gasLoadingAnimationIsShowing": "boolean", + "smartTransactionsError": null, + "smartTransactionsErrorMessageDismissed": "boolean", + "ledgerWebHidConnectedStatus": "string", + "ledgerTransportStatus": "string", + "newNftAddedMessage": "string", + "removeNftMessage": "string", + "newNetworkAddedName": "string", + "editedNetwork": "undefined", + "newNetworkAddedConfigurationId": "string", + "selectedNetworkConfigurationId": "string", + "sendInputCurrencySwitched": "boolean", + "newTokensImported": "string", + "newTokensImportedError": "string", + "onboardedInThisUISession": "boolean", + "customTokenAmount": "string", + "scrollToBottom": "boolean", + "txId": null, + "accountDetailsAddress": "string", + "showDeleteMetaMetricsDataModal": "boolean", + "showDataDeletionErrorModal": "boolean", + "snapsInstallPrivacyWarningShown": "boolean", + "isAddingNewNetwork": "boolean", + "isMultiRpcOnboarding": "boolean", + "isAccessedFromDappConnectedSitePopover": "boolean", + "errorInSettings": null, + "showNewSrpAddedToast": "boolean", + "showPasswordChangeToast": null, + "showCopyAddressToast": "boolean", + "showClaimSubmitToast": null, + "showSupportDataConsentModal": "boolean" + }, + "bridge": "object", + "confirmAlerts": "object", + "confirmTransaction": "object", + "gas": { "customData": { "price": null, "limit": null } }, + "history": { + "mostRecentOverviewPage": "/", + "redirectAfterDefaultPage": null + }, + "invalidCustomNetwork": "object", + "localeMessages": "object", "metamask": { "isInitialized": true, "isUnlocked": true, @@ -349,91 +430,10 @@ "rewardsSubscriptionTokens": "object", "srpSessionData": "object" }, - "appState": { - "customNonceValue": "", - "isAccountMenuOpen": false, - "isNetworkMenuOpen": false, - "nextNonce": null, - "pendingTokens": "object", - "welcomeScreenSeen": false, - "confirmationExchangeRates": {}, - "shouldClose": "boolean", - "menuOpen": "boolean", - "modal": "object", - "alertOpen": "boolean", - "alertMessage": null, - "pna25Acknowledged": null, - "qrCodeData": null, - "networkDropdownOpen": "boolean", - "importNftsModal": "object", - "showPermittedNetworkToastOpen": "boolean", - "showIpfsModalOpen": "boolean", - "showBasicFunctionalityModal": "boolean", - "externalServicesOnboardingToggleState": "boolean", - "keyringRemovalSnapModal": "object", - "showKeyringRemovalSnapModal": "boolean", - "importTokensModalOpen": "boolean", - "deprecatedNetworkModalOpen": "boolean", - "accountDetail": "object", - "isLoading": "boolean", - "isNftStillFetchingIndication": "boolean", - "showNftDetectionEnablementToast": "boolean", - "loadingMessage": "undefined", - "warning": "undefined", - "buyView": "object", - "defaultHdPaths": "object", - "networksTabSelectedRpcUrl": "string", - "requestAccountTabs": "object", - "openMetaMaskTabs": "object", - "currentWindowTab": "object", - "showWhatsNewPopup": "boolean", - "showTermsOfUsePopup": "boolean", - "singleExceptions": "object", - "gasLoadingAnimationIsShowing": "boolean", - "smartTransactionsError": null, - "smartTransactionsErrorMessageDismissed": "boolean", - "ledgerWebHidConnectedStatus": "string", - "ledgerTransportStatus": "string", - "newNftAddedMessage": "string", - "removeNftMessage": "string", - "newNetworkAddedName": "string", - "editedNetwork": "undefined", - "newNetworkAddedConfigurationId": "string", - "selectedNetworkConfigurationId": "string", - "sendInputCurrencySwitched": "boolean", - "newTokensImported": "string", - "newTokensImportedError": "string", - "onboardedInThisUISession": "boolean", - "customTokenAmount": "string", - "scrollToBottom": "boolean", - "txId": null, - "accountDetailsAddress": "string", - "showDeleteMetaMetricsDataModal": "boolean", - "showDataDeletionErrorModal": "boolean", - "snapsInstallPrivacyWarningShown": "boolean", - "isAddingNewNetwork": "boolean", - "isMultiRpcOnboarding": "boolean", - "isAccessedFromDappConnectedSitePopover": "boolean", - "errorInSettings": null, - "showNewSrpAddedToast": "boolean", - "showPasswordChangeToast": null, - "showCopyAddressToast": "boolean", - "showClaimSubmitToast": null, - "showSupportDataConsentModal": "boolean" - }, - "DNS": "object", - "history": { - "mostRecentOverviewPage": "/", - "redirectAfterDefaultPage": null - }, - "send": "object", - "confirmAlerts": "object", - "confirmTransaction": "object", - "swaps": "object", "ramps": "object", - "bridge": "object", - "gas": { "customData": { "price": null, "limit": null } }, - "localeMessages": "object", + "rewards": "object", + "send": "object", "smartAccounts": "object", - "rewards": "object" + "swaps": "object", + "unconnectedAccount": { "state": "CLOSED" } } From cc388b37e57a2af2ac17fe6bc80d1ec6d5c1efe6 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Tue, 25 Nov 2025 21:55:30 +0530 Subject: [PATCH 17/30] lint fix --- test/e2e/tests/settings/state-logs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/tests/settings/state-logs.json b/test/e2e/tests/settings/state-logs.json index 0efae3a22c4f..e2839a366805 100644 --- a/test/e2e/tests/settings/state-logs.json +++ b/test/e2e/tests/settings/state-logs.json @@ -69,7 +69,7 @@ "onboardedInThisUISession": "boolean", "openMetaMaskTabs": {}, "pendingTokens": {}, - "pna25Acknowledged": null, + "pna25Acknowledged": "null", "qrCodeData": "null", "removeNftMessage": "string", "requestAccountTabs": {}, From 845037293c87b439e2c1429eea4f539136c5cd58 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Wed, 26 Nov 2025 15:40:48 +0530 Subject: [PATCH 18/30] lint fix --- app/scripts/constants/sentry-state.ts | 2 +- ui/components/app/toast-master/selectors.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index aa1d2ef52783..79309ccce204 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -419,7 +419,7 @@ export const SENTRY_UI_STATE = { welcomeScreenSeen: true, slides: false, confirmationExchangeRates: true, - pna25Acknowledged: true, + pna25Acknowledged: false, }, metamask: { ...flattenedBackgroundStateMask, diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index 45152714f0a5..7a5a9c101d0a 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -264,7 +264,7 @@ export function selectShowPna25Banner(state: Pick): boolean { return false; // User hasn't completed onboarding yet } - const isPna25Enabled = remoteFeatureFlags?.['extension-ux-pna25']; + const isPna25Enabled = remoteFeatureFlags?.extensionUxPna25; // Check all conditions if (!isPna25Enabled) { From 58a3457dcb07b874b67a5129193e0b9f6b316bd6 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Wed, 26 Nov 2025 15:45:48 +0530 Subject: [PATCH 19/30] updated state to be boolea --- app/scripts/controllers/app-state-controller.test.ts | 8 ++++---- app/scripts/controllers/app-state-controller.ts | 4 ++-- test/e2e/default-fixture.js | 2 +- .../errors-after-init-opt-in-background-state.json | 2 +- .../errors-after-init-opt-in-ui-state.json | 4 ++-- .../errors-before-init-opt-in-background-state.json | 2 +- .../errors-before-init-opt-in-ui-state.json | 2 +- test/e2e/tests/settings/state-logs.json | 2 +- ui/components/app/toast-master/selectors.ts | 8 ++++---- ui/ducks/app/app.ts | 4 ++-- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/scripts/controllers/app-state-controller.test.ts b/app/scripts/controllers/app-state-controller.test.ts index 23163e24f2d1..fcade9450da5 100644 --- a/app/scripts/controllers/app-state-controller.test.ts +++ b/app/scripts/controllers/app-state-controller.test.ts @@ -792,7 +792,7 @@ describe('AppStateController', () => { "outdatedBrowserWarningLastShown": null, "pendingShieldCohort": null, "pendingShieldCohortTxType": null, - "pna25Acknowledged": null, + "pna25Acknowledged": false, "popupGasPollTokens": [], "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": false, @@ -884,7 +884,7 @@ describe('AppStateController', () => { "outdatedBrowserWarningLastShown": null, "pendingShieldCohort": null, "pendingShieldCohortTxType": null, - "pna25Acknowledged": null, + "pna25Acknowledged": false, "popupGasPollTokens": [], "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": false, @@ -965,7 +965,7 @@ describe('AppStateController', () => { "onboardingDate": null, "outdatedBrowserWarningLastShown": null, "pendingShieldCohortTxType": null, - "pna25Acknowledged": null, + "pna25Acknowledged": false, "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": false, "recoveryPhraseReminderLastShown": 1000, @@ -1056,7 +1056,7 @@ describe('AppStateController', () => { "outdatedBrowserWarningLastShown": null, "pendingShieldCohort": null, "pendingShieldCohortTxType": null, - "pna25Acknowledged": null, + "pna25Acknowledged": false, "popupGasPollTokens": [], "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": false, diff --git a/app/scripts/controllers/app-state-controller.ts b/app/scripts/controllers/app-state-controller.ts index 64223dbbe80e..2457c0d54009 100644 --- a/app/scripts/controllers/app-state-controller.ts +++ b/app/scripts/controllers/app-state-controller.ts @@ -101,7 +101,7 @@ export type AppStateControllerState = { networkConnectionBanner: NetworkConnectionBanner; newPrivacyPolicyToastClickedOrClosed: boolean | null; newPrivacyPolicyToastShownDate: number | null; - pna25Acknowledged: boolean | null; + pna25Acknowledged: boolean; nftsDetectionNoticeDismissed: boolean; nftsDropdownState: Json; notificationGasPollTokens: string[]; @@ -270,7 +270,7 @@ const getDefaultAppStateControllerState = (): AppStateControllerState => ({ lastViewedUserSurvey: null, newPrivacyPolicyToastClickedOrClosed: null, newPrivacyPolicyToastShownDate: null, - pna25Acknowledged: null, + pna25Acknowledged: false, nftsDetectionNoticeDismissed: false, notificationGasPollTokens: [], onboardingDate: null, diff --git a/test/e2e/default-fixture.js b/test/e2e/default-fixture.js index 1ee357c730d3..70b996f97607 100644 --- a/test/e2e/default-fixture.js +++ b/test/e2e/default-fixture.js @@ -113,7 +113,7 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { notificationGasPollTokens: [], popupGasPollTokens: [], recoveryPhraseReminderHasBeenShown: true, - pna25Acknowledged: null, + pna25Acknowledged: false, recoveryPhraseReminderLastShown: '__FIXTURE_SUBSTITUTION__currentDateInMilliseconds', showTestnetMessageInDropdown: true, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index 1e20d1d5edf5..d02bea0087a2 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -52,7 +52,7 @@ "outdatedBrowserWarningLastShown": "object", "pendingShieldCohort": null, "pendingShieldCohortTxType": null, - "pna25Acknowledged": null, + "pna25Acknowledged": false, "popupGasPollTokens": "object", "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": true, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index ae583df65e9f..cd4456af9cc0 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -14,7 +14,7 @@ "modal": "object", "alertOpen": "boolean", "alertMessage": null, - "pna25Acknowledged": null, + "pna25Acknowledged": false, "qrCodeData": null, "networkDropdownOpen": "boolean", "importNftsModal": "object", @@ -152,7 +152,7 @@ "lastViewedUserSurvey": null, "newPrivacyPolicyToastClickedOrClosed": "boolean", "newPrivacyPolicyToastShownDate": "number", - "pna25Acknowledged": null, + "pna25Acknowledged": false, "nftsDetectionNoticeDismissed": false, "notificationGasPollTokens": "object", "onboardingDate": null, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json index baa79556c4d7..eaeea78a4e91 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json @@ -53,7 +53,7 @@ "showShieldEntryModalOnce": "boolean", "pendingShieldCohort": null, "pendingShieldCohortTxType": null, - "pna25Acknowledged": null, + "pna25Acknowledged": false, "appActiveTab": "object" }, "BridgeController": {}, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json index 18ecd85c429e..480b10de9be6 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -45,7 +45,7 @@ "onboardingDate": null, "outdatedBrowserWarningLastShown": "object", "pendingShieldCohortTxType": null, - "pna25Acknowledged": null, + "pna25Acknowledged": false, "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": true, "recoveryPhraseReminderLastShown": "number", diff --git a/test/e2e/tests/settings/state-logs.json b/test/e2e/tests/settings/state-logs.json index 5bb697efeec3..c40d0074bec1 100644 --- a/test/e2e/tests/settings/state-logs.json +++ b/test/e2e/tests/settings/state-logs.json @@ -69,7 +69,7 @@ "onboardedInThisUISession": "boolean", "openMetaMaskTabs": {}, "pendingTokens": {}, - "pna25Acknowledged": "null", + "pna25Acknowledged": "boolean", "qrCodeData": "null", "removeNftMessage": "string", "requestAccountTabs": {}, diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index 7a5a9c101d0a..8dafa9caacd9 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -244,9 +244,9 @@ export function selectShowShieldEndingToast( * - User has completed onboarding (completedOnboarding === true) * - LaunchDarkly feature flag (extension-ux-pna25) is enabled (boolean) * - User has opted into metrics (participateInMetaMetrics === true) - * - User is an EXISTING user (pna25Acknowledged === null) - * New users will have pna25Acknowledged = true (opted in) or false (opted out) - * Existing users will have pna25Acknowledged = null (state didn't exist when they onboarded) + * - User is an EXISTING user (pna25Acknowledged === false) + * New users will have pna25Acknowledged = true (saw updated policy during onboarding) + * Existing users will have pna25Acknowledged = false (default, need to show banner) * * @param state - The application state containing the banner data. * @returns Boolean indicating whether to show the banner @@ -279,6 +279,6 @@ export function selectShowPna25Banner(state: Pick): boolean { return false; // User already acknowledged } - // Show banner only for existing users who opted in before this feature + // Show banner for users who haven't acknowledged (pna25Acknowledged === false) return true; } diff --git a/ui/ducks/app/app.ts b/ui/ducks/app/app.ts index 562a5ffd8a86..b2866a2b237b 100644 --- a/ui/ducks/app/app.ts +++ b/ui/ducks/app/app.ts @@ -42,7 +42,7 @@ type AppState = { }; alertOpen: boolean; alertMessage: string | null; - pna25Acknowledged: boolean | null; + pna25Acknowledged: boolean; qrCodeData: { type?: string | null; values?: { address?: string | null }; @@ -175,7 +175,7 @@ const initialState: AppState = { }, alertOpen: false, alertMessage: null, - pna25Acknowledged: null, + pna25Acknowledged: false, qrCodeData: null, networkDropdownOpen: false, importNftsModal: { open: false }, From 4bfd97dba17cd217345790917ff98cb538f17701 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Wed, 26 Nov 2025 16:05:47 +0530 Subject: [PATCH 20/30] lint fix --- ui/components/app/toast-master/selectors.ts | 4 ++-- ui/pages/onboarding-flow/metametrics/metametrics.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index 8dafa9caacd9..6a1318bebdb8 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -279,6 +279,6 @@ export function selectShowPna25Banner(state: Pick): boolean { return false; // User already acknowledged } - // Show banner for users who haven't acknowledged (pna25Acknowledged === false) - return true; + // Only show banner if explicitly false (existing users who haven't acknowledged) + return pna25Acknowledged === false; } diff --git a/ui/pages/onboarding-flow/metametrics/metametrics.js b/ui/pages/onboarding-flow/metametrics/metametrics.js index 46bbeb411195..608f0305eb82 100644 --- a/ui/pages/onboarding-flow/metametrics/metametrics.js +++ b/ui/pages/onboarding-flow/metametrics/metametrics.js @@ -147,7 +147,7 @@ export default function OnboardingMetametrics() { // This means they saw the updated policy during onboarding (whether they opted in or out) // No need to show banner to new users who already saw the updated message if (isPna25Enabled) { - await submitRequestToBackgroundAndCatch('setPna25Acknowledged', [true]); + submitRequestToBackgroundAndCatch('setPna25Acknowledged', [true]); } } catch (error) { log.error('onConfirm::error', error); From e6758fc9403cb7db5d21a6f246434cce4470f1e5 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Wed, 26 Nov 2025 16:44:55 +0530 Subject: [PATCH 21/30] lint fix --- .../errors-after-init-opt-in-background-state.json | 2 +- .../errors-after-init-opt-in-ui-state.json | 4 ++-- .../errors-before-init-opt-in-background-state.json | 2 +- .../errors-before-init-opt-in-ui-state.json | 2 +- ui/pages/onboarding-flow/metametrics/metametrics.js | 10 ++++++++-- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index d02bea0087a2..985cb15ab7f7 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -52,7 +52,7 @@ "outdatedBrowserWarningLastShown": "object", "pendingShieldCohort": null, "pendingShieldCohortTxType": null, - "pna25Acknowledged": false, + "pna25Acknowledged": "boolean", "popupGasPollTokens": "object", "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": true, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index cd4456af9cc0..bdd04c68687c 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -14,7 +14,7 @@ "modal": "object", "alertOpen": "boolean", "alertMessage": null, - "pna25Acknowledged": false, + "pna25Acknowledged": "boolean", "qrCodeData": null, "networkDropdownOpen": "boolean", "importNftsModal": "object", @@ -152,7 +152,7 @@ "lastViewedUserSurvey": null, "newPrivacyPolicyToastClickedOrClosed": "boolean", "newPrivacyPolicyToastShownDate": "number", - "pna25Acknowledged": false, + "pna25Acknowledged": "boolean", "nftsDetectionNoticeDismissed": false, "notificationGasPollTokens": "object", "onboardingDate": null, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json index eaeea78a4e91..63e21fc22039 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json @@ -53,7 +53,7 @@ "showShieldEntryModalOnce": "boolean", "pendingShieldCohort": null, "pendingShieldCohortTxType": null, - "pna25Acknowledged": false, + "pna25Acknowledged": "boolean", "appActiveTab": "object" }, "BridgeController": {}, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json index 480b10de9be6..d3ffc35e6fcc 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -45,7 +45,7 @@ "onboardingDate": null, "outdatedBrowserWarningLastShown": "object", "pendingShieldCohortTxType": null, - "pna25Acknowledged": false, + "pna25Acknowledged": "boolean", "productTour": "accountIcon", "recoveryPhraseReminderHasBeenShown": true, "recoveryPhraseReminderLastShown": "number", diff --git a/ui/pages/onboarding-flow/metametrics/metametrics.js b/ui/pages/onboarding-flow/metametrics/metametrics.js index 608f0305eb82..aa68bf8adbe1 100644 --- a/ui/pages/onboarding-flow/metametrics/metametrics.js +++ b/ui/pages/onboarding-flow/metametrics/metametrics.js @@ -51,7 +51,7 @@ import { } from '../../../components/component-library'; import { FirstTimeFlowType } from '../../../../shared/constants/onboarding'; import { getBrowserName } from '../../../../shared/modules/browser-runtime.utils'; -import { submitRequestToBackgroundAndCatch } from '../../../components/app/toast-master/utils'; +import { submitRequestToBackground } from '../../../store/background-connection'; const isFirefox = getBrowserName() === PLATFORM_FIREFOX; @@ -146,8 +146,14 @@ export default function OnboardingMetametrics() { // If LD flag is enabled, set pna25Acknowledged to true // This means they saw the updated policy during onboarding (whether they opted in or out) // No need to show banner to new users who already saw the updated message + // Await to ensure state update completes before navigation if (isPna25Enabled) { - submitRequestToBackgroundAndCatch('setPna25Acknowledged', [true]); + try { + await submitRequestToBackground('setPna25Acknowledged', [true]); + } catch (error) { + // Log error but don't block navigation if state update fails + log.error('Error setting pna25Acknowledged:', error); + } } } catch (error) { log.error('onConfirm::error', error); From 971c1d0bfae2c323ac6785ea9b2dfbe636f3004b Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Wed, 26 Nov 2025 17:21:15 +0530 Subject: [PATCH 22/30] added onboarding --- .../metametrics/metametrics.js | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/ui/pages/onboarding-flow/metametrics/metametrics.js b/ui/pages/onboarding-flow/metametrics/metametrics.js index aa68bf8adbe1..2290e96fc0bf 100644 --- a/ui/pages/onboarding-flow/metametrics/metametrics.js +++ b/ui/pages/onboarding-flow/metametrics/metametrics.js @@ -125,6 +125,18 @@ export default function OnboardingMetametrics() { ); dispatch(setParticipateInMetaMetrics(true)); + // Set pna25Acknowledged to true for new users who opt into metrics during onboarding + // This means they saw the updated policy during onboarding + // Only set if feature flag is enabled, as the banner only shows when flag is enabled + if (isPna25Enabled) { + try { + await submitRequestToBackground('setPna25Acknowledged', [true]); + } catch (error) { + // Log error but don't block onboarding if state update fails + log.error('Error setting pna25Acknowledged:', error); + } + } + await trackEvent({ category: MetaMetricsEventCategory.Onboarding, event: MetaMetricsEventName.AppInstalled, @@ -142,19 +154,6 @@ export default function OnboardingMetametrics() { dispatch(setParticipateInMetaMetrics(false)); dispatch(setDataCollectionForMarketing(false)); } - - // If LD flag is enabled, set pna25Acknowledged to true - // This means they saw the updated policy during onboarding (whether they opted in or out) - // No need to show banner to new users who already saw the updated message - // Await to ensure state update completes before navigation - if (isPna25Enabled) { - try { - await submitRequestToBackground('setPna25Acknowledged', [true]); - } catch (error) { - // Log error but don't block navigation if state update fails - log.error('Error setting pna25Acknowledged:', error); - } - } } catch (error) { log.error('onConfirm::error', error); } finally { From 7b4cf07879e4f386006f3596d3de05b682396c8b Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Wed, 26 Nov 2025 22:20:25 +0530 Subject: [PATCH 23/30] added build flag --- builds.yml | 7 ++++ .../app/toast-master/toast-master.js | 6 ++-- ui/ducks/metamask/metamask.js | 6 ++++ .../metametrics/metametrics.js | 32 ++++++++----------- ui/pages/onboarding-flow/welcome/welcome.js | 7 ++++ ui/store/actionConstants.ts | 1 + ui/store/actions.ts | 13 ++++++++ 7 files changed, 51 insertions(+), 21 deletions(-) diff --git a/builds.yml b/builds.yml index f564ed0e89c3..d76ecf1f29a5 100644 --- a/builds.yml +++ b/builds.yml @@ -35,6 +35,7 @@ buildTypes: - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/10.2.3/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management - IS_SIDEPANEL: false + - EXTENSION_UX_PNA25: true # for seedless onboarding (social login) - GOOGLE_PROD_CLIENT_ID - APPLE_PROD_CLIENT_ID @@ -69,6 +70,7 @@ buildTypes: - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/10.2.3/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management - IS_SIDEPANEL: false + - EXTENSION_UX_PNA25: true # for seedless onboarding (social login) - GOOGLE_BETA_CLIENT_ID - APPLE_BETA_CLIENT_ID @@ -103,6 +105,7 @@ buildTypes: - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/10.2.3/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management + - EXTENSION_UX_PNA25: true # for seedless onboarding (social login) - GOOGLE_EXPERIMENTAL_CLIENT_ID - APPLE_EXPERIMENTAL_CLIENT_ID @@ -138,6 +141,7 @@ buildTypes: - ACCOUNT_SNAPS_DIRECTORY_URL: https://metamask.github.io/snaps-directory-staging/main/account-management - EIP_4337_ENTRYPOINT: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' - IS_SIDEPANEL: false + - EXTENSION_UX_PNA25: true # for seedless onboarding (social login) - GOOGLE_FLASK_CLIENT_ID - APPLE_FLASK_CLIENT_ID @@ -460,3 +464,6 @@ env: # This should only be used for local testing, and should not be enabled in any # production builds (including beta and Flask). - FORCE_PREINSTALLED_SNAPS: 'false' + + # PNA25 (Privacy Notice) + - EXTENSION_UX_PNA25: true diff --git a/ui/components/app/toast-master/toast-master.js b/ui/components/app/toast-master/toast-master.js index cd3bafdebf75..853e778c8c3f 100644 --- a/ui/components/app/toast-master/toast-master.js +++ b/ui/components/app/toast-master/toast-master.js @@ -41,6 +41,7 @@ import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../shared/constants/ import { addPermittedAccount, hidePermittedNetworkToast, + setPna25Acknowledged, } from '../../../store/actions'; import { AvatarNetwork, @@ -108,7 +109,6 @@ import { setShowClaimSubmitToast, setShieldPausedToastLastClickedOrClosed, setShieldEndingToastLastClickedOrClosed, - setPna25Acknowledged, } from './utils'; export function ToastMaster({ location } = {}) { @@ -756,6 +756,7 @@ function ShieldEndingToast() { function Pna25Banner() { const t = useI18nContext(); + const dispatch = useDispatch(); const showPna25Banner = useSelector(selectShowPna25Banner); @@ -764,12 +765,11 @@ function Pna25Banner() { global.platform.openTab({ url: METAMETRICS_SETTINGS_LINK, }); - setPna25Acknowledged(true); }; const handleClose = () => { // Just acknowledge without opening link - setPna25Acknowledged(true); + dispatch(setPna25Acknowledged(true)); }; return ( diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index 5c4f67b57b28..ab71231f0640 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -145,6 +145,12 @@ export default function reduceMetamask(state = initialState, action) { dataCollectionForMarketing: action.value, }; + case actionConstants.SET_PNA25_ACKNOWLEDGED: + return { + ...metamaskState, + pna25Acknowledged: action.value, + }; + case actionConstants.COMPLETE_ONBOARDING: { return { ...metamaskState, diff --git a/ui/pages/onboarding-flow/metametrics/metametrics.js b/ui/pages/onboarding-flow/metametrics/metametrics.js index 2290e96fc0bf..e859ab190a07 100644 --- a/ui/pages/onboarding-flow/metametrics/metametrics.js +++ b/ui/pages/onboarding-flow/metametrics/metametrics.js @@ -20,6 +20,7 @@ import { useI18nContext } from '../../../hooks/useI18nContext'; import { setParticipateInMetaMetrics, setDataCollectionForMarketing, + setPna25Acknowledged, } from '../../../store/actions'; import { getCurrentKeyring, @@ -29,7 +30,6 @@ import { getIsParticipateInMetaMetricsSet, getParticipateInMetaMetrics, } from '../../../selectors'; -import { getRemoteFeatureFlags } from '../../../selectors/remote-feature-flags'; import { MetaMetricsEventCategory, @@ -51,7 +51,6 @@ import { } from '../../../components/component-library'; import { FirstTimeFlowType } from '../../../../shared/constants/onboarding'; import { getBrowserName } from '../../../../shared/modules/browser-runtime.utils'; -import { submitRequestToBackground } from '../../../store/background-connection'; const isFirefox = getBrowserName() === PLATFORM_FIREFOX; @@ -67,11 +66,8 @@ export default function OnboardingMetametrics() { ); const participateInMetaMetrics = useSelector(getParticipateInMetaMetrics); const dataCollectionForMarketing = useSelector(getDataCollectionForMarketing); - const remoteFeatureFlags = useSelector(getRemoteFeatureFlags); - // Check if the PNA25 feature is enabled - // extension-ux-pna25 is a boolean LaunchDarkly flag - const isPna25Enabled = remoteFeatureFlags?.extensionUxPna25; + const isPna25Enabled = process.env.EXTENSION_UX_PNA25 === 'true'; const [ isParticipateInMetaMetricsChecked, @@ -119,24 +115,24 @@ export default function OnboardingMetametrics() { const handleContinue = async (e) => { e.preventDefault(); try { + // Set pna25Acknowledged to true for all new users who complete onboarding + // This indicates they saw the updated policy during onboarding + // Only set if feature flag is enabled, as the banner only shows when flag is enabled + if (isPna25Enabled) { + try { + await dispatch(setPna25Acknowledged(true)); + } catch (error) { + // Log error but don't block onboarding if state update fails + log.error('Error setting pna25Acknowledged:', error); + } + } + if (isParticipateInMetaMetricsChecked) { dispatch( setDataCollectionForMarketing(isDataCollectionForMarketingChecked), ); dispatch(setParticipateInMetaMetrics(true)); - // Set pna25Acknowledged to true for new users who opt into metrics during onboarding - // This means they saw the updated policy during onboarding - // Only set if feature flag is enabled, as the banner only shows when flag is enabled - if (isPna25Enabled) { - try { - await submitRequestToBackground('setPna25Acknowledged', [true]); - } catch (error) { - // Log error but don't block onboarding if state update fails - log.error('Error setting pna25Acknowledged:', error); - } - } - await trackEvent({ category: MetaMetricsEventCategory.Onboarding, event: MetaMetricsEventName.AppInstalled, diff --git a/ui/pages/onboarding-flow/welcome/welcome.js b/ui/pages/onboarding-flow/welcome/welcome.js index 5eaf0578b757..380dd9a060fc 100644 --- a/ui/pages/onboarding-flow/welcome/welcome.js +++ b/ui/pages/onboarding-flow/welcome/welcome.js @@ -31,6 +31,7 @@ import { setFirstTimeFlowType, startOAuthLogin, setParticipateInMetaMetrics, + setPna25Acknowledged, getIsSeedlessOnboardingUserAuthenticated, } from '../../../store/actions'; import { @@ -420,6 +421,12 @@ export default function OnboardingWelcome() { if (!isFireFox) { // automatically set participate in meta metrics to true for social login users in chrome dispatch(setParticipateInMetaMetrics(true)); + // Set pna25Acknowledged for social login users who are automatically opted into metrics + // They skip the metametrics page, so we need to set it here + const isPna25Enabled = process.env.EXTENSION_UX_PNA25 === 'true'; + if (isPna25Enabled) { + dispatch(setPna25Acknowledged(true)); + } } } catch (error) { handleLoginError(error); diff --git a/ui/store/actionConstants.ts b/ui/store/actionConstants.ts index 08c181c0dda9..65302ae05e5d 100644 --- a/ui/store/actionConstants.ts +++ b/ui/store/actionConstants.ts @@ -101,6 +101,7 @@ export const UPDATE_CUSTOM_NONCE = 'UPDATE_CUSTOM_NONCE'; export const SET_PARTICIPATE_IN_METAMETRICS = 'SET_PARTICIPATE_IN_METAMETRICS'; export const SET_DATA_COLLECTION_FOR_MARKETING = 'SET_DATA_COLLECTION_FOR_MARKETING'; +export const SET_PNA25_ACKNOWLEDGED = 'SET_PNA25_ACKNOWLEDGED'; export const DELETE_METAMETRICS_DATA_MODAL_OPEN = 'DELETE_METAMETRICS_DATA_MODAL_OPEN'; export const DELETE_METAMETRICS_DATA_MODAL_CLOSE = diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 0e356d40cd65..80c6a3d08e72 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -4783,6 +4783,19 @@ export function setDataCollectionForMarketing( }; } +export function setPna25Acknowledged( + acknowledged: boolean, +): ThunkAction, MetaMaskReduxState, unknown, AnyAction> { + return async (dispatch: MetaMaskReduxDispatch) => { + log.debug(`background.setPna25Acknowledged`); + await submitRequestToBackground('setPna25Acknowledged', [acknowledged]); + dispatch({ + type: actionConstants.SET_PNA25_ACKNOWLEDGED, + value: acknowledged, + }); + }; +} + /** * Sets marketing consent with OAuth service for social login users. * From a23ac5d3a9a2629e9a8bd1ebbd8ef2ee9d732745 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Wed, 26 Nov 2025 22:29:45 +0530 Subject: [PATCH 24/30] added pna banner --- test/e2e/tests/settings/state-logs.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/tests/settings/state-logs.json b/test/e2e/tests/settings/state-logs.json index c40d0074bec1..551a9d0c5af9 100644 --- a/test/e2e/tests/settings/state-logs.json +++ b/test/e2e/tests/settings/state-logs.json @@ -710,6 +710,7 @@ "pendingRevocations": [], "pendingShieldCohort": "null", "pendingShieldCohortTxType": "null", + "pna25Acknowledged": "boolean", "permissionActivityLog": [ { "id": "number", From af8f50e5575b8b30ed0ac558ef5a1db571f4df70 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Thu, 27 Nov 2025 15:24:16 +0530 Subject: [PATCH 25/30] removed unused code --- ui/ducks/metamask/metamask.js | 6 ------ ui/pages/onboarding-flow/metametrics/metametrics.js | 3 +-- ui/store/actionConstants.ts | 1 - ui/store/actions.ts | 6 +----- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index ab71231f0640..5c4f67b57b28 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -145,12 +145,6 @@ export default function reduceMetamask(state = initialState, action) { dataCollectionForMarketing: action.value, }; - case actionConstants.SET_PNA25_ACKNOWLEDGED: - return { - ...metamaskState, - pna25Acknowledged: action.value, - }; - case actionConstants.COMPLETE_ONBOARDING: { return { ...metamaskState, diff --git a/ui/pages/onboarding-flow/metametrics/metametrics.js b/ui/pages/onboarding-flow/metametrics/metametrics.js index e859ab190a07..a5acda48061a 100644 --- a/ui/pages/onboarding-flow/metametrics/metametrics.js +++ b/ui/pages/onboarding-flow/metametrics/metametrics.js @@ -67,8 +67,7 @@ export default function OnboardingMetametrics() { const participateInMetaMetrics = useSelector(getParticipateInMetaMetrics); const dataCollectionForMarketing = useSelector(getDataCollectionForMarketing); // Check if the PNA25 feature is enabled - const isPna25Enabled = process.env.EXTENSION_UX_PNA25 === 'true'; - + const isPna25Enabled = process.env.EXTENSION_UX_PNA25; const [ isParticipateInMetaMetricsChecked, setIsParticipateInMetaMetricsChecked, diff --git a/ui/store/actionConstants.ts b/ui/store/actionConstants.ts index 65302ae05e5d..08c181c0dda9 100644 --- a/ui/store/actionConstants.ts +++ b/ui/store/actionConstants.ts @@ -101,7 +101,6 @@ export const UPDATE_CUSTOM_NONCE = 'UPDATE_CUSTOM_NONCE'; export const SET_PARTICIPATE_IN_METAMETRICS = 'SET_PARTICIPATE_IN_METAMETRICS'; export const SET_DATA_COLLECTION_FOR_MARKETING = 'SET_DATA_COLLECTION_FOR_MARKETING'; -export const SET_PNA25_ACKNOWLEDGED = 'SET_PNA25_ACKNOWLEDGED'; export const DELETE_METAMETRICS_DATA_MODAL_OPEN = 'DELETE_METAMETRICS_DATA_MODAL_OPEN'; export const DELETE_METAMETRICS_DATA_MODAL_CLOSE = diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 80c6a3d08e72..cd76fd54a477 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -4786,13 +4786,9 @@ export function setDataCollectionForMarketing( export function setPna25Acknowledged( acknowledged: boolean, ): ThunkAction, MetaMaskReduxState, unknown, AnyAction> { - return async (dispatch: MetaMaskReduxDispatch) => { + return async () => { log.debug(`background.setPna25Acknowledged`); await submitRequestToBackground('setPna25Acknowledged', [acknowledged]); - dispatch({ - type: actionConstants.SET_PNA25_ACKNOWLEDGED, - value: acknowledged, - }); }; } From 1252cb73d16db95fe48075566d3faa4a3b22e508 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Thu, 27 Nov 2025 15:28:24 +0530 Subject: [PATCH 26/30] nit fix --- ui/pages/onboarding-flow/welcome/welcome.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/pages/onboarding-flow/welcome/welcome.js b/ui/pages/onboarding-flow/welcome/welcome.js index 380dd9a060fc..5cac613284a7 100644 --- a/ui/pages/onboarding-flow/welcome/welcome.js +++ b/ui/pages/onboarding-flow/welcome/welcome.js @@ -423,8 +423,7 @@ export default function OnboardingWelcome() { dispatch(setParticipateInMetaMetrics(true)); // Set pna25Acknowledged for social login users who are automatically opted into metrics // They skip the metametrics page, so we need to set it here - const isPna25Enabled = process.env.EXTENSION_UX_PNA25 === 'true'; - if (isPna25Enabled) { + if (process.env.EXTENSION_UX_PNA25) { dispatch(setPna25Acknowledged(true)); } } From b70dd38affa37ae47ff45c66e9da4c74035cf863 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Thu, 27 Nov 2025 15:42:37 +0530 Subject: [PATCH 27/30] updated welcome logic --- test/e2e/tests/settings/state-logs.json | 1 - ui/components/app/toast-master/selectors.ts | 22 ++++++++++----------- ui/ducks/app/app.ts | 2 -- ui/pages/onboarding-flow/welcome/welcome.js | 6 ------ 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/test/e2e/tests/settings/state-logs.json b/test/e2e/tests/settings/state-logs.json index 7c7785ce26c6..d4acae38c362 100644 --- a/test/e2e/tests/settings/state-logs.json +++ b/test/e2e/tests/settings/state-logs.json @@ -69,7 +69,6 @@ "onboardedInThisUISession": "boolean", "openMetaMaskTabs": {}, "pendingTokens": {}, - "pna25Acknowledged": "boolean", "qrCodeData": "null", "removeNftMessage": "string", "requestAccountTabs": {}, diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index 6a1318bebdb8..8b1e3e92d7b2 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -242,29 +242,29 @@ export function selectShowShieldEndingToast( /** * Determines if the PNA25 banner should be shown based on: * - User has completed onboarding (completedOnboarding === true) - * - LaunchDarkly feature flag (extension-ux-pna25) is enabled (boolean) + * - LaunchDarkly flag (extensionUxPna25) is enabled for existing users + * - Build-time environment variable (EXTENSION_UX_PNA25) is enabled * - User has opted into metrics (participateInMetaMetrics === true) - * - User is an EXISTING user (pna25Acknowledged === false) - * New users will have pna25Acknowledged = true (saw updated policy during onboarding) - * Existing users will have pna25Acknowledged = false (default, need to show banner) + * - User hasn't acknowledged the banner yet (pna25Acknowledged === false) + * + * Regular new users: Go through metametrics page → pna25Acknowledged = true → don't see banner + * Social login users: Skip metametrics page → pna25Acknowledged = false → see banner + * Existing users: pna25Acknowledged = false (default) → see banner * * @param state - The application state containing the banner data. * @returns Boolean indicating whether to show the banner */ export function selectShowPna25Banner(state: Pick): boolean { - const { - completedOnboarding, - participateInMetaMetrics, - pna25Acknowledged, - remoteFeatureFlags, - } = state.metamask || {}; + const { completedOnboarding, participateInMetaMetrics, pna25Acknowledged } = + state.metamask || {}; // Only show to users who have completed onboarding if (!completedOnboarding) { return false; // User hasn't completed onboarding yet } - const isPna25Enabled = remoteFeatureFlags?.extensionUxPna25; + // Check if the PNA25 feature is enabled via build-time environment variable + const isPna25Enabled = process.env.EXTENSION_UX_PNA25 === 'true'; // Check all conditions if (!isPna25Enabled) { diff --git a/ui/ducks/app/app.ts b/ui/ducks/app/app.ts index b2866a2b237b..c3be8d5c3051 100644 --- a/ui/ducks/app/app.ts +++ b/ui/ducks/app/app.ts @@ -42,7 +42,6 @@ type AppState = { }; alertOpen: boolean; alertMessage: string | null; - pna25Acknowledged: boolean; qrCodeData: { type?: string | null; values?: { address?: string | null }; @@ -175,7 +174,6 @@ const initialState: AppState = { }, alertOpen: false, alertMessage: null, - pna25Acknowledged: false, qrCodeData: null, networkDropdownOpen: false, importNftsModal: { open: false }, diff --git a/ui/pages/onboarding-flow/welcome/welcome.js b/ui/pages/onboarding-flow/welcome/welcome.js index 5cac613284a7..5eaf0578b757 100644 --- a/ui/pages/onboarding-flow/welcome/welcome.js +++ b/ui/pages/onboarding-flow/welcome/welcome.js @@ -31,7 +31,6 @@ import { setFirstTimeFlowType, startOAuthLogin, setParticipateInMetaMetrics, - setPna25Acknowledged, getIsSeedlessOnboardingUserAuthenticated, } from '../../../store/actions'; import { @@ -421,11 +420,6 @@ export default function OnboardingWelcome() { if (!isFireFox) { // automatically set participate in meta metrics to true for social login users in chrome dispatch(setParticipateInMetaMetrics(true)); - // Set pna25Acknowledged for social login users who are automatically opted into metrics - // They skip the metametrics page, so we need to set it here - if (process.env.EXTENSION_UX_PNA25) { - dispatch(setPna25Acknowledged(true)); - } } } catch (error) { handleLoginError(error); From 7af809ad070837f22c3e0f1ae3a11cec6c961544 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Thu, 27 Nov 2025 16:43:53 +0530 Subject: [PATCH 28/30] lint fix --- .../errors-after-init-opt-in-ui-state.json | 1 - ui/components/app/toast-master/selectors.ts | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index bdd04c68687c..3eac46e3998c 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -14,7 +14,6 @@ "modal": "object", "alertOpen": "boolean", "alertMessage": null, - "pna25Acknowledged": "boolean", "qrCodeData": null, "networkDropdownOpen": "boolean", "importNftsModal": "object", diff --git a/ui/components/app/toast-master/selectors.ts b/ui/components/app/toast-master/selectors.ts index 8b1e3e92d7b2..fc9ee581fd72 100644 --- a/ui/components/app/toast-master/selectors.ts +++ b/ui/components/app/toast-master/selectors.ts @@ -242,8 +242,7 @@ export function selectShowShieldEndingToast( /** * Determines if the PNA25 banner should be shown based on: * - User has completed onboarding (completedOnboarding === true) - * - LaunchDarkly flag (extensionUxPna25) is enabled for existing users - * - Build-time environment variable (EXTENSION_UX_PNA25) is enabled + * - LaunchDarkly feature flag (extensionUxPna25) is enabled * - User has opted into metrics (participateInMetaMetrics === true) * - User hasn't acknowledged the banner yet (pna25Acknowledged === false) * @@ -255,16 +254,20 @@ export function selectShowShieldEndingToast( * @returns Boolean indicating whether to show the banner */ export function selectShowPna25Banner(state: Pick): boolean { - const { completedOnboarding, participateInMetaMetrics, pna25Acknowledged } = - state.metamask || {}; + const { + completedOnboarding, + participateInMetaMetrics, + pna25Acknowledged, + remoteFeatureFlags, + } = state.metamask || {}; // Only show to users who have completed onboarding if (!completedOnboarding) { return false; // User hasn't completed onboarding yet } - // Check if the PNA25 feature is enabled via build-time environment variable - const isPna25Enabled = process.env.EXTENSION_UX_PNA25 === 'true'; + // For onboarding screen, we use local flag and for existing users, we use LaunchDarkly flag + const isPna25Enabled = remoteFeatureFlags?.extensionUxPna25; // Check all conditions if (!isPna25Enabled) { From 5236ca25936de0002d8afaf33c4f887660479717 Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Thu, 27 Nov 2025 17:45:36 +0530 Subject: [PATCH 29/30] added metrics logic to settings --- ui/hooks/useMetametrics.ts | 13 +++++++++++-- ui/pages/onboarding-flow/welcome/welcome.js | 5 +++++ ui/selectors/metametrics.js | 2 ++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/ui/hooks/useMetametrics.ts b/ui/hooks/useMetametrics.ts index ca3126f8483c..69470f418943 100644 --- a/ui/hooks/useMetametrics.ts +++ b/ui/hooks/useMetametrics.ts @@ -1,11 +1,14 @@ import { useState, useCallback } from 'react'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import log from 'loglevel'; import { setParticipateInMetaMetrics, + setPna25Acknowledged, showLoadingIndication, hideLoadingIndication, } from '../store/actions'; +import { getPna25Acknowledged } from '../selectors/metametrics'; +import { getRemoteFeatureFlags } from '../selectors/remote-feature-flags'; /** * Provides a hook to enable MetaMetrics tracking. @@ -22,6 +25,8 @@ export function useEnableMetametrics(): { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const pna25Acknowledged = useSelector(getPna25Acknowledged); + const remoteFeatureFlags = useSelector(getRemoteFeatureFlags); const enableMetametrics = useCallback(async () => { setLoading(true); @@ -30,6 +35,10 @@ export function useEnableMetametrics(): { try { await dispatch(setParticipateInMetaMetrics(true)); + const isPna25Enabled = remoteFeatureFlags?.extensionUxPna25; + if (isPna25Enabled && pna25Acknowledged === false) { + await dispatch(setPna25Acknowledged(true)); + } } catch (e) { setError(e instanceof Error ? e.message : 'An unexpected error occurred'); log.error(e); @@ -40,7 +49,7 @@ export function useEnableMetametrics(): { } dispatch(hideLoadingIndication()); - }, [dispatch]); + }, [dispatch, pna25Acknowledged, remoteFeatureFlags]); return { enableMetametrics, diff --git a/ui/pages/onboarding-flow/welcome/welcome.js b/ui/pages/onboarding-flow/welcome/welcome.js index 5eaf0578b757..3d99991580fd 100644 --- a/ui/pages/onboarding-flow/welcome/welcome.js +++ b/ui/pages/onboarding-flow/welcome/welcome.js @@ -31,6 +31,7 @@ import { setFirstTimeFlowType, startOAuthLogin, setParticipateInMetaMetrics, + setPna25Acknowledged, getIsSeedlessOnboardingUserAuthenticated, } from '../../../store/actions'; import { @@ -420,6 +421,10 @@ export default function OnboardingWelcome() { if (!isFireFox) { // automatically set participate in meta metrics to true for social login users in chrome dispatch(setParticipateInMetaMetrics(true)); + // Set pna25Acknowledged to true for social login users if feature flag is enabled + if (process.env.EXTENSION_UX_PNA25) { + dispatch(setPna25Acknowledged(true)); + } } } catch (error) { handleLoginError(error); diff --git a/ui/selectors/metametrics.js b/ui/selectors/metametrics.js index cdf1b1dba7c0..f036091251e2 100644 --- a/ui/selectors/metametrics.js +++ b/ui/selectors/metametrics.js @@ -14,6 +14,8 @@ export const getParticipateInMetaMetrics = (state) => export const getIsParticipateInMetaMetricsSet = (state) => state.metamask.participateInMetaMetrics !== null; +export const getPna25Acknowledged = (state) => state.metamask.pna25Acknowledged; + export const getLatestMetricsEventTimestamp = (state) => state.metamask.latestNonAnonymousEventTimestamp; From 7a157ea4e7218354e8066eb072602ca294c7bf3a Mon Sep 17 00:00:00 2001 From: NidhiKJha Date: Fri, 28 Nov 2025 02:43:30 +0530 Subject: [PATCH 30/30] updated sentry state --- app/scripts/constants/sentry-state.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index 79309ccce204..c98f5a270b12 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -419,7 +419,6 @@ export const SENTRY_UI_STATE = { welcomeScreenSeen: true, slides: false, confirmationExchangeRates: true, - pna25Acknowledged: false, }, metamask: { ...flattenedBackgroundStateMask,