…nchDarkly flag cp-13.40.0 (#44496)
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->
## **Description**
<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->
The scam questionnaire (introduced in #43822) currently ships
hardcoded-on, gated only by client-side conditions (MetaMask origin +
transfer type + Blockaid `Malicious` verdict). There is no server-side
rollout control or kill-switch — if the questionnaire causes unexpected
issues at launch, our only remediation is a client release.
This PR adds a LaunchDarkly-backed remote feature flag
(`productSafetyScamQuestionnaireEnabled`) as a rollout gate on top of
the existing conditions. Flag off (the default) → users see the
pre-existing Blockaid danger alert modal exactly as they did before
#43822. Flag on → users see the questionnaire. `footer.tsx` is
untouched; the flag check is folded into `isScamQuestionnaireRequired`
inside `useSendScamQuestionnaire`, so the existing branch point in the
footer keys off the same boolean it already reads.
### Implementation notes
- Uses `useABTest` with `{ trackExposure: false }`. This is a staged
rollout, not an A/B test — `Experiment Viewed` events are suppressed so
the experiments dashboard stays clean. `useABTest` is used intentionally
so a future A/B experiment is a small diff (add variants, register an
analytics mapping, flip `trackExposure` back on).
- Safe fallback: when LaunchDarkly is unreachable or the flag is unset,
`useABTest` resolves to the local `control` variant with
`showQuestionnaire: false`, matching the "default off in prod"
acceptance criterion.
- Config lives in `shared/lib/ab-testing/configs/scam-questionnaire.ts`
alongside sibling A/B test configs. No analytics mapping is registered
(rollout, not experiment).
- E2E registry entry is set with `inProd: false` and defaults to 100%
control. This should be flipped to `inProd: true` once the LaunchDarkly
flag is fully configured for the production environment.
## **Changelog**
<!--
If this PR is not End-User-Facing and should not show up in the
CHANGELOG, you can choose to either:
1. Write `CHANGELOG entry: null`
2. Label with `no-changelog`
If this PR is End-User-Facing, please write a short User-Facing
description in the past tense like:
`CHANGELOG entry: Added a new tab for users to see their NFTs`
`CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`
(This helps the Release Engineer do their job more quickly and
accurately)
-->
CHANGELOG entry: null
## **Related issues**
Fixes:
[PSAFE-558](https://consensyssoftware.atlassian.net/browse/PSAFE-558)
## **Manual testing steps**
### Scenario A — flag off (default; existing modal path)
1. Ensure `.manifest-overrides.json` does not override
`productSafetyScamQuestionnaireEnabled` (or the file does not exist).
2. Build and load the extension.
3. Initiate a send transaction from within MetaMask to a recipient that
Blockaid flags as `Malicious`.
4. On the confirmation screen, click the red "Confirm" button.
5. Verify the pre-existing "Are you sure?" Blockaid danger alert modal
appears — not the scam questionnaire.
### Scenario B — flag on (questionnaire path)
1. Create or update `.manifest-overrides.json` in the repo root:
```json
{
"_flags": {
"remoteFeatureFlags": {
"productSafetyScamQuestionnaireEnabled": {
"name": "treatment",
"value": { "showQuestionnaire": true }
}
}
}
}
```
2. Ensure `.metamaskrc` has
`MANIFEST_OVERRIDES=.manifest-overrides.json`.
3. Rebuild and reload the extension.
4. Repeat the send flow from Scenario A.
5. Verify the scam questionnaire modal appears when clicking the red
Confirm button — not the pre-existing alert modal.
6. Complete the questionnaire (clean pass, bypass, or reject) and verify
behavior matches the current questionnaire flow (unchanged by this PR).
<!--
## **Screenshots/Recordings**
### **Before**
### **After**
-->
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
[PSAFE-558]:
https://consensyssoftware.atlassian.net/browse/PSAFE-558?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes which safety UI appears on malicious wallet-initiated sends at
confirm time; default-off control limits blast radius but
misconfiguration could hide the questionnaire or leave it on
unexpectedly.
>
> **Overview**
> Adds a **LaunchDarkly remote rollout**
(`productSafetyScamQuestionnaireEnabled`) so the send-flow scam
questionnaire is not always on. New shared config maps **control** →
questionnaire off and **treatment** → on.
>
> **`useSendScamQuestionnaire`** now reads the flag via **`useABTest`**
with **`trackExposure: false`** (staged rollout, not an experiment).
**`isScamQuestionnaireRequired`** only stays true when
**`variant.showQuestionnaire`** is true in addition to the existing
MetaMask send + Blockaid malicious + unconfirmed alert checks. Flag off
or unset → users keep the prior Blockaid danger alert modal path; the
confirm footer still branches on the same boolean.
>
> Registers the flag in the **E2E feature-flag registry** (`inProd:
false`, default control). Unit tests cover control vs treatment and the
footer test sets **`remoteFeatureFlags`** to **`treatment`** so the
questionnaire path still passes.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
c8f1394. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Description
The scam questionnaire (introduced in #43822) currently ships
hardcoded-on, gated only by client-side conditions (MetaMask origin +
transfer type + Blockaid
Maliciousverdict). There is no server-siderollout control or kill-switch — if the questionnaire causes unexpected
issues at launch, our only remediation is a client release.
This PR adds a LaunchDarkly-backed remote feature flag
(
productSafetyScamQuestionnaireEnabled) as a rollout gate on top ofthe existing conditions. Flag off (the default) → users see the
pre-existing Blockaid danger alert modal exactly as they did before
#43822. Flag on → users see the questionnaire.
footer.tsxisuntouched; the flag check is folded into
isScamQuestionnaireRequiredinside
useSendScamQuestionnaire, so the existing branch point in thefooter keys off the same boolean it already reads.
Implementation notes
useABTestwith{ trackExposure: false }. This is a stagedrollout, not an A/B test —
Experiment Viewedevents are suppressed sothe experiments dashboard stays clean.
useABTestis used intentionallyso a future A/B experiment is a small diff (add variants, register an
analytics mapping, flip
trackExposureback on).useABTestresolves to the localcontrolvariant withshowQuestionnaire: false, matching the "default off in prod"acceptance criterion.
shared/lib/ab-testing/configs/scam-questionnaire.tsalongside sibling A/B test configs. No analytics mapping is registered
(rollout, not experiment).
inProd: falseand defaults to 100%control. This should be flipped to
inProd: trueonce the LaunchDarklyflag is fully configured for the production environment.
Changelog
CHANGELOG entry: null
Related issues
Fixes:
PSAFE-558
Manual testing steps
Scenario A — flag off (default; existing modal path)
.manifest-overrides.jsondoes not overrideproductSafetyScamQuestionnaireEnabled(or the file does not exist).Blockaid flags as
Malicious.appears — not the scam questionnaire.
Scenario B — flag on (questionnaire path)
.manifest-overrides.jsonin the repo root:{ "_flags": { "remoteFeatureFlags": { "productSafetyScamQuestionnaireEnabled": { "name": "treatment", "value": { "showQuestionnaire": true } } } } }.metamaskrchasMANIFEST_OVERRIDES=.manifest-overrides.json.Confirm button — not the pre-existing alert modal.
behavior matches the current questionnaire flow (unchanged by this PR).
Pre-merge author checklist
Docs and MetaMask
Extension Coding
Standards.
if applicable
guidelines).
Not required for external contributors.
Pre-merge reviewer checklist
app, test code being changed).
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
Note
Medium Risk
Changes which safety UI appears on malicious wallet-initiated sends at
confirm time; default-off control limits blast radius but
misconfiguration could hide the questionnaire or leave it on
unexpectedly.
Overview
Adds a LaunchDarkly remote rollout
(
productSafetyScamQuestionnaireEnabled) so the send-flow scamquestionnaire is not always on. New shared config maps control →
questionnaire off and treatment → on.
useSendScamQuestionnairenow reads the flag viauseABTestwith
trackExposure: false(staged rollout, not an experiment).isScamQuestionnaireRequiredonly stays true whenvariant.showQuestionnaireis true in addition to the existingMetaMask send + Blockaid malicious + unconfirmed alert checks. Flag off
or unset → users keep the prior Blockaid danger alert modal path; the
confirm footer still branches on the same boolean.
Registers the flag in the E2E feature-flag registry (
inProd: false, default control). Unit tests cover control vs treatment and thefooter test sets
remoteFeatureFlagstotreatmentso thequestionnaire path still passes.
Reviewed by Cursor Bugbot for commit
c8f1394. Bugbot is set up for automated
code reviews on this repo. Configure
here.