diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index b79222921..7833b25f3 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -1702,6 +1702,9 @@
"initialTransactionConfirmed": {
"message": "Your initial transaction was confirmed by the network. Click OK to go back."
},
+ "install": {
+ "message": "Install"
+ },
"insufficientBalance": {
"message": "Insufficient balance."
},
diff --git a/patches/@metamask+snap-controllers+0.20.0.patch b/patches/@metamask+snap-controllers+0.20.0.patch
new file mode 100644
index 000000000..6e1df558c
--- /dev/null
+++ b/patches/@metamask+snap-controllers+0.20.0.patch
@@ -0,0 +1,62 @@
+diff --git a/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js b/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js
+index ad84417..158e8e6 100644
+--- a/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js
++++ b/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js
+@@ -30,6 +30,7 @@ const RequestQueue_1 = require("./RequestQueue");
+ const utils_3 = require("./utils");
+ const Timer_1 = require("./Timer");
+ exports.controllerName = 'SnapController';
++exports.SNAP_APPROVAL_INSTALL = 'wallet_installSnap';
+ exports.SNAP_APPROVAL_UPDATE = 'wallet_updateSnap';
+ const TRUNCATED_SNAP_PROPERTIES = new Set([
+ 'initialPermissions',
+@@ -738,7 +739,7 @@ class SnapController extends controllers_1.BaseControllerV2 {
+ id: snapId,
+ versionRange,
+ });
+- await this.authorize(snapId);
++ await this.authorize(origin, snapId);
+ await this._startSnap({
+ snapId,
+ sourceCode,
+@@ -1073,18 +1074,34 @@ class SnapController extends controllers_1.BaseControllerV2 {
+ * @param snapId - The id of the Snap.
+ * @returns The snap's approvedPermissions.
+ */
+- async authorize(snapId) {
++ async authorize(origin, snapId) {
+ console.info(`Authorizing snap: ${snapId}`);
+ const snapsState = this.state.snaps;
+ const snap = snapsState[snapId];
+ const { initialPermissions } = snap;
+ try {
+- if ((0, utils_1.isNonEmptyArray)(Object.keys(initialPermissions))) {
+- const processedPermissions = this.processSnapPermissions(initialPermissions);
+- const [approvedPermissions] = await this.messagingSystem.call('PermissionController:requestPermissions', { origin: snapId }, processedPermissions);
+- return Object.values(approvedPermissions).map((perm) => perm.parentCapability);
++ const processedPermissions = this.processSnapPermissions(initialPermissions);
++ const id = (0, nanoid_1.nanoid)();
++ const isApproved = await this.messagingSystem.call('ApprovalController:addRequest', {
++ origin,
++ id,
++ type: exports.SNAP_APPROVAL_INSTALL,
++ requestData: {
++ // Mirror previous installation metadata
++ metadata: { id, origin: snapId, dappOrigin: origin },
++ permissions: processedPermissions,
++ snapId,
++ },
++ }, true);
++ if (!isApproved) {
++ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest();
++ }
++ if ((0, utils_1.isNonEmptyArray)(Object.keys(processedPermissions))) {
++ await this.messagingSystem.call('PermissionController:grantPermissions', {
++ approvedPermissions: processedPermissions,
++ subject: { origin: snapId },
++ });
+ }
+- return [];
+ }
+ finally {
+ const runtime = this.getRuntimeExpect(snapId);
diff --git a/test/e2e/snaps/test-snap-bip-44.spec.js b/test/e2e/snaps/test-snap-bip-44.spec.js
index 5795fa777..1db055b3a 100644
--- a/test/e2e/snaps/test-snap-bip-44.spec.js
+++ b/test/e2e/snaps/test-snap-bip-44.spec.js
@@ -79,6 +79,7 @@ describe('Test Snap bip-44', function () {
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
+ await driver.delay(1000);
await driver.clickElement('#sendBip44');
// check the results of the public key test
diff --git a/test/e2e/snaps/test-snap-confirm.spec.js b/test/e2e/snaps/test-snap-confirm.spec.js
index f4dcfa99d..c6a35657a 100644
--- a/test/e2e/snaps/test-snap-confirm.spec.js
+++ b/test/e2e/snaps/test-snap-confirm.spec.js
@@ -64,6 +64,7 @@ describe('Test Snap Confirm', function () {
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
+ await driver.delay(1000);
await driver.clickElement('#sendConfirmButton');
// hit 'approve' on the custom confirm
diff --git a/test/e2e/snaps/test-snap-error.spec.js b/test/e2e/snaps/test-snap-error.spec.js
index bb4fc251a..177184727 100644
--- a/test/e2e/snaps/test-snap-error.spec.js
+++ b/test/e2e/snaps/test-snap-error.spec.js
@@ -64,6 +64,7 @@ describe('Test Snap Error', function () {
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
+ await driver.delay(1000);
await driver.clickElement('#sendError');
await driver.navigate(PAGES.HOME);
diff --git a/test/e2e/snaps/test-snap-managestate.spec.js b/test/e2e/snaps/test-snap-managestate.spec.js
index 03e481e89..06fddda59 100644
--- a/test/e2e/snaps/test-snap-managestate.spec.js
+++ b/test/e2e/snaps/test-snap-managestate.spec.js
@@ -73,6 +73,7 @@ describe('Test Snap manageState', function () {
windowHandles = await driver.getAllWindowHandles();
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
await driver.fill('#dataManageState', '23');
+ await driver.delay(1000);
await driver.clickElement('#sendManageState');
// check the results of the public key test
diff --git a/test/e2e/snaps/test-snap-notification.spec.js b/test/e2e/snaps/test-snap-notification.spec.js
index 0a303bc9c..25f7cbe34 100644
--- a/test/e2e/snaps/test-snap-notification.spec.js
+++ b/test/e2e/snaps/test-snap-notification.spec.js
@@ -72,6 +72,7 @@ describe('Test Snap Notification', function () {
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
+ await driver.delay(1000);
await driver.clickElement('#sendInAppNotification');
// try to go to the MM pages
diff --git a/ui/pages/home/home.container.js b/ui/pages/home/home.container.js
index cfca56bad..1417ec97a 100644
--- a/ui/pages/home/home.container.js
+++ b/ui/pages/home/home.container.js
@@ -5,7 +5,7 @@ import {
activeTabHasPermissions,
getFirstPermissionRequest,
///: BEGIN:ONLY_INCLUDE_IN(flask)
- getFirstSnapUpdateRequest,
+ getFirstSnapInstallOrUpdateRequest,
///: END:ONLY_INCLUDE_IN
getIsMainnet,
getOriginOfCurrentTab,
@@ -98,7 +98,7 @@ const mapStateToProps = (state) => {
///: BEGIN:ONLY_INCLUDE_IN(flask)
if (!firstPermissionsRequest) {
- firstPermissionsRequest = getFirstSnapUpdateRequest(state);
+ firstPermissionsRequest = getFirstSnapInstallOrUpdateRequest(state);
firstPermissionsRequestId = firstPermissionsRequest?.metadata.id || null;
}
///: END:ONLY_INCLUDE_IN
diff --git a/ui/pages/permissions-connect/flask/snap-install/snap-install.js b/ui/pages/permissions-connect/flask/snap-install/snap-install.js
index 231e45a39..2a12340d8 100644
--- a/ui/pages/permissions-connect/flask/snap-install/snap-install.js
+++ b/ui/pages/permissions-connect/flask/snap-install/snap-install.js
@@ -35,10 +35,13 @@ export default function SnapInstall({
);
const onSubmit = useCallback(
- () => approveSnapInstall(request),
+ () => approveSnapInstall(request.metadata.id),
[request, approveSnapInstall],
);
+ const hasPermissions =
+ request?.permissions && Object.keys(request.permissions).length > 0;
+
const bip44LegacyEntropyPermissions =
request.permissions &&
Object.keys(request.permissions).filter((v) =>
@@ -88,18 +91,22 @@ export default function SnapInstall({
snapVersion={targetSubjectMetadata.version}
boxProps={{ alignItems: ALIGN_ITEMS.CENTER }}
/>
-
- {t('snapRequestsPermission')}
-
-
+ {hasPermissions && (
+ <>
+
+ {t('snapRequestsPermission')}
+
+
+ >
+ )}
setIsShowingWarning(true) : onSubmit
}
- submitText={t('approveAndInstall')}
+ submitText={t(hasPermissions ? 'approveAndInstall' : 'install')}
/>
{isShowingWarning && (
diff --git a/ui/pages/permissions-connect/flask/snap-update/snap-update.js b/ui/pages/permissions-connect/flask/snap-update/snap-update.js
index d3f7d52fb..5127646d5 100644
--- a/ui/pages/permissions-connect/flask/snap-update/snap-update.js
+++ b/ui/pages/permissions-connect/flask/snap-update/snap-update.js
@@ -33,7 +33,7 @@ export default function SnapUpdate({
);
const onSubmit = useCallback(
- () => approveSnapUpdate(request),
+ () => approveSnapUpdate(request.metadata.id),
[request, approveSnapUpdate],
);
@@ -48,6 +48,15 @@ export default function SnapUpdate({
[request.permissions],
);
+ const approvedPermissions = request.approvedPermissions ?? {};
+ const revokedPermissions = request.unusedPermissions ?? {};
+ const newPermissions = request.newPermissions ?? {};
+ const hasPermissions =
+ Object.keys(approvedPermissions).length +
+ Object.keys(revokedPermissions).length +
+ Object.keys(newPermissions).length >
+ 0;
+
return (
{t('snapUpdateExplanation', [`${request.metadata.dappOrigin}`])}
-
- {t('snapRequestsPermission')}
-
-
+ {hasPermissions && (
+ <>
+
+ {t('snapRequestsPermission')}
+
+
+ >
+ )}
(
{
- approvePermissionsRequest(...args);
+ approveSnapInstall={(requestId) => {
+ approvePendingApproval(requestId, true);
this.redirect(true);
}}
- rejectSnapInstall={(requestId) =>
- this.cancelPermissionsRequest(requestId)
- }
+ rejectSnapInstall={(requestId) => {
+ rejectPendingApproval(
+ requestId,
+ serializeError(ethErrors.provider.userRejectedRequest()),
+ );
+ this.redirect(false);
+ }}
targetSubjectMetadata={targetSubjectMetadata}
/>
)}
@@ -328,13 +339,17 @@ export default class PermissionConnect extends Component {
render={() => (
{
- approvePermissionsRequest(...args);
+ approveSnapUpdate={(requestId) => {
+ approvePendingApproval(requestId, true);
this.redirect(true);
}}
- rejectSnapUpdate={(requestId) =>
- this.cancelPermissionsRequest(requestId)
- }
+ rejectSnapUpdate={(requestId) => {
+ rejectPendingApproval(
+ requestId,
+ serializeError(ethErrors.provider.userRejectedRequest()),
+ );
+ this.redirect(false);
+ }}
targetSubjectMetadata={targetSubjectMetadata}
/>
)}
diff --git a/ui/pages/permissions-connect/permissions-connect.container.js b/ui/pages/permissions-connect/permissions-connect.container.js
index c30a440d9..d99ed279c 100644
--- a/ui/pages/permissions-connect/permissions-connect.container.js
+++ b/ui/pages/permissions-connect/permissions-connect.container.js
@@ -6,7 +6,7 @@ import {
getPermissionsRequests,
getSelectedAddress,
///: BEGIN:ONLY_INCLUDE_IN(flask)
- getSnapUpdateRequests,
+ getSnapInstallOrUpdateRequests,
///: END:ONLY_INCLUDE_IN
getTargetSubjectMetadata,
} from '../../selectors';
@@ -19,6 +19,10 @@ import {
showModal,
getCurrentWindowTab,
getRequestAccountTabIds,
+ ///: BEGIN:ONLY_INCLUDE_IN(flask)
+ resolvePendingApproval,
+ rejectPendingApproval,
+ ///: END:ONLY_INCLUDE_IN
} from '../../store/actions';
import {
CONNECT_ROUTE,
@@ -42,7 +46,7 @@ const mapStateToProps = (state, ownProps) => {
///: BEGIN:ONLY_INCLUDE_IN(flask)
permissionsRequests = [
...permissionsRequests,
- ...getSnapUpdateRequests(state),
+ ...getSnapInstallOrUpdateRequests(state),
];
///: END:ONLY_INCLUDE_IN
const currentAddress = getSelectedAddress(state);
@@ -139,6 +143,12 @@ const mapDispatchToProps = (dispatch) => {
dispatch(approvePermissionsRequest(request)),
rejectPermissionsRequest: (requestId) =>
dispatch(rejectPermissionsRequest(requestId)),
+ ///: BEGIN:ONLY_INCLUDE_IN(flask)
+ approvePendingApproval: (id, value) =>
+ dispatch(resolvePendingApproval(id, value)),
+ rejectPendingApproval: (id, error) =>
+ dispatch(rejectPendingApproval(id, error)),
+ ///: END:ONLY_INCLUDE_IN
showNewAccountModal: ({ onCreateNewAccount, newAccountNumber }) => {
return dispatch(
showModal({
diff --git a/ui/selectors/permissions.js b/ui/selectors/permissions.js
index ea1fc5d6d..272b18681 100644
--- a/ui/selectors/permissions.js
+++ b/ui/selectors/permissions.js
@@ -281,14 +281,17 @@ export function getLastConnectedInfo(state) {
}
///: BEGIN:ONLY_INCLUDE_IN(flask)
-export function getSnapUpdateRequests(state) {
+export function getSnapInstallOrUpdateRequests(state) {
return Object.values(state.metamask.pendingApprovals)
- .filter(({ type }) => type === 'wallet_updateSnap')
+ .filter(
+ ({ type }) =>
+ type === 'wallet_installSnap' || type === 'wallet_updateSnap',
+ )
.map(({ requestData }) => requestData);
}
-export function getFirstSnapUpdateRequest(state) {
- const requests = getSnapUpdateRequests(state);
+export function getFirstSnapInstallOrUpdateRequest(state) {
+ const requests = getSnapInstallOrUpdateRequests(state);
return requests && requests[0] ? requests[0] : null;
}
///: END:ONLY_INCLUDE_IN