Skip to content

chore: add remote feature flag for multichain accounts #33112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ui/selectors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './accounts';
export * from './remote-feature-flags';
export * from './origin-throttling';
export * from './multichain/networks';
export * from './multichain-accounts';
93 changes: 93 additions & 0 deletions ui/selectors/multichain-accounts/feature-flags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { type RemoteFeatureFlagsState } from '../remote-feature-flags';
import {
type MultichainAccountsFeatureFlag,
getIsMultichainAccountsState1Enabled,
getIsMultichainAccountsState2Enabled,
} from './feature-flags';

jest.mock('../../../package.json', () => ({
version: '12.0.0',
}));

type TestState = RemoteFeatureFlagsState & {
metamask: {
remoteFeatureFlags: {
enableMultichainAccounts: MultichainAccountsFeatureFlag;
};
};
};

const disabledStateMock: MultichainAccountsFeatureFlag = {
enabled: false,
featureVersion: null,
minimumVersion: null,
};

const state1Mock: MultichainAccountsFeatureFlag = {
enabled: true,
featureVersion: '1',
minimumVersion: '13.0.0',
};

const state2Mock: MultichainAccountsFeatureFlag = {
enabled: true,
featureVersion: '2',
minimumVersion: '14.0.0',
};

const getMockState = (
multichainAccountsFeatureFlagMock: MultichainAccountsFeatureFlag,
): TestState =>
Object.freeze({
metamask: {
remoteFeatureFlags: {
enableMultichainAccounts: multichainAccountsFeatureFlagMock,
},
},
});

describe('Multichain Accounts Feature Flags', () => {
beforeEach(() => {
jest.clearAllMocks(); // Clears all mocks before each test
});

describe('getIsMultichainAccountsState1Enabled', () => {
it('returns false for disabled state', () => {
expect(
getIsMultichainAccountsState1Enabled(getMockState(disabledStateMock)),
).toBe(false);
});

it('returns true for state 1', () => {
expect(
getIsMultichainAccountsState1Enabled(getMockState(state1Mock)),
).toBe(true);
});

it('returns true for state 2', () => {
expect(
getIsMultichainAccountsState1Enabled(getMockState(state2Mock)),
).toBe(true);
});
});

describe('getIsMultichainAccountsState2Enabled', () => {
it('returns false for disabled state', () => {
expect(
getIsMultichainAccountsState2Enabled(getMockState(disabledStateMock)),
).toBe(false);
});

it('returns false for state 1', () => {
expect(
getIsMultichainAccountsState2Enabled(getMockState(state1Mock)),
).toBe(false);
});

it('returns true for state 2', () => {
expect(
getIsMultichainAccountsState2Enabled(getMockState(state2Mock)),
).toBe(true);
});
});
});
115 changes: 115 additions & 0 deletions ui/selectors/multichain-accounts/feature-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
Infer,
object,
boolean,
nullable,
string,
assert,
} from '@metamask/superstruct';
import semver from 'semver';
import packageJson from '../../../package.json';
import {
getRemoteFeatureFlags,
type RemoteFeatureFlagsState,
} from '../remote-feature-flags';

/**
* Feature flag structure for multichain accounts features
*/
const MultichainAccountsFeatureFlag = object({
enabled: boolean(),
featureVersion: nullable(string()),
minimumVersion: nullable(string()),
});

/**
* Feature flag type for multichain accounts features
*/
export type MultichainAccountsFeatureFlag = Infer<
typeof MultichainAccountsFeatureFlag
>;

const APP_VERSION = packageJson.version;
const FEATURE_VERSION_1 = '1';
const FEATURE_VERSION_2 = '2';

/**
* Checks if the multichain accounts feature is enabled for a given state and feature version.
*
* @param enableMultichainAccounts - The MetaMask state object
* @param featureVersion - The specific feature version to check
* @returns boolean - True if the feature is enabled for the given state and version, false otherwise.
*/
export const isMultichainAccountsFeatureEnabled = (
enableMultichainAccounts: MultichainAccountsFeatureFlag,
featureVersion: string,
) => {
const {
enabled,
featureVersion: currentFeatureVersion,
minimumVersion,
} = enableMultichainAccounts;

return (
enabled &&
currentFeatureVersion &&
minimumVersion &&
currentFeatureVersion === featureVersion &&
semver.gte(minimumVersion, APP_VERSION)
);
};

/**
* Selector to get the multichain accounts remote feature flags.
*
* @param state - The MetaMask state object
* @returns MultichainAccountsFeatureFlag - The feature flags for multichain accounts.
*/
export const getMultichainAccountsRemoteFeatureFlags = (
state: RemoteFeatureFlagsState,
) => {
const multichainAccountsFeatureFlags =
getRemoteFeatureFlags(state).enableMultichainAccounts;

try {
assert(multichainAccountsFeatureFlags, MultichainAccountsFeatureFlag);
} catch (error) {
console.warn('Invalid multichain accounts feature flags:', error);
return {
enabled: false,
featureVersion: null,
minimumVersion: null,
};
}

return multichainAccountsFeatureFlags;
};

/**
* Selector to check if the multichain accounts feature is enabled for state 1.
*
* @param state - The MetaMask state object
* @returns boolean - True if the feature is enabled for state 1, false otherwise.
*/
export const getIsMultichainAccountsState1Enabled = (
state: RemoteFeatureFlagsState,
) => {
const flags = getMultichainAccountsRemoteFeatureFlags(state);
return (
isMultichainAccountsFeatureEnabled(flags, FEATURE_VERSION_2) ||
isMultichainAccountsFeatureEnabled(flags, FEATURE_VERSION_1)
);
};

/**
* Selector to check if the multichain accounts feature is enabled for state 2.
*
* @param state - The MetaMask state object
* @returns boolean - True if the feature is enabled for state 2, false otherwise.
*/
export const getIsMultichainAccountsState2Enabled = (
state: RemoteFeatureFlagsState,
) => {
const flags = getMultichainAccountsRemoteFeatureFlags(state);
return isMultichainAccountsFeatureEnabled(flags, FEATURE_VERSION_2);
};
1 change: 1 addition & 0 deletions ui/selectors/multichain-accounts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './feature-flags';
Loading