{"version":3,"file":"manageState.cjs","sourceRoot":"","sources":["../../src/restricted/manageState.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CryptographicFunctions } from '@metamask/key-tree';\nimport type {\n  PermissionSpecificationBuilder,\n  RestrictedMethodOptions,\n  ValidPermissionSpecification,\n} from '@metamask/permission-controller';\nimport { PermissionType, SubjectType } from '@metamask/permission-controller';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type { ManageStateParams, ManageStateResult } from '@metamask/snaps-sdk';\nimport { ManageStateOperation } from '@metamask/snaps-sdk';\nimport { STATE_ENCRYPTION_MAGIC_VALUE } from '@metamask/snaps-utils';\nimport type { Json, NonEmptyArray } from '@metamask/utils';\nimport { isObject, getJsonSize } from '@metamask/utils';\n\nimport type { MethodHooksObject } from '../utils';\nimport { deriveEntropyFromSeed } from '../utils';\n\n// The salt used for SIP-6-based entropy derivation.\nexport const STATE_ENCRYPTION_SALT = 'snap_manageState encryption';\n\nconst methodName = 'snap_manageState';\n\nexport type ManageStateMethodHooks = {\n  /**\n   * Waits for the extension to be unlocked.\n   *\n   * @returns A promise that resolves once the extension is unlocked.\n   */\n  getUnlockPromise: (shouldShowUnlockRequest: boolean) => Promise<void>;\n\n  /**\n   * A function that clears the state of the requesting Snap.\n   */\n  clearSnapState: (snapId: string, encrypted: boolean) => void;\n\n  /**\n   * A function that gets the encrypted state of the requesting Snap.\n   *\n   * @returns The current state of the Snap.\n   */\n  getSnapState: (\n    snapId: string,\n    encrypted: boolean,\n  ) => Promise<Record<string, Json>>;\n\n  /**\n   * A function that updates the state of the requesting Snap.\n   *\n   * @param newState - The new state of the Snap.\n   */\n  updateSnapState: (\n    snapId: string,\n    newState: Record<string, Json>,\n    encrypted: boolean,\n  ) => Promise<void>;\n};\n\ntype ManageStateSpecificationBuilderOptions = {\n  allowedCaveats?: Readonly<NonEmptyArray<string>> | null;\n  methodHooks: ManageStateMethodHooks;\n};\n\ntype ManageStateSpecification = ValidPermissionSpecification<{\n  permissionType: PermissionType.RestrictedMethod;\n  targetName: typeof methodName;\n  methodImplementation: ReturnType<typeof getManageStateImplementation>;\n  allowedCaveats: Readonly<NonEmptyArray<string>> | null;\n}>;\n\n/**\n * The specification builder for the `snap_manageState` permission.\n * `snap_manageState` lets the Snap store and manage some of its state on\n * your device.\n *\n * @param options - The specification builder options.\n * @param options.allowedCaveats - The optional allowed caveats for the permission.\n * @param options.methodHooks - The RPC method hooks needed by the method implementation.\n * @returns The specification for the `snap_manageState` permission.\n */\nexport const specificationBuilder: PermissionSpecificationBuilder<\n  PermissionType.RestrictedMethod,\n  ManageStateSpecificationBuilderOptions,\n  ManageStateSpecification\n> = ({\n  allowedCaveats = null,\n  methodHooks,\n}: ManageStateSpecificationBuilderOptions) => {\n  return {\n    permissionType: PermissionType.RestrictedMethod,\n    targetName: methodName,\n    allowedCaveats,\n    methodImplementation: getManageStateImplementation(methodHooks),\n    subjectTypes: [SubjectType.Snap],\n  };\n};\n\nconst methodHooks: MethodHooksObject<ManageStateMethodHooks> = {\n  getUnlockPromise: true,\n  clearSnapState: true,\n  getSnapState: true,\n  updateSnapState: true,\n};\n\nexport const manageStateBuilder = Object.freeze({\n  targetName: methodName,\n  specificationBuilder,\n  methodHooks,\n} as const);\n\nexport const STORAGE_SIZE_LIMIT = 64_000_000; // In bytes (64 MB)\n\ntype GetEncryptionKeyArgs = {\n  snapId: string;\n  seed: Uint8Array;\n  cryptographicFunctions?: CryptographicFunctions | undefined;\n};\n\n/**\n * Get a deterministic encryption key to use for encrypting and decrypting the\n * state.\n *\n * This key should only be used for state encryption using `snap_manageState`.\n * To get other encryption keys, a different salt can be used.\n *\n * @param args - The encryption key args.\n * @param args.snapId - The ID of the snap to get the encryption key for.\n * @param args.seed - The mnemonic seed to derive the encryption key\n * from.\n * @param args.cryptographicFunctions - The cryptographic functions to use for\n * the client.\n * @returns The state encryption key.\n */\nexport async function getEncryptionEntropy({\n  seed,\n  snapId,\n  cryptographicFunctions,\n}: GetEncryptionKeyArgs) {\n  return await deriveEntropyFromSeed({\n    seed,\n    input: snapId,\n    salt: STATE_ENCRYPTION_SALT,\n    magic: STATE_ENCRYPTION_MAGIC_VALUE,\n    cryptographicFunctions,\n  });\n}\n\n/**\n * Builds the method implementation for `snap_manageState`.\n *\n * @param hooks - The RPC method hooks.\n * @param hooks.clearSnapState - A function that clears the state stored for a\n * snap.\n * @param hooks.getSnapState - A function that fetches the persisted decrypted\n * state for a snap.\n * @param hooks.updateSnapState - A function that updates the state stored for a\n * snap.\n * @param hooks.getUnlockPromise - A function that resolves once the MetaMask\n * extension is unlocked and prompts the user to unlock their MetaMask if it is\n * locked.\n * @returns The method implementation which either returns `null` for a\n * successful state update/deletion or returns the decrypted state.\n * @throws If the params are invalid.\n */\nexport function getManageStateImplementation({\n  getUnlockPromise,\n  clearSnapState,\n  getSnapState,\n  updateSnapState,\n}: ManageStateMethodHooks) {\n  return async function manageState(\n    options: RestrictedMethodOptions<ManageStateParams>,\n  ): Promise<ManageStateResult> {\n    const {\n      params = {},\n      method,\n      context: { origin },\n    } = options;\n    const validatedParams = getValidatedParams(params, method);\n\n    // If the encrypted param is undefined or null we default to true.\n    const shouldEncrypt = validatedParams.encrypted ?? true;\n\n    // We only need to prompt the user when the mnemonic is needed\n    // which it isn't for the clear operation or unencrypted storage.\n    if (\n      shouldEncrypt &&\n      validatedParams.operation !== ManageStateOperation.ClearState\n    ) {\n      await getUnlockPromise(true);\n    }\n\n    switch (validatedParams.operation) {\n      case ManageStateOperation.ClearState:\n        clearSnapState(origin, shouldEncrypt);\n        return null;\n\n      case ManageStateOperation.GetState: {\n        return await getSnapState(origin, shouldEncrypt);\n      }\n\n      case ManageStateOperation.UpdateState: {\n        await updateSnapState(origin, validatedParams.newState, shouldEncrypt);\n        return null;\n      }\n\n      default:\n        throw rpcErrors.invalidParams(\n          `Invalid ${method} operation: \"${\n            validatedParams.operation as string\n          }\"`,\n        );\n    }\n  };\n}\n\n/**\n * Validates the manageState method `params` and returns them cast to the correct\n * type. Throws if validation fails.\n *\n * @param params - The unvalidated params object from the method request.\n * @param method - RPC method name used for debugging errors.\n * @param storageSizeLimit - Maximum allowed size (in bytes) of a new state object.\n * @returns The validated method parameter object.\n */\nexport function getValidatedParams(\n  params: unknown,\n  method: string,\n  storageSizeLimit = STORAGE_SIZE_LIMIT,\n): ManageStateParams {\n  if (!isObject(params)) {\n    throw rpcErrors.invalidParams({\n      message: 'Expected params to be a single object.',\n    });\n  }\n\n  const { operation, newState, encrypted } = params;\n\n  if (\n    !operation ||\n    typeof operation !== 'string' ||\n    !Object.values(ManageStateOperation).includes(\n      operation as ManageStateOperation,\n    )\n  ) {\n    throw rpcErrors.invalidParams({\n      message: 'Must specify a valid manage state \"operation\".',\n    });\n  }\n\n  if (encrypted !== undefined && typeof encrypted !== 'boolean') {\n    throw rpcErrors.invalidParams({\n      message: '\"encrypted\" parameter must be a boolean if specified.',\n    });\n  }\n\n  if (operation === ManageStateOperation.UpdateState) {\n    if (!isObject(newState)) {\n      throw rpcErrors.invalidParams({\n        message: `Invalid ${method} \"newState\" parameter: The new state must be a plain object.`,\n      });\n    }\n\n    let size;\n    try {\n      // `getJsonSize` will throw if the state is not JSON serializable.\n      size = getJsonSize(newState);\n    } catch {\n      throw rpcErrors.invalidParams({\n        message: `Invalid ${method} \"newState\" parameter: The new state must be JSON serializable.`,\n      });\n    }\n\n    if (size > storageSizeLimit) {\n      throw rpcErrors.invalidParams({\n        message: `Invalid ${method} \"newState\" parameter: The new state must not exceed ${\n          storageSizeLimit / 1_000_000\n        } MB in size.`,\n      });\n    }\n  }\n\n  return params as ManageStateParams;\n}\n"]}