Skip to content
Open
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
2 changes: 0 additions & 2 deletions app/scripts/constants/snaps.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Needed for webpack to analyze the preinstalled snaps
export const PREINSTALLED_SNAPS_URLS = [
///: BEGIN:ONLY_INCLUDE_IF(gator-permissions)
new URL(
'@metamask/permissions-kernel-snap/dist/preinstalled-snap.json',
// @ts-expect-error TS1470: 'import.meta' is not allowed in CommonJS
Expand All @@ -11,7 +10,6 @@ export const PREINSTALLED_SNAPS_URLS = [
// @ts-expect-error TS1470: 'import.meta' is not allowed in CommonJS
import.meta.url,
),
///: END:ONLY_INCLUDE_IF
new URL(
'@metamask/message-signing-snap/dist/preinstalled-snap.json',
// @ts-expect-error TS1470: 'import.meta' is not allowed in CommonJS
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { GatorPermissionsController } from '@metamask/gator-permissions-controller';
import { buildControllerInitRequestMock } from '../test/utils';
import type { ControllerInitRequest } from '../types';
import { isGatorPermissionsFeatureEnabled } from '../../../../shared/modules/environment';
import { getEnabledAdvancedPermissions } from '../../../../shared/modules/environment';
import {
getGatorPermissionsControllerMessenger,
GatorPermissionsControllerMessenger,
Expand Down Expand Up @@ -36,7 +36,9 @@ describe('GatorPermissionsControllerInit', () => {

beforeEach(() => {
jest.resetAllMocks();
jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(true);
jest
.mocked(getEnabledAdvancedPermissions)
.mockReturnValue(['native-token-stream']);
process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID =
MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID;
});
Expand All @@ -59,7 +61,9 @@ describe('GatorPermissionsControllerInit', () => {

it('initializes with correct messenger and state(gator permissions feature enabled)', () => {
const requestMock = buildInitRequestMock();
jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(true);
jest
.mocked(getEnabledAdvancedPermissions)
.mockReturnValue(['native-token-stream']);
GatorPermissionsControllerInit(requestMock);

expect(GatorPermissionsControllerClassMock).toHaveBeenCalledWith({
Expand All @@ -74,7 +78,7 @@ describe('GatorPermissionsControllerInit', () => {

it('initializes with correct messenger and state(gator permissions feature disabled)', () => {
const requestMock = buildInitRequestMock();
jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(false);
jest.mocked(getEnabledAdvancedPermissions).mockReturnValue([]);
GatorPermissionsControllerInit(requestMock);

expect(GatorPermissionsControllerClassMock).toHaveBeenCalledWith({
Expand All @@ -101,7 +105,9 @@ describe('GatorPermissionsControllerInit', () => {
it('handles undefined persistedState.GatorPermissionsController', () => {
const requestMock = buildInitRequestMock();
requestMock.persistedState.GatorPermissionsController = undefined;
jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(true);
jest
.mocked(getEnabledAdvancedPermissions)
.mockReturnValue(['native-token-stream']);

GatorPermissionsControllerInit(requestMock);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from '@metamask/gator-permissions-controller';
import { assertIsValidSnapId } from '@metamask/snaps-utils';
import { ControllerInitFunction } from '../types';
import { isGatorPermissionsFeatureEnabled } from '../../../../shared/modules/environment';
import { getEnabledAdvancedPermissions } from '../../../../shared/modules/environment';
import { GatorPermissionsControllerMessenger } from '../messengers/gator-permissions';

const generateDefaultGatorPermissionsControllerState =
Expand All @@ -26,7 +26,8 @@ const generateDefaultGatorPermissionsControllerState =
}
}

const isGatorPermissionsEnabled = isGatorPermissionsFeatureEnabled();
const isGatorPermissionsEnabled =
getEnabledAdvancedPermissions().length > 0;

const state: Partial<GatorPermissionsControllerState> = {
isGatorPermissionsEnabled,
Expand Down
29 changes: 24 additions & 5 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ import { FirstTimeFlowType } from '../../shared/constants/onboarding';
import { updateCurrentLocale } from '../../shared/lib/translate';
import {
getIsSeedlessOnboardingFeatureEnabled,
isGatorPermissionsFeatureEnabled,
getEnabledAdvancedPermissions,
} from '../../shared/modules/environment';
import { isSnapPreinstalled } from '../../shared/lib/snaps/snaps';
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
Expand Down Expand Up @@ -1144,12 +1144,31 @@ export default class MetamaskController extends EventEmitter {
(meta) =>
meta.hash === hash && meta.status === TransactionStatus.submitted,
),
processRequestExecutionPermissions: isGatorPermissionsFeatureEnabled()
? forwardRequestToSnap.bind(null, {
processRequestExecutionPermissions: async (params, req) => {
const enabledTypes = getEnabledAdvancedPermissions();

if (!params || params.length === 0) {
throw rpcErrors.methodNotSupported('No permission type provided');
}

for (const param of params) {
const permissionType = param?.permission?.type;
if (!enabledTypes.includes(permissionType)) {
throw rpcErrors.methodNotSupported(
`Permission type '${permissionType ?? 'unknown'}' is not enabled`,
);
}
}

return forwardRequestToSnap(
{
snapId: process.env.PERMISSIONS_KERNEL_SNAP_ID,
handleRequest: this.handleSnapRequest.bind(this),
})
: undefined,
},
params,
req,
);
},
});

// ensure isClientOpenAndUnlocked is updated when memState updates
Expand Down
15 changes: 6 additions & 9 deletions builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ buildTypes:
- tron
- multi-srp
- multichain
- gator-permissions
env:
- INFURA_FLASK_PROJECT_ID
- SEGMENT_FLASK_WRITE_KEY
Expand All @@ -147,7 +146,7 @@ buildTypes:
- APPLE_FLASK_CLIENT_ID
- GOOGLE_CLIENT_ID_REF: GOOGLE_FLASK_CLIENT_ID
- APPLE_CLIENT_ID_REF: APPLE_FLASK_CLIENT_ID
- GATOR_PERMISSIONS_ENABLED: true
- GATOR_ENABLED_PERMISSION_TYPES: 'native-token-stream,native-token-periodic,erc20-token-stream,erc20-token-periodic'
- GATOR_PERMISSIONS_REVOCATION_ENABLED: true

isPrerelease: true
Expand Down Expand Up @@ -190,6 +189,10 @@ features:
dest: snaps/institutional-wallet-snap.json
- src: ./node_modules/@metamask/solana-wallet-snap/dist/preinstalled-snap.json
dest: snaps/solana-wallet-snap.json
- src: ./node_modules/@metamask/permissions-kernel-snap/dist/preinstalled-snap.json
dest: snaps/permissions-kernel-snap.json
- src: ./node_modules/@metamask/gator-permissions-snap/dist/preinstalled-snap.json
dest: snaps/gator-permissions-snap.json
- ./{app,shared,ui}/**/keyring-snaps/**
bitcoin:
assets:
Expand All @@ -208,12 +211,6 @@ features:
ocap-kernel:
assets:
- ./{app,shared,ui}/**/ocap-kernel/**
gator-permissions:
assets:
- src: ./node_modules/@metamask/permissions-kernel-snap/dist/preinstalled-snap.json
dest: snaps/permissions-kernel-snap.json
- src: ./node_modules/@metamask/gator-permissions-snap/dist/preinstalled-snap.json
dest: snaps/gator-permissions-snap.json
build-experimental:
assets:
- src: ./node_modules/@metamask/account-watcher/dist/preinstalled-snap.json
Expand Down Expand Up @@ -389,7 +386,7 @@ env:
- METAMASK_RAMP_API_CONTENT_BASE_URL: https://on-ramp-content.api.cx.metamask.io

# EIP-7715 Readable Permissions
- GATOR_PERMISSIONS_ENABLED: false
- GATOR_ENABLED_PERMISSION_TYPES: ''
- PERMISSIONS_KERNEL_SNAP_ID: 'npm:@metamask/permissions-kernel-snap'
- GATOR_PERMISSIONS_PROVIDER_SNAP_ID: 'npm:@metamask/gator-permissions-snap'
- GATOR_PERMISSIONS_REVOCATION_ENABLED: false
Expand Down
2 changes: 0 additions & 2 deletions shared/lib/snaps/snaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ export const PREINSTALLED_SNAPS = [
///: BEGIN:ONLY_INCLUDE_IF(tron)
'npm:@metamask/tron-wallet-snap',
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(gator-permissions)
'npm:@metamask/permissions-kernel-snap',
'npm:@metamask/gator-permissions-snap',
///: END:ONLY_INCLUDE_IF
];

/**
Expand Down
55 changes: 44 additions & 11 deletions shared/modules/environment.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ENVIRONMENT } from '../../development/build/constants';
import {
isGatorPermissionsFeatureEnabled,
getEnabledAdvancedPermissions,
isProduction,
isGatorPermissionsRevocationFeatureEnabled,
} from './environment';
Expand Down Expand Up @@ -32,20 +32,53 @@ describe('isProduction', () => {
});
});

describe('isGatorPermissionsFeatureEnabled', () => {
it('should return true when GATOR_PERMISSIONS_ENABLED is "true"', () => {
process.env.GATOR_PERMISSIONS_ENABLED = 'true';
expect(isGatorPermissionsFeatureEnabled()).toBe(true);
describe('getEnabledAdvancedPermissions', () => {
let originalGatorEnabledPermissionTypes: string | undefined;

beforeAll(() => {
originalGatorEnabledPermissionTypes =
process.env.GATOR_ENABLED_PERMISSION_TYPES;
});

afterAll(() => {
process.env.GATOR_ENABLED_PERMISSION_TYPES =
originalGatorEnabledPermissionTypes;
});

it('should return an empty array when GATOR_ENABLED_PERMISSION_TYPES is not set', () => {
delete process.env.GATOR_ENABLED_PERMISSION_TYPES;
expect(getEnabledAdvancedPermissions()).toStrictEqual([]);
});

it('should return an empty array when GATOR_ENABLED_PERMISSION_TYPES is an empty string', () => {
process.env.GATOR_ENABLED_PERMISSION_TYPES = '';
expect(getEnabledAdvancedPermissions()).toStrictEqual([]);
});

it('should parse comma-separated values correctly', () => {
process.env.GATOR_ENABLED_PERMISSION_TYPES =
'native-token-stream,native-token-periodic,erc20-token-stream';
expect(getEnabledAdvancedPermissions()).toStrictEqual([
'native-token-stream',
'native-token-periodic',
'erc20-token-stream',
]);
});

it('should return false when GATOR_PERMISSIONS_ENABLED is "false"', () => {
process.env.GATOR_PERMISSIONS_ENABLED = 'false';
expect(isGatorPermissionsFeatureEnabled()).toBe(false);
it('should filter out empty strings from the result', () => {
process.env.GATOR_ENABLED_PERMISSION_TYPES =
'native-token-stream,,erc20-token-stream';
expect(getEnabledAdvancedPermissions()).toStrictEqual([
'native-token-stream',
'erc20-token-stream',
]);
});

it('should return false when GATOR_PERMISSIONS_ENABLED is undefined', () => {
delete process.env.GATOR_PERMISSIONS_ENABLED;
expect(isGatorPermissionsFeatureEnabled()).toBe(false);
it('should handle a single permission type', () => {
process.env.GATOR_ENABLED_PERMISSION_TYPES = 'native-token-stream';
expect(getEnabledAdvancedPermissions()).toStrictEqual([
'native-token-stream',
]);
});
});

Expand Down
14 changes: 12 additions & 2 deletions shared/modules/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,18 @@ export const getIsSettingsPageDevOptionsEnabled = (): boolean => {
return process.env.ENABLE_SETTINGS_PAGE_DEV_OPTIONS?.toString() === 'true';
};

export const isGatorPermissionsFeatureEnabled = (): boolean => {
return process.env.GATOR_PERMISSIONS_ENABLED?.toString() === 'true';
/**
* Returns the list of enabled Gator permission types from the environment configuration.
* These permission types control which advanced permissions (e.g., token streams,
* periodic transfers) are available in the current build.
*
* @returns An array of enabled permission type strings (e.g., 'native-token-stream',
* 'erc20-token-periodic'), or an empty array if none are configured.
*/
export const getEnabledAdvancedPermissions = (): string[] => {
const enabled =
process.env.GATOR_ENABLED_PERMISSION_TYPES?.toString().trim() || '';
return enabled.split(',').filter(Boolean);
};

export const isGatorPermissionsRevocationFeatureEnabled = (): boolean => {
Expand Down
18 changes: 18 additions & 0 deletions test/e2e/tests/settings/state-logs.json
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,24 @@
"svgIcon": "string",
"version": "string"
},
"npm:@metamask/permissions-kernel-snap": {
"extensionId": "null",
"iconUrl": "null",
"name": "string",
"origin": "string",
"subjectType": "string",
"svgIcon": "string",
"version": "string"
},
"npm:@metamask/gator-permissions-snap": {
"extensionId": "null",
"iconUrl": "null",
"name": "string",
"origin": "string",
"subjectType": "string",
"svgIcon": "string",
"version": "string"
},
"npm:@metamask/solana-wallet-snap": {
"extensionId": "null",
"iconUrl": "null",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '../../../../../../test/data/confirmations/helper';
import { renderWithConfirmContextProvider } from '../../../../../../test/lib/confirmations/render-helpers';
import { useAssetDetails } from '../../../hooks/useAssetDetails';
import { isGatorPermissionsFeatureEnabled } from '../../../../../../shared/modules/environment';
import { getEnabledAdvancedPermissions } from '../../../../../../shared/modules/environment';
import { DEFAULT_ROUTE } from '../../../../../helpers/constants/routes';
import Info from './info';

Expand Down Expand Up @@ -55,7 +55,9 @@ jest.mock('../../../hooks/useTransactionFocusEffect', () => ({

jest.mock('../../../../../../shared/modules/environment', () => ({
...jest.requireActual('../../../../../../shared/modules/environment'),
isGatorPermissionsFeatureEnabled: jest.fn().mockReturnValue(true),
getEnabledAdvancedPermissions: jest
.fn()
.mockReturnValue(['native-token-stream']),
}));

jest.mock('react-router-dom-v5-compat', () => ({
Expand Down Expand Up @@ -99,7 +101,7 @@ describe('Info', () => {
});

it('throws an error if gator permissions feature is not enabled', () => {
jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(false);
jest.mocked(getEnabledAdvancedPermissions).mockReturnValue([]);

const state = getMockTypedSignPermissionConfirmState();
const mockStore = configureMockStore([])(state);
Expand Down
4 changes: 2 additions & 2 deletions ui/pages/confirmations/components/confirm/info/info.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TransactionType } from '@metamask/transaction-controller';
import { ApprovalType } from '@metamask/controller-utils';
import React, { useMemo } from 'react';
import { isGatorPermissionsFeatureEnabled } from '../../../../../../shared/modules/environment';
import { getEnabledAdvancedPermissions } from '../../../../../../shared/modules/environment';
import { useTrustSignalMetrics } from '../../../../trust-signals/hooks/useTrustSignalMetrics';
import { useConfirmContext } from '../../../context/confirm';
import { useSmartTransactionFeatureFlags } from '../../../hooks/useSmartTransactionFeatureFlags';
Expand Down Expand Up @@ -47,7 +47,7 @@ const Info = () => {
return TypedSignV1Info;
}
if (signatureRequest?.decodedPermission) {
if (!isGatorPermissionsFeatureEnabled()) {
if (getEnabledAdvancedPermissions().length === 0) {
throw new Error('Gator permissions feature is not enabled');
}

Expand Down
Loading