{"version":3,"file":"requestSnaps.cjs","sourceRoot":"","sources":["../../src/permitted/requestSnaps.ts"],"names":[],"mappings":";;;AAOA,qDAAiD;AAMjD,uDAG+B;AAM/B,2CAAwD;AAExD,6DAAsE;AAGtE,MAAM,SAAS,GAAyC;IACtD,YAAY,EAAE,IAAI;IAClB,kBAAkB,EAAE,IAAI;IACxB,cAAc,EAAE,IAAI;CACrB,CAAC;AAEF;;GAEG;AACU,QAAA,mBAAmB,GAI5B;IACF,WAAW,EAAE,CAAC,qBAAqB,CAAC;IACpC,cAAc,EAAE,0BAA0B;IAC1C,SAAS;CACV,CAAC;AAkCF;;;;;;;GAOG;AACH,SAAgB,iBAAiB,CAC/B,mBAAyD,EACzD,cAAuC;IAEvC,MAAM,YAAY,GAAG,mBAAmB,CACtC,uCAA0B,CAC3B,EAAE,OAAO,EAAE,IAAI,CACd,CAAC,MAA4B,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,4BAAc,CAAC,OAAO,CACzE,CAAC;IAEF,MAAM,cAAc,GAAG,YAAY,EAAE,KAAK,CAAC;IAC3C,IAAI,IAAA,gBAAQ,EAAC,cAAc,CAAC,EAAE,CAAC;QAC7B,OAAO,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,CAAC,aAAa,EAAE,EAAE,CACzD,IAAA,mBAAW,EAAC,cAAc,EAAE,aAAa,CAAC,CAC3C,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAjBD,8CAiBC;AAED;;;;;;;GAOG;AACH,SAAgB,yBAAyB,CACvC,mBAAyD,EACzD,oBAA6B;IAE7B,IAAA,4CAA8B,EAAC,oBAAoB,CAAC,CAAC;IAErD,IAAI,CAAC,mBAAmB,CAAC,uCAA0B,CAAC,EAAE,CAAC;QACrD,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAED,MAAM,YAAY,GAAG,mBAAmB,CACtC,uCAA0B,CAC3B,CAAC,OAAO,EAAE,IAAI,CACb,CAAC,MAA4B,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,4BAAc,CAAC,OAAO,CACzE,CAAC;IAEF,MAAM,cAAc,GAAI,YAAY,EAAE,KAA8B,IAAI,EAAE,CAAC;IAE3E,MAAM,cAAc,GAClB,oBAAoB,CAAC,uCAA0B,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAEpE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;QACxB,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC;QAC9B,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC;KAC/B,CAAC,CAAC;IAEH,MAAM,iBAAiB,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,CAC7C,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAClB,OAAO,CAAC,MAAM,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;QACnE,OAAO,OAAO,CAAC;IACjB,CAAC,EACD,EAAE,CACH,CAAC;IAEF,oBAAoB,CAAC,uCAA0B,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK;QAC/D,iBAAiB,CAAC;IAEpB,OAAO,oBAAoB,CAAC;AAC9B,CAAC;AAtCD,8DAsCC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,KAAK,UAAU,0BAA0B,CACvC,GAAuC,EACvC,GAA+C,EAC/C,KAAc,EACd,GAA6B,EAC7B,EAAE,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAqB;IAEvE,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC;IAClC,IAAI,CAAC,IAAA,gBAAQ,EAAC,cAAc,CAAC,EAAE,CAAC;QAC9B,OAAO,GAAG,CACR,sBAAS,CAAC,aAAa,CAAC;YACtB,OAAO,EAAE,6BAA6B;SACvC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7C,OAAO,GAAG,CACR,sBAAS,CAAC,aAAa,CAAC;gBACtB,OAAO,EAAE,gDAAgD;aAC1D,CAAC,CACH,CAAC;QACJ,CAAC;QAED,MAAM,oBAAoB,GAAG;YAC3B,CAAC,uCAA0B,CAAC,EAAE;gBAC5B,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,4BAAc,CAAC,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;aACnE;SACsB,CAAC;QAC1B,MAAM,mBAAmB,GAAG,MAAM,cAAc,EAAE,CAAC;QAEnD,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACzB,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,MAAM,kBAAkB,CAAC,oBAAoB,CAAC,CAAC;YACpE,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC,IAAI,CACxB,uCAA0B,CACL,CAAC;QAC1B,CAAC;aAAM,IAAI,iBAAiB,CAAC,mBAAmB,EAAE,cAAc,CAAC,EAAE,CAAC;YAClE,GAAG,CAAC,MAAM,GAAG,MAAM,YAAY,CAAC,cAAc,CAAC,CAAC;QAClD,CAAC;aAAM,CAAC;YACN,MAAM,wBAAwB,GAAG,yBAAyB,CACxD,mBAAmB,EACnB,oBAAoB,CACrB,CAAC;YAEF,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,MAAM,kBAAkB,CAAC,wBAAwB,CAAC,CAAC;YACxE,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC,IAAI,CACxB,uCAA0B,CACL,CAAC;QAC1B,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,OAAO,GAAG,EAAE,CAAC;AACf,CAAC","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"]}