Skip to content

Commit 99e5d3b

Browse files
authored
chore: add remote feature flag for multichain accounts (#33112)
<!-- 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? --> [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/33112?quickstart=1) Add remote feature flag for multichain accounts. The feature flag structure is, ```ts { enabled: boolean; featureVersion: string; minimumVersion: string; }; ``` ## **Related issues** Fixes: None ## **Manual testing steps** Not applicable ## **Screenshots/Recordings** applicable ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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.
1 parent 086222d commit 99e5d3b

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed

ui/selectors/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from './accounts';
1010
export * from './remote-feature-flags';
1111
export * from './origin-throttling';
1212
export * from './multichain/networks';
13+
export * from './multichain-accounts';
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { type RemoteFeatureFlagsState } from '../remote-feature-flags';
2+
import {
3+
type MultichainAccountsFeatureFlag,
4+
getIsMultichainAccountsState1Enabled,
5+
getIsMultichainAccountsState2Enabled,
6+
} from './feature-flags';
7+
8+
jest.mock('../../../package.json', () => ({
9+
version: '12.0.0',
10+
}));
11+
12+
type TestState = RemoteFeatureFlagsState & {
13+
metamask: {
14+
remoteFeatureFlags: {
15+
enableMultichainAccounts: MultichainAccountsFeatureFlag;
16+
};
17+
};
18+
};
19+
20+
const disabledStateMock: MultichainAccountsFeatureFlag = {
21+
enabled: false,
22+
featureVersion: null,
23+
minimumVersion: null,
24+
};
25+
26+
const state1Mock: MultichainAccountsFeatureFlag = {
27+
enabled: true,
28+
featureVersion: '1',
29+
minimumVersion: '13.0.0',
30+
};
31+
32+
const state2Mock: MultichainAccountsFeatureFlag = {
33+
enabled: true,
34+
featureVersion: '2',
35+
minimumVersion: '14.0.0',
36+
};
37+
38+
const getMockState = (
39+
multichainAccountsFeatureFlagMock: MultichainAccountsFeatureFlag,
40+
): TestState =>
41+
Object.freeze({
42+
metamask: {
43+
remoteFeatureFlags: {
44+
enableMultichainAccounts: multichainAccountsFeatureFlagMock,
45+
},
46+
},
47+
});
48+
49+
describe('Multichain Accounts Feature Flags', () => {
50+
beforeEach(() => {
51+
jest.clearAllMocks(); // Clears all mocks before each test
52+
});
53+
54+
describe('getIsMultichainAccountsState1Enabled', () => {
55+
it('returns false for disabled state', () => {
56+
expect(
57+
getIsMultichainAccountsState1Enabled(getMockState(disabledStateMock)),
58+
).toBe(false);
59+
});
60+
61+
it('returns true for state 1', () => {
62+
expect(
63+
getIsMultichainAccountsState1Enabled(getMockState(state1Mock)),
64+
).toBe(true);
65+
});
66+
67+
it('returns true for state 2', () => {
68+
expect(
69+
getIsMultichainAccountsState1Enabled(getMockState(state2Mock)),
70+
).toBe(true);
71+
});
72+
});
73+
74+
describe('getIsMultichainAccountsState2Enabled', () => {
75+
it('returns false for disabled state', () => {
76+
expect(
77+
getIsMultichainAccountsState2Enabled(getMockState(disabledStateMock)),
78+
).toBe(false);
79+
});
80+
81+
it('returns false for state 1', () => {
82+
expect(
83+
getIsMultichainAccountsState2Enabled(getMockState(state1Mock)),
84+
).toBe(false);
85+
});
86+
87+
it('returns true for state 2', () => {
88+
expect(
89+
getIsMultichainAccountsState2Enabled(getMockState(state2Mock)),
90+
).toBe(true);
91+
});
92+
});
93+
});
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {
2+
Infer,
3+
object,
4+
boolean,
5+
nullable,
6+
string,
7+
assert,
8+
} from '@metamask/superstruct';
9+
import semver from 'semver';
10+
import packageJson from '../../../package.json';
11+
import {
12+
getRemoteFeatureFlags,
13+
type RemoteFeatureFlagsState,
14+
} from '../remote-feature-flags';
15+
16+
/**
17+
* Feature flag structure for multichain accounts features
18+
*/
19+
const MultichainAccountsFeatureFlag = object({
20+
enabled: boolean(),
21+
featureVersion: nullable(string()),
22+
minimumVersion: nullable(string()),
23+
});
24+
25+
/**
26+
* Feature flag type for multichain accounts features
27+
*/
28+
export type MultichainAccountsFeatureFlag = Infer<
29+
typeof MultichainAccountsFeatureFlag
30+
>;
31+
32+
const APP_VERSION = packageJson.version;
33+
const FEATURE_VERSION_1 = '1';
34+
const FEATURE_VERSION_2 = '2';
35+
36+
/**
37+
* Checks if the multichain accounts feature is enabled for a given state and feature version.
38+
*
39+
* @param enableMultichainAccounts - The MetaMask state object
40+
* @param featureVersion - The specific feature version to check
41+
* @returns boolean - True if the feature is enabled for the given state and version, false otherwise.
42+
*/
43+
export const isMultichainAccountsFeatureEnabled = (
44+
enableMultichainAccounts: MultichainAccountsFeatureFlag,
45+
featureVersion: string,
46+
) => {
47+
const {
48+
enabled,
49+
featureVersion: currentFeatureVersion,
50+
minimumVersion,
51+
} = enableMultichainAccounts;
52+
53+
return (
54+
enabled &&
55+
currentFeatureVersion &&
56+
minimumVersion &&
57+
currentFeatureVersion === featureVersion &&
58+
semver.gte(minimumVersion, APP_VERSION)
59+
);
60+
};
61+
62+
/**
63+
* Selector to get the multichain accounts remote feature flags.
64+
*
65+
* @param state - The MetaMask state object
66+
* @returns MultichainAccountsFeatureFlag - The feature flags for multichain accounts.
67+
*/
68+
export const getMultichainAccountsRemoteFeatureFlags = (
69+
state: RemoteFeatureFlagsState,
70+
) => {
71+
const multichainAccountsFeatureFlags =
72+
getRemoteFeatureFlags(state).enableMultichainAccounts;
73+
74+
try {
75+
assert(multichainAccountsFeatureFlags, MultichainAccountsFeatureFlag);
76+
} catch (error) {
77+
console.warn('Invalid multichain accounts feature flags:', error);
78+
return {
79+
enabled: false,
80+
featureVersion: null,
81+
minimumVersion: null,
82+
};
83+
}
84+
85+
return multichainAccountsFeatureFlags;
86+
};
87+
88+
/**
89+
* Selector to check if the multichain accounts feature is enabled for state 1.
90+
*
91+
* @param state - The MetaMask state object
92+
* @returns boolean - True if the feature is enabled for state 1, false otherwise.
93+
*/
94+
export const getIsMultichainAccountsState1Enabled = (
95+
state: RemoteFeatureFlagsState,
96+
) => {
97+
const flags = getMultichainAccountsRemoteFeatureFlags(state);
98+
return (
99+
isMultichainAccountsFeatureEnabled(flags, FEATURE_VERSION_2) ||
100+
isMultichainAccountsFeatureEnabled(flags, FEATURE_VERSION_1)
101+
);
102+
};
103+
104+
/**
105+
* Selector to check if the multichain accounts feature is enabled for state 2.
106+
*
107+
* @param state - The MetaMask state object
108+
* @returns boolean - True if the feature is enabled for state 2, false otherwise.
109+
*/
110+
export const getIsMultichainAccountsState2Enabled = (
111+
state: RemoteFeatureFlagsState,
112+
) => {
113+
const flags = getMultichainAccountsRemoteFeatureFlags(state);
114+
return isMultichainAccountsFeatureEnabled(flags, FEATURE_VERSION_2);
115+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './feature-flags';

0 commit comments

Comments
 (0)