@metamask/snaps-rpc-methods
Version:
MetaMask Snaps JSON-RPC method implementations
1 lines • 10.3 kB
Source Map (JSON)
{"version":3,"file":"requestSnaps.cjs","sourceRoot":"","sources":["../../src/permitted/requestSnaps.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine';\nimport type {\n PermissionConstraint,\n RequestedPermissions,\n Caveat,\n PermittedHandlerExport,\n} from '@metamask/permission-controller';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type {\n RequestSnapsParams,\n RequestSnapsResult,\n} from '@metamask/snaps-sdk';\nimport type { SnapsPermissionRequest } from '@metamask/snaps-utils';\nimport {\n SnapCaveatType,\n verifyRequestedSnapPermissions,\n} from '@metamask/snaps-utils';\nimport type {\n JsonRpcRequest,\n PendingJsonRpcResponse,\n Json,\n} from '@metamask/utils';\nimport { hasProperty, isObject } from '@metamask/utils';\n\nimport { WALLET_SNAP_PERMISSION_KEY } from '../restricted/invokeSnap';\nimport type { MethodHooksObject } from '../utils';\n\nconst hookNames: MethodHooksObject<RequestSnapsHooks> = {\n installSnaps: true,\n requestPermissions: true,\n getPermissions: true,\n};\n\n/**\n * `wallet_requestSnaps` installs the requested Snaps and requests permission to use them if necessary.\n */\nexport const requestSnapsHandler: PermittedHandlerExport<\n RequestSnapsHooks,\n RequestSnapsParams,\n RequestSnapsResult\n> = {\n methodNames: ['wallet_requestSnaps'],\n implementation: requestSnapsImplementation,\n hookNames,\n};\n\nexport type RequestSnapsHooks = {\n /**\n * Installs the requested snaps if they are permitted.\n */\n installSnaps: (\n requestedSnaps: RequestSnapsParams,\n ) => Promise<RequestSnapsResult>;\n\n /**\n * Initiates a permission request for the requesting origin.\n *\n * @returns The result of the permissions request.\n */\n requestPermissions: (\n permissions: RequestedPermissions,\n ) => Promise<\n [\n Record<string, PermissionConstraint>,\n { data: Record<string, unknown>; id: string; origin: string },\n ]\n >;\n\n /**\n * Gets the current permissions for the requesting origin.\n *\n * @returns The current permissions of the requesting origin.\n */\n getPermissions: () => Promise<\n Record<string, PermissionConstraint> | undefined\n >;\n};\n\n/**\n * Checks whether an origin has existing `wallet_snap` permission and\n * whether or not it has the requested snapIds caveat.\n *\n * @param existingPermissions - The existing permissions for the origin.\n * @param requestedSnaps - The requested snaps.\n * @returns True if the existing permissions satisfy the requested snaps, otherwise false.\n */\nexport function hasRequestedSnaps(\n existingPermissions: Record<string, PermissionConstraint>,\n requestedSnaps: Record<string, unknown>,\n): boolean {\n const snapIdCaveat = existingPermissions[\n WALLET_SNAP_PERMISSION_KEY\n ]?.caveats?.find(\n (caveat: Caveat<string, Json>) => caveat.type === SnapCaveatType.SnapIds,\n );\n\n const permittedSnaps = snapIdCaveat?.value;\n if (isObject(permittedSnaps)) {\n return Object.keys(requestedSnaps).every((requestedSnap) =>\n hasProperty(permittedSnaps, requestedSnap),\n );\n }\n return false;\n}\n\n/**\n * Constructs a valid permission request with merged caveats based on existing permissions\n * and the requested snaps.\n *\n * @param existingPermissions - The existing permissions for the origin.\n * @param requestedPermissions - The permission request passed into `requestPermissions`.\n * @returns `requestedPermissions`.\n */\nexport function getSnapPermissionsRequest(\n existingPermissions: Record<string, PermissionConstraint>,\n requestedPermissions: unknown,\n): SnapsPermissionRequest {\n verifyRequestedSnapPermissions(requestedPermissions);\n\n if (!existingPermissions[WALLET_SNAP_PERMISSION_KEY]) {\n return requestedPermissions;\n }\n\n const snapIdCaveat = existingPermissions[\n WALLET_SNAP_PERMISSION_KEY\n ].caveats?.find(\n (caveat: Caveat<string, Json>) => caveat.type === SnapCaveatType.SnapIds,\n );\n\n const permittedSnaps = (snapIdCaveat?.value as Record<string, Json>) ?? {};\n\n const requestedSnaps =\n requestedPermissions[WALLET_SNAP_PERMISSION_KEY].caveats[0].value;\n\n const snapIdSet = new Set([\n ...Object.keys(permittedSnaps),\n ...Object.keys(requestedSnaps),\n ]);\n\n const mergedCaveatValue = [...snapIdSet].reduce<Record<string, Json>>(\n (request, snapId) => {\n request[snapId] = requestedSnaps[snapId] ?? permittedSnaps[snapId];\n return request;\n },\n {},\n );\n\n requestedPermissions[WALLET_SNAP_PERMISSION_KEY].caveats[0].value =\n mergedCaveatValue;\n\n return requestedPermissions;\n}\n\n/**\n * The `wallet_requestSnaps` method implementation.\n * Tries to install the requested snaps and adds them to the JSON-RPC response.\n *\n * @param req - The JSON-RPC request object.\n * @param res - The JSON-RPC response object.\n * @param _next - The `json-rpc-engine` \"next\" callback. Not used by this\n * function.\n * @param end - The `json-rpc-engine` \"end\" callback.\n * @param hooks - The RPC method hooks.\n * @param hooks.installSnaps - A function that tries to install a given snap, prompting the user if necessary.\n * @param hooks.requestPermissions - A function that requests permissions on\n * behalf of a subject.\n * @param hooks.getPermissions - A function that gets the current permissions.\n * @returns A promise that resolves once the JSON-RPC response has been modified.\n * @throws If the params are invalid.\n */\nasync function requestSnapsImplementation(\n req: JsonRpcRequest<RequestSnapsParams>,\n res: PendingJsonRpcResponse<RequestSnapsResult>,\n _next: unknown,\n end: JsonRpcEngineEndCallback,\n { installSnaps, requestPermissions, getPermissions }: RequestSnapsHooks,\n): Promise<void> {\n const requestedSnaps = req.params;\n if (!isObject(requestedSnaps)) {\n return end(\n rpcErrors.invalidParams({\n message: '\"params\" must be an object.',\n }),\n );\n }\n\n try {\n if (Object.keys(requestedSnaps).length === 0) {\n return end(\n rpcErrors.invalidParams({\n message: 'Request must have at least one requested snap.',\n }),\n );\n }\n\n const requestedPermissions = {\n [WALLET_SNAP_PERMISSION_KEY]: {\n caveats: [{ type: SnapCaveatType.SnapIds, value: requestedSnaps }],\n },\n } as RequestedPermissions;\n const existingPermissions = await getPermissions();\n\n if (!existingPermissions) {\n const [, metadata] = await requestPermissions(requestedPermissions);\n res.result = metadata.data[\n WALLET_SNAP_PERMISSION_KEY\n ] as RequestSnapsResult;\n } else if (hasRequestedSnaps(existingPermissions, requestedSnaps)) {\n res.result = await installSnaps(requestedSnaps);\n } else {\n const mergedPermissionsRequest = getSnapPermissionsRequest(\n existingPermissions,\n requestedPermissions,\n );\n\n const [, metadata] = await requestPermissions(mergedPermissionsRequest);\n res.result = metadata.data[\n WALLET_SNAP_PERMISSION_KEY\n ] as RequestSnapsResult;\n }\n } catch (error) {\n res.error = error;\n }\n\n return end();\n}\n"]}