diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 3405e0712..b187737be 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -271,6 +271,9 @@
"addNfts": {
"message": "Add NFTs"
},
+ "addSnapAccountModalDescription": {
+ "message": "Discover options to keep your account secure with MetaMask Snaps"
+ },
"addSuggestedNFTs": {
"message": "Add suggested NFTs"
},
@@ -706,6 +709,21 @@
"complianceSettingsExplanation": {
"message": "Change your settings or view reports by opening up Codefi Compliance or disconnect below."
},
+ "configureSnapPopupDescription": {
+ "message": "You're now leaving MetaMask to configure this snap."
+ },
+ "configureSnapPopupInstallDescription": {
+ "message": "You're now leaving MetaMask to install this snap."
+ },
+ "configureSnapPopupInstallTitle": {
+ "message": "Install snap"
+ },
+ "configureSnapPopupLink": {
+ "message": "Click this link to continue:"
+ },
+ "configureSnapPopupTitle": {
+ "message": "Configure snap"
+ },
"confirm": {
"message": "Confirm"
},
@@ -1701,6 +1719,9 @@
"general": {
"message": "General"
},
+ "getStarted": {
+ "message": "Get started"
+ },
"globalTitle": {
"message": "Global menu"
},
@@ -3126,6 +3147,10 @@
"message": "Allow the snap to run indefinitely while, for example, processing large amounts of data.",
"description": "An extended description for the `endowment:long-running` permission"
},
+ "permission_manageAccounts": {
+ "message": "Add and control Ethereum accounts",
+ "description": "The description for `snap_manageAccounts` permission"
+ },
"permission_manageBip32Keys": {
"message": "Control your accounts and assets under $1 ($2).",
"description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'."
@@ -3739,6 +3764,9 @@
"message": "Set a spending cap for your $1",
"description": "$1 is a token symbol"
},
+ "settingAddSnapAccount": {
+ "message": "Add snap account"
+ },
"settings": {
"message": "Settings"
},
@@ -3843,6 +3871,9 @@
"smartSwapsSubDescription": {
"message": "* Smart Swaps will attempt to submit your transaction privately, multiple times. If all attempts fail, the transaction will be broadcast publicly to ensure your Swap successfully goes through."
},
+ "snapConfigure": {
+ "message": "Configure"
+ },
"snapConnectionWarning": {
"message": "$1 wants to connect to $2. Only continue if you trust this website.",
"description": "$2 is the snap and $1 is the dapp requesting connection to the snap."
@@ -3851,6 +3882,47 @@
"message": "This content is coming from $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
},
+ "snapCreateAccountSubtitle": {
+ "message": "Choose how to secure your new account using MetaMask Snaps."
+ },
+ "snapCreateAccountTitle": {
+ "message": "Create a $1 account",
+ "description": "Title of the Create Snap Account Page, $1 is the text using a different color"
+ },
+ "snapCreateAccountTitle2": {
+ "message": "snap",
+ "description": "$1 of the snapCreateAccountTitle"
+ },
+ "snapCreatedByMetaMask": {
+ "message": "By MetaMask"
+ },
+ "snapDetailAudits": {
+ "message": "Audit"
+ },
+ "snapDetailDeveloper": {
+ "message": "Developer"
+ },
+ "snapDetailLastUpdated": {
+ "message": "Updated"
+ },
+ "snapDetailManageSnap": {
+ "message": "Manage snap"
+ },
+ "snapDetailTags": {
+ "message": "Tags"
+ },
+ "snapDetailVersion": {
+ "message": "Version"
+ },
+ "snapDetailWebsite": {
+ "message": "Website"
+ },
+ "snapDetailsCreateASnapAccount": {
+ "message": "Create a Snap Account"
+ },
+ "snapDetailsInstalled": {
+ "message": "Installed"
+ },
"snapError": {
"message": "Snap Error: '$1'. Error Code: '$2'",
"description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code."
@@ -3892,6 +3964,9 @@
"message": "Installation failed",
"description": "Error title used when snap installation fails."
},
+ "snapIsAudited": {
+ "message": "Audited"
+ },
"snapResultError": {
"message": "Error"
},
@@ -3904,6 +3979,9 @@
"snapUpdate": {
"message": "Update snap"
},
+ "snapUpdateAvailable": {
+ "message": "Update available"
+ },
"snapUpdateErrorDescription": {
"message": "$1 couldn’t be updated.",
"description": "Error description used when snap update fails. $1 is the snap name."
diff --git a/app/images/add-snaps-image.svg b/app/images/add-snaps-image.svg
new file mode 100644
index 000000000..7a59fef9e
--- /dev/null
+++ b/app/images/add-snaps-image.svg
@@ -0,0 +1,218 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/images/snap-account-page.svg b/app/images/snap-account-page.svg
new file mode 100644
index 000000000..f0df8e6b1
--- /dev/null
+++ b/app/images/snap-account-page.svg
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/scripts/controllers/permissions/snaps/snap-permissions.js b/app/scripts/controllers/permissions/snaps/snap-permissions.js
index cfada9365..4788608d0 100644
--- a/app/scripts/controllers/permissions/snaps/snap-permissions.js
+++ b/app/scripts/controllers/permissions/snaps/snap-permissions.js
@@ -1,8 +1,8 @@
-import { endowmentPermissionBuilders } from '@metamask/snaps-controllers';
import {
restrictedMethodPermissionBuilders,
selectHooks,
} from '@metamask/rpc-methods';
+import { endowmentPermissionBuilders } from '@metamask/snaps-controllers';
import {
ExcludedSnapEndowments,
ExcludedSnapPermissions,
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index 85b2ccc43..052f218a3 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -4,6 +4,9 @@ import { IPFS_DEFAULT_GATEWAY_URL } from '../../../shared/constants/network';
import { LedgerTransportTypes } from '../../../shared/constants/hardware-wallets';
import { ThemeType } from '../../../shared/constants/preferences';
import { shouldShowLineaMainnet } from '../../../shared/modules/network.utils';
+///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+import { KEYRING_SNAPS_REGISTRY_URL } from '../../../shared/constants/app';
+///: END:ONLY_INCLUDE_IN
export default class PreferencesController {
/**
@@ -65,8 +68,12 @@ export default class PreferencesController {
ledgerTransportType: window.navigator.hid
? LedgerTransportTypes.webhid
: LedgerTransportTypes.u2f,
+ snapRegistryList: {},
transactionSecurityCheckEnabled: false,
theme: ThemeType.os,
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ snapsAddSnapAccountModalDismissed: false,
+ ///: END:ONLY_INCLUDE_IN
isLineaMainnetReleased: false,
...opts.initState,
};
@@ -511,6 +518,23 @@ export default class PreferencesController {
return this.store.getState().disabledRpcMethodPreferences;
}
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ setSnapsAddSnapAccountModalDismissed(value) {
+ this.store.updateState({ snapsAddSnapAccountModalDismissed: value });
+ }
+
+ async updateSnapRegistry() {
+ let snapRegistry;
+ try {
+ snapRegistry = await fetch(KEYRING_SNAPS_REGISTRY_URL);
+ } catch (error) {
+ console.error(`Failed to fetch registry: `, error);
+ snapRegistry = {};
+ }
+ this.store.updateState({ snapRegistryList: snapRegistry });
+ }
+ ///: END:ONLY_INCLUDE_IN
+
//
// PRIVATE METHODS
//
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index b71dbbe7d..c60ed5fc6 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -49,13 +49,12 @@ import {
SubjectMetadataController,
SubjectType,
} from '@metamask/subject-metadata-controller';
+import SmartTransactionsController from '@metamask/smart-transactions-controller';
///: BEGIN:ONLY_INCLUDE_IN(snaps)
import { encrypt, decrypt } from '@metamask/browser-passworder';
import { RateLimitController } from '@metamask/rate-limit-controller';
import { NotificationController } from '@metamask/notification-controller';
-///: END:ONLY_INCLUDE_IN
-import SmartTransactionsController from '@metamask/smart-transactions-controller';
-///: BEGIN:ONLY_INCLUDE_IN(snaps)
+
import {
CronjobController,
JsonSnapsRegistry,
@@ -63,6 +62,9 @@ import {
IframeExecutionService,
} from '@metamask/snaps-controllers';
///: END:ONLY_INCLUDE_IN
+///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+import { SnapKeyring } from '@metamask/eth-snap-keyring';
+///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import {
@@ -772,6 +774,16 @@ export default class MetamaskController extends EventEmitter {
///: END:ONLY_INCLUDE_IN
}
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ additionalKeyrings.push(
+ (() => {
+ const builder = () => new SnapKeyring(this.snapController);
+ builder.type = SnapKeyring.type;
+ return builder;
+ })(),
+ );
+ ///: END:ONLY_INCLUDE_IN
+
this.keyringController = new KeyringController({
keyringBuilders: additionalKeyrings,
initState: initState.KeyringController,
@@ -1679,7 +1691,28 @@ export default class MetamaskController extends EventEmitter {
});
}
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ /**
+ * Initialize the snap keyring if it is not present.
+ */
+ async getSnapKeyring() {
+ if (!this.snapKeyring) {
+ let [snapKeyring] = this.keyringController.getKeyringsByType(
+ KeyringType.snap,
+ );
+ if (!snapKeyring) {
+ snapKeyring = await this.keyringController.addNewKeyring(
+ KeyringType.snap,
+ );
+ }
+ this.snapKeyring = snapKeyring;
+ }
+ return this.snapKeyring;
+ }
+ ///: END:ONLY_INCLUDE_IN
+
///: BEGIN:ONLY_INCLUDE_IN(snaps)
+
/**
* Constructor helper for getting Snap permission specifications.
*/
@@ -1735,6 +1768,16 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger,
'SnapController:updateSnapState',
),
+ ///: END:ONLY_INCLUDE_IN
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ getSnapKeyring: this.getSnapKeyring.bind(this),
+ saveSnapKeyring: async () => {
+ await this.keyringController.persistAllKeyrings();
+ await this.keyringController._updateMemStoreKeyrings();
+ await this.keyringController.fullUpdate();
+ },
+ ///: END:ONLY_INCLUDE_IN
+ ///: BEGIN:ONLY_INCLUDE_IN(snaps)
}),
};
}
@@ -2167,6 +2210,13 @@ export default class MetamaskController extends EventEmitter {
preferencesController.setTransactionSecurityCheckEnabled.bind(
preferencesController,
),
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ setSnapsAddSnapAccountModalDismissed:
+ preferencesController.setSnapsAddSnapAccountModalDismissed.bind(
+ preferencesController,
+ ),
+ ///: END:ONLY_INCLUDE_IN
+
// AssetsContractController
getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this),
@@ -2408,6 +2458,11 @@ export default class MetamaskController extends EventEmitter {
dismissNotifications: this.dismissNotifications.bind(this),
markNotificationsAsRead: this.markNotificationsAsRead.bind(this),
///: END:ONLY_INCLUDE_IN
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ updateSnapRegistry: this.preferencesController.updateSnapRegistry.bind(
+ preferencesController,
+ ),
+ ///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(desktop)
// Desktop
getDesktopEnabled: this.desktopController.getDesktopEnabled.bind(
@@ -2771,6 +2826,7 @@ export default class MetamaskController extends EventEmitter {
// set new identities
this.preferencesController.setAddresses(accounts);
this.selectFirstIdentity();
+
return vault;
} finally {
releaseLock();
diff --git a/builds.yml b/builds.yml
index ca31ff44d..019da9378 100644
--- a/builds.yml
+++ b/builds.yml
@@ -46,6 +46,7 @@ buildTypes:
- snaps
- desktop
- build-flask
+ - keyring-snaps
env:
- INFURA_FLASK_PROJECT_ID
- SEGMENT_FLASK_WRITE_KEY
@@ -64,6 +65,7 @@ buildTypes:
- snaps
- desktop
- build-flask
+ - keyring-snaps
env:
- INFURA_FLASK_PROJECT_ID
- SEGMENT_FLASK_WRITE_KEY
@@ -138,6 +140,11 @@ features:
- src: ./app/build-types/flask/images/
dest: images
- ./{app,shared,ui}/**/flask/**
+ keyring-snaps:
+ env:
+ - KEYRING_SNAPS_REGISTRY_URL: https://metamask.github.io/keyring-snaps-registry/prod/registry.json
+ assets:
+ - ./{app,shared,ui}/**/keyring-snaps/**
# Env variables that are required for all types of builds
#
diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json
index 06df25223..f3373a420 100644
--- a/lavamoat/browserify/beta/policy.json
+++ b/lavamoat/browserify/beta/policy.json
@@ -933,7 +933,7 @@
"@metamask/eth-json-rpc-middleware>@metamask/utils": true,
"@metamask/eth-json-rpc-middleware>pify": true,
"@metamask/eth-json-rpc-middleware>safe-stable-stringify": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"vinyl>clone": true
@@ -955,9 +955,9 @@
"packages": {
"@metamask/browser-passworder": true,
"@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util": true,
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true,
"@metamask/eth-keyring-controller>obs-store": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"browserify>events": true
}
},
@@ -968,7 +968,7 @@
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"@metamask/scure-bip39": true,
"browserify>buffer": true
}
@@ -1010,11 +1010,44 @@
"crypto": true
}
},
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util": {
+ "packages": {
+ "@ethereumjs/tx>@ethereumjs/util": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethjs-util": true,
+ "bn.js": true,
+ "browserify>buffer": true,
+ "eth-sig-util>tweetnacl": true,
+ "eth-sig-util>tweetnacl-util": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography": {
+ "globals": {
+ "TextDecoder": true,
+ "crypto": true
+ },
+ "packages": {
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethjs-util": {
+ "packages": {
+ "browserify>buffer": true,
+ "ethjs>ethjs-util>is-hex-prefixed": true,
+ "ethjs>ethjs-util>strip-hex-prefix": true
+ }
+ },
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": {
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"browserify>buffer": true,
"browserify>events": true,
"ethereumjs-wallet>randombytes": true
@@ -1107,6 +1140,39 @@
"ganache>secp256k1>elliptic": true
}
},
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": {
+ "packages": {
+ "@ethereumjs/tx>@ethereumjs/util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethjs-util": true,
+ "bn.js": true,
+ "browserify>buffer": true,
+ "eth-sig-util>tweetnacl": true,
+ "eth-sig-util>tweetnacl-util": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography": {
+ "globals": {
+ "TextDecoder": true,
+ "crypto": true
+ },
+ "packages": {
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethjs-util": {
+ "packages": {
+ "browserify>buffer": true,
+ "ethjs>ethjs-util>is-hex-prefixed": true,
+ "ethjs>ethjs-util>strip-hex-prefix": true
+ }
+ },
"@metamask/eth-token-tracker": {
"globals": {
"console.warn": true
@@ -1234,42 +1300,9 @@
"browserify>events": true
}
},
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": {
- "packages": {
- "@ethereumjs/tx>@ethereumjs/util": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": true,
- "bn.js": true,
- "browserify>buffer": true,
- "eth-sig-util>tweetnacl": true,
- "eth-sig-util>tweetnacl-util": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": {
- "globals": {
- "TextDecoder": true,
- "crypto": true
- },
- "packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
- "globals": {
- "TextEncoder": true,
- "crypto": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": {
- "packages": {
- "browserify>buffer": true,
- "ethjs>ethjs-util>is-hex-prefixed": true,
- "ethjs>ethjs-util>strip-hex-prefix": true
- }
- },
"@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": {
"packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true
}
},
"@metamask/eth-trezor-keyring>@trezor/connect-web": {
@@ -1612,7 +1645,7 @@
"setTimeout": true
},
"packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/safe-event-emitter": true,
diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json
index 99706027e..aa3b955d2 100644
--- a/lavamoat/browserify/desktop/policy.json
+++ b/lavamoat/browserify/desktop/policy.json
@@ -1004,7 +1004,7 @@
"@metamask/eth-json-rpc-middleware>@metamask/utils": true,
"@metamask/eth-json-rpc-middleware>pify": true,
"@metamask/eth-json-rpc-middleware>safe-stable-stringify": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"vinyl>clone": true
@@ -1026,9 +1026,9 @@
"packages": {
"@metamask/browser-passworder": true,
"@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util": true,
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true,
"@metamask/eth-keyring-controller>obs-store": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"browserify>events": true
}
},
@@ -1039,7 +1039,7 @@
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"@metamask/scure-bip39": true,
"browserify>buffer": true
}
@@ -1081,11 +1081,44 @@
"crypto": true
}
},
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util": {
+ "packages": {
+ "@ethereumjs/tx>@ethereumjs/util": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethjs-util": true,
+ "bn.js": true,
+ "browserify>buffer": true,
+ "eth-sig-util>tweetnacl": true,
+ "eth-sig-util>tweetnacl-util": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography": {
+ "globals": {
+ "TextDecoder": true,
+ "crypto": true
+ },
+ "packages": {
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethjs-util": {
+ "packages": {
+ "browserify>buffer": true,
+ "ethjs>ethjs-util>is-hex-prefixed": true,
+ "ethjs>ethjs-util>strip-hex-prefix": true
+ }
+ },
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": {
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"browserify>buffer": true,
"browserify>events": true,
"ethereumjs-wallet>randombytes": true
@@ -1178,6 +1211,94 @@
"ganache>secp256k1>elliptic": true
}
},
+ "@metamask/eth-snap-keyring": {
+ "globals": {
+ "console.error": true,
+ "console.warn": true
+ },
+ "packages": {
+ "@ethereumjs/tx": true,
+ "@metamask/eth-snap-keyring>@metamask/keyring-api": true,
+ "@metamask/eth-snap-keyring>@metamask/utils": true,
+ "@metamask/eth-snap-keyring>uuid": true,
+ "browserify>events": true,
+ "superstruct": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": {
+ "packages": {
+ "@ethereumjs/tx>@ethereumjs/util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethjs-util": true,
+ "bn.js": true,
+ "browserify>buffer": true,
+ "eth-sig-util>tweetnacl": true,
+ "eth-sig-util>tweetnacl-util": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography": {
+ "globals": {
+ "TextDecoder": true,
+ "crypto": true
+ },
+ "packages": {
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethjs-util": {
+ "packages": {
+ "browserify>buffer": true,
+ "ethjs>ethjs-util>is-hex-prefixed": true,
+ "ethjs>ethjs-util>strip-hex-prefix": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/keyring-api": {
+ "packages": {
+ "@metamask/eth-snap-keyring>@metamask/keyring-api>@metamask/utils": true,
+ "@metamask/eth-snap-keyring>@metamask/keyring-api>uuid": true,
+ "superstruct": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/keyring-api>@metamask/utils": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true
+ },
+ "packages": {
+ "browserify>buffer": true,
+ "nock>debug": true,
+ "semver": true,
+ "superstruct": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/keyring-api>uuid": {
+ "globals": {
+ "crypto": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/utils": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true
+ },
+ "packages": {
+ "browserify>buffer": true,
+ "nock>debug": true,
+ "semver": true,
+ "superstruct": true
+ }
+ },
+ "@metamask/eth-snap-keyring>uuid": {
+ "globals": {
+ "crypto": true
+ }
+ },
"@metamask/eth-token-tracker": {
"globals": {
"console.warn": true
@@ -1305,42 +1426,9 @@
"browserify>events": true
}
},
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": {
- "packages": {
- "@ethereumjs/tx>@ethereumjs/util": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": true,
- "bn.js": true,
- "browserify>buffer": true,
- "eth-sig-util>tweetnacl": true,
- "eth-sig-util>tweetnacl-util": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": {
- "globals": {
- "TextDecoder": true,
- "crypto": true
- },
- "packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
- "globals": {
- "TextEncoder": true,
- "crypto": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": {
- "packages": {
- "browserify>buffer": true,
- "ethjs>ethjs-util>is-hex-prefixed": true,
- "ethjs>ethjs-util>strip-hex-prefix": true
- }
- },
"@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": {
"packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true
}
},
"@metamask/eth-trezor-keyring>@trezor/connect-web": {
@@ -1683,7 +1771,7 @@
"setTimeout": true
},
"packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/safe-event-emitter": true,
diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json
index 99706027e..aa3b955d2 100644
--- a/lavamoat/browserify/flask/policy.json
+++ b/lavamoat/browserify/flask/policy.json
@@ -1004,7 +1004,7 @@
"@metamask/eth-json-rpc-middleware>@metamask/utils": true,
"@metamask/eth-json-rpc-middleware>pify": true,
"@metamask/eth-json-rpc-middleware>safe-stable-stringify": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"vinyl>clone": true
@@ -1026,9 +1026,9 @@
"packages": {
"@metamask/browser-passworder": true,
"@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util": true,
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true,
"@metamask/eth-keyring-controller>obs-store": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"browserify>events": true
}
},
@@ -1039,7 +1039,7 @@
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"@metamask/scure-bip39": true,
"browserify>buffer": true
}
@@ -1081,11 +1081,44 @@
"crypto": true
}
},
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util": {
+ "packages": {
+ "@ethereumjs/tx>@ethereumjs/util": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethjs-util": true,
+ "bn.js": true,
+ "browserify>buffer": true,
+ "eth-sig-util>tweetnacl": true,
+ "eth-sig-util>tweetnacl-util": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography": {
+ "globals": {
+ "TextDecoder": true,
+ "crypto": true
+ },
+ "packages": {
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethjs-util": {
+ "packages": {
+ "browserify>buffer": true,
+ "ethjs>ethjs-util>is-hex-prefixed": true,
+ "ethjs>ethjs-util>strip-hex-prefix": true
+ }
+ },
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": {
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"browserify>buffer": true,
"browserify>events": true,
"ethereumjs-wallet>randombytes": true
@@ -1178,6 +1211,94 @@
"ganache>secp256k1>elliptic": true
}
},
+ "@metamask/eth-snap-keyring": {
+ "globals": {
+ "console.error": true,
+ "console.warn": true
+ },
+ "packages": {
+ "@ethereumjs/tx": true,
+ "@metamask/eth-snap-keyring>@metamask/keyring-api": true,
+ "@metamask/eth-snap-keyring>@metamask/utils": true,
+ "@metamask/eth-snap-keyring>uuid": true,
+ "browserify>events": true,
+ "superstruct": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": {
+ "packages": {
+ "@ethereumjs/tx>@ethereumjs/util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethjs-util": true,
+ "bn.js": true,
+ "browserify>buffer": true,
+ "eth-sig-util>tweetnacl": true,
+ "eth-sig-util>tweetnacl-util": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography": {
+ "globals": {
+ "TextDecoder": true,
+ "crypto": true
+ },
+ "packages": {
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethjs-util": {
+ "packages": {
+ "browserify>buffer": true,
+ "ethjs>ethjs-util>is-hex-prefixed": true,
+ "ethjs>ethjs-util>strip-hex-prefix": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/keyring-api": {
+ "packages": {
+ "@metamask/eth-snap-keyring>@metamask/keyring-api>@metamask/utils": true,
+ "@metamask/eth-snap-keyring>@metamask/keyring-api>uuid": true,
+ "superstruct": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/keyring-api>@metamask/utils": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true
+ },
+ "packages": {
+ "browserify>buffer": true,
+ "nock>debug": true,
+ "semver": true,
+ "superstruct": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/keyring-api>uuid": {
+ "globals": {
+ "crypto": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/utils": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true
+ },
+ "packages": {
+ "browserify>buffer": true,
+ "nock>debug": true,
+ "semver": true,
+ "superstruct": true
+ }
+ },
+ "@metamask/eth-snap-keyring>uuid": {
+ "globals": {
+ "crypto": true
+ }
+ },
"@metamask/eth-token-tracker": {
"globals": {
"console.warn": true
@@ -1305,42 +1426,9 @@
"browserify>events": true
}
},
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": {
- "packages": {
- "@ethereumjs/tx>@ethereumjs/util": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": true,
- "bn.js": true,
- "browserify>buffer": true,
- "eth-sig-util>tweetnacl": true,
- "eth-sig-util>tweetnacl-util": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": {
- "globals": {
- "TextDecoder": true,
- "crypto": true
- },
- "packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
- "globals": {
- "TextEncoder": true,
- "crypto": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": {
- "packages": {
- "browserify>buffer": true,
- "ethjs>ethjs-util>is-hex-prefixed": true,
- "ethjs>ethjs-util>strip-hex-prefix": true
- }
- },
"@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": {
"packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true
}
},
"@metamask/eth-trezor-keyring>@trezor/connect-web": {
@@ -1683,7 +1771,7 @@
"setTimeout": true
},
"packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/safe-event-emitter": true,
diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json
index 06df25223..f3373a420 100644
--- a/lavamoat/browserify/main/policy.json
+++ b/lavamoat/browserify/main/policy.json
@@ -933,7 +933,7 @@
"@metamask/eth-json-rpc-middleware>@metamask/utils": true,
"@metamask/eth-json-rpc-middleware>pify": true,
"@metamask/eth-json-rpc-middleware>safe-stable-stringify": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"vinyl>clone": true
@@ -955,9 +955,9 @@
"packages": {
"@metamask/browser-passworder": true,
"@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util": true,
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true,
"@metamask/eth-keyring-controller>obs-store": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"browserify>events": true
}
},
@@ -968,7 +968,7 @@
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"@metamask/scure-bip39": true,
"browserify>buffer": true
}
@@ -1010,11 +1010,44 @@
"crypto": true
}
},
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util": {
+ "packages": {
+ "@ethereumjs/tx>@ethereumjs/util": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethjs-util": true,
+ "bn.js": true,
+ "browserify>buffer": true,
+ "eth-sig-util>tweetnacl": true,
+ "eth-sig-util>tweetnacl-util": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography": {
+ "globals": {
+ "TextDecoder": true,
+ "crypto": true
+ },
+ "packages": {
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethjs-util": {
+ "packages": {
+ "browserify>buffer": true,
+ "ethjs>ethjs-util>is-hex-prefixed": true,
+ "ethjs>ethjs-util>strip-hex-prefix": true
+ }
+ },
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": {
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"browserify>buffer": true,
"browserify>events": true,
"ethereumjs-wallet>randombytes": true
@@ -1107,6 +1140,39 @@
"ganache>secp256k1>elliptic": true
}
},
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": {
+ "packages": {
+ "@ethereumjs/tx>@ethereumjs/util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethjs-util": true,
+ "bn.js": true,
+ "browserify>buffer": true,
+ "eth-sig-util>tweetnacl": true,
+ "eth-sig-util>tweetnacl-util": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography": {
+ "globals": {
+ "TextDecoder": true,
+ "crypto": true
+ },
+ "packages": {
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethjs-util": {
+ "packages": {
+ "browserify>buffer": true,
+ "ethjs>ethjs-util>is-hex-prefixed": true,
+ "ethjs>ethjs-util>strip-hex-prefix": true
+ }
+ },
"@metamask/eth-token-tracker": {
"globals": {
"console.warn": true
@@ -1234,42 +1300,9 @@
"browserify>events": true
}
},
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": {
- "packages": {
- "@ethereumjs/tx>@ethereumjs/util": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": true,
- "bn.js": true,
- "browserify>buffer": true,
- "eth-sig-util>tweetnacl": true,
- "eth-sig-util>tweetnacl-util": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": {
- "globals": {
- "TextDecoder": true,
- "crypto": true
- },
- "packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
- "globals": {
- "TextEncoder": true,
- "crypto": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": {
- "packages": {
- "browserify>buffer": true,
- "ethjs>ethjs-util>is-hex-prefixed": true,
- "ethjs>ethjs-util>strip-hex-prefix": true
- }
- },
"@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": {
"packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true
}
},
"@metamask/eth-trezor-keyring>@trezor/connect-web": {
@@ -1612,7 +1645,7 @@
"setTimeout": true
},
"packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/safe-event-emitter": true,
diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json
index 8b3e07fcc..a097c6d55 100644
--- a/lavamoat/browserify/mmi/policy.json
+++ b/lavamoat/browserify/mmi/policy.json
@@ -1154,7 +1154,7 @@
"@metamask/eth-json-rpc-middleware>@metamask/utils": true,
"@metamask/eth-json-rpc-middleware>pify": true,
"@metamask/eth-json-rpc-middleware>safe-stable-stringify": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"vinyl>clone": true
@@ -1176,9 +1176,9 @@
"packages": {
"@metamask/browser-passworder": true,
"@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util": true,
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true,
"@metamask/eth-keyring-controller>obs-store": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"browserify>events": true
}
},
@@ -1189,7 +1189,7 @@
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"@metamask/scure-bip39": true,
"browserify>buffer": true
}
@@ -1231,11 +1231,44 @@
"crypto": true
}
},
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util": {
+ "packages": {
+ "@ethereumjs/tx>@ethereumjs/util": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography": true,
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethjs-util": true,
+ "bn.js": true,
+ "browserify>buffer": true,
+ "eth-sig-util>tweetnacl": true,
+ "eth-sig-util>tweetnacl-util": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography": {
+ "globals": {
+ "TextDecoder": true,
+ "crypto": true
+ },
+ "packages": {
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@metamask/eth-keyring-controller>@metamask/eth-sig-util>ethjs-util": {
+ "packages": {
+ "browserify>buffer": true,
+ "ethjs>ethjs-util>is-hex-prefixed": true,
+ "ethjs>ethjs-util>strip-hex-prefix": true
+ }
+ },
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": {
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"browserify>buffer": true,
"browserify>events": true,
"ethereumjs-wallet>randombytes": true
@@ -1328,6 +1361,39 @@
"ganache>secp256k1>elliptic": true
}
},
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": {
+ "packages": {
+ "@ethereumjs/tx>@ethereumjs/util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethjs-util": true,
+ "bn.js": true,
+ "browserify>buffer": true,
+ "eth-sig-util>tweetnacl": true,
+ "eth-sig-util>tweetnacl-util": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography": {
+ "globals": {
+ "TextDecoder": true,
+ "crypto": true
+ },
+ "packages": {
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util>ethjs-util": {
+ "packages": {
+ "browserify>buffer": true,
+ "ethjs>ethjs-util>is-hex-prefixed": true,
+ "ethjs>ethjs-util>strip-hex-prefix": true
+ }
+ },
"@metamask/eth-token-tracker": {
"globals": {
"console.warn": true
@@ -1455,42 +1521,9 @@
"browserify>events": true
}
},
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": {
- "packages": {
- "@ethereumjs/tx>@ethereumjs/util": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": true,
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": true,
- "bn.js": true,
- "browserify>buffer": true,
- "eth-sig-util>tweetnacl": true,
- "eth-sig-util>tweetnacl-util": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": {
- "globals": {
- "TextDecoder": true,
- "crypto": true
- },
- "packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": {
- "globals": {
- "TextEncoder": true,
- "crypto": true
- }
- },
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": {
- "packages": {
- "browserify>buffer": true,
- "ethjs>ethjs-util>is-hex-prefixed": true,
- "ethjs>ethjs-util>strip-hex-prefix": true
- }
- },
"@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": {
"packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true
}
},
"@metamask/eth-trezor-keyring>@trezor/connect-web": {
@@ -1833,7 +1866,7 @@
"setTimeout": true
},
"packages": {
- "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
+ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/safe-event-emitter": true,
diff --git a/package.json b/package.json
index ae531d703..3d6f2789a 100644
--- a/package.json
+++ b/package.json
@@ -51,8 +51,8 @@
"sentry:publish": "node ./development/sentry-publish.js",
"lint": "yarn lint:prettier && yarn lint:eslint && yarn lint:tsc && yarn lint:styles",
"lint:fix": "yarn lint:prettier:fix && yarn lint:eslint:fix && yarn lint:styles:fix",
- "lint:prettier": "prettier --check -- **/*.json",
- "lint:prettier:fix": "prettier --write -- **/*.json",
+ "lint:prettier": "prettier --check -- '**/*.json'",
+ "lint:prettier:fix": "prettier --write -- '**/*.json'",
"lint:changed": "./development/get-changed-file-names.sh | grep --regexp='[.]js$' | tr '\\n' '\\0' | xargs -0 eslint",
"lint:changed:fix": "./development/get-changed-file-names.sh | grep --regexp='[.]js$' | tr '\\n' '\\0' | xargs -0 eslint --fix",
"lint:changelog": "auto-changelog validate",
@@ -235,6 +235,7 @@
"@metamask/eth-json-rpc-middleware": "^11.0.0",
"@metamask/eth-keyring-controller": "^10.0.1",
"@metamask/eth-ledger-bridge-keyring": "^0.15.0",
+ "@metamask/eth-snap-keyring": "^0.1.3",
"@metamask/eth-token-tracker": "^4.0.0",
"@metamask/eth-trezor-keyring": "^1.0.0",
"@metamask/etherscan-link": "^2.2.0",
diff --git a/shared/constants/app.ts b/shared/constants/app.ts
index 3b99fa5e7..04cf708ba 100644
--- a/shared/constants/app.ts
+++ b/shared/constants/app.ts
@@ -63,6 +63,12 @@ export const MESSAGE_TYPE = {
///: END:ONLY_INCLUDE_IN
} as const;
+///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+// eslint-disable-next-line prefer-destructuring
+export const KEYRING_SNAPS_REGISTRY_URL =
+ process.env.KEYRING_SNAPS_REGISTRY_URL;
+///: END:ONLY_INCLUDE_IN
+
///: BEGIN:ONLY_INCLUDE_IN(snaps)
export const SNAP_DIALOG_TYPES = {
[DialogType.Alert]: MESSAGE_TYPE.SNAP_DIALOG_ALERT,
diff --git a/shared/constants/keyring.ts b/shared/constants/keyring.ts
index f285fa468..091317c27 100644
--- a/shared/constants/keyring.ts
+++ b/shared/constants/keyring.ts
@@ -8,10 +8,19 @@ export enum InternalKeyringType {
imported = 'Simple Key Pair',
}
+///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+export enum SnapKeyringType {
+ snap = 'Snap Keyring',
+}
+///: END:ONLY_INCLUDE_IN
+
/**
* All keyrings supported by MetaMask.
*/
export const KeyringType = {
...HardwareKeyringType,
...InternalKeyringType,
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ ...SnapKeyringType,
+ ///: END:ONLY_INCLUDE_IN
};
diff --git a/shared/constants/permissions.test.js b/shared/constants/permissions.test.js
index 031c41c0a..94f6388ef 100644
--- a/shared/constants/permissions.test.js
+++ b/shared/constants/permissions.test.js
@@ -24,7 +24,13 @@ describe('EndowmentPermissions', () => {
describe('RestrictedMethods', () => {
it('has the expected permission keys', () => {
- expect(Object.keys(RestrictedMethods).sort()).toStrictEqual(
+ // this is done because we there is a difference between flask and stable permissions
+ // the code fence in `shared/constants/snaps/permissions.ts` is not supported in jest
+ const mainBuildRestrictedMethodPermissions = Object.keys(RestrictedMethods)
+ .filter((key) => key !== 'snap_manageAccounts')
+ .sort();
+
+ expect(mainBuildRestrictedMethodPermissions).toStrictEqual(
[
'eth_accounts',
...Object.keys(restrictedMethodPermissionBuilders).filter(
@@ -35,3 +41,26 @@ describe('RestrictedMethods', () => {
);
});
});
+
+// Kept here because code fences are not supported in jest.
+// rpc methods flask has more restricted endowment permission builders
+jest.mock('@metamask/rpc-methods', () =>
+ jest.requireActual('@metamask/rpc-methods-flask'),
+);
+
+describe('Flask Restricted Methods', () => {
+ it('has the expected flask permission keys', () => {
+ const flaskExcludedSnapPermissions = Object.keys(
+ ExcludedSnapPermissions,
+ ).filter((key) => key !== 'snap_manageAccounts');
+
+ expect(Object.keys(RestrictedMethods).sort()).toStrictEqual(
+ [
+ 'eth_accounts',
+ ...Object.keys(restrictedMethodPermissionBuilders).filter(
+ (targetName) => !flaskExcludedSnapPermissions.includes(targetName),
+ ),
+ ].sort(),
+ );
+ });
+});
diff --git a/shared/constants/permissions.ts b/shared/constants/permissions.ts
index 3380327b2..1d0c60ac1 100644
--- a/shared/constants/permissions.ts
+++ b/shared/constants/permissions.ts
@@ -14,6 +14,9 @@ export const RestrictedMethods = Object.freeze({
snap_getEntropy: 'snap_getEntropy',
wallet_snap: 'wallet_snap',
///: END:ONLY_INCLUDE_IN
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ snap_manageAccounts: 'snap_manageAccounts',
+ ///: END:ONLY_INCLUDE_IN
} as const);
///: BEGIN:ONLY_INCLUDE_IN(snaps)
diff --git a/shared/constants/snaps/permissions.ts b/shared/constants/snaps/permissions.ts
index c77d4a3c4..360c4a379 100644
--- a/shared/constants/snaps/permissions.ts
+++ b/shared/constants/snaps/permissions.ts
@@ -13,7 +13,7 @@ export const EndowmentPermissions = Object.freeze({
// Methods / permissions in external packages that we are temporarily excluding.
export const ExcludedSnapPermissions = Object.freeze({
// TODO: Enable in Flask
- ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-flask)
+ ///: BEGIN:ONLY_INCLUDE_IN(build-main)
snap_manageAccounts:
'This permission is still in development and therefore not available.',
///: END:ONLY_INCLUDE_IN
diff --git a/test/data/mock-state.json b/test/data/mock-state.json
index dc9b7e3e0..07153cf85 100644
--- a/test/data/mock-state.json
+++ b/test/data/mock-state.json
@@ -139,6 +139,10 @@
{
"type": "Simple Key Pair",
"accounts": ["0xeb9e64b93097bc15f01f13eae97015c57ab64823"]
+ },
+ {
+ "type": "Snap Keyring",
+ "accounts": ["0xb552685e3d2790efd64a175b00d51f02cdafee5d"]
}
],
"identities": {
@@ -172,6 +176,22 @@
"subjectType": "snap"
}
},
+ "snapRegistryList": {
+ "a51ea3a8-f1b0-4613-9440-b80e2236713b": {
+ "id": "a51ea3a8-f1b0-4613-9440-b80e2236713b",
+ "snapId": "npm:@metamask/snap-simple-keyring",
+ "iconUrl": "",
+ "snapTitle": "Metamask Simple Keyring",
+ "snapSlug": "Secure your account with MetaMask Mobile",
+ "snapDescription": "A simple private key is a randomly generated string of characters that is used to sign transactions. This private key is stored securely within this snap.",
+ "tags": ["EOA"],
+ "developer": "Metamask",
+ "website": "https://www.consensys.net/",
+ "auditUrls": ["auditUrl1", "auditUrl2"],
+ "version": "1.0.0",
+ "lastUpdated": "April 20, 2023"
+ }
+ },
"notifications": {
"test": {
"id": "test",
diff --git a/test/e2e/snaps/enums.js b/test/e2e/snaps/enums.js
index 1e5558cf2..d19c74967 100644
--- a/test/e2e/snaps/enums.js
+++ b/test/e2e/snaps/enums.js
@@ -1,3 +1,5 @@
module.exports = {
TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/test-snaps/5.5.0/',
+ TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL:
+ 'https://metamask.github.io/snap-simple-keyring/latest/',
};
diff --git a/test/e2e/snaps/test-snap-manageAccount.spec.js b/test/e2e/snaps/test-snap-manageAccount.spec.js
new file mode 100644
index 000000000..9ba5dce54
--- /dev/null
+++ b/test/e2e/snaps/test-snap-manageAccount.spec.js
@@ -0,0 +1,113 @@
+const { strict: assert } = require('assert');
+const { withFixtures } = require('../helpers');
+const FixtureBuilder = require('../fixture-builder');
+const { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } = require('./enums');
+
+describe('Test Snap Account', function () {
+ it('can create a new snap account', async function () {
+ const ganacheOptions = {
+ accounts: [
+ {
+ secretKey:
+ '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
+ balance: 25000000000000000000,
+ },
+ ],
+ };
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions,
+ failOnConsoleError: false,
+ title: this.test.title,
+ },
+ async ({ driver }) => {
+ await driver.navigate();
+
+ // enter pw into extension
+ await driver.fill('#password', 'correct horse battery staple');
+ await driver.press('#password', driver.Key.ENTER);
+
+ // navigate to test snaps page and connect
+ await driver.openNewPage(TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL);
+ await driver.delay(1000);
+ const connectButton = await driver.findElement('#connectButton');
+ await driver.scrollToElement(connectButton);
+ await driver.delay(1000);
+ await driver.clickElement('#connectButton');
+ await driver.delay(500);
+
+ // switch to metamask extension and click connect
+ const windowHandles = await driver.waitUntilXWindowHandles(
+ 3,
+ 1000,
+ 10000,
+ );
+ await driver.switchToWindowWithTitle(
+ 'MetaMask Notification',
+ windowHandles,
+ );
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ try {
+ await driver.clickElement('[data-testid="snap-install-scroll"]');
+ } catch (_) {
+ console.log('Missing scroll');
+ }
+
+ await driver.waitForSelector({ text: 'Install' });
+
+ await driver.clickElement({
+ text: 'Install',
+ tag: 'button',
+ });
+
+ await driver.waitForSelector({ text: 'OK' });
+
+ await driver.clickElement({
+ text: 'OK',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithTitle(
+ 'SSK - Snap Simple Keyring',
+ windowHandles,
+ );
+
+ // check the dapp connection status
+ await driver.waitForSelector({
+ css: '#snapConnected',
+ text: 'Connected',
+ });
+
+ // create new account on dapp
+ await driver.clickElement({
+ text: 'Create Account',
+ tag: 'div',
+ });
+
+ // create name for account
+ await driver.fill("[placeholder='Name']", 'snap account');
+
+ await driver.clickElement({
+ text: 'Execute',
+ tag: 'button',
+ });
+
+ await driver.delay(1000);
+
+ // switch to metamask extension
+ await driver.switchToWindowWithTitle('MetaMask', windowHandles);
+
+ // click on accounts
+ await driver.clickElement('[data-testid="account-menu-icon"]');
+
+ const label = await driver.findElement('.mm-tag');
+ assert.strictEqual(await label.getText(), 'Snaps');
+ },
+ );
+ });
+});
diff --git a/ui/components/app/account-menu/keyring-label.js b/ui/components/app/account-menu/keyring-label.js
new file mode 100644
index 000000000..7abc718d1
--- /dev/null
+++ b/ui/components/app/account-menu/keyring-label.js
@@ -0,0 +1,61 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import { HardwareKeyringNames } from '../../../../shared/constants/hardware-wallets';
+import { KeyringType } from '../../../../shared/constants/keyring';
+
+export default function KeyringLabel({ keyring }) {
+ const t = useI18nContext();
+ let label = null;
+
+ // Keyring value might take a while to get a value
+ if (!keyring) {
+ return null;
+ }
+ const { type } = keyring;
+
+ switch (type) {
+ case KeyringType.qr:
+ label = HardwareKeyringNames.qr;
+ break;
+ case KeyringType.imported:
+ label = t('imported');
+ break;
+ case KeyringType.trezor:
+ label = HardwareKeyringNames.trezor;
+ break;
+ case KeyringType.ledger:
+ label = HardwareKeyringNames.ledger;
+ break;
+ case KeyringType.lattice:
+ label = HardwareKeyringNames.lattice;
+ break;
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ case KeyringType.snap:
+ label = t('snaps');
+ break;
+ ///: END:ONLY_INCLUDE_IN
+ default:
+ label = null;
+ }
+
+ ///: BEGIN:ONLY_INCLUDE_IN(mmi)
+ if (type.startsWith('Custody') && /JSONRPC/u.test(type)) {
+ label = type.split(' - ')[1];
+ return null;
+ }
+ ///: END:ONLY_INCLUDE_IN
+
+ if (label === null) {
+ return label;
+ }
+
+ return (
+ <>{label ?
{label}
: null}>
+ );
+}
+
+KeyringLabel.propTypes = {
+ keyring: PropTypes.object,
+};
diff --git a/ui/components/app/configure-snap-popup/configure-snap-popup.test.tsx b/ui/components/app/configure-snap-popup/configure-snap-popup.test.tsx
new file mode 100644
index 000000000..6a4ac9f8d
--- /dev/null
+++ b/ui/components/app/configure-snap-popup/configure-snap-popup.test.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import { fireEvent } from '@testing-library/react';
+import configureMockStore from 'redux-mock-store';
+import { renderWithProvider } from '../../../../test/lib/render-helpers';
+import messages from '../../../../app/_locales/en/messages.json';
+import mockState from '../../../../test/data/mock-state.json';
+import ConfigureSnapPopup, {
+ ConfigureSnapPopupType,
+} from './configure-snap-popup';
+
+const mockOnClose = jest.fn();
+const mockStore = configureMockStore([])(mockState);
+describe('ConfigureSnapPopup', () => {
+ global.platform = { openTab: jest.fn() };
+
+ it('should show configure popup title and description', async () => {
+ const { getByText } = renderWithProvider(
+ ,
+ mockStore,
+ );
+ expect(
+ getByText(messages.configureSnapPopupTitle.message),
+ ).toBeInTheDocument();
+ expect(
+ getByText(messages.configureSnapPopupDescription.message),
+ ).toBeInTheDocument();
+ });
+
+ it('should show install popup title and description', async () => {
+ const { getByText } = renderWithProvider(
+ ,
+ mockStore,
+ );
+ expect(
+ getByText(messages.configureSnapPopupInstallTitle.message),
+ ).toBeInTheDocument();
+ expect(
+ getByText(messages.configureSnapPopupInstallDescription.message),
+ ).toBeInTheDocument();
+ });
+
+ it('should open link on click of link', async () => {
+ const { getByText } = renderWithProvider(
+ ,
+ mockStore,
+ );
+ const link = getByText('mockLink');
+ await fireEvent.click(link);
+ expect(global.platform.openTab).toHaveBeenCalledWith({
+ url: 'mockLink',
+ });
+ });
+});
diff --git a/ui/components/app/configure-snap-popup/configure-snap-popup.tsx b/ui/components/app/configure-snap-popup/configure-snap-popup.tsx
new file mode 100644
index 000000000..d62edd27c
--- /dev/null
+++ b/ui/components/app/configure-snap-popup/configure-snap-popup.tsx
@@ -0,0 +1,99 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {
+ BUTTON_VARIANT,
+ Button,
+ Text,
+ Box,
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalHeader,
+} from '../../component-library';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import {
+ AlignItems,
+ Display,
+ FlexDirection,
+ JustifyContent,
+ TextAlign,
+ TextVariant,
+} from '../../../helpers/constants/design-system';
+
+export enum ConfigureSnapPopupType {
+ CONFIGURE = 'configure',
+ INSTALL = 'install',
+}
+
+export default function ConfigureSnapPopup({
+ type,
+ isOpen,
+ onClose,
+ link,
+}: {
+ type: ConfigureSnapPopupType;
+ isOpen: boolean;
+ onClose: () => void;
+ link: string;
+}) {
+ const t = useI18nContext();
+
+ return (
+
+
+
+
+ {type === ConfigureSnapPopupType.CONFIGURE
+ ? t('configureSnapPopupTitle')
+ : t('configureSnapPopupInstallTitle')}
+
+
+
+
+ {type === ConfigureSnapPopupType.CONFIGURE
+ ? t('configureSnapPopupDescription')
+ : t('configureSnapPopupInstallDescription')}
+
+
+ {t('configureSnapPopupLink')}
+
+ {
+ global.platform.openTab({
+ url: link,
+ });
+ }}
+ >
+ {link}
+
+
+
+
+ );
+}
+
+ConfigureSnapPopup.propTypes = {
+ type: PropTypes.oneOf([
+ ConfigureSnapPopupType.CONFIGURE,
+ ConfigureSnapPopupType.INSTALL,
+ ]).isRequired,
+ isOpen: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ link: PropTypes.string.isRequired,
+};
diff --git a/ui/components/app/configure-snap-popup/index.js b/ui/components/app/configure-snap-popup/index.js
new file mode 100644
index 000000000..60684ebc0
--- /dev/null
+++ b/ui/components/app/configure-snap-popup/index.js
@@ -0,0 +1 @@
+export { default, ConfigureSnapPopupType } from './configure-snap-popup';
diff --git a/ui/components/app/modals/account-details-modal/account-details-modal.component.js b/ui/components/app/modals/account-details-modal/account-details-modal.component.js
index f35a8153b..1a759713b 100644
--- a/ui/components/app/modals/account-details-modal/account-details-modal.component.js
+++ b/ui/components/app/modals/account-details-modal/account-details-modal.component.js
@@ -6,8 +6,10 @@ import AccountModalContainer from '../account-modal-container';
import QrView from '../../../ui/qr-code';
import EditableLabel from '../../../ui/editable-label';
import Button from '../../../ui/button';
-import { getURLHostName } from '../../../../helpers/utils/util';
-import { isHardwareKeyring } from '../../../../helpers/utils/hardware';
+import {
+ getURLHostName,
+ isAbleToExportAccount,
+} from '../../../../helpers/utils/util';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import CustodyLabels from '../../../institutional/custody-labels/custody-labels';
import { toChecksumHexAddress } from '../../../../../shared/modules/hexstring-utils';
@@ -65,11 +67,7 @@ export default class AccountDetailsModal extends Component {
return kr.accounts.includes(address);
});
- let exportPrivateKeyFeatureEnabled = true;
- // This feature is disabled for hardware wallets
- if (isHardwareKeyring(keyring?.type)) {
- exportPrivateKeyFeatureEnabled = false;
- }
+ let exportPrivateKeyFeatureEnabled = isAbleToExportAccount(keyring?.type);
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
if (keyring?.type?.search('Custody') !== -1) {
diff --git a/ui/components/app/modals/account-details-modal/account-details-modal.test.js b/ui/components/app/modals/account-details-modal/account-details-modal.test.js
index 7b85c27bb..259da80ca 100644
--- a/ui/components/app/modals/account-details-modal/account-details-modal.test.js
+++ b/ui/components/app/modals/account-details-modal/account-details-modal.test.js
@@ -103,4 +103,133 @@ describe('Account Details Modal', () => {
expect(queryByText(/block.explorer/u)).toBeInTheDocument();
});
+
+ it('does not display export private key if the keyring is snaps', () => {
+ const mockStateWithSnapKeyring = {
+ appState: {
+ networkDropdownOpen: false,
+ gasIsLoading: false,
+ isLoading: false,
+ modal: {
+ open: false,
+ modalState: {
+ name: null,
+ props: {},
+ },
+ previousModalState: {
+ name: null,
+ },
+ },
+ warning: null,
+ customTokenAmount: '10',
+ },
+ history: {
+ mostRecentOverviewPage: '/mostRecentOverviewPage',
+ },
+ metamask: {
+ providerConfig: {
+ type: 'rpc',
+ chainId: '0x5',
+ ticker: 'ETH',
+ id: 'testNetworkConfigurationId',
+ },
+ keyrings: [
+ {
+ type: 'Snap Keyring',
+ accounts: [
+ '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b',
+ ],
+ },
+ {
+ type: 'Ledger Hardware',
+ accounts: ['0xc42edfcc21ed14dda456aa0756c153f7985d8813'],
+ },
+ {
+ type: 'Simple Key Pair',
+ accounts: ['0xeb9e64b93097bc15f01f13eae97015c57ab64823'],
+ },
+ ],
+ identities: {
+ '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': {
+ address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ name: 'Test Account',
+ },
+ '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b': {
+ address: '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b',
+ name: 'Test Account 2',
+ },
+ '0xc42edfcc21ed14dda456aa0756c153f7985d8813': {
+ address: '0xc42edfcc21ed14dda456aa0756c153f7985d8813',
+ name: 'Test Ledger 1',
+ },
+ '0xeb9e64b93097bc15f01f13eae97015c57ab64823': {
+ name: 'Test Account 3',
+ address: '0xeb9e64b93097bc15f01f13eae97015c57ab64823',
+ },
+ },
+ networkDetails: {
+ EIPS: {
+ 1559: true,
+ },
+ },
+ frequentRpcListDetail: [],
+ subjectMetadata: {
+ 'npm:@metamask/test-snap-bip44': {
+ name: '@metamask/test-snap-bip44',
+ version: '1.2.3',
+ subjectType: 'snap',
+ },
+ },
+ notifications: {
+ test: {
+ id: 'test',
+ origin: 'local:http://localhost:8086/',
+ createdDate: 1652967897732,
+ readDate: null,
+ message: 'Hello, http://localhost:8086!',
+ },
+ test2: {
+ id: 'test2',
+ origin: 'local:http://localhost:8086/',
+ createdDate: 1652967897732,
+ readDate: 1652967897732,
+ message: 'Hello, http://localhost:8086!',
+ },
+ },
+ cachedBalances: {},
+ incomingTransactions: {},
+ selectedAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ accounts: {
+ '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': {
+ balance: '0x346ba7725f412cbfdb',
+ address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ },
+ '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b': {
+ address: '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b',
+ balance: '0x0',
+ },
+ '0xc42edfcc21ed14dda456aa0756c153f7985d8813': {
+ address: '0xc42edfcc21ed14dda456aa0756c153f7985d8813',
+ balance: '0x0',
+ },
+ '0xeb9e64b93097bc15f01f13eae97015c57ab64823': {
+ address: '0xeb9e64b93097bc15f01f13eae97015c57ab64823',
+ balance: '0x0',
+ },
+ },
+ },
+ };
+ const mockStoreWithSnapKeyring = configureMockState([thunk])(
+ mockStateWithSnapKeyring,
+ );
+ const { queryByText } = renderWithProvider(
+ ,
+ mockStoreWithSnapKeyring,
+ );
+
+ const exportPrivateKeyButton = queryByText(exportPrivateKey.message);
+
+ expect(exportPrivateKeyButton).not.toBeInTheDocument();
+ });
});
diff --git a/ui/components/multichain/account-details/account-details-display.js b/ui/components/multichain/account-details/account-details-display.js
index 210670fbb..dfceec94d 100644
--- a/ui/components/multichain/account-details/account-details-display.js
+++ b/ui/components/multichain/account-details/account-details-display.js
@@ -11,7 +11,7 @@ import {
getHardwareWalletType,
getMetaMaskKeyrings,
} from '../../../selectors';
-import { isHardwareKeyring } from '../../../helpers/utils/hardware';
+import { isAbleToExportAccount } from '../../../helpers/utils/util';
import {
BUTTON_SECONDARY_SIZES,
ButtonSecondary,
@@ -43,7 +43,7 @@ export const AccountDetailsDisplay = ({
const keyrings = useSelector(getMetaMaskKeyrings);
const keyring = keyrings.find((kr) => kr.accounts.includes(address));
- const exportPrivateKeyFeatureEnabled = !isHardwareKeyring(keyring?.type);
+ const exportPrivateKeyFeatureEnabled = isAbleToExportAccount(keyring?.type);
const chainId = useSelector(getCurrentChainId);
const deviceName = useSelector(getHardwareWalletType);
diff --git a/ui/components/multichain/account-list-item/account-list-item.js b/ui/components/multichain/account-list-item/account-list-item.js
index 4871b4c28..5066578fd 100644
--- a/ui/components/multichain/account-list-item/account-list-item.js
+++ b/ui/components/multichain/account-list-item/account-list-item.js
@@ -60,6 +60,10 @@ function getLabel(keyring = {}, t) {
return HardwareKeyringNames.ledger;
case KeyringType.lattice:
return HardwareKeyringNames.lattice;
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ case KeyringType.snap:
+ return t('snaps');
+ ///: END:ONLY_INCLUDE_IN
default:
return null;
}
diff --git a/ui/components/multichain/account-list-item/account-list-item.test.js b/ui/components/multichain/account-list-item/account-list-item.test.js
index 6b4a60e6a..3cd9cf3c3 100644
--- a/ui/components/multichain/account-list-item/account-list-item.test.js
+++ b/ui/components/multichain/account-list-item/account-list-item.test.js
@@ -102,4 +102,17 @@ describe('AccountListItem', () => {
expect(getByAltText(`${connectedAvatarName} logo`)).toBeInTheDocument();
});
+
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ it('renders the snap label for snap accounts', () => {
+ const { getByText } = render({
+ identity: {
+ address: '0xb552685e3d2790eFd64a175B00D51F02cdaFEe5D',
+ name: 'Snap Account',
+ },
+ });
+
+ expect(getByText('Snaps')).toBeInTheDocument();
+ });
+ ///: END:ONLY_INCLUDE_IN
});
diff --git a/ui/components/multichain/account-list-menu/account-list-menu.js b/ui/components/multichain/account-list-menu/account-list-menu.js
index a53c1c255..ff0f6b236 100644
--- a/ui/components/multichain/account-list-menu/account-list-menu.js
+++ b/ui/components/multichain/account-list-menu/account-list-menu.js
@@ -33,6 +33,9 @@ import {
} from '../../../../shared/constants/metametrics';
import {
CONNECT_HARDWARE_ROUTE,
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ ADD_SNAP_ACCOUNT_ROUTE,
+ ///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
CUSTODY_ACCOUNT_ROUTE,
///: END:ONLY_INCLUDE_IN
@@ -247,6 +250,30 @@ export const AccountListMenu = ({ onClose }) => {
{t('hardwareWallet')}
+ {
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ <>
+
+ {
+ dispatch(toggleAccountMenu());
+ getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
+ ? global.platform.openExtensionInBrowser(
+ ADD_SNAP_ACCOUNT_ROUTE,
+ null,
+ true,
+ )
+ : history.push(ADD_SNAP_ACCOUNT_ROUTE);
+ }}
+ >
+ {t('settingAddSnapAccount')}
+
+
+ >
+ ///: END:ONLY_INCLUDE_IN
+ }
{
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
diff --git a/ui/components/multichain/account-list-menu/account-list-menu.test.js b/ui/components/multichain/account-list-menu/account-list-menu.test.js
index 8e4637f93..b1c789b2f 100644
--- a/ui/components/multichain/account-list-menu/account-list-menu.test.js
+++ b/ui/components/multichain/account-list-menu/account-list-menu.test.js
@@ -1,12 +1,33 @@
/* eslint-disable jest/require-top-level-describe */
import React from 'react';
import reactRouterDom from 'react-router-dom';
-import { fireEvent, renderWithProvider } from '../../../../test/jest';
+import { fireEvent, renderWithProvider, waitFor } from '../../../../test/jest';
import configureStore from '../../../store/store';
import mockState from '../../../../test/data/mock-state.json';
-import { CONNECT_HARDWARE_ROUTE } from '../../../helpers/constants/routes';
+///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+import messages from '../../../../app/_locales/en/messages.json';
+import {
+ CONNECT_HARDWARE_ROUTE,
+ ADD_SNAP_ACCOUNT_ROUTE,
+} from '../../../helpers/constants/routes';
+///: END:ONLY_INCLUDE_IN
import { AccountListMenu } from '.';
+///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+const mockToggleAccountMenu = jest.fn();
+const mockGetEnvironmentType = jest.fn();
+
+jest.mock('../../../store/actions.ts', () => ({
+ ...jest.requireActual('../../../store/actions.ts'),
+ toggleAccountMenu: () => mockToggleAccountMenu,
+}));
+
+jest.mock('../../../../app/scripts/lib/util', () => ({
+ ...jest.requireActual('../../../../app/scripts/lib/util'),
+ getEnvironmentType: () => mockGetEnvironmentType,
+}));
+///: END:ONLY_INCLUDE_IN
+
const render = (props = { onClose: () => jest.fn() }) => {
const store = configureStore({
activeTab: {
@@ -137,4 +158,32 @@ describe('AccountListMenu', () => {
const searchBox = document.querySelector('input[type=search]');
expect(searchBox).toBeInTheDocument();
});
+
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ it('renders the add snap account button', async () => {
+ const { getByText } = render();
+ const addSnapAccountButton = getByText(
+ messages.settingAddSnapAccount.message,
+ );
+ expect(addSnapAccountButton).toBeInTheDocument();
+
+ fireEvent.click(addSnapAccountButton);
+
+ await waitFor(() => {
+ expect(mockToggleAccountMenu).toHaveBeenCalled();
+ });
+ });
+
+ it('pushes history when clicking add snap account from extended view', async () => {
+ const { getByText } = render();
+ mockGetEnvironmentType.mockReturnValueOnce('fullscreen');
+ const addSnapAccountButton = getByText(
+ messages.settingAddSnapAccount.message,
+ );
+ fireEvent.click(addSnapAccountButton);
+ await waitFor(() => {
+ expect(historyPushMock).toHaveBeenCalledWith(ADD_SNAP_ACCOUNT_ROUTE);
+ });
+ });
+ ///: END:ONLY_INCLUDE_IN
});
diff --git a/ui/helpers/constants/routes.ts b/ui/helpers/constants/routes.ts
index 974af5edc..3e2e4e1b1 100644
--- a/ui/helpers/constants/routes.ts
+++ b/ui/helpers/constants/routes.ts
@@ -50,6 +50,9 @@ const CONNECT_SNAP_UPDATE_ROUTE = '/snap-update';
const CONNECT_SNAP_RESULT_ROUTE = '/snap-install-result';
const NOTIFICATIONS_ROUTE = '/notifications';
///: END:ONLY_INCLUDE_IN
+///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+const ADD_SNAP_ACCOUNT_ROUTE = '/add-snap-account';
+///: END:ONLY_INCLUDE_IN
const CONNECTED_ROUTE = '/connected';
const CONNECTED_ACCOUNTS_ROUTE = '/connected/accounts';
const SWAPS_ROUTE = '/swaps';
@@ -132,6 +135,10 @@ const PATH_NAME_MAP = {
[CONFIRM_IMPORT_TOKEN_ROUTE]: 'Confirm Import Token Page',
[CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE]: 'Confirm Add Suggested Token Page',
[NEW_ACCOUNT_ROUTE]: 'New Account Page',
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ [ADD_SNAP_ACCOUNT_ROUTE]: 'Add Snap Account List Page',
+ [`${ADD_SNAP_ACCOUNT_ROUTE}/:snapId`]: `Add Snap Account Page`,
+ ///: END:ONLY_INCLUDE_IN
[CONFIRM_ADD_SUGGESTED_NFT_ROUTE]: 'Confirm Add Suggested NFT Page',
[CONNECT_HARDWARE_ROUTE]: 'Connect Hardware Wallet Page',
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
@@ -249,6 +256,9 @@ export {
CONNECT_SNAP_RESULT_ROUTE,
NOTIFICATIONS_ROUTE,
///: END:ONLY_INCLUDE_IN
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ ADD_SNAP_ACCOUNT_ROUTE,
+ ///: END:ONLY_INCLUDE_IN
CONNECTED_ROUTE,
CONNECTED_ACCOUNTS_ROUTE,
PATH_NAME_MAP,
diff --git a/ui/helpers/utils/permission.js b/ui/helpers/utils/permission.js
index c43f79a51..34e01ee7b 100644
--- a/ui/helpers/utils/permission.js
+++ b/ui/helpers/utils/permission.js
@@ -255,6 +255,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({
leftIcon: IconName.SecurityKey,
weight: 3,
}),
+
[RestrictedMethods.snap_manageState]: ({ t }) => ({
label: t('permission_manageState'),
description: t('permission_manageStateDescription'),
@@ -389,6 +390,14 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({
return results;
},
///: END:ONLY_INCLUDE_IN
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ [RestrictedMethods.snap_manageAccounts]: ({ t }) => ({
+ label: t('permission_manageAccounts'),
+ leftIcon: getLeftIcon(IconName.UserCircleAdd),
+ rightIcon: null,
+ weight: 3,
+ }),
+ ///: END:ONLY_INCLUDE_IN
[UNKNOWN_PERMISSION]: ({ t, permissionName }) => ({
label: t('permission_unknown', [permissionName ?? 'undefined']),
leftIcon: getLeftIcon(IconName.Question),
diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js
index 5f0030ed3..20ba3656b 100644
--- a/ui/helpers/utils/util.js
+++ b/ui/helpers/utils/util.js
@@ -621,3 +621,13 @@ export const getNetworkNameFromProviderType = (providerName) => {
}
return providerName;
};
+
+/**
+ * Checks if the given keyring type is able to export an account.
+ *
+ * @param keyringType - The type of the keyring.
+ * @returns {boolean} `false` if the keyring type includes 'Hardware' or 'Snap', `true` otherwise.
+ */
+export const isAbleToExportAccount = (keyringType = '') => {
+ return !keyringType.includes('Hardware') && !keyringType.includes('Snap');
+};
diff --git a/ui/pages/keyring-snaps/add-snap-account-modal/add-snap-account-modal.stories.tsx b/ui/pages/keyring-snaps/add-snap-account-modal/add-snap-account-modal.stories.tsx
new file mode 100644
index 000000000..2f5ef9f72
--- /dev/null
+++ b/ui/pages/keyring-snaps/add-snap-account-modal/add-snap-account-modal.stories.tsx
@@ -0,0 +1,36 @@
+import { useArgs } from '@storybook/client-api';
+import { StoryFn } from '@storybook/react';
+import React from 'react';
+import { BUTTON_VARIANT, Button } from '../../../components/component-library';
+import AddSnapAccountModal from '.';
+
+const AddSnapAccountModalStory = {
+ title: 'Components/App/AddSnapAccountModal',
+ component: AddSnapAccountModal,
+ argTypes: {},
+};
+
+export const DefaultStory: StoryFn = () => {
+ const [{ isShowingModal }, updateArgs] = useArgs();
+
+ return (
+ <>
+ updateArgs({ isShowingModal: true })}
+ >
+ Open modal
+
+ {isShowingModal && (
+ updateArgs({ isShowingModal: false })}
+ />
+ )}
+ >
+ );
+};
+
+DefaultStory.storyName = 'Default';
+
+export default AddSnapAccountModalStory;
diff --git a/ui/pages/keyring-snaps/add-snap-account-modal/add-snap-account-modal.tsx b/ui/pages/keyring-snaps/add-snap-account-modal/add-snap-account-modal.tsx
new file mode 100644
index 000000000..374f17b46
--- /dev/null
+++ b/ui/pages/keyring-snaps/add-snap-account-modal/add-snap-account-modal.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import {
+ BUTTON_VARIANT,
+ Box,
+ Button,
+ Modal,
+ ModalContent,
+ ModalHeader,
+ ModalOverlay,
+ Text,
+} from '../../../components/component-library';
+import {
+ AlignItems,
+ Display,
+ FlexDirection,
+ JustifyContent,
+ TextAlign,
+ TextVariant,
+} from '../../../helpers/constants/design-system';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+
+export default function AddSnapAccountModal({
+ onClose,
+ isOpen,
+}: {
+ isOpen: boolean;
+ onClose: () => void;
+}) {
+ const t = useI18nContext();
+
+ return (
+
+
+
+
+ {t('settingAddSnapAccount')}
+
+
+
+
+
+
+ {t('addSnapAccountModalDescription')}
+
+ {
+ onClose();
+ }}
+ >
+ {t('getStarted')}
+
+
+
+
+ );
+}
diff --git a/ui/pages/keyring-snaps/add-snap-account-modal/index.ts b/ui/pages/keyring-snaps/add-snap-account-modal/index.ts
new file mode 100644
index 000000000..285965e91
--- /dev/null
+++ b/ui/pages/keyring-snaps/add-snap-account-modal/index.ts
@@ -0,0 +1 @@
+export { default } from './add-snap-account-modal';
diff --git a/ui/pages/keyring-snaps/add-snap-account.tsx b/ui/pages/keyring-snaps/add-snap-account.tsx
new file mode 100644
index 000000000..023892a04
--- /dev/null
+++ b/ui/pages/keyring-snaps/add-snap-account.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { Route, Switch } from 'react-router-dom';
+import { ADD_SNAP_ACCOUNT_ROUTE } from '../../helpers/constants/routes';
+import {
+ BackgroundColor,
+ Display,
+ JustifyContent,
+} from '../../helpers/constants/design-system';
+import { Box } from '../../components/component-library';
+import NewSnapAccountPage from './new-snap-account-page';
+import SnapAccountDetailPage from './snap-account-detail-page';
+
+export default function AddSnapAccountPage() {
+ return (
+
+
+ <>
+
+
+ >
+
+
+ );
+}
diff --git a/ui/pages/keyring-snaps/constants.ts b/ui/pages/keyring-snaps/constants.ts
new file mode 100644
index 000000000..29fbf55bc
--- /dev/null
+++ b/ui/pages/keyring-snaps/constants.ts
@@ -0,0 +1 @@
+export const METAMASK_DEVELOPER = 'metamask';
diff --git a/ui/pages/keyring-snaps/index.scss b/ui/pages/keyring-snaps/index.scss
new file mode 100644
index 000000000..8762f63fd
--- /dev/null
+++ b/ui/pages/keyring-snaps/index.scss
@@ -0,0 +1,2 @@
+@import "./snap-account-detail-page/snap-account-detail-page";
+@import "./new-snap-account-page/new-snap-account-page";
diff --git a/ui/pages/keyring-snaps/index.ts b/ui/pages/keyring-snaps/index.ts
new file mode 100644
index 000000000..ef43f8bf5
--- /dev/null
+++ b/ui/pages/keyring-snaps/index.ts
@@ -0,0 +1 @@
+export { default } from './add-snap-account';
diff --git a/ui/pages/keyring-snaps/new-snap-account-page/index.ts b/ui/pages/keyring-snaps/new-snap-account-page/index.ts
new file mode 100644
index 000000000..4bb5e161e
--- /dev/null
+++ b/ui/pages/keyring-snaps/new-snap-account-page/index.ts
@@ -0,0 +1,2 @@
+export { default } from './new-snap-account-page';
+export type { SnapCardProps, SnapDetails } from './new-snap-account-page';
diff --git a/ui/pages/keyring-snaps/new-snap-account-page/new-snap-account-page.scss b/ui/pages/keyring-snaps/new-snap-account-page/new-snap-account-page.scss
new file mode 100644
index 000000000..9ae038d23
--- /dev/null
+++ b/ui/pages/keyring-snaps/new-snap-account-page/new-snap-account-page.scss
@@ -0,0 +1,32 @@
+.snap-account-page {
+ $width-screen-sm-min: 85vw;
+ $width-screen-md-min: 80vw;
+ $width-screen-lg-min: 62vw;
+
+ @include screen-sm-min {
+ width: $width-screen-sm-min;
+ }
+
+ @include screen-md-min {
+ width: $width-screen-md-min;
+ }
+
+ @include screen-lg-min {
+ width: $width-screen-lg-min;
+ }
+
+ background-image: url('images/snap-account-page.svg');
+ background-size: 100% auto;
+ background-repeat: no-repeat;
+ background-position: bottom;
+}
+
+.snap-account-cards {
+ grid-template-columns: repeat(3, 1fr);
+}
+
+.snap-account-color-text {
+ background-image: linear-gradient(0.45deg, #f6851b, #43aefc);
+ background-clip: text;
+ color: transparent;
+}
diff --git a/ui/pages/keyring-snaps/new-snap-account-page/new-snap-account-page.test.tsx b/ui/pages/keyring-snaps/new-snap-account-page/new-snap-account-page.test.tsx
new file mode 100644
index 000000000..8935d62df
--- /dev/null
+++ b/ui/pages/keyring-snaps/new-snap-account-page/new-snap-account-page.test.tsx
@@ -0,0 +1,145 @@
+import { fireEvent, waitFor } from '@testing-library/react';
+import React from 'react';
+import configureMockStore from 'redux-mock-store';
+import messages from '../../../../app/_locales/en/messages.json';
+import { renderWithProvider } from '../../../../test/lib/render-helpers';
+import NewSnapAccountPage from '.';
+
+const mockHistoryPush = jest.fn();
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ push: mockHistoryPush,
+ }),
+}));
+
+const mockState = {
+ metamask: {
+ snapRegistryList: {
+ 'a51ea3a8-f1b0-4613-9440-b80e2236713b': {
+ id: 'a51ea3a8-f1b0-4613-9440-b80e2236713b',
+ snapId: 'npm:@metamask/snap-simple-keyring',
+ iconUrl: '',
+ snapTitle: 'Metamask Simple Keyring',
+ snapSlug: 'Secure your account with MetaMask Mobile',
+ snapDescription:
+ 'A simple private key is a randomly generated string of characters that is used to sign transactions. This private key is stored securely within this snap.',
+ tags: ['EOA'],
+ developer: 'Metamask',
+ website: 'https://www.consensys.net/',
+ auditUrls: ['auditUrl1', 'auditUrl2'],
+ version: '1.0.0',
+ lastUpdated: 'April 20, 2023',
+ },
+ },
+ snaps: {
+ 'npm:@metamask/snap-simple-keyring': {
+ id: 'npm:@metamask/snap-simple-keyring',
+ origin: 'npm:@metamask/snap-simple-keyring',
+ version: '5.1.2',
+ iconUrl: null,
+ initialPermissions: {
+ 'endowment:manageAccount': {},
+ },
+ manifest: {
+ description: 'An example keymanagement snap',
+ proposedName: 'Example Key Management Test Snap',
+ repository: {
+ type: 'git',
+ url: 'https://github.com/MetaMask/snap-simple-keyring.git',
+ },
+ source: {
+ location: {
+ npm: {
+ filePath: 'dist/bundle.js',
+ packageName: '@metamask/test-snap-account',
+ registry: 'https://registry.npmjs.org',
+ },
+ },
+ shasum: 'L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=',
+ },
+ version: '0.0.1',
+ },
+ versionHistory: [
+ {
+ date: 1680686075921,
+ origin: 'https://metamask.github.io',
+ version: '0.0.1',
+ },
+ ],
+ },
+ },
+ },
+};
+
+const renderComponent = (props = {}) => {
+ const mockStore = configureMockStore([])(mockState);
+ return renderWithProvider( , mockStore);
+};
+
+describe('NewSnapAccountPage', () => {
+ it('should render the popup', async () => {
+ const { getByText } = renderComponent();
+ const popupTitle = getByText(messages.settingAddSnapAccount.message);
+ expect(popupTitle).toBeInTheDocument();
+
+ const closeButton = getByText(messages.getStarted.message);
+ await fireEvent.click(closeButton);
+ await waitFor(() => {
+ expect(popupTitle).not.toBeInTheDocument();
+ });
+ });
+
+ it('should render the texts', async () => {
+ const { getByText } = renderComponent();
+ expect(
+ getByText(messages.settingAddSnapAccount.message),
+ ).toBeInTheDocument();
+ expect(
+ getByText(messages.addSnapAccountModalDescription.message),
+ ).toBeInTheDocument();
+ });
+
+ it('should render all the keymanagement snaps', async () => {
+ const { getAllByTestId } = renderComponent();
+ const keyManagementSnaps = getAllByTestId('key-management-snap');
+ expect(keyManagementSnaps.length).toBe(
+ Object.values(mockState.metamask.snapRegistryList).length,
+ );
+ });
+
+ it('should go to snap detail page on click of snap carot', async () => {
+ const { getAllByTestId } = renderComponent();
+ const iconCarot = getAllByTestId('to-snap-detail')[0];
+
+ await fireEvent.click(iconCarot);
+
+ await waitFor(() => {
+ expect(mockHistoryPush).toHaveBeenCalled();
+ });
+ });
+
+ it('should show configure button after clicking', async () => {
+ const { getByTestId, getByText } = renderComponent();
+ const configureButton = getByTestId('configure-snap-button');
+
+ await fireEvent.click(configureButton);
+ await waitFor(() => {
+ const configureSnapTitleInPopup = getByText(
+ messages.configureSnapPopupTitle.message,
+ );
+ expect(configureSnapTitleInPopup).toBeInTheDocument();
+ });
+
+ const closeButton = getByText(messages.getStarted.message);
+ await fireEvent.click(closeButton);
+
+ await waitFor(() => {
+ const configureSnapTitleInPopup = getByText(
+ messages.configureSnapPopupTitle.message,
+ );
+ expect(configureSnapTitleInPopup).toBeInTheDocument();
+ });
+ });
+});
diff --git a/ui/pages/keyring-snaps/new-snap-account-page/new-snap-account-page.tsx b/ui/pages/keyring-snaps/new-snap-account-page/new-snap-account-page.tsx
new file mode 100644
index 000000000..2ec9aea8c
--- /dev/null
+++ b/ui/pages/keyring-snaps/new-snap-account-page/new-snap-account-page.tsx
@@ -0,0 +1,134 @@
+import { Snap } from '@metamask/snaps-utils';
+import React, { useState, useEffect } from 'react';
+import { shallowEqual, useSelector } from 'react-redux';
+import { useHistory } from 'react-router-dom';
+import { Box, Text } from '../../../components/component-library';
+import {
+ AlignItems,
+ Display,
+ FlexDirection,
+ FlexWrap,
+ JustifyContent,
+ TextColor,
+ TextVariant,
+} from '../../../helpers/constants/design-system';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import {
+ getSnaps,
+ getsnapsAddSnapAccountModalDismissed,
+ getSnapRegistry,
+} from '../../../selectors';
+import {
+ setSnapsAddSnapAccountModalDismissed,
+ updateSnapRegistry,
+} from '../../../store/actions';
+import AddSnapAccountModal from '../add-snap-account-modal';
+import SnapCard from '../snap-card/snap-card';
+
+export interface SnapDetails {
+ id: string;
+ snapId: string;
+ iconUrl: string;
+ snapTitle: string;
+ snapSlug: string;
+ snapDescription: string;
+ tags: string[];
+ developer: string;
+ website: string;
+ auditUrls: string[];
+ version: string;
+ lastUpdated: string;
+}
+
+export interface SnapCardProps extends SnapDetails {
+ isInstalled: boolean;
+ updateAvailable: boolean;
+}
+
+export default function NewSnapAccountPage() {
+ const t = useI18nContext();
+ const history = useHistory();
+ const [showPopup, setShowPopup] = useState(true);
+ const installedSnaps: Record = useSelector(getSnaps);
+ const snapRegistryList: Record = useSelector(
+ getSnapRegistry,
+ shallowEqual,
+ );
+ useEffect(() => {
+ updateSnapRegistry().catch((err) =>
+ console.log(`Failed to fetch snap list: ${err}`),
+ );
+ }, []);
+
+ const hidePopup = async () => {
+ setShowPopup(false);
+ await setSnapsAddSnapAccountModalDismissed();
+ };
+
+ const snapsAddSnapAccountModalDismissed = useSelector(
+ getsnapsAddSnapAccountModalDismissed,
+ );
+
+ return (
+
+ {
+ await hidePopup();
+ }}
+ isOpen={showPopup && !snapsAddSnapAccountModalDismissed}
+ />
+
+
+ {t('snapCreateAccountTitle', [
+
+ {t('snapCreateAccountTitle2')}
+ ,
+ ])}
+
+
+ {t('snapCreateAccountSubtitle')}
+
+
+
+ {Object.values(snapRegistryList).map(
+ (snap: SnapDetails, index: number) => {
+ const foundSnap = Object.values(installedSnaps).find(
+ (installedSnap) => installedSnap.id === snap.snapId,
+ );
+
+ const isInstalled = Boolean(foundSnap);
+
+ return (
+ {
+ history.push(`/add-snap-account/${snap.id}`);
+ }}
+ />
+ );
+ },
+ )}
+
+
+ );
+}
diff --git a/ui/pages/keyring-snaps/snap-account-detail-page/__snapshots__/snap-account-detail-page.test.tsx.snap b/ui/pages/keyring-snaps/snap-account-detail-page/__snapshots__/snap-account-detail-page.test.tsx.snap
new file mode 100644
index 000000000..86dad4839
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-account-detail-page/__snapshots__/snap-account-detail-page.test.tsx.snap
@@ -0,0 +1,232 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SnapAccountDetails should take a snapshot 1`] = `
+
+
+
+
+
+ Create a Snap Account
+
+
+
+ Metamask Simple Keyring
+
+
+
+
+
+ Metamask Simple Keyring
+
+
+
+
+ Install snap
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Secure your account with MetaMask Mobile
+
+
+ A simple private key is a randomly generated string of characters that is used to sign transactions. This private key is stored securely within this snap.
+
+
+
+
+
+
+ Developer
+
+
+ Metamask
+
+
+
+
+ Website
+
+ https://www.consensys.net/
+
+
+
+
+ Version
+
+
+ 1.0.0
+
+
+
+
+ Updated
+
+
+ April 20, 2023
+
+
+
+
+
+
+`;
diff --git a/ui/pages/keyring-snaps/snap-account-detail-page/detail.tsx b/ui/pages/keyring-snaps/snap-account-detail-page/detail.tsx
new file mode 100644
index 000000000..7e47b59ad
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-account-detail-page/detail.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { Box, Text } from '../../../components/component-library';
+import {
+ FlexDirection,
+ TextVariant,
+} from '../../../helpers/constants/design-system';
+
+const Detail = ({
+ title,
+ children,
+}: React.PropsWithChildren<{ title: string }>) => {
+ return (
+
+
+ {title}
+
+ {children}
+
+ );
+};
+
+export default Detail;
diff --git a/ui/pages/keyring-snaps/snap-account-detail-page/header.tsx b/ui/pages/keyring-snaps/snap-account-detail-page/header.tsx
new file mode 100644
index 000000000..ff44b01d4
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-account-detail-page/header.tsx
@@ -0,0 +1,214 @@
+import React, { useState } from 'react';
+import ConfigureSnapPopup, {
+ ConfigureSnapPopupType,
+} from '../../../components/app/configure-snap-popup/configure-snap-popup';
+import {
+ BUTTON_VARIANT,
+ Box,
+ Button,
+ Icon,
+ IconName,
+ Tag,
+ Text,
+} from '../../../components/component-library';
+import {
+ AlignItems,
+ BackgroundColor,
+ BorderColor,
+ Display,
+ FlexDirection,
+ JustifyContent,
+ TextColor,
+ TextVariant,
+} from '../../../helpers/constants/design-system';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import { SnapCardProps } from '../new-snap-account-page/new-snap-account-page';
+import { METAMASK_DEVELOPER } from '../constants';
+
+export const SnapDetailHeader = ({
+ updateAvailable,
+ snapTitle,
+ isInstalled,
+ iconUrl,
+ developer,
+ auditUrls,
+ website,
+}: Pick<
+ SnapCardProps,
+ | 'updateAvailable'
+ | 'snapTitle'
+ | 'isInstalled'
+ | 'iconUrl'
+ | 'developer'
+ | 'auditUrls'
+ | 'website'
+>) => {
+ const t = useI18nContext();
+ const [showConfigPopover, setShowConfigPopover] = useState(false);
+ const [showConfigPopoverType, setShowConfigPopoverType] =
+ useState(ConfigureSnapPopupType.INSTALL);
+
+ return (
+ <>
+
+
+ history.back()}
+ >
+ {t('snapDetailsCreateASnapAccount')}
+
+
+ {snapTitle}
+
+
+
+
+ {snapTitle}
+
+ {isInstalled && (
+
+ )}
+
+
+ {isInstalled && updateAvailable && (
+ {
+ setShowConfigPopoverType(ConfigureSnapPopupType.INSTALL);
+ setShowConfigPopover(true);
+ }}
+ >
+ {t('snapUpdateAvailable')}
+
+ )}
+ {isInstalled && (
+ {
+ setShowConfigPopoverType(ConfigureSnapPopupType.CONFIGURE);
+ setShowConfigPopover(true);
+ }}
+ >
+ {t('snapConfigure')}
+
+ )}
+ {!isInstalled && (
+ {
+ setShowConfigPopoverType(ConfigureSnapPopupType.INSTALL);
+ setShowConfigPopover(true);
+ }}
+ >
+ {t('snapInstall')}
+
+ )}
+
+
+
+
+
+
+ {developer.toLowerCase() === METAMASK_DEVELOPER && (
+
+ {' '}
+
+ {t('snapCreatedByMetaMask')}
+
+
+ }
+ labelProps={{
+ color: TextColor.infoDefault,
+ }}
+ className=""
+ marginRight={1}
+ />
+ )}
+ {auditUrls.length > 0 && (
+
+ {' '}
+
+ {t('snapIsAudited')}
+
+
+ }
+ labelProps={{
+ color: TextColor.infoDefault,
+ }}
+ />
+ )}
+
+
+ setShowConfigPopover(false)}
+ link={website}
+ />
+ >
+ );
+};
diff --git a/ui/pages/keyring-snaps/snap-account-detail-page/index.ts b/ui/pages/keyring-snaps/snap-account-detail-page/index.ts
new file mode 100644
index 000000000..56caa6c61
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-account-detail-page/index.ts
@@ -0,0 +1 @@
+export { default } from './snap-account-detail-page';
diff --git a/ui/pages/keyring-snaps/snap-account-detail-page/snap-account-detail-page.scss b/ui/pages/keyring-snaps/snap-account-detail-page/snap-account-detail-page.scss
new file mode 100644
index 000000000..8d8854ec8
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-account-detail-page/snap-account-detail-page.scss
@@ -0,0 +1,24 @@
+
+.snap-details-page {
+ $width-screen-sm-min: 85vw;
+ $width-screen-md-min: 80vw;
+ $width-screen-lg-min: 62vw;
+
+ @include screen-sm-min {
+ width: $width-screen-sm-min;
+ }
+
+ @include screen-md-min {
+ width: $width-screen-md-min;
+ }
+
+ @include screen-lg-min {
+ width: $width-screen-lg-min;
+ }
+
+
+ .snap-detail-icon {
+ width: 16px;
+ height: 16px;
+ }
+}
diff --git a/ui/pages/keyring-snaps/snap-account-detail-page/snap-account-detail-page.test.tsx b/ui/pages/keyring-snaps/snap-account-detail-page/snap-account-detail-page.test.tsx
new file mode 100644
index 000000000..d50aa90bc
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-account-detail-page/snap-account-detail-page.test.tsx
@@ -0,0 +1,239 @@
+import { fireEvent, waitFor } from '@testing-library/react';
+import React from 'react';
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import messages from '../../../../app/_locales/en/messages.json';
+import mockState from '../../../../test/data/mock-state.json';
+import { renderWithProvider } from '../../../../test/lib/render-helpers';
+import SnapAccountDetailPage from '.';
+
+const snap = {
+ id: 'a51ea3a8-f1b0-4613-9440-b80e2236713b',
+ snapId: 'npm:@metamask/snap-simple-keyring',
+ iconUrl: '',
+ snapTitle: 'Metamask Simple Keyring',
+ snapSlug: 'Secure your account with MetaMask Mobile',
+ snapDescription:
+ 'A simple private key is a randomly generated string of characters that is used to sign transactions. This private key is stored securely within this snap.',
+ tags: ['EOA'],
+ developer: 'Metamask',
+ website: 'https://www.consensys.net/',
+ auditUrls: ['auditUrl1', 'auditUrl2'],
+ version: '1.0.0',
+ lastUpdated: 'April 20, 2023',
+};
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ push: jest.fn(),
+ }),
+ useParams: jest
+ .fn()
+ .mockReturnValue({ snapId: 'a51ea3a8-f1b0-4613-9440-b80e2236713b' }),
+}));
+
+const renderComponent = (state, props = {}) => {
+ const mockStore = configureMockStore([thunk])(state);
+ return renderWithProvider(
+ ,
+ mockStore,
+ `/add-snap-account/${snap.id}`,
+ );
+};
+describe('SnapAccountDetails', () => {
+ it('should take a snapshot', () => {
+ const { container } = renderComponent(mockState);
+ expect(container).toMatchSnapshot();
+ });
+
+ it('should render the snap details', async () => {
+ const { getAllByText, getByText } = renderComponent(mockState);
+
+ expect(getAllByText(snap.snapTitle).length).toBe(2);
+ expect(getByText(snap.snapSlug)).toBeInTheDocument();
+ expect(getByText(snap.snapDescription)).toBeInTheDocument();
+ snap.tags.forEach((tag) => {
+ expect(getByText(tag)).toBeInTheDocument();
+ });
+
+ expect(getByText(snap.developer)).toBeInTheDocument();
+ expect(getByText(snap.website)).toBeInTheDocument();
+
+ snap.auditUrls.forEach((auditUrl) => {
+ expect(getByText(auditUrl)).toBeInTheDocument();
+ });
+
+ expect(getByText(snap.version)).toBeInTheDocument();
+ expect(getByText(snap.lastUpdated)).toBeInTheDocument();
+ });
+
+ it('it should render configure if snap is already installed', async () => {
+ const mockStateForConfig = {
+ metamask: {
+ snapRegistryList: {
+ 'a51ea3a8-f1b0-4613-9440-b80e2236713b': {
+ id: 'a51ea3a8-f1b0-4613-9440-b80e2236713b',
+ snapId: 'npm:@metamask/snap-simple-keyring',
+ iconUrl: '',
+ snapTitle: 'Metamask Simple Keyring',
+ snapSlug: 'Secure your account with MetaMask Mobile',
+ snapDescription:
+ 'A simple private key is a randomly generated string of characters that is used to sign transactions. This private key is stored securely within this snap.',
+ tags: ['EOA'],
+ developer: 'Metamask',
+ website: 'https://www.consensys.net/',
+ auditUrls: ['auditUrl1', 'auditUrl2'],
+ version: '1.0.0',
+ lastUpdated: 'April 20, 2023',
+ },
+ },
+ snaps: {
+ 'npm:@metamask/snap-simple-keyring': {
+ id: 'npm:@metamask/snap-simple-keyring',
+ origin: 'npm:@metamask/snap-simple-keyring',
+ version: '1.0.0',
+ iconUrl: null,
+ initialPermissions: {
+ 'endowment:manageAccount': {},
+ },
+ manifest: {
+ description: 'An example keymanagement snap',
+ proposedName: 'Example Key Management Test Snap',
+ repository: {
+ type: 'git',
+ url: 'https://github.com/MetaMask/snap-simple-keyring.git',
+ },
+ source: {
+ location: {
+ npm: {
+ filePath: 'dist/bundle.js',
+ packageName: '@metamask/test-snap-account',
+ registry: 'https://registry.npmjs.org',
+ },
+ },
+ shasum: 'L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=',
+ },
+ version: '0.0.1',
+ },
+ versionHistory: [
+ {
+ date: 1680686075921,
+ origin: 'https://metamask.github.io',
+ version: '0.0.1',
+ },
+ ],
+ },
+ },
+ },
+ };
+ const { queryByText, getByText } = renderComponent(mockStateForConfig);
+ expect(queryByText(messages.snapInstall.message)).not.toBeInTheDocument();
+
+ expect(
+ queryByText(messages.snapUpdateAvailable.message),
+ ).not.toBeInTheDocument();
+
+ const configureButton = getByText(messages.snapConfigure.message);
+ expect(configureButton).toBeInTheDocument();
+ });
+
+ it('it should render install if snap is not installed', async () => {
+ const { queryByText, getByText, getAllByText } = renderComponent(mockState);
+ expect(
+ queryByText(messages.snapUpdateAvailable.message),
+ ).not.toBeInTheDocument();
+
+ expect(queryByText(messages.snapConfigure.message)).not.toBeInTheDocument();
+
+ const installButton = getByText(messages.snapInstall.message);
+ expect(installButton).toBeInTheDocument();
+
+ fireEvent.click(installButton);
+
+ // expect(mockInstallSnapFromSnapAccounts).toHaveBeenCalledTimes(1);
+ await waitFor(() => {
+ expect(
+ getAllByText(messages.configureSnapPopupInstallTitle.message).length,
+ ).toBe(2);
+ });
+ });
+
+ it('it should render update if snap update is available', async () => {
+ const mockStateForConfig = {
+ metamask: {
+ snapRegistryList: {
+ 'a51ea3a8-f1b0-4613-9440-b80e2236713b': {
+ id: 'a51ea3a8-f1b0-4613-9440-b80e2236713b',
+ snapId: 'npm:@metamask/snap-simple-keyring',
+ iconUrl: '',
+ snapTitle: 'MetaMask Simple Keyring',
+ snapSlug: 'Secure your account with MetaMask Mobile',
+ snapDescription:
+ 'A simple private key is a randomly generated string of characters that is used to sign transactions. This private key is stored securely within this snap.',
+ tags: ['EOA'],
+ developer: 'MetaMask',
+ website: 'https://www.consensys.net/',
+ auditUrls: ['auditUrl1', 'auditUrl2'],
+ version: '1.0.0',
+ lastUpdated: 'April 20, 2023',
+ },
+ },
+ snaps: {
+ 'npm:@metamask/snap-simple-keyring': {
+ id: 'npm:@metamask/snap-simple-keyring',
+ origin: 'npm:@metamask/snap-simple-keyring',
+ version: '0.0.0',
+ iconUrl: null,
+ initialPermissions: {
+ 'endowment:manageAccount': {},
+ },
+ manifest: {
+ description: 'An example keymanagement snap',
+ proposedName: 'Example Key Management Test Snap',
+ repository: {
+ type: 'git',
+ url: 'https://github.com/MetaMask/snap-simple-keyring.git',
+ },
+ source: {
+ location: {
+ npm: {
+ filePath: 'dist/bundle.js',
+ packageName: '@metamask/test-snap-account',
+ registry: 'https://registry.npmjs.org',
+ },
+ },
+ shasum: 'L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=',
+ },
+ version: '0.0.1',
+ },
+ versionHistory: [
+ {
+ date: 1680686075921,
+ origin: 'https://metamask.github.io',
+ version: '0.0.1',
+ },
+ ],
+ },
+ },
+ },
+ };
+ const { queryByText, getByText } = renderComponent(mockStateForConfig);
+ expect(queryByText(messages.snapInstall.message)).not.toBeInTheDocument();
+
+ expect(
+ queryByText(messages.snapUpdateAvailable.message),
+ ).toBeInTheDocument();
+
+ const configureButton = getByText(messages.snapConfigure.message);
+ expect(configureButton).toBeInTheDocument();
+
+ fireEvent.click(configureButton);
+
+ await waitFor(() => {
+ expect(
+ getByText(messages.configureSnapPopupTitle.message),
+ ).toBeInTheDocument();
+ });
+ });
+});
diff --git a/ui/pages/keyring-snaps/snap-account-detail-page/snap-account-detail-page.tsx b/ui/pages/keyring-snaps/snap-account-detail-page/snap-account-detail-page.tsx
new file mode 100644
index 000000000..711446ef4
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-account-detail-page/snap-account-detail-page.tsx
@@ -0,0 +1,147 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import { useHistory, useParams } from 'react-router-dom';
+import semver from 'semver';
+import {
+ BUTTON_VARIANT,
+ Box,
+ Button,
+ Tag,
+ Text,
+} from '../../../components/component-library';
+import {
+ BlockSize,
+ Display,
+ FlexDirection,
+ TextColor,
+ TextVariant,
+} from '../../../helpers/constants/design-system';
+import {
+ ADD_SNAP_ACCOUNT_ROUTE,
+ SNAPS_VIEW_ROUTE,
+} from '../../../helpers/constants/routes';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import { getSnapRegistry, getSnaps } from '../../../selectors';
+import { SnapDetails } from '../new-snap-account-page';
+import Detail from './detail';
+import { SnapDetailHeader } from './header';
+
+interface RouteParams {
+ snapId: string;
+}
+
+export default function SnapAccountDetailPage() {
+ const t = useI18nContext();
+ const history = useHistory();
+
+ const { snapId } = useParams();
+ const installedSnaps = useSelector(getSnaps);
+ const snapRegistryList: Record =
+ useSelector(getSnapRegistry);
+ const currentSnap = Object.values(snapRegistryList).find(
+ (snap) => snap.id === snapId,
+ );
+
+ if (!currentSnap) {
+ history.push(ADD_SNAP_ACCOUNT_ROUTE);
+ return null;
+ }
+
+ const isInstalled = Boolean(installedSnaps[currentSnap.snapId]);
+
+ const updateAvailable =
+ isInstalled &&
+ semver.gt(currentSnap.version, installedSnaps[currentSnap.snapId].version);
+
+ return (
+
+
+
+
+
+ {currentSnap.snapSlug}
+
+
+ {currentSnap.snapDescription}
+
+
+
+
+ {currentSnap.tags.map((tag, index) => {
+ return (
+
+ );
+ })}
+
+
+ {currentSnap.developer}
+
+ {currentSnap.website}
+
+ {currentSnap.auditUrls.map((auditLink, index) => {
+ return (
+
+
+ {auditLink}
+
+
+ );
+ })}
+
+
+ {currentSnap.version}
+
+
+ {currentSnap.lastUpdated}
+
+ {isInstalled && (
+
+
+ history.push(
+ `${SNAPS_VIEW_ROUTE}/${encodeURIComponent(
+ currentSnap.snapId,
+ )}`,
+ )
+ }
+ >
+ {t('snapDetailManageSnap')}
+
+
+ )}
+
+
+
+ );
+}
diff --git a/ui/pages/keyring-snaps/snap-card/__snapshots__/snap-card.test.tsx.snap b/ui/pages/keyring-snaps/snap-card/__snapshots__/snap-card.test.tsx.snap
new file mode 100644
index 000000000..01cf8ff49
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-card/__snapshots__/snap-card.test.tsx.snap
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SnapCard should render 1`] = `
+
+
+
+
+ Metamask Simple Keyring
+
+
+ Secure your account with MetaMask Mobile
+
+
+
+
+
+
+`;
diff --git a/ui/pages/keyring-snaps/snap-card/index.ts b/ui/pages/keyring-snaps/snap-card/index.ts
new file mode 100644
index 000000000..32ddd24ac
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-card/index.ts
@@ -0,0 +1 @@
+export { default } from './snap-card';
diff --git a/ui/pages/keyring-snaps/snap-card/snap-card.test.tsx b/ui/pages/keyring-snaps/snap-card/snap-card.test.tsx
new file mode 100644
index 000000000..9660d8cc0
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-card/snap-card.test.tsx
@@ -0,0 +1,75 @@
+import { fireEvent, waitFor } from '@testing-library/react';
+import React from 'react';
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import messages from '../../../../app/_locales/en/messages.json';
+import { renderWithProvider } from '../../../../test/lib/render-helpers';
+import SnapCard from './snap-card';
+
+const mockHistoryPush = jest.fn();
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ push: mockHistoryPush,
+ }),
+}));
+
+const snap = {
+ id: 'a51ea3a8-f1b0-4613-9440-b80e2236713b',
+ snapId: 'npm:@metamask/snap-simple-keyring',
+ iconUrl: '',
+ snapTitle: 'Metamask Simple Keyring',
+ snapSlug: 'Secure your account with MetaMask Mobile',
+ snapDescription:
+ 'A simple private key is a randomly generated string of characters that is used to sign transactions. This private key is stored securely within this snap.',
+ tags: ['EOA'],
+ developer: 'Metamask',
+ website: 'https://www.consensys.net/',
+ auditUrls: ['auditUrl1', 'auditUrl2'],
+ version: '1.0.0',
+ lastUpdated: 'April 20, 2023',
+};
+
+const renderComponent = (props) => {
+ const mockStore = configureMockStore([thunk])({});
+ return renderWithProvider( , mockStore);
+};
+describe('SnapCard', () => {
+ it('should render', () => {
+ const { container } = renderComponent(snap);
+ expect(container).toMatchSnapshot();
+ });
+
+ it('should show install button', async () => {
+ const { getByText } = renderComponent({ ...snap, isInstalled: false });
+ expect(getByText(snap.snapTitle)).toBeInTheDocument();
+ expect(getByText(snap.snapSlug)).toBeInTheDocument();
+ const installButton = getByText(messages.install.message);
+
+ expect(installButton).toBeInTheDocument();
+ fireEvent.click(installButton);
+ await waitFor(() => {
+ expect(mockHistoryPush).toHaveBeenCalledWith(
+ `/add-snap-account/${snap.id}`,
+ );
+ });
+ });
+
+ it('should show configure button', async () => {
+ const { getByText } = renderComponent({ ...snap, isInstalled: true });
+ expect(getByText(snap.snapTitle)).toBeInTheDocument();
+ expect(getByText(snap.snapSlug)).toBeInTheDocument();
+ const configureButton = getByText(messages.snapConfigure.message);
+
+ expect(configureButton).toBeInTheDocument();
+ fireEvent.click(configureButton);
+
+ // shows popover
+ await waitFor(() => {
+ expect(
+ getByText(messages.configureSnapPopupTitle.message),
+ ).toBeInTheDocument();
+ });
+ });
+});
diff --git a/ui/pages/keyring-snaps/snap-card/snap-card.tsx b/ui/pages/keyring-snaps/snap-card/snap-card.tsx
new file mode 100644
index 000000000..30277e626
--- /dev/null
+++ b/ui/pages/keyring-snaps/snap-card/snap-card.tsx
@@ -0,0 +1,139 @@
+import React, { useState } from 'react';
+import { useHistory } from 'react-router-dom';
+import ConfigureSnapPopup, {
+ ConfigureSnapPopupType,
+} from '../../../components/app/configure-snap-popup';
+import {
+ BUTTON_VARIANT,
+ Box,
+ Button,
+ Icon,
+ IconName,
+ Text,
+} from '../../../components/component-library';
+import {
+ AlignItems,
+ BackgroundColor,
+ BorderColor,
+ BorderRadius,
+ Color,
+ Display,
+ FlexDirection,
+ IconColor,
+ JustifyContent,
+ TextVariant,
+} from '../../../helpers/constants/design-system';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import { SnapCardProps } from '../new-snap-account-page/new-snap-account-page';
+
+export default function SnapCard({
+ iconUrl,
+ snapTitle,
+ snapSlug,
+ isInstalled,
+ website,
+ id,
+ onClickFunc,
+}: Pick<
+ SnapCardProps,
+ 'iconUrl' | 'snapTitle' | 'snapSlug' | 'isInstalled' | 'website' | 'id'
+> & { onClickFunc: () => void }) {
+ const t = useI18nContext();
+ const history = useHistory();
+ const [showConfigPopover, setShowConfigPopover] = useState(false);
+
+ return (
+
+
+
+ {iconUrl ? (
+
+ ) : (
+ // This is the fallback icon based on the first letter of the snap name.
+
+ {snapTitle ? snapTitle[0] : '?'}
+
+ )}
+
+ {isInstalled ? (
+ setShowConfigPopover(true)}
+ >
+ {t('snapConfigure')}
+
+ ) : (
+ {
+ history.push(`/add-snap-account/${id}`);
+ }}
+ >
+ {t('install')}
+
+ )}
+
+
+ {snapTitle}
+
+
+ {snapSlug}
+
+
+
+
+
+ setShowConfigPopover(false)}
+ link={website}
+ />
+
+ );
+}
diff --git a/ui/pages/pages.scss b/ui/pages/pages.scss
index ef23b24fc..f9795cb28 100644
--- a/ui/pages/pages.scss
+++ b/ui/pages/pages.scss
@@ -1,5 +1,6 @@
/** Please import your files in alphabetical order **/
@import 'add-nft/index';
+@import 'keyring-snaps/index';
@import 'import-token/index';
@import 'asset/asset';
@import 'confirm-import-token/index';
diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js
index d958cfd8f..3a29ff768 100644
--- a/ui/pages/routes/routes.component.js
+++ b/ui/pages/routes/routes.component.js
@@ -42,6 +42,9 @@ import TokenDetailsPage from '../token-details';
///: BEGIN:ONLY_INCLUDE_IN(snaps)
import Notifications from '../notifications';
///: END:ONLY_INCLUDE_IN
+///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+import AddSnapAccountPage from '../keyring-snaps/add-snap-account';
+///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(desktop)
import { registerOnDesktopDisconnect } from '../../hooks/desktopHooks';
import DesktopErrorPage from '../desktop-error';
@@ -90,6 +93,9 @@ import {
///: BEGIN:ONLY_INCLUDE_IN(snaps)
NOTIFICATIONS_ROUTE,
///: END:ONLY_INCLUDE_IN
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ ADD_SNAP_ACCOUNT_ROUTE,
+ ///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(desktop)
DESKTOP_PAIRING_ROUTE,
DESKTOP_ERROR_ROUTE,
@@ -335,6 +341,14 @@ export default class Routes extends Component {
///: END:ONLY_INCLUDE_IN
}
+ {
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+
+ ///: END:ONLY_INCLUDE_IN
+ }
{
- return async (dispatch: MetaMaskReduxDispatch) => {
- await submitRequestToBackground('removeSnap', [snapId]);
- await forceUpdateMetamaskState(dispatch);
+ return async (dispatch: MetaMaskReduxDispatch, getState) => {
+ dispatch(showLoadingIndication());
+ ///: END:ONLY_INCLUDE_IN
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ const subjects = getPermissionSubjects(getState()) as {
+ [k: string]: { permissions: Record };
+ };
+
+ const isAccountsSnap =
+ subjects[snapId]?.permissions?.snap_manageAccounts !== undefined;
+
+ try {
+ ///: END:ONLY_INCLUDE_IN
+ ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+ if (isAccountsSnap) {
+ const accounts = (await handleSnapRequest({
+ snapId,
+ origin: 'metamask',
+ handler: HandlerType.OnRpcRequest,
+ request: {
+ id: uuidV4(),
+ jsonrpc: '2.0',
+ method: 'keyring_listAccounts',
+ },
+ })) as unknown as any[];
+ for (const account of accounts) {
+ dispatch(removeAccount(account.address.toLowerCase()));
+ }
+ }
+ ///: END:ONLY_INCLUDE_IN
+ ///: BEGIN:ONLY_INCLUDE_IN(snaps)
+
+ await submitRequestToBackground('removeSnap', [snapId]);
+ await forceUpdateMetamaskState(dispatch);
+ } catch (error) {
+ dispatch(displayWarning(error));
+ throw error;
+ } finally {
+ dispatch(hideLoadingIndication());
+ }
};
}
@@ -1135,9 +1181,10 @@ export async function handleSnapRequest(args: {
origin: string;
handler: string;
request: {
+ id?: string;
jsonrpc: '2.0';
method: string;
- params: Record;
+ params?: Record;
};
}): Promise {
return submitRequestToBackground('handleSnapRequest', [args]);
@@ -4343,3 +4390,15 @@ export function setSnapsInstallPrivacyWarningShownStatus(shown: boolean) {
};
}
///: END:ONLY_INCLUDE_IN
+
+///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
+export async function setSnapsAddSnapAccountModalDismissed() {
+ await submitRequestToBackground('setSnapsAddSnapAccountModalDismissed', [
+ true,
+ ]);
+}
+
+export async function updateSnapRegistry() {
+ await submitRequestToBackground('updateSnapRegistry', []);
+}
+///: END:ONLY_INCLUDE_IN
diff --git a/yarn.lock b/yarn.lock
index 389c05c8c..8d44f4106 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4186,7 +4186,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/eth-sig-util@npm:5.0.2, @metamask/eth-sig-util@npm:^5.0.0, @metamask/eth-sig-util@npm:^5.0.1, @metamask/eth-sig-util@npm:^5.0.2":
+"@metamask/eth-sig-util@npm:5.0.2":
version: 5.0.2
resolution: "@metamask/eth-sig-util@npm:5.0.2"
dependencies:
@@ -4200,6 +4200,20 @@ __metadata:
languageName: node
linkType: hard
+"@metamask/eth-sig-util@npm:^5.0.0, @metamask/eth-sig-util@npm:^5.0.1, @metamask/eth-sig-util@npm:^5.0.2, @metamask/eth-sig-util@npm:^5.1.0":
+ version: 5.1.0
+ resolution: "@metamask/eth-sig-util@npm:5.1.0"
+ dependencies:
+ "@ethereumjs/util": ^8.0.6
+ bn.js: ^4.12.0
+ ethereum-cryptography: ^2.0.0
+ ethjs-util: ^0.1.6
+ tweetnacl: ^1.0.3
+ tweetnacl-util: ^0.15.1
+ checksum: c639e3bf91625faeb0230a6314f0b2d05e8f5e2989542d3e0eed1d21b7b286e1860f68629870fd7e568c1a599b3993c4210403fb4c84a625fb1e75ef676eab4f
+ languageName: node
+ linkType: hard
+
"@metamask/eth-simple-keyring@npm:^5.0.0":
version: 5.0.0
resolution: "@metamask/eth-simple-keyring@npm:5.0.0"
@@ -4212,6 +4226,22 @@ __metadata:
languageName: node
linkType: hard
+"@metamask/eth-snap-keyring@npm:^0.1.3":
+ version: 0.1.3
+ resolution: "@metamask/eth-snap-keyring@npm:0.1.3"
+ dependencies:
+ "@ethereumjs/tx": ^4.1.2
+ "@metamask/eth-sig-util": ^5.1.0
+ "@metamask/keyring-api": ^0.1.3
+ "@metamask/snaps-controllers": ^0.35.2-flask.1
+ "@metamask/utils": ^6.1.0
+ "@types/uuid": ^9.0.1
+ superstruct: ^1.0.3
+ uuid: ^9.0.0
+ checksum: f77cb75fcbd05024693488c42d244f78e7ee87f3f295ac95971182a78f1399428acc5f10aaf6d6ba08fa28cc3519d4e644226ac7cdf22e77a8659f945d9686ce
+ languageName: node
+ linkType: hard
+
"@metamask/eth-token-tracker@npm:^4.0.0":
version: 4.0.0
resolution: "@metamask/eth-token-tracker@npm:4.0.0"
@@ -4300,6 +4330,21 @@ __metadata:
languageName: node
linkType: hard
+"@metamask/keyring-api@npm:^0.1.3":
+ version: 0.1.3
+ resolution: "@metamask/keyring-api@npm:0.1.3"
+ dependencies:
+ "@metamask/providers": ^11.0.0
+ "@metamask/snaps-controllers": ^0.35.2-flask.1
+ "@metamask/snaps-utils": ^0.35.2-flask.1
+ "@metamask/utils": ^6.0.1
+ "@types/uuid": ^9.0.1
+ superstruct: ^1.0.3
+ uuid: ^9.0.0
+ checksum: 2307b5162dbb66d82f7d8ab8e9f1c3a0ef581b915702187b6b8fa5d8e8533838d0539d6f35853ef4f25096f13c9dbf4505fadff247b73f6d489d9c904015d21c
+ languageName: node
+ linkType: hard
+
"@metamask/logo@npm:^3.1.1":
version: 3.1.1
resolution: "@metamask/logo@npm:3.1.1"
@@ -4705,7 +4750,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/snaps-controllers-flask@npm:@metamask/snaps-controllers@0.35.2-flask.1":
+"@metamask/snaps-controllers-flask@npm:@metamask/snaps-controllers@0.35.2-flask.1, @metamask/snaps-controllers@npm:^0.35.2-flask.1":
version: 0.35.2-flask.1
resolution: "@metamask/snaps-controllers@npm:0.35.2-flask.1"
dependencies:
@@ -4952,7 +4997,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/utils@npm:^6.0.0, @metamask/utils@npm:^6.0.1":
+"@metamask/utils@npm:^6.0.0, @metamask/utils@npm:^6.0.1, @metamask/utils@npm:^6.1.0":
version: 6.1.0
resolution: "@metamask/utils@npm:6.1.0"
dependencies:
@@ -7925,17 +7970,17 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:*, @types/node@npm:>=13.7.0, @types/node@npm:^18.15.11":
- version: 18.16.12
- resolution: "@types/node@npm:18.16.12"
- checksum: 90b316c097a059534870bc8e358c7996d99e3bb4395c88a91b893b925ad34e32ff1177009ec6c16a6467266414dca64ec9613e9e6bb3f91b6de0ab629d3bb3b9
+"@types/node@npm:*, @types/node@npm:>=13.7.0":
+ version: 20.2.5
+ resolution: "@types/node@npm:20.2.5"
+ checksum: 38ce7c7e9d76880dc632f71d71e0d5914fcda9d5e9a7095d6c339abda55ca4affb0f2a882aeb29398f8e09d2c5151f0b6586c81c8ccdfe529c34b1ea3337425e
languageName: node
linkType: hard
"@types/node@npm:^16.0.0, @types/node@npm:^16.11.26, @types/node@npm:^16.7.10":
- version: 16.18.31
- resolution: "@types/node@npm:16.18.31"
- checksum: 1e0bbbdcfdb80ebb9c5544a58b9692964dca08175a9d2f787a1ed8c75c253d106d56cf7d94c5ba0b44e1627000d093d599f995aeb657f5edbf577e66565b017a
+ version: 16.18.34
+ resolution: "@types/node@npm:16.18.34"
+ checksum: 35c0ffe09687578d002ceb7e706d0ba450546aeb3d2716f28691f2af0063bd274dbde0f741d087ea217f2a8db413eb700d22dfb4f08a67986ff801423bd7be8d
languageName: node
linkType: hard
@@ -7946,6 +7991,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/node@npm:^18.15.11":
+ version: 18.16.16
+ resolution: "@types/node@npm:18.16.16"
+ checksum: 0efad726dd1e0bef71c392c708fc5d78c5b39c46b0ac5186fee74de4ccb1b2e847b3fa468da67d62812f56569da721b15bf31bdc795e6c69b56c73a45079ed2d
+ languageName: node
+ linkType: hard
+
"@types/normalize-package-data@npm:^2.4.0":
version: 2.4.0
resolution: "@types/normalize-package-data@npm:2.4.0"
@@ -8229,10 +8281,10 @@ __metadata:
languageName: node
linkType: hard
-"@types/uuid@npm:^9.0.0":
- version: 9.0.1
- resolution: "@types/uuid@npm:9.0.1"
- checksum: c472b8a77cbeded4bc529220b8611afa39bd64677f507838f8083d8aac8033b1f88cb9ddaa2f8589e0dcd2317291d0f6e1379f82d5ceebd6f74f3b4825288e00
+"@types/uuid@npm:^9.0.0, @types/uuid@npm:^9.0.1":
+ version: 9.0.2
+ resolution: "@types/uuid@npm:9.0.2"
+ checksum: 1754bcf3444e1e3aeadd6e774fc328eb53bc956665e2e8fb6ec127aa8e1f43d9a224c3d22a9a6233dca8dd81a12dc7fed4d84b8876dd5ec82d40f574f7ff8b68
languageName: node
linkType: hard
@@ -10804,7 +10856,7 @@ __metadata:
languageName: node
linkType: hard
-"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.1.1, bn.js@npm:^4.11.0, bn.js@npm:^4.11.7, bn.js@npm:^4.11.8, bn.js@npm:^4.11.9":
+"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.1.1, bn.js@npm:^4.11.0, bn.js@npm:^4.11.7, bn.js@npm:^4.11.8, bn.js@npm:^4.11.9, bn.js@npm:^4.12.0":
version: 4.12.0
resolution: "bn.js@npm:4.12.0"
checksum: 39afb4f15f4ea537b55eaf1446c896af28ac948fdcf47171961475724d1bb65118cca49fa6e3d67706e4790955ec0e74de584e45c8f1ef89f46c812bee5b5a12
@@ -11745,9 +11797,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001426, caniuse-lite@npm:^1.0.30001449":
- version: 1.0.30001481
- resolution: "caniuse-lite@npm:1.0.30001481"
- checksum: 8200a043c191b4fd4fe0beda37a58fd61869c895ab93f87bdd0420e5927453f48434d716ce9da8552ff6c3ecc4dcd1366354cda3a134f3cc844af741574a7cab
+ version: 1.0.30001497
+ resolution: "caniuse-lite@npm:1.0.30001497"
+ checksum: 6721120f9a588c442a81cf32f911b4e97a88cb129c27bd2cb0fce6447ad058baa12affa1ee09c517f9e088c7ce74964154d032b6631f66d75dd37c6bc59a67f6
languageName: node
linkType: hard
@@ -24447,6 +24499,7 @@ __metadata:
"@metamask/eth-json-rpc-middleware": ^11.0.0
"@metamask/eth-keyring-controller": ^10.0.1
"@metamask/eth-ledger-bridge-keyring": ^0.15.0
+ "@metamask/eth-snap-keyring": ^0.1.3
"@metamask/eth-token-tracker": ^4.0.0
"@metamask/eth-trezor-keyring": ^1.0.0
"@metamask/etherscan-link": ^2.2.0