|
| 1 | +# Remote Feature Flags |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This document outlines the process for adding and managing remote feature flags in MetaMask (Extension and Mobile). Remote feature flags allow us to control feature availability and behavior in production without requiring a new release. They are created on the LaunchDarkly platform and distributed by our internal API [ClientConfigAPI](https://github.com/consensys-vertical-apps/mmwp-client-config-api). Please read the [Remote feature flags ADR](https://github.com/MetaMask/decisions/pull/43) for more details. |
| 6 | + |
| 7 | +### Choosing the Right Feature Flag Type |
| 8 | + |
| 9 | +Choose the appropriate feature flag type based on your needs: |
| 10 | + |
| 11 | +**1. Boolean flag**: Use when you need a simple ON/OFF toggle for a feature. In this example: `my-feature` is enabled for all users. |
| 12 | + |
| 13 | +```json |
| 14 | +{ |
| 15 | + "name": "my-feature", |
| 16 | + "value": true |
| 17 | +} |
| 18 | +``` |
| 19 | + |
| 20 | +**2. Object flag with scope based on "threshold"**: Use for: |
| 21 | + |
| 22 | +- controlling % of users who see a feature |
| 23 | + In this example: the feature is enabled for 30% of users and disabled for 70% of users. |
| 24 | + |
| 25 | +```json |
| 26 | +[ |
| 27 | + { |
| 28 | + "name": "feature is ON", |
| 29 | + "scope": { |
| 30 | + "type": "threshold", |
| 31 | + "value": 0.3 |
| 32 | + }, |
| 33 | + "value": true |
| 34 | + }, |
| 35 | + { |
| 36 | + "name": "feature is OFF", |
| 37 | + "scope": { |
| 38 | + "type": "threshold", |
| 39 | + "value": 1 |
| 40 | + }, |
| 41 | + "value": false |
| 42 | + } |
| 43 | +] |
| 44 | +``` |
| 45 | + |
| 46 | +- A/B testing different feature variants |
| 47 | + In this configuration, the swap button will be blue for 25% of users, red for 25% of users, green for 25% of users, and yellow for 25% of users |
| 48 | + |
| 49 | +```json |
| 50 | +[[ |
| 51 | + { |
| 52 | + "name": "blue", |
| 53 | + "scope": { |
| 54 | + "type": "threshold", |
| 55 | + "value": 0.25 |
| 56 | + }, |
| 57 | + "value": "0000FF" |
| 58 | + }, |
| 59 | + { |
| 60 | + "name": "red", |
| 61 | + "scope": { |
| 62 | + "type": "threshold", |
| 63 | + "value": 0.5 |
| 64 | + }, |
| 65 | + "value": "FF0000" |
| 66 | + }, |
| 67 | + { |
| 68 | + "name": "green", |
| 69 | + "scope": { |
| 70 | + "type": "threshold", |
| 71 | + "value": 0.75 |
| 72 | + }, |
| 73 | + "value": "00FF00" |
| 74 | + }, |
| 75 | + { |
| 76 | + "name": "yellow", |
| 77 | + "scope": { |
| 78 | + "type": "threshold", |
| 79 | + "value": 1 |
| 80 | + }, |
| 81 | + "value": "FFF500" |
| 82 | + } |
| 83 | +] |
| 84 | +``` |
| 85 | + |
| 86 | +The distribution is deterministic based on the user's `metametricsId`, ensuring consistent group assignment across sessions. |
| 87 | + |
| 88 | +## Implementation Guide |
| 89 | + |
| 90 | +### 1. Creating feature flag in LaunchDarkly. |
| 91 | + |
| 92 | +1. Name your flag with team prefix (e.g., `confirmations-blockaid`, `ramps-card`) |
| 93 | +2. Request LaunchDarkly access from TechOps. Here's a template email: |
| 94 | + |
| 95 | +``` |
| 96 | +Subject: Requesting access to LaunchDarkly for [team] |
| 97 | + |
| 98 | +Dear TechOps, |
| 99 | +Can I please get access to LaunchDarkly with the "Metamask Extension and Mobile" role? (role key: `metamask-config`) |
| 100 | + |
| 101 | +Thanks! |
| 102 | +``` |
| 103 | + |
| 104 | +3. Select appropriate project in LaunchDarkly: |
| 105 | + - Extension: "MetaMask Client Config API - Extension" |
| 106 | + - Mobile: "MetaMask Client Config API - Mobile" |
| 107 | +4. Create the flag following the [LaunchDarkly flag creation guide](https://docs.launchdarkly.com/home/flags/new) |
| 108 | + |
| 109 | +### 2. Using Remote Feature Flags in MetaMask clients |
| 110 | + |
| 111 | +#### Extension |
| 112 | + |
| 113 | +To use a remote feature flag in the MetaMask extension, follow these steps: |
| 114 | + |
| 115 | +1. Add selector from `import { getRemoteFeatureFlags } from 'path/to/selectors';` in your component file. |
| 116 | +2. Use the selector to get the feature flag value. |
| 117 | + |
| 118 | +```typescript |
| 119 | +const { testFeatureFlag } = getRemoteFeatureFlags(state); |
| 120 | +``` |
| 121 | + |
| 122 | +3. Use the feature flag value in your component. |
| 123 | + |
| 124 | +```typescript |
| 125 | +if (testFeatureFlag) { |
| 126 | + // Use the feature flag value |
| 127 | +} |
| 128 | +``` |
| 129 | + |
| 130 | +#### Mobile |
| 131 | + |
| 132 | +##### Create a selector for your feature flag |
| 133 | + |
| 134 | +- Location: `app/selectors/featureFlagsController` |
| 135 | +- Create a new folder with your feature flag name |
| 136 | +- Follow the structure from `app/selectors/featureFlagsController/minimumAppVersion` |
| 137 | + |
| 138 | +Your selector must include: |
| 139 | + |
| 140 | +- State selectors for each feature flag value |
| 141 | +- Fallback values for invalid remote flag values |
| 142 | +- TypeScript types |
| 143 | +- Unit tests |
| 144 | +- Any required business logic for value manipulation |
| 145 | + |
| 146 | +> [!IMPORTANT] |
| 147 | +> Always use the specific feature flag selector when accessing values. Accessing feature flags directly from Redux state or the main selector bypasses fallback values and can cause crashes. |
| 148 | +
|
| 149 | +### 3. Testing Remote Feature Flags |
| 150 | + |
| 151 | +#### Extension |
| 152 | + |
| 153 | +##### Local Feature Flag Override |
| 154 | + |
| 155 | +- Developers can override `remoteFeatureFlag` values by defining them in `.manifest-overrides.json` and enable `MANIFEST_OVERRIDES=.manifest-overrides.json` in the `.metamaskrc.dist` locally. |
| 156 | +- These overrides take precedence over values received from the controller |
| 157 | +- Accessible in all development environments: |
| 158 | + - Local builds |
| 159 | + - Webpack builds |
| 160 | + - E2E tests |
| 161 | + |
| 162 | +##### Developer Validation |
| 163 | + |
| 164 | +##### A. Local Build |
| 165 | + |
| 166 | +1. Define overrides in `remoteFeatureFlags` in `manifest-overrides.json`: |
| 167 | + |
| 168 | +```json |
| 169 | +{ |
| 170 | + "_flags": { |
| 171 | + "remoteFeatureFlags": { |
| 172 | + "testFlagForThreshold": { |
| 173 | + "name": "test-flag", |
| 174 | + "value": "121212" |
| 175 | + } |
| 176 | + } |
| 177 | + } |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | +2. Verify in Developer Options: |
| 182 | + - Open extension |
| 183 | + - Click on the account menu (top-right corner) |
| 184 | + - Select "Settings" > "Developer Options" |
| 185 | + - Look for "Remote Feature Flags" section to verify your overrides |
| 186 | + |
| 187 | +##### B. E2E Test |
| 188 | + |
| 189 | +Add the customized value in your test configuration: |
| 190 | + |
| 191 | +```typescript |
| 192 | +fixtures: new FixtureBuilder() |
| 193 | + .withMetaMetricsController({ |
| 194 | + metaMetricsId: MOCK_META_METRICS_ID, |
| 195 | + participateInMetaMetrics: true, |
| 196 | + }) |
| 197 | + .build(), |
| 198 | +``` |
| 199 | + |
| 200 | +#### Mobile |
| 201 | + |
| 202 | +##### Local Feature Flag Override |
| 203 | + |
| 204 | +1. Set environment variable in `.js.env` and run the app. This forces the fallback values to override any remote values. |
| 205 | + |
| 206 | +``` |
| 207 | +OVERRIDE_REMOTE_FEATURE_FLAGS=TRUE |
| 208 | +``` |
| 209 | + |
| 210 | +2. Test different feature flag scenarios: |
| 211 | + - Modify the default values in your feature flag selector |
| 212 | + - Run the application to verify behavior with different values |
| 213 | + - Remember to test both override and non-override scenarios |
0 commit comments