(undefined);
+
+ useEffect(() => {
+ let cancelled = false;
+ async function fetchPage(id: string) {
+ try {
+ setError(undefined);
+ setLoading(true);
+
+ const newData = (await handleSnapRequest({
+ snapId: id,
+ origin: '',
+ handler: 'onSettingsPage',
+ request: {
+ jsonrpc: '2.0',
+ method: ' ',
+ },
+ })) as { id: string };
+ if (!cancelled) {
+ setData(newData);
+ forceUpdateMetamaskState(dispatch);
+ }
+ } catch (err) {
+ if (!cancelled) {
+ setError(err as Error);
+ }
+ } finally {
+ if (!cancelled) {
+ setLoading(false);
+ }
+ }
+ }
+
+ if (snapId) {
+ fetchPage(snapId);
+ }
+
+ return () => {
+ cancelled = true;
+ };
+ }, [snapId]);
+
+ return { data, error, loading };
+}
diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx
index e105493485ec..59b6619a21b4 100644
--- a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx
+++ b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx
@@ -145,7 +145,7 @@ describe('PersonalSignInfo', () => {
getMockPersonalSignConfirmStateForRequest(signatureRequestSIWE);
(utils.isSIWESignatureRequest as jest.Mock).mockReturnValue(false);
- (snapUtils.isSnapId as jest.Mock).mockReturnValue(true);
+ (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true);
const mockStore = configureMockStore([])(state);
const { queryByText, getByText } = renderWithConfirmContextProvider(
@@ -167,7 +167,7 @@ describe('PersonalSignInfo', () => {
const state =
getMockPersonalSignConfirmStateForRequest(signatureRequestSIWE);
(utils.isSIWESignatureRequest as jest.Mock).mockReturnValue(false);
- (snapUtils.isSnapId as jest.Mock).mockReturnValue(true);
+ (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true);
const mockStore = configureMockStore([])(state);
const { getByText, queryByText } = renderWithConfirmContextProvider(
diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx
index 2b1e6969ddd5..83696b69ac64 100644
--- a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx
+++ b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx
@@ -65,7 +65,7 @@ describe('TypedSignInfo', () => {
type: TransactionType.signTypedData,
chainId: '0x5',
});
- (snapUtils.isSnapId as jest.Mock).mockReturnValue(true);
+ (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true);
const mockStore = configureMockStore([])(mockState);
const { queryByText } = renderWithConfirmContextProvider(
,
@@ -88,7 +88,7 @@ describe('TypedSignInfo', () => {
type: TransactionType.signTypedData,
chainId: '0x5',
});
- (snapUtils.isSnapId as jest.Mock).mockReturnValue(false);
+ (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(false);
const mockStore = configureMockStore([])(mockState);
const { queryByText } = renderWithConfirmContextProvider(
,
diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx
index 56421561ccd2..640b663e5cc1 100644
--- a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx
+++ b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx
@@ -153,7 +153,7 @@ describe('TypedSignInfo', () => {
type: TransactionType.signTypedData,
chainId: '0x5',
});
- (snapUtils.isSnapId as jest.Mock).mockReturnValue(true);
+ (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true);
const mockStore = configureMockStore([])(mockState);
const { queryByText } = renderWithConfirmContextProvider(
,
@@ -177,7 +177,7 @@ describe('TypedSignInfo', () => {
type: TransactionType.signTypedData,
chainId: '0x5',
});
- (snapUtils.isSnapId as jest.Mock).mockReturnValue(false);
+ (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(false);
const mockStore = configureMockStore([])(mockState);
const { queryByText } = renderWithConfirmContextProvider(
,
diff --git a/ui/pages/settings/index.scss b/ui/pages/settings/index.scss
index f57e1c310998..f9b08529ee65 100644
--- a/ui/pages/settings/index.scss
+++ b/ui/pages/settings/index.scss
@@ -56,7 +56,7 @@
&__title {
@include design-system.screen-sm-min {
- width: 197px;
+ margin-right: 16px;
}
@include design-system.screen-sm-max {
@@ -230,6 +230,7 @@
display: flex;
flex-direction: column;
flex: 1 1 auto;
+ max-width: 100vw;
@include design-system.screen-sm-min {
flex: 0 0 40%;
diff --git a/ui/pages/settings/settings.component.js b/ui/pages/settings/settings.component.js
index 724c661c9aeb..37257e2c8fcb 100644
--- a/ui/pages/settings/settings.component.js
+++ b/ui/pages/settings/settings.component.js
@@ -21,6 +21,7 @@ import {
ADD_POPULAR_CUSTOM_NETWORK,
DEFAULT_ROUTE,
NOTIFICATIONS_SETTINGS_ROUTE,
+ SNAP_SETTINGS_ROUTE,
} from '../../helpers/constants/routes';
import { getSettingsRoutes } from '../../helpers/utils/settings-search';
@@ -31,6 +32,7 @@ import {
IconName,
Box,
Text,
+ IconSize,
} from '../../components/component-library';
import {
AlignItems,
@@ -44,6 +46,8 @@ import MetafoxLogo from '../../components/ui/metafox-logo';
// eslint-disable-next-line import/no-restricted-paths
import { getEnvironmentType } from '../../../app/scripts/lib/util';
import { ENVIRONMENT_TYPE_POPUP } from '../../../shared/constants/app';
+import { SnapIcon } from '../../components/app/snaps/snap-icon';
+import { SnapSettingsRenderer } from '../../components/app/snaps/snap-settings-page';
import SettingsTab from './settings-tab';
import AdvancedTab from './advanced-tab';
import InfoTab from './info-tab';
@@ -70,6 +74,8 @@ class SettingsPage extends PureComponent {
mostRecentOverviewPage: PropTypes.string.isRequired,
pathnameI18nKey: PropTypes.string,
remoteFeatureFlags: PropTypes.object.isRequired,
+ settingsPageSnaps: PropTypes.array,
+ snapSettingsTitle: PropTypes.string,
toggleNetworkMenu: PropTypes.func.isRequired,
useExternalServices: PropTypes.bool,
};
@@ -210,19 +216,24 @@ class SettingsPage extends PureComponent {
renderTitle() {
const { t } = this.context;
- const { isPopup, pathnameI18nKey, addressName } = this.props;
+ const { isPopup, pathnameI18nKey, addressName, snapSettingsTitle } =
+ this.props;
let titleText;
if (isPopup && addressName) {
titleText = t('details');
} else if (pathnameI18nKey && isPopup) {
titleText = t(pathnameI18nKey);
+ } else if (snapSettingsTitle) {
+ titleText = snapSettingsTitle;
} else {
titleText = t('settings');
}
return (
- {titleText}
+
+ {titleText}
+
);
}
@@ -293,15 +304,31 @@ class SettingsPage extends PureComponent {
}
renderTabs() {
- const { history, currentPath, useExternalServices } = this.props;
+ const { history, currentPath, useExternalServices, settingsPageSnaps } =
+ this.props;
const { t } = this.context;
+ const snapsSettings = settingsPageSnaps.map(({ id, name }) => {
+ return {
+ content: name,
+ icon: (
+
+ ),
+ key: `${SNAP_SETTINGS_ROUTE}/${encodeURIComponent(id)}`,
+ };
+ });
+
const tabs = [
{
content: t('general'),
icon: ,
key: GENERAL_ROUTE,
},
+ ...snapsSettings,
{
content: t('advanced'),
icon: ,
@@ -390,6 +417,10 @@ class SettingsPage extends PureComponent {
)}
/>
+
{
metamask: { currencyRates },
} = state;
const remoteFeatureFlags = getRemoteFeatureFlags(state);
+
+ const settingsPageSnapsIds = getSettingsPageSnapsIds(state);
+ const snapsMetadata = getSnapsMetadata(state);
const conversionDate = currencyRates[ticker]?.conversionDate;
const pathNameTail = pathname.match(/[^/]+$/u)[0];
@@ -75,6 +83,7 @@ const mapStateToProps = (state, ownProps) => {
const isAddPopularCustomNetwork = Boolean(
pathname.match(ADD_POPULAR_CUSTOM_NETWORK),
);
+ const isSnapSettingsRoute = Boolean(pathname.match(SNAP_SETTINGS_ROUTE));
const isPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP;
const pathnameI18nKey = ROUTES_TO_I18N_KEYS[pathname];
@@ -102,6 +111,16 @@ const mapStateToProps = (state, ownProps) => {
);
const useExternalServices = getUseExternalServices(state);
+ const snapNameGetter = getSnapName(snapsMetadata);
+
+ const settingsPageSnaps = settingsPageSnapsIds.map((snapId) => ({
+ id: snapId,
+ name: snapNameGetter(snapId),
+ }));
+
+ const snapSettingsTitle =
+ isSnapSettingsRoute && snapNameGetter(decodeSnapIdFromPathname(pathname));
+
return {
addNewNetwork,
addressName,
@@ -115,6 +134,8 @@ const mapStateToProps = (state, ownProps) => {
mostRecentOverviewPage: getMostRecentOverviewPage(state),
pathnameI18nKey,
remoteFeatureFlags,
+ settingsPageSnaps,
+ snapSettingsTitle,
useExternalServices,
};
};
diff --git a/ui/pages/settings/settings.stories.js b/ui/pages/settings/settings.stories.js
index 53437f4175db..b6b695a89cb1 100644
--- a/ui/pages/settings/settings.stories.js
+++ b/ui/pages/settings/settings.stories.js
@@ -62,6 +62,7 @@ const Settings = ({ history }) => {
pathnameI18nKey={pathnameI18nKey}
backRoute={SETTINGS_ROUTE}
remoteFeatureFlags={{}}
+ settingsPageSnaps={[]}
/>
);
diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js
index 02adf1241e30..caa20245bb7d 100644
--- a/ui/selectors/selectors.js
+++ b/ui/selectors/selectors.js
@@ -12,6 +12,7 @@ import { NameType } from '@metamask/name-controller';
import { TransactionStatus } from '@metamask/transaction-controller';
import { isEvmAccountType } from '@metamask/keyring-api';
import { RpcEndpointType } from '@metamask/network-controller';
+import { SnapEndowments } from '@metamask/snaps-rpc-methods';
import {
getCurrentChainId,
getProviderConfig,
@@ -112,6 +113,7 @@ import { BridgeFeatureFlagsKey } from '../../app/scripts/controllers/bridge/type
import { hasTransactionData } from '../../shared/modules/transaction.utils';
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
import { createDeepEqualSelector } from '../../shared/modules/selectors/util';
+import { isSnapIgnoredInProd } from '../helpers/utils/snaps';
import {
getAllUnapprovedTransactions,
getCurrentNetworkTransactions,
@@ -1920,6 +1922,19 @@ export const getInsightSnaps = createDeepEqualSelector(
},
);
+export const getSettingsPageSnaps = createDeepEqualSelector(
+ getEnabledSnaps,
+ getPermissionSubjects,
+ (snaps, subjects) => {
+ return Object.values(snaps).filter(
+ ({ id, preinstalled }) =>
+ subjects[id]?.permissions[SnapEndowments.SettingsPage] &&
+ preinstalled &&
+ !isSnapIgnoredInProd(id),
+ );
+ },
+);
+
export const getSignatureInsightSnaps = createDeepEqualSelector(
getEnabledSnaps,
getPermissionSubjects,
@@ -1950,6 +1965,11 @@ export const getNameLookupSnapsIds = createDeepEqualSelector(
},
);
+export const getSettingsPageSnapsIds = createDeepEqualSelector(
+ getSettingsPageSnaps,
+ (snaps) => snaps.map((snap) => snap.id),
+);
+
export const getNotifySnaps = createDeepEqualSelector(
getEnabledSnaps,
getPermissionSubjects,