UNPKG

@metamask/snaps-rpc-methods

Version:
169 lines 7.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getValidatedParams = exports.getManageStateImplementation = exports.getEncryptionEntropy = exports.STORAGE_SIZE_LIMIT = exports.manageStateBuilder = exports.specificationBuilder = exports.STATE_ENCRYPTION_SALT = void 0; const permission_controller_1 = require("@metamask/permission-controller"); const rpc_errors_1 = require("@metamask/rpc-errors"); const snaps_sdk_1 = require("@metamask/snaps-sdk"); const snaps_utils_1 = require("@metamask/snaps-utils"); const utils_1 = require("@metamask/utils"); const utils_2 = require("../utils.cjs"); // The salt used for SIP-6-based entropy derivation. exports.STATE_ENCRYPTION_SALT = 'snap_manageState encryption'; const methodName = 'snap_manageState'; /** * The specification builder for the `snap_manageState` permission. * `snap_manageState` lets the Snap store and manage some of its state on * your device. * * @param options - The specification builder options. * @param options.allowedCaveats - The optional allowed caveats for the permission. * @param options.methodHooks - The RPC method hooks needed by the method implementation. * @returns The specification for the `snap_manageState` permission. */ const specificationBuilder = ({ allowedCaveats = null, methodHooks, }) => { return { permissionType: permission_controller_1.PermissionType.RestrictedMethod, targetName: methodName, allowedCaveats, methodImplementation: getManageStateImplementation(methodHooks), subjectTypes: [permission_controller_1.SubjectType.Snap], }; }; exports.specificationBuilder = specificationBuilder; const methodHooks = { getUnlockPromise: true, clearSnapState: true, getSnapState: true, updateSnapState: true, }; exports.manageStateBuilder = Object.freeze({ targetName: methodName, specificationBuilder: exports.specificationBuilder, methodHooks, }); exports.STORAGE_SIZE_LIMIT = 64000000; // In bytes (64 MB) /** * Get a deterministic encryption key to use for encrypting and decrypting the * state. * * This key should only be used for state encryption using `snap_manageState`. * To get other encryption keys, a different salt can be used. * * @param args - The encryption key args. * @param args.snapId - The ID of the snap to get the encryption key for. * @param args.seed - The mnemonic seed to derive the encryption key * from. * @param args.cryptographicFunctions - The cryptographic functions to use for * the client. * @returns The state encryption key. */ async function getEncryptionEntropy({ seed, snapId, cryptographicFunctions, }) { return await (0, utils_2.deriveEntropyFromSeed)({ seed, input: snapId, salt: exports.STATE_ENCRYPTION_SALT, magic: snaps_utils_1.STATE_ENCRYPTION_MAGIC_VALUE, cryptographicFunctions, }); } exports.getEncryptionEntropy = getEncryptionEntropy; /** * Builds the method implementation for `snap_manageState`. * * @param hooks - The RPC method hooks. * @param hooks.clearSnapState - A function that clears the state stored for a * snap. * @param hooks.getSnapState - A function that fetches the persisted decrypted * state for a snap. * @param hooks.updateSnapState - A function that updates the state stored for a * snap. * @param hooks.getUnlockPromise - A function that resolves once the MetaMask * extension is unlocked and prompts the user to unlock their MetaMask if it is * locked. * @returns The method implementation which either returns `null` for a * successful state update/deletion or returns the decrypted state. * @throws If the params are invalid. */ function getManageStateImplementation({ getUnlockPromise, clearSnapState, getSnapState, updateSnapState, }) { return async function manageState(options) { const { params = {}, method, context: { origin }, } = options; const validatedParams = getValidatedParams(params, method); // If the encrypted param is undefined or null we default to true. const shouldEncrypt = validatedParams.encrypted ?? true; // We only need to prompt the user when the mnemonic is needed // which it isn't for the clear operation or unencrypted storage. if (shouldEncrypt && validatedParams.operation !== snaps_sdk_1.ManageStateOperation.ClearState) { await getUnlockPromise(true); } switch (validatedParams.operation) { case snaps_sdk_1.ManageStateOperation.ClearState: clearSnapState(origin, shouldEncrypt); return null; case snaps_sdk_1.ManageStateOperation.GetState: { return await getSnapState(origin, shouldEncrypt); } case snaps_sdk_1.ManageStateOperation.UpdateState: { await updateSnapState(origin, validatedParams.newState, shouldEncrypt); return null; } default: throw rpc_errors_1.rpcErrors.invalidParams(`Invalid ${method} operation: "${validatedParams.operation}"`); } }; } exports.getManageStateImplementation = getManageStateImplementation; /** * Validates the manageState method `params` and returns them cast to the correct * type. Throws if validation fails. * * @param params - The unvalidated params object from the method request. * @param method - RPC method name used for debugging errors. * @param storageSizeLimit - Maximum allowed size (in bytes) of a new state object. * @returns The validated method parameter object. */ function getValidatedParams(params, method, storageSizeLimit = exports.STORAGE_SIZE_LIMIT) { if (!(0, utils_1.isObject)(params)) { throw rpc_errors_1.rpcErrors.invalidParams({ message: 'Expected params to be a single object.', }); } const { operation, newState, encrypted } = params; if (!operation || typeof operation !== 'string' || !Object.values(snaps_sdk_1.ManageStateOperation).includes(operation)) { throw rpc_errors_1.rpcErrors.invalidParams({ message: 'Must specify a valid manage state "operation".', }); } if (encrypted !== undefined && typeof encrypted !== 'boolean') { throw rpc_errors_1.rpcErrors.invalidParams({ message: '"encrypted" parameter must be a boolean if specified.', }); } if (operation === snaps_sdk_1.ManageStateOperation.UpdateState) { if (!(0, utils_1.isObject)(newState)) { throw rpc_errors_1.rpcErrors.invalidParams({ message: `Invalid ${method} "newState" parameter: The new state must be a plain object.`, }); } let size; try { // `getJsonSize` will throw if the state is not JSON serializable. size = (0, utils_1.getJsonSize)(newState); } catch { throw rpc_errors_1.rpcErrors.invalidParams({ message: `Invalid ${method} "newState" parameter: The new state must be JSON serializable.`, }); } if (size > storageSizeLimit) { throw rpc_errors_1.rpcErrors.invalidParams({ message: `Invalid ${method} "newState" parameter: The new state must not exceed ${storageSizeLimit / 1000000} MB in size.`, }); } } return params; } exports.getValidatedParams = getValidatedParams; //# sourceMappingURL=manageState.cjs.map