Skip to content
Open
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
25 changes: 20 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 @@ -1139,12 +1139,27 @@ 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();

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
13 changes: 11 additions & 2 deletions shared/modules/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@ 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() || '';
return enabled.split(',').filter(Boolean);
};

export const isGatorPermissionsRevocationFeatureEnabled = (): boolean => {
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