diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 546f26ec89bd..4eb64441690d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,7 +9,7 @@ * @MetaMask/extension-devs **/snaps/** @MetaMask/snaps-devs -**/flask/** @MetaMask/snaps-devs +**/flask/** @MetaMask/extension-devs @MetaMask/snaps-devs development/ @MetaMask/extension-devs @kumavis lavamoat/ @MetaMask/extension-devs @MetaMask/supply-chain @MetaMask/snaps-devs diff --git a/.storybook/test-data.js b/.storybook/test-data.js index 353064992c42..b78ae04c0620 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -1062,12 +1062,6 @@ const state = { unapprovedEncryptionPublicKeyMsgCount: 0, unapprovedTypedMessages: {}, unapprovedTypedMessagesCount: 0, - keyringTypes: [ - KeyringType.imported, - KeyringType.hdKeyTree, - KeyringType.trezor, - KeyringType.ledger, - ], keyrings: [ { type: KeyringType.hdKeyTree, diff --git a/.yarn/patches/@metamask-keyring-controller-npm-8.0.0-806630ae4e.patch b/.yarn/patches/@metamask-keyring-controller-npm-8.0.0-806630ae4e.patch deleted file mode 100644 index 331eea84d518..000000000000 --- a/.yarn/patches/@metamask-keyring-controller-npm-8.0.0-806630ae4e.patch +++ /dev/null @@ -1,40 +0,0 @@ -diff --git a/dist/KeyringController.d.ts b/dist/KeyringController.d.ts -index 44c6427ec774475b87f60995738f736611875194..d359269ad413a0a626d06e44a84eb0cbbdb8c7b0 100644 ---- a/dist/KeyringController.d.ts -+++ b/dist/KeyringController.d.ts -@@ -5,6 +5,7 @@ import { BaseControllerV2 } from '@metamask/base-controller'; - import type { PersonalMessageParams, TypedMessageParams } from '@metamask/message-manager'; - import type { PreferencesController } from '@metamask/preferences-controller'; - import type { Eip1024EncryptedData, Hex, Keyring, Json } from '@metamask/utils'; -+import type { KeyringController as EthKeyringController } from '@metamask/eth-keyring-controller'; - import type { Patch } from 'immer'; - declare const name = "KeyringController"; - /** -@@ -171,6 +172,10 @@ export declare class KeyringController extends BaseControllerV2 { -+ // Expecting reject error but throwing manually rather than waiting -+ }); - __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_cancelAbstractMessage).call(this, messageManager, messageId); -- throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the request.'); -+ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest(`MetaMask ${messageName} Signature: User denied message signature.`); - } - yield signMessage(messageParamsWithId, signingOpts); - const signatureResult = yield signaturePromise; -@@ -305,7 +308,7 @@ _SignatureController_keyringController = new WeakMap(), _SignatureController_isE - return __awaiter(this, void 0, void 0, function* () { - return yield __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_signAbstractMessage).call(this, __classPrivateFieldGet(this, _SignatureController_personalMessageManager, "f"), controller_utils_1.ApprovalType.PersonalSign, msgParams, (cleanMsgParams) => __awaiter(this, void 0, void 0, function* () { return yield __classPrivateFieldGet(this, _SignatureController_keyringController, "f").signPersonalMessage(cleanMsgParams); })); - }); --}, _SignatureController_signTypedMessage = function _SignatureController_signTypedMessage(msgParams, -+}, _SignatureController_signTypedMessage = function _SignatureController_signTypedMessage(msgParams, - /* istanbul ignore next */ - opts = { parseJsonData: true }) { - return __awaiter(this, void 0, void 0, function* () { diff --git a/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch b/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch new file mode 100644 index 000000000000..128ad3f80189 --- /dev/null +++ b/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch @@ -0,0 +1,17 @@ +diff --git a/dist/SignatureController.js b/dist/SignatureController.js +index 46d4b4d0553f86d368d30b7e90a9dc2e03d26ef9..e7063a3753bc3821e661c11132e33304b4fce416 100644 +--- a/dist/SignatureController.js ++++ b/dist/SignatureController.js +@@ -280,8 +280,11 @@ _SignatureController_isEthSignEnabled = new WeakMap(), _SignatureController_getA + resultCallbacks = acceptResult.resultCallbacks; + } + catch (_a) { ++ signaturePromise.catch(() => { ++ // Expecting reject error but throwing manually rather than waiting ++ }); + __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_cancelAbstractMessage).call(this, messageManager, messageId); +- throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the request.'); ++ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest(`MetaMask ${messageName} Signature: User denied message signature.`); + } + yield signMessage(messageParamsWithId, signingOpts); + const signatureResult = yield signaturePromise; diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 6b4803e13345..213d8876df39 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "Backup-Erinnerung zur geheimen Wiederherstellungsphrase verwerfen" }, - "displayNftMedia": { - "message": "NFT-Medien anzeigen" - }, "domain": { "message": "Domain" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " In den Einstellungen aktivieren." }, + "enableOpenSeaAPI": { + "message": "OpenSea API aktivieren" + }, + "enableOpenSeaAPIDescription": { + "message": "Verwenden Sie die OpenSea's API, um NFT-Daten abzurufen. Die NFT-Auto-Erkennung basiert auf der OpenSea's API und wird nicht verfügbar sein, wenn diese deaktiviert ist." + }, "enableSmartSwaps": { "message": "Smart Swaps aktivieren" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "Experimentell" }, - "exploreMetaMaskSnaps": { - "message": "MetaMask Snaps erforschen" - }, "extendWalletWithSnaps": { - "message": "Erweitern Sie Ihr Wallet-Erlebnis." + "message": "Erweitern Sie Ihr Wallet-Erlebnis.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "Externe Erweiterung" diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 4dc7e5681b04..b5f4828434af 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "Απορρίψτε την υπενθύμιση δημιουργίας αντιγράφων ασφαλείας της Μυστικής Φράσης Ανάκτησης" }, - "displayNftMedia": { - "message": "Εμφάνιση των μέσων NFT" - }, "domain": { "message": "Τομέας" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": "Ενεργοποιήστε το από τις Ρυθμίσεις." }, + "enableOpenSeaAPI": { + "message": "Ενεργοποίηση OpenSea API" + }, + "enableOpenSeaAPIDescription": { + "message": "Χρησιμοποιήστε το API OpenSea για λήψη δεδομένων NFT. Η αυτόματη ανίχνευση NFT βασίζεται στο API του OpenSea, και δεν θα είναι διαθέσιμη όταν αυτό είναι απενεργοποιημένο." + }, "enableSmartSwaps": { "message": "Ενεργοποίηση των έξυπνων ανταλλαγών" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "Πειραματικά" }, - "exploreMetaMaskSnaps": { - "message": "Εξερευνήστε το MetaMask Snaps" - }, "extendWalletWithSnaps": { - "message": "Προσαρμόστε την εμπειρία του πορτοφολιού σας." + "message": "Προσαρμόστε την εμπειρία του πορτοφολιού σας.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "Εξωτερική επέκταση" diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 35b90c86f90f..8d3c090ca1c5 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -153,6 +153,9 @@ "accountSelectionRequired": { "message": "You need to select an account!" }, + "accountsConnected": { + "message": "Accounts connected" + }, "active": { "message": "Active" }, @@ -841,6 +844,9 @@ "message": "$1 is not connected to any sites.", "description": "$1 is the account name" }, + "connectedSnapAndNoAccountDescription": { + "message": "MetaMask is connected to this site, but no accounts are connected yet" + }, "connecting": { "message": "Connecting..." }, @@ -1311,6 +1317,10 @@ "disconnectThisAccount": { "message": "Disconnect this account" }, + "discoverSnaps": { + "message": "Discover Snaps", + "description": "Text that links to the Snaps website. Displayed in a banner on Snaps list page in settings." + }, "dismiss": { "message": "Dismiss" }, @@ -1320,12 +1330,6 @@ "dismissReminderField": { "message": "Dismiss Secret Recovery Phrase backup reminder" }, - "displayNftMedia": { - "message": "Display NFT media" - }, - "displayNftMediaDesc": { - "message": "Displaying NFT media and data exposes your IP address to OpenSea or other third parties. NFT autodetection relies on this feature, and won't be available when it is turned off. If NFT media is fully located on IPFS, it can still be displayed even when this feature is turned off." - }, "domain": { "message": "Domain" }, @@ -1451,6 +1455,12 @@ "enableFromSettings": { "message": " Enable it from Settings." }, + "enableOpenSeaAPI": { + "message": "Enable OpenSea API" + }, + "enableOpenSeaAPIDescription": { + "message": "Use OpenSea's API to fetch NFT data. NFT auto-detection relies on OpenSea's API, and will not be available when this is turned off." + }, "enableSmartSwaps": { "message": "Enable smart swaps" }, @@ -1593,11 +1603,9 @@ "experimental": { "message": "Experimental" }, - "exploreMetaMaskSnaps": { - "message": "Explore MetaMask Snaps" - }, "extendWalletWithSnaps": { - "message": "Customize your wallet experience." + "message": "Explore community-built Snaps to customize your web3 experience", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "External extension" @@ -2544,7 +2552,7 @@ "message": "New contract" }, "newNFTDetectedInImportNFTsMessage": { - "message": "To see your NFT, turn on Display NFT media in $1.", + "message": "To use Opensea see your NFT, turn on 'Enable OpenSea API' in $1.", "description": "$1 is used for newNFTDetectedInImportNFTsMessageStrongText" }, "newNFTDetectedInImportNFTsMessageStrongText": { @@ -2586,6 +2594,9 @@ "message": "This token is an NFT. Add on the $1", "description": "$1 is a clickable link with text defined by the 'importNFTPage' key" }, + "nftAlreadyAdded": { + "message": "NFT has already been added." + }, "nftDisclaimer": { "message": "Disclaimer: MetaMask pulls the media file from the source url. This url sometimes is changed by the marketplace the NFT was minted on." }, @@ -3877,8 +3888,8 @@ "selectAnAction": { "message": "Select an action" }, - "selectDisplayMediaPrivacyPreference": { - "message": "Turn on Display NFT media" + "selectEnableOpenseaAPIPrivacyPreference": { + "message": "Turn on Enable Opensea API" }, "selectHdPath": { "message": "Select HD path" @@ -4188,6 +4199,9 @@ "snaps": { "message": "Snaps" }, + "snapsConnected": { + "message": "Snaps connected" + }, "snapsInsightLoading": { "message": "Loading transaction insight..." }, @@ -4209,6 +4223,9 @@ "message": "Consensys has no access to information you share with Third Party Services.", "description": "Third part of a message in popup modal displayed when installing a snap for the first time." }, + "snapsSettings": { + "message": "Snap settings" + }, "snapsSettingsDescription": { "message": "Manage your Snaps" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 04d79421b13f..7f9b105957b4 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "Ignorar el recordatorio de respaldo de la frase de recuperación" }, - "displayNftMedia": { - "message": "Mostrar medios NFT" - }, "domain": { "message": "Dominio" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " Actívela en Configuración." }, + "enableOpenSeaAPI": { + "message": "Habilite el API de OpenSea" + }, + "enableOpenSeaAPIDescription": { + "message": "Utilice la API de OpenSea para obtener los datos de NFT. La autodetección de NFT depende de la API de OpenSea y no estará disponible si la API está desactivada." + }, "enableSmartSwaps": { "message": "Habilitar intercambios inteligentes" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "Experimental" }, - "exploreMetaMaskSnaps": { - "message": "Explore MetaMask Snaps" - }, "extendWalletWithSnaps": { - "message": "Amplíe la experiencia de uso de su monedero." + "message": "Amplíe la experiencia de uso de su monedero.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "Extensión externa" diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 1323503dd935..77d571d76648 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -722,6 +722,12 @@ "enableFromSettings": { "message": " Actívela en Configuración." }, + "enableOpenSeaAPI": { + "message": "Activar API de OpenSea" + }, + "enableOpenSeaAPIDescription": { + "message": "Utilice la API de OpenSea para obtener los datos de NFT. La autodetección de NFT depende de la API de OpenSea y no estará disponible si la API está desactivada." + }, "enableToken": { "message": "activar $1", "description": "$1 is a token symbol, e.g. ETH" diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index a094c9c7d283..33cca3bfc540 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "Annuler le rappel de sauvegarde de la phrase secrète de récupération" }, - "displayNftMedia": { - "message": "Afficher les médias NFT" - }, "domain": { "message": "Domaine" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " Activez-la depuis les Paramètres." }, + "enableOpenSeaAPI": { + "message": "Activer l’API OpenSea" + }, + "enableOpenSeaAPIDescription": { + "message": "Utilisez l’API OpenSea pour récupérer les données de NFT. La détection automatique de NFT repose sur l’API OpenSea et ne sera pas disponible si elle est désactivée." + }, "enableSmartSwaps": { "message": "Activer les contrats de swap intelligents" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "Expérimental" }, - "exploreMetaMaskSnaps": { - "message": "Explorer les Snaps MetaMask" - }, "extendWalletWithSnaps": { - "message": "Personnalisez votre expérience de portefeuille." + "message": "Personnalisez votre expérience de portefeuille.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "Extension externe" diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index ad8d94663ad8..658e6bb42e33 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "सीक्रेट रिकवरी फ्रेज़ बैकअप रिमाइंडर खारिज करें" }, - "displayNftMedia": { - "message": "NFT मीडिया को दिखाएं" - }, "domain": { "message": "डोमेन" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " इसे सेटिंग्स से इनेबल करें।" }, + "enableOpenSeaAPI": { + "message": "OpenSea API इनेबल करें" + }, + "enableOpenSeaAPIDescription": { + "message": "NFT डेटा लाने के लिए OpenSea के API का उपयोग करें। NFT ऑटो-डिटेक्शन OpenSea के API पर निर्भर करता है, और इसके बंद होने पर उपलब्ध नहीं होगा।" + }, "enableSmartSwaps": { "message": "स्मार्ट स्वैप इनेबल करें" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "एक्सपेरिमेंटल" }, - "exploreMetaMaskSnaps": { - "message": "MetaMask Snaps को एक्सप्लोर करें" - }, "extendWalletWithSnaps": { - "message": "वॉलेट एक्सपीरियंस को कस्टमाइज़ करें।" + "message": "वॉलेट एक्सपीरियंस को कस्टमाइज़ करें।", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "बाहरी एक्स्टेन्शन" diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index cc71f52f8b4a..4c4d238492bf 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "Lewatkan pengingat pencadangan Frasa Pemulihan Rahasia" }, - "displayNftMedia": { - "message": "Tampilkan media NFT" - }, "domain": { "message": "Domain" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " Aktifkan dari Pengaturan." }, + "enableOpenSeaAPI": { + "message": "Aktifkan API OpenSea" + }, + "enableOpenSeaAPIDescription": { + "message": "Gunakan API OpenSea untuk mengambil data NFT. Deteksi otomatis NFT bergantung pada API OpenSea, dan tidak akan tersedia saat API ditutup." + }, "enableSmartSwaps": { "message": "Aktifkan pertukaran cerdas" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "Eksperimental" }, - "exploreMetaMaskSnaps": { - "message": "Jelajahi MetaMask Snaps" - }, "extendWalletWithSnaps": { - "message": "Perluas pengalaman dompet." + "message": "Perluas pengalaman dompet.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "Ekstensi eksternal" diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index a6e7a17ae5b6..267ab31b4fdb 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "シークレットリカバリーフレーズのバックアップリマインダーを解除" }, - "displayNftMedia": { - "message": "NFTメディアの表示" - }, "domain": { "message": "ドメイン" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " 設定で有効にします。" }, + "enableOpenSeaAPI": { + "message": "OpenSea APIを有効にする" + }, + "enableOpenSeaAPIDescription": { + "message": "OpenSea APIを使用してNFTデータを取得します。NFT自動検出はOpenSea APIを使用するため、この設定をオフにすると利用できなくなります。" + }, "enableSmartSwaps": { "message": "スマートスワップを有効にする" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "試験運用" }, - "exploreMetaMaskSnaps": { - "message": "MetaMask Snapsを閲覧" - }, "extendWalletWithSnaps": { - "message": "ウォレットのユーザー体験をカスタマイズします。" + "message": "ウォレットのユーザー体験をカスタマイズします。", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "外部拡張機能" diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index ffcc2ff0a51c..6e6e5f1af2ce 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "비밀복구구문 백업 알림 해지" }, - "displayNftMedia": { - "message": "NFT 미디어 표시" - }, "domain": { "message": "도메인" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " 설정에서 이 기능을 활성화합니다." }, + "enableOpenSeaAPI": { + "message": "OpenSea API 활성화" + }, + "enableOpenSeaAPIDescription": { + "message": "OpenSea의 API를 사용하여 NFT 데이터를 가져옵니다. NFT 자동 감지는 OpenSea의 API에 의존하며 이 API가 꺼져 있으면 사용할 수 없습니다." + }, "enableSmartSwaps": { "message": "스마트 스왑 활성화" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "실험적" }, - "exploreMetaMaskSnaps": { - "message": "MetaMask 스냅 탐색" - }, "extendWalletWithSnaps": { - "message": "지갑 경험을 개인 맞춤하세요." + "message": "지갑 경험을 개인 맞춤하세요.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "외부 확장" diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 0bae792b6d03..908dcf4cf6c9 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "Descartar o lembrete de backup da Frase de Recuperação Secreta" }, - "displayNftMedia": { - "message": "Exibir mídias de NFTs" - }, "domain": { "message": "Domínio" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " Ative nas Configurações." }, + "enableOpenSeaAPI": { + "message": "Habilitar API do OpenSea" + }, + "enableOpenSeaAPIDescription": { + "message": "Use a API OpenSea para recuperar dados de NFTs. A detecção automática de NFTs depende da API OpenSea e não estará disponível quando essa opção estiver desativada." + }, "enableSmartSwaps": { "message": "Ativar trocas inteligentes" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "Experimental" }, - "exploreMetaMaskSnaps": { - "message": "Explorar os snaps da MetaMask" - }, "extendWalletWithSnaps": { - "message": "Personalize sua experiência com a carteira." + "message": "Personalize sua experiência com a carteira.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "Extensão externa" diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 7ce8d6227003..f8db416d5650 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -722,6 +722,12 @@ "enableFromSettings": { "message": " Ative nas Configurações." }, + "enableOpenSeaAPI": { + "message": "Ativar a API OpenSea" + }, + "enableOpenSeaAPIDescription": { + "message": "Use a API OpenSea para recuperar dados de NFTs. A detecção automática de NFTs depende da API OpenSea e não estará disponível quando essa opção estiver desativada." + }, "enableToken": { "message": "ativar $1", "description": "$1 is a token symbol, e.g. ETH" diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index f1183d777d62..a43db33c1199 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "Отклонить напоминание о необходимости создать резервную копию секретной фразы для восстановления" }, - "displayNftMedia": { - "message": "Показать NFT-носитель" - }, "domain": { "message": "Домен" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " Включите его в Настройках." }, + "enableOpenSeaAPI": { + "message": "Включить API OpenSea" + }, + "enableOpenSeaAPIDescription": { + "message": "Используйте API OpenSea для получения данных NFT. Для автоматического обнаружения NFT используется API OpenSea, и такое обнаружение будет недоступно, если этот API отключен." + }, "enableSmartSwaps": { "message": "Включить смарт-свопы" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "Экспериментальные" }, - "exploreMetaMaskSnaps": { - "message": "Обзор MetaMask Snaps" - }, "extendWalletWithSnaps": { - "message": "Настройте кошелек под себя." + "message": "Настройте кошелек под себя.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "Внешнее расширение" diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 6ce8f1cd43a6..014e1a06c385 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "I-dismiss ang back up na paalala ng Lihim na Parirala sa Pagbawi" }, - "displayNftMedia": { - "message": "Ipakita ang NFT media" - }, "domain": { "message": "Domain" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " Paganahin ito mula sa Mga Setting." }, + "enableOpenSeaAPI": { + "message": "Paganahin sa OpenSea API" + }, + "enableOpenSeaAPIDescription": { + "message": "Gamitin ang API ng Opensea upang kunin ang NFT data. ang NFT auto-detection ay umaasa sa API ng OpenSea, at hindi magiging available kapag ito ay isinara." + }, "enableSmartSwaps": { "message": "Paganahin ang mga smart swap" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "Eksperimental" }, - "exploreMetaMaskSnaps": { - "message": "Galugarin ang MetaMask Snaps" - }, "extendWalletWithSnaps": { - "message": "I-customize ang iyong karanasan sa wallet." + "message": "I-customize ang iyong karanasan sa wallet.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "External Extension" diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 8dbff6bdd7b5..1ceb3c54d670 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "Gizli Kurtarma İfadesi yedekleme hatırlatma uyarısını yoksay" }, - "displayNftMedia": { - "message": "NFT medyasını göster" - }, "domain": { "message": "Alan" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " Ayarlardan etkinleştir." }, + "enableOpenSeaAPI": { + "message": "OpenSea API'yi etkinleştir" + }, + "enableOpenSeaAPIDescription": { + "message": "NFT verilerini almak için OpenSea API'sini kullanın. NFT otomatik algılama OpenSea API'ye dayalıdır ve bu kapatılırsa mevcut olmayacaktır." + }, "enableSmartSwaps": { "message": "Akıllı swap'ları etkinleştirin" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "Deneysel" }, - "exploreMetaMaskSnaps": { - "message": "MetaMask Snaplerini keşfedin" - }, "extendWalletWithSnaps": { - "message": "Cüzdan deneyimini kişiselleştirin." + "message": "Cüzdan deneyimini kişiselleştirin.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "Harici uzantı" diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 218e2d577d98..d17b243151f6 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "Tắt lời nhắc sao lưu Cụm từ khôi phục bí mật" }, - "displayNftMedia": { - "message": "Hiển thị phương tiện NFT" - }, "domain": { "message": "Tên miền" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " Bật trong Cài Đặt." }, + "enableOpenSeaAPI": { + "message": "Bật API OpenSea" + }, + "enableOpenSeaAPIDescription": { + "message": "Sử dụng API của OpenSea để tìm nạp dữ liệu NFT. Tính năng tự động phát hiện NFT dựa vào API của OpenSea và sẽ không khả dụng nếu tính năng này bị tắt." + }, "enableSmartSwaps": { "message": "Kích hoạt hoán đổi thông minh" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "Thử nghiệm" }, - "exploreMetaMaskSnaps": { - "message": "Khám phá MetaMask Snap" - }, "extendWalletWithSnaps": { - "message": "Tuỳ biến trải nghiệm sử dụng ví." + "message": "Tuỳ biến trải nghiệm sử dụng ví.", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "Tiện ích bên ngoài" diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index e08602da7788..d07fc2f81d7b 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -1277,9 +1277,6 @@ "dismissReminderField": { "message": "关闭账户私钥助记词备份提醒" }, - "displayNftMedia": { - "message": "显示NFT媒体" - }, "domain": { "message": "域" }, @@ -1405,6 +1402,12 @@ "enableFromSettings": { "message": " 从设置中启用它。" }, + "enableOpenSeaAPI": { + "message": "启用 OpenSea API" + }, + "enableOpenSeaAPIDescription": { + "message": "使用 OpenSea 的 API 获取 NFT 数据。NFT 自动检测依赖于 OpenSea 的 API,在后者关闭时自动检测将不可用。" + }, "enableSmartSwaps": { "message": "启用智能兑换" }, @@ -1535,11 +1538,9 @@ "experimental": { "message": "实验性" }, - "exploreMetaMaskSnaps": { - "message": "探索 MetaMask Snaps" - }, "extendWalletWithSnaps": { - "message": "扩展钱包体验。" + "message": "扩展钱包体验。", + "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, "externalExtension": { "message": "外部扩展程序" diff --git a/app/images/blockaid-security-provider.png b/app/images/blockaid-security-provider.png deleted file mode 100644 index 34f9ac1a476d..000000000000 Binary files a/app/images/blockaid-security-provider.png and /dev/null differ diff --git a/app/images/blockaid-security-provider.svg b/app/images/blockaid-security-provider.svg new file mode 100644 index 000000000000..5646ed66987c --- /dev/null +++ b/app/images/blockaid-security-provider.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/scripts/background.js b/app/scripts/background.js index 57f5ab43c4c1..4ae4ecbbce87 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -248,7 +248,6 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { * @property {object} unapprovedTypedMsgs - An object of messages pending approval, mapping a unique ID to the options. * @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs. * @property {number} pendingApprovalCount - The number of pending request in the approval controller. - * @property {string[]} keyringTypes - An array of unique keyring identifying strings, representing available strategies for creating accounts. * @property {Keyring[]} keyrings - An array of keyring descriptions, summarizing the accounts that are available for use, and what keyrings they belong to. * @property {string} selectedAddress - A lower case hex string of the currently selected address. * @property {string} currentCurrency - A string identifying the user's preferred display currency, for use in showing conversion rates. diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 564ec1dff2c3..a72e64219df4 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -11,6 +11,7 @@ import { import { ObservableStore } from '@metamask/obs-store'; import { bufferToHex, keccak } from 'ethereumjs-util'; import { v4 as uuidv4 } from 'uuid'; +import { NameType } from '@metamask/name-controller'; import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app'; import { METAMETRICS_ANONYMOUS_ID, @@ -800,6 +801,10 @@ export default class MetaMetricsController { ///: END:ONLY_INCLUDE_IN [MetaMetricsUserTrait.SecurityProviders]: metamaskState.transactionSecurityCheckEnabled ? ['opensea'] : [], + ///: BEGIN:ONLY_INCLUDE_IN(petnames) + [MetaMetricsUserTrait.PetnameAddressCount]: + this._getPetnameAddressCount(metamaskState), + ///: END:ONLY_INCLUDE_IN }; if (!previousUserTraits) { @@ -1076,4 +1081,29 @@ export default class MetaMetricsController { }; this.segment[eventType](modifiedPayload, modifiedCallback); } + + /** + * Returns the total number of Ethereum addresses with saved petnames, + * including all chain ID variations. + * + * @param {object} metamaskState + * @returns {number} + */ + _getPetnameAddressCount(metamaskState) { + const addressNames = metamaskState.names?.[NameType.ETHEREUM_ADDRESS] ?? {}; + + return Object.keys(addressNames).reduce((totalCount, address) => { + const addressEntry = addressNames[address]; + + const addressNameCount = Object.keys(addressEntry).reduce( + (count, chainId) => { + const hasName = Boolean(addressEntry[chainId].name?.length); + return count + (hasName ? 1 : 0); + }, + 0, + ); + + return totalCount + addressNameCount; + }, 0); + } } diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 0e060bed1e72..3532e3b15de3 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -1,6 +1,7 @@ import { strict as assert } from 'assert'; import sinon from 'sinon'; import { toHex } from '@metamask/controller-utils'; +import { NameType } from '@metamask/name-controller'; import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app'; import { createSegmentMock } from '../lib/segment'; import { @@ -1028,6 +1029,38 @@ describe('MetaMetricsController', function () { useTokenDetection: true, desktopEnabled: false, security_providers: [], + names: { + [NameType.ETHEREUM_ADDRESS]: { + '0x123': { + '0x1': { + name: 'Test 1', + }, + '0x2': { + name: 'Test 2', + }, + '0x3': { + name: null, + }, + }, + '0x456': { + '0x1': { + name: 'Test 3', + }, + }, + '0x789': { + '0x1': { + name: null, + }, + }, + }, + otherType: { + otherValue: { + otherVariation: { + name: 'Test 4', + }, + }, + }, + }, }); assert.deepEqual(traits, { @@ -1056,6 +1089,9 @@ describe('MetaMetricsController', function () { [MetaMetricsUserTrait.MmiAccountAddress]: null, [MetaMetricsUserTrait.MmiIsCustodian]: false, ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(petnames) + [MetaMetricsUserTrait.PetnameAddressCount]: 3, + ///: END:ONLY_INCLUDE_IN }); }); diff --git a/app/scripts/lib/SnapsNameProvider.test.ts b/app/scripts/lib/SnapsNameProvider.test.ts index 5ac250c9f494..cef4356e379c 100644 --- a/app/scripts/lib/SnapsNameProvider.test.ts +++ b/app/scripts/lib/SnapsNameProvider.test.ts @@ -139,7 +139,7 @@ describe('SnapsNameProvider', () => { const response = await provider.getProposedNames({ value: VALUE_MOCK, type: NameType.ETHEREUM_ADDRESS, - chainId: CHAIN_ID_MOCK, + variation: CHAIN_ID_MOCK, }); expect(response).toStrictEqual({ @@ -193,7 +193,7 @@ describe('SnapsNameProvider', () => { const response = await provider.getProposedNames({ value: VALUE_MOCK, type: NameType.ETHEREUM_ADDRESS, - chainId: CHAIN_ID_MOCK, + variation: CHAIN_ID_MOCK, }); expect(response).toStrictEqual({ @@ -224,7 +224,7 @@ describe('SnapsNameProvider', () => { const response = await provider.getProposedNames({ value: VALUE_MOCK, type: NameType.ETHEREUM_ADDRESS, - chainId: CHAIN_ID_MOCK, + variation: CHAIN_ID_MOCK, }); expect(response).toStrictEqual({ diff --git a/app/scripts/lib/SnapsNameProvider.ts b/app/scripts/lib/SnapsNameProvider.ts index 59438d725b3e..abf6cc8761b1 100644 --- a/app/scripts/lib/SnapsNameProvider.ts +++ b/app/scripts/lib/SnapsNameProvider.ts @@ -107,7 +107,7 @@ export class SnapsNameProvider implements NameProvider { snap: TruncatedSnap, request: NameProviderRequest, ): Promise<{ sourceId: string; result: NameProviderSourceResult }> { - const { chainId: chainIdHex, value } = request; + const { variation: chainIdHex, value } = request; const sourceId = snap.id; const chainIdDecimal = parseInt(chainIdHex, 16); diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 8a1be42a182c..2b908647deea 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -113,7 +113,6 @@ export const SENTRY_BACKGROUND_STATE = { KeyringController: { isUnlocked: true, keyrings: false, - keyringTypes: false, }, LoggingController: { logs: false, diff --git a/app/scripts/lib/snap-keyring/index.ts b/app/scripts/lib/snap-keyring/index.ts index abec622dc16a..5ef2a2be9501 100644 --- a/app/scripts/lib/snap-keyring/index.ts +++ b/app/scripts/lib/snap-keyring/index.ts @@ -1 +1 @@ -export { default as snapKeyringBuilder } from './snap-keyring'; +export { snapKeyringBuilder, getAccountsBySnapId } from './snap-keyring'; diff --git a/app/scripts/lib/snap-keyring/snap-keyring.ts b/app/scripts/lib/snap-keyring/snap-keyring.ts index 73e921b53d41..26ab295cffe1 100644 --- a/app/scripts/lib/snap-keyring/snap-keyring.ts +++ b/app/scripts/lib/snap-keyring/snap-keyring.ts @@ -7,12 +7,28 @@ import type { import type { KeyringController } from '@metamask/keyring-controller'; import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app'; import { t } from '../../translate'; +import MetamaskController from '../../metamask-controller'; + +/** + * Get the addresses of the accounts managed by a given Snap. + * + * @param controller - Instance of the MetaMask Controller. + * @param snapId - Snap ID to get accounts for. + * @returns The addresses of the accounts. + */ +export const getAccountsBySnapId = async ( + controller: MetamaskController, + snapId: string, +) => { + const snapKeyring: SnapKeyring = await controller.getSnapKeyring(); + return await snapKeyring.getAccountsBySnapId(snapId); +}; + /** * Constructs a SnapKeyring builder with specified handlers for managing snap accounts. * * @param getSnapController - A function that retrieves the Snap Controller instance. * @param getApprovalController - A function that retrieves the Approval Controller instance. - * @param getKeyringController - A function that retrieves the Keyring Controller instance. * @param getCoreKeyringController - A function that retrieves the Core Keyring Controller instance. * @param removeAccountHelper - A function to help remove an account based on its address. * @returns The constructed SnapKeyring builder instance with the following methods: @@ -20,10 +36,9 @@ import { t } from '../../translate'; * - `addAccount`: Initiates the process of adding an account with user confirmation and handling the user input. * - `removeAccount`: Initiates the process of removing an account with user confirmation and handling the user input. */ -const snapKeyringBuilder = ( +export const snapKeyringBuilder = ( getSnapController: () => SnapController, getApprovalController: () => ApprovalController, - getKeyringController: () => KeyringController, getCoreKeyringController: () => KeyringController, removeAccountHelper: (address: string) => Promise, ) => { @@ -34,7 +49,7 @@ const snapKeyringBuilder = ( return addresses.includes(address.toLowerCase()); }, saveState: async () => { - await getKeyringController().persistAllKeyrings(); + await getCoreKeyringController().persistAllKeyrings(); }, addAccount: async ( _address: string, @@ -60,7 +75,7 @@ const snapKeyringBuilder = ( if (confirmationResult) { try { await handleUserInput(confirmationResult); - await getKeyringController().persistAllKeyrings(); + await getCoreKeyringController().persistAllKeyrings(); await getApprovalController().success({ message: t('snapAccountCreated') ?? 'Your account is ready!', header: [snapAuthorshipHeader], @@ -112,7 +127,7 @@ const snapKeyringBuilder = ( try { await removeAccountHelper(address); await handleUserInput(confirmationResult); - await getKeyringController().persistAllKeyrings(); + await getCoreKeyringController().persistAllKeyrings(); await getApprovalController().success({ message: t('snapAccountRemoved') ?? 'Account removed', header: [snapAuthorshipHeader], @@ -143,5 +158,3 @@ const snapKeyringBuilder = ( builder.type = SnapKeyring.type; return builder; }; - -export default snapKeyringBuilder; diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js index a0dba444b2df..51b69a12008f 100644 --- a/app/scripts/metamask-controller.actions.test.js +++ b/app/scripts/metamask-controller.actions.test.js @@ -177,7 +177,7 @@ describe('MetaMaskController', function () { ]), Promise.resolve(1).then(() => { keyringControllerState1 = JSON.stringify( - metamaskController.keyringController.memStore.getState(), + metamaskController.coreKeyringController.state, ); metamaskController.importAccountWithStrategy('privateKey', [ importPrivkey, @@ -185,7 +185,7 @@ describe('MetaMaskController', function () { }), Promise.resolve(2).then(() => { keyringControllerState2 = JSON.stringify( - metamaskController.keyringController.memStore.getState(), + metamaskController.coreKeyringController.state, ); }), ]); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 321f459884a0..27ebd056f8f3 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -240,7 +240,7 @@ import { IndexedDBPPOMStorage } from './lib/ppom/indexed-db-backend'; ///: END:ONLY_INCLUDE_IN import { updateCurrentLocale } from './translate'; ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) -import { snapKeyringBuilder } from './lib/snap-keyring'; +import { snapKeyringBuilder, getAccountsBySnapId } from './lib/snap-keyring'; ///: END:ONLY_INCLUDE_IN export const METAMASK_CONTROLLER_EVENTS = { @@ -857,14 +857,12 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) const getSnapController = () => this.snapController; const getApprovalController = () => this.approvalController; - const getKeyringController = () => this.keyringController; const getCoreKeyringController = () => this.coreKeyringController; additionalKeyrings.push( snapKeyringBuilder( getSnapController, getApprovalController, - getKeyringController, getCoreKeyringController, (address) => this.removeAccount(address), ), @@ -930,9 +928,6 @@ export default class MetamaskController extends EventEmitter { }, ); - this.keyringController = - this.coreKeyringController.getEthKeyringController(); - const getIdentities = () => this.preferencesController.store.getState().identities; @@ -954,8 +949,8 @@ export default class MetamaskController extends EventEmitter { permissionSpecifications: { ...getPermissionSpecifications({ getIdentities, - getAllAccounts: this.keyringController.getAccounts.bind( - this.keyringController, + getAllAccounts: this.coreKeyringController.getAccounts.bind( + this.coreKeyringController, ), captureKeyringTypesWithMissingIdentities: ( identities = {}, @@ -1252,8 +1247,8 @@ export default class MetamaskController extends EventEmitter { this.networkController.state.providerConfig.chainId, preferencesStore: this.preferencesController.store, txHistoryLimit: 60, - signTransaction: this.keyringController.signTransaction.bind( - this.keyringController, + signTransaction: this.coreKeyringController.signTransaction.bind( + this.coreKeyringController, ), provider: this.provider, blockTracker: this.blockTracker, @@ -1439,9 +1434,13 @@ export default class MetamaskController extends EventEmitter { this.signatureController = new SignatureController({ messenger: this.controllerMessenger.getRestricted({ name: 'SignatureController', - allowedActions: [`${this.approvalController.name}:addRequest`], + allowedActions: [ + `${this.approvalController.name}:addRequest`, + `${this.coreKeyringController.name}:signMessage`, + `${this.coreKeyringController.name}:signPersonalMessage`, + `${this.coreKeyringController.name}:signTypedMessage`, + ], }), - keyringController: this.keyringController, isEthSignEnabled: () => this.preferencesController.store.getState() ?.disabledRpcMethodPreferences?.eth_sign, @@ -1539,7 +1538,6 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(petnames) this.nameController = new NameController({ - getChainId: () => this.networkController.state.providerConfig.chainId, messenger: this.controllerMessenger.getRestricted({ name: 'NameController', allowedActions: [], @@ -1735,7 +1733,7 @@ export default class MetamaskController extends EventEmitter { AppStateController: this.appStateController.store, AppMetadataController: this.appMetadataController.store, TransactionController: this.txController.store, - KeyringController: this.keyringController.store, + KeyringController: this.coreKeyringController, PreferencesController: this.preferencesController.store, MetaMetricsController: this.metaMetricsController.store, AddressBookController: this.addressBookController, @@ -1787,7 +1785,7 @@ export default class MetamaskController extends EventEmitter { AppMetadataController: this.appMetadataController.store, NetworkController: this.networkController, CachedBalancesController: this.cachedBalancesController.store, - KeyringController: this.keyringController.memStore, + KeyringController: this.coreKeyringController, PreferencesController: this.preferencesController.store, MetaMetricsController: this.metaMetricsController.store, AddressBookController: this.addressBookController, @@ -1930,6 +1928,8 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) /** * Initialize the snap keyring if it is not present. + * + * @returns {SnapKeyring} */ async getSnapKeyring() { let [snapKeyring] = this.coreKeyringController.getKeyringsByType( @@ -2295,11 +2295,14 @@ export default class MetamaskController extends EventEmitter { * @returns {object} status */ getState() { - const { vault } = this.keyringController.store.getState(); + const { vault } = this.coreKeyringController.state; const isInitialized = Boolean(vault); const flatState = this.memStore.getFlatState(); + // The vault should not be exposed to the UI + delete flatState.vault; + return { isInitialized, ...flatState, @@ -2430,12 +2433,15 @@ export default class MetamaskController extends EventEmitter { requestUserApproval: approvalController.addAndShowApprovalRequest.bind(approvalController), - // primary HD keyring management + // primary keyring management addNewAccount: this.addNewAccount.bind(this), verifySeedPhrase: this.verifySeedPhrase.bind(this), resetAccount: this.resetAccount.bind(this), removeAccount: this.removeAccount.bind(this), importAccountWithStrategy: this.importAccountWithStrategy.bind(this), + ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) + getAccountsBySnapId: (snapId) => getAccountsBySnapId(this, snapId), + ///: END:ONLY_INCLUDE_IN // hardware wallets connectHardware: this.connectHardware.bind(this), @@ -2944,7 +2950,7 @@ export default class MetamaskController extends EventEmitter { async exportAccount(address, password) { await this.verifyPassword(password); - return this.keyringController.exportAccount(address, password); + return this.coreKeyringController.exportAccount(password, address); } async getTokenStandardAndDetails(address, userAddress, tokenId) { @@ -3041,18 +3047,13 @@ export default class MetamaskController extends EventEmitter { async createNewVaultAndKeychain(password) { const releaseLock = await this.createVaultMutex.acquire(); try { - let vault; - const accounts = await this.keyringController.getAccounts(); - if (accounts.length > 0) { - vault = await this.keyringController.fullUpdate(); - } else { - vault = await this.keyringController.createNewVaultAndKeychain( - password, - ); - const addresses = await this.keyringController.getAccounts(); - this.preferencesController.setAddresses(addresses); - this.selectFirstIdentity(); - } + const vault = await this.coreKeyringController.createNewVaultAndKeychain( + password, + ); + + const accounts = await this.coreKeyringController.getAccounts(); + this.preferencesController.setAddresses(accounts); + this.selectFirstIdentity(); return vault; } finally { @@ -3293,7 +3294,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} password - The user's password */ async verifyPassword(password) { - await this.keyringController.verifyPassword(password); + await this.coreKeyringController.verifyPassword(password); } /** @@ -3373,7 +3374,7 @@ export default class MetamaskController extends EventEmitter { keyringName, ); if (!keyring) { - keyring = await this.keyringController.addNewKeyring(keyringName); + keyring = await this.coreKeyringController.addNewKeyring(keyringName); } if (hdPath && keyring.setHdPath) { keyring.setHdPath(hdPath); @@ -3427,7 +3428,7 @@ export default class MetamaskController extends EventEmitter { // Merge with existing accounts // and make sure addresses are not repeated - const oldAccounts = await this.keyringController.getAccounts(); + const oldAccounts = await this.coreKeyringController.getAccounts(); const accountsToTrack = [ ...new Set( oldAccounts.concat(accounts.map((a) => a.address.toLowerCase())), @@ -3543,9 +3544,11 @@ export default class MetamaskController extends EventEmitter { const keyring = await this.getKeyringForDevice(deviceName, hdPath); keyring.setAccountToUnlock(index); - const oldAccounts = await this.keyringController.getAccounts(); - const keyState = await this.keyringController.addNewAccount(keyring); - const newAccounts = await this.keyringController.getAccounts(); + const oldAccounts = await this.coreKeyringController.getAccounts(); + const keyState = await this.coreKeyringController.addNewAccountForKeyring( + keyring, + ); + const newAccounts = await this.coreKeyringController.getAccounts(); this.preferencesController.setAddresses(newAccounts); newAccounts.forEach((address) => { if (!oldAccounts.includes(address)) { @@ -3694,7 +3697,7 @@ export default class MetamaskController extends EventEmitter { address, ); // Remove account from the keyring - await this.keyringController.removeAccount(address); + await this.coreKeyringController.removeAccount(address); const updatedKeyringAccounts = keyring ? await keyring.getAccounts() : {}; if (updatedKeyringAccounts?.length === 0) { keyring.destroy?.(); @@ -4652,7 +4655,7 @@ export default class MetamaskController extends EventEmitter { * @returns {boolean} Whether the extension is unlocked. */ isUnlocked() { - return this.keyringController.memStore.getState().isUnlocked; + return this.coreKeyringController.state.isUnlocked; } //============================================================================= diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index c8e6c070e14d..40ad6d240527 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -318,13 +318,16 @@ describe('MetaMaskController', () => { }); jest.spyOn( - metamaskController.keyringController, + metamaskController.coreKeyringController, 'createNewVaultAndKeychain', ); jest.spyOn( metamaskController.coreKeyringController, 'createNewVaultAndRestore', ); + jest + .spyOn(metamaskController.preferencesController, 'removeAddress') + .mockImplementation((address) => address); }); describe('should reset states on first time profile load', () => { @@ -363,7 +366,7 @@ describe('MetaMaskController', () => { it('adds 1 account', async () => { const keyringAccounts = - await metamaskController.keyringController.getAccounts(); + await metamaskController.coreKeyringController.getAccounts(); expect(keyringAccounts[keyringAccounts.length - 1]).toStrictEqual( '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', ); @@ -413,15 +416,16 @@ describe('MetaMaskController', () => { describe('#createNewVaultAndKeychain', () => { it('can only create new vault on keyringController once', async () => { jest.spyOn(metamaskController, 'selectFirstIdentity').mockReturnValue(); - const password = 'a-fake-password'; - await metamaskController.createNewVaultAndKeychain(password); - await metamaskController.createNewVaultAndKeychain(password); + const vault1 = await metamaskController.createNewVaultAndKeychain( + password, + ); + const vault2 = await metamaskController.createNewVaultAndKeychain( + password, + ); - expect( - metamaskController.keyringController.createNewVaultAndKeychain, - ).toHaveBeenCalledTimes(1); + expect(vault1).toStrictEqual(vault2); }); }); @@ -622,7 +626,7 @@ describe('MetaMaskController', () => { }); it('should add the Trezor Hardware keyring', async () => { - jest.spyOn(metamaskController.keyringController, 'addNewKeyring'); + jest.spyOn(metamaskController.coreKeyringController, 'addNewKeyring'); await metamaskController .connectHardware(HardwareDeviceNames.trezor, 0) .catch(() => null); @@ -631,13 +635,13 @@ describe('MetaMaskController', () => { KeyringType.trezor, ); expect( - metamaskController.keyringController.addNewKeyring, + metamaskController.coreKeyringController.addNewKeyring, ).toHaveBeenCalledWith(KeyringType.trezor); expect(keyrings).toHaveLength(1); }); it('should add the Ledger Hardware keyring', async () => { - jest.spyOn(metamaskController.keyringController, 'addNewKeyring'); + jest.spyOn(metamaskController.coreKeyringController, 'addNewKeyring'); await metamaskController .connectHardware(HardwareDeviceNames.ledger, 0) .catch(() => null); @@ -646,7 +650,7 @@ describe('MetaMaskController', () => { KeyringType.ledger, ); expect( - metamaskController.keyringController.addNewKeyring, + metamaskController.coreKeyringController.addNewKeyring, ).toHaveBeenCalledWith(KeyringType.ledger); expect(keyrings).toHaveLength(1); }); @@ -729,13 +733,20 @@ describe('MetaMaskController', () => { describe('unlockHardwareWalletAccount', () => { const accountToUnlock = 10; beforeEach(async () => { + await metamaskController.coreKeyringController.createNewVaultAndRestore( + 'password', + TEST_SEED, + ); jest.spyOn(window, 'open').mockReturnValue(); jest - .spyOn(metamaskController.keyringController, 'addNewAccount') + .spyOn( + metamaskController.coreKeyringController, + 'addNewAccountForKeyring', + ) .mockReturnValue('0x123'); jest - .spyOn(metamaskController.keyringController, 'getAccounts') + .spyOn(metamaskController.coreKeyringController, 'getAccounts') .mockResolvedValueOnce(['0x1']) .mockResolvedValueOnce(['0x2']) .mockResolvedValueOnce(['0x3']); @@ -749,9 +760,6 @@ describe('MetaMaskController', () => { .spyOn(metamaskController.preferencesController, 'setAccountLabel') .mockReturnValue(); - await metamaskController - .connectHardware(HardwareDeviceNames.trezor, 0, `m/44'/1'/0'/0`) - .catch(() => null); await metamaskController.unlockHardwareWalletAccount( accountToUnlock, HardwareDeviceNames.trezor, @@ -769,14 +777,14 @@ describe('MetaMaskController', () => { it('should call keyringController.addNewAccount', async () => { expect( - metamaskController.keyringController.addNewAccount, + metamaskController.coreKeyringController.addNewAccountForKeyring, ).toHaveBeenCalledTimes(1); }); it('should call keyringController.getAccounts', async () => { expect( - metamaskController.keyringController.getAccounts, - ).toHaveBeenCalledTimes(3); + metamaskController.coreKeyringController.getAccounts, + ).toHaveBeenCalledTimes(2); }); it('should call preferencesController.setAddresses', async () => { @@ -817,7 +825,7 @@ describe('MetaMaskController', () => { await metamaskController.createNewVaultAndKeychain('password'); await metamaskController.addNewAccount(1); const getAccounts = - await metamaskController.keyringController.getAccounts(); + await metamaskController.coreKeyringController.getAccounts(); expect(getAccounts).toHaveLength(2); }); }); @@ -876,7 +884,7 @@ describe('MetaMaskController', () => { destroy: jest.fn(), }; jest - .spyOn(metamaskController.keyringController, 'removeAccount') + .spyOn(metamaskController.coreKeyringController, 'removeAccount') .mockReturnValue(); jest .spyOn(metamaskController, 'removeAllAccountPermissions') @@ -894,7 +902,7 @@ describe('MetaMaskController', () => { it('should call keyringController.removeAccount', async () => { expect( - metamaskController.keyringController.removeAccount, + metamaskController.coreKeyringController.removeAccount, ).toHaveBeenCalledWith(addressToRemove); }); it('should call metamaskController.removeAllAccountPermissions', async () => { diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 5f198c10ec85..137d438d31e5 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -933,12 +933,16 @@ } }, "@metamask/eth-keyring-controller": { + "globals": { + "console.error": true + }, "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-keyring-controller>@metamask/utils": true, + "@metamask/obs-store": true, "webpack>events": true } }, @@ -994,7 +998,7 @@ "@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, + "@ethereumjs/tx>ethereum-cryptography": true, "bn.js": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, @@ -1002,21 +1006,6 @@ "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-simple-keyring": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1042,10 +1031,17 @@ "crypto": true } }, - "@metamask/eth-keyring-controller>obs-store": { + "@metamask/eth-keyring-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/eth-token-tracker>safe-event-emitter": true, - "watchify>xtend": true + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/eth-ledger-bridge-keyring": { @@ -1559,51 +1555,13 @@ "@metamask/keyring-controller": { "packages": { "@metamask/base-controller": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller": true, + "@metamask/eth-keyring-controller": true, "@metamask/keyring-controller>@metamask/utils": true, "@metamask/keyring-controller>ethereumjs-wallet": true, "eth-json-rpc-filters>async-mutex": true, "ethereumjs-util": true } }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/browser-passworder": true, - "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true, - "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/utils": true, - "@metamask/obs-store": true, - "webpack>events": true - } - }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/eth-sig-util": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "bn.js": true, - "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethjs-util": true, - "eth-sig-util>tweetnacl": true, - "eth-sig-util>tweetnacl-util": true - } - }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, "@metamask/keyring-controller>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1771,7 +1729,22 @@ "fetch": true }, "packages": { - "@metamask/base-controller": true + "@metamask/base-controller": true, + "@metamask/name-controller>@metamask/utils": true, + "eth-json-rpc-filters>async-mutex": true + } + }, + "@metamask/name-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/network-controller": { diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index 35711e8e5ab9..5f3e7b1e6a65 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -1004,12 +1004,16 @@ } }, "@metamask/eth-keyring-controller": { + "globals": { + "console.error": true + }, "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-keyring-controller>@metamask/utils": true, + "@metamask/obs-store": true, "webpack>events": true } }, @@ -1065,7 +1069,7 @@ "@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, + "@ethereumjs/tx>ethereum-cryptography": true, "bn.js": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, @@ -1073,21 +1077,6 @@ "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-simple-keyring": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1113,10 +1102,17 @@ "crypto": true } }, - "@metamask/eth-keyring-controller>obs-store": { + "@metamask/eth-keyring-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/eth-token-tracker>safe-event-emitter": true, - "watchify>xtend": true + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/eth-ledger-bridge-keyring": { @@ -1700,51 +1696,13 @@ "@metamask/keyring-controller": { "packages": { "@metamask/base-controller": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller": true, + "@metamask/eth-keyring-controller": true, "@metamask/keyring-controller>@metamask/utils": true, "@metamask/keyring-controller>ethereumjs-wallet": true, "eth-json-rpc-filters>async-mutex": true, "ethereumjs-util": true } }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/browser-passworder": true, - "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true, - "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/utils": true, - "@metamask/obs-store": true, - "webpack>events": true - } - }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/eth-sig-util": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "bn.js": true, - "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethjs-util": true, - "eth-sig-util>tweetnacl": true, - "eth-sig-util>tweetnacl-util": true - } - }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, "@metamask/keyring-controller>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1912,7 +1870,22 @@ "fetch": true }, "packages": { - "@metamask/base-controller": true + "@metamask/base-controller": true, + "@metamask/name-controller>@metamask/utils": true, + "eth-json-rpc-filters>async-mutex": true + } + }, + "@metamask/name-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/network-controller": { diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 9db7cc3bdce0..62ee4d7636f5 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1004,12 +1004,16 @@ } }, "@metamask/eth-keyring-controller": { + "globals": { + "console.error": true + }, "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-keyring-controller>@metamask/utils": true, + "@metamask/obs-store": true, "webpack>events": true } }, @@ -1065,7 +1069,7 @@ "@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, + "@ethereumjs/tx>ethereum-cryptography": true, "bn.js": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, @@ -1073,21 +1077,6 @@ "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-simple-keyring": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1113,10 +1102,17 @@ "crypto": true } }, - "@metamask/eth-keyring-controller>obs-store": { + "@metamask/eth-keyring-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/eth-token-tracker>safe-event-emitter": true, - "watchify>xtend": true + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/eth-ledger-bridge-keyring": { @@ -1700,51 +1696,13 @@ "@metamask/keyring-controller": { "packages": { "@metamask/base-controller": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller": true, + "@metamask/eth-keyring-controller": true, "@metamask/keyring-controller>@metamask/utils": true, "@metamask/keyring-controller>ethereumjs-wallet": true, "eth-json-rpc-filters>async-mutex": true, "ethereumjs-util": true } }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/browser-passworder": true, - "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true, - "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/utils": true, - "@metamask/obs-store": true, - "webpack>events": true - } - }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/eth-sig-util": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "bn.js": true, - "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethjs-util": true, - "eth-sig-util>tweetnacl": true, - "eth-sig-util>tweetnacl-util": true - } - }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, "@metamask/keyring-controller>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1912,7 +1870,22 @@ "fetch": true }, "packages": { - "@metamask/base-controller": true + "@metamask/base-controller": true, + "@metamask/name-controller>@metamask/utils": true, + "eth-json-rpc-filters>async-mutex": true + } + }, + "@metamask/name-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/network-controller": { diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index c413d7fa8933..3620f072010a 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -933,12 +933,16 @@ } }, "@metamask/eth-keyring-controller": { + "globals": { + "console.error": true + }, "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-keyring-controller>@metamask/utils": true, + "@metamask/obs-store": true, "webpack>events": true } }, @@ -994,7 +998,7 @@ "@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, + "@ethereumjs/tx>ethereum-cryptography": true, "bn.js": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, @@ -1002,21 +1006,6 @@ "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-simple-keyring": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1042,10 +1031,17 @@ "crypto": true } }, - "@metamask/eth-keyring-controller>obs-store": { + "@metamask/eth-keyring-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/eth-token-tracker>safe-event-emitter": true, - "watchify>xtend": true + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/eth-ledger-bridge-keyring": { @@ -1559,51 +1555,13 @@ "@metamask/keyring-controller": { "packages": { "@metamask/base-controller": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller": true, + "@metamask/eth-keyring-controller": true, "@metamask/keyring-controller>@metamask/utils": true, "@metamask/keyring-controller>ethereumjs-wallet": true, "eth-json-rpc-filters>async-mutex": true, "ethereumjs-util": true } }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/browser-passworder": true, - "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true, - "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/utils": true, - "@metamask/obs-store": true, - "webpack>events": true - } - }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/eth-sig-util": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "bn.js": true, - "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethjs-util": true, - "eth-sig-util>tweetnacl": true, - "eth-sig-util>tweetnacl-util": true - } - }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, "@metamask/keyring-controller>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1771,7 +1729,22 @@ "fetch": true }, "packages": { - "@metamask/base-controller": true + "@metamask/base-controller": true, + "@metamask/name-controller>@metamask/utils": true, + "eth-json-rpc-filters>async-mutex": true + } + }, + "@metamask/name-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/network-controller": { diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index b04f94e1e8d5..da52999e2f72 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1073,12 +1073,16 @@ } }, "@metamask/eth-keyring-controller": { + "globals": { + "console.error": true + }, "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-keyring-controller>@metamask/utils": true, + "@metamask/obs-store": true, "webpack>events": true } }, @@ -1134,7 +1138,7 @@ "@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, + "@ethereumjs/tx>ethereum-cryptography": true, "bn.js": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, @@ -1142,21 +1146,6 @@ "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-simple-keyring": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1182,10 +1171,17 @@ "crypto": true } }, - "@metamask/eth-keyring-controller>obs-store": { + "@metamask/eth-keyring-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/eth-token-tracker>safe-event-emitter": true, - "watchify>xtend": true + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/eth-ledger-bridge-keyring": { @@ -1699,51 +1695,13 @@ "@metamask/keyring-controller": { "packages": { "@metamask/base-controller": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller": true, + "@metamask/eth-keyring-controller": true, "@metamask/keyring-controller>@metamask/utils": true, "@metamask/keyring-controller>ethereumjs-wallet": true, "eth-json-rpc-filters>async-mutex": true, "ethereumjs-util": true } }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/browser-passworder": true, - "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true, - "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/utils": true, - "@metamask/obs-store": true, - "webpack>events": true - } - }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/eth-sig-util": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "bn.js": true, - "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethjs-util": true, - "eth-sig-util>tweetnacl": true, - "eth-sig-util>tweetnacl-util": true - } - }, - "@metamask/keyring-controller>@metamask/eth-keyring-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, "@metamask/keyring-controller>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1911,7 +1869,22 @@ "fetch": true }, "packages": { - "@metamask/base-controller": true + "@metamask/base-controller": true, + "@metamask/name-controller>@metamask/utils": true, + "eth-json-rpc-filters>async-mutex": true + } + }, + "@metamask/name-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/network-controller": { diff --git a/package.json b/package.json index c9d22a2878e2..4ff01e9f53aa 100644 --- a/package.json +++ b/package.json @@ -205,8 +205,7 @@ "request@^2.88.2": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", "request@^2.85.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", "lavamoat-core@npm:^14.4.1": "patch:lavamoat-core@npm%3A14.4.1#~/.yarn/patches/lavamoat-core-npm-14.4.1-c4e8bbb016.patch", - "@metamask/keyring-controller@^8.0.0": "patch:@metamask/keyring-controller@npm%3A8.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-8.0.0-806630ae4e.patch", - "@metamask/signature-controller@^5.3.0": "patch:@metamask/signature-controller@npm%3A5.3.0#./.yarn/patches/@metamask-signature-controller-npm-5.3.0-225628460b.patch", + "@metamask/signature-controller@^6.0.0": "patch:@metamask/signature-controller@npm%3A6.0.0#~/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch", "semver@7.3.7": "^7.5.4", "semver@7.3.8": "^7.5.4" }, @@ -246,9 +245,9 @@ "@metamask/design-tokens": "^1.12.0", "@metamask/desktop": "^0.3.0", "@metamask/eth-json-rpc-middleware": "^11.0.0", - "@metamask/eth-keyring-controller": "^10.0.1", + "@metamask/eth-keyring-controller": "^13.0.1", "@metamask/eth-ledger-bridge-keyring": "^0.15.0", - "@metamask/eth-snap-keyring": "0.3.0", + "@metamask/eth-snap-keyring": "0.3.1", "@metamask/eth-token-tracker": "^4.0.0", "@metamask/eth-trezor-keyring": "^1.1.0", "@metamask/etherscan-link": "^2.2.0", @@ -256,12 +255,12 @@ "@metamask/gas-fee-controller": "^6.0.1", "@metamask/jazzicon": "^2.0.0", "@metamask/key-tree": "^9.0.0", - "@metamask/keyring-controller": "^8.0.0", + "@metamask/keyring-controller": "^8.0.1", "@metamask/logging-controller": "^1.0.1", "@metamask/logo": "^3.1.1", "@metamask/message-manager": "^7.3.0", "@metamask/metamask-eth-abis": "^3.0.0", - "@metamask/name-controller": "^1.0.0", + "@metamask/name-controller": "^3.0.0", "@metamask/network-controller": "^12.2.0", "@metamask/notification-controller": "^3.0.0", "@metamask/obs-store": "^8.1.0", @@ -275,7 +274,7 @@ "@metamask/safe-event-emitter": "^2.0.0", "@metamask/scure-bip39": "^2.0.3", "@metamask/selected-network-controller": "^1.0.0", - "@metamask/signature-controller": "^5.3.0", + "@metamask/signature-controller": "^6.0.0", "@metamask/slip44": "^3.0.0", "@metamask/smart-transactions-controller": "^4.0.0", "@metamask/snaps-controllers": "^2.0.1", diff --git a/shared/constants/labels.ts b/shared/constants/labels.ts index ab03c4eb0a5d..dc2ee63a7c95 100644 --- a/shared/constants/labels.ts +++ b/shared/constants/labels.ts @@ -3,8 +3,8 @@ export const TRUNCATED_NAME_CHAR_LIMIT = 11; // The number of characters to slice from the beginning of an address for truncated format: // `${TRUNCATED_ADDRESS_START_CHARS}...${TRUNCATED_ADDRESS_END_CHARS}` -export const TRUNCATED_ADDRESS_START_CHARS = 5; +export const TRUNCATED_ADDRESS_START_CHARS = 7; // The number of characters to slice from the end of an address for truncated format: // `${TRUNCATED_ADDRESS_START_CHARS}...${TRUNCATED_ADDRESS_END_CHARS}` -export const TRUNCATED_ADDRESS_END_CHARS = 4; +export const TRUNCATED_ADDRESS_END_CHARS = 5; diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index b17c67e63038..e9b26f6c6712 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -455,6 +455,9 @@ export enum MetaMetricsUserTrait { */ MmiIsCustodian = 'mmi_is_custodian', ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(petnames) + PetnameAddressCount = 'petname_addresses_count', + ///: END:ONLY_INCLUDE_IN } /** @@ -545,6 +548,11 @@ export enum MetaMetricsEventName { PermissionsApproved = 'Permissions Approved', PermissionsRejected = 'Permissions Rejected', PermissionsRequested = 'Permissions Requested', + PetnameCreated = 'Petname Created', + PetnameDeleted = 'Petname Deleted', + PetnameDisplayed = 'Petname Displayed', + PetnameModalOpened = 'Petname Modal Opened', + PetnameUpdated = 'Petname Updated', PhishingPageDisplayed = 'Phishing Page Displayed', PortfolioLinkClicked = 'Portfolio Link Clicked', ProviderMethodCalled = 'Provider Method Called', @@ -665,18 +673,19 @@ export enum MetaMetricsEventCategory { Navigation = 'Navigation', Network = 'Network', Onboarding = 'Onboarding', + Petnames = 'Petnames', Phishing = 'Phishing', Retention = 'Retention', ServiceWorkers = 'service_workers', Settings = 'Settings', Snaps = 'Snaps', Swaps = 'Swaps', + Tokens = 'Tokens', Transactions = 'Transactions', Wallet = 'Wallet', ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) MMI = 'Institutional', ///: END:ONLY_INCLUDE_IN - Tokens = 'Tokens', } export enum MetaMetricsEventLinkType { diff --git a/shared/constants/names.ts b/shared/constants/names.ts deleted file mode 100644 index 33538a59192c..000000000000 --- a/shared/constants/names.ts +++ /dev/null @@ -1,13 +0,0 @@ -export enum NameSourceId { - ENS = 'ens', - ETHERSCAN = 'etherscan', - LENS_PROTOCOL = 'lens', - TOKEN = 'token', -} - -export const DEFAULT_NAME_SOURCE_PRIORITY = [ - NameSourceId.ENS, - NameSourceId.LENS_PROTOCOL, - NameSourceId.TOKEN, - NameSourceId.ETHERSCAN, -]; diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 73a36e256759..961b5a559c8c 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -135,7 +135,7 @@ export const UI_NOTIFICATIONS = { id: 23, date: null, image: { - src: 'images/blockaid-security-provider.png', + src: 'images/blockaid-security-provider.svg', width: '100%', }, }, diff --git a/test/e2e/accounts/test-remove-accounts-snap.spec.js b/test/e2e/accounts/test-remove-accounts-snap.spec.js new file mode 100644 index 000000000000..850eca6a1f72 --- /dev/null +++ b/test/e2e/accounts/test-remove-accounts-snap.spec.js @@ -0,0 +1,127 @@ +const { strict: assert } = require('assert'); +const { + withFixtures, + defaultGanacheOptions, + unlockWallet, + WINDOW_TITLES, + switchToNotificationWindow, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); +const { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } = require('./utils'); + +describe('Remove Account Snap', function () { + it('disable a snap and remove it', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + failOnConsoleError: false, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + + await unlockWallet(driver); + + // Navigate to test Snaps page and connect. + await driver.openNewPage(TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL); + + // Connect the dapp. + await driver.clickElement('#connectButton'); + await switchToNotificationWindow(driver); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // Scroll to the bottom of the page. + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); + + // Click the install button to install the snap. + await driver.waitForSelector({ text: 'Install' }); + await driver.clickElement({ + text: 'Install', + tag: 'button', + }); + await driver.waitForSelector({ text: 'OK' }); + await driver.clickElement({ + text: 'OK', + tag: 'button', + }); + + // Move back to the snap window to test the create account flow. + await driver.switchToWindowWithTitle( + WINDOW_TITLES.SnapSimpleKeyringDapp, + ); + + // 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', + }); + await driver.clickElement({ + text: 'Create Account', + tag: 'button', + }); + await switchToNotificationWindow(driver); + await driver.clickElement('[data-testid="confirmation-submit-button"]'); + await driver.findElement({ + tag: 'div', + text: 'Your account is ready!', + }); + + // Click the OK button. + await driver.clickElement('[data-testid="confirmation-submit-button"]'); + + // Switch back to the test dapp window. + await driver.switchToWindowWithTitle( + WINDOW_TITLES.SnapSimpleKeyringDapp, + ); + + await driver.findElement({ + tag: 'p', + text: 'Successful request', + }); + + // Navigate to settings. + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.clickElement({ text: 'Snaps', tag: 'div' }); + await driver.clickElement({ + text: 'MetaMask Simple Snap Keyring', + tag: 'p', + }); + + // Disable the snap. + await driver.clickElement('.toggle-button > div'); + + // Remove the snap. + await driver.clickElement({ + text: 'Remove MetaMask Simple Snap Keyring', + tag: 'p', + }); + await driver.clickElement('#popoverRemoveSnapButton'); + + // Assert that the snap was removed. + const removeResult = await driver.findElement( + '.snap-list-tab__container--no-snaps_inner', + ); + assert.equal( + await removeResult.getText(), + "You don't have any snaps installed.", + ); + }, + ); + }); +}); diff --git a/test/e2e/snaps/petnames.spec.js b/test/e2e/flask/petnames.spec.js similarity index 51% rename from test/e2e/snaps/petnames.spec.js rename to test/e2e/flask/petnames.spec.js index e4019484ef86..7f1a3ebc4979 100644 --- a/test/e2e/snaps/petnames.spec.js +++ b/test/e2e/flask/petnames.spec.js @@ -1,7 +1,6 @@ -const { strict: assert } = require('assert'); const { withFixtures, openDapp, convertToHexValue } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); +const { TEST_SNAPS_WEBSITE_URL } = require('../snaps/enums'); const SIGNATURE_TYPE = { TYPED_V3: 'v3', @@ -88,84 +87,41 @@ async function focusTestDapp(driver) { } async function showThirdPartyDetails(driver) { - const verifyContractDetailsButton = await driver.findElement( + await driver.clickElement( '.signature-request-content__verify-contract-details', ); - - verifyContractDetailsButton.click(); - - await driver.delay(3000); - - return await driver.findElement('.contract-details-modal'); } async function closeThirdPartyDetails(driver) { await driver.clickElement({ text: 'Got it', tag: 'button' }); } -async function getAddressesInMessage(driver) { - return await driver.findElements( - '.signature-request-data__node__value__address', - ); -} - -async function expectName( - parent, - expectedValue, - expectedProposedName, - isSaved, -) { - const value = await ( - await parent.nestedFindElement(isSaved ? '.name__name' : '.name__value') - ).getText(); +async function expectName(driver, expectedValue, isSaved) { + const containerClass = isSaved ? 'name__saved' : 'name__missing'; + const valueClass = isSaved ? 'name__name' : 'name__value'; - assert.equal(value, expectedValue, 'Name value is incorrect'); - - if (expectedProposedName) { - const proposedName = ( - await (await parent.nestedFindElement(`.name__proposed`))?.getText() - ) - ?.replace('“', '') - .replace('”', ''); - - assert.equal( - proposedName, - expectedProposedName, - 'Proposed name is incorrect', - ); - } + await driver.findElement({ + css: `.${containerClass} .${valueClass}`, + text: expectedValue, + }); +} - if (isSaved) { - await parent.nestedFindElement(`.name__saved`); - } else { - await parent.nestedFindElement(`.name__missing`); - } +async function clickName(driver, value) { + await driver.clickElement({ + css: `.name`, + text: value, + }); } -async function saveName(driver, parent, name, proposedName) { - (await parent.nestedFindElement('.name')).click(); - (await driver.findElement('.form-combo-field')).click(); +async function saveName(driver, value, name, proposedName) { + await clickName(driver, value); + await driver.clickElement('.form-combo-field'); if (proposedName) { - const options = await driver.findElements( - '.form-combo-field__option-primary', - ); - - let found = false; - - for (const option of options) { - const text = await option.getText(); - - if (text === proposedName) { - option.click(); - found = true; - break; - } - } - - if (!found) { - assert.fail('Could not find proposed name'); - } + await driver.clickElement({ + css: '.form-combo-field__option-primary', + text: proposedName, + }); } if (name) { @@ -177,28 +133,21 @@ async function saveName(driver, parent, name, proposedName) { await driver.clickElement({ text: 'Save', tag: 'button' }); } -async function getProposedNames(driver, parent) { - (await parent.nestedFindElement('.name')).click(); - (await driver.findElement('.form-combo-field')).click(); - - const primaryTextElements = await driver.findElements( - '.form-combo-field__option-primary', - ); - - const secondaryTextElements = await driver.findElements( - '.form-combo-field__option-secondary', - ); +async function expectProposedNames(driver, value, options) { + await clickName(driver, value); + await driver.clickElement('.form-combo-field'); - const proposedNames = []; + for (const option of options) { + await driver.findElement({ + css: '.form-combo-field__option-primary', + text: option[0], + }); - for (let i = 0; i < primaryTextElements.length; i++) { - const primaryText = await primaryTextElements[i].getText(); - const secondaryText = await secondaryTextElements[i].getText(); - - proposedNames.push([primaryText, secondaryText]); + await driver.findElement({ + css: '.form-combo-field__option-secondary', + text: option[1], + }); } - - return proposedNames; } describe('Petnames', function () { @@ -218,39 +167,22 @@ describe('Petnames', function () { await openDapp(driver); await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V3); await focusNotification(driver); - - let addresses = await getAddressesInMessage(driver); - - await expectName(addresses[0], '0xCD2...D826', 'test.lens', false); - await expectName(addresses[1], '0xbBb...BBbB', 'test2.lens', false); - - await saveName(driver, addresses[0], undefined, 'test.lens'); - await saveName(driver, addresses[1], undefined, 'test2.lens'); - - let contractDetailsModal = await showThirdPartyDetails(driver); - - await expectName( - contractDetailsModal, - '0xCcC...cccC', - 'test3.lens', - false, - ); - - await saveName(driver, contractDetailsModal, 'Custom Name'); + await expectName(driver, '0xCD2a3...DD826', false); + await expectName(driver, '0xbBbBB...bBBbB', false); + await saveName(driver, '0xCD2a3...DD826', undefined, 'test.lens'); + await saveName(driver, '0xbBbBB...bBBbB', undefined, 'test2.lens'); + await showThirdPartyDetails(driver); + await expectName(driver, '0xCcCCc...ccccC', false); + await saveName(driver, '0xCcCCc...ccccC', 'Custom Name'); await closeThirdPartyDetails(driver); await rejectSignatureRequest(driver); await focusTestDapp(driver); await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V3); await focusNotification(driver); - - addresses = await getAddressesInMessage(driver); - - await expectName(addresses[0], 'test.lens', undefined, true); - await expectName(addresses[1], 'test2.lens', undefined, true); - - contractDetailsModal = await showThirdPartyDetails(driver); - - await expectName(contractDetailsModal, 'Custom Name', undefined, true); + await expectName(driver, 'test.lens', true); + await expectName(driver, 'test2.lens', true); + await showThirdPartyDetails(driver); + await expectName(driver, 'Custom Name', true); }, ); }); @@ -271,42 +203,25 @@ describe('Petnames', function () { await openDapp(driver); await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V4); await focusNotification(driver); - - let addresses = await getAddressesInMessage(driver); - - await expectName(addresses[0], '0xCD2...D826', 'test.lens', false); - await expectName(addresses[1], '0xDea...beeF', 'Test Token', false); - await expectName(addresses[2], '0xbBb...BBbB', 'test2.lens', false); - await expectName(addresses[3], '0xB0B...Ea57', 'Test Token 2', false); - await expectName(addresses[4], '0xB0B...0000', undefined, false); - - await saveName(driver, addresses[0], undefined, 'test.lens'); - await saveName(driver, addresses[3], undefined, 'Test Token 2'); - - let contractDetailsModal = await showThirdPartyDetails(driver); - - await expectName( - contractDetailsModal, - '0xCcC...cccC', - 'test3.lens', - false, - ); - - await saveName(driver, contractDetailsModal, 'Custom Name'); + await expectName(driver, '0xCD2a3...DD826', false); + await expectName(driver, '0xDeaDb...DbeeF', false); + await expectName(driver, '0xbBbBB...bBBbB', false); + await expectName(driver, '0xB0Bda...bEa57', false); + await expectName(driver, '0xB0B0b...00000', false); + await saveName(driver, '0xCD2a3...DD826', undefined, 'test.lens'); + await saveName(driver, '0xB0Bda...bEa57', undefined, 'Test Token 2'); + await showThirdPartyDetails(driver); + await expectName(driver, '0xCcCCc...ccccC', false); + await saveName(driver, '0xCcCCc...ccccC', 'Custom Name'); await closeThirdPartyDetails(driver); await rejectSignatureRequest(driver); await focusTestDapp(driver); await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V4); await focusNotification(driver); - - addresses = await getAddressesInMessage(driver); - - await expectName(addresses[0], 'test.lens', undefined, true); - await expectName(addresses[3], 'Test Token 2', undefined, true); - - contractDetailsModal = await showThirdPartyDetails(driver); - - await expectName(contractDetailsModal, 'Custom Name', undefined, true); + await expectName(driver, 'test.lens', true); + await expectName(driver, 'Test Token 2', true); + await showThirdPartyDetails(driver); + await expectName(driver, 'Custom Name', true); }, ); }); @@ -330,12 +245,9 @@ describe('Petnames', function () { await focusTestDapp(driver); await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V4); await focusNotification(driver); - - const addresses = await getAddressesInMessage(driver); - - assert.deepEqual(await getProposedNames(driver, addresses[0]), [ - ['example.domain - 0xCD2 / 0x539', 'Name Lookup Example Snap'], + await expectProposedNames(driver, '0xCD2a3...DD826', [ ['test.lens', 'Lens Protocol'], + ['example.domain - 0xcd2 / 0x539', 'Name Lookup Example Snap'], ]); }, ); diff --git a/test/e2e/snaps/ppom-toggle-settings.spec.js b/test/e2e/flask/ppom-toggle-settings.spec.js similarity index 100% rename from test/e2e/snaps/ppom-toggle-settings.spec.js rename to test/e2e/flask/ppom-toggle-settings.spec.js diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index f78f7e8a0b91..91a32c5c5fdb 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -396,9 +396,9 @@ async function setupMocking(server, testSpecificMock, { chainId }) { async function mockLensNameProvider(server) { const handlesByAddress = { - '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826': 'test.lens', - '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB': 'test2.lens', - '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC': 'test3.lens', + '0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826': 'test.lens', + '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb': 'test2.lens', + '0xcccccccccccccccccccccccccccccccccccccccc': 'test3.lens', }; await server.forPost('https://api.lens.dev').thenCallback((request) => { @@ -424,8 +424,8 @@ async function mockLensNameProvider(server) { async function mockTokenNameProvider(server) { const namesByAddress = { - '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF': 'Test Token', - '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57': 'Test Token 2', + '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef': 'Test Token', + '0xb0bdabea57b0bdabea57b0bdabea57b0bdabea57': 'Test Token 2', }; for (const address of Object.keys(namesByAddress)) { diff --git a/test/e2e/nft/view-erc1155-details.spec.js b/test/e2e/nft/view-erc1155-details.spec.js index 99133de7f65b..b782a82b3088 100644 --- a/test/e2e/nft/view-erc1155-details.spec.js +++ b/test/e2e/nft/view-erc1155-details.spec.js @@ -58,7 +58,7 @@ describe('View ERC1155 NFT details', function () { const nftContract = await driver.findElement( '.nft-details__contract-wrapper', ); - assert.equal(await nftContract.getText(), '0x581...5947'); + assert.equal(await nftContract.getText(), '0x581c3...45947'); }, ); }); diff --git a/test/e2e/nft/view-nft-details.spec.js b/test/e2e/nft/view-nft-details.spec.js index 2648085ddb0f..fa80ba7ad1f8 100644 --- a/test/e2e/nft/view-nft-details.spec.js +++ b/test/e2e/nft/view-nft-details.spec.js @@ -57,7 +57,7 @@ describe('View NFT details', function () { const nftContract = await driver.findElement( '.nft-details__contract-wrapper', ); - assert.equal(await nftContract.getText(), '0x581...5947'); + assert.equal(await nftContract.getText(), '0x581c3...45947'); }, ); }); diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js index 34ff1288e188..c5383103d6b2 100644 --- a/test/e2e/run-all.js +++ b/test/e2e/run-all.js @@ -107,6 +107,7 @@ async function main() { testPaths = [ ...(await getTestPathsForTestDir(path.join(__dirname, 'snaps'))), ...(await getTestPathsForTestDir(path.join(__dirname, 'accounts'))), + ...(await getTestPathsForTestDir(path.join(__dirname, 'flask'))), ]; } else if (rpc) { const testDir = path.join(__dirname, 'json-rpc'); @@ -134,8 +135,10 @@ async function main() { 'settings-add-snap-account-toggle.spec.js', 'test-snap-accounts.spec.js', 'test-create-snap-account.spec.js', + 'test-remove-accounts-snap.spec.js', 'test-snap-lifecycle.spec.js', 'test-snap-get-locale.spec.js', + 'ppom-blockaid-alert.spec.js', 'ppom-toggle-settings.spec.js', 'petnames.spec.js', ]; diff --git a/test/e2e/snaps/ppom-blockaid-alert.spec.js b/test/e2e/snaps/ppom-blockaid-alert.spec.js new file mode 100644 index 000000000000..a9fb855acb24 --- /dev/null +++ b/test/e2e/snaps/ppom-blockaid-alert.spec.js @@ -0,0 +1,265 @@ +const { strict: assert } = require('assert'); +const FixtureBuilder = require('../fixture-builder'); +const { + defaultGanacheOptions, + getWindowHandles, + openDapp, + unlockWallet, + withFixtures, +} = require('../helpers'); + +const { + CHAIN_IDS, + NETWORK_TYPES, +} = require('../../../shared/constants/network'); + +const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; +const selectedAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'; + +const mainnetProviderConfig = { + providerConfig: { + chainId: CHAIN_IDS.MAINNET, + nickname: '', + rpcUrl: '', + type: NETWORK_TYPES.MAINNET, + }, +}; + +async function mockInfura(mockServer) { + await mockServer + .forPost() + .withJsonBodyIncluding({ method: 'eth_estimateGas' }) + .thenCallback((req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: '0x5cec', + }, + }; + }); + await mockServer + .forPost() + .withJsonBodyIncluding({ method: 'eth_getBalance' }) + .thenCallback((req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: '0x55DE6A779BBAC0000', + }, + }; + }); + await mockServer + .forPost() + .withJsonBodyIncluding({ method: 'eth_getTransactionCount' }) + .thenCallback((req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: '0x115e89f', + }, + }; + }); + await mockServer + .forPost() + .withJsonBodyIncluding({ method: 'eth_blockNumber' }) + .thenCallback((req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: '0x1', + // result: '0x115e89f', + }, + }; + }); + await mockServer + .forPost() + .withJsonBodyIncluding({ method: 'eth_gasPrice' }) + .thenCallback((req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: '0x09184e72a000', + }, + }; + }); +} + +/** + * Tests various Blockaid PPOM security alerts. Data for the E2E test requests and responses are provided here: + * + * @see {@link https://wobbly-nutmeg-8a5.notion.site/MM-E2E-Testing-1e51b617f79240a49cd3271565c6e12d} + */ +describe('Confirmation Security Alert - Blockaid', function () { + it('should not show security alerts for benign requests', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkController(mainnetProviderConfig) + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfura, + title: this.test.title, + }, + + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + const testBenignConfigs = [ + { + logExpectedDetail: 'Benign 1', + method: 'eth_sendTransaction', + params: [ + { + from: selectedAddress, + data: '0x095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + to: '0x6b175474e89094c44da98b954eedeac495271d0f', + value: '0x0', + }, + ], + }, + { + logExpectedDetail: 'blur', + method: 'eth_signTypedData_v4', + params: [ + selectedAddress, + '{"types":{"Order":[{"name":"trader","type":"address"},{"name":"side","type":"uint8"},{"name":"matchingPolicy","type":"address"},{"name":"collection","type":"address"},{"name":"tokenId","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"paymentToken","type":"address"},{"name":"price","type":"uint256"},{"name":"listingTime","type":"uint256"},{"name":"expirationTime","type":"uint256"},{"name":"fees","type":"Fee[]"},{"name":"salt","type":"uint256"},{"name":"extraParams","type":"bytes"},{"name":"nonce","type":"uint256"}],"Fee":[{"name":"rate","type":"uint16"},{"name":"recipient","type":"address"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"Blur Exchange","version":"1.0","chainId":"1","verifyingContract":"0x000000000000ad05ccc4f10045630fb830b95127"},"primaryType":"Order","message":{"trader":"0xd854343f41b2138b686f2d3ba38402a9f7fb4337","side":"1","matchingPolicy":"0x0000000000dab4a563819e8fd93dba3b25bc3495","collection":"0xc4a5025c4563ad0acc09d92c2506e6744dad58eb","tokenId":"30420","amount":"1","paymentToken":"0x0000000000000000000000000000000000000000","price":"1000000000000000000","listingTime":"1679418212","expirationTime":"1680023012","salt":"154790208154270131670189427454206980105","extraParams":"0x01","nonce":"0"}}', + ], + }, + { + logExpectedDetail: 'seaport', + method: 'eth_signTypedData_v4', + params: [ + selectedAddress, + '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}]},"primaryType":"OrderComponents","domain":{"name":"Seaport","version":"1.4","chainId":"1","verifyingContract":"0x00000000000001ad428e4906aE43D8F9852d0dD6"},"message":{"offerer":"0xCaFca5eDFb361E8A39a735233f23DAf86CBeD5FC","offer":[{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"2500000000000000","endAmount":"2500000000000000"}],"consideration":[{"itemType":"2","token":"0xaA7200ee500dE2dcde75E996De83CBD73BCa9705","identifierOrCriteria":"11909","startAmount":"1","endAmount":"1","recipient":"0xCaFca5eDFb361E8A39a735233f23DAf86CBeD5FC"},{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"62500000000000","endAmount":"62500000000000","recipient":"0x0000a26b00c1F0DF003000390027140000fAa719"},{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"12500000000000","endAmount":"12500000000000","recipient":"0x8324BdEF2F30E08E368f2Fa2F14143cDCA77423D"}],"startTime":"1681835413","endTime":"1682094598","orderType":"0","zone":"0x004C00500000aD104D7DBd00e3ae0A5C00560C00","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"24446860302761739304752683030156737591518664810215442929812618382526293324216","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","totalOriginalConsiderationItems":"3","counter":"0"}}', + ], + }, + ]; + + for (const config of testBenignConfigs) { + const { logExpectedDetail, method, params } = config; + + // Send JSON-RPC request + const request = JSON.stringify({ + jsonrpc: '2.0', + method, + params, + }); + await driver.executeScript( + `window.transactionHash = window.ethereum.request(${request})`, + ); + + // Wait for confirmation pop-up + await driver.waitUntilXWindowHandles(3); + const windowHandles = await getWindowHandles(driver, 3); + await driver.switchToWindowWithTitle('MetaMask Notification'); + + const isPresent = await driver.isElementPresent(bannerAlertSelector); + assert.equal( + isPresent, + false, + `Banner alert unexpectedly found. \nExpected detail: ${logExpectedDetail}`, + ); + + // Wait for confirmation pop-up to close + await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.switchToWindow(windowHandles.dapp); + } + }, + ); + }); + + /** + * Disclaimer: this test may be missing checks for some reason types. e.g. blur, domain, and failed + */ + it('should show security alerts for malicious requests', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkController(mainnetProviderConfig) + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfura, + title: this.test.title, + }, + + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + const expectedTitle = 'This is a deceptive request'; + + const testMaliciousConfigs = [ + { + btnSelector: '#maliciousPermit', + expectedDescription: + 'If you approve this request, a third party known for scams might take all your assets.', + expectedReason: 'permit_farming', + }, + { + btnSelector: '#maliciousSeaport', + expectedDescription: + 'If you approve this request, someone can steal your assets listed on OpenSea.', + expectedReason: 'seaport_farming', + }, + { + btnSelector: '#maliciousTradeOrder', + expectedDescription: + 'If you approve this request, you might lose your assets.', + expectedReason: 'trade_order_farming', + }, + ]; + + for (const config of testMaliciousConfigs) { + const { expectedDescription, expectedReason, btnSelector } = config; + + // Click TestDapp button to send JSON-RPC request + await driver.clickElement(btnSelector); + + // Wait for confirmation pop-up + await driver.waitUntilXWindowHandles(3); + const windowHandles = await getWindowHandles(driver, 3); + await driver.switchToWindowWithTitle('MetaMask Notification'); + + const bannerAlert = await driver.findElement(bannerAlertSelector); + const bannerAlertText = await bannerAlert.getText(); + + assert( + bannerAlertText.includes(expectedTitle), + `Expected banner alert title: ${expectedTitle} \nExpected reason: ${expectedReason}\n`, + ); + assert( + bannerAlertText.includes(expectedDescription), + `Expected banner alert description: ${expectedDescription} \nExpected reason: ${expectedReason}\n`, + ); + + // Wait for confirmation pop-up to close + await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.switchToWindow(windowHandles.dapp); + } + }, + ); + }); +}); diff --git a/test/e2e/tests/import-flow.spec.js b/test/e2e/tests/import-flow.spec.js index 3991350a6407..23f51ec87f50 100644 --- a/test/e2e/tests/import-flow.spec.js +++ b/test/e2e/tests/import-flow.spec.js @@ -56,7 +56,7 @@ describe('Import flow', function () { const address = await driver.findElement( '.multichain-address-copy-button', ); - assert.equal(await address.getText(), '0x0Cc...afD3'); + assert.equal(await address.getText(), '0x0Cc52...7afD3'); await driver.clickElement('.mm-modal button[aria-label="Close"]'); diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index d41af246394d..5aa7a4426840 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -68,8 +68,8 @@ }, "KeyringController": { "isUnlocked": false, - "keyringTypes": "object", - "keyrings": "object" + "keyrings": "object", + "vault": "string" }, "LoggingController": { "logs": "object" }, "MetaMetricsController": { diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 98a7ecf58f11..b3fa6f2ba7e7 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -80,7 +80,6 @@ } }, "cachedBalances": "object", - "keyringTypes": "object", "keyrings": "object", "useNonceField": false, "usePhishDetect": true, diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js index 4ceaded9cee0..96697f354f23 100644 --- a/test/jest/mock-store.js +++ b/test/jest/mock-store.js @@ -260,7 +260,6 @@ export const createSwapsMockStore = () => { }, selectedAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', currentLocale: 'en', - keyringTypes: [KeyringType.imported, KeyringType.hdKeyTree], keyrings: [ { type: KeyringType.hdKeyTree, diff --git a/types/eth-keyring-controller.d.ts b/types/eth-keyring-controller.d.ts deleted file mode 100644 index ee7525bf394d..000000000000 --- a/types/eth-keyring-controller.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -declare module '@metamask/eth-keyring-controller' { - export class KeyringController { - signMessage: (...any) => any; - - signPersonalMessage: (...any) => any; - - signTypedMessage: (...any) => any; - - getKeyringForAccount: (address: string) => Promise<{ - type: string; - }>; - - getEncryptionPublicKey: (address: string) => Promise; - - decryptMessage: (...any) => any; - } -} diff --git a/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.scss b/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.scss index e9b958789754..7ba24fd41ceb 100644 --- a/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.scss +++ b/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.scss @@ -1,6 +1,6 @@ .unconnected-account-alert { &__content { - border-radius: 0; + border-radius: 0 !important; // To override the box border radius because of popover } &__footer { diff --git a/ui/components/app/connected-accounts-list/connected-accounts-list-item/connected-accounts-list-item.component.js b/ui/components/app/connected-accounts-list/connected-accounts-list-item/connected-accounts-list-item.component.js index 4ec24a7b48b0..5590edee8755 100644 --- a/ui/components/app/connected-accounts-list/connected-accounts-list-item/connected-accounts-list-item.component.js +++ b/ui/components/app/connected-accounts-list/connected-accounts-list-item/connected-accounts-list-item.component.js @@ -1,54 +1,114 @@ +import React from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; -import React, { PureComponent } from 'react'; -import Identicon from '../../../ui/identicon'; +import { useSelector } from 'react-redux'; +import { shortenAddress } from '../../../../helpers/utils/util'; -export default class ConnectedAccountsListItem extends PureComponent { - static contextTypes = { - t: PropTypes.func.isRequired, - }; +import { + AvatarAccount, + AvatarAccountSize, + AvatarAccountVariant, + Box, + Text, +} from '../../../component-library'; +import { getUseBlockie } from '../../../../selectors'; +import { + AlignItems, + BackgroundColor, + Display, + FlexDirection, + JustifyContent, + TextColor, + TextVariant, +} from '../../../../helpers/constants/design-system'; - static propTypes = { - address: PropTypes.string.isRequired, - className: PropTypes.string, - name: PropTypes.node.isRequired, - status: PropTypes.string, - action: PropTypes.node, - options: PropTypes.node, - }; - - static defaultProps = { - className: null, - options: null, - action: null, - }; - - render() { - const { address, className, name, status, action, options } = this.props; - - return ( -
-
- -
-

- {name} -

+export default function ConnectedAccountsListItem({ + address, + className = null, + name, + status, + action = null, + options = null, + backgroundColor, +}) { + const useBlockie = useSelector(getUseBlockie); + const containerbackgroundColor = + backgroundColor ?? BackgroundColor.backgroundDefault; + return ( + + + + + + + {name} + + {shortenAddress(address)} {status ? ( -

-    + {status} -

+ ) : null} +
+ {action} -
-
- {options} -
- ); - } + + + + {options} + + ); } +ConnectedAccountsListItem.propTypes = { + /** + * Address for Avatar + */ + address: PropTypes.string.isRequired, + /** + * An additional className to apply + */ + className: PropTypes.string, + /** + * Name of the account + */ + name: PropTypes.node.isRequired, + /** + * Status showing connected, not connected and active state + */ + status: PropTypes.string, + /** + * Action for account + */ + action: PropTypes.node, + /** + * Render Options button with actions + */ + options: PropTypes.node, + /** + * ContainerbackgroundColor showing highlighted state when not connected + */ + backgroundColor: PropTypes.string, +}; diff --git a/ui/components/app/connected-accounts-list/connected-accounts-list.component.js b/ui/components/app/connected-accounts-list/connected-accounts-list.component.js index 11563efc82d5..a1f0bbba2124 100644 --- a/ui/components/app/connected-accounts-list/connected-accounts-list.component.js +++ b/ui/components/app/connected-accounts-list/connected-accounts-list.component.js @@ -1,6 +1,15 @@ import PropTypes from 'prop-types'; import React, { PureComponent } from 'react'; -import { IconName } from '../../component-library'; +import { + BackgroundColor, + TextVariant, +} from '../../../helpers/constants/design-system'; +import { + ButtonLink, + ButtonLinkSize, + IconName, + Text, +} from '../../component-library'; import { MenuItem } from '../../ui/menu'; import ConnectedAccountsListItem from './connected-accounts-list-item'; import ConnectedAccountsListOptions from './connected-accounts-list-options'; @@ -80,16 +89,20 @@ export default class ConnectedAccountsList extends PureComponent { return ( connectAccount(accountToConnect.address)} - > - {t('connect')} - + + connectAccount(accountToConnect.address)} + size={ButtonLinkSize.Inherit} + > + {t('connect')} + + } /> ); @@ -116,12 +129,15 @@ export default class ConnectedAccountsList extends PureComponent { const { t } = this.context; return ( - this.switchAccount(address)} - > - {t('switchToThisAccount')} - + + this.switchAccount(address)} + size={ButtonLinkSize.Inherit} + > + {t('switchToThisAccount')} + + ); } @@ -139,7 +155,7 @@ export default class ConnectedAccountsList extends PureComponent { { const t = useI18nContext(); @@ -29,47 +44,60 @@ const ConnectedAccountsPermissions = ({ permissions }) => { ); return ( -
-

+ - {t('permissions')} -

+ + {expanded ? ( + + + {t('authorizedPermissions')}: + +
    + {permissionLabels.map(({ label }, idx) => ( +
  • + +
  • + ))} +
+
+ ) : null} + ); }; diff --git a/ui/components/app/connected-accounts-permissions/index.scss b/ui/components/app/connected-accounts-permissions/index.scss index 6e9ff8dc7c5b..9bf85e0780cb 100644 --- a/ui/components/app/connected-accounts-permissions/index.scss +++ b/ui/components/app/connected-accounts-permissions/index.scss @@ -1,37 +1,4 @@ .connected-accounts-permissions { - @include H7; - - display: flex; - flex-direction: column; - color: var(--color-text-alternative); - - strong { - font-weight: bold; - } - - p + p { - padding-top: 8px; - } - - &__header { - @include H6; - - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - cursor: pointer; - color: var(--color-text-default); - - button { - font-size: $font-size-paragraph; - background: none; - padding: 0; - margin-left: 8px; - color: var(--color-icon-default); - } - } - &__list { padding-block: 8px; height: 100%; diff --git a/ui/components/app/connected-sites-list/connected-snaps.js b/ui/components/app/connected-sites-list/connected-snaps.js new file mode 100644 index 000000000000..30e6fcfb48ad --- /dev/null +++ b/ui/components/app/connected-sites-list/connected-snaps.js @@ -0,0 +1,140 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { useHistory } from 'react-router-dom'; +import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-utils'; +import { useDispatch, useSelector } from 'react-redux'; +import { SnapCaveatType } from '@metamask/rpc-methods'; +import { Box, IconName, IconSize, Text } from '../../component-library'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { MenuItem } from '../../ui/menu'; +import { SNAPS_VIEW_ROUTE } from '../../../helpers/constants/routes'; +import SnapAvatar from '../snaps/snap-avatar'; +import { + AlignItems, + BlockSize, + Display, + FlexDirection, + JustifyContent, + TextVariant, +} from '../../../helpers/constants/design-system'; +import ConnectedAccountsListOptions from '../connected-accounts-list/connected-accounts-list-options'; +import { + getOriginOfCurrentTab, + getPermissionSubjects, +} from '../../../selectors'; +import { removePermissionsFor, updateCaveat } from '../../../store/actions'; + +export default function ConnectedSnaps({ connectedSubjects }) { + const [showOptions, setShowOptions] = useState(); + const t = useI18nContext(); + const history = useHistory(); + const dispatch = useDispatch(); + const subjects = useSelector(getPermissionSubjects); + const connectedOrigin = useSelector(getOriginOfCurrentTab); + + const onDisconnect = (snapId) => { + const caveatValue = + subjects[connectedOrigin].permissions[WALLET_SNAP_PERMISSION_KEY] + .caveats[0].value; + const newCaveatValue = { ...caveatValue }; + delete newCaveatValue[snapId]; + if (Object.keys(newCaveatValue).length > 0) { + dispatch( + updateCaveat( + connectedOrigin, + WALLET_SNAP_PERMISSION_KEY, + SnapCaveatType.SnapIds, + newCaveatValue, + ), + ); + } else { + dispatch( + removePermissionsFor({ + [connectedOrigin]: [WALLET_SNAP_PERMISSION_KEY], + }), + ); + } + }; + + const renderListItemOptions = (snapId) => { + return ( + setShowOptions()} + onShowOptions={() => setShowOptions(snapId)} + show={showOptions === snapId} + > + { + e.preventDefault(); + onDisconnect(snapId); + }} + > + {t('disconnect')} + + + history.push(`${SNAPS_VIEW_ROUTE}/${encodeURIComponent(snapId)}`) + } + > + {t('snapsSettings')} + + + ); + }; + + return ( + + {connectedSubjects.map((subject) => ( + + + + + {subject.name} + + + {renderListItemOptions(subject.origin)} + + ))} + + ); +} + +ConnectedSnaps.propTypes = { + /** + * Shape of ConnectedSnaps + */ + connectedSubjects: PropTypes.arrayOf( + PropTypes.shape({ + /** + * It should have a name for Snap + */ + name: PropTypes.string, + /** + * Origin of connected subject, in case of snaps it's snapId + */ + origin: PropTypes.string, + }), + ).isRequired, +}; diff --git a/ui/components/app/connected-sites-list/index.scss b/ui/components/app/connected-sites-list/index.scss index 7df824c5f9ad..01129de154c3 100644 --- a/ui/components/app/connected-sites-list/index.scss +++ b/ui/components/app/connected-sites-list/index.scss @@ -11,7 +11,6 @@ flex-direction: row; justify-content: space-between; align-items: center; - border-top: 1px solid var(--color-border-muted); padding: 16px 24px; & &-link-button { diff --git a/ui/components/app/connected-status-indicator/connected-status-indicator.js b/ui/components/app/connected-status-indicator/connected-status-indicator.js index c1a4eae402f0..991420145af9 100644 --- a/ui/components/app/connected-status-indicator/connected-status-indicator.js +++ b/ui/components/app/connected-status-indicator/connected-status-indicator.js @@ -2,9 +2,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { findKey } from 'lodash'; +import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-utils'; import { STATUS_CONNECTED, STATUS_CONNECTED_TO_ANOTHER_ACCOUNT, + STATUS_CONNECTED_TO_SNAP, STATUS_NOT_CONNECTED, } from '../../../helpers/constants/connected-sites'; import { @@ -15,6 +17,7 @@ import { useI18nContext } from '../../../hooks/useI18nContext'; import { getAddressConnectedSubjectMap, getOriginOfCurrentTab, + getPermissionsForActiveTab, getSelectedAddress, } from '../../../selectors'; import { ConnectedSiteMenu } from '../../multichain'; @@ -23,6 +26,13 @@ export default function ConnectedStatusIndicator({ onClick }) { const t = useI18nContext(); const selectedAddress = useSelector(getSelectedAddress); + + const permissionsForActiveTab = useSelector(getPermissionsForActiveTab); + + const activeWalletSnap = permissionsForActiveTab + .map((permission) => permission.key) + .includes(WALLET_SNAP_PERMISSION_KEY); + const addressConnectedSubjectMap = useSelector(getAddressConnectedSubjectMap); const originOfCurrentTab = useSelector(getOriginOfCurrentTab); @@ -35,6 +45,8 @@ export default function ConnectedStatusIndicator({ onClick }) { status = STATUS_CONNECTED; } else if (findKey(addressConnectedSubjectMap, originOfCurrentTab)) { status = STATUS_CONNECTED_TO_ANOTHER_ACCOUNT; + } else if (activeWalletSnap) { + status = STATUS_CONNECTED_TO_SNAP; } else { status = STATUS_NOT_CONNECTED; } @@ -42,7 +54,10 @@ export default function ConnectedStatusIndicator({ onClick }) { let globalMenuColor = Color.iconAlternative; if (status === STATUS_CONNECTED) { globalMenuColor = Color.successDefault; - } else if (status === STATUS_CONNECTED_TO_ANOTHER_ACCOUNT) { + } else if ( + status === STATUS_CONNECTED_TO_ANOTHER_ACCOUNT || + status === STATUS_CONNECTED_TO_SNAP + ) { globalMenuColor = BackgroundColor.backgroundDefault; } diff --git a/ui/components/app/detected-token/detected-token-address/detected-token-address.test.js b/ui/components/app/detected-token/detected-token-address/detected-token-address.test.js index de3fc18d6cfc..cf7c0bade7b6 100644 --- a/ui/components/app/detected-token/detected-token-address/detected-token-address.test.js +++ b/ui/components/app/detected-token/detected-token-address/detected-token-address.test.js @@ -14,6 +14,6 @@ describe('DetectedTokenAddress', () => { renderWithProvider(, store); expect(screen.getByText('Token address:')).toBeInTheDocument(); - expect(screen.getByText('0xc01...2a6f')).toBeInTheDocument(); + expect(screen.getByText('0xc011a...f2a6f')).toBeInTheDocument(); }); }); diff --git a/ui/components/app/detected-token/detected-token-details/detected-token-details.test.js b/ui/components/app/detected-token/detected-token-details/detected-token-details.test.js index f767d560fb2e..1c15944445fb 100644 --- a/ui/components/app/detected-token/detected-token-details/detected-token-details.test.js +++ b/ui/components/app/detected-token/detected-token-details/detected-token-details.test.js @@ -80,7 +80,7 @@ describe('DetectedTokenDetails', () => { expect(screen.getByText('0 SNX')).toBeInTheDocument(); expect(screen.getByText('$0')).toBeInTheDocument(); expect(screen.getByText('Token address:')).toBeInTheDocument(); - expect(screen.getByText('0xc01...2a6f')).toBeInTheDocument(); + expect(screen.getByText('0xc011a...f2a6f')).toBeInTheDocument(); expect(screen.getByText('From token lists:')).toBeInTheDocument(); expect(screen.getByText('Aave, Bancor')).toBeInTheDocument(); expect(screen.getByText('+ 10 more')).toBeInTheDocument(); diff --git a/ui/components/app/detected-token/detected-token.test.js b/ui/components/app/detected-token/detected-token.test.js index 34358ffbbd1b..b6cf8ad0ed21 100644 --- a/ui/components/app/detected-token/detected-token.test.js +++ b/ui/components/app/detected-token/detected-token.test.js @@ -19,9 +19,9 @@ describe('DetectedToken', () => { expect(screen.getByText('0 FSW')).toBeInTheDocument(); expect(screen.getAllByText('$0')).toHaveLength(3); expect(screen.getAllByText('Token address:')).toHaveLength(3); - expect(screen.getByText('0x514...86CA')).toBeInTheDocument(); - expect(screen.getByText('0xc00...6888')).toBeInTheDocument(); - expect(screen.getByText('0xfff...26DB')).toBeInTheDocument(); + expect(screen.getByText('0x51491...986CA')).toBeInTheDocument(); + expect(screen.getByText('0xc00e9...26888')).toBeInTheDocument(); + expect(screen.getByText('0xfffff...126DB')).toBeInTheDocument(); expect(screen.getAllByText('From token lists:')).toHaveLength(3); expect(screen.getByText('Aave, Bancor')).toBeInTheDocument(); expect(screen.getByText('+ 9 more')).toBeInTheDocument(); diff --git a/ui/components/app/modals/contract-details-modal/contract-details-modal.js b/ui/components/app/modals/contract-details-modal/contract-details-modal.js index 9a722c440d0b..fe6ebd423faf 100644 --- a/ui/components/app/modals/contract-details-modal/contract-details-modal.js +++ b/ui/components/app/modals/contract-details-modal/contract-details-modal.js @@ -28,7 +28,6 @@ import NftCollectionImage from '../../../ui/nft-collection-image/nft-collection- import { ButtonIcon, IconName, Text } from '../../../component-library'; import Name from '../../name/name'; import { usePetnamesEnabled } from '../../../../hooks/usePetnamesEnabled'; -import { DEFAULT_NAME_SOURCE_PRIORITY } from '../../../../../shared/constants/names'; export default function ContractDetailsModal({ onClose, @@ -219,11 +218,7 @@ export default function ContractDetailsModal({ {petnamesEnabled ? ( - + ) : (
- 0xc0f...4978 - - - “ - TestProposedName - ” - -
-
- -`; - -exports[`Name renders address with proposed name according to source priority 1`] = ` -
-
-
- - - 0xc0f...4978 - - - “ - TestProposedName - ” + 0xC0ffe...54977
@@ -73,23 +39,3 @@ exports[`Name renders address with saved name 1`] = `
`; - -exports[`Name renders address without proposed name 1`] = ` -
-
-
- - - 0xc0f...4979 - -
-
-
-`; diff --git a/ui/components/app/name/index.scss b/ui/components/app/name/index.scss index becff5641d2a..e180c5a68168 100644 --- a/ui/components/app/name/index.scss +++ b/ui/components/app/name/index.scss @@ -5,6 +5,7 @@ align-items: center; gap: 5px; font-size: 12px; + max-width: 100%; &__missing { background-color: var(--color-warning-muted); @@ -23,15 +24,17 @@ } &__value, - &__proposed { + &__name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &__value { color: var(--color-warning-default); } &__name { color: var(--color-info-default); } - - &__proposed { - font-style: italic; - } } diff --git a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap index 2f4fc518007d..f26559cf09eb 100644 --- a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap +++ b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap @@ -1,5 +1,229 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`NameDetails renders proposed names 1`] = ` + +
+
+
+
+
+ @@ -159,7 +289,7 @@ export default function NameDetails({ id="address" className="name-details__address" label={t('nameAddressLabel')} - value={value} + value={formattedValue} marginBottom={4} disabled endAccessory={ @@ -167,7 +297,7 @@ export default function NameDetails({ display={Display.Flex} iconName={copiedAddress ? IconName.CopySuccess : IconName.Copy} size={ButtonIconSize.Sm} - onClick={() => handleCopyAddress(value)} + onClick={handleCopyClick} color={IconColor.iconMuted} ariaLabel={t('copyAddress')} /> diff --git a/ui/components/app/name/name.stories.tsx b/ui/components/app/name/name.stories.tsx index f0815764e22a..1bee178eaa0f 100644 --- a/ui/components/app/name/name.stories.tsx +++ b/ui/components/app/name/name.stories.tsx @@ -5,8 +5,7 @@ import { Provider } from 'react-redux'; import configureStore from '../../../store/store'; import Name from './name'; -const addressProposedMock = '0xc0ffee254729296a45a3885639AC7E10F9d54979'; -const addressNoProposedMock = '0xc0ffee254729296a45a3885639AC7E10F9d54978'; +const addressNoSavedNameMock = '0xc0ffee254729296a45a3885639AC7E10F9d54978'; const addressSavedNameMock = '0xc0ffee254729296a45a3885639AC7E10F9d54977'; const chainIdMock = '0x1'; @@ -17,25 +16,57 @@ const storeMock = configureStore({ }, names: { [NameType.ETHEREUM_ADDRESS]: { - [addressProposedMock]: { + [addressNoSavedNameMock]: { [chainIdMock]: { proposedNames: { - ens: ['test.eth'], - etherscan: ['TestContract'], - token: ['TestToken'], - lens: ['test.lens'], + ens: { + proposedNames: ['test.eth'], + lastRequestTime: 123, + retryDelay: null, + }, + etherscan: { + proposedNames: ['TestContract'], + lastRequestTime: 123, + retryDelay: null, + }, + token: { + proposedNames: ['Test Token'], + lastRequestTime: 123, + retryDelay: null, + }, + lens: { + proposedNames: ['test.lens'], + lastRequestTime: 123, + retryDelay: null, + }, }, }, }, [addressSavedNameMock]: { [chainIdMock]: { proposedNames: { - ens: ['test.eth'], - etherscan: ['TestContract'], - token: ['TestToken'], - lens: ['test.lens'], + ens: { + proposedNames: ['test.eth'], + lastRequestTime: 123, + retryDelay: null, + }, + etherscan: { + proposedNames: ['TestContract'], + lastRequestTime: 123, + retryDelay: null, + }, + token: { + proposedNames: ['Test Token'], + lastRequestTime: 123, + retryDelay: null, + }, + lens: { + proposedNames: ['test.lens'], + lastRequestTime: 123, + retryDelay: null, + }, }, - name: 'TestToken', + name: 'Test Token', sourceId: 'token', }, }, @@ -51,10 +82,9 @@ const storeMock = configureStore({ }); /** - * Displays the saved or proposed name for a raw value such as an Ethereum address.

- * Proposed names are populated in the state using the `NameController` and the attached `NameProvider` instances.

- * These name providers use multiple sources such as ENS, Etherscan, and the Blockchain itself.

- * Clicking the component will display a modal to select a proposed name or enter a custom name. + * Displays the saved name for a raw value such as an Ethereum address.

+ * Clicking the component will display a modal to select a proposed name or enter a custom name.

+ * Proposed names are populated in the state using the `NameController` and the attached `NameProvider` instances. */ export default { title: 'Components/App/Name', @@ -70,16 +100,6 @@ export default { description: `The type of value.

Limited to the values in the \`NameType\` enum.`, }, - sourcePriority: { - control: 'object', - description: `The order of priority to use when choosing which proposed name to display.

- The available source IDs are defined by the \`NameProvider\` instances passed to the \`NameController\`.

- Current options include:

- \`ens\`
- \`etherscan\`
- \`lens\`
- \`token\``, - }, disableEdit: { control: 'boolean', description: `Whether to prevent the modal from opening when the component is clicked.`, @@ -87,68 +107,31 @@ export default { defaultValue: { summary: false }, }, }, - disableUpdate: { - control: 'boolean', - description: `Whether to disable updating the proposed names on render.`, - table: { - defaultValue: { summary: false }, - }, - }, - updateDelay: { - control: 'number', - description: `The minimum number of seconds to wait between updates of the proposed names on render.`, - table: { - defaultValue: { summary: 300 }, - }, - }, }, args: { - value: addressProposedMock, + value: addressNoSavedNameMock, type: NameType.ETHEREUM_ADDRESS, - sourcePriority: ['ens'], disableEdit: false, - disableUpdate: false, - updateDelay: 300, }, decorators: [(story) => {story()}], }; // eslint-disable-next-line jsdoc/require-param /** - * A proposed name matching the value and type has been found in the state.

- * Which proposed name is displayed is configurable by the `sourcePriority` property. + * No name has been saved for the value and type. */ export const DefaultStory = (args) => { return ; }; -DefaultStory.storyName = 'Proposed Name'; - -/** No proposed name matching the value and type has been found in the state. */ -export const NoProposedNameStory = () => { - return ( - - ); -}; - -NoProposedNameStory.storyName = 'No Proposed Name'; +DefaultStory.storyName = 'No Saved Name'; /** * A name was previously saved for this value and type.

* The component will still display a modal when clicked to edit the name. */ export const SavedNameStory = () => { - return ( - - ); + return ; }; SavedNameStory.storyName = 'Saved Name'; @@ -161,7 +144,6 @@ export const EditDisabledStory = () => { ); diff --git a/ui/components/app/name/name.test.tsx b/ui/components/app/name/name.test.tsx index 96c59637d598..3f845b41a6aa 100644 --- a/ui/components/app/name/name.test.tsx +++ b/ui/components/app/name/name.test.tsx @@ -2,23 +2,20 @@ import * as React from 'react'; import { NameType } from '@metamask/name-controller'; import configureStore from 'redux-mock-store'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; -import { updateProposedNames } from '../../../store/actions'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../shared/constants/metametrics'; import Name from './name'; -jest.mock('../../../store/actions', () => ({ - updateProposedNames: jest.fn(), -})); - jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), useDispatch: () => jest.fn(), })); -const ADDRESS_NO_PROPOSED_NAME_MOCK = - '0xc0ffee254729296a45a3885639AC7E10F9d54979'; -const ADDRESS_PROPOSED_NAME_MOCK = '0xc0ffee254729296a45a3885639AC7E10F9d54978'; -const ADDRESS_SAVED_NAME_MOCK = '0xc0ffee254729296a45a3885639AC7E10F9d54977'; -const ADDRESS_LAST_UPDATED_MOCK = '0xc0ffee254729296a45a3885639AC7E10F9d54976'; +const ADDRESS_NO_SAVED_NAME_MOCK = '0xc0ffee254729296a45a3885639ac7e10f9d54977'; +const ADDRESS_SAVED_NAME_MOCK = '0xc0ffee254729296a45a3885639ac7e10f9d54979'; const CHAIN_ID_MOCK = '0x1'; const PROPOSED_NAME_MOCK = 'TestProposedName'; const PROPOSED_NAME_2_MOCK = 'TestProposedName2'; @@ -27,8 +24,6 @@ const SOURCE_ID_MOCK = 'TestSourceId'; const SOURCE_ID_2_MOCK = 'TestSourceId2'; const SOURCE_ID_EMPTY_MOCK = 'TestSourceIdEmpty'; const SOURCE_ID_UNDEFINED_MOCK = 'TestSourceIdUndefined'; -const LAST_UPDATED_MOCK = 150; -const DEFAULT_UPDATE_DELAY = 300; const STATE_MOCK = { metamask: { @@ -37,7 +32,7 @@ const STATE_MOCK = { }, names: { [NameType.ETHEREUM_ADDRESS]: { - [ADDRESS_PROPOSED_NAME_MOCK]: { + [ADDRESS_NO_SAVED_NAME_MOCK]: { [CHAIN_ID_MOCK]: { proposedNames: { [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK], @@ -49,15 +44,10 @@ const STATE_MOCK = { }, [ADDRESS_SAVED_NAME_MOCK]: { [CHAIN_ID_MOCK]: { - proposedNames: null, + proposedNames: { [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK] }, name: SAVED_NAME_MOCK, }, }, - [ADDRESS_LAST_UPDATED_MOCK]: { - [CHAIN_ID_MOCK]: { - proposedNamesLastUpdated: LAST_UPDATED_MOCK, - }, - }, }, }, }, @@ -65,31 +55,16 @@ const STATE_MOCK = { describe('Name', () => { const store = configureStore()(STATE_MOCK); - const updateProposedNamesMock = jest.mocked(updateProposedNames); beforeEach(() => { jest.resetAllMocks(); }); - it('renders address without proposed name', () => { - const { container } = renderWithProvider( - , - store, - ); - - expect(container).toMatchSnapshot(); - }); - - it('renders address with proposed name', () => { + it('renders address with no saved name', () => { const { container } = renderWithProvider( , store, ); @@ -99,112 +74,35 @@ describe('Name', () => { it('renders address with saved name', () => { const { container } = renderWithProvider( - , - store, - ); - - expect(container).toMatchSnapshot(); - }); - - it('renders address with proposed name according to source priority', () => { - const { container } = renderWithProvider( - , + , store, ); expect(container).toMatchSnapshot(); }); - it('updates proposed names on render', () => { - renderWithProvider( - , - store, - ); - - expect(updateProposedNamesMock).toHaveBeenCalledWith({ - value: ADDRESS_NO_PROPOSED_NAME_MOCK, - type: NameType.ETHEREUM_ADDRESS, - }); - }); - - it('does not update proposed names on render if disabled', () => { - renderWithProvider( - , - store, - ); - - expect(updateProposedNamesMock).toHaveBeenCalledTimes(0); - }); - - it.each([ - ['default', undefined], - ['custom', 10000], - ])( - 'does not update proposed names on subsequent render until %s delay has elapsed', - async (_, updateDelay) => { - jest - .spyOn(Date, 'now') - .mockReturnValue( - (LAST_UPDATED_MOCK + (updateDelay ?? DEFAULT_UPDATE_DELAY) - 1) * - 1000, - ); - - const { rerender } = renderWithProvider( - , + describe('metrics', () => { + it.each([ + ['saved', ADDRESS_SAVED_NAME_MOCK, true], + ['not saved', ADDRESS_NO_SAVED_NAME_MOCK, false], + ])('sends displayed event with %s name', async (_, value, hasPetname) => { + const trackEventMock = jest.fn(); + + renderWithProvider( + + + , store, ); - expect(updateProposedNamesMock).toHaveBeenCalledTimes(0); - - rerender( - , - ); - - expect(updateProposedNamesMock).toHaveBeenCalledTimes(0); - - jest - .spyOn(Date, 'now') - .mockReturnValue( - (LAST_UPDATED_MOCK + (updateDelay ?? DEFAULT_UPDATE_DELAY)) * 1000, - ); - - rerender( - , - ); - - expect(updateProposedNamesMock).toHaveBeenCalledTimes(1); - }, - ); + expect(trackEventMock).toHaveBeenCalledWith({ + event: MetaMetricsEventName.PetnameDisplayed, + category: MetaMetricsEventCategory.Petnames, + properties: { + petname_category: NameType.ETHEREUM_ADDRESS, + has_petname: hasPetname, + }, + }); + }); + }); }); diff --git a/ui/components/app/name/name.tsx b/ui/components/app/name/name.tsx index c27a0cf2b79c..c239630e146e 100644 --- a/ui/components/app/name/name.tsx +++ b/ui/components/app/name/name.tsx @@ -1,67 +1,66 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; import { NameType } from '@metamask/name-controller'; import classnames from 'classnames'; -import { useDispatch } from 'react-redux'; +import { toChecksumAddress } from 'ethereumjs-util'; import { Icon, IconName, IconSize } from '../../component-library'; import { shortenAddress } from '../../../helpers/utils/util'; import { useName } from '../../../hooks/useName'; -import { updateProposedNames } from '../../../store/actions'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../shared/constants/metametrics'; import NameDetails from './name-details/name-details'; -const DEFAULT_UPDATE_DELAY = 60 * 5; // 5 Minutes - export interface NameProps { /** Whether to prevent the modal from opening when the component is clicked. */ disableEdit?: boolean; - /** Whether to disable updating the proposed names on render. */ - disableUpdate?: boolean; - - /** The order of source IDs to prioritise when choosing which proposed name to display. */ - sourcePriority?: string[]; + /** Whether this is being rendered inside the NameDetails modal. */ + internal?: boolean; /** The type of value, e.g. NameType.ETHEREUM_ADDRESS */ type: NameType; - /** The minimum number of seconds to wait between updates of the proposed names on render. */ - updateDelay?: number; - /** The raw value to display the name of. */ value: string; } +function formatValue(value: string, type: NameType): string { + switch (type) { + case NameType.ETHEREUM_ADDRESS: + return shortenAddress(toChecksumAddress(value)); + + default: + return value; + } +} + export default function Name({ value, type, - sourcePriority, disableEdit, - updateDelay, - disableUpdate, + internal, }: NameProps) { const [modalOpen, setModalOpen] = useState(false); - const dispatch = useDispatch(); + const trackEvent = useContext(MetaMetricsContext); - const { name, proposedNames, proposedNamesLastUpdated } = useName( - value, - type, - ); + const { name } = useName(value, type); useEffect(() => { - if (disableUpdate) { - return; - } - - const nowMilliseconds = Date.now(); - const nowSeconds = Math.floor(nowMilliseconds / 1000); - const secondsSinceLastUpdate = nowSeconds - (proposedNamesLastUpdated ?? 0); - const delay = updateDelay ?? DEFAULT_UPDATE_DELAY; - - if (secondsSinceLastUpdate < delay) { + if (internal) { return; } - dispatch(updateProposedNames({ value, type })); - }); + trackEvent({ + event: MetaMetricsEventName.PetnameDisplayed, + category: MetaMetricsEventCategory.Petnames, + properties: { + petname_category: type, + has_petname: Boolean(name?.length), + }, + }); + }, []); const handleClick = useCallback(() => { setModalOpen(true); @@ -71,34 +70,14 @@ export default function Name({ setModalOpen(false); }, [setModalOpen]); - const proposedName = useMemo((): string | undefined => { - for (const sourceId of sourcePriority ?? []) { - const sourceProposedNames = proposedNames[sourceId] ?? []; - - if (sourceProposedNames.length) { - return sourceProposedNames[0]; - } - } - - return undefined; - }, [proposedNames, sourcePriority]); - - const formattedValue = - type === NameType.ETHEREUM_ADDRESS ? shortenAddress(value) : value; - + const formattedValue = formatValue(value, type); const hasName = Boolean(name); - const hasProposedName = Boolean(proposedName); const iconName = hasName ? IconName.Save : IconName.Warning; return (
{!disableEdit && modalOpen && ( - + )}
{!hasName && {formattedValue}} {hasName && {name}} - {!hasName && hasProposedName && ( - “{proposedName}” - )}
); diff --git a/ui/components/app/nft-details/__snapshots__/nft-details.test.js.snap b/ui/components/app/nft-details/__snapshots__/nft-details.test.js.snap index 8b1a3aff08da..0d9c362cc85d 100644 --- a/ui/components/app/nft-details/__snapshots__/nft-details.test.js.snap +++ b/ui/components/app/nft-details/__snapshots__/nft-details.test.js.snap @@ -155,7 +155,7 @@ exports[`NFT Details should match minimal props and state snapshot 1`] = `
- 0xDc7...6414 + 0xDc738...06414
{ e.preventDefault(); history.push(`${SECURITY_ROUTE}#opensea-api`); + onActionButtonClick?.(); }} > {t('newNFTDetectedInImportNFTsMessage', [ @@ -25,3 +27,7 @@ export default function NftsDetectionNoticeImportNFTs() { ); } + +NftsDetectionNoticeImportNFTs.propTypes = { + onActionButtonClick: PropTypes.func.isRequired, +}; diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.js b/ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.js index 1018ed2c3b86..e7562dc92a48 100644 --- a/ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.js +++ b/ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.js @@ -48,9 +48,7 @@ export default function SignatureRequestSIWEHeader({ {fromAccount && ( )} diff --git a/ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap b/ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap index 055cde8c011c..780adfe87f04 100644 --- a/ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap +++ b/ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap @@ -365,7 +365,7 @@ exports[`Signature Request Component render should match snapshot when we are us
- 0xCD2...D826 + 0xCD2a3...DD826
@@ -441,7 +441,7 @@ exports[`Signature Request Component render should match snapshot when we are us
- 0xDea...beeF + 0xDeaDb...DbeeF
@@ -576,7 +576,7 @@ exports[`Signature Request Component render should match snapshot when we are us
- 0xbBb...BBbB + 0xbBbBB...bBBbB
@@ -652,7 +652,7 @@ exports[`Signature Request Component render should match snapshot when we are us
- 0xB0B...Ea57 + 0xB0Bda...bEa57
@@ -728,7 +728,7 @@ exports[`Signature Request Component render should match snapshot when we are us
- 0xB0B...0000 + 0xB0B0b...00000
@@ -1140,7 +1140,7 @@ exports[`Signature Request Component render should match snapshot when we want t
- 0xCD2...D826 + 0xCD2a3...DD826
@@ -1216,7 +1216,7 @@ exports[`Signature Request Component render should match snapshot when we want t
- 0xDea...beeF + 0xDeaDb...DbeeF
@@ -1351,7 +1351,7 @@ exports[`Signature Request Component render should match snapshot when we want t
- 0xbBb...BBbB + 0xbBbBB...bBBbB
@@ -1427,7 +1427,7 @@ exports[`Signature Request Component render should match snapshot when we want t
- 0xB0B...Ea57 + 0xB0Bda...bEa57
@@ -1503,7 +1503,7 @@ exports[`Signature Request Component render should match snapshot when we want t
- 0xB0B...0000 + 0xB0B0b...00000
diff --git a/ui/components/app/signature-request/signature-request-data/signature-request-data.js b/ui/components/app/signature-request/signature-request-data/signature-request-data.js index dac3dd2928da..e6a4a97d12e6 100644 --- a/ui/components/app/signature-request/signature-request-data/signature-request-data.js +++ b/ui/components/app/signature-request/signature-request-data/signature-request-data.js @@ -22,7 +22,6 @@ import { sanitizeString } from '../../../../helpers/utils/util'; import { Box, Text } from '../../../component-library'; import { usePetnamesEnabled } from '../../../../hooks/usePetnamesEnabled'; import Name from '../../name/name'; -import { DEFAULT_NAME_SOURCE_PRIORITY } from '../../../../../shared/constants/names'; function SignatureRequestData({ data }) { const identities = useSelector(getMemoizedMetaMaskIdentities); @@ -71,11 +70,7 @@ function SignatureRequestData({ data }) { className="signature-request-data__node__value__address" > {petnamesEnabled ? ( - + ) : (
{ store, ); - expect(getByText('0xbBb...BBbB')).toBeInTheDocument(); + expect(getByText('0xbBbBB...bBBbB')).toBeInTheDocument(); }); it('should render Identicon for second wallet in "to" array of objects', () => { @@ -289,7 +289,7 @@ describe('Signature Request Data', () => { store, ); - expect(getByText('0xB0B...Ea57')).toBeInTheDocument(); + expect(getByText('0xB0Bda...bEa57')).toBeInTheDocument(); }); it('should render Identicon for third wallet in "to" array of objects', () => { @@ -307,8 +307,7 @@ describe('Signature Request Data', () => { , store, ); - - expect(getByText('0xB0B...0000')).toBeInTheDocument(); + expect(getByText('0xB0B0b...00000')).toBeInTheDocument(); }); it('should escape RTL character in label or value', () => { diff --git a/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap b/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap index 37ad62f03ea4..8fcb3d670805 100644 --- a/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap +++ b/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap @@ -96,7 +96,7 @@ exports[`AccountListItem renders AccountListItem component and shows account nam

- 0x0DC...E7bc + 0x0DCD5...3E7bc

{ it('should show the address in the account button for multichain', () => { const { getByText } = render({ showAddress: true }); - expect(getByText('0x0DC...E7bc')).toBeInTheDocument(); + expect(getByText('0x0DCD5...3E7bc')).toBeInTheDocument(); }); }); diff --git a/ui/components/multichain/connected-site-menu/connected-site-menu.js b/ui/components/multichain/connected-site-menu/connected-site-menu.js index 16d7a345d9ee..792621763e43 100644 --- a/ui/components/multichain/connected-site-menu/connected-site-menu.js +++ b/ui/components/multichain/connected-site-menu/connected-site-menu.js @@ -4,6 +4,7 @@ import classNames from 'classnames'; import { useSelector } from 'react-redux'; import { STATUS_CONNECTED_TO_ANOTHER_ACCOUNT, + STATUS_CONNECTED_TO_SNAP, STATUS_NOT_CONNECTED, } from '../../../helpers/constants/connected-sites'; import { @@ -35,6 +36,9 @@ export const ConnectedSiteMenu = ({ }) => { const t = useI18nContext(); const selectedAccount = useSelector(getSelectedIdentity); + const isConnectedtoOtherAccountOrSnap = + status === STATUS_CONNECTED_TO_ANOTHER_ACCOUNT || + status === STATUS_CONNECTED_TO_SNAP; return ( } > diff --git a/ui/components/multichain/import-nfts-modal/import-nfts-modal.js b/ui/components/multichain/import-nfts-modal/import-nfts-modal.js index 51c7eb89ff03..78b36e9ca2fe 100644 --- a/ui/components/multichain/import-nfts-modal/import-nfts-modal.js +++ b/ui/components/multichain/import-nfts-modal/import-nfts-modal.js @@ -24,8 +24,8 @@ import { useI18nContext } from '../../../hooks/useI18nContext'; import { getCurrentChainId, getIsMainnet, + getOpenSeaEnabled, getSelectedAddress, - getUseNftDetection, } from '../../../selectors'; import { addNftVerifyOwnership, @@ -52,12 +52,14 @@ import { ModalOverlay, } from '../../component-library'; import Tooltip from '../../ui/tooltip'; +import { useNftsCollections } from '../../../hooks/useNftsCollections'; +import { checkTokenIdExists } from '../../../helpers/utils/util'; export const ImportNftsModal = ({ onClose }) => { const t = useI18nContext(); const history = useHistory(); const dispatch = useDispatch(); - const useNftDetection = useSelector(getUseNftDetection); + const isDisplayNFTMediaToggleEnabled = useSelector(getOpenSeaEnabled); const isMainnet = useSelector(getIsMainnet); const nftsDropdownState = useSelector(getNftsDropdownState); const selectedAddress = useSelector(getSelectedAddress); @@ -67,12 +69,15 @@ export const ImportNftsModal = ({ onClose }) => { tokenId: initialTokenId, ignoreErc20Token, } = useSelector((state) => state.appState.importNftsModal); - + const existingNfts = useNftsCollections(); const [nftAddress, setNftAddress] = useState(initialTokenAddress ?? ''); const [tokenId, setTokenId] = useState(initialTokenId ?? ''); const [disabled, setDisabled] = useState(true); const [nftAddFailed, setNftAddFailed] = useState(false); const trackEvent = useContext(MetaMetricsContext); + const [nftAddressValidationError, setNftAddressValidationError] = + useState(null); + const [duplicateTokenIdError, setDuplicateTokenIdError] = useState(null); const handleAddNft = async () => { try { @@ -129,12 +134,32 @@ export const ImportNftsModal = ({ onClose }) => { }; const validateAndSetAddress = (val) => { + setNftAddressValidationError(null); + if (val && !isValidHexAddress(val)) { + setNftAddressValidationError(t('invalidAddress')); + } setDisabled(!isValidHexAddress(val) || !tokenId); setNftAddress(val); }; const validateAndSetTokenId = (val) => { - setDisabled(!isValidHexAddress(nftAddress) || !val || isNaN(Number(val))); + setDuplicateTokenIdError(null); + // Check if tokenId is already imported + const tokenIdExists = checkTokenIdExists( + nftAddress, + val, + existingNfts.collections, + ); + if (tokenIdExists) { + setDuplicateTokenIdError(t('nftAlreadyAdded')); + } + setDisabled( + !isValidHexAddress(nftAddress) || + !val || + isNaN(Number(val)) || + tokenIdExists, + ); + setTokenId(val); }; @@ -156,9 +181,9 @@ export const ImportNftsModal = ({ onClose }) => { {t('importNFT')} - {isMainnet && !useNftDetection ? ( + {isMainnet && !isDisplayNFTMediaToggleEnabled ? ( - + ) : null} {nftAddFailed && ( @@ -210,6 +235,8 @@ export const ImportNftsModal = ({ onClose }) => { validateAndSetAddress(e.target.value); setNftAddFailed(false); }} + helpText={nftAddressValidationError} + error={Boolean(nftAddressValidationError)} /> @@ -242,6 +269,8 @@ export const ImportNftsModal = ({ onClose }) => { validateAndSetTokenId(e.target.value); setNftAddFailed(false); }} + helpText={duplicateTokenIdError} + error={duplicateTokenIdError} /> diff --git a/ui/components/multichain/import-nfts-modal/import-nfts-modal.test.js b/ui/components/multichain/import-nfts-modal/import-nfts-modal.test.js index c9180a15278c..778591258d29 100644 --- a/ui/components/multichain/import-nfts-modal/import-nfts-modal.test.js +++ b/ui/components/multichain/import-nfts-modal/import-nfts-modal.test.js @@ -189,4 +189,20 @@ describe('ImportNftsModal', () => { expect(useHistory().push).toHaveBeenCalledWith(DEFAULT_ROUTE); }); + + it('should set error message when address invalid', () => { + const onClose = jest.fn(); + const { getByText, getByPlaceholderText } = renderWithProvider( + , + store, + ); + + const addressInput = getByPlaceholderText('0x...'); + fireEvent.change(addressInput, { + target: { value: INVALID_ADDRESS }, + }); + + const errorMessage = getByText('Invalid address'); + expect(errorMessage).toBeInTheDocument(); + }); }); diff --git a/ui/components/ui/form-combo-field/index.scss b/ui/components/ui/form-combo-field/index.scss index 37d8325654df..efb2e097bdeb 100644 --- a/ui/components/ui/form-combo-field/index.scss +++ b/ui/components/ui/form-combo-field/index.scss @@ -54,4 +54,11 @@ color: var(--color-text-alternative); font-size: 12px; } + + &__option-primary, + &__option-secondary { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } } diff --git a/ui/contexts/metametrics.js b/ui/contexts/metametrics.js index ea9444b3b6d1..b26229ca197d 100644 --- a/ui/contexts/metametrics.js +++ b/ui/contexts/metametrics.js @@ -37,7 +37,7 @@ import { trackMetaMetricsEvent, trackMetaMetricsPage } from '../store/actions'; /** * @typedef {( * payload: UIMetricsEventPayload, - * options: MetaMetricsEventOptions + * options?: MetaMetricsEventOptions * ) => Promise} UITrackEventMethod */ diff --git a/ui/helpers/constants/connected-sites.js b/ui/helpers/constants/connected-sites.js index 2a5579fcda93..793f36787108 100644 --- a/ui/helpers/constants/connected-sites.js +++ b/ui/helpers/constants/connected-sites.js @@ -2,3 +2,4 @@ export const STATUS_CONNECTED = 'STATUS_CONNECTED'; export const STATUS_CONNECTED_TO_ANOTHER_ACCOUNT = 'STATUS_CONNECTED_TO_ANOTHER_ACCOUNT'; export const STATUS_NOT_CONNECTED = 'STATUS_NOT_CONNECTED'; +export const STATUS_CONNECTED_TO_SNAP = 'STATUS_CONNECTED_TO_SNAP'; diff --git a/ui/helpers/constants/settings.js b/ui/helpers/constants/settings.js index 108292274a85..f557b3fc9275 100644 --- a/ui/helpers/constants/settings.js +++ b/ui/helpers/constants/settings.js @@ -222,8 +222,8 @@ export const SETTINGS_CONSTANTS = [ }, { tabMessage: (t) => t('securityAndPrivacy'), - sectionMessage: (t) => t('displayNftMedia'), - descriptionMessage: (t) => t('displayNftMediaDesc'), + sectionMessage: (t) => t('enableOpenSeaAPI'), + descriptionMessage: (t) => t('enableOpenSeaAPIDescription'), route: `${SECURITY_ROUTE}#opensea-api`, icon: 'fa fa-lock', }, diff --git a/ui/helpers/utils/settings-search.test.js b/ui/helpers/utils/settings-search.test.js index 59f15f018edc..cde4452d6ab3 100644 --- a/ui/helpers/utils/settings-search.test.js +++ b/ui/helpers/utils/settings-search.test.js @@ -101,10 +101,10 @@ const t = (key) => { return 'Autodetect tokens'; case 'autoDetectTokensDescription': return 'We use third-party APIs to detect and display new tokens sent to your wallet. Turn off if you don’t want the app to pull data from those services.'; - case 'displayNftMedia': - return 'Display NFT media'; - case 'displayNftMediaDesc': - return "Displaying NFT media and data exposes your IP address to OpenSea or other third parties. NFT autodetection relies on this feature, and won't be available when it is turned off."; + case 'enableOpenSeaAPI': + return 'Enable OpenSea API'; + case 'enableOpenSeaAPIDescription': + return "Use OpenSea's API to fetch NFT data. NFT auto-detection relies on OpenSea's API, and will not be available when this is turned off."; case 'useNftDetection': return 'Autodetect NFTs'; case 'useNftDetectionDescriptionText': diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js index 20ba3656b39d..f80d87770274 100644 --- a/ui/helpers/utils/util.js +++ b/ui/helpers/utils/util.js @@ -10,8 +10,11 @@ import bowser from 'bowser'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import { getSnapPrefix } from '@metamask/snaps-utils'; import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/rpc-methods'; +// eslint-disable-next-line import/no-duplicates import { isObject } from '@metamask/utils'; ///: END:ONLY_INCLUDE_IN +// eslint-disable-next-line import/no-duplicates +import { isStrictHexString } from '@metamask/utils'; import { CHAIN_IDS, NETWORK_TYPES } from '../../../shared/constants/network'; import { toChecksumHexAddress, @@ -30,8 +33,10 @@ import { SNAPS_METADATA, } from '../../../shared/constants/snaps'; ///: END:ONLY_INCLUDE_IN - // formatData :: ( date: ) -> String +import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; +import { hexToDecimal } from '../../../shared/modules/conversion.utils'; + export function formatDate(date, format = "M/d/y 'at' T") { if (!date) { return ''; @@ -207,7 +212,7 @@ export function getRandomFileName() { * Returns the given address if it is no longer than 10 characters. * Shortened addresses are 13 characters long. * - * Example output: 0xabcd...1234 + * Example output: 0xabcde...12345 * * @param {string} address - The address to shorten. * @returns {string} The shortened address, or the original if it was no longer @@ -631,3 +636,34 @@ export const getNetworkNameFromProviderType = (providerName) => { export const isAbleToExportAccount = (keyringType = '') => { return !keyringType.includes('Hardware') && !keyringType.includes('Snap'); }; + +/** + * Checks if a tokenId in Hex or decimal format already exists in an object. + * + * @param {string} address - collection address. + * @param {string} tokenId - tokenId to search for + * @param {*} obj - object to look into + * @returns {boolean} `false` if tokenId does not already exist. + */ +export const checkTokenIdExists = (address, tokenId, obj) => { + // check if input tokenId is hexadecimal + // If it is convert to decimal and compare with existing tokens + const isHex = isStrictHexString(tokenId); + let convertedTokenId = tokenId; + if (isHex) { + // Convert to decimal + convertedTokenId = hexToDecimal(tokenId); + } + + if (obj[address]) { + const value = obj[address]; + return lodash.some(value.nfts, (nft) => { + return ( + nft.address === address && + (isEqualCaseInsensitive(nft.tokenId, tokenId) || + isEqualCaseInsensitive(nft.tokenId, convertedTokenId.toString())) + ); + }); + } + return false; +}; diff --git a/ui/helpers/utils/util.test.js b/ui/helpers/utils/util.test.js index 77ac796c2d9d..652efcd9e7f2 100644 --- a/ui/helpers/utils/util.test.js +++ b/ui/helpers/utils/util.test.js @@ -929,4 +929,95 @@ describe('util', () => { expect(util.getNetworkNameFromProviderType('rpc')).toStrictEqual(''); }); }); + + describe('checkTokenIdExists()', () => { + const data = { + '0x2df920B180c58766951395c26ecF1EC2063490Fa': { + collectionName: 'Numbers', + nfts: [ + { + address: '0x2df920B180c58766951395c26ecF1EC2063490Fa', + description: 'Numbers', + favorite: false, + name: 'Numbers #132', + tokenId: '132', + }, + ], + }, + '0x2df920B180c58766951395c26ecF1EC206343334': { + collectionName: 'toto', + nfts: [ + { + address: '0x2df920B180c58766951395c26ecF1EC206343334', + description: 'toto', + favorite: false, + name: 'toto#3453', + tokenId: '3453', + }, + ], + }, + '0xf4910C763eD4e47A585E2D34baA9A4b611aE448C': { + collectionName: 'foo', + nfts: [ + { + address: '0xf4910C763eD4e47A585E2D34baA9A4b611aE448C', + description: 'foo', + favorite: false, + name: 'toto#111486581076844052489180254627234340268504869259922513413248833349282110111749', + tokenId: + '111486581076844052489180254627234340268504869259922513413248833349282110111749', + }, + ], + }, + }; + it('should return true if it exists', () => { + expect( + util.checkTokenIdExists( + '0x2df920B180c58766951395c26ecF1EC206343334', + '3453', + data, + ), + ).toBeTruthy(); + }); + + it('should return true if it exists in decimal format', () => { + expect( + util.checkTokenIdExists( + '0x2df920B180c58766951395c26ecF1EC206343334', + '0xD7D', + data, + ), + ).toBeTruthy(); + }); + + it('should return true if is exists but input is not decimal nor hex', () => { + expect( + util.checkTokenIdExists( + '0xf4910C763eD4e47A585E2D34baA9A4b611aE448C', + '111486581076844052489180254627234340268504869259922513413248833349282110111749', + data, + ), + ).toBeTruthy(); + }); + + it('should return false if it does not exists', () => { + expect( + util.checkTokenIdExists( + '0x2df920B180c58766951395c26ecF1EC206343334', + '1122', + data, + ), + ).toBeFalsy(); + }); + + it('should return false if it address does not exists', () => { + expect( + util.checkTokenIdExists( + '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57', + '1122', + data, + ), + ).toBeFalsy(); + }); + }); }); diff --git a/ui/hooks/useAddressDetails.test.js b/ui/hooks/useAddressDetails.test.js index cf09f1b9d4a4..a8c2cfa26047 100644 --- a/ui/hooks/useAddressDetails.test.js +++ b/ui/hooks/useAddressDetails.test.js @@ -96,7 +96,7 @@ describe('useAddressDetails', () => { '0x06195827297c7A80a443b6894d3BDB8824b43896', ); const { toName, isTrusted } = result.current; - expect(toName).toBe('0x061...3896'); + expect(toName).toBe('0x06195...43896'); expect(isTrusted).toBe(false); }); }); diff --git a/ui/hooks/useName.test.ts b/ui/hooks/useName.test.ts index d5a5b17f36ff..d2def7b8223b 100644 --- a/ui/hooks/useName.test.ts +++ b/ui/hooks/useName.test.ts @@ -13,14 +13,13 @@ jest.mock('../selectors', () => ({ const CHAIN_ID_MOCK = '0x1'; const CHAIN_ID_2_MOCK = '0x2'; -const VALUE_MOCK = '0x0'; +const VALUE_MOCK = '0xabc123'; const TYPE_MOCK = NameType.ETHEREUM_ADDRESS; const NAME_MOCK = 'TestName'; const SOURCE_ID_MOCK = 'TestSourceId'; const PROPOSED_NAMES_MOCK = { [SOURCE_ID_MOCK]: ['TestProposedName', 'TestProposedName2'], }; -const PROPOSED_NAMES_LAST_UPDATED_MOCK = 1234567890; describe('useName', () => { const getCurrentChainIdMock = jest.mocked(getCurrentChainId); @@ -41,7 +40,6 @@ describe('useName', () => { name: null, sourceId: null, proposedNames: {}, - proposedNamesLastUpdated: null, }); }); @@ -53,7 +51,6 @@ describe('useName', () => { name: NAME_MOCK, proposedNames: PROPOSED_NAMES_MOCK, sourceId: SOURCE_ID_MOCK, - proposedNamesLastUpdated: PROPOSED_NAMES_LAST_UPDATED_MOCK, }, }, }, @@ -65,7 +62,6 @@ describe('useName', () => { name: null, sourceId: null, proposedNames: {}, - proposedNamesLastUpdated: null, }); }); @@ -77,7 +73,6 @@ describe('useName', () => { name: NAME_MOCK, proposedNames: PROPOSED_NAMES_MOCK, sourceId: SOURCE_ID_MOCK, - proposedNamesLastUpdated: PROPOSED_NAMES_LAST_UPDATED_MOCK, }, }, }, @@ -89,7 +84,6 @@ describe('useName', () => { name: NAME_MOCK, sourceId: SOURCE_ID_MOCK, proposedNames: PROPOSED_NAMES_MOCK, - proposedNamesLastUpdated: PROPOSED_NAMES_LAST_UPDATED_MOCK, }); }); @@ -101,7 +95,6 @@ describe('useName', () => { name: NAME_MOCK, proposedNames: PROPOSED_NAMES_MOCK, sourceId: SOURCE_ID_MOCK, - proposedNamesLastUpdated: PROPOSED_NAMES_LAST_UPDATED_MOCK, }, }, }, @@ -113,7 +106,6 @@ describe('useName', () => { name: NAME_MOCK, sourceId: SOURCE_ID_MOCK, proposedNames: PROPOSED_NAMES_MOCK, - proposedNamesLastUpdated: PROPOSED_NAMES_LAST_UPDATED_MOCK, }); }); @@ -127,7 +119,6 @@ describe('useName', () => { name: NAME_MOCK, proposedNames: PROPOSED_NAMES_MOCK, sourceId: SOURCE_ID_MOCK, - proposedNamesLastUpdated: PROPOSED_NAMES_LAST_UPDATED_MOCK, }, }, }, @@ -139,7 +130,28 @@ describe('useName', () => { name: NAME_MOCK, sourceId: SOURCE_ID_MOCK, proposedNames: PROPOSED_NAMES_MOCK, - proposedNamesLastUpdated: PROPOSED_NAMES_LAST_UPDATED_MOCK, + }); + }); + + it('normalizes addresses to lowercase', () => { + getNamesMock.mockReturnValue({ + [TYPE_MOCK]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: NAME_MOCK, + proposedNames: PROPOSED_NAMES_MOCK, + sourceId: SOURCE_ID_MOCK, + }, + }, + }, + }); + + const nameEntry = useName('0xAbC123', TYPE_MOCK); + + expect(nameEntry).toStrictEqual({ + name: NAME_MOCK, + sourceId: SOURCE_ID_MOCK, + proposedNames: PROPOSED_NAMES_MOCK, }); }); }); diff --git a/ui/hooks/useName.ts b/ui/hooks/useName.ts index 67a43031a23f..ed155bdc82a6 100644 --- a/ui/hooks/useName.ts +++ b/ui/hooks/useName.ts @@ -3,6 +3,26 @@ import { useSelector } from 'react-redux'; import { isEqual } from 'lodash'; import { getCurrentChainId, getNames } from '../selectors'; +function normalizeValue(value: string, type: string): string { + switch (type) { + case NameType.ETHEREUM_ADDRESS: + return value.toLowerCase(); + + default: + return value; + } +} + +function getVariationKey(type: string, chainId: string): string { + switch (type) { + case NameType.ETHEREUM_ADDRESS: + return chainId; + + default: + return ''; + } +} + export function useName( value: string, type: NameType, @@ -10,16 +30,14 @@ export function useName( ): NameEntry { const names = useSelector(getNames, isEqual); const chainId = useSelector(getCurrentChainId); - - const variationKey = - variation ?? (type === NameType.ETHEREUM_ADDRESS ? chainId : ''); - - const nameEntry = names[type]?.[value]?.[variationKey]; + const normalizedValue = normalizeValue(value, type); + const typeVariationKey = getVariationKey(type, chainId); + const variationKey = variation ?? typeVariationKey; + const nameEntry = names[type]?.[normalizedValue]?.[variationKey]; return { name: nameEntry?.name ?? null, sourceId: nameEntry?.sourceId ?? null, proposedNames: nameEntry?.proposedNames ?? {}, - proposedNamesLastUpdated: nameEntry?.proposedNamesLastUpdated ?? null, }; } diff --git a/ui/hooks/useTransactionDisplayData.test.js b/ui/hooks/useTransactionDisplayData.test.js index 9399acfb9839..03e03b800f70 100644 --- a/ui/hooks/useTransactionDisplayData.test.js +++ b/ui/hooks/useTransactionDisplayData.test.js @@ -33,7 +33,7 @@ const expectedResults = [ { title: 'Send', category: TransactionGroupCategory.send, - subtitle: 'To: 0xffe...1a97', + subtitle: 'To: 0xffe5b...91a97', subtitleContainsOrigin: false, date: formatDateWithYearContext(1589314601567), primaryCurrency: '-1 ETH', @@ -47,7 +47,7 @@ const expectedResults = [ { title: 'Send', category: TransactionGroupCategory.send, - subtitle: 'To: 0x0cc...8848', + subtitle: 'To: 0x0ccc8...f8848', subtitleContainsOrigin: false, date: formatDateWithYearContext(1589314355872), primaryCurrency: '-2 ETH', @@ -60,7 +60,7 @@ const expectedResults = [ { title: 'Send', category: TransactionGroupCategory.send, - subtitle: 'To: 0xffe...1a97', + subtitle: 'To: 0xffe5b...91a97', subtitleContainsOrigin: false, date: formatDateWithYearContext(1589314345433), primaryCurrency: '-2 ETH', @@ -73,7 +73,7 @@ const expectedResults = [ { title: 'Receive', category: TransactionGroupCategory.receive, - subtitle: 'From: 0x31b...4523', + subtitle: 'From: 0x31b98...84523', subtitleContainsOrigin: false, date: formatDateWithYearContext(1589314295000), primaryCurrency: '18.75 ETH', @@ -86,7 +86,7 @@ const expectedResults = [ { title: 'Receive', category: TransactionGroupCategory.receive, - subtitle: 'From: 0x9ec...a149', + subtitle: 'From: 0x9eca6...6a149', subtitleContainsOrigin: false, date: formatDateWithYearContext(1588972833000), primaryCurrency: '0 ETH', @@ -99,7 +99,7 @@ const expectedResults = [ { title: 'Receive', category: TransactionGroupCategory.receive, - subtitle: 'From: 0xee0...febb', + subtitle: 'From: 0xee014...efebb', subtitleContainsOrigin: false, date: formatDateWithYearContext(1585087013000), primaryCurrency: '1 ETH', @@ -138,7 +138,7 @@ const expectedResults = [ { title: 'Safe transfer from', category: TransactionGroupCategory.send, - subtitle: 'To: 0xe7d...dd98', + subtitle: 'To: 0xe7d52...0dd98', subtitleContainsOrigin: true, primaryCurrency: '-0 ETH', senderAddress: '0x806627172af48bd5b0765d3449a7def80d6576ff', diff --git a/ui/pages/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap b/ui/pages/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap index cbeb323e9f0a..7e905833d834 100644 --- a/ui/pages/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap +++ b/ui/pages/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap @@ -244,7 +244,7 @@ exports[`ConfirmSendEther should render correct information for for confirm send class="sender-to-recipient__name" data-testid="sender-to-recipient__name" > - 0x0c5...AaFb + 0x0c54F...7AaFb
diff --git a/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap b/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap index 81000b0b0287..af1c57973377 100644 --- a/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap +++ b/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap @@ -364,7 +364,7 @@ exports[`Confirm Signature Request Component render should match snapshot 1`] =
- 0xCD2...D826 + 0xCD2a3...DD826
@@ -440,7 +440,7 @@ exports[`Confirm Signature Request Component render should match snapshot 1`] =
- 0xDea...beeF + 0xDeaDb...DbeeF
@@ -575,7 +575,7 @@ exports[`Confirm Signature Request Component render should match snapshot 1`] =
- 0xbBb...BBbB + 0xbBbBB...bBBbB
@@ -651,7 +651,7 @@ exports[`Confirm Signature Request Component render should match snapshot 1`] =
- 0xB0B...Ea57 + 0xB0Bda...bEa57
@@ -727,7 +727,7 @@ exports[`Confirm Signature Request Component render should match snapshot 1`] =
- 0xB0B...0000 + 0xB0B0b...00000
diff --git a/ui/pages/confirm-transaction-base/__snapshots__/confirm-transaction-base.test.js.snap b/ui/pages/confirm-transaction-base/__snapshots__/confirm-transaction-base.test.js.snap index 01cecf920f09..c7f611e1d895 100644 --- a/ui/pages/confirm-transaction-base/__snapshots__/confirm-transaction-base.test.js.snap +++ b/ui/pages/confirm-transaction-base/__snapshots__/confirm-transaction-base.test.js.snap @@ -219,7 +219,7 @@ exports[`Confirm Transaction Base should match snapshot 1`] = ` class="sender-to-recipient__name" data-testid="sender-to-recipient__name" > - 0x85c...D65e + 0x85c16...DD65e diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.test.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.test.js index 882d32dc4dd9..cc5424348d2e 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.test.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.test.js @@ -34,14 +34,14 @@ const mockNetworkId = '5'; const mockTxParamsFromAddress = '0x123456789'; const mockTxParamsToAddress = '0x85c1685cfceaa5c0bdb1609fc536e9a8387dd65e'; -const mockTxParamsToAddressConcat = '0x85c...D65e'; +const mockTxParamsToAddressConcat = '0x85c16...DD65e'; const mockParsedTxDataToAddressWithout0x = 'e57e7847fd3661a9b7c86aaf1daea08d9da5750a'; -const mockParsedTxDataToAddress = '0xe57...750A'; +const mockParsedTxDataToAddress = '0xe57E7...5750A'; const mockPropsToAddress = '0x33m1685cfceaa5c0bdb1609fc536e9a8387dd567'; -const mockPropsToAddressConcat = '0x33m...d567'; +const mockPropsToAddressConcat = '0x33m16...dd567'; const mockTxParams = { from: mockTxParamsFromAddress, diff --git a/ui/pages/confirm-transaction/confirm-transaction.transaction.test.js b/ui/pages/confirm-transaction/confirm-transaction.transaction.test.js index 500501a9883b..83eb5356e356 100644 --- a/ui/pages/confirm-transaction/confirm-transaction.transaction.test.js +++ b/ui/pages/confirm-transaction/confirm-transaction.transaction.test.js @@ -39,7 +39,7 @@ describe('Confirm Transaction', () => { store, `${CONFIRM_TRANSACTION_ROUTE}/${unapprovedTransactionId}${CONFIRM_SEND_ETHER_PATH}`, ); - expect(getByText('0xb19...0c5e')).toBeInTheDocument(); + expect(getByText('0xb19Ac...f0c5e')).toBeInTheDocument(); expect(getByRole('button', { name: 'Details' })).toBeInTheDocument(); expect(getByRole('button', { name: 'Data' })).toBeInTheDocument(); expect(getByRole('button', { name: 'Hex' })).toBeInTheDocument(); diff --git a/ui/pages/connected-accounts/connected-accounts.component.js b/ui/pages/connected-accounts/connected-accounts.component.js index 6be0862d1074..eb3b5d107c20 100644 --- a/ui/pages/connected-accounts/connected-accounts.component.js +++ b/ui/pages/connected-accounts/connected-accounts.component.js @@ -1,75 +1,98 @@ import PropTypes from 'prop-types'; -import React, { PureComponent } from 'react'; +import React from 'react'; +import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-utils'; import Popover from '../../components/ui/popover'; import ConnectedAccountsList from '../../components/app/connected-accounts-list'; import ConnectedAccountsPermissions from '../../components/app/connected-accounts-permissions'; import { getURLHost } from '../../helpers/utils/util'; +import { useI18nContext } from '../../hooks/useI18nContext'; +import ConnectedSnaps from '../../components/app/connected-sites-list/connected-snaps'; +import { TextColor, TextVariant } from '../../helpers/constants/design-system'; +import { Box, Text } from '../../components/component-library'; -export default class ConnectedAccounts extends PureComponent { - static contextTypes = { - t: PropTypes.func.isRequired, - }; +export default function ConnectedAccounts({ + accountToConnect = null, + activeTabOrigin, + isActiveTabExtension, + connectAccount, + connectedAccounts, + history, + mostRecentOverviewPage, + permissions = undefined, + selectedAddress, + removePermittedAccount, + setSelectedAddress, + subjectMetadata, + originOfActiveTab, + permissionSubjects, +}) { + const t = useI18nContext(); + const connectedSubjectsMetadata = subjectMetadata[originOfActiveTab]; + const subjectHasSnaps = + permissionSubjects[originOfActiveTab]?.origin === + connectedSubjectsMetadata?.origin && + permissionSubjects[originOfActiveTab]?.permissions[ + WALLET_SNAP_PERMISSION_KEY + ]; - static defaultProps = { - accountToConnect: null, - permissions: undefined, - }; + const connectedSnaps = + subjectHasSnaps && + Object.keys( + permissionSubjects[originOfActiveTab]?.permissions?.wallet_snap + ?.caveats[0]?.value, + ); + const connectedSnapsMetaData = + subjectHasSnaps && connectedSnaps?.map((sub) => subjectMetadata[sub]); - static propTypes = { - accountToConnect: PropTypes.object, - activeTabOrigin: PropTypes.string.isRequired, - connectAccount: PropTypes.func.isRequired, - connectedAccounts: PropTypes.array.isRequired, - mostRecentOverviewPage: PropTypes.string.isRequired, - permissions: PropTypes.array, - isActiveTabExtension: PropTypes.bool.isRequired, - selectedAddress: PropTypes.string.isRequired, - removePermittedAccount: PropTypes.func.isRequired, - setSelectedAddress: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, - }; + const connectedAccountsDescription = + connectedAccounts.length > 0 + ? t('connectedAccountsDescriptionPlural', [connectedAccounts.length]) + : t('connectedAccountsDescriptionSingular'); - render() { - const { - accountToConnect, - activeTabOrigin, - isActiveTabExtension, - connectAccount, - connectedAccounts, - history, - mostRecentOverviewPage, - permissions, - selectedAddress, - removePermittedAccount, - setSelectedAddress, - } = this.props; - const { t } = this.context; + let subtitle; + if (connectedAccounts.length && !subjectHasSnaps) { + subtitle = connectedAccountsDescription; + } else if (subjectHasSnaps && !connectedAccounts.length) { + subtitle = t('connectedSnapAndNoAccountDescription'); + } else if (connectedAccounts && subjectHasSnaps) { + subtitle = null; + } else { + subtitle = t('connectedAccountsEmptyDescription'); + } - const connectedAccountsDescription = - connectedAccounts.length > 1 - ? t('connectedAccountsDescriptionPlural', [connectedAccounts.length]) - : t('connectedAccountsDescriptionSingular'); + return ( + history.push(mostRecentOverviewPage)} + footerClassName="connected-accounts__footer" + ConnectedAccountsPermissions={{}} + footer={ + connectedAccounts.length > 0 && ( // show permissions only for connected accounts not snaps + + ) + } + > + + {connectedAccounts.length > 0 ? ( + + + {t('accountsConnected')} ({connectedAccounts.length}) + + + ) : null} - return ( - history.push(mostRecentOverviewPage)} - footerClassName="connected-accounts__footer" - footer={ - permissions?.length > 0 && ( - - ) - } - > - - ); - } + + {subjectHasSnaps && connectedSnapsMetaData.length > 0 && ( + <> + + + {t('snapsConnected')} ({connectedSnaps.length}) + + + + + )} + + ); } + +ConnectedAccounts.propTypes = { + accountToConnect: PropTypes.object, + activeTabOrigin: PropTypes.string.isRequired, + connectAccount: PropTypes.func.isRequired, + connectedAccounts: PropTypes.array.isRequired, + mostRecentOverviewPage: PropTypes.string.isRequired, + permissions: PropTypes.array, + isActiveTabExtension: PropTypes.bool.isRequired, + selectedAddress: PropTypes.string.isRequired, + removePermittedAccount: PropTypes.func.isRequired, + setSelectedAddress: PropTypes.func.isRequired, + history: PropTypes.object.isRequired, + subjectMetadata: PropTypes.arrayOf(PropTypes.object).isRequired, + originOfActiveTab: PropTypes.string, + permissionSubjects: PropTypes.object, +}; diff --git a/ui/pages/connected-accounts/connected-accounts.container.js b/ui/pages/connected-accounts/connected-accounts.container.js index 8a8cffb3b530..6cb68fa31e2f 100644 --- a/ui/pages/connected-accounts/connected-accounts.container.js +++ b/ui/pages/connected-accounts/connected-accounts.container.js @@ -2,8 +2,11 @@ import { connect } from 'react-redux'; import { getAccountToConnectToActiveTab, getOrderedConnectedAccountsForActiveTab, + getOriginOfCurrentTab, getPermissionsForActiveTab, + getPermissionSubjects, getSelectedAddress, + getSubjectMetadata, } from '../../selectors'; import { isExtensionUrl } from '../../helpers/utils/util'; import { @@ -20,6 +23,9 @@ const mapStateToProps = (state) => { const connectedAccounts = getOrderedConnectedAccountsForActiveTab(state); const permissions = getPermissionsForActiveTab(state); const selectedAddress = getSelectedAddress(state); + const subjectMetadata = getSubjectMetadata(state); + const originOfActiveTab = getOriginOfCurrentTab(state); + const permissionSubjects = getPermissionSubjects(state); const isActiveTabExtension = isExtensionUrl(activeTab); return { @@ -30,6 +36,9 @@ const mapStateToProps = (state) => { mostRecentOverviewPage: getMostRecentOverviewPage(state), permissions, selectedAddress, + subjectMetadata, + originOfActiveTab, + permissionSubjects, }; }; diff --git a/ui/pages/connected-accounts/connected-accounts.stories.js b/ui/pages/connected-accounts/connected-accounts.stories.js index ea2aa55af55c..49e297194a24 100644 --- a/ui/pages/connected-accounts/connected-accounts.stories.js +++ b/ui/pages/connected-accounts/connected-accounts.stories.js @@ -6,6 +6,54 @@ export default { title: 'Pages/ConnectedAccounts', }; +const subjectMetadata = { + 'https://snaps.metamask.io/': { + extensionId: null, + iconUrl: null, + name: 'Starknet', + origin: 'npm:@consensys/starknet-snap', + subjectType: 'snap', + svgIcon: '...', + }, + 'local:http://localhost:8080/': { + extensionId: null, + iconUrl: null, + name: 'MetaMask Example Snap', + origin: 'local:http://localhost:8080/', + subjectType: 'snap', + svgIcon: '...', + version: '0.6.0', + }, +}; + +const permissionSubjects = { + origin: 'https://snaps.metamask.io', + permissions: { + wallet_snap: { + caveats: [ + { + type: 'snapIds', + value: { + 'npm:@consensys/starknet-snap': { + version: '2.1.0', + }, + 'npm:@pianity/arsnap': { + version: '0.2.2', + }, + 'npm:tezos-metamask-snap': { + version: '1.0.1', + }, + }, + }, + ], + date: 1695297309455, + id: 'rbS-Jp76heHR4y3Y1OUFQ', + invoker: 'https://snaps.metamask.io', + parentCapability: 'wallet_snap', + }, + }, +}; + const account = [ { name: 'Account 1', @@ -25,6 +73,9 @@ export const DefaultStory = () => { connectAccount={action('Account Connected')} removePermittedAccount={action('Account Removed')} setSelectedAddress={action('Selected Address Changed')} + originOfActiveTab="https://snaps.metamask.io/" + subjectMetadata={subjectMetadata} + permissionSubjects={permissionSubjects} /> ); }; diff --git a/ui/pages/institutional/connect-custody/__snapshots__/account-list.test.js.snap b/ui/pages/institutional/connect-custody/__snapshots__/account-list.test.js.snap index 555d1689c64a..727df282a2df 100644 --- a/ui/pages/institutional/connect-custody/__snapshots__/account-list.test.js.snap +++ b/ui/pages/institutional/connect-custody/__snapshots__/account-list.test.js.snap @@ -54,7 +54,7 @@ exports[`CustodyAccountList renders accounts 1`] = ` - 0x123...7890 + 0x12345...67890 - 0x098...4321 + 0x09876...54321 - Display NFT media + Enable OpenSea API
- Displaying NFT media and data exposes your IP address to OpenSea or other third parties. NFT autodetection relies on this feature, and won't be available when it is turned off. If NFT media is fully located on IPFS, it can still be displayed even when this feature is turned off. + Use OpenSea's API to fetch NFT data. NFT auto-detection relies on OpenSea's API, and will not be available when this is turned off.
- {t('displayNftMedia')} + {t('enableOpenSeaAPI')}
- {t('displayNftMediaDesc')} + {t('enableOpenSeaAPIDescription')}
-
{ const snapsList = useSelector((state) => getSnapsList(state)); return ( -
- {snapsList.length ? ( + + {snapsList.length > 0 && (
{snapsList.map((snap) => { @@ -61,62 +70,71 @@ const SnapListTab = () => { })}
- ) : ( + )} + {snapsList.length <= 5 && ( - - - - {t('noSnaps')} - - + + + {t('noSnaps')} + + + )} + - {`${t('learnMoreUpperCase')}`} + {`${t('discoverSnaps')}`}
)} -
+ ); }; diff --git a/ui/pages/token-allowance/token-allowance.test.js b/ui/pages/token-allowance/token-allowance.test.js index 7000fc0e23a0..d9f9021e6b5e 100644 --- a/ui/pages/token-allowance/token-allowance.test.js +++ b/ui/pages/token-allowance/token-allowance.test.js @@ -74,7 +74,6 @@ const state = { }, ], transactions: [], - keyringTypes: [], keyrings: [ { type: KeyringType.hdKeyTree, diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 116ab183074f..19c191108b51 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -6,9 +6,6 @@ import { ThunkAction } from 'redux-thunk'; import { Action, AnyAction } from 'redux'; import { ethErrors, serializeError } from 'eth-rpc-errors'; import { Hex, Json } from '@metamask/utils'; -///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) -import { v4 as uuidV4 } from 'uuid'; -///: END:ONLY_INCLUDE_IN import { AssetsContractController, BalanceMap, @@ -19,9 +16,6 @@ import { PayloadAction } from '@reduxjs/toolkit'; import { GasFeeController } from '@metamask/gas-fee-controller'; import { PermissionsRequest } from '@metamask/permission-controller'; import { NonEmptyArray } from '@metamask/controller-utils'; -///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) -import { HandlerType } from '@metamask/snaps-utils'; -///: END:ONLY_INCLUDE_IN import { SetNameRequest, UpdateProposedNamesRequest, @@ -1147,7 +1141,7 @@ export function enableSnap( ///: BEGIN:ONLY_INCLUDE_IN(snaps) export function removeSnap( snapId: string, -): ThunkAction { +): ThunkAction, MetaMaskReduxState, any, AnyAction> { return async ( dispatch: MetaMaskReduxDispatch, ///: END:ONLY_INCLUDE_IN @@ -1172,19 +1166,13 @@ export function removeSnap( ///: 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())); - } + const addresses: string[] = await submitRequestToBackground( + 'getAccountsBySnapId', + [snapId], + ); + addresses.forEach((address) => + dispatch(removeAccount(address.toLowerCase())), + ); } ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(snaps) diff --git a/yarn.lock b/yarn.lock index 3d249a42c77f..c442d1f6fa6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3865,17 +3865,17 @@ __metadata: languageName: node linkType: hard -"@metamask/base-controller@npm:^3.0.0, @metamask/base-controller@npm:^3.1.0, @metamask/base-controller@npm:^3.2.0, @metamask/base-controller@npm:^3.2.1": - version: 3.2.1 - resolution: "@metamask/base-controller@npm:3.2.1" +"@metamask/base-controller@npm:^3.0.0, @metamask/base-controller@npm:^3.1.0, @metamask/base-controller@npm:^3.2.0, @metamask/base-controller@npm:^3.2.1, @metamask/base-controller@npm:^3.2.2": + version: 3.2.2 + resolution: "@metamask/base-controller@npm:3.2.2" dependencies: "@metamask/utils": "npm:^6.2.0" immer: "npm:^9.0.6" - checksum: ff4db984a72c942694b0ab849ec61f1c36423e6c6b7144a560f52fb6449e91dd4ce3b937a3b7e092468d0c679f305cc0c805085593f9b83acd98fbfe6f971b69 + checksum: 4d6537ef8cefbf091e652393d2a1d9c61d728032d0286bf22a228f8c8dc1b82e7551346fd17a92841d4fb61dd3bf37409e2305b504db393190363072ae528ca0 languageName: node linkType: hard -"@metamask/browser-passworder@npm:^4.0.2, @metamask/browser-passworder@npm:^4.1.0": +"@metamask/browser-passworder@npm:^4.1.0": version: 4.1.0 resolution: "@metamask/browser-passworder@npm:4.1.0" checksum: c09db69dd80ed020e017e70bc0589348343e659c8f402810a5c950ca42a16340a43998a0651ec0747cf4b2e2c0ce16dadd8511d2760ecb958e0a0a09fa95016e @@ -3920,9 +3920,9 @@ __metadata: languageName: node linkType: hard -"@metamask/controller-utils@npm:^5.0.0": - version: 5.0.0 - resolution: "@metamask/controller-utils@npm:5.0.0" +"@metamask/controller-utils@npm:^5.0.1": + version: 5.0.1 + resolution: "@metamask/controller-utils@npm:5.0.1" dependencies: "@metamask/eth-query": "npm:^3.0.1" "@metamask/utils": "npm:^6.2.0" @@ -3932,7 +3932,7 @@ __metadata: ethereumjs-util: "npm:^7.0.10" ethjs-unit: "npm:^0.1.6" fast-deep-equal: "npm:^3.1.3" - checksum: 4fbefc1affa9aee110fcb1b7fc95a96f29fc6d41d6fa6b92bd691d541833cf8531a282d7866daeb7585741533b50a5d9e8d0564071fc1abf09769c9fe039845b + checksum: e830a4e86085463ca2a81adb5cf71b81e4ab66468a8cadb6b2f4ef083f4746d2303b1d8587adf20f80eb618181a427288c115b63b695020511b4be8c152b52f3 languageName: node linkType: hard @@ -4073,19 +4073,6 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-keyring-controller@npm:^10.0.1": - version: 10.0.1 - resolution: "@metamask/eth-keyring-controller@npm:10.0.1" - dependencies: - "@metamask/browser-passworder": "npm:^4.0.2" - "@metamask/eth-hd-keyring": "npm:^6.0.0" - "@metamask/eth-sig-util": "npm:5.0.2" - "@metamask/eth-simple-keyring": "npm:^5.0.0" - obs-store: "npm:^4.0.3" - checksum: b739eca06665ab384a33fb1c82b1c9e52de69223ff950a99676d1a542b1b1acaa21d3a1ed07cd7fda23fd3b70510706f2ab65bc7631bfd8902bab8bcf41ace0f - languageName: node - linkType: hard - "@metamask/eth-keyring-controller@npm:^13.0.1": version: 13.0.1 resolution: "@metamask/eth-keyring-controller@npm:13.0.1" @@ -4123,20 +4110,6 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-sig-util@npm:5.0.2": - version: 5.0.2 - resolution: "@metamask/eth-sig-util@npm:5.0.2" - dependencies: - "@ethereumjs/util": "npm:^8.0.0" - bn.js: "npm:^4.11.8" - ethereum-cryptography: "npm:^1.1.2" - ethjs-util: "npm:^0.1.6" - tweetnacl: "npm:^1.0.3" - tweetnacl-util: "npm:^0.15.1" - checksum: 37489528d8e987bae00d9c5b2025b86e9ca6914cc9f3b98bae2eb39c3a890959d15cb09fad9c761f88ce0ae49e2a91f0237425f5771b1e3f0be62f2965e46a38 - languageName: node - linkType: hard - "@metamask/eth-sig-util@npm:^5.0.1, @metamask/eth-sig-util@npm:^5.0.2": version: 5.1.0 resolution: "@metamask/eth-sig-util@npm:5.1.0" @@ -4192,9 +4165,9 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-snap-keyring@npm:0.3.0": - version: 0.3.0 - resolution: "@metamask/eth-snap-keyring@npm:0.3.0" +"@metamask/eth-snap-keyring@npm:0.3.1": + version: 0.3.1 + resolution: "@metamask/eth-snap-keyring@npm:0.3.1" dependencies: "@ethereumjs/tx": "npm:^4.2.0" "@metamask/eth-sig-util": "npm:^7.0.0" @@ -4204,7 +4177,7 @@ __metadata: "@types/uuid": "npm:^9.0.1" superstruct: "npm:^1.0.3" uuid: "npm:^9.0.0" - checksum: d20b691cc10e66b2ed58444a2a0df7b698dbe3f9f5e45caa747cc4ca009b314c478c9f1618465a55a322e34437ab05be5477b74eb70c5f26600c45c6431e4d90 + checksum: 286448d2302b3ac663e71176a8464405cdaa30841e980dbac808913a9c1418b4965625c1e3be3d8b3f601ec59257b365e507aef77cf3edca150d8ea6ac3461f5 languageName: node linkType: hard @@ -4336,43 +4309,23 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-controller@npm:8.0.0": - version: 8.0.0 - resolution: "@metamask/keyring-controller@npm:8.0.0" - dependencies: - "@keystonehq/metamask-airgapped-keyring": "npm:^0.13.1" - "@metamask/base-controller": "npm:^3.2.1" - "@metamask/eth-keyring-controller": "npm:^13.0.1" - "@metamask/message-manager": "npm:^7.3.3" - "@metamask/preferences-controller": "npm:^4.4.1" - "@metamask/utils": "npm:^6.2.0" - async-mutex: "npm:^0.2.6" - ethereumjs-util: "npm:^7.0.10" - ethereumjs-wallet: "npm:^1.0.1" - immer: "npm:^9.0.6" - peerDependencies: - "@metamask/preferences-controller": ^4.4.1 - checksum: 2cd0173d210c634357aa8be0c3fc2472939e6919f69b4d375ebc6c464d116928603b3b917a4fa6120e75870bc2288553a23f5659a6719c84902d982e0aa376a4 - languageName: node - linkType: hard - -"@metamask/keyring-controller@patch:@metamask/keyring-controller@npm%3A8.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-8.0.0-806630ae4e.patch": - version: 8.0.0 - resolution: "@metamask/keyring-controller@patch:@metamask/keyring-controller@npm%3A8.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-8.0.0-806630ae4e.patch::version=8.0.0&hash=65339a" +"@metamask/keyring-controller@npm:^8.0.1": + version: 8.0.1 + resolution: "@metamask/keyring-controller@npm:8.0.1" dependencies: "@keystonehq/metamask-airgapped-keyring": "npm:^0.13.1" - "@metamask/base-controller": "npm:^3.2.1" + "@metamask/base-controller": "npm:^3.2.2" "@metamask/eth-keyring-controller": "npm:^13.0.1" - "@metamask/message-manager": "npm:^7.3.3" - "@metamask/preferences-controller": "npm:^4.4.1" + "@metamask/message-manager": "npm:^7.3.4" + "@metamask/preferences-controller": "npm:^4.4.2" "@metamask/utils": "npm:^6.2.0" async-mutex: "npm:^0.2.6" ethereumjs-util: "npm:^7.0.10" ethereumjs-wallet: "npm:^1.0.1" immer: "npm:^9.0.6" peerDependencies: - "@metamask/preferences-controller": ^4.4.1 - checksum: f9a4f8eaec927b5c2fdc91f12066254a90b4358e79dcf01ff4c380414cdb53f37914cb9e5dd71a9758fd67ffe7346fcb181f2124e6d6f1fca8c21af5bb5b1920 + "@metamask/preferences-controller": ^4.4.2 + checksum: e950270c6f22b0745d4a1f35c94601bbe4642da573850f071e25c94befa3b8e95c5891a1fb317827e884438a34a7cc4cf7da9a838080ad13cd15c54cf4e56327 languageName: node linkType: hard @@ -4397,19 +4350,19 @@ __metadata: languageName: node linkType: hard -"@metamask/message-manager@npm:^7.2.0, @metamask/message-manager@npm:^7.3.0, @metamask/message-manager@npm:^7.3.3": - version: 7.3.3 - resolution: "@metamask/message-manager@npm:7.3.3" +"@metamask/message-manager@npm:^7.3.0, @metamask/message-manager@npm:^7.3.2, @metamask/message-manager@npm:^7.3.4": + version: 7.3.4 + resolution: "@metamask/message-manager@npm:7.3.4" dependencies: - "@metamask/base-controller": "npm:^3.2.1" - "@metamask/controller-utils": "npm:^5.0.0" + "@metamask/base-controller": "npm:^3.2.2" + "@metamask/controller-utils": "npm:^5.0.1" "@metamask/eth-sig-util": "npm:^7.0.0" "@metamask/utils": "npm:^6.2.0" "@types/uuid": "npm:^8.3.0" ethereumjs-util: "npm:^7.0.10" jsonschema: "npm:^1.2.4" uuid: "npm:^8.3.2" - checksum: 1f9175c3ce44bf8d8ee80e391f1d36f559cef31e883a6ba7e3032d06c0263cc8e1d5fe15a591583bd415bbb01d113750ff6de99220d99ef71b4057985e34406e + checksum: 59ac697ac75f86d820b756dfaf1496a86061132abea651a563f526c74c8cf4b39f7dcfc28ce6357c0d2bf1495fa5a8abce23a882a831ba6ad8361bc871fb95c5 languageName: node linkType: hard @@ -4420,13 +4373,15 @@ __metadata: languageName: node linkType: hard -"@metamask/name-controller@npm:^1.0.0": - version: 1.0.0 - resolution: "@metamask/name-controller@npm:1.0.0" +"@metamask/name-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "@metamask/name-controller@npm:3.0.0" dependencies: - "@metamask/base-controller": "npm:^3.2.1" + "@metamask/base-controller": "npm:^3.2.2" + "@metamask/utils": "npm:^6.2.0" + async-mutex: "npm:^0.2.6" immer: "npm:^9.0.6" - checksum: 2366672299a6cf4f7ad78dd023e110fb175606911018a8b2efd1243898d2e74f9eb4fd59b1579c386441b21f000f74e700dd47b955aa99b6f0ba08686dcf2104 + checksum: 8754f3b2e64c9bdac36d882fd4cbc650eef7322beaa6fb5095f5e289bae15f8658aa74f9183832423aaeec40edebfd14c14cb6da7a38224789f35f080079425a languageName: node linkType: hard @@ -4632,13 +4587,13 @@ __metadata: languageName: node linkType: hard -"@metamask/preferences-controller@npm:^4.1.0, @metamask/preferences-controller@npm:^4.4.1": - version: 4.4.1 - resolution: "@metamask/preferences-controller@npm:4.4.1" +"@metamask/preferences-controller@npm:^4.1.0, @metamask/preferences-controller@npm:^4.4.2": + version: 4.4.2 + resolution: "@metamask/preferences-controller@npm:4.4.2" dependencies: - "@metamask/base-controller": "npm:^3.2.1" - "@metamask/controller-utils": "npm:^5.0.0" - checksum: 2404cf4c6f207316af7fd8cf9a7ae2ff3f38766bedde67103143d964ad4e253c8cf228da692a80644e65f072306bfaa807895771e45e93797ab879b9645744a7 + "@metamask/base-controller": "npm:^3.2.2" + "@metamask/controller-utils": "npm:^5.0.1" + checksum: 873530da1f20ec1d830d41114ef0b134de77abd283e2e06b02b79454c2c96fb13b674074aa1facf37b62e6fe3dc785caf20958a69501fef6450c3f77e0ce5ad7 languageName: node linkType: hard @@ -4765,41 +4720,41 @@ __metadata: languageName: node linkType: hard -"@metamask/signature-controller@npm:5.3.0": - version: 5.3.0 - resolution: "@metamask/signature-controller@npm:5.3.0" +"@metamask/signature-controller@npm:6.0.0": + version: 6.0.0 + resolution: "@metamask/signature-controller@npm:6.0.0" dependencies: - "@metamask/approval-controller": "npm:^3.5.0" - "@metamask/base-controller": "npm:^3.2.0" - "@metamask/controller-utils": "npm:^4.3.0" - "@metamask/message-manager": "npm:^7.2.0" + "@metamask/approval-controller": "npm:^3.5.1" + "@metamask/base-controller": "npm:^3.2.1" + "@metamask/controller-utils": "npm:^4.3.2" + "@metamask/message-manager": "npm:^7.3.2" "@metamask/utils": "npm:^6.2.0" eth-rpc-errors: "npm:^4.0.2" ethereumjs-util: "npm:^7.0.10" immer: "npm:^9.0.6" lodash: "npm:^4.17.21" peerDependencies: - "@metamask/approval-controller": ^3.5.0 - checksum: d5b5aad5ed4c97032f3fd3d1dc7f260d700ae8b630f50b30019519b79b760bdaa4ad368721db5b24719062bd650193f23b9410a51e34c3d363f33f9ad83528d6 + "@metamask/approval-controller": ^3.5.1 + checksum: 4d2b6e47d721905e8e0fde981a492f3bcb498893b7892a0a649389ef957cbdcbe00238144c37dad189c5da4121606ffe0f1c061f8bc473c073ce84e4fbe24965 languageName: node linkType: hard -"@metamask/signature-controller@patch:@metamask/signature-controller@npm%3A5.3.0#./.yarn/patches/@metamask-signature-controller-npm-5.3.0-225628460b.patch::locator=metamask-crx%40workspace%3A.": - version: 5.3.0 - resolution: "@metamask/signature-controller@patch:@metamask/signature-controller@npm%3A5.3.0#./.yarn/patches/@metamask-signature-controller-npm-5.3.0-225628460b.patch::version=5.3.0&hash=b8da18&locator=metamask-crx%40workspace%3A." +"@metamask/signature-controller@patch:@metamask/signature-controller@npm%3A6.0.0#~/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch": + version: 6.0.0 + resolution: "@metamask/signature-controller@patch:@metamask/signature-controller@npm%3A6.0.0#~/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch::version=6.0.0&hash=ae5433" dependencies: - "@metamask/approval-controller": "npm:^3.5.0" - "@metamask/base-controller": "npm:^3.2.0" - "@metamask/controller-utils": "npm:^4.3.0" - "@metamask/message-manager": "npm:^7.2.0" + "@metamask/approval-controller": "npm:^3.5.1" + "@metamask/base-controller": "npm:^3.2.1" + "@metamask/controller-utils": "npm:^4.3.2" + "@metamask/message-manager": "npm:^7.3.2" "@metamask/utils": "npm:^6.2.0" eth-rpc-errors: "npm:^4.0.2" ethereumjs-util: "npm:^7.0.10" immer: "npm:^9.0.6" lodash: "npm:^4.17.21" peerDependencies: - "@metamask/approval-controller": ^3.5.0 - checksum: 973259698560a21b8f7d5436f4e4c198202781d7b796012b2d6336bd1738eeedfe1bd405e352c468b46958889f12a709f1b3247ec6e9e8f388984fa25ae0eff3 + "@metamask/approval-controller": ^3.5.1 + checksum: f178e2aacfe031930eb68018571e7531bb91b742a2d4beacb51d5ff8d35e609d0dd601c7cd8ae734c9f8a1f7c41d3ca4d59b3fd48261fb691223f0e6b85ba7d9 languageName: node linkType: hard @@ -23908,9 +23863,9 @@ __metadata: "@metamask/eslint-config-nodejs": "npm:^9.0.0" "@metamask/eslint-config-typescript": "npm:^9.0.1" "@metamask/eth-json-rpc-middleware": "npm:^11.0.0" - "@metamask/eth-keyring-controller": "npm:^10.0.1" + "@metamask/eth-keyring-controller": "npm:^13.0.1" "@metamask/eth-ledger-bridge-keyring": "npm:^0.15.0" - "@metamask/eth-snap-keyring": "npm:0.3.0" + "@metamask/eth-snap-keyring": "npm:0.3.1" "@metamask/eth-token-tracker": "npm:^4.0.0" "@metamask/eth-trezor-keyring": "npm:^1.1.0" "@metamask/etherscan-link": "npm:^2.2.0" @@ -23919,12 +23874,12 @@ __metadata: "@metamask/gas-fee-controller": "npm:^6.0.1" "@metamask/jazzicon": "npm:^2.0.0" "@metamask/key-tree": "npm:^9.0.0" - "@metamask/keyring-controller": "npm:^8.0.0" + "@metamask/keyring-controller": "npm:^8.0.1" "@metamask/logging-controller": "npm:^1.0.1" "@metamask/logo": "npm:^3.1.1" "@metamask/message-manager": "npm:^7.3.0" "@metamask/metamask-eth-abis": "npm:^3.0.0" - "@metamask/name-controller": "npm:^1.0.0" + "@metamask/name-controller": "npm:^3.0.0" "@metamask/network-controller": "npm:^12.2.0" "@metamask/notification-controller": "npm:^3.0.0" "@metamask/obs-store": "npm:^8.1.0" @@ -23939,7 +23894,7 @@ __metadata: "@metamask/safe-event-emitter": "npm:^2.0.0" "@metamask/scure-bip39": "npm:^2.0.3" "@metamask/selected-network-controller": "npm:^1.0.0" - "@metamask/signature-controller": "npm:^5.3.0" + "@metamask/signature-controller": "npm:^6.0.0" "@metamask/slip44": "npm:^3.0.0" "@metamask/smart-transactions-controller": "npm:^4.0.0" "@metamask/snaps-controllers": "npm:^2.0.1" @@ -26196,18 +26151,6 @@ __metadata: languageName: node linkType: hard -"obs-store@npm:^4.0.3": - version: 4.0.3 - resolution: "obs-store@npm:4.0.3" - dependencies: - readable-stream: "npm:^2.2.2" - safe-event-emitter: "npm:^1.0.1" - through2: "npm:^2.0.3" - xtend: "npm:^4.0.1" - checksum: d56da17699a00fc16d9485aa668cf8d27a6cbf9ae38905cd0bfd75eb37f32bbfed9e976a257371d969b18d627c0c8a6f7b13c5e3f277aa36c740f2bc90ae2a11 - languageName: node - linkType: hard - "on-exit-leak-free@npm:^2.1.0": version: 2.1.0 resolution: "on-exit-leak-free@npm:2.1.0"