Skip to content

Commit 6b5fea5

Browse files
authored
Update RpcHandler to restrict supported chainIds (#200)
* Update RpcHandler to restrict supported chainIds * Remove production configuration from example .env files * Add reference to production configuration to snap example configuration files. Remove mainnet from supported chains in test dapp.
1 parent 476e653 commit 6b5fea5

File tree

9 files changed

+134
-46
lines changed

9 files changed

+134
-46
lines changed

.github/workflows/build-lint-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ env:
1212
GATOR_PERMISSIONS_PROVIDER_SNAP_ID: ${{ vars.GATOR_PERMISSIONS_PROVIDER_SNAP_ID }}
1313
MESSAGE_SIGNING_SNAP_ID: ${{ vars.MESSAGE_SIGNING_SNAP_ID }}
1414
KERNEL_SNAP_ID: ${{ vars.KERNEL_SNAP_ID }}
15+
SUPPORTED_CHAIN_IDS: ${{ vars.SUPPORTED_CHAIN_IDS }}
1516

1617
jobs:
1718
prepare:

.github/workflows/publish-release.yml

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@ on:
77
required: true
88
SLACK_WEBHOOK_URL:
99
required: true
10+
env:
11+
SNAP_ENV: ${{ vars.SNAP_ENV }}
12+
PRICE_API_BASE_URL: ${{ vars.PRICE_API_BASE_URL }}
13+
STORE_PERMISSIONS_ENABLED: ${{ vars.STORE_PERMISSIONS_ENABLED }}
14+
ACCOUNT_API_BASE_URL: ${{ vars.ACCOUNT_API_BASE_URL }}
15+
TOKENS_API_BASE_URL: ${{ vars.TOKENS_API_BASE_URL }}
16+
GATOR_PERMISSIONS_PROVIDER_SNAP_ID: ${{ vars.GATOR_PERMISSIONS_PROVIDER_SNAP_ID }}
17+
MESSAGE_SIGNING_SNAP_ID: ${{ vars.MESSAGE_SIGNING_SNAP_ID }}
18+
KERNEL_SNAP_ID: ${{ vars.KERNEL_SNAP_ID }}
19+
SUPPORTED_CHAIN_IDS: ${{ vars.SUPPORTED_CHAIN_IDS }}
1020

1121
jobs:
1222
publish-release:
1323
name: Publish GitHub Release
1424
runs-on: ubuntu-latest
1525
permissions:
1626
contents: write
17-
env:
18-
SNAP_ENV: ${{ vars.SNAP_ENV }}
19-
PRICE_API_BASE_URL: ${{ vars.PRICE_API_BASE_URL }}
20-
STORE_PERMISSIONS_ENABLED: ${{ vars.STORE_PERMISSIONS_ENABLED }}
21-
GATOR_PERMISSIONS_PROVIDER_SNAP_ID: ${{ vars.GATOR_PERMISSIONS_PROVIDER_SNAP_ID }}
22-
MESSAGE_SIGNING_SNAP_ID: ${{ vars.MESSAGE_SIGNING_SNAP_ID }}
23-
KERNEL_SNAP_ID: ${{ vars.KERNEL_SNAP_ID }}
24-
ACCOUNT_API_BASE_URL: ${{ vars.ACCOUNT_API_BASE_URL }}
25-
TOKENS_API_BASE_URL: ${{ vars.TOKENS_API_BASE_URL }}
2627
steps:
2728
- name: Checkout and setup environment
2829
uses: MetaMask/action-checkout-and-setup@v1
@@ -47,10 +48,6 @@ jobs:
4748
name: Dry run publish to NPM
4849
runs-on: ubuntu-latest
4950
needs: publish-release
50-
env:
51-
SNAP_ENV: ${{ vars.SNAP_ENV }}
52-
PRICE_API_BASE_URL: ${{ vars.PRICE_API_BASE_URL }}
53-
STORE_PERMISSIONS_ENABLED: ${{ vars.STORE_PERMISSIONS_ENABLED }}
5451
steps:
5552
- name: Checkout and setup environment
5653
uses: MetaMask/action-checkout-and-setup@v1

packages/gator-permissions-snap/.env.example

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@ MESSAGE_SIGNING_SNAP_ID=local:http://localhost:8080
1717
ACCOUNT_API_BASE_URL=https://accounts.api.cx.metamask.io
1818
TOKENS_API_BASE_URL=https://tokens.api.cx.metamask.io
1919

20-
# Prod
21-
SNAP_ENV=production
22-
STORE_PERMISSIONS_ENABLED=false
23-
PRICE_API_BASE_URL=https://price.api.cx.metamask.io
24-
ACCOUNT_API_BASE_URL=https://accounts.api.cx.metamask.io
25-
TOKENS_API_BASE_URL=https://tokens.api.cx.metamask.io
26-
KERNEL_SNAP_ID=npm:@metamask/permissions-kernel-snap
27-
MESSAGE_SIGNING_SNAP_ID=npm:@metamask/message-signing-snap
20+
# Support only testnets
21+
SUPPORTED_CHAIN_IDS=97,5115,6342,10200,80069,84532,421614,11155111,11155420,1301,80002,763373
22+
23+
# Production configuration can be found here https://github.com/MetaMask/snap-7715-permissions/settings/variables/actions

packages/gator-permissions-snap/snap.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
TOKENS_API_BASE_URL,
1515
KERNEL_SNAP_ID,
1616
MESSAGE_SIGNING_SNAP_ID,
17+
SUPPORTED_CHAIN_IDS,
1718
} = process.env;
1819

1920
if (!SNAP_ENV) {
@@ -65,6 +66,12 @@ if (!TOKENS_API_BASE_URL) {
6566
);
6667
}
6768

69+
if (!SUPPORTED_CHAIN_IDS) {
70+
throw new InternalError(
71+
'SUPPORTED_CHAIN_IDS must be set as an environment variable.',
72+
);
73+
}
74+
6875
const config: SnapConfig = {
6976
input: resolve(__dirname, 'src/index.ts'),
7077
server: {
@@ -82,6 +89,7 @@ const config: SnapConfig = {
8289
TOKENS_API_BASE_URL,
8390
KERNEL_SNAP_ID,
8491
MESSAGE_SIGNING_SNAP_ID,
92+
SUPPORTED_CHAIN_IDS,
8593
},
8694
};
8795

packages/gator-permissions-snap/src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ if (!messageSigningSnapId) {
6767
throw new InternalError('MESSAGE_SIGNING_SNAP_ID is not set');
6868
}
6969

70+
const supportedChainIdsString = process.env.SUPPORTED_CHAIN_IDS;
71+
if (!supportedChainIdsString) {
72+
throw new InternalError('SUPPORTED_CHAIN_IDS is not set');
73+
}
74+
75+
const supportedChainIds = supportedChainIdsString
76+
.split(',')
77+
.map((chainIdString) => parseInt(chainIdString, 10));
78+
7079
// set up dependencies
7180

7281
const accountApiClient = new AccountApiClient({
@@ -167,6 +176,7 @@ const permissionHandlerFactory = new PermissionHandlerFactory({
167176
const rpcHandler = createRpcHandler({
168177
permissionHandlerFactory,
169178
profileSyncManager,
179+
supportedChainIds,
170180
});
171181

172182
// configure RPC methods bindings

packages/gator-permissions-snap/src/rpc/rpcHandler.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import type { PermissionResponse } from '@metamask/7715-permissions-shared/types';
22
import { logger } from '@metamask/7715-permissions-shared/utils';
3-
import { UserRejectedRequestError, type Json } from '@metamask/snaps-sdk';
3+
import {
4+
InvalidInputError,
5+
UserRejectedRequestError,
6+
type Json,
7+
} from '@metamask/snaps-sdk';
8+
import { hexToNumber } from '@metamask/utils';
49

510
import type { PermissionHandlerFactory } from '../core/permissionHandlerFactory';
611
import { DEFAULT_GATOR_PERMISSION_TO_OFFER } from '../permissions/permissionOffers';
@@ -40,14 +45,18 @@ export type RpcHandler = {
4045
* @param config - The parameters for creating the RPC handler.
4146
* @param config.permissionHandlerFactory - The factory for creating permission handlers.
4247
* @param config.profileSyncManager - The profile sync manager.
48+
* @param config.supportedChainIds - The supported chain IDs.
4349
* @returns An object with RPC handler methods.
4450
*/
45-
export function createRpcHandler(config: {
51+
export function createRpcHandler({
52+
permissionHandlerFactory,
53+
profileSyncManager,
54+
supportedChainIds,
55+
}: {
4656
permissionHandlerFactory: PermissionHandlerFactory;
4757
profileSyncManager: ProfileSyncManager;
58+
supportedChainIds: number[];
4859
}): RpcHandler {
49-
const { permissionHandlerFactory, profileSyncManager } = config;
50-
5160
/**
5261
* Handles grant permission requests.
5362
*
@@ -59,6 +68,18 @@ export function createRpcHandler(config: {
5968
const { permissionsRequest, siteOrigin } =
6069
validatePermissionRequestParam(params);
6170

71+
const unsupportedChainIds = permissionsRequest
72+
.filter(
73+
(request) => !supportedChainIds.includes(hexToNumber(request.chainId)),
74+
)
75+
.map((request) => request.chainId);
76+
77+
if (unsupportedChainIds.length > 0) {
78+
throw new InvalidInputError(
79+
`Unsupported chain IDs: ${unsupportedChainIds.join(', ')}`,
80+
);
81+
}
82+
6283
const permissionsToStore: {
6384
permissionResponse: PermissionResponse;
6485
siteOrigin: string;

packages/gator-permissions-snap/test/rpc/rpcHandler.test.ts

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ const TEST_CHAIN_ID = '0x1' as const;
1616
const TEST_EXPIRY = Math.floor(Date.now() / 1000) + 86400; // 24 hours from now
1717
const TEST_CONTEXT = '0xabcd' as const;
1818

19+
const TEST_SUPPORTED_CHAIN_IDS = [0x1, 0x10];
20+
21+
const UNSUPPORTED_CHAIN_ID = '0xFA11' as const;
22+
1923
const VALID_PERMISSION_REQUEST: PermissionRequest = {
2024
chainId: TEST_CHAIN_ID,
2125
rules: [
@@ -103,6 +107,7 @@ describe('RpcHandler', () => {
103107
handler = createRpcHandler({
104108
permissionHandlerFactory: mockHandlerFactory,
105109
profileSyncManager: mockProfileSyncManager,
110+
supportedChainIds: TEST_SUPPORTED_CHAIN_IDS,
106111
});
107112
});
108113

@@ -130,6 +135,54 @@ describe('RpcHandler', () => {
130135
expect(result).toStrictEqual([VALID_PERMISSION_RESPONSE]);
131136
});
132137

138+
it('should handle a permission request with a different supported chainId successfully', async () => {
139+
const differentSupportedChainId = '0x10';
140+
const permissionRequestWithDifferentSupportedChainId = {
141+
...VALID_PERMISSION_REQUEST,
142+
chainId: differentSupportedChainId,
143+
};
144+
145+
const requestWithDifferentSupportedChainId = {
146+
permissionsRequest: [
147+
permissionRequestWithDifferentSupportedChainId,
148+
] as unknown as Json[],
149+
siteOrigin: TEST_SITE_ORIGIN,
150+
};
151+
152+
const responseWithDifferentSupportedChainId = {
153+
approved: true,
154+
response: {
155+
...VALID_PERMISSION_RESPONSE,
156+
chainId: differentSupportedChainId,
157+
},
158+
} as const;
159+
160+
mockHandler.handlePermissionRequest.mockImplementation(
161+
async () => responseWithDifferentSupportedChainId,
162+
);
163+
164+
const result = await handler.grantPermission(
165+
requestWithDifferentSupportedChainId,
166+
);
167+
168+
expect(mockHandlerFactory.createPermissionHandler).toHaveBeenCalledTimes(
169+
1,
170+
);
171+
172+
expect(mockHandlerFactory.createPermissionHandler).toHaveBeenCalledWith(
173+
permissionRequestWithDifferentSupportedChainId,
174+
);
175+
176+
expect(mockHandler.handlePermissionRequest).toHaveBeenCalledTimes(1);
177+
expect(mockHandler.handlePermissionRequest).toHaveBeenCalledWith(
178+
TEST_SITE_ORIGIN,
179+
);
180+
181+
expect(result).toStrictEqual([
182+
responseWithDifferentSupportedChainId.response,
183+
]);
184+
});
185+
133186
it('should throw an error if no parameters are provided', async () => {
134187
await expect(handler.grantPermission()).rejects.toThrow(
135188
'Failed type validation: : Required',
@@ -213,12 +266,12 @@ describe('RpcHandler', () => {
213266
it('should handle multiple permission requests in parallel', async () => {
214267
const secondPermissionRequest = {
215268
...VALID_PERMISSION_REQUEST,
216-
chainId: '0x2' as const,
269+
chainId: TEST_CHAIN_ID,
217270
};
218271

219272
const secondResponse = {
220273
...VALID_PERMISSION_RESPONSE,
221-
chainId: '0x2' as const,
274+
chainId: TEST_CHAIN_ID,
222275
};
223276

224277
const request: Json = {
@@ -259,7 +312,7 @@ describe('RpcHandler', () => {
259312
it('should handle mixed success/failure responses for multiple requests', async () => {
260313
const secondPermissionRequest = {
261314
...VALID_PERMISSION_REQUEST,
262-
chainId: '0x2' as const,
315+
chainId: TEST_CHAIN_ID,
263316
};
264317

265318
const request: Json = {
@@ -290,12 +343,12 @@ describe('RpcHandler', () => {
290343
it('should process multiple permission requests sequentially (no concurrency)', async () => {
291344
const secondPermissionRequest = {
292345
...VALID_PERMISSION_REQUEST,
293-
chainId: '0x2' as const,
346+
chainId: TEST_CHAIN_ID,
294347
};
295348

296349
const secondResponse = {
297350
...VALID_PERMISSION_RESPONSE,
298-
chainId: '0x2' as const,
351+
chainId: TEST_CHAIN_ID,
299352
};
300353

301354
const request: Json = {
@@ -448,12 +501,12 @@ describe('RpcHandler', () => {
448501
it('should maintain response order matching request order', async () => {
449502
const secondPermissionRequest = {
450503
...VALID_PERMISSION_REQUEST,
451-
chainId: '0x2' as const,
504+
chainId: TEST_CHAIN_ID,
452505
};
453506

454507
const secondResponse = {
455508
...VALID_PERMISSION_RESPONSE,
456-
chainId: '0x2' as const,
509+
chainId: TEST_CHAIN_ID,
457510
};
458511

459512
const request: Json = {
@@ -480,6 +533,20 @@ describe('RpcHandler', () => {
480533
const result = await handler.grantPermission(request);
481534
expect(result).toStrictEqual([VALID_PERMISSION_RESPONSE, secondResponse]);
482535
});
536+
537+
it('throws an error if the chain ID is not supported', async () => {
538+
const request: Json = {
539+
permissionsRequest: [
540+
VALID_PERMISSION_REQUEST,
541+
{ ...VALID_PERMISSION_REQUEST, chainId: UNSUPPORTED_CHAIN_ID },
542+
],
543+
siteOrigin: TEST_SITE_ORIGIN,
544+
} as unknown as Json;
545+
546+
await expect(handler.grantPermission(request)).rejects.toThrow(
547+
`Unsupported chain IDs: ${UNSUPPORTED_CHAIN_ID}`,
548+
);
549+
});
483550
});
484551

485552
describe('getPermissionOffers', () => {
@@ -532,7 +599,7 @@ describe('RpcHandler', () => {
532599
},
533600
{
534601
permissionResponse: {
535-
chainId: '0x2' as const,
602+
chainId: TEST_CHAIN_ID,
536603
expiry: TEST_EXPIRY + 1000,
537604
signer: {
538605
type: 'account' as const,

packages/permissions-kernel-snap/.env.example

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,4 @@ SNAP_ENV=local
55
# Permissions snap id
66
GATOR_PERMISSIONS_PROVIDER_SNAP_ID=local:http://localhost:8082
77

8-
9-
# Prod
10-
SNAP_ENV=production
11-
GATOR_PERMISSIONS_PROVIDER_SNAP_ID=npm:@metamask/gator-permissions-snap
12-
# By default logging is disabled in production. If you want to enable it in production you have to set this variable to true
13-
# ENABLE_LOGGING=true
8+
# Production configuration can be found here https://github.com/MetaMask/snap-7715-permissions/settings/variables/actions

packages/site/.env.example

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,4 @@ GATSBY_KERNEL_SNAP_ORIGIN=local:http://localhost:8081
33
GATSBY_GATOR_SNAP_ORIGIN=local:http://localhost:8082
44
GATSBY_MESSAGE_SIGNING_SNAP_ORIGIN=npm:@metamask/message-signing-snap
55
GATSBY_BUNDLER_RPC_URL=https://<bundler-url>
6-
GATSBY_SUPPORTED_CHAINS=1,11155111
7-
8-
# Production
9-
GATSBY_GATOR_SNAP_ORIGIN=npm:@metamask/gator-permissions-snap
10-
GATSBY_KERNEL_SNAP_ORIGIN=npm:@metamask/permissions-kernel-snap
11-
GATSBY_MESSAGE_SIGNING_SNAP_ORIGIN=npm:@metamask/message-signing-snap
12-
GATSBY_BUNDLER_RPC_URL=https://<bundler-url>
136
GATSBY_SUPPORTED_CHAINS=11155111

0 commit comments

Comments
 (0)