@metamask/snaps-rpc-methods
Version:
MetaMask Snaps JSON-RPC method implementations
121 lines • 5.53 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSnapPermissionsRequest = exports.hasRequestedSnaps = exports.requestSnapsHandler = void 0;
const rpc_errors_1 = require("@metamask/rpc-errors");
const snaps_utils_1 = require("@metamask/snaps-utils");
const utils_1 = require("@metamask/utils");
const invokeSnap_1 = require("../restricted/invokeSnap.cjs");
const hookNames = {
installSnaps: true,
requestPermissions: true,
getPermissions: true,
};
/**
* `wallet_requestSnaps` installs the requested Snaps and requests permission to use them if necessary.
*/
exports.requestSnapsHandler = {
methodNames: ['wallet_requestSnaps'],
implementation: requestSnapsImplementation,
hookNames,
};
/**
* Checks whether an origin has existing `wallet_snap` permission and
* whether or not it has the requested snapIds caveat.
*
* @param existingPermissions - The existing permissions for the origin.
* @param requestedSnaps - The requested snaps.
* @returns True if the existing permissions satisfy the requested snaps, otherwise false.
*/
function hasRequestedSnaps(existingPermissions, requestedSnaps) {
const snapIdCaveat = existingPermissions[invokeSnap_1.WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat) => caveat.type === snaps_utils_1.SnapCaveatType.SnapIds);
const permittedSnaps = snapIdCaveat?.value;
if ((0, utils_1.isObject)(permittedSnaps)) {
return Object.keys(requestedSnaps).every((requestedSnap) => (0, utils_1.hasProperty)(permittedSnaps, requestedSnap));
}
return false;
}
exports.hasRequestedSnaps = hasRequestedSnaps;
/**
* Constructs a valid permission request with merged caveats based on existing permissions
* and the requested snaps.
*
* @param existingPermissions - The existing permissions for the origin.
* @param requestedPermissions - The permission request passed into `requestPermissions`.
* @returns `requestedPermissions`.
*/
function getSnapPermissionsRequest(existingPermissions, requestedPermissions) {
(0, snaps_utils_1.verifyRequestedSnapPermissions)(requestedPermissions);
if (!existingPermissions[invokeSnap_1.WALLET_SNAP_PERMISSION_KEY]) {
return requestedPermissions;
}
const snapIdCaveat = existingPermissions[invokeSnap_1.WALLET_SNAP_PERMISSION_KEY].caveats?.find((caveat) => caveat.type === snaps_utils_1.SnapCaveatType.SnapIds);
const permittedSnaps = snapIdCaveat?.value ?? {};
const requestedSnaps = requestedPermissions[invokeSnap_1.WALLET_SNAP_PERMISSION_KEY].caveats[0].value;
const snapIdSet = new Set([
...Object.keys(permittedSnaps),
...Object.keys(requestedSnaps),
]);
const mergedCaveatValue = [...snapIdSet].reduce((request, snapId) => {
request[snapId] = requestedSnaps[snapId] ?? permittedSnaps[snapId];
return request;
}, {});
requestedPermissions[invokeSnap_1.WALLET_SNAP_PERMISSION_KEY].caveats[0].value =
mergedCaveatValue;
return requestedPermissions;
}
exports.getSnapPermissionsRequest = getSnapPermissionsRequest;
/**
* The `wallet_requestSnaps` method implementation.
* Tries to install the requested snaps and adds them to the JSON-RPC response.
*
* @param req - The JSON-RPC request object.
* @param res - The JSON-RPC response object.
* @param _next - The `json-rpc-engine` "next" callback. Not used by this
* function.
* @param end - The `json-rpc-engine` "end" callback.
* @param hooks - The RPC method hooks.
* @param hooks.installSnaps - A function that tries to install a given snap, prompting the user if necessary.
* @param hooks.requestPermissions - A function that requests permissions on
* behalf of a subject.
* @param hooks.getPermissions - A function that gets the current permissions.
* @returns A promise that resolves once the JSON-RPC response has been modified.
* @throws If the params are invalid.
*/
async function requestSnapsImplementation(req, res, _next, end, { installSnaps, requestPermissions, getPermissions }) {
const requestedSnaps = req.params;
if (!(0, utils_1.isObject)(requestedSnaps)) {
return end(rpc_errors_1.rpcErrors.invalidParams({
message: '"params" must be an object.',
}));
}
try {
if (Object.keys(requestedSnaps).length === 0) {
return end(rpc_errors_1.rpcErrors.invalidParams({
message: 'Request must have at least one requested snap.',
}));
}
const requestedPermissions = {
[invokeSnap_1.WALLET_SNAP_PERMISSION_KEY]: {
caveats: [{ type: snaps_utils_1.SnapCaveatType.SnapIds, value: requestedSnaps }],
},
};
const existingPermissions = await getPermissions();
if (!existingPermissions) {
const [, metadata] = await requestPermissions(requestedPermissions);
res.result = metadata.data[invokeSnap_1.WALLET_SNAP_PERMISSION_KEY];
}
else if (hasRequestedSnaps(existingPermissions, requestedSnaps)) {
res.result = await installSnaps(requestedSnaps);
}
else {
const mergedPermissionsRequest = getSnapPermissionsRequest(existingPermissions, requestedPermissions);
const [, metadata] = await requestPermissions(mergedPermissionsRequest);
res.result = metadata.data[invokeSnap_1.WALLET_SNAP_PERMISSION_KEY];
}
}
catch (error) {
res.error = error;
}
return end();
}
//# sourceMappingURL=requestSnaps.cjs.map