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')} + + + + + + ); +} + +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 ( + <> + + {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')} + + + + + + ); +} 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`] = ` +
+
+
+
+ + +

+ Metamask Simple Keyring +

+
+
+
+

+ Metamask Simple Keyring +

+
+
+ +
+
+
+
+ +
+
+

+

+ + +

+ By MetaMask +

+
+

+
+
+

+

+ + +

+ Audited +

+
+

+
+
+
+
+
+

+ 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. +

+
+
+
+

+ Tags +

+
+

+ EOA +

+
+
+
+

+ Developer +

+

+ Metamask +

+
+
+

+ Website +

+ https://www.consensys.net/ +
+
+

+ Audit +

+

+ + auditUrl1 + +

+

+ + auditUrl2 + +

+
+
+

+ 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 ( + <> + + + + + {snapTitle} + + + + + {snapTitle} + + {isInstalled && ( + + )} + + + {isInstalled && updateAvailable && ( + + )} + {isInstalled && ( + + )} + {!isInstalled && ( + + )} + + + + + + + {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 ( + + + + ); + })} + + + {currentSnap.version} + + + {currentSnap.lastUpdated} + + {isInstalled && ( + + + + )} + + + + ); +} 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`] = ` +
+
+
+
+
+

+ M +

+
+
+ +
+

+ 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 ? ( + + ) : ( + + )} + + + {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