diff --git a/.circleci/config.yml b/.circleci/config.yml index e8ddb0237283..4b51c0ef4d1d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,27 +3,30 @@ version: 2.1 executors: node-browsers: docker: - - image: cimg/node:18.18-browsers + - image: cimg/node:20.11-browsers environment: + FONTCONFIG_PATH: /etc/fonts NODE_OPTIONS: --max_old_space_size=3072 node-browsers-medium-plus: docker: - - image: cimg/node:18.18-browsers + - image: cimg/node:20.11-browsers resource_class: medium+ environment: + FONTCONFIG_PATH: /etc/fonts NODE_OPTIONS: --max_old_space_size=4096 node-browsers-large: docker: - - image: cimg/node:18.18-browsers + - image: cimg/node:20.11-browsers resource_class: large environment: + FONTCONFIG_PATH: /etc/fonts NODE_OPTIONS: --max_old_space_size=4096 shellcheck: docker: - image: koalaman/shellcheck-alpine@sha256:dfaf08fab58c158549d3be64fb101c626abc5f16f341b569092577ae207db199 playwright: docker: - - image: mcr.microsoft.com/playwright:v1.39.0-jammy + - image: mcr.microsoft.com/playwright:v1.41.1-jammy orbs: gh: circleci/github-cli@2.0 diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index f976ae63022d..819be0fac1ec 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -104,6 +104,7 @@ body: - Keystone - GridPlus Lattice1 - AirGap Vault + - imToken - Other (please elaborate in the "Additional Context" section) - type: textarea id: additional diff --git a/.nvmrc b/.nvmrc index 3f430af82b3d..9a2a0e219c9b 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18 +v20 diff --git a/.storybook/initial-states/approval-screens/add-suggested-token.js b/.storybook/initial-states/approval-screens/add-suggested-token.js index 4197140e6259..f6c7cfea3706 100644 --- a/.storybook/initial-states/approval-screens/add-suggested-token.js +++ b/.storybook/initial-states/approval-screens/add-suggested-token.js @@ -9,7 +9,7 @@ export const pendingTokenApprovals = { address: '0x6b175474e89094c44da98b954eedeac495271d0f', symbol: 'ETH', decimals: 18, - image: './images/eth_logo.png', + image: './images/eth_logo.svg', unlisted: false, }, }, @@ -118,4 +118,4 @@ export const pendingTokenApprovals = { }, }, }, -}; \ No newline at end of file +}; diff --git a/.storybook/test-data.js b/.storybook/test-data.js index d5d59d6bd100..939c99d60300 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -115,7 +115,7 @@ const state = { address: '0x6b175474e89094c44da98b954eedeac495271d0f', symbol: 'ETH', decimals: 18, - image: './images/eth_logo.png', + image: './images/eth_logo.svg', unlisted: false, }, '0xB8c77482e45F1F44dE1745F52C74426C631bDD52': { diff --git a/.vscode/settings.json b/.vscode/settings.json index cb8fc7465369..dcc13f2343e8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,6 @@ "fileMatch": ["app/manifest/*/*.json"], "url": "https://json.schemastore.org/chrome-manifest" } - ] + ], + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch b/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch new file mode 100644 index 000000000000..4d04b1c74db2 --- /dev/null +++ b/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch @@ -0,0 +1,88 @@ +diff --git a/dist/utils.js b/dist/utils.js +index 810f229841ffff83f7a28191bc558862b1809e01..aa2ea845d4cccfac8e28f575d7972bd2dba8decf 100644 +--- a/dist/utils.js ++++ b/dist/utils.js +@@ -11,35 +11,40 @@ const uuid_1 = require("uuid"); + * @returns The name of the keyring type. + */ + function keyringTypeToName(keyringType) { +- switch (keyringType) { +- case keyring_controller_1.KeyringTypes.simple: { +- return 'Account'; +- } +- case keyring_controller_1.KeyringTypes.hd: { +- return 'Account'; +- } +- case keyring_controller_1.KeyringTypes.trezor: { +- return 'Trezor'; +- } +- case keyring_controller_1.KeyringTypes.ledger: { +- return 'Ledger'; +- } +- case keyring_controller_1.KeyringTypes.lattice: { +- return 'Lattice'; +- } +- case keyring_controller_1.KeyringTypes.qr: { +- return 'QR'; +- } +- case keyring_controller_1.KeyringTypes.snap: { +- return 'Snap Account'; +- } +- case keyring_controller_1.KeyringTypes.custody: { +- return 'Custody'; +- } +- default: { +- throw new Error(`Unknown keyring ${keyringType}`); +- } ++ // Custody Keyrings follow this pattern ++ if (/^Custody.*/.test(keyringType)) { ++ return "Custody"; ++ } ++ ++ switch (keyringType) { ++ case keyring_controller_1.KeyringTypes.simple: { ++ return "Account"; + } ++ case keyring_controller_1.KeyringTypes.hd: { ++ return "Account"; ++ } ++ case keyring_controller_1.KeyringTypes.trezor: { ++ return "Trezor"; ++ } ++ case keyring_controller_1.KeyringTypes.ledger: { ++ return "Ledger"; ++ } ++ case keyring_controller_1.KeyringTypes.lattice: { ++ return "Lattice"; ++ } ++ case keyring_controller_1.KeyringTypes.qr: { ++ return "QR"; ++ } ++ case keyring_controller_1.KeyringTypes.snap: { ++ return "Snap Account"; ++ } ++ case keyring_controller_1.KeyringTypes.custody: { ++ return "Custody"; ++ } ++ default: { ++ throw new Error(`Unknown keyring ${keyringType}`); ++ } ++ } + } + exports.keyringTypeToName = keyringTypeToName; + /** +@@ -48,10 +53,10 @@ exports.keyringTypeToName = keyringTypeToName; + * @returns The generated UUID. + */ + function getUUIDFromAddressOfNormalAccount(address) { +- const v4options = { +- random: (0, ethereumjs_util_1.sha256FromString)(address).slice(0, 16), +- }; +- return (0, uuid_1.v4)(v4options); ++ const v4options = { ++ random: (0, ethereumjs_util_1.sha256FromString)(address).slice(0, 16), ++ }; ++ return (0, uuid_1.v4)(v4options); + } + exports.getUUIDFromAddressOfNormalAccount = getUUIDFromAddressOfNormalAccount; + //# sourceMappingURL=utils.js.map diff --git a/.yarn/patches/@metamask-accounts-controller-npm-5.0.0-f877105fa0.patch b/.yarn/patches/@metamask-accounts-controller-npm-5.0.0-f877105fa0.patch deleted file mode 100644 index 83b3b7e0d62a..000000000000 --- a/.yarn/patches/@metamask-accounts-controller-npm-5.0.0-f877105fa0.patch +++ /dev/null @@ -1,18 +0,0 @@ -diff --git a/dist/utils.js b/dist/utils.js -index 810f229841ffff83f7a28191bc558862b1809e01..2daf3b12cf8a77aad9a5e0a6167734540805ecfc 100644 ---- a/dist/utils.js -+++ b/dist/utils.js -@@ -36,6 +36,13 @@ function keyringTypeToName(keyringType) { - case keyring_controller_1.KeyringTypes.custody: { - return 'Custody'; - } -+ /** -+ * PATCH INFORMATION - The keyring type used for custody account has been changed from 'Custody' to 'Custody - JSONRPC'. -+ * To do: Develop a better solution to keep all keyring types in sync. -+ */ -+ case 'Custody - JSONRPC': { -+ return 'Custody'; -+ } - default: { - throw new Error(`Unknown keyring ${keyringType}`); - } diff --git a/README.md b/README.md index 5854622d7b0c..0d802f5a6542 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D ## Building locally -- Install [Node.js](https://nodejs.org) version 18 +- Install [Node.js](https://nodejs.org) version 20 - If you are using [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) (recommended) running `nvm use` will automatically choose the right node version for you. - Enable Corepack by executing the command `corepack enable` within the metamask-extension project. Corepack is a utility included with Node.js by default. It manages Yarn on a per-project basis, using the version specified by the `packageManager` property in the project's package.json file. Please note that modern releases of [Yarn](https://yarnpkg.com/getting-started/install) are not intended to be installed globally or via npm. - Duplicate `.metamaskrc.dist` within the root and rename it to `.metamaskrc` by running `cp .metamaskrc{.dist,}`. diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index df3249954412..6e2203e74ae1 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "Bridge" }, - "bridgeDescription": { - "message": "Token von verschiedenen Netzwerken übermitteln" - }, - "bridgeDisabled": { - "message": "In diesem Netzwerk ist keine Brücke verfügbar" - }, "browserNotSupported": { "message": "Ihr Browser wird nicht unterstützt …" }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "Verbindungsanfrage" }, - "connections": { - "message": "Verbindungen" - }, "contactUs": { "message": "Kontaktaufnahme" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "Wählen Sie die Verwahrungskonten zur Nutzung in MetaMask Institutional aus." }, - "selectAnAction": { - "message": "Eine Aktion auswählen" - }, "selectHdPath": { "message": "HD-Pfad auswählen" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "Übermitteln Sie uns einen Fehlerbericht." }, - "sendDescription": { - "message": "Krypto an ein beliebiges Konto senden" - }, "sendSpecifiedTokens": { "message": "$1 senden", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "Anteil" }, - "stakeDescription": { - "message": "Halten Sie Ihre Kryptowährung und erzielen Sie potenzielle Gewinne" - }, "stateLogError": { "message": "Fehler beim Abfragen der Statusprotokolle." }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "Dezentralisierter Austausch" }, - "swapDescription": { - "message": "Token tauschen und handeln" - }, "swapDirectContract": { "message": "Direkter Contract" }, - "swapDisabled": { - "message": "Handeln ist in diesem Netzwerk nicht verfügbar" - }, "swapEditLimit": { "message": "Limit bearbeiten" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index c75b3949ca72..2da2bbea9cd4 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "Διασύνδεση" }, - "bridgeDescription": { - "message": "Μεταφορά tokens από διαφορετικά δίκτυα" - }, - "bridgeDisabled": { - "message": "Η διασύνδεση δεν είναι διαθέσιμη σε αυτό το δίκτυο" - }, "browserNotSupported": { "message": "Το Πρόγραμμα Περιήγησής σας δεν υποστηρίζεται..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "Αίτημα σύνδεσης" }, - "connections": { - "message": "Συνδέσεις" - }, "contactUs": { "message": "Επικοινωνήστε μαζί μας" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "Επιλέξτε τους λογαριασμούς θεματοφύλακα που θα χρησιμοποιηθούν στο MetaMask Institutional." }, - "selectAnAction": { - "message": "Επιλέξτε μια ενέργεια" - }, "selectHdPath": { "message": "Επιλέξτε τη διαδρομή HD" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "Στείλτε μας μια αναφορά σφάλματος." }, - "sendDescription": { - "message": "Στείλτε κρυπτονομίσματα σε οποιονδήποτε λογαριασμό" - }, "sendSpecifiedTokens": { "message": "Αποστολή $1", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "Stake" }, - "stakeDescription": { - "message": "Κρατήστε τα κρυπτονομίσματά σας και κερδίστε πιθανά κέρδη" - }, "stateLogError": { "message": "Σφάλμα κατά την ανάκτηση αρχείων καταγραφής κατάστασης." }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "Αποκεντρωμένη ανταλλαγή" }, - "swapDescription": { - "message": "Ανταλλάξτε και διαπραγματευτείτε τα tokens σας" - }, "swapDirectContract": { "message": "Άμεσο συμβόλαιο" }, - "swapDisabled": { - "message": "Η ανταλλαγή δεν είναι διαθέσιμη σε αυτό το δίκτυο" - }, "swapEditLimit": { "message": "Επεξεργασία ορίου" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 24185ec19f07..e14ebec0f238 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -634,12 +634,6 @@ "bridge": { "message": "Bridge" }, - "bridgeDescription": { - "message": "Transfer tokens from different networks" - }, - "bridgeDisabled": { - "message": "Bridge is not available in this network" - }, "browserNotSupported": { "message": "Your browser is not supported..." }, @@ -658,12 +652,6 @@ "buyAndSell": { "message": "Buy & Sell" }, - "buyAndSellDescription": { - "message": "Buy crypto or sell your crypto for cash" - }, - "buyAndSellDisabled": { - "message": "Buy & Sell is not available in this network" - }, "buyAsset": { "message": "Buy $1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" @@ -792,6 +780,12 @@ "confirmRecoveryPhrase": { "message": "Confirm Secret Recovery Phrase" }, + "confirmTitleDescSignature": { + "message": "Only sign this message if you fully understand the content and trust the requesting site" + }, + "confirmTitleSignature": { + "message": "Signature request" + }, "confirmed": { "message": "Confirmed" }, @@ -916,9 +910,6 @@ "connectionRequest": { "message": "Connection request" }, - "connections": { - "message": "Connections" - }, "contactUs": { "message": "Contact us" }, @@ -1673,7 +1664,7 @@ "message": "Suggest address names" }, "externalNameSourcesSettingDescription": { - "message": "We pull data from third parties like Etherscan, Infura, and Lens Protocol, to suggest names for addresses on signatures requests. Turning on name suggestions exposes your IP address to these third parties." + "message": "We pull data from third parties like Etherscan, Infura, and Lens Protocol, to suggest names for addresses on signature and transaction requests. Turning on name suggestions exposes your IP address to these third parties." }, "failed": { "message": "Failed" @@ -1916,7 +1907,6 @@ "hideSentitiveInfo": { "message": "Hide sensitive information" }, - "hideToken": { "message": "Hide token" }, @@ -1998,6 +1988,9 @@ "ignoreTokenWarning": { "message": "If you hide tokens, they will not be shown in your wallet. However, you can still add them by searching for them." }, + "imToken": { + "message": "imToken" + }, "import": { "message": "Import", "description": "Button to import an account from a selected file" @@ -2206,6 +2199,9 @@ "ipfsToggleModalSettings": { "message": "Settings > Security and privacy" }, + "isSigningOrSubmitting": { + "message": "A previous transaction is still being signed or submitted" + }, "jazzAndBlockies": { "message": "Jazzicons and Blockies are two different styles of unique icons that help you identify an account at a glance." }, @@ -3139,18 +3135,6 @@ "notificationsOpenBetaSnapsTitle": { "message": "Introducing MetaMask Snaps" }, - "notificationsStakingPortfolioActionText": { - "message": "Got it", - "description": "Action text for a notification in the 'See What's New' popup. Tells users that staking is now available in the portfolio app." - }, - "notificationsStakingPortfolioDescription": { - "message": "Now you can stake ETH and manage your rewards all in one place.", - "description": "Description for a notification in the 'See What's New' popup. Tells users that staking is now available in the portfolio app." - }, - "notificationsStakingPortfolioTitle": { - "message": "Stake smarter in Portfolio", - "description": "Title for a notification in the 'See What's New' popup. Tells users that staking is now available in the portfolio app." - }, "notificationsU2FLedgerLiveDescription": { "message": "U2F and Ledger Live are no longer available on Chrome. You can still connect Ledger devices on Chrome using Webhid.", "description": "Description of a notification in the 'See What's New' popup. Describes the U2F and Ledger Live connection modes are now deprecated" @@ -3629,6 +3613,12 @@ "personalAddressDetected": { "message": "Personal address detected. Input the token contract address." }, + "petnamesEnabledToggle": { + "message": "Petnames" + }, + "petnamesEnabledToggleDescription": { + "message": "This allows you to assign a name to any address. This name will be visible to you only." + }, "pinExtensionDescription": { "message": "Navigate to the extension menu and pin MetaMask Institutional for seamless access." }, @@ -4096,9 +4086,6 @@ "selectAccountsForSnap": { "message": "Select the account(s) to use with this snap" }, - "selectActionButton": { - "message": "Select action" - }, "selectAll": { "message": "Select all" }, @@ -4114,9 +4101,6 @@ "selectAnAccountHelp": { "message": "Select the custodian accounts to use in MetaMask Institutional." }, - "selectAnAction": { - "message": "Select an action" - }, "selectEnableDisplayMediaPrivacyPreference": { "message": "Turn on Display NFT Media" }, @@ -4147,9 +4131,6 @@ "sendBugReport": { "message": "Send us a bug report." }, - "sendDescription": { - "message": "Send crypto to any account" - }, "sendNoContactsConversionText": { "message": "click here" }, @@ -4206,8 +4187,8 @@ "settingsSearchMatchingNotFound": { "message": "No matching results found." }, - "settingsSubHeadingSignatures": { - "message": "Signature requests" + "settingsSubHeadingSignaturesAndTransactions": { + "message": "Signature and transaction requests" }, "show": { "message": "Show" @@ -4282,6 +4263,9 @@ "signin": { "message": "Sign-In" }, + "signing": { + "message": "Signing" + }, "simulationErrorMessageV2": { "message": "We were not able to estimate gas. There might be an error in the contract and this transaction may fail." }, @@ -4634,9 +4618,6 @@ "stake": { "message": "Stake" }, - "stakeDescription": { - "message": "Stake your crypto to earn rewards" - }, "stateLogError": { "message": "Error in retrieving state logs." }, @@ -4834,15 +4815,9 @@ "swapDecentralizedExchange": { "message": "Decentralized exchange" }, - "swapDescription": { - "message": "Swap and trade your tokens" - }, "swapDirectContract": { "message": "Direct contract" }, - "swapDisabled": { - "message": "Swap is not available in this network" - }, "swapEditLimit": { "message": "Edit limit" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 6bf1f5eabe37..a622ad164e56 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "Puente" }, - "bridgeDescription": { - "message": "Transferir tokens de diferentes redes" - }, - "bridgeDisabled": { - "message": "Bridge no está disponible en esta red" - }, "browserNotSupported": { "message": "Su explorador no es compatible..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "Solicitud de conexión" }, - "connections": { - "message": "Conexiones" - }, "contactUs": { "message": "Contáctenos" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "Seleccione las cuentas custodiadas para usar en MetaMask Institutional." }, - "selectAnAction": { - "message": "Seleccionar una acción" - }, "selectHdPath": { "message": "Seleccione la ruta de acceso al disco duro" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "Envíenos un informe de error." }, - "sendDescription": { - "message": "Enviar criptomonedas a cualquier cuenta" - }, "sendSpecifiedTokens": { "message": "Enviar $1", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "Staking" }, - "stakeDescription": { - "message": "Guarde su cripto y obtenga ganancias potenciales" - }, "stateLogError": { "message": "Error al recuperar los registros de estado." }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "Cambio descentralizado" }, - "swapDescription": { - "message": "Intercambie y opere con sus tokens" - }, "swapDirectContract": { "message": "Contrato directo" }, - "swapDisabled": { - "message": "El intercambio no está disponible en esta red" - }, "swapEditLimit": { "message": "Editar límite" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index f636f183083d..a1fd16bb48db 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "Pont" }, - "bridgeDescription": { - "message": "Transférez des jetons provenant de différents réseaux" - }, - "bridgeDisabled": { - "message": "La passerelle n’est pas disponible sur ce réseau" - }, "browserNotSupported": { "message": "Votre navigateur internet n’est pas compatible..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "Demande de connexion" }, - "connections": { - "message": "Connexions" - }, "contactUs": { "message": "Nous contacter" }, @@ -3572,9 +3563,6 @@ "selectAnAccountHelp": { "message": "Sélectionnez les comptes de dépôt que vous voulez utiliser dans MetaMask Institutional." }, - "selectAnAction": { - "message": "Sélectionner une action" - }, "selectHdPath": { "message": "Sélectionner le chemin HD" }, @@ -3599,9 +3587,6 @@ "sendBugReport": { "message": "Envoyez-nous un rapport de bogue." }, - "sendDescription": { - "message": "Transférez des crypto-actifs vers n’importe quel compte" - }, "sendSpecifiedTokens": { "message": "Envoyer $1", "description": "Symbol of the specified token" @@ -3951,9 +3936,6 @@ "stake": { "message": "Staker" }, - "stakeDescription": { - "message": "Conservez vos crypto-actifs et dégagez des bénéfices potentiels" - }, "stateLogError": { "message": "Erreur lors du chargement des journaux d’état." }, @@ -4139,15 +4121,9 @@ "swapDecentralizedExchange": { "message": "Échange décentralisé" }, - "swapDescription": { - "message": "Échangez vos jetons" - }, "swapDirectContract": { "message": "Contrat direct" }, - "swapDisabled": { - "message": "Impossible d’effectuer un échange sur ce réseau" - }, "swapEditLimit": { "message": "Modifier la limite" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index b2e96a1306fc..ba44de085b58 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "ब्रिज" }, - "bridgeDescription": { - "message": "अलग-अलग नेटवर्कों से टोकन ट्रांसफर करें" - }, - "bridgeDisabled": { - "message": "इस नेटवर्क में ब्रिज उपलब्ध नहीं है" - }, "browserNotSupported": { "message": "आपका ब्राउज़र सपोर्टेड नहीं है..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "कनेक्शन अनुरोध" }, - "connections": { - "message": "कनेक्शन" - }, "contactUs": { "message": "हमसे कॉन्टेक्ट करें" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "MetaMask Institutional में इस्तेमाल करने के लिए कस्टोडियन एकाउंट्स को चुनें।" }, - "selectAnAction": { - "message": "एक एक्शन चुनें" - }, "selectHdPath": { "message": "HD पथ को चुनें" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "हमें एक बग रिपोर्ट भेजें।" }, - "sendDescription": { - "message": "किसी भी खाता में क्रिप्टो भेजें" - }, "sendSpecifiedTokens": { "message": "$1 भेजें", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "हिस्सेदारी" }, - "stakeDescription": { - "message": "अपने क्रिप्टो को रोक कर रखें और शानदार लाभ कमाएं" - }, "stateLogError": { "message": "स्टेट लॉग को पुनर्प्राप्त करने में गड़बड़ी।" }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "विकेंद्रीकृत विनिमय" }, - "swapDescription": { - "message": "अपने टोकन स्वैप करें और ट्रेड करें" - }, "swapDirectContract": { "message": "प्रत्यक्ष कॉन्ट्रैक्ट" }, - "swapDisabled": { - "message": "इस नेटवर्क में स्वैप उपलब्ध नहीं है" - }, "swapEditLimit": { "message": "सीमा बदलें" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 4b08d153e91f..2b36f85dcbdd 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "Bridge" }, - "bridgeDescription": { - "message": "Transfer token dari jaringan yang berbeda" - }, - "bridgeDisabled": { - "message": "Bridge tidak tersedia di jaringan ini" - }, "browserNotSupported": { "message": "Browser Anda tidak didukung..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "Permintaan koneksi" }, - "connections": { - "message": "Koneksi" - }, "contactUs": { "message": "Hubungi kami" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "Pilih akun kustodian untuk digunakan di MetaMask Institutional." }, - "selectAnAction": { - "message": "Pilih tindakan" - }, "selectHdPath": { "message": "Pilih path HD" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "Kirimi kami laporan bug." }, - "sendDescription": { - "message": "Kirim kripto ke akun mana pun" - }, "sendSpecifiedTokens": { "message": "Kirim $1", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "Stake" }, - "stakeDescription": { - "message": "Pertahankan kripto Anda dan dapatkan potensi keuntungan" - }, "stateLogError": { "message": "Terjadi kesalahan pada log status pengambilan." }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "Bursa terdesentralisasi" }, - "swapDescription": { - "message": "Tukar dan perdagangkan token Anda" - }, "swapDirectContract": { "message": "Kontrak langsung" }, - "swapDisabled": { - "message": "Pertukaran tidak tersedia di jaringan ini" - }, "swapEditLimit": { "message": "Edit batas" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 3b53b796ec00..213da59f6196 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "ブリッジ" }, - "bridgeDescription": { - "message": "別のネットワークからトークンを転送します" - }, - "bridgeDisabled": { - "message": "ブリッジはこのネットワークで利用できません" - }, "browserNotSupported": { "message": "ご使用のブラウザはサポートされていません..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "接続リクエスト" }, - "connections": { - "message": "接続" - }, "contactUs": { "message": "お問い合わせ" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "MetaMask Institutionalで使用するカストディアンアカウントを選択します。" }, - "selectAnAction": { - "message": "アクションを選択してください" - }, "selectHdPath": { "message": "HDパスを選択" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "バグの報告をお送りください。" }, - "sendDescription": { - "message": "仮想通貨を任意のアカウントに送金します" - }, "sendSpecifiedTokens": { "message": "$1を送金", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "ステーク" }, - "stakeDescription": { - "message": "仮想通貨を保持して利益を得ます" - }, "stateLogError": { "message": "ステートログの取得中にエラーが発生しました。" }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "分散型取引所" }, - "swapDescription": { - "message": "トークンのスワップや取引を行います" - }, "swapDirectContract": { "message": "ダイレクトコントラクト" }, - "swapDisabled": { - "message": "このネットワークではスワップが利用できません" - }, "swapEditLimit": { "message": "限度額を編集" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index a209f6cea852..4d6b92ec6337 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "브리지" }, - "bridgeDescription": { - "message": "다양한 네트워크에서 토큰 전송" - }, - "bridgeDisabled": { - "message": "이 네트워크에서는 브릿지를 사용할 수 없습니다" - }, "browserNotSupported": { "message": "지원되지 않는 브라우저입니다..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "연결 요청" }, - "connections": { - "message": "연결" - }, "contactUs": { "message": "문의하기" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "MetaMask Institutional에서 사용할 수탁 계정을 선택하세요." }, - "selectAnAction": { - "message": "작업 선택" - }, "selectHdPath": { "message": "HD 경로 선택" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "버그 리포트 전송" }, - "sendDescription": { - "message": "모든 계정으로 암호화폐 보내기" - }, "sendSpecifiedTokens": { "message": "$1 보내기", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "스테이크" }, - "stakeDescription": { - "message": "암호화폐를 보유하고 잠재적인 수익을 얻으세요" - }, "stateLogError": { "message": "상태 로그를 가져오는 도중 오류가 발생했습니다." }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "분산형 교환" }, - "swapDescription": { - "message": "토큰 스왑 및 거래" - }, "swapDirectContract": { "message": "직접 계약" }, - "swapDisabled": { - "message": "이 네트워크에서는 스왑할 수 없습니다" - }, "swapEditLimit": { "message": "한도 편집" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 08ba050c67d7..a008906c8c1f 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "Ponte" }, - "bridgeDescription": { - "message": "Transfira tokens de redes diferentes" - }, - "bridgeDisabled": { - "message": "A ponte não está disponível nesta rede" - }, "browserNotSupported": { "message": "Seu navegador não é compatível..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "Solicitação de conexão" }, - "connections": { - "message": "Conexões" - }, "contactUs": { "message": "Fale conosco" }, @@ -3573,9 +3564,6 @@ "selectAnAccountHelp": { "message": "Selecione as contas custodiantes para usar no MetaMask Institutional." }, - "selectAnAction": { - "message": "Selecione uma ação" - }, "selectHdPath": { "message": "Selecione o caminho do disco rígido" }, @@ -3600,9 +3588,6 @@ "sendBugReport": { "message": "Envie-nos um relatório de bugs." }, - "sendDescription": { - "message": "Envie criptoativos para qualquer conta" - }, "sendSpecifiedTokens": { "message": "Enviar $1", "description": "Symbol of the specified token" @@ -3952,9 +3937,6 @@ "stake": { "message": "Stake" }, - "stakeDescription": { - "message": "Segure seus criptoativos e ganhe lucros em potencial" - }, "stateLogError": { "message": "Erro ao recuperar os logs de estado." }, @@ -4140,15 +4122,9 @@ "swapDecentralizedExchange": { "message": "Corretora descentralizada" }, - "swapDescription": { - "message": "Troque e negocie seus tokens" - }, "swapDirectContract": { "message": "Contrato direto" }, - "swapDisabled": { - "message": "A troca não está disponível nesta rede" - }, "swapEditLimit": { "message": "Editar limite" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 5e57dd99fa88..e6d074be82c2 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "Мост" }, - "bridgeDescription": { - "message": "Передавайте токены из разных сетей" - }, - "bridgeDisabled": { - "message": "В этой сети недоступен мост" - }, "browserNotSupported": { "message": "Ваш браузер не поддерживается..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "Запрос подключения" }, - "connections": { - "message": "Связи" - }, "contactUs": { "message": "Свяжитесь с нами" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "Выберите депозитарные счета для использования в MetaMask Institutional." }, - "selectAnAction": { - "message": "Выберите аукцион" - }, "selectHdPath": { "message": "Выберите путь HD" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "Отправьте нам отчет об ошибке." }, - "sendDescription": { - "message": "Отправляйте криптовалюту на любой счет" - }, "sendSpecifiedTokens": { "message": "Отправить $1", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "Выполнить стейкинг" }, - "stakeDescription": { - "message": "Держите криптовалюту и зарабатывайте потенциально прибыль" - }, "stateLogError": { "message": "Ошибка при получении журналов состояния." }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "Децентрализованная биржа" }, - "swapDescription": { - "message": "Выполняйте своп токенов и торгуйте ими" - }, "swapDirectContract": { "message": "Прямой контракт" }, - "swapDisabled": { - "message": "В этой сети недоступен своп" - }, "swapEditLimit": { "message": "Изменить лимит" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index d1ec71faf0a5..59ad2f4b3017 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "Bridge" }, - "bridgeDescription": { - "message": "Maglipat ng mga token mula sa iba't ibang network" - }, - "bridgeDisabled": { - "message": "Hindi available ang bridge sa network na ito" - }, "browserNotSupported": { "message": "Hindi sinusuportahan ang iyong browser..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "Kahilingan sa koneksyon" }, - "connections": { - "message": "Mga Koneksyon" - }, "contactUs": { "message": "Makipag-ugnayan sa amin" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "Piliin ang mga custodian account para gamitin sa MetaMask Institutional." }, - "selectAnAction": { - "message": "Pumili ng aksyon" - }, "selectHdPath": { "message": "Pumili ng HD Path" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "Padalhan kami ng ulat ng bug." }, - "sendDescription": { - "message": "Magpadala ng crypto sa anumang account" - }, "sendSpecifiedTokens": { "message": "Magpadala ng $1", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "Mag-stake" }, - "stakeDescription": { - "message": "I-hold ang iyong crypto at kumita ng potensyal na tubo" - }, "stateLogError": { "message": "Error sa pagkuha ng mga log ng estado." }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "Decentralized exchange" }, - "swapDescription": { - "message": "Ipagpalit at i-trade ang iyong mga token" - }, "swapDirectContract": { "message": "Direktang kontrata" }, - "swapDisabled": { - "message": "Hindi available ang swap sa network na ito" - }, "swapEditLimit": { "message": "I-edit ang limitasyon" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index a1155795312e..5df99319d8e6 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "Köprü" }, - "bridgeDescription": { - "message": "Farklı ağlardan tokenleri transfer et" - }, - "bridgeDisabled": { - "message": "Köprü bu ağda kullanılamaz" - }, "browserNotSupported": { "message": "Tarayıcınız desteklenmiyor..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "Bağlantı talebi" }, - "connections": { - "message": "Bağlantılar" - }, "contactUs": { "message": "Bize ulaşın" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "MetaMask Institutional'da kullanılacak saklayıcı kurum hesaplarını seçin." }, - "selectAnAction": { - "message": "Bir eylem seçin" - }, "selectHdPath": { "message": "HD yolunu seç" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "Bize bir hata raporu gönder." }, - "sendDescription": { - "message": "Dilediğiniz hesaba kripto gönderin" - }, "sendSpecifiedTokens": { "message": "$1 Gönder", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "Pay" }, - "stakeDescription": { - "message": "Kriptonuzu tutun ve potansiyel kârlar elde edin" - }, "stateLogError": { "message": "Durum günlükleri alınırken hata." }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "Merkeziyetsiz borsa" }, - "swapDescription": { - "message": "Tokenlerinizle swap ve ticaret yapın" - }, "swapDirectContract": { "message": "Doğrudan sözleşme" }, - "swapDisabled": { - "message": "Swap bu ağda kullanılamaz" - }, "swapEditLimit": { "message": "Limiti düzenle" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index ec301c511b71..03b3454e0c61 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "Cầu nối" }, - "bridgeDescription": { - "message": "Chuyển token từ các mạng khác nhau" - }, - "bridgeDisabled": { - "message": "Cầu không khả dụng trong mạng này" - }, "browserNotSupported": { "message": "Trình duyệt của bạn không được hỗ trợ..." }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "Yêu cầu kết nối" }, - "connections": { - "message": "Kết nối" - }, "contactUs": { "message": "Liên hệ với chúng tôi" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "Chọn tài khoản lưu ký để sử dụng trong MetaMask Institutional." }, - "selectAnAction": { - "message": "Chọn một hành động" - }, "selectHdPath": { "message": "Chọn đường dẫn HD" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "Gửi báo cáo lỗi." }, - "sendDescription": { - "message": "Gửi tiền mã hóa đến bất kỳ tài khoản nào" - }, "sendSpecifiedTokens": { "message": "Gửi $1", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "Stake" }, - "stakeDescription": { - "message": "Giữ tiền mã hóa và kiếm lợi nhuận tiềm năng" - }, "stateLogError": { "message": "Lỗi khi truy xuất nhật ký trạng thái." }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "Sàn giao dịch phi tập trung" }, - "swapDescription": { - "message": "Hoán đổi và giao dịch token của bạn" - }, "swapDirectContract": { "message": "Hợp đồng trực tiếp" }, - "swapDisabled": { - "message": "Hoán đổi không khả dụng trong mạng này" - }, "swapEditLimit": { "message": "Chỉnh sửa giới hạn" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index afae5f8bcbf3..6ef6b2407274 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -586,12 +586,6 @@ "bridge": { "message": "跨链桥" }, - "bridgeDescription": { - "message": "从不同网络传送代币" - }, - "bridgeDisabled": { - "message": "在此网络中无法使用跨链桥" - }, "browserNotSupported": { "message": "您的浏览器不受支持……" }, @@ -828,9 +822,6 @@ "connectionRequest": { "message": "连接请求" }, - "connections": { - "message": "连接" - }, "contactUs": { "message": "联系我们" }, @@ -3569,9 +3560,6 @@ "selectAnAccountHelp": { "message": "选择要在 MetaMask Institutional 使用的托管账户。" }, - "selectAnAction": { - "message": "选择操作" - }, "selectHdPath": { "message": "选择 HD 路径" }, @@ -3596,9 +3584,6 @@ "sendBugReport": { "message": "向我们发送错误报告。" }, - "sendDescription": { - "message": "将加密货币发送到任何账户" - }, "sendSpecifiedTokens": { "message": "发送 $1", "description": "Symbol of the specified token" @@ -3948,9 +3933,6 @@ "stake": { "message": "质押" }, - "stakeDescription": { - "message": "继续持有加密货币,赚取潜在利润" - }, "stateLogError": { "message": "检索状态日志时出错。" }, @@ -4136,15 +4118,9 @@ "swapDecentralizedExchange": { "message": "去中心化交易所" }, - "swapDescription": { - "message": "兑换和交易您的代币" - }, "swapDirectContract": { "message": "直接合约" }, - "swapDisabled": { - "message": "在此网络无法兑换代币" - }, "swapEditLimit": { "message": "编辑限制" }, diff --git a/app/images/acala-network-logo.svg b/app/images/acala-network-logo.svg new file mode 100644 index 000000000000..04f09bc2a57f --- /dev/null +++ b/app/images/acala-network-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/images/arbitrum-nova-logo.svg b/app/images/arbitrum-nova-logo.svg new file mode 100644 index 000000000000..bc54b2f8ba4a --- /dev/null +++ b/app/images/arbitrum-nova-logo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/images/arbitrum.svg b/app/images/arbitrum.svg index 5e79c8ccd5c7..8863afe882c8 100644 --- a/app/images/arbitrum.svg +++ b/app/images/arbitrum.svg @@ -1,9 +1,12 @@ - - - - - - - - + + + + + + + \ No newline at end of file diff --git a/app/images/astar-logo.svg b/app/images/astar-logo.svg new file mode 100644 index 000000000000..c9d9cd5954cb --- /dev/null +++ b/app/images/astar-logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/images/avax-token.svg b/app/images/avax-token.svg new file mode 100644 index 000000000000..55473a0f2400 --- /dev/null +++ b/app/images/avax-token.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/images/bahamut.png b/app/images/bahamut.png new file mode 100644 index 000000000000..81fbdb1698eb Binary files /dev/null and b/app/images/bahamut.png differ diff --git a/app/images/base.svg b/app/images/base.svg new file mode 100644 index 000000000000..22d89358f633 --- /dev/null +++ b/app/images/base.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/blackfort.png b/app/images/blackfort.png new file mode 100644 index 000000000000..bb4415d4158a Binary files /dev/null and b/app/images/blackfort.png differ diff --git a/app/images/bnb.svg b/app/images/bnb.svg new file mode 100644 index 000000000000..f39dba1be163 --- /dev/null +++ b/app/images/bnb.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/app/images/canto.svg b/app/images/canto.svg new file mode 100644 index 000000000000..2d6a2c860c99 --- /dev/null +++ b/app/images/canto.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/celo.svg b/app/images/celo.svg index f13ac7edc56e..521b76d1a95a 100644 --- a/app/images/celo.svg +++ b/app/images/celo.svg @@ -1 +1,7 @@ -Artboard 1 \ No newline at end of file + + + + + diff --git a/app/images/conflux.svg b/app/images/conflux.svg new file mode 100644 index 000000000000..711878c042d9 --- /dev/null +++ b/app/images/conflux.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/images/core.svg b/app/images/core.svg new file mode 100644 index 000000000000..1663f3cc144e --- /dev/null +++ b/app/images/core.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/images/cronos.svg b/app/images/cronos.svg new file mode 100644 index 000000000000..3b9261d4378c --- /dev/null +++ b/app/images/cronos.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/app/images/dexalut-subnet.svg b/app/images/dexalut-subnet.svg new file mode 100644 index 000000000000..7d37e7e9c4ce --- /dev/null +++ b/app/images/dexalut-subnet.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/app/images/dfk.png b/app/images/dfk.png new file mode 100644 index 000000000000..ae328921822f Binary files /dev/null and b/app/images/dfk.png differ diff --git a/app/images/dogechain.jpeg b/app/images/dogechain.jpeg new file mode 100644 index 000000000000..f24299bc2c74 Binary files /dev/null and b/app/images/dogechain.jpeg differ diff --git a/app/images/endurance-smart-chain-mainnet.png b/app/images/endurance-smart-chain-mainnet.png new file mode 100644 index 000000000000..c733c586b621 Binary files /dev/null and b/app/images/endurance-smart-chain-mainnet.png differ diff --git a/app/images/eth_classic.svg b/app/images/eth_classic.svg new file mode 100644 index 000000000000..92146954cde6 --- /dev/null +++ b/app/images/eth_classic.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/app/images/eth_logo.svg b/app/images/eth_logo.svg new file mode 100644 index 000000000000..356b8e8779f2 --- /dev/null +++ b/app/images/eth_logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/images/evmos.svg b/app/images/evmos.svg new file mode 100644 index 000000000000..8be9324b476a --- /dev/null +++ b/app/images/evmos.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/images/fantom-opera.svg b/app/images/fantom-opera.svg index 02297ee3a331..292ecaee00cf 100644 --- a/app/images/fantom-opera.svg +++ b/app/images/fantom-opera.svg @@ -1 +1,10 @@ - \ No newline at end of file + + + Fantom circle@2x + + + + + + + \ No newline at end of file diff --git a/app/images/flare-mainnet.svg b/app/images/flare-mainnet.svg new file mode 100644 index 000000000000..f2ec35a35668 --- /dev/null +++ b/app/images/flare-mainnet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/images/fuse-mainnet.jpeg b/app/images/fuse-mainnet.jpeg new file mode 100644 index 000000000000..5c8d583b2889 Binary files /dev/null and b/app/images/fuse-mainnet.jpeg differ diff --git a/app/images/haqq.svg b/app/images/haqq.svg new file mode 100644 index 000000000000..1452fe7e6ef0 --- /dev/null +++ b/app/images/haqq.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/icons/stake.svg b/app/images/icons/stake.svg deleted file mode 100644 index 54c6762e3cab..000000000000 --- a/app/images/icons/stake.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/images/kcc-mainnet.svg b/app/images/kcc-mainnet.svg new file mode 100644 index 000000000000..5835b1cd2e07 --- /dev/null +++ b/app/images/kcc-mainnet.svg @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/app/images/klaytn.svg b/app/images/klaytn.svg new file mode 100644 index 000000000000..4eb1ff5f107b --- /dev/null +++ b/app/images/klaytn.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/kroma.svg b/app/images/kroma.svg new file mode 100644 index 000000000000..8e0c57d828ff --- /dev/null +++ b/app/images/kroma.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/app/images/lightlink.svg b/app/images/lightlink.svg new file mode 100644 index 000000000000..a41b9f0ded27 --- /dev/null +++ b/app/images/lightlink.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/linea-logo-mainnet.svg b/app/images/linea-logo-mainnet.svg new file mode 100644 index 000000000000..bb56377bd349 --- /dev/null +++ b/app/images/linea-logo-mainnet.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/app/images/manta.svg b/app/images/manta.svg new file mode 100644 index 000000000000..cc407e9c04f4 --- /dev/null +++ b/app/images/manta.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/images/mantle.svg b/app/images/mantle.svg new file mode 100644 index 000000000000..d8fb1ccfe42e --- /dev/null +++ b/app/images/mantle.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/matic-token.svg b/app/images/matic-token.svg new file mode 100644 index 000000000000..10b192a9fa15 --- /dev/null +++ b/app/images/matic-token.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/app/images/moonbeam.svg b/app/images/moonbeam.svg new file mode 100644 index 000000000000..106338e4f1e0 --- /dev/null +++ b/app/images/moonbeam.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/images/moonriver.svg b/app/images/moonriver.svg new file mode 100644 index 000000000000..b52ac55335e1 --- /dev/null +++ b/app/images/moonriver.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/near-aurora.svg b/app/images/near-aurora.svg new file mode 100644 index 000000000000..2797061f9b88 --- /dev/null +++ b/app/images/near-aurora.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/images/nebula.svg b/app/images/nebula.svg new file mode 100644 index 000000000000..8dd2bc0199dd --- /dev/null +++ b/app/images/nebula.svg @@ -0,0 +1 @@ +画板 1 \ No newline at end of file diff --git a/app/images/oasys.svg b/app/images/oasys.svg new file mode 100644 index 000000000000..3570b8fceea8 --- /dev/null +++ b/app/images/oasys.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/okx.svg b/app/images/okx.svg new file mode 100644 index 000000000000..1ee5d55653a7 --- /dev/null +++ b/app/images/okx.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/app/images/optimism.svg b/app/images/optimism.svg index e8c6e64e4e6e..2e5ce3103d08 100644 --- a/app/images/optimism.svg +++ b/app/images/optimism.svg @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/images/pgn.svg b/app/images/pgn.svg new file mode 100644 index 000000000000..11a983b149d9 --- /dev/null +++ b/app/images/pgn.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/images/polygon-zkevm.svg b/app/images/polygon-zkevm.svg new file mode 100644 index 000000000000..b70d50aa752d --- /dev/null +++ b/app/images/polygon-zkevm.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/app/images/portfolio-stake-notification-light-mode.png b/app/images/portfolio-stake-notification-light-mode.png deleted file mode 100644 index 33cbe2933920..000000000000 Binary files a/app/images/portfolio-stake-notification-light-mode.png and /dev/null differ diff --git a/app/images/pulse.svg b/app/images/pulse.svg new file mode 100644 index 000000000000..dec9daf839db --- /dev/null +++ b/app/images/pulse.svg @@ -0,0 +1,18 @@ + + + PulseChain Logo Shape + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/images/shardeum-1.svg b/app/images/shardeum-1.svg new file mode 100644 index 000000000000..66f8716d7c74 --- /dev/null +++ b/app/images/shardeum-1.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/images/shardeum-2.svg b/app/images/shardeum-2.svg new file mode 100644 index 000000000000..a7e59cef4b25 --- /dev/null +++ b/app/images/shardeum-2.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/images/shiba.svg b/app/images/shiba.svg new file mode 100644 index 000000000000..78588a2ab961 --- /dev/null +++ b/app/images/shiba.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + diff --git a/app/images/songbird.svg b/app/images/songbird.svg new file mode 100644 index 000000000000..3adced1d1d69 --- /dev/null +++ b/app/images/songbird.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/images/step.svg b/app/images/step.svg new file mode 100644 index 000000000000..d4a282f65a39 --- /dev/null +++ b/app/images/step.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/telos.svg b/app/images/telos.svg new file mode 100644 index 000000000000..49cc05f821a7 Binary files /dev/null and b/app/images/telos.svg differ diff --git a/app/images/tenet.svg b/app/images/tenet.svg new file mode 100644 index 000000000000..af4092c75b34 --- /dev/null +++ b/app/images/tenet.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/xdc-token.png b/app/images/xdc-token.png new file mode 100644 index 000000000000..ec74f7ace1b1 Binary files /dev/null and b/app/images/xdc-token.png differ diff --git a/app/images/zk-sync.svg b/app/images/zk-sync.svg index ad3f730f4147..55021930f1da 100644 --- a/app/images/zk-sync.svg +++ b/app/images/zk-sync.svg @@ -1,5 +1,14 @@ - - - - + + + + + + + + + diff --git a/app/images/zkatana.png b/app/images/zkatana.png new file mode 100644 index 000000000000..e26636312cc9 Binary files /dev/null and b/app/images/zkatana.png differ diff --git a/app/images/zora.svg b/app/images/zora.svg new file mode 100644 index 000000000000..c7315c1fba13 --- /dev/null +++ b/app/images/zora.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 7ab11ecbb908..c8e46a5283b1 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -156,6 +156,7 @@ export default class MetaMetricsController { metaMetricsId: null, eventsBeforeMetricsOptIn: [], traits: {}, + previousUserTraits: {}, ...initState, fragments: { ...initState?.fragments, @@ -790,7 +791,7 @@ export default class MetaMetricsController { [MetaMetricsUserTrait.NftAutodetectionEnabled]: metamaskState.useNftDetection, [MetaMetricsUserTrait.NumberOfAccounts]: Object.values( - metamaskState.identities, + metamaskState.internalAccounts.accounts, ).length, [MetaMetricsUserTrait.NumberOfNftCollections]: this._getAllUniqueNFTAddressesLength(metamaskState.allNfts), @@ -818,10 +819,8 @@ export default class MetaMetricsController { [MetaMetricsUserTrait.SecurityProviders]: securityProvider ? [securityProvider] : [], - ///: BEGIN:ONLY_INCLUDE_IF(petnames) [MetaMetricsUserTrait.PetnameAddressCount]: this._getPetnameAddressCount(metamaskState), - ///: END:ONLY_INCLUDE_IF }; if (!previousUserTraits) { diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 82517c821a05..9a7540c078f4 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -1024,6 +1024,12 @@ describe('MetaMetricsController', function () { }, 'network-configuration-id-3': { chainId: '0xaf' }, }, + internalAccounts: { + accounts: { + mock1: {}, + mock2: {}, + }, + }, identities: [{}, {}], ledgerTransportType: 'web-hid', openSeaEnabled: true, @@ -1115,6 +1121,12 @@ describe('MetaMetricsController', function () { }, ledgerTransportType: 'web-hid', openSeaEnabled: true, + internalAccounts: { + accounts: { + mock1: {}, + mock2: {}, + }, + }, identities: [{}, {}], useNftDetection: false, theme: 'default', @@ -1139,6 +1151,13 @@ describe('MetaMetricsController', function () { }, ledgerTransportType: 'web-hid', openSeaEnabled: false, + internalAccounts: { + accounts: { + mock1: {}, + mock2: {}, + mock3: {}, + }, + }, identities: [{}, {}, {}], useNftDetection: false, theme: 'default', @@ -1170,6 +1189,12 @@ describe('MetaMetricsController', function () { }, ledgerTransportType: 'web-hid', openSeaEnabled: true, + internalAccounts: { + accounts: { + mock1: {}, + mock2: {}, + }, + }, identities: [{}, {}], useNftDetection: true, theme: 'default', @@ -1190,6 +1215,12 @@ describe('MetaMetricsController', function () { }, ledgerTransportType: 'web-hid', openSeaEnabled: true, + internalAccounts: { + accounts: { + mock1: {}, + mock2: {}, + }, + }, identities: [{}, {}], useNftDetection: true, theme: 'default', diff --git a/app/scripts/controllers/network-order.ts b/app/scripts/controllers/network-order.ts index d579956a807f..8bd8372d05c0 100644 --- a/app/scripts/controllers/network-order.ts +++ b/app/scripts/controllers/network-order.ts @@ -15,11 +15,15 @@ const controllerName = 'NetworkOrderController'; /** * The network ID of a network. */ -export type NetworkId = string; + +export type NetworksInfo = { + networkId: string; + networkRpcUrl: string; +}; // State shape for NetworkOrderController export type NetworkOrderControllerState = { - orderedNetworkList: NetworkId[]; + orderedNetworkList: NetworksInfo[]; }; // Describes the structure of a state change event @@ -48,7 +52,7 @@ export type NetworkOrderControllerMessenger = RestrictedControllerMessenger< >; // Default state for the controller -const defaultState = { +const defaultState: NetworkOrderControllerState = { orderedNetworkList: [], }; @@ -116,17 +120,40 @@ export class NetworkOrderController extends BaseController< const combinedNetworks = [...MAINNET_CHAINS, ...networkConfigurations]; // Extract unique chainIds from the combined networks - const uniqueChainIds = combinedNetworks.map((item) => item.chainId); + const uniqueChainIds = combinedNetworks.map((item) => ({ + networkId: item.chainId, + networkRpcUrl: item.rpcUrl, + })); + + // Arrays to store reordered and new unique chainIds + let reorderedNetworks: NetworksInfo[] = []; + const newUniqueNetworks: NetworksInfo[] = []; + + // Iterate through uniqueChainIds to reorder existing elements + uniqueChainIds.forEach((newItem) => { + const existingIndex = this.state.orderedNetworkList.findIndex( + (item) => + item.networkId === newItem.networkId && + item.networkRpcUrl === newItem.networkRpcUrl, + ); + // eslint-disable-next-line no-negated-condition + if (existingIndex !== -1) { + // Reorder existing element + reorderedNetworks[existingIndex] = newItem; + } else { + // Add new unique element + newUniqueNetworks.push(newItem); + } + }); + + // Filter out null values and concatenate reordered and new unique networks + reorderedNetworks = reorderedNetworks + .filter((item) => Boolean(item)) + .concat(newUniqueNetworks); // Update the state with the new networks list this.update((state) => { - // Combine existing networks with unique chainIds, excluding duplicates - state.orderedNetworkList = [ - ...state.orderedNetworkList, - ...uniqueChainIds.filter( - (id) => !state.orderedNetworkList.includes(id), - ), - ]; + state.orderedNetworkList = reorderedNetworks; }); } diff --git a/app/scripts/controllers/preferences.d.ts b/app/scripts/controllers/preferences.d.ts new file mode 100644 index 000000000000..a01cc27101e3 --- /dev/null +++ b/app/scripts/controllers/preferences.d.ts @@ -0,0 +1,16 @@ +export type AccountIdentityEntry = { + address: string; + name: string; +}; + +export type PreferencesControllerState = { + identities: { [address: string]: AccountIdentityEntry }; + securityAlertsEnabled: boolean; +}; + +export type PreferencesController = { + store: { + getState: () => PreferencesControllerState; + subscribe: (callback: (state: PreferencesControllerState) => void) => void; + }; +}; diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index bcf39d31bedb..9f410f28b6ef 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -91,6 +91,7 @@ export default class PreferencesController { showTestNetworks: false, useNativeCurrencyAsPrimaryCurrency: true, hideZeroBalanceTokens: false, + petnamesEnabled: true, }, // ENS decentralized website resolution ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, @@ -107,9 +108,7 @@ export default class PreferencesController { snapsAddSnapAccountModalDismissed: false, ///: END:ONLY_INCLUDE_IF isLineaMainnetReleased: false, - ///: BEGIN:ONLY_INCLUDE_IF(petnames) useExternalNameSources: true, - ///: END:ONLY_INCLUDE_IF ...opts.initState, }; @@ -271,7 +270,6 @@ export default class PreferencesController { } ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(petnames) /** * Setter for the `useExternalNameSources` property * @@ -282,7 +280,6 @@ export default class PreferencesController { useExternalNameSources, }); } - ///: END:ONLY_INCLUDE_IF /** * Setter for the `advancedGasFee` property diff --git a/app/scripts/lib/AbstractPetnamesBridge.test.ts b/app/scripts/lib/AbstractPetnamesBridge.test.ts new file mode 100644 index 000000000000..608454b38afe --- /dev/null +++ b/app/scripts/lib/AbstractPetnamesBridge.test.ts @@ -0,0 +1,454 @@ +import { + FALLBACK_VARIATION, + NameController, + NameControllerState, + NameOrigin, + NameType, +} from '@metamask/name-controller'; +import { + AbstractPetnamesBridge, + ChangeType, + PetnameEntry, + PetnamesBridgeMessenger, +} from './AbstractPetnamesBridge'; + +const ADDRESS_MIXEDCASE_MOCK = '0xAbc'; +const ADDRESS_LOWERCASE_MOCK = ADDRESS_MIXEDCASE_MOCK.toLowerCase(); +const NAME1_MOCK = 'name1'; +const NAME2_MOCK = 'name2'; +const CHAIN_ID_MOCK = '0x1'; +const ORIGIN_MOCK = NameOrigin.ADDRESS_BOOK; + +const NON_PARTICIPANT_ORIGIN_MOCK = NameOrigin.API; +const PARTICIPANT_ORIGIN_MOCK = NameOrigin.ACCOUNT_IDENTITY; + +const NO_SOURCE_ENTRIES: PetnameEntry[] = []; + +/** + * Creates a PetnameEntry with the given name and address. + * + * @param address + * @param name + */ +function createPetnameEntry(address: string, name: string): PetnameEntry { + return { + value: address, + name, + type: NameType.ETHEREUM_ADDRESS, + sourceId: undefined, + variation: CHAIN_ID_MOCK, + origin: ORIGIN_MOCK, + }; +} + +const EMPTY_NAME_STATE: NameControllerState = { + names: { + [NameType.ETHEREUM_ADDRESS]: {}, + }, + nameSources: {}, +}; + +/** + * Creates NameControllerState containing a single Petname with the given name and address. + * This is used to simulate a NameController state where a Petname has been set + * with a call to NameController.setName(createPetnameEntry(name) as SetNameRequest). + * + * @param address + * @param name + */ +function createNameState(address: string, name: string): NameControllerState { + return { + ...EMPTY_NAME_STATE, + names: { + [NameType.ETHEREUM_ADDRESS]: { + [address]: { + [CHAIN_ID_MOCK]: { + name, + sourceId: null, + proposedNames: {}, + origin: ORIGIN_MOCK, + }, + }, + }, + }, + }; +} + +class TestPetnamesBridge extends AbstractPetnamesBridge { + getSourceEntries = jest.fn(); + + updateSourceEntry = jest.fn(); + + onSourceChange = jest.fn(); + + shouldSyncPetname = jest.fn((_: PetnameEntry) => true); +} + +function createNameControllerMock(state: NameControllerState) { + return { + state, + setName: jest.fn(), + } as unknown as jest.Mocked; +} + +function createMessengerMock(): jest.Mocked { + return { + subscribe: jest.fn(), + } as any; +} + +describe('AbstractPetnamesBridge', () => { + let messenger: jest.Mocked; + + beforeEach(() => { + jest.resetAllMocks(); + messenger = createMessengerMock(); + }); + + describe('init', () => { + it('should subscribe to name controller and source changes when two-way bridge', () => { + const nameController = createNameControllerMock(EMPTY_NAME_STATE); + const bridge = new TestPetnamesBridge({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.init(); + + expect(messenger.subscribe).toHaveBeenCalledWith( + 'NameController:stateChange', + expect.any(Function), + ); + }); + + it('should not subscribe to name controller changes when one-way bridge', () => { + const nameController = createNameControllerMock(EMPTY_NAME_STATE); + const bridge = new TestPetnamesBridge({ + isTwoWay: false, + nameController, + messenger, + }); + bridge.init(); + + expect(messenger.subscribe).not.toHaveBeenCalled(); + }); + }); + + describe('synchronize Source->Petnames', () => { + it('adds entry when Source entry is added', () => { + const nameController = createNameControllerMock(EMPTY_NAME_STATE); + const bridge = new TestPetnamesBridge({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.init(); + + const PETNAME_ENTRY = createPetnameEntry( + ADDRESS_LOWERCASE_MOCK, + NAME1_MOCK, + ); + bridge.getSourceEntries.mockReturnValue([PETNAME_ENTRY]); + + const sourceListener = bridge.onSourceChange.mock.calls[0][0]; + sourceListener(); + + expect(nameController.setName).toHaveBeenCalledTimes(1); + expect(nameController.setName).toHaveBeenCalledWith(PETNAME_ENTRY); + }); + + it('updates entry when Source entry is updated', () => { + const nameController = createNameControllerMock( + createNameState(ADDRESS_LOWERCASE_MOCK, NAME1_MOCK), + ); + const bridge = new TestPetnamesBridge({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.init(); + + const UPDATED_PETNAME_ENTRY = createPetnameEntry( + ADDRESS_LOWERCASE_MOCK, + 'updatedName', + ); + bridge.getSourceEntries.mockReturnValue([UPDATED_PETNAME_ENTRY]); + + const sourceListener = bridge.onSourceChange.mock.calls[0][0]; + sourceListener(); + + expect(nameController.setName).toHaveBeenCalledTimes(1); + expect(nameController.setName).toHaveBeenCalledWith( + UPDATED_PETNAME_ENTRY, + ); + }); + + it('deletes entry when Source entry is deleted if two-way bridge', () => { + const nameController = createNameControllerMock( + createNameState(ADDRESS_LOWERCASE_MOCK, NAME1_MOCK), + ); + const bridge = new TestPetnamesBridge({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.init(); + + bridge.getSourceEntries.mockReturnValue(NO_SOURCE_ENTRIES); + + const sourceListener = bridge.onSourceChange.mock.calls[0][0]; + sourceListener(); + + expect(nameController.setName).toHaveBeenCalledTimes(1); + expect(nameController.setName).toHaveBeenCalledWith({ + value: ADDRESS_LOWERCASE_MOCK, + variation: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + // Name is set to null. sourceId and origin should not be set. + name: null, + }); + }); + + it('uses lowercase values when comparing ethereumAddresses', () => { + const nameController = createNameControllerMock( + createNameState(ADDRESS_LOWERCASE_MOCK, NAME1_MOCK), + ); + const bridge = new TestPetnamesBridge({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.init(); + + const PETNAME_ENTRY = createPetnameEntry( + ADDRESS_MIXEDCASE_MOCK, + NAME1_MOCK, + ); + bridge.getSourceEntries.mockReturnValue([PETNAME_ENTRY]); + + const sourceListener = bridge.onSourceChange.mock.calls[0][0]; + sourceListener(); + + // No change should be made because the lower-case and mixed-case addresses + // should be considered equal. + expect(nameController.setName).not.toHaveBeenCalled(); + }); + + describe('shouldSyncPetname', () => { + it('masks certain Petname entries from being deleted', () => { + const nameController = createNameControllerMock({ + ...EMPTY_NAME_STATE, + names: { + [NameType.ETHEREUM_ADDRESS]: { + [ADDRESS_LOWERCASE_MOCK]: { + [CHAIN_ID_MOCK]: { + origin: NON_PARTICIPANT_ORIGIN_MOCK, + name: NAME1_MOCK, + sourceId: null, + proposedNames: {}, + }, + [FALLBACK_VARIATION]: { + origin: PARTICIPANT_ORIGIN_MOCK, + name: NAME2_MOCK, + sourceId: null, + proposedNames: {}, + }, + }, + }, + }, + }); + const bridge = new TestPetnamesBridge({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.shouldSyncPetname.mockImplementation( + (entry: PetnameEntry) => entry.origin === PARTICIPANT_ORIGIN_MOCK, + ); + bridge.init(); + + bridge.getSourceEntries.mockReturnValue(NO_SOURCE_ENTRIES); + + const sourceListener = bridge.onSourceChange.mock.calls[0][0]; + sourceListener(); + + // Only the entry with the PARTICIPANT_ORIGIN_MOCK should be deleted. + expect(nameController.setName).toHaveBeenCalledTimes(1); + expect(nameController.setName).toHaveBeenCalledWith({ + value: ADDRESS_LOWERCASE_MOCK, + variation: FALLBACK_VARIATION, + type: NameType.ETHEREUM_ADDRESS, + // Name is set to null. sourceId and origin should not be set. + name: null, + }); + }); + }); + }); + + describe('synchronize Petnames->Source (two-way bridge only)', () => { + it('should throw an error when updateSourceEntry is not overridden', () => { + const nameController = createNameControllerMock( + createNameState(ADDRESS_LOWERCASE_MOCK, NAME1_MOCK), + ); + const bridge = new (class extends AbstractPetnamesBridge { + // NOTE: updateSourceEntry is not overridden for this test class. + getSourceEntries = jest.fn(); + + onSourceChange = jest.fn(); + })({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.init(); + + bridge.getSourceEntries.mockReturnValue(NO_SOURCE_ENTRIES); + + expect(() => { + const petnamesListener = messenger.subscribe.mock + .calls[0][1] as () => void; + petnamesListener(); + }).toThrowError( + 'updateSourceEntry must be overridden for two-way bridges', + ); + }); + + it('calls updateSourceEntry with ADDED entry when Petnames entry is added', () => { + const nameController = createNameControllerMock( + createNameState(ADDRESS_LOWERCASE_MOCK, NAME1_MOCK), + ); + const bridge = new TestPetnamesBridge({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.init(); + + bridge.getSourceEntries.mockReturnValue(NO_SOURCE_ENTRIES); + + const petnamesListener = messenger.subscribe.mock + .calls[0][1] as () => void; + petnamesListener(); + + expect(bridge.updateSourceEntry).toHaveBeenCalledTimes(1); + expect(bridge.updateSourceEntry).toHaveBeenCalledWith( + ChangeType.ADDED, + createPetnameEntry(ADDRESS_LOWERCASE_MOCK, NAME1_MOCK), + ); + }); + + it('calls updateSourceEntry with UPDATED entry when Petnames entry is updated', () => { + const nameController = createNameControllerMock( + createNameState(ADDRESS_LOWERCASE_MOCK, NAME1_MOCK), + ); + const bridge = new TestPetnamesBridge({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.init(); + + bridge.getSourceEntries.mockReturnValue([ + createPetnameEntry(ADDRESS_LOWERCASE_MOCK, NAME1_MOCK), + ]); + + const UPDATED_NAME = 'updatedName'; + nameController.state = createNameState( + ADDRESS_LOWERCASE_MOCK, + UPDATED_NAME, + ); + + const petnamesListener = messenger.subscribe.mock + .calls[0][1] as () => void; + petnamesListener(); + + expect(bridge.updateSourceEntry).toHaveBeenCalledTimes(1); + expect(bridge.updateSourceEntry).toHaveBeenCalledWith( + ChangeType.UPDATED, + createPetnameEntry(ADDRESS_LOWERCASE_MOCK, UPDATED_NAME), + ); + }); + + it('calls updateSourceEntry with DELETED entry when Petnames entry is deleted', () => { + const nameController = createNameControllerMock(EMPTY_NAME_STATE); + const bridge = new TestPetnamesBridge({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.init(); + + const PETNAME_ENTRY_WITH_NAME_1 = createPetnameEntry( + ADDRESS_LOWERCASE_MOCK, + NAME1_MOCK, + ); + bridge.getSourceEntries.mockReturnValue([PETNAME_ENTRY_WITH_NAME_1]); + + const petnamesListener = messenger.subscribe.mock + .calls[0][1] as () => void; + petnamesListener(); + + expect(bridge.updateSourceEntry).toHaveBeenCalledTimes(1); + expect(bridge.updateSourceEntry).toHaveBeenCalledWith( + ChangeType.DELETED, + PETNAME_ENTRY_WITH_NAME_1, + ); + }); + + describe('shouldSyncPetname', () => { + it('masks certain Petname entries from being added to the Source', () => { + const nameController = createNameControllerMock({ + ...EMPTY_NAME_STATE, + names: { + [NameType.ETHEREUM_ADDRESS]: { + [ADDRESS_LOWERCASE_MOCK]: { + [CHAIN_ID_MOCK]: { + origin: NON_PARTICIPANT_ORIGIN_MOCK, + name: NAME1_MOCK, + sourceId: null, + proposedNames: {}, + }, + [FALLBACK_VARIATION]: { + origin: PARTICIPANT_ORIGIN_MOCK, + name: NAME2_MOCK, + sourceId: null, + proposedNames: {}, + }, + }, + }, + }, + }); + const bridge = new TestPetnamesBridge({ + isTwoWay: true, + nameController, + messenger, + }); + bridge.shouldSyncPetname.mockImplementation( + (entry: PetnameEntry) => entry.origin === PARTICIPANT_ORIGIN_MOCK, + ); + bridge.init(); + + bridge.getSourceEntries.mockReturnValue(NO_SOURCE_ENTRIES); + + bridge.getSourceEntries.mockReturnValue(NO_SOURCE_ENTRIES); + + const petnamesListener = messenger.subscribe.mock + .calls[0][1] as () => void; + petnamesListener(); + + // Only the entry with the PARTICIPANT_ORIGIN_MOCK should be added. + expect(bridge.updateSourceEntry).toHaveBeenCalledTimes(1); + expect(bridge.updateSourceEntry).toHaveBeenCalledWith( + ChangeType.ADDED, + { + value: ADDRESS_LOWERCASE_MOCK, + name: NAME2_MOCK, + type: NameType.ETHEREUM_ADDRESS, + variation: FALLBACK_VARIATION, + origin: PARTICIPANT_ORIGIN_MOCK, + }, + ); + }); + }); + }); +}); diff --git a/app/scripts/lib/AbstractPetnamesBridge.ts b/app/scripts/lib/AbstractPetnamesBridge.ts new file mode 100644 index 000000000000..3528a02487d6 --- /dev/null +++ b/app/scripts/lib/AbstractPetnamesBridge.ts @@ -0,0 +1,290 @@ +import { + NameController, + NameStateChange, + NameType, + SetNameRequest, +} from '@metamask/name-controller'; +import { RestrictedControllerMessenger } from '@metamask/base-controller'; + +// Use the same type for both the source entries and the argument to NameController::setName. +export type PetnameEntry = SetNameRequest & { + // Name cannot be null in a PetnameEntry, as opposed to in a SetNameRequest, + // where a null name indicates deletion. + name: string; +}; + +// The type of change that occurred. +export enum ChangeType { + ADDED = 'ADDED', + UPDATED = 'UPDATED', + DELETED = 'DELETED', +} + +enum SyncDirection { + SOURCE_TO_PETNAMES = 'Source->Petnames', + PETNAMES_TO_SOURCE = 'Petnames->Source', +} + +// A list of changes, grouped by type. +type ChangeList = Record; + +type AllowedEvents = NameStateChange; + +export type PetnamesBridgeMessenger = RestrictedControllerMessenger< + 'PetnamesBridge', + never, + AllowedEvents, + never, + AllowedEvents['type'] +>; + +/** + * Get a string key for the given entry. + * + * @param entry + * @param entry.type + * @param entry.variation + * @param entry.value + */ +function getKey({ type, variation, value }: PetnameEntry): string { + const normalizedValue = + type === NameType.ETHEREUM_ADDRESS ? value.toLowerCase() : value; + return `${type}/${variation}/${normalizedValue}`; +} + +/** + * Abstract class representing a bridge between petnames and a data source. + * Provides methods for synchronizing petnames with the data source and handling changes. + */ +export abstract class AbstractPetnamesBridge { + #isTwoWay: boolean; + + #nameController: NameController; + + #synchronizingDirection: SyncDirection | null = null; + + #messenger: PetnamesBridgeMessenger; + + /** + * @param options + * @param options.isTwoWay - Indicates whether the bridge is two-way or not. One-way bridges are Source->Petnames only. + * @param options.nameController + * @param options.messenger + */ + constructor({ + isTwoWay, + nameController, + messenger, + }: { + isTwoWay: boolean; + nameController: NameController; + messenger: PetnamesBridgeMessenger; + }) { + this.#isTwoWay = isTwoWay; + this.#nameController = nameController; + this.#messenger = messenger; + } + + // Initializes listeners + init(): void { + if (this.#isTwoWay) { + this.#messenger.subscribe('NameController:stateChange', () => + this.#synchronize(SyncDirection.PETNAMES_TO_SOURCE), + ); + } + this.onSourceChange(() => + this.#synchronize(SyncDirection.SOURCE_TO_PETNAMES), + ); + } + + /** + * Adds a listener for source change events. + * + * @param listener - The listener function to be called when a source change event occurs. + */ + protected abstract onSourceChange(listener: () => void): void; + + /** + * Retrieves the source entries. + * + * @returns An array of PetnameEntry objects representing the source entries. + */ + protected abstract getSourceEntries(): PetnameEntry[]; + + /** + * Update the Source with the given entry. To be overridden by two-way subclasses. + * + * @param _type + * @param _entry + */ + protected updateSourceEntry(_type: ChangeType, _entry: PetnameEntry): void { + throw new Error('updateSourceEntry must be overridden for two-way bridges'); + } + + /** + * This predicate describes a subset of Petnames state that is relevant + * to the bridge. + * + * By default, the shouldSyncPetname method returns true for all Petnames, + * meaning every PetnameEntry in the NameController state is considered for + * synchronization. This would result in Petnames state being a mirror of + * the source entries or vice versa after synchronization. + * + * If you override this method to return false for some Petnames, those + * entries are effectively 'masked' or excluded from the synchronization + * process. This has a couple of implications: + * + * Source->Petnames direction: Masked Petname entries will not be deleted. + * If shouldSyncPetname returns false for some target Petname,that entry + * will not be deleted from Petnames state during synchronization. + * + * Petnames->Source direction: Masked Petname entries will not be added to + * Source. If shouldSyncPetname returns false for some Petname, that entry + * will not be added to the Source list during synchronization. + * + * @param _targetEntry - The entry from Petname state to check for membership. + * @returns true iff the target Petname entry should participate in synchronization. + */ + protected shouldSyncPetname(_targetEntry: PetnameEntry): boolean { + // All petname entries are sync participants by default. + return true; + } + + /** + * Synchronizes Petnames with the Source or vice versa, depending on the direction. + * + * @param direction - The direction to synchronize in. + */ + #synchronize(direction: SyncDirection): void { + if (this.#synchronizingDirection === direction) { + throw new Error( + `Attempted to synchronize recursively in same direction: ${direction}`, + ); + } + if (this.#synchronizingDirection !== null) { + return; // Ignore calls while updating in the opposite direction + } + + this.#synchronizingDirection = direction; + + const [newEntries, prevEntries] = + direction === 'Source->Petnames' + ? [this.getSourceEntries(), this.#getPetnameEntries()] + : [this.#getPetnameEntries(), this.getSourceEntries()]; + + const changeList = this.#computeChangeList(prevEntries, newEntries); + this.#applyChangeList(changeList); + + this.#synchronizingDirection = null; + } + + /** + * Extract PetnameEntry objects from the name controller state. + */ + #getPetnameEntries(): PetnameEntry[] { + const { names } = this.#nameController.state; + const entries: PetnameEntry[] = []; + for (const type of Object.values(NameType)) { + for (const value of Object.keys(names[type])) { + for (const variation of Object.keys(names[type][value])) { + const { name, sourceId, origin } = names[type][value][variation]; + if (!name) { + continue; + } + const entry = { + value, + type, + name, + variation, + sourceId: sourceId ?? undefined, + origin: origin ?? undefined, + }; + if (this.shouldSyncPetname(entry)) { + entries.push(entry); + } + } + } + } + return entries; + } + + /** + * Updates Petnames with the given entry. + * + * @param type - The type of change that occurred. + * @param entry - The entry to update the name controller with. + */ + #updatePetnameEntry(type: ChangeType, entry: PetnameEntry): void { + if (type === ChangeType.DELETED) { + delete entry.sourceId; + delete entry.origin; + this.#nameController.setName({ + ...entry, + name: null, + }); + } else { + // ADDED or UPDATED + this.#nameController.setName(entry); + } + } + + /** + * Computes the list of changes between the previous and new entries. + * + * @param prevEntries - The previous entries. + * @param newEntries - The new entries. + * @returns A ChangeList object representing the changes that occurred between prevEntries and newEntries. + */ + #computeChangeList( + prevEntries: PetnameEntry[], + newEntries: PetnameEntry[], + ): ChangeList { + const added: PetnameEntry[] = []; + const updated: PetnameEntry[] = []; + const deleted: PetnameEntry[] = []; + + const prevEntriesMap = new Map(prevEntries.map((e) => [getKey(e), e])); + const newEntriesMap = new Map(newEntries.map((e) => [getKey(e), e])); + + newEntriesMap.forEach((newEntry, newKey) => { + const oldEntry = prevEntriesMap.get(newKey); + if (oldEntry) { + if (newEntry.name !== oldEntry.name) { + updated.push(newEntry); + } + } else { + added.push(newEntry); + } + }); + + prevEntriesMap.forEach((oldEntry, oldKey) => { + if (!newEntriesMap.has(oldKey)) { + deleted.push(oldEntry); + } + }); + + return { + [ChangeType.ADDED]: added, + [ChangeType.UPDATED]: updated, + [ChangeType.DELETED]: deleted, + }; + } + + /** + * Applies the given change list to either the Petnames or Source, depending on the current synchrnoization direction. + * + * @param changeList + */ + #applyChangeList(changeList: ChangeList): void { + const applyChange = + this.#synchronizingDirection === SyncDirection.SOURCE_TO_PETNAMES + ? this.#updatePetnameEntry.bind(this) + : this.updateSourceEntry.bind(this); + + for (const type of Object.values(ChangeType)) { + for (const entry of changeList[type]) { + applyChange(type, entry); + } + } + } +} diff --git a/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts b/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts new file mode 100644 index 000000000000..49be9d87cdaa --- /dev/null +++ b/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts @@ -0,0 +1,218 @@ +import { + FALLBACK_VARIATION, + NameController, + NameControllerState, + NameType, + NameOrigin, +} from '@metamask/name-controller'; +import { + PreferencesController, + PreferencesControllerState, +} from '../controllers/preferences'; +import { AccountIdentitiesPetnamesBridge } from './AccountIdentitiesPetnamesBridge'; +import { PetnameEntry } from './AbstractPetnamesBridge'; + +const ADDRESS_MOCK = '0xabc'; +const NAME_MOCK = 'name1'; + +/** + * Creates a PetnameEntry with the given name and address. + * + * @param address + * @param name + */ +function createAccountIdentityPetnameEntry( + address: string, + name: string, +): PetnameEntry { + return { + value: address, + name, + type: NameType.ETHEREUM_ADDRESS, + sourceId: undefined, + variation: FALLBACK_VARIATION, + origin: NameOrigin.ACCOUNT_IDENTITY, + }; +} + +const EMPTY_NAME_STATE: NameControllerState = { + names: { + [NameType.ETHEREUM_ADDRESS]: {}, + }, + nameSources: {}, +}; + +/** + * Creates NameControllerState containing a single Petname with the given name and address. + * This is used to simulate a NameController state where a Petname has been set + * with a call to NameController.setName(createPetnameEntry(name) as SetNameRequest). + * + * @param address + * @param name + * @param sourceId + * @param origin + */ +function createNameStateWithPetname( + address: string, + name: string, + sourceId: string | null = null, + origin: NameOrigin | null = null, +): NameControllerState { + return { + ...EMPTY_NAME_STATE, + names: { + [NameType.ETHEREUM_ADDRESS]: { + [address]: { + [FALLBACK_VARIATION]: { + name, + proposedNames: {}, + sourceId, + origin, + }, + }, + }, + }, + }; +} + +const EMPTY_PREFERENCES_STATE = { + identities: {}, +} as PreferencesControllerState; + +/** + * Creates PreferencesControllerState containing a single identity with the given name and address. + * + * @param address + * @param name + */ +function createPreferencesStateWithIdentity( + address: string, + name: string, +): PreferencesControllerState { + return { + ...EMPTY_PREFERENCES_STATE, + identities: { + [address]: { + address, + name, + }, + }, + }; +} + +function createPreferencesControllerMock( + initialState: PreferencesControllerState, +): jest.Mocked & { + store: jest.Mocked; + updateMockStateAndTriggerListener(newState: PreferencesControllerState): void; +} { + let state = initialState; + return { + store: { + getState: jest.fn(() => state), + subscribe: jest.fn(), + }, + updateMockStateAndTriggerListener(newState) { + state = newState; + this.store.subscribe.mock.calls[0][0](state); + }, + }; +} + +function createNameControllerMock( + state: NameControllerState, +): jest.Mocked { + return { + state, + setName: jest.fn(), + } as any; +} + +describe('AccountIdentitiesPetnamesBridge', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('adds petnames entry when account id entry added', () => { + const preferencesController = createPreferencesControllerMock( + EMPTY_PREFERENCES_STATE, + ); + const nameController = createNameControllerMock(EMPTY_NAME_STATE); + const bridge = new AccountIdentitiesPetnamesBridge({ + preferencesController, + nameController, + messenger: {} as any, + }); + bridge.init(); + + preferencesController.updateMockStateAndTriggerListener( + createPreferencesStateWithIdentity(ADDRESS_MOCK, NAME_MOCK), + ); + + expect(nameController.setName).toHaveBeenCalledTimes(1); + expect(nameController.setName).toHaveBeenCalledWith( + createAccountIdentityPetnameEntry(ADDRESS_MOCK, NAME_MOCK), + ); + }); + + it('updates entry when account id is updated', () => { + const preferencesController = createPreferencesControllerMock( + createPreferencesStateWithIdentity(ADDRESS_MOCK, NAME_MOCK), + ); + const nameController = createNameControllerMock( + createNameStateWithPetname(ADDRESS_MOCK, NAME_MOCK), + ); + const bridge = new AccountIdentitiesPetnamesBridge({ + preferencesController, + nameController, + messenger: {} as any, + }); + bridge.init(); + + const UPDATED_NAME = 'updatedName'; + + preferencesController.updateMockStateAndTriggerListener( + createPreferencesStateWithIdentity(ADDRESS_MOCK, UPDATED_NAME), + ); + + expect(nameController.setName).toHaveBeenCalledTimes(1); + expect(nameController.setName).toHaveBeenCalledWith( + createAccountIdentityPetnameEntry(ADDRESS_MOCK, UPDATED_NAME), + ); + }); + + describe('shouldSyncPetname', () => { + it.each([ + { + origin: NameOrigin.ACCOUNT_IDENTITY, + expectedReturn: true, + }, + { + origin: NameOrigin.API, + expectedReturn: false, + }, + ])( + 'returns $expectedReturn if origin is $origin', + ({ origin, expectedReturn }) => { + class TestBridge extends AccountIdentitiesPetnamesBridge { + public shouldSyncPetname(entry: PetnameEntry): boolean { + return super.shouldSyncPetname(entry); + } + } + const preferencesController = createPreferencesControllerMock( + EMPTY_PREFERENCES_STATE, + ); + const nameController = createNameControllerMock(EMPTY_NAME_STATE); + const bridge = new TestBridge({ + preferencesController, + nameController, + messenger: {} as any, + }); + bridge.init(); + expect(bridge.shouldSyncPetname({ origin } as PetnameEntry)).toBe( + expectedReturn, + ); + }, + ); + }); +}); diff --git a/app/scripts/lib/AccountIdentitiesPetnamesBridge.ts b/app/scripts/lib/AccountIdentitiesPetnamesBridge.ts new file mode 100644 index 000000000000..77a0e32cd4af --- /dev/null +++ b/app/scripts/lib/AccountIdentitiesPetnamesBridge.ts @@ -0,0 +1,64 @@ +import { + FALLBACK_VARIATION, + NameController, + NameType, + NameOrigin, +} from '@metamask/name-controller'; +import { + PreferencesController, + AccountIdentityEntry, +} from '../controllers/preferences'; +import { + PetnameEntry, + AbstractPetnamesBridge, + PetnamesBridgeMessenger, +} from './AbstractPetnamesBridge'; + +/** + * A petnames bridge that uses the account identities from the preferences controller as the source. + */ +export class AccountIdentitiesPetnamesBridge extends AbstractPetnamesBridge { + #preferencesController: PreferencesController; + + constructor({ + preferencesController, + nameController, + messenger, + }: { + preferencesController: PreferencesController; + nameController: NameController; + messenger: PetnamesBridgeMessenger; + }) { + super({ isTwoWay: false, nameController, messenger }); + this.#preferencesController = preferencesController; + } + + /** + * @override + */ + protected getSourceEntries(): PetnameEntry[] { + const { identities } = this.#preferencesController.store.getState(); + return Object.values(identities).map((identity: AccountIdentityEntry) => ({ + value: identity.address, + type: NameType.ETHEREUM_ADDRESS, + name: identity.name, + sourceId: undefined, + variation: FALLBACK_VARIATION, + origin: NameOrigin.ACCOUNT_IDENTITY, + })); + } + + /** + * @override + */ + protected onSourceChange(listener: () => void): void { + this.#preferencesController.store.subscribe(listener); + } + + /** + * @override + */ + protected shouldSyncPetname(targetEntry: PetnameEntry): boolean { + return targetEntry.origin === NameOrigin.ACCOUNT_IDENTITY; + } +} diff --git a/app/scripts/lib/AddressBookPetnamesBridge.test.ts b/app/scripts/lib/AddressBookPetnamesBridge.test.ts index 3d7ab4ee58af..c98e6a5e309e 100644 --- a/app/scripts/lib/AddressBookPetnamesBridge.test.ts +++ b/app/scripts/lib/AddressBookPetnamesBridge.test.ts @@ -1,9 +1,16 @@ -import { NameController, NameType } from '@metamask/name-controller'; -import { AddressBookController } from '@metamask/address-book-controller'; import { - AddressBookPetnamesBridge, - AddressBookPetnamesBridgeMessenger, -} from './AddressBookPetnamesBridge'; + NameController, + NameControllerState, + NameOrigin, + NameType, + SetNameRequest, +} from '@metamask/name-controller'; +import { + AddressBookController, + AddressBookState, +} from '@metamask/address-book-controller'; +import { AddressBookPetnamesBridge } from './AddressBookPetnamesBridge'; +import { PetnamesBridgeMessenger } from './AbstractPetnamesBridge'; const ADDRESS_MOCK = '0xabc'; const NAME_MOCK = 'testName'; @@ -11,112 +18,160 @@ const NAME_2_MOCK = 'testName2'; const CHAIN_ID_MOCK = '0x1'; function createAddressBookControllerMock( - state: any = {}, -): jest.Mocked { + state: AddressBookState, +): jest.Mocked & { + // Override the definition of state. Otherwise state is readonly. + state: AddressBookState; +} { return { - state: { - addressBook: state, - }, + state, set: jest.fn(), delete: jest.fn(), subscribe: jest.fn(), - } as any; + } as unknown as jest.Mocked; } function createNameControllerMock( - state: any = {}, + state: NameControllerState, ): jest.Mocked { return { - state: { - names: { - ethereumAddress: state, - }, - }, + state, setName: jest.fn(), } as any; } -function createMessengerMock(): jest.Mocked { +function createMessengerMock(): jest.Mocked { return { subscribe: jest.fn(), } as any; } +const EMPTY_NAME_STATE: NameControllerState = { + names: { + [NameType.ETHEREUM_ADDRESS]: {}, + }, + nameSources: {}, +}; + +/** + * Creates NameControllerState containing a single Petname with the given name and address. + * + * @param address + * @param name + */ +function createNameState(address: string, name: string): NameControllerState { + return { + ...EMPTY_NAME_STATE, + names: { + [NameType.ETHEREUM_ADDRESS]: { + [address]: { + [CHAIN_ID_MOCK]: { + name, + sourceId: null, + proposedNames: {}, + origin: NameOrigin.ADDRESS_BOOK, + }, + }, + }, + }, + }; +} + +const EMPTY_ADDRESS_BOOK_STATE: AddressBookState = { + addressBook: {}, +}; + +/** + * Creates AddressBookState containing a single entry with the given name, address and isEns value. + * + * @param address + * @param name + * @param isEns + */ +function createAddressBookState( + address: string, + name: string, + isEns: boolean, +): AddressBookState { + return { + ...EMPTY_ADDRESS_BOOK_STATE, + addressBook: { + [CHAIN_ID_MOCK]: { + [address]: { + address, + name, + isEns, + chainId: CHAIN_ID_MOCK, + memo: '', + }, + }, + }, + }; +} + describe('AddressBookPetnamesBridge', () => { - let addressBookControllerDefault; - let nameControllerDefault; - let messengerDefault; - let options: any; + let messenger: jest.Mocked; beforeEach(() => { jest.resetAllMocks(); - addressBookControllerDefault = createAddressBookControllerMock(); - nameControllerDefault = createNameControllerMock(); - messengerDefault = createMessengerMock(); - - options = { - addressBookController: addressBookControllerDefault, - nameController: nameControllerDefault, - messenger: messengerDefault, - }; + messenger = createMessengerMock(); }); describe('NameController', () => { it('adds entry when address book entry added', () => { - new AddressBookPetnamesBridge(options).init(); + const addressBookController = createAddressBookControllerMock( + EMPTY_ADDRESS_BOOK_STATE, + ); + const nameController = createNameControllerMock(EMPTY_NAME_STATE); + new AddressBookPetnamesBridge({ + addressBookController, + nameController, + messenger, + }).init(); - addressBookControllerDefault.subscribe.mock.calls[0][0]({ - addressBook: { - [CHAIN_ID_MOCK]: { - [ADDRESS_MOCK]: { - address: ADDRESS_MOCK, - name: NAME_MOCK, - chainId: CHAIN_ID_MOCK, - isEns: true, - } as any, - }, - }, - }); + addressBookController.state = createAddressBookState( + ADDRESS_MOCK, + NAME_MOCK, + true, + ); + const listener = addressBookController.subscribe.mock + .calls[0][0] as () => void; + listener(); - expect(nameControllerDefault.setName).toHaveBeenCalledTimes(1); - expect(nameControllerDefault.setName).toHaveBeenCalledWith({ + expect(nameController.setName).toHaveBeenCalledTimes(1); + expect(nameController.setName).toHaveBeenCalledWith({ value: ADDRESS_MOCK, type: NameType.ETHEREUM_ADDRESS, name: NAME_MOCK, sourceId: 'ens', variation: CHAIN_ID_MOCK, - }); + origin: NameOrigin.ADDRESS_BOOK, + } as SetNameRequest); }); it('updates entry when address book entry is updated', () => { - const nameController = createNameControllerMock({ - [ADDRESS_MOCK]: { - [CHAIN_ID_MOCK]: { - name: NAME_MOCK, - sourceId: null, - proposedNames: {}, - }, - }, - }); + const addressBookController = createAddressBookControllerMock( + createAddressBookState(ADDRESS_MOCK, NAME_MOCK, true), + ); + const nameController = createNameControllerMock( + createNameState(ADDRESS_MOCK, NAME_MOCK), + ); new AddressBookPetnamesBridge({ - ...options, + addressBookController, nameController, + messenger, }).init(); - addressBookControllerDefault.subscribe.mock.calls[0][0]({ - addressBook: { - [CHAIN_ID_MOCK]: { - [ADDRESS_MOCK]: { - address: ADDRESS_MOCK, - name: NAME_2_MOCK, - chainId: CHAIN_ID_MOCK, - isEns: false, - } as any, - }, - }, - }); + addressBookController.state = createAddressBookState( + ADDRESS_MOCK, + NAME_2_MOCK, + false, + ); + const listener = addressBookController.subscribe.mock + .calls[0][0] as () => void; + listener(); expect(nameController.setName).toHaveBeenCalledTimes(1); expect(nameController.setName).toHaveBeenCalledWith({ @@ -125,28 +180,28 @@ describe('AddressBookPetnamesBridge', () => { name: NAME_2_MOCK, sourceId: undefined, variation: CHAIN_ID_MOCK, - }); + origin: NameOrigin.ADDRESS_BOOK, + } as SetNameRequest); }); it('deletes entry when address book entry is deleted', () => { - const nameController = createNameControllerMock({ - [ADDRESS_MOCK]: { - [CHAIN_ID_MOCK]: { - name: NAME_MOCK, - sourceId: null, - proposedNames: {}, - } as any, - }, - }); - + const addressBookController = createAddressBookControllerMock( + createAddressBookState(ADDRESS_MOCK, NAME_MOCK, true), + ); + const nameController = createNameControllerMock( + createNameState(ADDRESS_MOCK, NAME_MOCK), + ); new AddressBookPetnamesBridge({ - ...options, + addressBookController, nameController, + messenger, }).init(); - addressBookControllerDefault.subscribe.mock.calls[0][0]({ - addressBook: {}, - }); + addressBookController.state = EMPTY_ADDRESS_BOOK_STATE; + + const listener = addressBookController.subscribe.mock + .calls[0][0] as () => void; + listener(); expect(nameController.setName).toHaveBeenCalledTimes(1); expect(nameController.setName).toHaveBeenCalledWith({ @@ -155,33 +210,29 @@ describe('AddressBookPetnamesBridge', () => { name: null, sourceId: undefined, variation: CHAIN_ID_MOCK, - }); + } as SetNameRequest); }); }); describe('AddressBookController', () => { it('adds entry when petname added', () => { - new AddressBookPetnamesBridge(options).init(); - - messengerDefault.subscribe.mock.calls[0][1]( - { - names: { - [NameType.ETHEREUM_ADDRESS]: { - [ADDRESS_MOCK]: { - [CHAIN_ID_MOCK]: { - name: NAME_MOCK, - sourceId: null, - proposedNames: {}, - }, - }, - }, - }, - }, - undefined, + const addressBookController = createAddressBookControllerMock( + EMPTY_ADDRESS_BOOK_STATE, ); + const nameController = createNameControllerMock(EMPTY_NAME_STATE); + new AddressBookPetnamesBridge({ + addressBookController, + nameController, + messenger, + }).init(); - expect(addressBookControllerDefault.set).toHaveBeenCalledTimes(1); - expect(addressBookControllerDefault.set).toHaveBeenCalledWith( + nameController.state = createNameState(ADDRESS_MOCK, NAME_MOCK); + + const listener = messenger.subscribe.mock.calls[0][1] as () => void; + listener(); + + expect(addressBookController.set).toHaveBeenCalledTimes(1); + expect(addressBookController.set).toHaveBeenCalledWith( ADDRESS_MOCK, NAME_MOCK, CHAIN_ID_MOCK, @@ -189,38 +240,22 @@ describe('AddressBookPetnamesBridge', () => { }); it('updates entry when petname updated', () => { - const addressBookController = createAddressBookControllerMock({ - [CHAIN_ID_MOCK]: { - [ADDRESS_MOCK]: { - address: ADDRESS_MOCK, - name: NAME_MOCK, - chainId: CHAIN_ID_MOCK, - isEns: false, - } as any, - }, - }); - + const addressBookController = createAddressBookControllerMock( + createAddressBookState(ADDRESS_MOCK, NAME_MOCK, false), + ); + const nameController = createNameControllerMock( + createNameState(ADDRESS_MOCK, NAME_MOCK), + ); new AddressBookPetnamesBridge({ - ...options, addressBookController, + nameController, + messenger, }).init(); - messengerDefault.subscribe.mock.calls[0][1]( - { - names: { - [NameType.ETHEREUM_ADDRESS]: { - [ADDRESS_MOCK]: { - [CHAIN_ID_MOCK]: { - name: NAME_2_MOCK, - sourceId: null, - proposedNames: {}, - }, - }, - }, - }, - }, - undefined, - ); + nameController.state = createNameState(ADDRESS_MOCK, NAME_2_MOCK); + + const listener = messenger.subscribe.mock.calls[0][1] as () => void; + listener(); expect(addressBookController.set).toHaveBeenCalledTimes(1); expect(addressBookController.set).toHaveBeenCalledWith( @@ -231,30 +266,22 @@ describe('AddressBookPetnamesBridge', () => { }); it('deletes entry when petname deleted', () => { - const addressBookController = createAddressBookControllerMock({ - [CHAIN_ID_MOCK]: { - [ADDRESS_MOCK]: { - address: ADDRESS_MOCK, - name: NAME_MOCK, - chainId: CHAIN_ID_MOCK, - isEns: false, - } as any, - }, - }); - + const addressBookController = createAddressBookControllerMock( + createAddressBookState(ADDRESS_MOCK, NAME_MOCK, false), + ); + const nameController = createNameControllerMock( + createNameState(ADDRESS_MOCK, NAME_MOCK), + ); new AddressBookPetnamesBridge({ - ...options, addressBookController, + nameController, + messenger, }).init(); - messengerDefault.subscribe.mock.calls[0][1]( - { - names: { - [NameType.ETHEREUM_ADDRESS]: {}, - }, - }, - undefined, - ); + nameController.state = EMPTY_NAME_STATE; + + const listener = messenger.subscribe.mock.calls[0][1] as () => void; + listener(); expect(addressBookController.delete).toHaveBeenCalledTimes(1); expect(addressBookController.delete).toHaveBeenCalledWith( diff --git a/app/scripts/lib/AddressBookPetnamesBridge.ts b/app/scripts/lib/AddressBookPetnamesBridge.ts index 94f2edb18fc3..1ec6aa3aea08 100644 --- a/app/scripts/lib/AddressBookPetnamesBridge.ts +++ b/app/scripts/lib/AddressBookPetnamesBridge.ts @@ -1,47 +1,19 @@ -import { - AddressBookController, - AddressBookState, -} from '@metamask/address-book-controller'; -import { RestrictedControllerMessenger } from '@metamask/base-controller'; +import { AddressBookController } from '@metamask/address-book-controller'; import { NameController, - NameControllerState, - NameStateChange, NameType, + NameOrigin, } from '@metamask/name-controller'; -import { cloneDeep } from 'lodash'; -import log from 'loglevel'; - -type Entry = { - address: string; - name: string; - chainId: string; - isEns: boolean; -}; - -type AllowedEvents = NameStateChange; - -export type AddressBookPetnamesBridgeMessenger = RestrictedControllerMessenger< - 'AddressBookPetnamesBridge', - never, - AllowedEvents, - never, - AllowedEvents['type'] ->; +import { + AbstractPetnamesBridge, + PetnamesBridgeMessenger, + ChangeType, + PetnameEntry, +} from './AbstractPetnamesBridge'; -export class AddressBookPetnamesBridge { +export class AddressBookPetnamesBridge extends AbstractPetnamesBridge { #addressBookController: AddressBookController; - #addressBookState: AddressBookState; - - #nameController: NameController; - - #nameState: NameControllerState; - - #messenger: AddressBookPetnamesBridgeMessenger; - - #updating: boolean; - constructor({ addressBookController, nameController, @@ -49,199 +21,63 @@ export class AddressBookPetnamesBridge { }: { addressBookController: AddressBookController; nameController: NameController; - messenger: AddressBookPetnamesBridgeMessenger; + messenger: PetnamesBridgeMessenger; }) { - this.#addressBookController = addressBookController; - this.#addressBookState = addressBookController.state; - this.#nameController = nameController; - this.#nameState = nameController.state; - this.#messenger = messenger; - this.#updating = false; - } - - init() { - this.#addressBookController.subscribe((state) => { - try { - this.#onAddressBookStateChange(state); - } catch (error) { - log.debug( - 'Error synchronising address book update with petnames', - error, - ); - } - }); - - this.#messenger.subscribe('NameController:stateChange', (state) => { - try { - this.#onPetnameStateChange(state); - } catch (error) { - log.debug('Error synchronising petname update with petnames', error); - } - }); - } - - #onPetnameStateChange(newState: NameControllerState) { - if (this.#updating) { - return; - } - - this.#updating = true; - - const newEntries = this.#getPetnameEntries(newState); - const oldEntries = this.#getAddressBookEntries(this.#addressBookState); - - const { added, updated, deleted } = this.#groupEntries( - oldEntries, - newEntries, - ); - - for (const entry of [...added, ...updated]) { - this.#addressBookController.set( - entry.address, - entry.name, - entry.chainId as any, - ); - - log.debug('Updated address book following petname update', entry); - } - - for (const entry of deleted) { - this.#addressBookController.delete(entry.chainId as any, entry.address); - log.debug('Removed address book entry following petname removal', entry); - } - - this.#addressBookState = cloneDeep(this.#addressBookController.state); - this.#nameState = cloneDeep(newState); - this.#updating = false; - } - - #onAddressBookStateChange(newState: AddressBookState) { - if (this.#updating) { - return; - } - - this.#updating = true; - - const newEntries = this.#getAddressBookEntries(newState); - const oldEntries = this.#getPetnameEntries(this.#nameState); - - const { added, updated, deleted } = this.#groupEntries( - oldEntries, - newEntries, - ); - - for (const entry of [...added, ...updated]) { - this.#nameController.setName({ - value: entry.address, - type: NameType.ETHEREUM_ADDRESS, - name: entry.name, - sourceId: entry.isEns ? 'ens' : undefined, - variation: entry.chainId, - }); + super({ isTwoWay: true, nameController, messenger }); - log.debug('Updated petname following address book update', entry); - } - - for (const entry of deleted) { - this.#nameController.setName({ - value: entry.address, - type: NameType.ETHEREUM_ADDRESS, - name: null, - variation: entry.chainId, - }); - - log.debug('Removed petname following address book removal', entry); - } - - this.#addressBookState = cloneDeep(newState); - this.#nameState = cloneDeep(this.#nameController.state); - this.#updating = false; - } - - #groupEntries(oldEntries: Entry[], newEntries: Entry[]) { - const added = newEntries.filter( - (newEntry) => - !oldEntries.some( - (oldEntry) => - oldEntry.address === newEntry.address && - oldEntry.chainId === newEntry.chainId, - ), - ); - - const updated = newEntries.filter((newEntry) => - oldEntries.some( - (oldEntry) => - oldEntry.address === newEntry.address && - oldEntry.chainId === newEntry.chainId && - oldEntry.name !== newEntry.name, - ), - ); - - const deleted = oldEntries.filter( - (oldEntry) => - !newEntries.some( - (newEntry) => - newEntry.address === oldEntry.address && - newEntry.chainId === oldEntry.chainId, - ), - ); - - return { added, updated, deleted }; - } - - #getPetnameEntries(state: NameControllerState): Entry[] { - const entries: Entry[] = []; - - for (const address of Object.keys(state.names.ethereumAddress)) { - const addressEntries = state.names.ethereumAddress[address]; - - for (const chainId of Object.keys(addressEntries)) { - const entry = state.names.ethereumAddress[address][chainId as any]; - const normalizedAddress = address.toLowerCase(); - const normalizedChainId = chainId.toLowerCase(); - const { sourceId, name } = entry; - - if (!name?.length) { - continue; - } - - entries.push({ - address: normalizedAddress, - name, - chainId: normalizedChainId, - isEns: sourceId === 'ens', - }); - } - } - - return entries; + this.#addressBookController = addressBookController; } - #getAddressBookEntries(state: AddressBookState): Entry[] { - const entries: Entry[] = []; - + /** + * @override + */ + protected getSourceEntries(): PetnameEntry[] { + const entries: PetnameEntry[] = []; + const { state } = this.#addressBookController; for (const chainId of Object.keys(state.addressBook)) { const chainEntries = state.addressBook[chainId as any]; for (const address of Object.keys(chainEntries)) { const entry = state.addressBook[chainId as any][address]; - const normalizedAddress = address.toLowerCase(); const normalizedChainId = chainId.toLowerCase(); const { name, isEns } = entry; - if (!name?.length || !normalizedAddress?.length) { + if (!name?.length || !address?.length) { continue; } entries.push({ - address: normalizedAddress, + value: address, name, - chainId: normalizedChainId, - isEns, + variation: normalizedChainId, + type: NameType.ETHEREUM_ADDRESS, + sourceId: isEns ? 'ens' : undefined, + origin: NameOrigin.ADDRESS_BOOK, }); } } - return entries; } + + /** + * @override + */ + protected updateSourceEntry(type: ChangeType, entry: PetnameEntry): void { + if (type === ChangeType.DELETED) { + this.#addressBookController.delete(entry.variation as any, entry.value); + } else { + this.#addressBookController.set( + entry.value, + entry.name as any, + entry.variation as any, + ); + } + } + + /** + * @override + */ + onSourceChange(listener: () => void): void { + this.#addressBookController.subscribe(listener); + } } diff --git a/app/scripts/lib/backup.test.js b/app/scripts/lib/backup.test.js index 43be1dcb0db8..ceab30f28188 100644 --- a/app/scripts/lib/backup.test.js +++ b/app/scripts/lib/backup.test.js @@ -183,7 +183,14 @@ const jsonData = JSON.stringify({ lastSelected: 1693289751176, }, options: {}, - methods: [...Object.values(EthMethod)], + methods: [ + EthMethod.PersonalSign, + EthMethod.Sign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV1, + EthMethod.SignTypedDataV3, + EthMethod.SignTypedDataV4, + ], type: EthAccountType.Eoa, }, }, diff --git a/app/scripts/lib/createStreamSink.js b/app/scripts/lib/createStreamSink.js index 84ad69db1813..a6d4895cd197 100644 --- a/app/scripts/lib/createStreamSink.js +++ b/app/scripts/lib/createStreamSink.js @@ -1,7 +1,7 @@ -import { Writable as WritableStream } from 'readable-stream'; +import { Writable } from 'readable-stream'; import promiseToCallback from 'promise-to-callback'; -class AsyncWritableStream extends WritableStream { +class AsyncWritableStream extends Writable { constructor(asyncWriteFn, _opts) { const opts = { objectMode: true, ..._opts }; super(opts); diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index 5dc027bf594b..ef7bed81b2d0 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -7,7 +7,7 @@ import { BlockaidResultType, } from '../../../../shared/constants/security-provider'; import { CHAIN_IDS } from '../../../../shared/constants/network'; -import PreferencesController from '../../controllers/preferences'; +import { PreferencesController } from '../../controllers/preferences'; const { sentry } = global as any; @@ -22,6 +22,16 @@ const ConfirmationMethods = Object.freeze([ 'personal_sign', ]); +export const SUPPORTED_CHAIN_IDS: string[] = [ + CHAIN_IDS.MAINNET, + CHAIN_IDS.BSC, + CHAIN_IDS.POLYGON, + CHAIN_IDS.ARBITRUM, + CHAIN_IDS.OPTIMISM, + CHAIN_IDS.AVALANCHE, + CHAIN_IDS.LINEA_MAINNET, +]; + /** * Middleware function that handles JSON RPC requests. * This function will be called for every JSON RPC request. @@ -49,7 +59,7 @@ export function createPPOMMiddleware( if ( securityAlertsEnabled && ConfirmationMethods.includes(req.method) && - chainId === CHAIN_IDS.MAINNET + SUPPORTED_CHAIN_IDS.includes(chainId) ) { // eslint-disable-next-line require-atomic-updates req.securityAlertResponse = await ppomController.usePPOM( diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index d543d34ea947..c0c70d761c4e 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -136,6 +136,11 @@ export const SENTRY_BACKGROUND_STATE = { segmentApiCalls: false, traits: false, }, + NameController: { + names: false, + nameSources: false, + useExternalNameSources: false, + }, NetworkController: { networkConfigurations: false, networksMetadata: true, @@ -201,6 +206,7 @@ export const SENTRY_BACKGROUND_STATE = { showFiatInTestnets: true, showTestNetworks: true, useNativeCurrencyAsPrimaryCurrency: true, + petnamesEnabled: true, }, selectedAddress: false, snapRegistryList: false, diff --git a/app/scripts/lib/snap-keyring/snap-keyring.ts b/app/scripts/lib/snap-keyring/snap-keyring.ts index 755bcac8d50e..885df9f1cbef 100644 --- a/app/scripts/lib/snap-keyring/snap-keyring.ts +++ b/app/scripts/lib/snap-keyring/snap-keyring.ts @@ -16,6 +16,10 @@ import { RestrictedControllerMessenger } from '@metamask/base-controller'; import { MaybeUpdateState, TestOrigin } from '@metamask/phishing-controller'; import { SnapId } from '@metamask/snaps-sdk'; import { GetSubjectMetadata } from '@metamask/permission-controller'; +import { + AccountsControllerGetAccountByAddressAction, + AccountsControllerSetSelectedAccountAction, +} from '@metamask/accounts-controller'; import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app'; import { t } from '../../translate'; import MetamaskController from '../../metamask-controller'; @@ -49,7 +53,9 @@ type SnapKeyringBuilderAllowActions = | MaybeUpdateState | TestOrigin | KeyringControllerGetAccountsAction - | GetSubjectMetadata; + | GetSubjectMetadata + | AccountsControllerSetSelectedAccountAction + | AccountsControllerGetAccountByAddressAction; type snapKeyringBuilderMessenger = RestrictedControllerMessenger< 'SnapKeyringBuilder', @@ -172,6 +178,19 @@ export const snapKeyringBuilder = ( await handleUserInput(confirmationResult); await persistKeyringHelper(); setSelectedAccountHelper(address); + const internalAccount = controllerMessenger.call( + 'AccountsController:getAccountByAddress', + address, + ); + if (!internalAccount) { + throw new Error( + `Internal account not found for address: ${address}`, + ); + } + controllerMessenger.call( + 'AccountsController:setSelectedAccount', + internalAccount.id, + ); await controllerMessenger.call('ApprovalController:showSuccess', { header: [snapAuthorshipHeader], title: t('snapAccountCreated') as string, diff --git a/app/scripts/lib/transaction/metrics.test.ts b/app/scripts/lib/transaction/metrics.test.ts index 55a3794c7734..6a0f4cc9bcf2 100644 --- a/app/scripts/lib/transaction/metrics.test.ts +++ b/app/scripts/lib/transaction/metrics.test.ts @@ -135,8 +135,6 @@ describe('Transaction metrics', () => { gas_edit_type: 'none', network: mockNetworkId, referrer: ORIGIN_METAMASK, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidReason.notApplicable, source: MetaMetricsTransactionEventSource.User, status: 'unapproved', token_standard: TokenStandard.none, diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index 1d76e70c46e0..a9c601ef4566 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -25,6 +25,7 @@ import { MetaMetricsEventCategory, MetaMetricsEventFragment, MetaMetricsEventName, + MetaMetricsEventUiCustomization, MetaMetricsPageObject, MetaMetricsReferrerObject, } from '../../../../shared/constants/metametrics'; @@ -35,11 +36,7 @@ import { TRANSACTION_ENVELOPE_TYPE_NAMES, } from '../../../../shared/lib/transactions-controller-utils'; ///: BEGIN:ONLY_INCLUDE_IF(blockaid) -import { - BlockaidReason, - BlockaidResultType, -} from '../../../../shared/constants/security-provider'; -import { getBlockaidMetricsParams } from '../../../../ui/helpers/utils/metrics'; +import { getBlockaidMetricsProps } from '../../../../ui/helpers/utils/metrics'; ///: END:ONLY_INCLUDE_IF import { getSnapAndHardwareInfoForMetrics, @@ -763,9 +760,6 @@ async function buildEventFragmentProperties({ finalApprovalAmount, contractMethodName, securityProviderResponse, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - securityAlertResponse, - ///: END:ONLY_INCLUDE_IF simulationFails, } = transactionMeta; const query = new EthQuery(transactionMetricsRequest.provider); @@ -928,37 +922,30 @@ async function buildEventFragmentProperties({ } } - let uiCustomizations; - let additionalBlockaidParams; + const uiCustomizations = []; + /** securityProviderResponse is used by the OpenSea <> Blockaid provider */ // eslint-disable-next-line no-lonely-if if (securityProviderResponse?.flagAsDangerous === 1) { - uiCustomizations = ['flagged_as_malicious']; + uiCustomizations.push(MetaMetricsEventUiCustomization.FlaggedAsMalicious); } else if (securityProviderResponse?.flagAsDangerous === 2) { - uiCustomizations = ['flagged_as_safety_unknown']; - } else { - uiCustomizations = null; + uiCustomizations.push( + MetaMetricsEventUiCustomization.FlaggedAsSafetyUnknown, + ); } ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - if (securityAlertResponse?.result_type === BlockaidResultType.Failed) { - uiCustomizations = ['security_alert_failed']; - } else { - additionalBlockaidParams = getBlockaidMetricsParams( - securityAlertResponse as any, - ); - uiCustomizations = additionalBlockaidParams?.ui_customizations ?? null; - } + const blockaidProperties: any = getBlockaidMetricsProps(transactionMeta); + if (blockaidProperties?.ui_customizations?.length > 0) { + uiCustomizations.push(...blockaidProperties.ui_customizations); + } ///: END:ONLY_INCLUDE_IF if (simulationFails) { - if (uiCustomizations === null) { - uiCustomizations = ['gas_estimation_failed']; - } else { - uiCustomizations.push('gas_estimation_failed'); - } + uiCustomizations.push(MetaMetricsEventUiCustomization.GasEstimationFailed); } + /** The transaction status property is not considered sensitive and is now included in the non-anonymous event */ let properties = { chain_id: chainId, @@ -969,6 +956,7 @@ async function buildEventFragmentProperties({ eip_1559_version: eip1559Version, gas_edit_type: 'none', gas_edit_attempted: 'none', + gas_estimation_failed: Boolean(simulationFails), account_type: await transactionMetricsRequest.getAccountType( transactionMetricsRequest.getSelectedAddress(), ), @@ -979,15 +967,11 @@ async function buildEventFragmentProperties({ token_standard: tokenStandard, transaction_type: transactionType, transaction_speed_up: type === TransactionType.retry, - ...additionalBlockaidParams, - ui_customizations: uiCustomizations?.length > 0 ? uiCustomizations : null, ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - security_alert_response: - securityAlertResponse?.result_type ?? BlockaidResultType.NotApplicable, - security_alert_reason: - securityAlertResponse?.reason ?? BlockaidReason.notApplicable, + ...blockaidProperties, ///: END:ONLY_INCLUDE_IF - gas_estimation_failed: Boolean(simulationFails), + // ui_customizations must come after ...blockaidProperties + ui_customizations: uiCustomizations.length > 0 ? uiCustomizations : null, } as Record; const snapAndHardwareInfo = await getSnapAndHardwareInfoForMetrics( diff --git a/app/scripts/lib/transaction/util.test.ts b/app/scripts/lib/transaction/util.test.ts index d28099611a6a..fbb8cb9b198a 100644 --- a/app/scripts/lib/transaction/util.test.ts +++ b/app/scripts/lib/transaction/util.test.ts @@ -7,6 +7,7 @@ import { } from '@metamask/transaction-controller'; import { UserOperationController } from '@metamask/user-operation-controller'; import { cloneDeep } from 'lodash'; +import { PPOMController } from '@metamask/ppom-validator'; import { AddDappTransactionRequest, AddTransactionOptions, @@ -62,6 +63,17 @@ function createUserOperationControllerMock() { } as unknown as jest.Mocked; } +///: BEGIN:ONLY_INCLUDE_IF(blockaid) +function createPPOMControllerMock() { + return { + usePPOM: jest.fn().mockResolvedValue({ + reason: 'testReason', + result_type: 'testResultType', + }), + } as unknown as jest.Mocked; +} +///: END:ONLY_INCLUDE_IF + async function flushPromises() { return new Promise((resolve) => setImmediate(resolve)); } @@ -71,6 +83,9 @@ describe('Transaction Utils', () => { let dappRequest: AddDappTransactionRequest; let transactionController: jest.Mocked; let userOperationController: jest.Mocked; + ///: BEGIN:ONLY_INCLUDE_IF(blockaid) + let ppomController: jest.Mocked; + ///: END:ONLY_INCLUDE_IF beforeEach(() => { jest.resetAllMocks(); @@ -78,6 +93,10 @@ describe('Transaction Utils', () => { request = cloneDeep(TRANSACTION_REQUEST_MOCK); transactionController = createTransactionControllerMock(); userOperationController = createUserOperationControllerMock(); + ///: BEGIN:ONLY_INCLUDE_IF(blockaid) + ppomController = createPPOMControllerMock(); + request.ppomController = ppomController; + ///: END:ONLY_INCLUDE_IF transactionController.addTransaction.mockResolvedValue({ result: Promise.resolve('testHash'), @@ -186,7 +205,7 @@ describe('Transaction Utils', () => { describe('if selected account is smart contract', () => { beforeEach(() => { - request.selectedAccount.type = 'eip155:eip4337'; + request.selectedAccount.type = 'eip155:erc4337'; }); it('adds user operation', async () => { @@ -344,6 +363,102 @@ describe('Transaction Utils', () => { ); }); }); + + describe('when blockaid is enabled', () => { + it('validates if blockaid is enabled and chain id is supported', async () => { + await addTransaction({ + ...request, + securityAlertsEnabled: true, + chainId: '0x1', + }); + + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledTimes(1); + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledWith(TRANSACTION_PARAMS_MOCK, { + ...TRANSACTION_OPTIONS_MOCK, + securityAlertResponse: { + reason: 'testReason', + result_type: 'testResultType', + }, + }); + + expect(request.ppomController.usePPOM).toHaveBeenCalledTimes(1); + expect(request.ppomController.usePPOM).toHaveBeenCalledWith( + expect.any(Function), + ); + expect(request.ppomController.usePPOM).toHaveReturnedWith( + Promise.resolve({ + reason: 'testReason', + result_type: 'testResultType', + }), + ); + }); + + it('does not validate if blockaid is enabled and chain id is not supported', async () => { + await addTransaction({ + ...request, + securityAlertsEnabled: true, + chainId: '0xF', + }); + + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledTimes(1); + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledWith( + TRANSACTION_PARAMS_MOCK, + TRANSACTION_OPTIONS_MOCK, + ); + + expect(request.ppomController.usePPOM).toHaveBeenCalledTimes(0); + }); + }); + + describe('when blockaid is disabled', () => { + it('does not validate if blockaid is disabled and chain id is supported', async () => { + await addTransaction({ + ...request, + securityAlertsEnabled: false, + chainId: '0x1', + }); + + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledTimes(1); + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledWith( + TRANSACTION_PARAMS_MOCK, + TRANSACTION_OPTIONS_MOCK, + ); + + expect(request.ppomController.usePPOM).toHaveBeenCalledTimes(0); + }); + + it('does not validate if blockaid is disabled and chain id is not supported', async () => { + await addTransaction({ + ...request, + securityAlertsEnabled: false, + chainId: '0xF', + }); + + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledTimes(1); + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledWith( + TRANSACTION_PARAMS_MOCK, + TRANSACTION_OPTIONS_MOCK, + ); + + expect(request.ppomController.usePPOM).toHaveBeenCalledTimes(0); + }); + }); }); describe('addDappTransaction', () => { @@ -384,7 +499,7 @@ describe('Transaction Utils', () => { describe('if selected account is smart contract', () => { beforeEach(() => { - request.selectedAccount.type = 'eip155:eip4337'; + request.selectedAccount.type = 'eip155:erc4337'; }); it('adds user operation', async () => { diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index 275333b12172..8e4420b1bc50 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -8,20 +8,38 @@ import { AddUserOperationOptions, UserOperationController, } from '@metamask/user-operation-controller'; +///: BEGIN:ONLY_INCLUDE_IF(blockaid) +import { PPOMController } from '@metamask/ppom-validator'; +import { captureException } from '@sentry/browser'; import { addHexPrefix } from 'ethereumjs-util'; +import { SUPPORTED_CHAIN_IDS } from '../ppom/ppom-middleware'; +///: END:ONLY_INCLUDE_IF export type AddTransactionOptions = NonNullable< Parameters[1] >; type BaseAddTransactionRequest = { + chainId: string; networkClientId: string; + ppomController: PPOMController; + securityAlertsEnabled: boolean; selectedAccount: InternalAccount; transactionParams: TransactionParams; transactionController: TransactionController; userOperationController: UserOperationController; }; +/** + * Type for security alert response from transaction validator. + */ +export type SecurityAlertResponse = { + reason: string; + features?: string[]; + result_type: string; + providerRequestsCount?: Record; +}; + type FinalAddTransactionRequest = BaseAddTransactionRequest & { transactionOptions: AddTransactionOptions; }; @@ -66,13 +84,49 @@ export async function addDappTransaction( export async function addTransaction( request: AddTransactionRequest, ): Promise { - const { waitForSubmit } = request; + ///: BEGIN:ONLY_INCLUDE_IF(blockaid) + const { + transactionParams, + transactionOptions, + ppomController, + securityAlertsEnabled, + chainId, + } = request; + + if (securityAlertsEnabled && SUPPORTED_CHAIN_IDS.includes(chainId)) { + try { + const ppomRequest = { + method: 'eth_sendTransaction', + id: 'actionId' in transactionOptions ? transactionOptions.actionId : '', + origin: 'origin' in transactionOptions ? transactionOptions.origin : '', + params: [ + { + from: transactionParams.from, + to: transactionParams.to, + value: transactionParams.value, + data: transactionParams.data, + }, + ], + }; + + const securityAlertResponse = await ppomController.usePPOM( + async (ppom) => { + return ppom.validateJsonRpc(ppomRequest); + }, + ); + + request.transactionOptions.securityAlertResponse = securityAlertResponse; + } catch (e) { + captureException(e); + } + } + ///: END:ONLY_INCLUDE_IF const { transactionMeta, waitForHash } = await addTransactionOrUserOperation( request, ); - if (!waitForSubmit) { + if (!request.waitForSubmit) { waitForHash().catch(() => { // Not concerned with result. }); @@ -95,7 +149,7 @@ async function addTransactionOrUserOperation( ) { const { selectedAccount } = request; - const isSmartContractAccount = selectedAccount.type === 'eip155:eip4337'; + const isSmartContractAccount = selectedAccount.type === 'eip155:erc4337'; if (isSmartContractAccount) { return addUserOperationWithController(request); @@ -109,7 +163,6 @@ async function addTransactionWithController( ) { const { transactionController, transactionOptions, transactionParams } = request; - const { result, transactionMeta } = await transactionController.addTransaction( transactionParams, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 05151debebc2..5ec712f0c434 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -115,7 +115,7 @@ import { ERC721, } from '@metamask/controller-utils'; import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english'; -///: BEGIN:ONLY_INCLUDE_IF(petnames) + import { NameController, ENSNameProvider, @@ -123,7 +123,6 @@ import { TokenNameProvider, LensNameProvider, } from '@metamask/name-controller'; -///: END:ONLY_INCLUDE_IF import { QueuedRequestController, @@ -224,10 +223,9 @@ import { import { keyringSnapPermissionsBuilder } from './lib/keyring-snaps-permissions'; ///: END:ONLY_INCLUDE_IF -///: BEGIN:ONLY_INCLUDE_IF(petnames) import { SnapsNameProvider } from './lib/SnapsNameProvider'; import { AddressBookPetnamesBridge } from './lib/AddressBookPetnamesBridge'; -///: END:ONLY_INCLUDE_IF +import { AccountIdentitiesPetnamesBridge } from './lib/AccountIdentitiesPetnamesBridge'; ///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { createPPOMMiddleware } from './lib/ppom/ppom-middleware'; @@ -769,16 +767,13 @@ export default class MetamaskController extends EventEmitter { this.ppomController = new PPOMController({ messenger: this.controllerMessenger.getRestricted({ name: 'PPOMController', + allowedEvents: ['NetworkController:stateChange'], }), storageBackend: new IndexedDBPPOMStorage('PPOMDB', 1), provider: this.provider, ppomProvider: { PPOM: PPOMModule.PPOM, ppomInit: PPOMModule.default }, state: initState.PPOMController, chainId: this.networkController.state.providerConfig.chainId, - onNetworkChange: networkControllerMessenger.subscribe.bind( - networkControllerMessenger, - 'NetworkController:stateChange', - ), securityAlertsEnabled: this.preferencesController.store.getState().securityAlertsEnabled, onPreferencesChange: this.preferencesController.store.subscribe.bind( @@ -975,6 +970,8 @@ export default class MetamaskController extends EventEmitter { 'PhishingController:maybeUpdateState', 'KeyringController:getAccounts', 'SubjectMetadataController:getSubjectMetadata', + 'AccountsController:setSelectedAccount', + 'AccountsController:getAccountByAddress', ], }); @@ -1458,7 +1455,7 @@ export default class MetamaskController extends EventEmitter { this.networkController.state.providerConfig.chainId ], getSelectedAddress: () => - this.preferencesController.store.getState().selectedAddress, + this.accountsController.getSelectedAccount().address, incomingTransactions: { includeTokenTransfers: false, isEnabled: () => @@ -1701,7 +1698,6 @@ export default class MetamaskController extends EventEmitter { initState.SmartTransactionsController, ); - ///: BEGIN:ONLY_INCLUDE_IF(petnames) const isExternalNameSourcesEnabled = () => this.preferencesController.store.getState().useExternalNameSources; @@ -1734,15 +1730,22 @@ export default class MetamaskController extends EventEmitter { state: initState.NameController, }); + const petnamesBridgeMessenger = this.controllerMessenger.getRestricted({ + name: 'PetnamesBridge', + allowedEvents: ['NameController:stateChange'], + }); + new AddressBookPetnamesBridge({ addressBookController: this.addressBookController, nameController: this.nameController, - messenger: this.controllerMessenger.getRestricted({ - name: 'AddressBookPetnamesBridge', - allowedEvents: ['NameController:stateChange'], - }), + messenger: petnamesBridgeMessenger, + }).init(); + + new AccountIdentitiesPetnamesBridge({ + preferencesController: this.preferencesController, + nameController: this.nameController, + messenger: petnamesBridgeMessenger, }).init(); - ///: END:ONLY_INCLUDE_IF this.userOperationController = new UserOperationController({ entrypoint: process.env.EIP_4337_ENTRYPOINT, @@ -1949,9 +1952,7 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IF(blockaid) PPOMController: this.ppomController, ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(petnames) NameController: this.nameController, - ///: END:ONLY_INCLUDE_IF UserOperationController: this.userOperationController, ...resetOnRestartStore, }); @@ -1998,9 +1999,7 @@ export default class MetamaskController extends EventEmitter { this.institutionalFeaturesController.store, MmiConfigurationController: this.mmiConfigurationController.store, ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(petnames) NameController: this.nameController, - ///: END:ONLY_INCLUDE_IF UserOperationController: this.userOperationController, ...resetOnRestartStore, }, @@ -2725,12 +2724,10 @@ export default class MetamaskController extends EventEmitter { preferencesController, ), ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(petnames) setUseExternalNameSources: preferencesController.setUseExternalNameSources.bind( preferencesController, ), - ///: END:ONLY_INCLUDE_IF setUseRequestQueue: this.setUseRequestQueue.bind(this), setIpfsGateway: preferencesController.setIpfsGateway.bind( preferencesController, @@ -3018,6 +3015,8 @@ export default class MetamaskController extends EventEmitter { txController.updateTransactionSendFlowHistory.bind(txController), updatePreviousGasParams: txController.updatePreviousGasParams.bind(txController), + abortTransactionSigning: + txController.abortTransactionSigning.bind(txController), // decryptMessageController decryptMessage: this.decryptMessageController.decryptMessage.bind( @@ -3327,12 +3326,10 @@ export default class MetamaskController extends EventEmitter { // E2E testing throwTestError: this.throwTestError.bind(this), - ///: BEGIN:ONLY_INCLUDE_IF(petnames) updateProposedNames: this.nameController.updateProposedNames.bind( this.nameController, ), setName: this.nameController.setName.bind(this.nameController), - ///: END:ONLY_INCLUDE_IF }; } @@ -4157,6 +4154,12 @@ export default class MetamaskController extends EventEmitter { transactionOptions, transactionParams, userOperationController: this.userOperationController, + ///: BEGIN:ONLY_INCLUDE_IF(blockaid) + ppomController: this.ppomController, + securityAlertsEnabled: + this.preferencesController.store.getState()?.securityAlertsEnabled, + chainId: this.networkController.state.providerConfig.chainId, + ///: END:ONLY_INCLUDE_IF }; } diff --git a/app/scripts/migrations/108.test.ts b/app/scripts/migrations/108.test.ts new file mode 100644 index 000000000000..b48c8e1d897d --- /dev/null +++ b/app/scripts/migrations/108.test.ts @@ -0,0 +1,259 @@ +import { migrate, version } from './108'; + +const oldVersion = 107; + +describe('migration #108', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('does nothing if no address book state', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('does nothing if no address book entries', async () => { + const oldState = { + OtherController: {}, + AddressBookController: { + addressBook: {}, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('adds name entries', async () => { + const oldState = { + OtherController: {}, + AddressBookController: { + addressBook: { + '0x1': { + '0xc0ffee254729296a45a3885639AC7E10F9d54979': { + name: 'TestName1', + isEns: false, + }, + '0xc0ffee254729296a45a3885639AC7E10F9d54978': { + name: 'TestName2', + isEns: true, + }, + }, + '0x2': { + '0xc0ffee254729296a45a3885639AC7E10F9d54977': { + name: 'TestName3', + isEns: false, + }, + '0xc0ffee254729296a45a3885639AC7E10F9d54978': { + name: 'TestName4', + isEns: false, + }, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + ethereumAddress: { + '0xc0ffee254729296a45a3885639ac7e10f9d54979': { + '0x1': { + name: 'TestName1', + sourceId: null, + proposedNames: {}, + }, + }, + '0xc0ffee254729296a45a3885639ac7e10f9d54978': { + '0x1': { + name: 'TestName2', + sourceId: 'ens', + proposedNames: {}, + }, + '0x2': { + name: 'TestName4', + sourceId: null, + proposedNames: {}, + }, + }, + '0xc0ffee254729296a45a3885639ac7e10f9d54977': { + '0x2': { + name: 'TestName3', + sourceId: null, + proposedNames: {}, + }, + }, + }, + }, + }, + }); + }); + + it('keeps existing name entries', async () => { + const oldState = { + OtherController: {}, + AddressBookController: { + addressBook: { + '0x1': { + '0xc0ffee254729296a45a3885639AC7E10F9d54979': { + name: 'TestName1', + isEns: false, + }, + }, + }, + }, + NameController: { + names: { + ethereumAddress: { + '0xc0ffee254729296a45a3885639ac7e10f9d54978': { + '0x1': { + name: 'TestName2', + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + ethereumAddress: { + '0xc0ffee254729296a45a3885639ac7e10f9d54979': { + '0x1': { + name: 'TestName1', + sourceId: null, + proposedNames: {}, + }, + }, + '0xc0ffee254729296a45a3885639ac7e10f9d54978': { + '0x1': { + name: 'TestName2', + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }); + }); + + it('ignores address book entry if existing petname', async () => { + const oldState = { + OtherController: {}, + AddressBookController: { + addressBook: { + '0x1': { + '0xc0ffee254729296a45a3885639AC7E10F9d54979': { + name: 'TestName1', + isEns: false, + }, + }, + }, + }, + NameController: { + names: { + ethereumAddress: { + '0xc0ffee254729296a45a3885639ac7e10f9d54979': { + '0x1': { + name: 'TestName2', + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + ethereumAddress: { + '0xc0ffee254729296a45a3885639ac7e10f9d54979': { + '0x1': { + name: 'TestName2', + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }); + }); + + it('ignores address book entry if no name or address', async () => { + const oldState = { + OtherController: {}, + AddressBookController: { + addressBook: { + '0x1': { + '': { + name: 'TestName1', + isEns: false, + }, + '0xc0ffee254729296a45a3885639AC7E10F9d54979': { + name: '', + isEns: false, + }, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + ethereumAddress: {}, + }, + }, + }); + }); +}); diff --git a/app/scripts/migrations/108.ts b/app/scripts/migrations/108.ts new file mode 100644 index 000000000000..1ea75957b73a --- /dev/null +++ b/app/scripts/migrations/108.ts @@ -0,0 +1,72 @@ +// This migration is a copy of 100.ts, but we want it to run again once +// petnames is enabled. +import { cloneDeep, isEmpty } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 108; + +/** + * Copy all entries from AddressBookController to NameController. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + const addressBook = state?.AddressBookController?.addressBook ?? {}; + const names = state?.NameController?.names?.ethereumAddress ?? {}; + + if (isEmpty(Object.keys(addressBook))) { + return; + } + + for (const chainId of Object.keys(addressBook)) { + const chainAddressBook = addressBook[chainId]; + + for (const address of Object.keys(chainAddressBook)) { + const addressBookEntry = chainAddressBook[address]; + const normalizedAddress = address.toLowerCase(); + const nameEntry = names[normalizedAddress] ?? {}; + const nameChainEntry = nameEntry[chainId] ?? {}; + + // Ignore if petname already set, or if address book entry is missing name or address. + if ( + nameChainEntry.name?.length || + !addressBookEntry.name?.length || + !normalizedAddress?.length + ) { + continue; + } + + names[normalizedAddress] = nameEntry; + + nameEntry[chainId] = { + name: addressBookEntry.name, + sourceId: addressBookEntry.isEns ? 'ens' : null, + proposedNames: {}, + }; + } + } + + state.NameController = { + ...state.NameController, + names: { + ethereumAddress: names, + }, + }; +} diff --git a/app/scripts/migrations/109.test.ts b/app/scripts/migrations/109.test.ts new file mode 100644 index 000000000000..24f8dc5336a8 --- /dev/null +++ b/app/scripts/migrations/109.test.ts @@ -0,0 +1,294 @@ +import { + FALLBACK_VARIATION, + NameOrigin, + NameType, +} from '@metamask/name-controller'; +import { migrate, version } from './109'; + +const oldVersion = 108; + +const ADDRESS_1 = '0xc0ffee254729296a45a3885639AC7E10F9d54979'; +const NAME_1 = 'TestName1'; +const ADDRESS_2 = '0xc0ffee254729296a45a3885639AC7E10F9d54978'; +const NAME_2 = 'TestName2'; +const ADDRESS_3 = '0xc0ffee254729296a45a3885639AC7E10F9d54977'; +const NAME_3 = 'TestName3'; + +describe('migration #108', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('does nothing if no preferences state', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('does nothing if no account id entries', async () => { + const oldState = { + OtherController: {}, + PreferencesController: { + identities: {}, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('adds name entries', async () => { + const oldState = { + OtherController: {}, + PreferencesController: { + identities: { + [ADDRESS_1]: { + name: NAME_1, + address: ADDRESS_1, + }, + [ADDRESS_2]: { + name: NAME_2, + address: ADDRESS_2, + }, + [ADDRESS_3]: { + name: NAME_3, + address: ADDRESS_3, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + [NameType.ETHEREUM_ADDRESS]: { + [ADDRESS_1.toLowerCase()]: { + [FALLBACK_VARIATION]: { + name: NAME_1, + sourceId: null, + proposedNames: {}, + origin: NameOrigin.ACCOUNT_IDENTITY, + }, + }, + [ADDRESS_2.toLowerCase()]: { + [FALLBACK_VARIATION]: { + name: NAME_2, + sourceId: null, + proposedNames: {}, + origin: NameOrigin.ACCOUNT_IDENTITY, + }, + }, + [ADDRESS_3.toLowerCase()]: { + [FALLBACK_VARIATION]: { + name: NAME_3, + sourceId: null, + proposedNames: {}, + origin: NameOrigin.ACCOUNT_IDENTITY, + }, + }, + }, + }, + }, + }); + }); + + it('keeps existing name entries', async () => { + const oldState = { + OtherController: {}, + PreferencesController: { + identities: { + [ADDRESS_1]: { + name: NAME_1, + address: ADDRESS_1, + }, + }, + }, + NameController: { + names: { + [NameType.ETHEREUM_ADDRESS]: { + [ADDRESS_2.toLowerCase()]: { + [FALLBACK_VARIATION]: { + name: NAME_2, + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + [NameType.ETHEREUM_ADDRESS]: { + [ADDRESS_1.toLowerCase()]: { + [FALLBACK_VARIATION]: { + name: NAME_1, + sourceId: null, + proposedNames: {}, + origin: NameOrigin.ACCOUNT_IDENTITY, + }, + }, + [ADDRESS_2.toLowerCase()]: { + [FALLBACK_VARIATION]: { + name: NAME_2, + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }); + }); + + it('ignores account id entry if existing petname', async () => { + const oldState = { + OtherController: {}, + PreferencesController: { + identities: { + [ADDRESS_1]: { + name: NAME_1, + address: ADDRESS_1, + }, + }, + }, + NameController: { + names: { + [NameType.ETHEREUM_ADDRESS]: { + [ADDRESS_1.toLowerCase()]: { + [FALLBACK_VARIATION]: { + name: NAME_2, + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + [NameType.ETHEREUM_ADDRESS]: { + [ADDRESS_1.toLowerCase()]: { + [FALLBACK_VARIATION]: { + name: NAME_2, + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }); + }); + + it('ignores account id entry if no name or address', async () => { + const oldState = { + OtherController: {}, + PreferencesController: { + identities: { + [ADDRESS_1]: { + name: NAME_1, + address: '', + }, + [ADDRESS_2]: { + name: '', + address: ADDRESS_2, + }, + [ADDRESS_3]: { + name: NAME_3, + address: ADDRESS_3, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + [NameType.ETHEREUM_ADDRESS]: { + [ADDRESS_3.toLowerCase()]: { + [FALLBACK_VARIATION]: { + name: NAME_3, + sourceId: null, + proposedNames: {}, + origin: NameOrigin.ACCOUNT_IDENTITY, + }, + }, + }, + }, + }, + }); + }); + + it('does not modify state if there are no changes.', async () => { + const oldState = { + OtherController: {}, + PreferencesController: { + identities: { + [ADDRESS_1]: { + name: NAME_1, + address: '', + }, + [ADDRESS_2]: { + name: '', + address: ADDRESS_2, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); +}); diff --git a/app/scripts/migrations/109.ts b/app/scripts/migrations/109.ts new file mode 100644 index 000000000000..5c40b07d4b09 --- /dev/null +++ b/app/scripts/migrations/109.ts @@ -0,0 +1,79 @@ +import { cloneDeep, isEmpty } from 'lodash'; +import { FALLBACK_VARIATION, NameOrigin } from '@metamask/name-controller'; +import { PreferencesControllerState } from '../controllers/preferences'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 109; + +/** + * Copy all account identity entries from PreferencesController to NameController. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + const identities: PreferencesControllerState['identities'] = + state?.PreferencesController?.identities ?? {}; + + const names = state?.NameController?.names?.ethereumAddress ?? {}; + + if (isEmpty(Object.keys(identities))) { + return; + } + + let hasChanges = false; + + for (const address of Object.keys(identities)) { + const accountEntry = identities[address]; + + const normalizedAddress = address.toLowerCase(); + const nameEntry = names[normalizedAddress] ?? {}; + const petnameExists = Boolean(nameEntry[FALLBACK_VARIATION]?.name); + + // Ignore if petname already set, or if account entry is missing name or address. + if ( + petnameExists || + !accountEntry.name?.length || + !accountEntry.address?.length || + !normalizedAddress?.length + ) { + continue; + } + + names[normalizedAddress] = nameEntry; + + nameEntry[FALLBACK_VARIATION] = { + name: accountEntry.name, + sourceId: null, + proposedNames: {}, + origin: NameOrigin.ACCOUNT_IDENTITY, + }; + + hasChanges = true; + } + + if (hasChanges) { + state.NameController = { + ...state.NameController, + names: { + ethereumAddress: names, + }, + }; + } +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index a2e8b177bdfe..a2bbda0c0ec1 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -118,6 +118,8 @@ const migrations = [ require('./105'), require('./106'), require('./107'), + require('./108'), + require('./109'), ]; export default migrations; diff --git a/app/trezor-usb-permissions.html b/app/trezor-usb-permissions.html index ee92fa7c1c9c..0903f12d712d 100644 --- a/app/trezor-usb-permissions.html +++ b/app/trezor-usb-permissions.html @@ -30,6 +30,6 @@ - + diff --git a/development/generate-rc-commits.js b/development/generate-rc-commits.js new file mode 100644 index 000000000000..c894ef3ed3f3 --- /dev/null +++ b/development/generate-rc-commits.js @@ -0,0 +1,197 @@ +const fs = require('fs'); +const simpleGit = require('simple-git'); +/* + * This script is used to filter and group commits by teams based on unique commit messages. + * It takes two branches as input and generates a CSV file with the commit hash, commit message, author, team, and PR link. + * The teams and their members are defined in the 'authorTeams' object. + * + * Command to run the script: node development/generate-rc-commits.js origin/branchA origin/branchB + * Output: the generated commits will be in a file named 'commits.csv'. + */ + +// JSON mapping authors to teams +const authorTeams = { + Accounts: [ + 'Owen Craston', + 'Gustavo Antunes', + 'Monte Lai', + 'Daniel Rocha', + 'Howard Braham', + 'Kate Johnson', + 'Brian Bergeron', + ], + 'Extension UX': ['David Walsh', 'vthomas13', 'Nidhi Kumari', 'Victor Thomas'], + 'Extension Platform': [ + 'chloeYue', + 'Pedro Figueiredo', + 'danjm', + 'Danica Shen', + 'Brad Decker', + 'Mark Stacey', + 'hjetpoluru', + 'Harika Jetpoluru', + 'Marina Boboc', + 'Gauthier Petetin', + 'Dan Miller', + 'Dan J Miller', + 'Gudahtt', + 'David Murdoch', + ], + DappAPI: ['tmashuang', 'jiexi', 'BelfordZ', 'Shane'], + 'Confirmation UX': [ + 'Sylva Elendu', + 'Olusegun Akintayo', + 'Jyoti Puri', + 'Ariella Vu', + 'Sylva Elendu', + 'seaona', + ], + 'Confirmation Systems': [ + 'OGPoyraz', + 'vinistevam', + 'Matthew Walsh', + 'cryptotavares', + 'Vinicius Stevam', + 'Derek Brans', + ], + 'Design Systems': ['georgewrmarshall', 'Garrett Bear', 'George Marshall'], + Snaps: [ + 'David Drazic', + 'hmalik88', + 'Montoya', + 'Mrtenz', + 'Frederik Bolding', + 'Bowen Sanders', + 'Guillaume Roux', + 'Hassan Malik', + 'Maarten Zuidhoorn', + ], + Assets: ['salimtb', 'sahar-fehri', 'Brian Bergeron'], + Linea: ['VGau'], + lavamoat: ['weizman', 'legobeat', 'kumavis'], + 'Shared Libraries': ['Michele Esposito', 'Elliot Winkler'], + MMI: [ + 'António Regadas', + 'Albert Olivé', + 'Ramon AC', + 'Shane T', + 'Bernardo Garces Chapero', + ], + Swaps: ['Daniel', 'Davide Brocchetto'], + Devex: ['Thomas Huang', 'Alex Donesky', 'jiexi', 'Zachary Belford'], +}; + +// Function to get the team for a given author +function getTeamForAuthor(authorName) { + for (const [team, authors] of Object.entries(authorTeams)) { + if (authors.includes(authorName)) { + return team; + } + } + return 'Other/Unknown'; // Default team for unknown authors +} + +// Function to filter commits based on unique commit messages and group by teams +async function filterCommitsByTeam(branchA, branchB) { + try { + const git = simpleGit(); + + const logOptions = { + from: branchB, + to: branchA, + format: { + hash: '%H', + author: '%an', + message: '%s', + }, + }; + + const log = await git.log(logOptions); + const seenMessages = new Set(); + const seenMessagesArray = []; + const commitsByTeam = {}; + + const MAX_COMMITS = 500; // Limit the number of commits to process + + for (const commit of log.all) { + const { author, message, hash } = commit; + if (commitsByTeam.length >= MAX_COMMITS) { + break; + } + + const team = getTeamForAuthor(author); + + // Extract PR number from the commit message using regex + const prMatch = message.match(/\(#(\d{5})\)$/u); + const prLink = prMatch + ? `https://github.com/MetaMask/metamask-extension/pull/${prMatch[1]}` + : ''; + + // Check if the commit message is unique + if (!seenMessages.has(message)) { + seenMessagesArray.push(message); + seenMessages.add(message); + + // Initialize the team's commits array if it doesn't exist + if (!commitsByTeam[team]) { + commitsByTeam[team] = []; + } + + commitsByTeam[team].push({ + message, + author, + hash: hash.substring(0, 10), + prLink, + }); + } + } + + return commitsByTeam; + } catch (error) { + console.error(error); + return {}; + } +} + +function formatAsCSV(commitsByTeam) { + const csvContent = []; + for (const [team, commits] of Object.entries(commitsByTeam)) { + commits.forEach((commit) => { + const row = [ + commit.hash, + commit.message, + commit.author, + team, + commit.prLink, + ]; + csvContent.push(row.join(',')); + }); + } + csvContent.unshift('Commit Hash,Commit Message,Author,Team,PR Link'); + + return csvContent; +} + +async function main() { + const args = process.argv.slice(2); + + if (args.length !== 2) { + console.error('Usage: node script.js branchA branchB'); + process.exit(1); + } + + const branchA = args[0]; + const branchB = args[1]; + + const commitsByTeam = await filterCommitsByTeam(branchA, branchB); + + if (Object.keys(commitsByTeam).length === 0) { + console.log('No unique commits found.'); + } else { + const csvContent = formatAsCSV(commitsByTeam); + fs.writeFileSync('commits.csv', csvContent.join('\n')); + console.log('CSV file "commits.csv" created successfully.'); + } +} + +main(); diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index c5d34a648bcd..f67c33beb813 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -20,9 +20,9 @@ "@ensdomains/content-hash>cids": { "packages": { "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multicodec": true, "@ensdomains/content-hash>cids>multihashes": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multicodec": true } }, "@ensdomains/content-hash>cids>multibase": { @@ -34,17 +34,11 @@ "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true } }, - "@ensdomains/content-hash>cids>multicodec": { - "packages": { - "@ensdomains/content-hash>cids>uint8arrays": true, - "@ensdomains/content-hash>multicodec>varint": true - } - }, "@ensdomains/content-hash>cids>multihashes": { "packages": { "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes>varint": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multihashes>varint": true } }, "@ensdomains/content-hash>cids>uint8arrays": { @@ -53,7 +47,7 @@ "TextEncoder": true }, "packages": { - "@ensdomains/content-hash>cids>multibase": true + "@metamask/assets-controllers>multiformats": true } }, "@ensdomains/content-hash>js-base64": { @@ -76,15 +70,13 @@ } }, "@ensdomains/content-hash>multicodec>uint8arrays": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays>multibase": true, - "@ensdomains/content-hash>multihashes>web-encoding": true - } - }, - "@ensdomains/content-hash>multicodec>uint8arrays>multibase": { - "packages": { - "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true, - "@ensdomains/content-hash>multihashes>web-encoding": true + "@metamask/assets-controllers>multiformats": true } }, "@ensdomains/content-hash>multihashes": { @@ -264,15 +256,7 @@ "@ethersproject/abi>@ethersproject/keccak256": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true - } - }, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": { - "globals": { - "define": true - }, - "packages": { - "browserify>process": true + "@metamask/ethjs>js-sha3": true } }, "@ethersproject/abi>@ethersproject/logger": { @@ -530,6 +514,18 @@ "watchify>xtend": true } }, + "@lavamoat/lavadome-react": { + "globals": { + "Document.prototype": true, + "Element.prototype": true, + "Node.prototype": true, + "console.warn": true, + "document": true + }, + "packages": { + "react": true + } + }, "@material-ui/core": { "globals": { "Image": true, @@ -709,7 +705,7 @@ }, "@metamask/accounts-controller": { "packages": { - "@metamask/accounts-controller>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/eth-snap-keyring": true, "@metamask/keyring-api": true, "@metamask/keyring-controller": true, @@ -717,14 +713,6 @@ "uuid": true } }, - "@metamask/accounts-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/address-book-controller": { "packages": { "@metamask/address-book-controller>@metamask/base-controller": true, @@ -749,7 +737,7 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -852,7 +840,8 @@ "globals": { "TextDecoder": true, "TextEncoder": true, - "console.warn": true + "console.warn": true, + "crypto.subtle.digest": true } }, "@metamask/base-controller": { @@ -1220,22 +1209,14 @@ }, "@metamask/ethjs-contract": { "packages": { - "@metamask/ethjs-contract>js-sha3": true, "@metamask/ethjs-query>babel-runtime": true, "@metamask/ethjs>@metamask/ethjs-filter": true, "@metamask/ethjs>@metamask/ethjs-util": true, "@metamask/ethjs>ethjs-abi": true, + "@metamask/ethjs>js-sha3": true, "promise-to-callback": true } }, - "@metamask/ethjs-contract>js-sha3": { - "globals": { - "define": true - }, - "packages": { - "browserify>process": true - } - }, "@metamask/ethjs-query": { "globals": { "console": true @@ -1311,17 +1292,12 @@ }, "@metamask/ethjs>ethjs-abi": { "packages": { - "@metamask/ethjs>ethjs-abi>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, - "@metamask/ethjs>ethjs-abi>js-sha3": { - "packages": { - "browserify>process": true - } - }, "@metamask/ethjs>js-sha3": { "globals": { "define": true @@ -1470,6 +1446,12 @@ "immer": true } }, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { + "packages": { + "@metamask/ethjs>number-to-bn": true, + "bn.js": true + } + }, "@metamask/logo": { "globals": { "addEventListener": true, @@ -1486,8 +1468,8 @@ }, "@metamask/message-manager": { "packages": { - "@metamask/message-manager>@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1496,31 +1478,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1703,12 +1660,6 @@ "webpack>events": true } }, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, "@metamask/ppom-validator>elliptic": { "packages": { "@metamask/ppom-validator>elliptic>brorand": true, @@ -1838,7 +1789,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -1883,7 +1834,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -2933,9 +2884,9 @@ "name": "write" }, "packages": { + "@metamask/ethjs>js-sha3": true, "browserify>buffer": true, - "eth-ens-namehash>idna-uts46-hx": true, - "eth-ens-namehash>js-sha3": true + "eth-ens-namehash>idna-uts46-hx": true } }, "eth-ens-namehash>idna-uts46-hx": { @@ -2946,11 +2897,6 @@ "browserify>punycode": true } }, - "eth-ens-namehash>js-sha3": { - "packages": { - "browserify>process": true - } - }, "eth-json-rpc-filters": { "globals": { "console.error": true @@ -3071,7 +3017,7 @@ "packages": { "@ethereumjs/common>crc-32": true, "@ethersproject/abi": true, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "@metamask/ppom-validator>elliptic": true, "bn.js": true, "browserify>buffer": true, @@ -3179,7 +3125,7 @@ "intToBuffer": true }, "packages": { - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "bn.js": true, "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>buffer": true } diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index d5bbbd4b89a3..41af7deaa1e2 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -20,9 +20,9 @@ "@ensdomains/content-hash>cids": { "packages": { "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multicodec": true, "@ensdomains/content-hash>cids>multihashes": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multicodec": true } }, "@ensdomains/content-hash>cids>multibase": { @@ -34,17 +34,11 @@ "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true } }, - "@ensdomains/content-hash>cids>multicodec": { - "packages": { - "@ensdomains/content-hash>cids>uint8arrays": true, - "@ensdomains/content-hash>multicodec>varint": true - } - }, "@ensdomains/content-hash>cids>multihashes": { "packages": { "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes>varint": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multihashes>varint": true } }, "@ensdomains/content-hash>cids>uint8arrays": { @@ -53,7 +47,7 @@ "TextEncoder": true }, "packages": { - "@ensdomains/content-hash>cids>multibase": true + "@metamask/assets-controllers>multiformats": true } }, "@ensdomains/content-hash>js-base64": { @@ -76,15 +70,13 @@ } }, "@ensdomains/content-hash>multicodec>uint8arrays": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays>multibase": true, - "@ensdomains/content-hash>multihashes>web-encoding": true - } - }, - "@ensdomains/content-hash>multicodec>uint8arrays>multibase": { - "packages": { - "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true, - "@ensdomains/content-hash>multihashes>web-encoding": true + "@metamask/assets-controllers>multiformats": true } }, "@ensdomains/content-hash>multihashes": { @@ -264,15 +256,7 @@ "@ethersproject/abi>@ethersproject/keccak256": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true - } - }, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": { - "globals": { - "define": true - }, - "packages": { - "browserify>process": true + "@metamask/ethjs>js-sha3": true } }, "@ethersproject/abi>@ethersproject/logger": { @@ -530,6 +514,18 @@ "watchify>xtend": true } }, + "@lavamoat/lavadome-react": { + "globals": { + "Document.prototype": true, + "Element.prototype": true, + "Node.prototype": true, + "console.warn": true, + "document": true + }, + "packages": { + "react": true + } + }, "@material-ui/core": { "globals": { "Image": true, @@ -709,7 +705,7 @@ }, "@metamask/accounts-controller": { "packages": { - "@metamask/accounts-controller>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/eth-snap-keyring": true, "@metamask/keyring-api": true, "@metamask/keyring-controller": true, @@ -717,14 +713,6 @@ "uuid": true } }, - "@metamask/accounts-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/address-book-controller": { "packages": { "@metamask/address-book-controller>@metamask/base-controller": true, @@ -749,7 +737,7 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -852,7 +840,8 @@ "globals": { "TextDecoder": true, "TextEncoder": true, - "console.warn": true + "console.warn": true, + "crypto.subtle.digest": true } }, "@metamask/base-controller": { @@ -1297,22 +1286,14 @@ }, "@metamask/ethjs-contract": { "packages": { - "@metamask/ethjs-contract>js-sha3": true, "@metamask/ethjs-query>babel-runtime": true, "@metamask/ethjs>@metamask/ethjs-filter": true, "@metamask/ethjs>@metamask/ethjs-util": true, "@metamask/ethjs>ethjs-abi": true, + "@metamask/ethjs>js-sha3": true, "promise-to-callback": true } }, - "@metamask/ethjs-contract>js-sha3": { - "globals": { - "define": true - }, - "packages": { - "browserify>process": true - } - }, "@metamask/ethjs-query": { "globals": { "console": true @@ -1388,17 +1369,12 @@ }, "@metamask/ethjs>ethjs-abi": { "packages": { - "@metamask/ethjs>ethjs-abi>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, - "@metamask/ethjs>ethjs-abi>js-sha3": { - "packages": { - "browserify>process": true - } - }, "@metamask/ethjs>js-sha3": { "globals": { "define": true @@ -1547,6 +1523,12 @@ "immer": true } }, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { + "packages": { + "@metamask/ethjs>number-to-bn": true, + "bn.js": true + } + }, "@metamask/logo": { "globals": { "addEventListener": true, @@ -1563,8 +1545,8 @@ }, "@metamask/message-manager": { "packages": { - "@metamask/message-manager>@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1573,31 +1555,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1807,12 +1764,6 @@ "webpack>events": true } }, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, "@metamask/ppom-validator>elliptic": { "packages": { "@metamask/ppom-validator>elliptic>brorand": true, @@ -1979,7 +1930,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -2024,7 +1975,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -3190,9 +3141,9 @@ "name": "write" }, "packages": { + "@metamask/ethjs>js-sha3": true, "browserify>buffer": true, - "eth-ens-namehash>idna-uts46-hx": true, - "eth-ens-namehash>js-sha3": true + "eth-ens-namehash>idna-uts46-hx": true } }, "eth-ens-namehash>idna-uts46-hx": { @@ -3203,11 +3154,6 @@ "browserify>punycode": true } }, - "eth-ens-namehash>js-sha3": { - "packages": { - "browserify>process": true - } - }, "eth-json-rpc-filters": { "globals": { "console.error": true @@ -3328,7 +3274,7 @@ "packages": { "@ethereumjs/common>crc-32": true, "@ethersproject/abi": true, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "@metamask/ppom-validator>elliptic": true, "bn.js": true, "browserify>buffer": true, @@ -3436,7 +3382,7 @@ "intToBuffer": true }, "packages": { - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "bn.js": true, "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>buffer": true } diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index caa141248e0c..845be1f70a15 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -20,9 +20,9 @@ "@ensdomains/content-hash>cids": { "packages": { "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multicodec": true, "@ensdomains/content-hash>cids>multihashes": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multicodec": true } }, "@ensdomains/content-hash>cids>multibase": { @@ -34,17 +34,11 @@ "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true } }, - "@ensdomains/content-hash>cids>multicodec": { - "packages": { - "@ensdomains/content-hash>cids>uint8arrays": true, - "@ensdomains/content-hash>multicodec>varint": true - } - }, "@ensdomains/content-hash>cids>multihashes": { "packages": { "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes>varint": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multihashes>varint": true } }, "@ensdomains/content-hash>cids>uint8arrays": { @@ -53,7 +47,7 @@ "TextEncoder": true }, "packages": { - "@ensdomains/content-hash>cids>multibase": true + "@metamask/assets-controllers>multiformats": true } }, "@ensdomains/content-hash>js-base64": { @@ -76,15 +70,13 @@ } }, "@ensdomains/content-hash>multicodec>uint8arrays": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays>multibase": true, - "@ensdomains/content-hash>multihashes>web-encoding": true - } - }, - "@ensdomains/content-hash>multicodec>uint8arrays>multibase": { - "packages": { - "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true, - "@ensdomains/content-hash>multihashes>web-encoding": true + "@metamask/assets-controllers>multiformats": true } }, "@ensdomains/content-hash>multihashes": { @@ -264,15 +256,7 @@ "@ethersproject/abi>@ethersproject/keccak256": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true - } - }, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": { - "globals": { - "define": true - }, - "packages": { - "browserify>process": true + "@metamask/ethjs>js-sha3": true } }, "@ethersproject/abi>@ethersproject/logger": { @@ -530,6 +514,18 @@ "watchify>xtend": true } }, + "@lavamoat/lavadome-react": { + "globals": { + "Document.prototype": true, + "Element.prototype": true, + "Node.prototype": true, + "console.warn": true, + "document": true + }, + "packages": { + "react": true + } + }, "@material-ui/core": { "globals": { "Image": true, @@ -709,7 +705,7 @@ }, "@metamask/accounts-controller": { "packages": { - "@metamask/accounts-controller>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/eth-snap-keyring": true, "@metamask/keyring-api": true, "@metamask/keyring-controller": true, @@ -717,14 +713,6 @@ "uuid": true } }, - "@metamask/accounts-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/address-book-controller": { "packages": { "@metamask/address-book-controller>@metamask/base-controller": true, @@ -749,7 +737,7 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -852,7 +840,8 @@ "globals": { "TextDecoder": true, "TextEncoder": true, - "console.warn": true + "console.warn": true, + "crypto.subtle.digest": true } }, "@metamask/base-controller": { @@ -1297,22 +1286,14 @@ }, "@metamask/ethjs-contract": { "packages": { - "@metamask/ethjs-contract>js-sha3": true, "@metamask/ethjs-query>babel-runtime": true, "@metamask/ethjs>@metamask/ethjs-filter": true, "@metamask/ethjs>@metamask/ethjs-util": true, "@metamask/ethjs>ethjs-abi": true, + "@metamask/ethjs>js-sha3": true, "promise-to-callback": true } }, - "@metamask/ethjs-contract>js-sha3": { - "globals": { - "define": true - }, - "packages": { - "browserify>process": true - } - }, "@metamask/ethjs-query": { "globals": { "console": true @@ -1388,17 +1369,12 @@ }, "@metamask/ethjs>ethjs-abi": { "packages": { - "@metamask/ethjs>ethjs-abi>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, - "@metamask/ethjs>ethjs-abi>js-sha3": { - "packages": { - "browserify>process": true - } - }, "@metamask/ethjs>js-sha3": { "globals": { "define": true @@ -1547,6 +1523,12 @@ "immer": true } }, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { + "packages": { + "@metamask/ethjs>number-to-bn": true, + "bn.js": true + } + }, "@metamask/logo": { "globals": { "addEventListener": true, @@ -1563,8 +1545,8 @@ }, "@metamask/message-manager": { "packages": { - "@metamask/message-manager>@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1573,31 +1555,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1812,12 +1769,14 @@ "URL": true, "clearInterval": true, "console.error": true, + "crypto": true, "setInterval": true }, "packages": { + "@metamask/controller-utils": true, "@metamask/eth-query>json-rpc-random-id": true, "@metamask/ppom-validator>@metamask/base-controller": true, - "@metamask/ppom-validator>@metamask/controller-utils": true, + "@metamask/ppom-validator>crypto-js": true, "@metamask/ppom-validator>elliptic": true, "await-semaphore": true, "browserify>buffer": true @@ -1831,40 +1790,14 @@ "immer": true } }, - "@metamask/ppom-validator>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, - "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": { + "@metamask/ppom-validator>crypto-js": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "crypto": true, + "define": true, + "msCrypto": true }, "packages": { - "@metamask/utils>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true + "browserify>browser-resolve": true } }, "@metamask/ppom-validator>elliptic": { @@ -2033,7 +1966,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -2078,7 +2011,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -3244,9 +3177,9 @@ "name": "write" }, "packages": { + "@metamask/ethjs>js-sha3": true, "browserify>buffer": true, - "eth-ens-namehash>idna-uts46-hx": true, - "eth-ens-namehash>js-sha3": true + "eth-ens-namehash>idna-uts46-hx": true } }, "eth-ens-namehash>idna-uts46-hx": { @@ -3257,11 +3190,6 @@ "browserify>punycode": true } }, - "eth-ens-namehash>js-sha3": { - "packages": { - "browserify>process": true - } - }, "eth-json-rpc-filters": { "globals": { "console.error": true @@ -3382,7 +3310,7 @@ "packages": { "@ethereumjs/common>crc-32": true, "@ethersproject/abi": true, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "@metamask/ppom-validator>elliptic": true, "bn.js": true, "browserify>buffer": true, @@ -3490,7 +3418,7 @@ "intToBuffer": true }, "packages": { - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "bn.js": true, "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>buffer": true } diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index fc71925a23a8..5f31169dc6c2 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -20,9 +20,9 @@ "@ensdomains/content-hash>cids": { "packages": { "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multicodec": true, "@ensdomains/content-hash>cids>multihashes": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multicodec": true } }, "@ensdomains/content-hash>cids>multibase": { @@ -34,17 +34,11 @@ "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true } }, - "@ensdomains/content-hash>cids>multicodec": { - "packages": { - "@ensdomains/content-hash>cids>uint8arrays": true, - "@ensdomains/content-hash>multicodec>varint": true - } - }, "@ensdomains/content-hash>cids>multihashes": { "packages": { "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes>varint": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multihashes>varint": true } }, "@ensdomains/content-hash>cids>uint8arrays": { @@ -53,7 +47,7 @@ "TextEncoder": true }, "packages": { - "@ensdomains/content-hash>cids>multibase": true + "@metamask/assets-controllers>multiformats": true } }, "@ensdomains/content-hash>js-base64": { @@ -76,15 +70,13 @@ } }, "@ensdomains/content-hash>multicodec>uint8arrays": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays>multibase": true, - "@ensdomains/content-hash>multihashes>web-encoding": true - } - }, - "@ensdomains/content-hash>multicodec>uint8arrays>multibase": { - "packages": { - "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true, - "@ensdomains/content-hash>multihashes>web-encoding": true + "@metamask/assets-controllers>multiformats": true } }, "@ensdomains/content-hash>multihashes": { @@ -264,15 +256,7 @@ "@ethersproject/abi>@ethersproject/keccak256": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true - } - }, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": { - "globals": { - "define": true - }, - "packages": { - "browserify>process": true + "@metamask/ethjs>js-sha3": true } }, "@ethersproject/abi>@ethersproject/logger": { @@ -530,6 +514,18 @@ "watchify>xtend": true } }, + "@lavamoat/lavadome-react": { + "globals": { + "Document.prototype": true, + "Element.prototype": true, + "Node.prototype": true, + "console.warn": true, + "document": true + }, + "packages": { + "react": true + } + }, "@material-ui/core": { "globals": { "Image": true, @@ -709,7 +705,7 @@ }, "@metamask/accounts-controller": { "packages": { - "@metamask/accounts-controller>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/eth-snap-keyring": true, "@metamask/keyring-api": true, "@metamask/keyring-controller": true, @@ -717,14 +713,6 @@ "uuid": true } }, - "@metamask/accounts-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/address-book-controller": { "packages": { "@metamask/address-book-controller>@metamask/base-controller": true, @@ -749,7 +737,7 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -852,7 +840,8 @@ "globals": { "TextDecoder": true, "TextEncoder": true, - "console.warn": true + "console.warn": true, + "crypto.subtle.digest": true } }, "@metamask/base-controller": { @@ -1220,22 +1209,14 @@ }, "@metamask/ethjs-contract": { "packages": { - "@metamask/ethjs-contract>js-sha3": true, "@metamask/ethjs-query>babel-runtime": true, "@metamask/ethjs>@metamask/ethjs-filter": true, "@metamask/ethjs>@metamask/ethjs-util": true, "@metamask/ethjs>ethjs-abi": true, + "@metamask/ethjs>js-sha3": true, "promise-to-callback": true } }, - "@metamask/ethjs-contract>js-sha3": { - "globals": { - "define": true - }, - "packages": { - "browserify>process": true - } - }, "@metamask/ethjs-query": { "globals": { "console": true @@ -1311,17 +1292,12 @@ }, "@metamask/ethjs>ethjs-abi": { "packages": { - "@metamask/ethjs>ethjs-abi>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, - "@metamask/ethjs>ethjs-abi>js-sha3": { - "packages": { - "browserify>process": true - } - }, "@metamask/ethjs>js-sha3": { "globals": { "define": true @@ -1470,6 +1446,12 @@ "immer": true } }, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { + "packages": { + "@metamask/ethjs>number-to-bn": true, + "bn.js": true + } + }, "@metamask/logo": { "globals": { "addEventListener": true, @@ -1486,8 +1468,8 @@ }, "@metamask/message-manager": { "packages": { - "@metamask/message-manager>@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1496,31 +1478,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1735,12 +1692,14 @@ "URL": true, "clearInterval": true, "console.error": true, + "crypto": true, "setInterval": true }, "packages": { + "@metamask/controller-utils": true, "@metamask/eth-query>json-rpc-random-id": true, "@metamask/ppom-validator>@metamask/base-controller": true, - "@metamask/ppom-validator>@metamask/controller-utils": true, + "@metamask/ppom-validator>crypto-js": true, "@metamask/ppom-validator>elliptic": true, "await-semaphore": true, "browserify>buffer": true @@ -1754,40 +1713,14 @@ "immer": true } }, - "@metamask/ppom-validator>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, - "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": { + "@metamask/ppom-validator>crypto-js": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "crypto": true, + "define": true, + "msCrypto": true }, "packages": { - "@metamask/utils>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true + "browserify>browser-resolve": true } }, "@metamask/ppom-validator>elliptic": { @@ -1956,7 +1889,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -2001,7 +1934,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -3167,9 +3100,9 @@ "name": "write" }, "packages": { + "@metamask/ethjs>js-sha3": true, "browserify>buffer": true, - "eth-ens-namehash>idna-uts46-hx": true, - "eth-ens-namehash>js-sha3": true + "eth-ens-namehash>idna-uts46-hx": true } }, "eth-ens-namehash>idna-uts46-hx": { @@ -3180,11 +3113,6 @@ "browserify>punycode": true } }, - "eth-ens-namehash>js-sha3": { - "packages": { - "browserify>process": true - } - }, "eth-json-rpc-filters": { "globals": { "console.error": true @@ -3305,7 +3233,7 @@ "packages": { "@ethereumjs/common>crc-32": true, "@ethersproject/abi": true, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "@metamask/ppom-validator>elliptic": true, "bn.js": true, "browserify>buffer": true, @@ -3413,7 +3341,7 @@ "intToBuffer": true }, "packages": { - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "bn.js": true, "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>buffer": true } diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 28fddac75a84..bc3d6131a833 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -20,9 +20,9 @@ "@ensdomains/content-hash>cids": { "packages": { "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multicodec": true, "@ensdomains/content-hash>cids>multihashes": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multicodec": true } }, "@ensdomains/content-hash>cids>multibase": { @@ -34,17 +34,11 @@ "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true } }, - "@ensdomains/content-hash>cids>multicodec": { - "packages": { - "@ensdomains/content-hash>cids>uint8arrays": true, - "@ensdomains/content-hash>multicodec>varint": true - } - }, "@ensdomains/content-hash>cids>multihashes": { "packages": { "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes>varint": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multihashes>varint": true } }, "@ensdomains/content-hash>cids>uint8arrays": { @@ -53,7 +47,7 @@ "TextEncoder": true }, "packages": { - "@ensdomains/content-hash>cids>multibase": true + "@metamask/assets-controllers>multiformats": true } }, "@ensdomains/content-hash>js-base64": { @@ -76,15 +70,13 @@ } }, "@ensdomains/content-hash>multicodec>uint8arrays": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays>multibase": true, - "@ensdomains/content-hash>multihashes>web-encoding": true - } - }, - "@ensdomains/content-hash>multicodec>uint8arrays>multibase": { - "packages": { - "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true, - "@ensdomains/content-hash>multihashes>web-encoding": true + "@metamask/assets-controllers>multiformats": true } }, "@ensdomains/content-hash>multihashes": { @@ -264,15 +256,7 @@ "@ethersproject/abi>@ethersproject/keccak256": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true - } - }, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": { - "globals": { - "define": true - }, - "packages": { - "browserify>process": true + "@metamask/ethjs>js-sha3": true } }, "@ethersproject/abi>@ethersproject/logger": { @@ -530,6 +514,18 @@ "watchify>xtend": true } }, + "@lavamoat/lavadome-react": { + "globals": { + "Document.prototype": true, + "Element.prototype": true, + "Node.prototype": true, + "console.warn": true, + "document": true + }, + "packages": { + "react": true + } + }, "@material-ui/core": { "globals": { "Image": true, @@ -841,7 +837,7 @@ }, "@metamask/accounts-controller": { "packages": { - "@metamask/accounts-controller>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/eth-snap-keyring": true, "@metamask/keyring-api": true, "@metamask/keyring-controller": true, @@ -849,14 +845,6 @@ "uuid": true } }, - "@metamask/accounts-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/address-book-controller": { "packages": { "@metamask/address-book-controller>@metamask/base-controller": true, @@ -881,7 +869,7 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -984,7 +972,8 @@ "globals": { "TextDecoder": true, "TextEncoder": true, - "console.warn": true + "console.warn": true, + "crypto.subtle.digest": true } }, "@metamask/base-controller": { @@ -1352,22 +1341,14 @@ }, "@metamask/ethjs-contract": { "packages": { - "@metamask/ethjs-contract>js-sha3": true, "@metamask/ethjs-query>babel-runtime": true, "@metamask/ethjs>@metamask/ethjs-filter": true, "@metamask/ethjs>@metamask/ethjs-util": true, "@metamask/ethjs>ethjs-abi": true, + "@metamask/ethjs>js-sha3": true, "promise-to-callback": true } }, - "@metamask/ethjs-contract>js-sha3": { - "globals": { - "define": true - }, - "packages": { - "browserify>process": true - } - }, "@metamask/ethjs-query": { "globals": { "console": true @@ -1443,17 +1424,12 @@ }, "@metamask/ethjs>ethjs-abi": { "packages": { - "@metamask/ethjs>ethjs-abi>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, - "@metamask/ethjs>ethjs-abi>js-sha3": { - "packages": { - "browserify>process": true - } - }, "@metamask/ethjs>js-sha3": { "globals": { "define": true @@ -1602,6 +1578,12 @@ "immer": true } }, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { + "packages": { + "@metamask/ethjs>number-to-bn": true, + "bn.js": true + } + }, "@metamask/logo": { "globals": { "addEventListener": true, @@ -1618,8 +1600,8 @@ }, "@metamask/message-manager": { "packages": { - "@metamask/message-manager>@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1628,31 +1610,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1862,12 +1819,6 @@ "webpack>events": true } }, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, "@metamask/ppom-validator>elliptic": { "packages": { "@metamask/ppom-validator>elliptic>brorand": true, @@ -2034,7 +1985,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -2079,7 +2030,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ppom-validator>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -3245,9 +3196,9 @@ "name": "write" }, "packages": { + "@metamask/ethjs>js-sha3": true, "browserify>buffer": true, - "eth-ens-namehash>idna-uts46-hx": true, - "eth-ens-namehash>js-sha3": true + "eth-ens-namehash>idna-uts46-hx": true } }, "eth-ens-namehash>idna-uts46-hx": { @@ -3258,11 +3209,6 @@ "browserify>punycode": true } }, - "eth-ens-namehash>js-sha3": { - "packages": { - "browserify>process": true - } - }, "eth-json-rpc-filters": { "globals": { "console.error": true @@ -3383,7 +3329,7 @@ "packages": { "@ethereumjs/common>crc-32": true, "@ethersproject/abi": true, - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "@metamask/ppom-validator>elliptic": true, "bn.js": true, "browserify>buffer": true, @@ -3491,7 +3437,7 @@ "intToBuffer": true }, "packages": { - "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true, + "@metamask/ethjs>js-sha3": true, "bn.js": true, "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>buffer": true } diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 64b4854bde1a..96b6c50fcac7 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -50,12 +50,7 @@ "process.versions.node.split": true }, "packages": { - "@babel/code-frame>@babel/highlight>chalk>supports-color>has-flag": true - } - }, - "@babel/code-frame>@babel/highlight>chalk>supports-color>has-flag": { - "globals": { - "process.argv": true + "gulp-livereload>chalk>supports-color>has-flag": true } }, "@babel/code-frame>chalk": { @@ -91,12 +86,7 @@ "process.versions.node.split": true }, "packages": { - "@babel/code-frame>chalk>supports-color>has-flag": true - } - }, - "@babel/code-frame>chalk>supports-color>has-flag": { - "globals": { - "process.argv": true + "gulp-livereload>chalk>supports-color>has-flag": true } }, "@babel/core": { @@ -575,10 +565,10 @@ "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-optimise-call-expression": true, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers": true, + "@babel/preset-env>@babel/plugin-transform-classes>globals": true, "depcheck>@babel/traverse>@babel/helper-environment-visitor": true, "depcheck>@babel/traverse>@babel/helper-function-name": true, - "depcheck>@babel/traverse>@babel/helper-split-export-declaration": true, - "depcheck>@babel/traverse>globals": true + "depcheck>@babel/traverse>@babel/helper-split-export-declaration": true } }, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": { @@ -3116,8 +3106,8 @@ "eslint-plugin-prettier": true, "eslint-plugin-react": true, "eslint-plugin-react-hooks": true, - "eslint>@eslint/eslintrc>globals": true, "eslint>ajv": true, + "eslint>globals": true, "eslint>ignore": true, "eslint>minimatch": true, "mocha>strip-json-comments": true, @@ -3933,12 +3923,7 @@ "process.versions.node.split": true }, "packages": { - "gulp-rtlcss>rtlcss>chalk>supports-color>has-flag": true - } - }, - "gulp-rtlcss>rtlcss>chalk>supports-color>has-flag": { - "globals": { - "process.argv": true + "gulp-livereload>chalk>supports-color>has-flag": true } }, "gulp-rtlcss>rtlcss>postcss": { @@ -6531,7 +6516,12 @@ "process.platform": true }, "packages": { - "chalk>supports-color>has-flag": true + "mocha>supports-color>has-flag": true + } + }, + "mocha>supports-color>has-flag": { + "globals": { + "process.argv": true } }, "nock>debug": { diff --git a/package.json b/package.json index f8e417ac56d3..46f347b33760 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,7 @@ "glob-parent": "^6.0.2", "globalthis@^1.0.1": "1.0.1", "netmask": "^2.0.1", + "js-sha3": "^0.9.2", "json-schema": "^0.4.0", "ast-types": "^0.14.2", "x-default-browser": "^0.5.2", @@ -235,6 +236,7 @@ "@fortawesome/fontawesome-free": "^5.13.0", "@keystonehq/bc-ur-registry-eth": "^0.19.1", "@keystonehq/metamask-airgapped-keyring": "^0.13.1", + "@lavamoat/lavadome-react": "0.0.10", "@lavamoat/snow": "^2.0.1", "@material-ui/core": "^4.11.0", "@metamask-institutional/custody-controller": "^0.2.18", @@ -245,16 +247,16 @@ "@metamask-institutional/rpc-allowlist": "^1.0.0", "@metamask-institutional/sdk": "^0.1.23", "@metamask-institutional/transaction-update": "^0.1.32", - "@metamask/accounts-controller": "patch:@metamask/accounts-controller@npm%3A5.0.0#~/.yarn/patches/@metamask-accounts-controller-npm-5.0.0-f877105fa0.patch", + "@metamask/accounts-controller": "patch:@metamask/accounts-controller@npm%3A10.0.0#~/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch", "@metamask/address-book-controller": "^3.0.0", "@metamask/announcement-controller": "^5.0.1", "@metamask/approval-controller": "^5.1.1", "@metamask/assets-controllers": "^24.0.0", - "@metamask/base-controller": "^4.0.0", + "@metamask/base-controller": "^4.1.0", "@metamask/browser-passworder": "^4.3.0", "@metamask/contract-metadata": "^2.3.1", "@metamask/controller-utils": "^8.0.1", - "@metamask/design-tokens": "^1.12.0", + "@metamask/design-tokens": "^1.13.0", "@metamask/desktop": "^0.3.0", "@metamask/eth-json-rpc-middleware": "^12.0.1", "@metamask/eth-keyring-controller": "^15.1.0", @@ -269,13 +271,13 @@ "@metamask/ethjs-query": "^0.5.3", "@metamask/gas-fee-controller": "^12.0.0", "@metamask/jazzicon": "^2.0.0", - "@metamask/keyring-api": "^1.0.0", + "@metamask/keyring-api": "^3.0.0", "@metamask/keyring-controller": "patch:@metamask/keyring-controller@npm%3A9.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-9.0.0-f57ed3ebea.patch", "@metamask/logging-controller": "^1.0.1", "@metamask/logo": "^3.1.2", "@metamask/message-manager": "^7.3.0", "@metamask/metamask-eth-abis": "^3.0.0", - "@metamask/name-controller": "^4.0.0", + "@metamask/name-controller": "^4.2.0", "@metamask/network-controller": "^17.1.0", "@metamask/notification-controller": "^3.0.0", "@metamask/obs-store": "^8.1.0", @@ -283,7 +285,7 @@ "@metamask/phishing-controller": "^8.0.0", "@metamask/polling-controller": "^4.0.0", "@metamask/post-message-stream": "^7.0.0", - "@metamask/ppom-validator": "^0.10.0", + "@metamask/ppom-validator": "^0.22.0", "@metamask/providers": "^14.0.2", "@metamask/queued-request-controller": "^0.3.0", "@metamask/rate-limit-controller": "^3.0.0", @@ -296,7 +298,7 @@ "@metamask/snaps-rpc-methods": "^5.0.0", "@metamask/snaps-sdk": "^1.4.0", "@metamask/snaps-utils": "^5.2.0", - "@metamask/transaction-controller": "^20.0.0", + "@metamask/transaction-controller": "^21.1.0", "@metamask/user-operation-controller": "^1.0.0", "@metamask/utils": "^8.2.1", "@ngraveio/bc-ur": "^1.1.6", @@ -377,6 +379,7 @@ "remove-trailing-slash": "^0.1.1", "reselect": "^3.0.1", "ses": "^1.1.0", + "simple-git": "^3.20.0", "single-call-balance-checker-abi": "^1.0.0", "unicode-confusables": "^0.1.1", "uuid": "^8.3.2", @@ -396,6 +399,7 @@ "@babel/preset-typescript": "^7.23.2", "@babel/register": "^7.22.15", "@lavamoat/allow-scripts": "^3.0.1", + "@lavamoat/lavadome-core": "0.0.10", "@lavamoat/lavapack": "^6.0.2", "@metamask/auto-changelog": "^2.1.0", "@metamask/build-utils": "^1.0.0", @@ -424,7 +428,7 @@ "@storybook/react": "^7.4.6", "@storybook/react-webpack5": "^7.4.6", "@storybook/storybook-deployer": "^2.8.16", - "@storybook/test-runner": "^0.10.0", + "@storybook/test-runner": "^0.14.1", "@storybook/theming": "^7.4.6", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^10.4.8", @@ -443,7 +447,7 @@ "@types/jest": "^29.1.2", "@types/madge": "^5.0.0", "@types/mocha": "^10.0.3", - "@types/node": "^18", + "@types/node": "^20", "@types/pify": "^5.0.1", "@types/pump": "^1.1.1", "@types/react": "^16.9.53", @@ -584,8 +588,8 @@ "yargs": "^17.0.1" }, "engines": { - "node": ">= 18", - "yarn": "^4.0.0-rc.48" + "node": ">= 20", + "yarn": "^4.0.2" }, "lavamoat": { "allowScripts": { @@ -653,7 +657,8 @@ "@trezor/connect-web>@trezor/connect>@trezor/utxo-lib>blake-hash": false, "@trezor/connect-web>@trezor/connect>@trezor/utxo-lib>tiny-secp256k1": false, "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>usb": false, - "@metamask/ethjs-query>babel-runtime>core-js": false + "@metamask/ethjs-query>babel-runtime>core-js": false, + "@storybook/test-runner>@swc/core": false } }, "packageManager": "yarn@4.0.2" diff --git a/privacy-snapshot.json b/privacy-snapshot.json index c04b77708e6d..97e3501b5b43 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -16,7 +16,7 @@ "etherscan.io", "execution.metamask.io", "fonts.gstatic.com", - "gas-api.metaswap.codefi.network", + "gas.api.cx.metamask.io", "github.com", "goerli.infura.io", "localhost:8000", diff --git a/shared/constants/hardware-wallets.ts b/shared/constants/hardware-wallets.ts index 846168e166ed..e20222ff2e5e 100644 --- a/shared/constants/hardware-wallets.ts +++ b/shared/constants/hardware-wallets.ts @@ -37,6 +37,7 @@ export enum HardwareAffiliateLinks { airgap = 'https://airgap.it/', coolwallet = 'https://www.coolwallet.io/', dcent = 'https://dcentwallet.com/', + imtoken = 'https://token.im/', } export enum HardwareAffiliateTutorialLinks { @@ -47,6 +48,7 @@ export enum HardwareAffiliateTutorialLinks { airgap = 'https://support.airgap.it/guides/metamask/', coolwallet = 'https://www.coolwallet.io/metamask-step-by-step-guides/', dcent = 'https://medium.com/dcentwallet/dcent-wallet-now-supports-qr-based-protocol-to-link-with-metamask-57555f02603f', + imtoken = 'https://support.token.im/hc/en-us/articles/24652624775961/', } /** diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index d29ec42eb38c..3db2c67f6785 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -467,9 +467,7 @@ export enum MetaMetricsUserTrait { */ MmiIsCustodian = 'mmi_is_custodian', ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(petnames) PetnameAddressCount = 'petname_addresses_count', - ///: END:ONLY_INCLUDE_IF } /** @@ -492,6 +490,11 @@ export const REJECT_NOTIFICATION_CLOSE = 'Cancel Via Notification Close'; export const REJECT_NOTIFICATION_CLOSE_SIG = 'Cancel Sig Request Via Notification Close'; +/** + * The name of the event. Event definitions with corresponding properties can be found in the following document: + * + * @see {@link https://www.notion.so/f2997ab32326441793ff790ba5c60a6a?v=267d984721cd4a26be610b5caa3e25b7&pvs=4} + */ export enum MetaMetricsEventName { AccountAdded = 'Account Added', AccountAddSelected = 'Account Add Selected', @@ -592,7 +595,6 @@ export enum MetaMetricsEventName { SrpViewSrpText = 'Views SRP', SrpCopiedToClipboard = 'Copies SRP to clipboard', SrpToConfirmBackup = 'SRP Backup Confirm Displayed', - StakingEntryPointClicked = 'Stake Button Clicked', SupportLinkClicked = 'Support Link Clicked', TermsOfUseShown = 'Terms of Use Shown', TermsOfUseAccepted = 'Terms of Use Accepted', @@ -755,7 +757,9 @@ export enum MetaMetricsEventLocation { export enum MetaMetricsEventUiCustomization { FlaggedAsMalicious = 'flagged_as_malicious', FlaggedAsSafetyUnknown = 'flagged_as_safety_unknown', + FlaggedAsWarning = 'flagged_as_warning', GasEstimationFailed = 'gas_estimation_failed', + SecurityAlertFailed = 'security_alert_failed', Siwe = 'sign_in_with_ethereum', } diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 4e408c2f846e..720138726287 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -145,60 +145,62 @@ export const CHAIN_IDS = { GNOSIS: '0x64', ZKSYNC_ERA: '0x144', TEST_ETH: '0x539', + XDC_NETWORK_MAINNET: '0x32', + XDC_APOTHEM_TESTNET: '0x33', } as const; const CHAINLIST_CHAIN_IDS_MAP = { ...CHAIN_IDS, SCROLL: '0x82750', - ZORA_MAINNET: '0x76adf1', TAIKO_JOLNIR_L2_MAINNET: '0x28c5f', - POLYGON_ZKEVM: '0x44d', FANTOM_OPERA: '0xfa', CELO_MAINNET: '0xa4ec', + KAVA_EVM: '0x8ae', + HARMONY_MAINNET_SHARD_0: '0x63564c40', + CRONOS_MAINNET_BETA: '0x19', + FUSE_MAINNET: '0x7a', + Q_MAINNET: '0x8a71', + HUOBI_ECO_CHAIN_MAINNET: '0x80', + ACALA_NETWORK: '0x313', ARBITRUM_NOVA: '0xa4ba', - MANTLE: '0x1388', + ASTAR: '0x250', + BAHAMUT_MAINNET: '0x142d', + BLACKFORT_EXCHANGE_NETWORK: '0x1387', + CANTO: '0x1e14', + CONFLUX_ESPACE: '0x406', CORE_BLOCKCHAIN_MAINNET: '0x45c', - MANTA_PACIFIC_MAINNET: '0xa9', - PULSECHAIN_MAINNET: '0x171', - FUSE_GOLD_MAINNET: '0x7a', - KAVA_EVM: '0x8ae', + DEXALOT_SUBNET: '0x6984c', DFK_CHAIN: '0xd2af', - HARMONY_MAINNET_SHARD_0: '0x63564c40', - PGN_PUBLIC_GOODS_NETWORK: '0x1a8', - LIGHTLINK_PHOENIX_MAINNET: '0x762', - NEAR_AURORA_MAINNET: '0x4e454152', - KROMA_MAINNET: '0xff', - NEBULA_MAINNET: '0x585eb4b1', - KLAYTN_MAINNET_CYPRESS: '0x2019', + DOGECHAIN_MAINNET: '0x7d0', ENDURANCE_SMART_CHAIN_MAINNET: '0x288', - CRONOS_MAINNET_BETA: '0x19', - FLARE_MAINNET: '0xe', - KCC_MAINNET: '0x141', - SHARDEUM_SPHINX_1X: '0x1f92', ETHEREUM_CLASSIC_MAINNET: '0x3d', + EVMOS: '0x2329', + FLARE_MAINNET: '0xe', + FUSE_GOLD_MAINNET: '0x7a', HAQQ_NETWORK: '0x2be3', + KCC_MAINNET: '0x141', + KLAYTN_MAINNET_CYPRESS: '0x2019', + KROMA_MAINNET: '0xff', + LIGHTLINK_PHOENIX_MAINNET: '0x762', + MANTA_PACIFIC_MAINNET: '0xa9', + MANTLE: '0x1388', + NEAR_AURORA_MAINNET: '0x4e454152', + NEBULA_MAINNET: '0x585eb4b1', + OASYS_MAINNET: '0xf8', + OKXCHAIN_MAINNET: '0x42', + PGN_PUBLIC_GOODS_NETWORK: '0x1a8', + POLYGON_ZKEVM: '0x44d', + PULSECHAIN_MAINNET: '0x171', SHARDEUM_LIBERTY_2X: '0x1f91', - BLACKFORT_EXCHANGE_NETWORK: '0x1387', - CONFLUX_ESPACE: '0x406', - FUSE_MAINNET: '0x7a', - CANTO: '0x1e14', + SHARDEUM_SPHINX_1X: '0x1f92', SHIB_MAINNET: '0x1b', - OKXCHAIN_MAINNET: '0x42', - ZKATANA: '0x133e40', - DEXALOT_SUBNET: '0x6984c', - ASTAR: '0x250', - EVMOS: '0x2329', - BAHAMUT_MAINNET: '0x142d', SONGBIRD_CANARY_NETWORK: '0x13', STEP_NETWORK: '0x4d2', - VELAS_EVM_MAINNET: '0x6a', - Q_MAINNET: '0x8a71', TELOS_EVM_MAINNET: '0x28', TENET: '0x617', - DOGECHAIN_MAINNET: '0x7d0', - OASYS_MAINNET: '0xf8', - HUOBI_ECO_CHAIN_MAINNET: '0x80', - ACALA_NETWORK: '0x313', + VELAS_EVM_MAINNET: '0x6a', + ZKATANA: '0x133e40', + ZORA_MAINNET: '0x76adf1', } as const; // To add a deprecation warning to a network, add it to the array @@ -232,6 +234,8 @@ export const GNOSIS_DISPLAY_NAME = 'Gnosis'; export const ZK_SYNC_ERA_DISPLAY_NAME = 'zkSync Era Mainnet'; export const BASE_DISPLAY_NAME = 'Base Mainnet'; export const AURORA_ETH_DISPLAY_NAME = 'Aurora'; +export const XDC_NETWORK_MAINNET_DISPLAY_NAME = 'XDC Network Mainnet'; +export const XDC_APOTHEM_TESTNET_DISPLAY_NAME = 'XDC Apothem Testnet'; export const infuraProjectId = process.env.INFURA_PROJECT_ID; export const getRpcUrl = ({ @@ -283,6 +287,8 @@ export const CURRENCY_SYMBOLS = { GLIMMER: 'GLMR', MOONRIVER: 'MOVR', ONE: 'ONE', + XDC_NETWORK_MAINNET: 'XDC', + XDC_APOTHEM_TESTNET: 'TXDC', } as const; const CHAINLIST_CURRENCY_SYMBOLS_MAP = { @@ -344,13 +350,13 @@ const CHAINLIST_CURRENCY_SYMBOLS_MAP = { ACALA_NETWORK: 'ACA', } as const; -export const ETH_TOKEN_IMAGE_URL = './images/eth_logo.png'; +export const ETH_TOKEN_IMAGE_URL = './images/eth_logo.svg'; export const LINEA_GOERLI_TOKEN_IMAGE_URL = './images/linea-logo-testnet.png'; -export const LINEA_MAINNET_TOKEN_IMAGE_URL = './images/linea-logo-mainnet.png'; +export const LINEA_MAINNET_TOKEN_IMAGE_URL = './images/linea-logo-mainnet.svg'; export const TEST_ETH_TOKEN_IMAGE_URL = './images/black-eth-logo.svg'; -export const BNB_TOKEN_IMAGE_URL = './images/bnb.png'; -export const MATIC_TOKEN_IMAGE_URL = './images/matic-token.png'; -export const AVAX_TOKEN_IMAGE_URL = './images/avax-token.png'; +export const BNB_TOKEN_IMAGE_URL = './images/bnb.svg'; +export const MATIC_TOKEN_IMAGE_URL = './images/matic-token.svg'; +export const AVAX_TOKEN_IMAGE_URL = './images/avax-token.svg'; export const AETH_TOKEN_IMAGE_URL = './images/arbitrum.svg'; export const FTM_TOKEN_IMAGE_URL = './images/fantom-opera.svg'; export const HARMONY_ONE_TOKEN_IMAGE_URL = './images/harmony-one.svg'; @@ -359,7 +365,52 @@ export const PALM_TOKEN_IMAGE_URL = './images/palm.svg'; export const CELO_TOKEN_IMAGE_URL = './images/celo.svg'; export const GNOSIS_TOKEN_IMAGE_URL = './images/gnosis.svg'; export const ZK_SYNC_ERA_TOKEN_IMAGE_URL = './images/zk-sync.svg'; -export const BASE_TOKEN_IMAGE_URL = './images/base.png'; +export const XDC_TOKEN_IMAGE_URL = './images/xdc-token.png'; +export const BASE_TOKEN_IMAGE_URL = './images/base.svg'; +export const ACALA_TOKEN_IMAGE_URL = './images/acala-network-logo.svg'; +export const ARBITRUM_NOVA_IMAGE_URL = './images/arbitrum-nova-logo.svg'; +export const ASTAR_IMAGE_URL = './images/astar-logo.svg'; +export const BAHAMUT_IMAGE_URL = './images/bahamut.png'; +export const BLACKFORT_IMAGE_URL = './images/blackfort.png'; +export const CANTO_IMAGE_URL = './images/canto.svg'; +export const CONFLUX_ESPACE_IMAGE_URL = './images/conflux.svg'; +export const CORE_BLOCKCHAIN_MAINNET_IMAGE_URL = './images/core.svg'; +export const CRONOS_IMAGE_URL = './images/cronos.svg'; +export const DEXALOT_SUBNET_IMAGE_URL = './images/dexalut-subnet.svg'; +export const DFK_CHAIN_IMAGE_URL = './images/dfk.png'; +export const DOGECHAIN_IMAGE_URL = './images/dogechain.jpeg'; +export const ENDURANCE_SMART_CHAIN_MAINNET_IMAGE_URL = + './images/endurance-smart-chain.png'; +export const ETHEREUM_CLASSIC_MAINNET_IMAGE_URL = './images/eth_classic.svg'; +export const EVMOS_IMAGE_URL = './images/evmos.svg'; +export const FLARE_MAINNET_IMAGE_URL = './images/flare-mainnet.svg'; +export const FUSE_GOLD_MAINNET_IMAGE_URL = './images/fuse-mainnet.jpeg'; +export const HAQQ_NETWORK_IMAGE_URL = './images/haqq.svg'; +export const KCC_MAINNET_IMAGE_URL = './images/kcc-mainnet.svg'; +export const KLAYTN_MAINNET_IMAGE_URL = './images/klaytn.svg'; +export const KROMA_MAINNET_IMAGE_URL = './images/kroma.svg'; +export const LIGHT_LINK_IMAGE_URL = './images/lightlink.svg'; +export const MANTA_PACIFIC_MAINNET_IMAGE_URL = './images/manta.svg'; +export const MANTLE_MAINNET_IMAGE_URL = './images/mantle.svg'; +export const MOONBEAM_IMAGE_URL = './images/moonbeam.svg'; +export const MOONRIVER_IMAGE_URL = './images/moonriver.svg'; +export const NEAR_AURORA_MAINNET_IMAGE_URL = './images/near-aurora.svg'; +export const NEBULA_MAINNET_IMAGE_URL = './images/nebula.svg'; +export const OASYS_MAINNET_IMAGE_URL = './images/oasys.svg'; +export const OKXCHAIN_MAINNET_IMAGE_URL = './images/okx.svg'; +export const PGN_MAINNET_IMAGE_URL = './images/pgn.svg'; +export const ZKEVM_MAINNET_IMAGE_URL = './images/polygon-zkevm.svg'; +export const PULSECHAIN_MAINNET_IMAGE_URL = './images/pulse.svg'; +export const SHARDEUM_LIBERTY_2X_IMAGE_URL = './images/shardeum-2.svg'; +export const SHARDEUM_SPHINX_1X_IMAGE_URL = './images/shardeum-1.svg'; +export const SHIB_MAINNET_IMAGE_URL = './images/shiba.svg'; +export const SONGBIRD_MAINNET_IMAGE_URL = './images/songbird.svg'; +export const STEP_NETWORK_IMAGE_URL = './images/setp.svg'; +export const TELOS_EVM_MAINNET_IMAGE_URL = './images/telos.svg'; +export const TENET_MAINNET_IMAGE_URL = './images/tenet.svg'; +export const VELAS_EVM_MAINNET_IMAGE_URL = './images/velas.svg'; +export const ZKATANA_MAINNET_IMAGE_URL = './images/zkatana.svg'; +export const ZORA_MAINNET_IMAGE_URL = './images/zora.svg'; export const INFURA_PROVIDER_TYPES = [ NETWORK_TYPES.MAINNET, @@ -377,8 +428,8 @@ export const TEST_CHAINS = [ ]; export const MAINNET_CHAINS = [ - { chainId: CHAIN_IDS.MAINNET }, - { chainId: CHAIN_IDS.LINEA_MAINNET }, + { chainId: CHAIN_IDS.MAINNET, rpcUrl: MAINNET_RPC_URL }, + { chainId: CHAIN_IDS.LINEA_MAINNET, rpcUrl: LINEA_MAINNET_RPC_URL }, ]; const typedCapitalize = (k: K): Capitalize => @@ -574,6 +625,10 @@ export const CHAIN_ID_TO_CURRENCY_SYMBOL_MAP = { CHAINLIST_CURRENCY_SYMBOLS_MAP.HUOBI_ECO_CHAIN_MAINNET, [CHAINLIST_CHAIN_IDS_MAP.ACALA_NETWORK]: CHAINLIST_CURRENCY_SYMBOLS_MAP.ACALA_NETWORK, + [CHAINLIST_CHAIN_IDS_MAP.XDC_NETWORK_MAINNET]: + CHAINLIST_CURRENCY_SYMBOLS_MAP.XDC_NETWORK_MAINNET, + [CHAINLIST_CHAIN_IDS_MAP.XDC_APOTHEM_TESTNET]: + CHAINLIST_CURRENCY_SYMBOLS_MAP.XDC_APOTHEM_TESTNET, } as const; export const CHAIN_ID_TO_TYPE_MAP = { @@ -608,6 +663,55 @@ export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { [CHAIN_IDS.PALM]: PALM_TOKEN_IMAGE_URL, [CHAIN_IDS.CELO]: CELO_TOKEN_IMAGE_URL, [CHAIN_IDS.GNOSIS]: GNOSIS_TOKEN_IMAGE_URL, + [CHAIN_IDS.XDC_NETWORK_MAINNET]: XDC_TOKEN_IMAGE_URL, + [CHAIN_IDS.XDC_APOTHEM_TESTNET]: XDC_TOKEN_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.ACALA_NETWORK]: ACALA_TOKEN_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.ARBITRUM_NOVA]: ARBITRUM_NOVA_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.ASTAR]: ASTAR_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.BAHAMUT_MAINNET]: BAHAMUT_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.BLACKFORT_EXCHANGE_NETWORK]: BLACKFORT_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.CANTO]: CANTO_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.CONFLUX_ESPACE]: CONFLUX_ESPACE_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.CORE_BLOCKCHAIN_MAINNET]: + CORE_BLOCKCHAIN_MAINNET_IMAGE_URL, + [CHAIN_IDS.CRONOS]: CRONOS_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.DEXALOT_SUBNET]: DEXALOT_SUBNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.DFK_CHAIN]: DFK_CHAIN_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.DOGECHAIN_MAINNET]: DOGECHAIN_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.ENDURANCE_SMART_CHAIN_MAINNET]: + ENDURANCE_SMART_CHAIN_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.ETHEREUM_CLASSIC_MAINNET]: + ETHEREUM_CLASSIC_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.EVMOS]: EVMOS_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.FLARE_MAINNET]: FLARE_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.FUSE_GOLD_MAINNET]: FUSE_GOLD_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.HAQQ_NETWORK]: HAQQ_NETWORK_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.KCC_MAINNET]: KCC_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.KLAYTN_MAINNET_CYPRESS]: KLAYTN_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.KROMA_MAINNET]: KROMA_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.LIGHTLINK_PHOENIX_MAINNET]: LIGHT_LINK_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.MANTA_PACIFIC_MAINNET]: + MANTA_PACIFIC_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.MANTLE]: MANTLE_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.MOONBEAM]: MOONBEAM_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.MOONRIVER]: MOONRIVER_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.NEAR_AURORA_MAINNET]: NEAR_AURORA_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.NEBULA_MAINNET]: NEBULA_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.OASYS_MAINNET]: OASYS_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.OKXCHAIN_MAINNET]: OKXCHAIN_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.PGN_PUBLIC_GOODS_NETWORK]: PGN_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.POLYGON_ZKEVM]: ZKEVM_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.PULSECHAIN_MAINNET]: PULSECHAIN_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.SHARDEUM_LIBERTY_2X]: SHARDEUM_LIBERTY_2X_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.SHARDEUM_SPHINX_1X]: SHARDEUM_SPHINX_1X_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.SHIB_MAINNET]: SHIB_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.SONGBIRD_CANARY_NETWORK]: SONGBIRD_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.STEP_NETWORK]: STEP_NETWORK_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.TELOS_EVM_MAINNET]: TELOS_EVM_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.TENET]: TENET_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.VELAS_EVM_MAINNET]: VELAS_EVM_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.ZKATANA]: ZKATANA_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.ZORA_MAINNET]: ZORA_MAINNET_IMAGE_URL, } as const; export const CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP = { @@ -627,6 +731,9 @@ export const CHAIN_ID_TOKEN_IMAGE_MAP = { [CHAIN_IDS.OPTIMISM]: ETH_TOKEN_IMAGE_URL, [CHAIN_IDS.CELO]: CELO_TOKEN_IMAGE_URL, [CHAIN_IDS.GNOSIS]: GNOSIS_TOKEN_IMAGE_URL, + [CHAIN_IDS.XDC_NETWORK_MAINNET]: XDC_TOKEN_IMAGE_URL, + [CHAIN_IDS.XDC_APOTHEM_TESTNET]: XDC_TOKEN_IMAGE_URL, + [CHAIN_IDS.FANTOM]: FTM_TOKEN_IMAGE_URL, } as const; export const INFURA_BLOCKED_KEY = 'countryBlocked'; @@ -718,6 +825,14 @@ export const ETHERSCAN_SUPPORTED_NETWORKS = { domain: 'gnosisscan.io', subdomain: `${defaultEtherscanSubdomainPrefix}-gnosis`, }, + [CHAIN_IDS.XDC_NETWORK_MAINNET]: { + domain: 'blocksscan.io', + subdomain: `${defaultEtherscanSubdomainPrefix}-xdc`, + }, + [CHAIN_IDS.XDC_APOTHEM_TESTNET]: { + domain: 'blocksscan.io', + subdomain: `${defaultEtherscanSubdomainPrefix}-apothem`, + }, }; export const CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP = { @@ -760,6 +875,7 @@ export const BUYABLE_CHAINS_MAP: { | typeof CHAIN_IDS.SEPOLIA | typeof CHAIN_IDS.GNOSIS | typeof CHAIN_IDS.AURORA + | typeof CHAIN_IDS.XDC_APOTHEM_TESTNET >]: BuyableChainSettings; } = { [CHAIN_IDS.MAINNET]: { @@ -822,6 +938,10 @@ export const BUYABLE_CHAINS_MAP: { nativeCurrency: CURRENCY_SYMBOLS.ETH, network: 'zksync', }, + [CHAIN_IDS.XDC_NETWORK_MAINNET]: { + nativeCurrency: CURRENCY_SYMBOLS.XDC_NETWORK_MAINNET, + network: 'xdc', + }, }; export const FEATURED_RPCS: RPCDefinition[] = [ @@ -915,6 +1035,26 @@ export const FEATURED_RPCS: RPCDefinition[] = [ imageUrl: BASE_TOKEN_IMAGE_URL, }, }, + { + chainId: CHAIN_IDS.XDC_NETWORK_MAINNET, + nickname: XDC_NETWORK_MAINNET_DISPLAY_NAME, + rpcUrl: `https://erpc.xinfin.network`, + ticker: CURRENCY_SYMBOLS.XDC_NETWORK_MAINNET, + rpcPrefs: { + blockExplorerUrl: 'https://xdc.network/', + imageUrl: XDC_TOKEN_IMAGE_URL, + }, + }, + { + chainId: CHAIN_IDS.XDC_APOTHEM_TESTNET, + nickname: XDC_APOTHEM_TESTNET_DISPLAY_NAME, + rpcUrl: `https://rpc.apothem.network`, + ticker: CURRENCY_SYMBOLS.XDC_APOTHEM_TESTNET, + rpcPrefs: { + blockExplorerUrl: 'https://explorer.apothem.network/', + imageUrl: XDC_TOKEN_IMAGE_URL, + }, + }, ]; /** diff --git a/shared/constants/swaps.ts b/shared/constants/swaps.ts index ea738d76b740..145b7ee360cc 100644 --- a/shared/constants/swaps.ts +++ b/shared/constants/swaps.ts @@ -151,7 +151,7 @@ const SWAPS_TESTNET_CHAIN_ID = '0x539'; export const SWAPS_API_V2_BASE_URL = 'https://swap.metaswap.codefi.network'; export const SWAPS_DEV_API_V2_BASE_URL = 'https://swap.dev-api.cx.metamask.io'; -export const GAS_API_BASE_URL = 'https://gas-api.metaswap.codefi.network'; +export const GAS_API_BASE_URL = 'https://gas.api.cx.metamask.io'; export const GAS_DEV_API_BASE_URL = 'https://gas.uat-api.cx.metamask.io'; const BSC_DEFAULT_BLOCK_EXPLORER_URL = 'https://bscscan.com/'; diff --git a/shared/notifications/index.js b/shared/notifications/index.js index b3b77a4cc470..95ca72f29d36 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -8,8 +8,7 @@ export const NOTIFICATION_DROP_LEDGER_FIREFOX = 25; export const NOTIFICATION_OPEN_BETA_SNAPS = 26; export const NOTIFICATION_BUY_SELL_BUTTON = 27; export const NOTIFICATION_U2F_LEDGER_LIVE = 28; -export const NOTIFICATION_STAKING_PORTFOLIO = 29; -export const NOTIFICATION_BLOCKAID_DEFAULT = 30; +export const NOTIFICATION_BLOCKAID_DEFAULT = 29; export const UI_NOTIFICATIONS = { 1: { @@ -172,14 +171,6 @@ export const UI_NOTIFICATIONS = { id: Number(NOTIFICATION_U2F_LEDGER_LIVE), date: null, }, - [NOTIFICATION_STAKING_PORTFOLIO]: { - id: Number(NOTIFICATION_STAKING_PORTFOLIO), - date: null, - image: { - src: 'images/portfolio-stake-notification-light-mode.png', - width: '100%', - }, - }, ///: BEGIN:ONLY_INCLUDE_IF(blockaid) [NOTIFICATION_BLOCKAID_DEFAULT]: { id: Number(NOTIFICATION_BLOCKAID_DEFAULT), @@ -477,17 +468,6 @@ export const getTranslatedUINotifications = ( ) : '', }, - [NOTIFICATION_STAKING_PORTFOLIO]: { - ...UI_NOTIFICATIONS[NOTIFICATION_STAKING_PORTFOLIO], - title: t('notificationsStakingPortfolioTitle'), - description: [t('notificationsStakingPortfolioDescription')], - actionText: t('notificationsStakingPortfolioActionText'), - date: UI_NOTIFICATIONS[NOTIFICATION_STAKING_PORTFOLIO].date - ? new Intl.DateTimeFormat(formattedLocale).format( - new Date(UI_NOTIFICATIONS[NOTIFICATION_STAKING_PORTFOLIO].date), - ) - : '', - }, ///: BEGIN:ONLY_INCLUDE_IF(blockaid) [NOTIFICATION_BLOCKAID_DEFAULT]: { ...UI_NOTIFICATIONS[NOTIFICATION_BLOCKAID_DEFAULT], diff --git a/test/data/mock-pending-transaction-data.json b/test/data/mock-pending-transaction-data.json index 36f1d74767fe..12575a92df6c 100644 --- a/test/data/mock-pending-transaction-data.json +++ b/test/data/mock-pending-transaction-data.json @@ -78,7 +78,7 @@ "primaryTransaction": { "id": 6854191329910881, "time": 1631558469046, - "status": "approved", + "status": "submitted", "chainId": "0x5", "loadingDefaults": false, "dappSuggestedGasFees": null, diff --git a/test/data/mock-send-state.json b/test/data/mock-send-state.json index 71bea504a325..772d806174f2 100644 --- a/test/data/mock-send-state.json +++ b/test/data/mock-send-state.json @@ -211,6 +211,99 @@ }, "selectedAccount": "cf8dace4-9439-4bd4-b3a8-88c821c8fcb3" }, + "internalAccounts": { + "accounts": { + "cf8dace4-9439-4bd4-b3a8-88c821c8fcb3": { + "address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", + "id": "cf8dace4-9439-4bd4-b3a8-88c821c8fcb3", + "metadata": { + "keyring": { + "type": "HD Key Tree" + } + }, + "name": "Test Account", + "options": {}, + "methods": [ + "personal_sign", + "eth_sendTransaction", + "eth_sign", + "eth_signTransaction", + "eth_signTypedData_v1", + "eth_signTypedData_v2", + "eth_signTypedData_v3", + "eth_signTypedData_v4" + ], + "type": "eip155:eoa" + }, + "07c2cfec-36c9-46c4-8115-3836d3ac9047": { + "address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b", + "id": "07c2cfec-36c9-46c4-8115-3836d3ac9047", + "metadata": { + "keyring": { + "type": "HD Key Tree" + } + }, + "name": "Test Account 2", + "options": {}, + "methods": [ + "personal_sign", + "eth_sendTransaction", + "eth_sign", + "eth_signTransaction", + "eth_signTypedData_v1", + "eth_signTypedData_v2", + "eth_signTypedData_v3", + "eth_signTypedData_v4" + ], + "type": "eip155:eoa" + }, + "15e69915-2a1a-4019-93b3-916e11fd432f": { + "address": "0xc42edfcc21ed14dda456aa0756c153f7985d8813", + "id": "15e69915-2a1a-4019-93b3-916e11fd432f", + "metadata": { + "keyring": { + "type": "Ledger Hardware" + } + }, + "name": "Ledger Hardware 2", + "options": {}, + "methods": [ + "personal_sign", + "eth_sendTransaction", + "eth_sign", + "eth_signTransaction", + "eth_signTypedData_v1", + "eth_signTypedData_v2", + "eth_signTypedData_v3", + "eth_signTypedData_v4" + ], + "type": "eip155:eoa" + }, + "784225f4-d30b-4e77-a900-c8bbce735b88": { + "address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b", + "id": "784225f4-d30b-4e77-a900-c8bbce735b88", + "metadata": { + "keyring": { + "type": "HD Key Tree" + } + }, + "name": "Test Account 3", + "options": {}, + "methods": [ + "personal_sign", + "eth_sendTransaction", + "eth_sign", + "eth_signTransaction", + "eth_signTypedData_v1", + "eth_signTypedData_v2", + "eth_signTypedData_v3", + "eth_signTypedData_v4" + ], + "type": "eip155:eoa" + } + }, + "selectedAccount": "cf8dace4-9439-4bd4-b3a8-88c821c8fcb3" + }, "keyrings": [ { "type": "HD Key Tree", diff --git a/test/data/mock-state.json b/test/data/mock-state.json index ef6027d39cf4..68b81623104a 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -111,7 +111,8 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": true, - "useNativeCurrencyAsPrimaryCurrency": true + "useNativeCurrencyAsPrimaryCurrency": true, + "petnamesEnabled": false }, "ensResolutionsByAddress": {}, "isAccountMenuOpen": false, diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index ce3de22b87dd..66fbaf51a73e 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -304,6 +304,7 @@ function defaultFixture() { showFiatInTestnets: false, showTestNetworks: false, useNativeCurrencyAsPrimaryCurrency: true, + petnamesEnabled: true, }, selectedAddress: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', theme: 'light', @@ -426,6 +427,7 @@ function onboardingFixture() { showFiatInTestnets: false, showTestNetworks: false, useNativeCurrencyAsPrimaryCurrency: true, + petnamesEnabled: true, }, theme: 'light', useBlockie: false, @@ -829,6 +831,14 @@ class FixtureBuilder { }); } + withPreferencesControllerPetnamesDisabled() { + return this.withPreferencesController({ + preferences: { + petnamesEnabled: false, + }, + }); + } + withAccountsController(data) { merge(this.fixture.data.AccountsController, data); return this; diff --git a/test/e2e/flask/README.md b/test/e2e/flask/README.md new file mode 100644 index 000000000000..abb07a11e910 --- /dev/null +++ b/test/e2e/flask/README.md @@ -0,0 +1,6 @@ +# Flask directory +Add `.spec.js` or `.spec.ts` files to run only in flask here. + +> [!IMPORTANT] +> Ensure to update your files in `FLASK_ONLY_TESTS` in +> [`run-all.js`](https://github.com/MetaMask/metamask-extension/blob/develop/test/e2e/run-all.js) \ No newline at end of file diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index ccf8e74fef34..7b0e97678cdb 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -149,7 +149,7 @@ async function withFixtures(options, testSuite) { }); } - console.log(`\nExecuting testcase: ${title}\n`); + console.log(`\nExecuting testcase: '${title}'\n`); await testSuite({ driver: driverProxy ?? driver, @@ -159,6 +159,11 @@ async function withFixtures(options, testSuite) { mockedEndpoint, }); + const errorsAndExceptions = driver.summarizeErrorsAndExceptions(); + if (errorsAndExceptions && failOnConsoleError) { + throw new Error(errorsAndExceptions); + } + // At this point the suite has executed successfully, so we can log out a success message // (Note: a Chrome browser error will unfortunately pop up after this success message) console.log(`\nSuccess on testcase: '${title}'\n`); @@ -685,14 +690,18 @@ const generateGanacheOptions = ({ }; }; +// Edit priority gas fee form +const editGasfeeForm = async (driver, gasLimit, gasPrice) => { + const inputs = await driver.findElements('input[type="number"]'); + const gasLimitInput = inputs[0]; + const gasPriceInput = inputs[1]; + await gasLimitInput.fill(gasLimit); + await gasPriceInput.fill(gasPrice); + await driver.clickElement({ text: 'Save', tag: 'button' }); +}; + const openActionMenuAndStartSendFlow = async (driver) => { - // TODO: Update Test when Multichain Send Flow is added - if (process.env.MULTICHAIN) { - await driver.clickElement('[data-testid="app-footer-actions-button"]'); - await driver.clickElement('[data-testid="select-action-modal-item-send"]'); - } else { - await driver.clickElement('[data-testid="eth-overview-send"]'); - } + await driver.clickElement('[data-testid="eth-overview-send"]'); }; const sendTransaction = async ( @@ -754,28 +763,16 @@ const TEST_SEED_PHRASE_TWO = // Usually happens when onboarded to make sure the state is retrieved from metamaskState properly, or after txn is made const locateAccountBalanceDOM = async (driver, ganacheServer) => { const balance = await ganacheServer.getBalance(); - if (process.env.MULTICHAIN) { - await driver.clickElement(`[data-testid="home__asset-tab"]`); - await driver.findElement({ - css: '[data-testid="token-balance-overview-currency-display"]', - text: `${balance} ETH`, - }); - } else { - await driver.findElement({ - css: '[data-testid="eth-overview__primary-currency"]', - text: `${balance} ETH`, - }); - } + await driver.findElement({ + css: '[data-testid="eth-overview__primary-currency"]', + text: `${balance} ETH`, + }); }; const WALLET_PASSWORD = 'correct horse battery staple'; async function waitForAccountRendered(driver) { - await driver.findElement( - process.env.MULTICHAIN - ? '[data-testid="token-balance-overview-currency-display"]' - : '[data-testid="eth-overview__primary-currency"]', - ); + await driver.findElement('[data-testid="eth-overview__primary-currency"]'); } /** @@ -1033,4 +1030,5 @@ module.exports = { genRandInitBal, openActionMenuAndStartSendFlow, getCleanAppState, + editGasfeeForm, }; diff --git a/test/e2e/mmi/Dockerfile b/test/e2e/mmi/Dockerfile index ac847b4c0a8f..05a15b8025a6 100644 --- a/test/e2e/mmi/Dockerfile +++ b/test/e2e/mmi/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.41.0-focal AS build +FROM mcr.microsoft.com/playwright:v1.41.1-focal AS build WORKDIR '/usr/src/app' diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index 360913879c27..fd00bd914bbf 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -1,3 +1,5 @@ +const { GAS_API_BASE_URL } = require('../../shared/constants/swaps'); + const blacklistedHosts = [ 'arbitrum-mainnet.infura.io', 'goerli.infura.io', @@ -100,6 +102,9 @@ async function setupMocking(server, testSpecificMock, { chainId }) { .forPost( 'https://arbitrum-mainnet.infura.io/v3/00000000000000000000000000000000', ) + .withJsonBodyIncluding({ + method: 'eth_chainId', + }) .thenCallback(() => { return { statusCode: 200, @@ -158,9 +163,7 @@ async function setupMocking(server, testSpecificMock, { chainId }) { }); await server - .forGet( - `https://gas-api.metaswap.codefi.network/networks/${chainId}/gasPrices`, - ) + .forGet(`${GAS_API_BASE_URL}/networks/${chainId}/gasPrices`) .thenCallback(() => { return { statusCode: 200, @@ -190,9 +193,7 @@ async function setupMocking(server, testSpecificMock, { chainId }) { }); await server - .forGet( - `https://gas-api.metaswap.codefi.network/networks/${chainId}/suggestedGasFees`, - ) + .forGet(`${GAS_API_BASE_URL}/networks/${chainId}/suggestedGasFees`) .thenCallback(() => { return { statusCode: 200, diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js index f6650ca75ce4..267305c5eac6 100644 --- a/test/e2e/run-all.js +++ b/test/e2e/run-all.js @@ -9,8 +9,6 @@ const { loadBuildTypesConfig } = require('../../development/lib/build-type'); // These tests should only be run on Flask for now. const FLASK_ONLY_TESTS = [ - 'petnames-signatures.spec', - 'petnames-transactions.spec.js', 'test-snap-txinsights-v2.spec.js', 'test-snap-namelookup.spec.js', 'test-snap-homepage.spec.js', diff --git a/test/e2e/tests/account-menu/account-details.spec.js b/test/e2e/tests/account-menu/account-details.spec.js index 8b0ae7826942..bbb0be897bee 100644 --- a/test/e2e/tests/account-menu/account-details.spec.js +++ b/test/e2e/tests/account-menu/account-details.spec.js @@ -1,4 +1,5 @@ const { strict: assert } = require('assert'); +const { LavaDomeDebug } = require('@lavamoat/lavadome-core'); const { defaultGanacheOptions, withFixtures, @@ -40,7 +41,7 @@ describe('Show account details', function () { '[data-testid="account-details-key"]', ); const key = await keyContainer.getText(); - return key; + return LavaDomeDebug.stripDistractionFromText(key); } it('should show the QR code for the account', async function () { diff --git a/test/e2e/tests/account-token-list.spec.js b/test/e2e/tests/account-token-list.spec.js index a45ae535843c..8f7b3fe9302b 100644 --- a/test/e2e/tests/account-token-list.spec.js +++ b/test/e2e/tests/account-token-list.spec.js @@ -22,11 +22,9 @@ describe('Settings', function () { await unlockWallet(driver); await driver.clickElement('[data-testid="home__asset-tab"]'); - const tokenValue = process.env.MULTICHAIN ? '0\nETH' : '0 ETH'; + const tokenValue = '0 ETH'; const tokenListAmount = await driver.findElement( - process.env.MULTICHAIN - ? '[data-testid="token-balance-overview-currency-display"]' - : '[data-testid="multichain-token-list-item-value"]', + '[data-testid="multichain-token-list-item-value"]', ); assert.equal(await tokenListAmount.getText(), tokenValue); @@ -67,11 +65,9 @@ describe('Settings', function () { ); await driver.clickElement('[data-testid="home__asset-tab"]'); - const tokenValue = process.env.MULTICHAIN ? '0\nETH' : '0 ETH'; + const tokenValue = '0 ETH'; const tokenListAmount = await driver.findElement( - process.env.MULTICHAIN - ? '[data-testid="token-balance-overview-currency-display"]' - : '[data-testid="multichain-token-list-item-value"]', + '[data-testid="multichain-token-list-item-value"]', ); assert.equal(await tokenListAmount.getText(), tokenValue); diff --git a/test/e2e/tests/add-hide-token.spec.js b/test/e2e/tests/add-hide-token.spec.js index eb18355dc56b..9b2e184ff00b 100644 --- a/test/e2e/tests/add-hide-token.spec.js +++ b/test/e2e/tests/add-hide-token.spec.js @@ -8,7 +8,7 @@ const { } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -describe('Hide token', function () { +describe('Add hide token', function () { it('hides the token when clicked', async function () { await withFixtures( { @@ -54,20 +54,22 @@ describe('Hide token', function () { await driver.clickElement({ text: 'Tokens', tag: 'button' }); - await driver.clickElement({ text: 'TST', tag: 'span' }); + await driver.clickElement({ text: 'TST', tag: 'p' }); await driver.clickElement('[data-testid="asset-options__button"]'); await driver.clickElement('[data-testid="asset-options__hide"]'); // wait for confirm hide modal to be visible - const confirmHideModal = await driver.findVisibleElement('span .modal'); + const confirmHideModal = + '[data-testid="hide-token-confirmation-modal"]'; + await driver.findVisibleElement(confirmHideModal); await driver.clickElement( '[data-testid="hide-token-confirmation__hide"]', ); // wait for confirm hide modal to be removed from DOM. - await confirmHideModal.waitForElementState('hidden'); + await driver.waitForElementNotPresent(confirmHideModal); assets = await driver.findElements('.multichain-token-list-item'); assert.equal(assets.length, 1); diff --git a/test/e2e/tests/add-multiple-tokens.spec.js b/test/e2e/tests/add-multiple-tokens.spec.js index d6d9200fe464..a1da4eac1708 100644 --- a/test/e2e/tests/add-multiple-tokens.spec.js +++ b/test/e2e/tests/add-multiple-tokens.spec.js @@ -99,7 +99,7 @@ describe('Multiple ERC20 Watch Asset', function () { // Check all three tokens have been added to the token list. const addedTokens = await driver.findElements({ - tag: 'span', + tag: 'p', text: 'TST', }); assert.equal(addedTokens.length, 3); diff --git a/test/e2e/tests/custom-rpc-history.spec.js b/test/e2e/tests/custom-rpc-history.spec.js index f05bb77a1f26..ce8ed34185b5 100644 --- a/test/e2e/tests/custom-rpc-history.spec.js +++ b/test/e2e/tests/custom-rpc-history.spec.js @@ -8,7 +8,7 @@ const { } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -describe('Stores custom RPC history', function () { +describe('Custom RPC history', function () { it(`creates first custom RPC entry`, async function () { const port = 8546; const chainId = 1338; @@ -268,47 +268,52 @@ describe('Stores custom RPC history', function () { await unlockWallet(driver); await driver.waitForElementNotPresent('.loading-overlay'); + // Click add network from network options await driver.clickElement('[data-testid="network-display"]'); - await driver.clickElement({ text: 'Add network', tag: 'button' }); - + // Open network settings page await driver.findElement('.add-network__networks-container'); - + // Click Add network manually to trigger form await driver.clickElement({ text: 'Add a network manually', tag: 'h6', }); - - // // cancel new custom rpc + // cancel new custom rpc await driver.clickElement( '.networks-tab__add-network-form-footer button.btn-secondary', ); - + // find custom network http://127.0.0.1:8545/2 + const networkItemClassName = '.networks-tab__networks-list-name'; + const customNetworkName = 'http://127.0.0.1:8545/2'; const networkListItems = await driver.findClickableElements( - '.networks-tab__networks-list-name', + networkItemClassName, ); const lastNetworkListItem = networkListItems[networkListItems.length - 1]; await lastNetworkListItem.click(); - await driver.waitForSelector({ css: '.form-field .form-field__input:nth-of-type(1)', - value: 'http://127.0.0.1:8545/2', + value: customNetworkName, }); - - await driver.clickElement('.btn-danger'); - - // wait for confirm delete modal to be visible - await driver.findVisibleElement('span .modal'); - - await driver.clickElement( - '.button.btn-danger-primary.modal-container__footer-button', + // delete custom network in a modal + await driver.clickElement('.networks-tab__network-form .btn-danger'); + await driver.findVisibleElement( + '[data-testid="confirm-delete-network-modal"]', ); + await driver.clickElement({ text: 'Delete', tag: 'button' }); + await driver.waitForElementNotPresent( + '[data-testid="confirm-delete-network-modal"]', + ); + // There's a short slot to process deleting the network, + // hence there's a need to wait for the element to be removed to guarantee the action is executed completely + await driver.waitForElementNotPresent({ + tag: 'div', + text: customNetworkName, + }); - await driver.waitForElementNotPresent('span .modal'); - + // custom network http://127.0.0.1:8545/2 is removed from network list const newNetworkListItems = await driver.findElements( - '.networks-tab__networks-list-name', + networkItemClassName, ); assert.equal(networkListItems.length - 1, newNetworkListItems.length); diff --git a/test/e2e/tests/custom-token-add-approve.spec.js b/test/e2e/tests/custom-token-add-approve.spec.js index fcc8efc35968..d6d7372dfb5e 100644 --- a/test/e2e/tests/custom-token-add-approve.spec.js +++ b/test/e2e/tests/custom-token-add-approve.spec.js @@ -5,6 +5,7 @@ const { withFixtures, openDapp, unlockWallet, + editGasfeeForm, WINDOW_TITLES, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -252,27 +253,36 @@ describe('Create token, approve token and approve token without gas', function ( '5 TST', 'Default value is not correctly set', ); + await driver.waitForSelector( + { + css: '.box--flex-direction-row > h6', + text: '0.000895 ETH', + }, + { timeout: 15000 }, + ); // editing gas fee - const editBtn = await driver.findElements({ + const editBtn = await driver.findElement({ text: 'Edit', - class: 'btn-link > h6', + tag: 'h6', }); - editBtn[1].click(); + editBtn.click(); await driver.clickElement({ text: 'Edit suggested gas fee', tag: 'button', }); - const [gasLimitInput, gasPriceInput] = await driver.findElements( - 'input[type="number"]', - ); - await gasPriceInput.clear(); - await gasPriceInput.fill('10'); - await gasLimitInput.clear(); - await gasLimitInput.fill('60001'); - await driver.clickElement({ text: 'Save', tag: 'button' }); + + await driver.waitForSelector({ + text: 'Edit priority', + }); + await driver.waitForSelector({ + text: '0.00089526 ETH', + tag: 'h1', + }); + + await editGasfeeForm(driver, '60001', '10'); await driver.waitForSelector( { diff --git a/test/e2e/tests/custom-token-send-transfer.spec.js b/test/e2e/tests/custom-token-send-transfer.spec.js index e7036227ac2b..0ba98397520f 100644 --- a/test/e2e/tests/custom-token-send-transfer.spec.js +++ b/test/e2e/tests/custom-token-send-transfer.spec.js @@ -5,22 +5,13 @@ const { switchToNotificationWindow, openDapp, unlockWallet, + editGasfeeForm, WINDOW_TITLES, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); const recipientAddress = '0x2f318C334780961FB129D2a6c30D0763d9a5C970'; -const editGasfeeCustomToken = async (driver) => { - const inputs = await driver.findElements('input[type="number"]'); - const gasLimitInput = inputs[0]; - const gasPriceInput = inputs[1]; - await gasLimitInput.clear(); - await gasLimitInput.fill('60000'); - await gasPriceInput.clear(); - await gasPriceInput.fill('10'); - await driver.clickElement({ text: 'Save', tag: 'button' }); -}; describe('Transfer custom tokens @no-mmi', function () { const smartContract = SMART_CONTRACTS.HST; @@ -92,7 +83,7 @@ describe('Transfer custom tokens @no-mmi', function () { // edit gas fee await driver.clickElement({ text: 'Details', tag: 'button' }); await driver.clickElement({ text: 'Edit', tag: 'button' }); - await editGasfeeCustomToken(driver); + await editGasfeeForm(driver, '60000', '10'); await driver.clickElement({ text: 'Confirm', tag: 'button' }); // check that transaction has completed correctly and is displayed in the activity list @@ -141,7 +132,7 @@ describe('Transfer custom tokens @no-mmi', function () { { text: 'Edit suggested gas fee', tag: 'button' }, 10000, ); - await editGasfeeCustomToken(driver); + await editGasfeeForm(driver, '60000', '10'); await driver.clickElement({ text: 'Confirm', tag: 'button' }); // in extension, check that transaction has completed correctly and is displayed in the activity list diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index e5205f485713..4f1901c65c63 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -46,6 +46,7 @@ const removedBackgroundFields = [ 'AppStateController.currentPopupId', 'AppStateController.timeoutMinutes', 'PPOMController.chainStatus.0x539.lastVisited', + 'PPOMController.versionInfo', ]; const removedUiFields = removedBackgroundFields.map(backgroundToUiField); diff --git a/test/e2e/tests/eth-sign.spec.js b/test/e2e/tests/eth-sign.spec.js index 952850cc8164..f8254b380556 100644 --- a/test/e2e/tests/eth-sign.spec.js +++ b/test/e2e/tests/eth-sign.spec.js @@ -74,7 +74,7 @@ describe('Eth sign', function () { await approveEthSign( driver, '[data-testid="page-container-footer-next"]', - '.signature-request-warning__footer__sign-button', + '[data-testid="signature-warning-sign-button"]', ); // Switch to the Dapp await driver.waitUntilXWindowHandles(2); @@ -142,14 +142,14 @@ describe('Eth sign', function () { await approveEthSign( driver, '[data-testid="page-container-footer-next"]', - '.signature-request-warning__footer__sign-button', + '[data-testid="signature-warning-sign-button"]', ); // Confirm second eth sign await approveEthSign( driver, '[data-testid="page-container-footer-next"]', - '.signature-request-warning__footer__sign-button', + '[data-testid="signature-warning-sign-button"]', ); // Switch to the Dapp diff --git a/test/e2e/tests/gas-estimates.spec.js b/test/e2e/tests/gas-estimates.spec.js index f19c16ed3455..dcb7ce706e78 100644 --- a/test/e2e/tests/gas-estimates.spec.js +++ b/test/e2e/tests/gas-estimates.spec.js @@ -6,6 +6,7 @@ const { } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { CHAIN_IDS } = require('../../../shared/constants/network'); +const { GAS_API_BASE_URL } = require('../../../shared/constants/swaps'); describe('Gas estimates generated by MetaMask', function () { const preLondonGanacheOptions = generateGanacheOptions({ @@ -53,9 +54,7 @@ describe('Gas estimates generated by MetaMask', function () { ganacheOptions: postLondonGanacheOptions, testSpecificMock: (mockServer) => { mockServer - .forGet( - 'https://gas-api.metaswap.codefi.network/networks/1337/suggestedGasFees', - ) + .forGet(`${GAS_API_BASE_URL}/networks/1337/suggestedGasFees`) .thenCallback(() => { return { json: { @@ -97,9 +96,7 @@ describe('Gas estimates generated by MetaMask', function () { ganacheOptions: postLondonGanacheOptions, testSpecificMock: (mockServer) => { mockServer - .forGet( - 'https://gas-api.metaswap.codefi.network/networks/1337/suggestedGasFees', - ) + .forGet(`${GAS_API_BASE_URL}/networks/1337/suggestedGasFees`) .thenCallback(() => { return { statusCode: 422, @@ -177,7 +174,7 @@ describe('Gas estimates generated by MetaMask', function () { testSpecificMock: (mockServer) => { mockServer .forGet( - `https://gas-api.metaswap.codefi.network/networks/${parseInt( + `${GAS_API_BASE_URL}/networks/${parseInt( CHAIN_IDS.BSC, 16, )}/gasPrices`, diff --git a/test/e2e/tests/import-flow.spec.js b/test/e2e/tests/import-flow.spec.js index 03745977a1f3..bca7398e079c 100644 --- a/test/e2e/tests/import-flow.spec.js +++ b/test/e2e/tests/import-flow.spec.js @@ -67,15 +67,19 @@ describe('Import flow @no-mmi', function () { await driver.findVisibleElement('.qr-code__wrapper'); // shows a QR code for the account - await driver.findVisibleElement('.mm-modal'); + await driver.findVisibleElement( + '[data-testid="account-details-modal"]', + ); // shows the correct account address await driver.findElement({ css: '.multichain-address-copy-button', text: '0x0Cc52...7afD3', }); - await driver.clickElement('.mm-modal button[aria-label="Close"]'); - + await driver.clickElement('button[aria-label="Close"]'); + await driver.waitForElementNotPresent( + '[data-testid="account-details-modal"]', + ); // logs out of the account await driver.clickElement( '[data-testid="account-options-menu-button"]', diff --git a/test/e2e/tests/incremental-security.spec.js b/test/e2e/tests/incremental-security.spec.js index 7c7346c52561..bc0b83c6fc55 100644 --- a/test/e2e/tests/incremental-security.spec.js +++ b/test/e2e/tests/incremental-security.spec.js @@ -19,6 +19,7 @@ describe('Incremental Security', function () { }, ], }; + it('Back up Secret Recovery Phrase from backup reminder @no-mmi', async function () { await withFixtures( { @@ -80,11 +81,15 @@ describe('Incremental Security', function () { const publicAddress = await address.getText(); // wait for account modal to be visible - const accountModal = await driver.findVisibleElement('.mm-modal'); - await driver.clickElement('.mm-modal button[aria-label="Close"]'); + await driver.findVisibleElement( + '[data-testid="account-details-modal"]', + ); + await driver.clickElement('button[aria-label="Close"]'); // wait for account modal to be removed from DOM - await accountModal.waitForElementState('hidden'); + await driver.waitForElementNotPresent( + '[data-testid="account-details-modal"]', + ); // send to current account from dapp with different provider const windowHandles = await driver.getAllWindowHandles(); @@ -128,8 +133,11 @@ describe('Incremental Security', function () { await driver.clickElement('[data-testid="secure-wallet-recommended"]'); await driver.fill('[placeholder="Password"]', WALLET_PASSWORD); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitForElementNotPresent('.mm-modal-overlay'); + await driver.waitForElementNotPresent( + '[data-testid="reveal-srp-modal"]', + ); const recoveryPhraseRevealButton = await driver.findClickableElement( '[data-testid="recovery-phrase-reveal"]', diff --git a/test/e2e/tests/localization.spec.js b/test/e2e/tests/localization.spec.js index b492428fd506..7c8c35d3d169 100644 --- a/test/e2e/tests/localization.spec.js +++ b/test/e2e/tests/localization.spec.js @@ -26,13 +26,9 @@ describe('Localization', function () { async ({ driver }) => { await unlockWallet(driver); - const secondaryBalance = process.env.MULTICHAIN - ? await driver.findElement( - '[data-testid="multichain-token-list-item-secondary-value"]', - ) - : await driver.findElement( - '[data-testid="eth-overview__secondary-currency"]', - ); + const secondaryBalance = await driver.findElement( + '[data-testid="eth-overview__secondary-currency"]', + ); const secondaryBalanceText = await secondaryBalance.getText(); const [fiatAmount, fiatUnit] = secondaryBalanceText .trim() diff --git a/test/e2e/tests/lock-account.spec.js b/test/e2e/tests/lock-account.spec.js index 6f38ac623875..a4d23ab7838f 100644 --- a/test/e2e/tests/lock-account.spec.js +++ b/test/e2e/tests/lock-account.spec.js @@ -27,11 +27,9 @@ describe('Lock and unlock', function () { await lockButton.click(); await unlockWallet(driver); - const walletBalance = process.env.MULTICHAIN - ? await driver.findElement( - '.token-balance-overview__secondary-balance', - ) - : await driver.findElement('.eth-overview__primary-balance'); + const walletBalance = await driver.findElement( + '.eth-overview__primary-balance', + ); assert.equal(/^25\s*ETH$/u.test(await walletBalance.getText()), true); }, ); diff --git a/test/e2e/tests/metamask-responsive-ui.spec.js b/test/e2e/tests/metamask-responsive-ui.spec.js index ce3e635c3ba5..2252072172a8 100644 --- a/test/e2e/tests/metamask-responsive-ui.spec.js +++ b/test/e2e/tests/metamask-responsive-ui.spec.js @@ -74,9 +74,7 @@ describe('MetaMask Responsive UI', function () { // assert balance const balance = await driver.findElement( - process.env.MULTICHAIN - ? '[data-testid="token-balance-overview-currency-display"]' - : '[data-testid="eth-overview__primary-currency"]', + '[data-testid="eth-overview__primary-currency"]', ); assert.ok(/^0\sETH$/u.test(await balance.getText())); }, diff --git a/test/e2e/tests/metrics/signature-approved.spec.js b/test/e2e/tests/metrics/signature-approved.spec.js index bde3c6519d7f..2386435ec638 100644 --- a/test/e2e/tests/metrics/signature-approved.spec.js +++ b/test/e2e/tests/metrics/signature-approved.spec.js @@ -268,7 +268,7 @@ describe('Signature Approved Event @no-mmi', function () { await driver.delay(regularDelayMs); await driver.clickElement('[data-testid="page-container-footer-next"]'); await driver.clickElement( - '.signature-request-warning__footer__sign-button', + '[data-testid="signature-warning-sign-button"]', ); const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { diff --git a/test/e2e/tests/metrics/swaps.spec.js b/test/e2e/tests/metrics/swaps.spec.js index 1e7b1120a307..594c23d5ea2d 100644 --- a/test/e2e/tests/metrics/swaps.spec.js +++ b/test/e2e/tests/metrics/swaps.spec.js @@ -20,6 +20,7 @@ const { MetaMetricsEventCategory, MetaMetricsEventName, } = require('../../../../shared/constants/metametrics'); +const { GAS_API_BASE_URL } = require('../../../../shared/constants/swaps'); const { TOKENS_API_MOCK_RESULT, TOP_ASSETS_API_MOCK_RESULT, @@ -59,7 +60,7 @@ async function mockSegmentAndMetaswapRequests(mockServer) { json: AGGREGATOR_METADATA_API_MOCK_RESULT, })), await mockServer - .forGet('https://gas-api.metaswap.codefi.network/networks/1/gasPrices') + .forGet(`${GAS_API_BASE_URL}/networks/1/gasPrices`) .thenCallback(() => ({ statusCode: 200, json: GAS_PRICE_API_MOCK_RESULT, diff --git a/test/e2e/tests/migrate-old-vault.spec.js b/test/e2e/tests/migrate-old-vault.spec.js index e92f7c47de92..e9c801367041 100644 --- a/test/e2e/tests/migrate-old-vault.spec.js +++ b/test/e2e/tests/migrate-old-vault.spec.js @@ -29,11 +29,9 @@ describe('Migrate vault with old encryption', function () { await unlock(driver); await lock(driver); await unlock(driver); - const walletBalance = process.env.MULTICHAIN - ? await driver.findElement( - '.token-balance-overview__secondary-balance', - ) - : await driver.findElement('.eth-overview__primary-balance'); + const walletBalance = await driver.findElement( + '.eth-overview__primary-balance', + ); assert.equal(/^25\s*ETH$/u.test(await walletBalance.getText()), true); }, diff --git a/test/e2e/tests/network-error.spec.js b/test/e2e/tests/network-error.spec.js index b7115144b957..e8ddfff7d3bd 100644 --- a/test/e2e/tests/network-error.spec.js +++ b/test/e2e/tests/network-error.spec.js @@ -6,13 +6,12 @@ const { generateGanacheOptions, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); +const { GAS_API_BASE_URL } = require('../../../shared/constants/swaps'); describe('Gas API fallback', function () { async function mockGasApiDown(mockServer) { await mockServer - .forGet( - 'https://gas-api.metaswap.codefi.network/networks/1337/suggestedGasFees', - ) + .forGet(`${GAS_API_BASE_URL}/networks/1337/suggestedGasFees`) .always() .thenCallback(() => { return { diff --git a/test/e2e/tests/onboarding.spec.js b/test/e2e/tests/onboarding.spec.js index 173ff72ff072..51a1be758c59 100644 --- a/test/e2e/tests/onboarding.spec.js +++ b/test/e2e/tests/onboarding.spec.js @@ -117,12 +117,11 @@ describe('MetaMask onboarding @no-mmi', function () { await driver.clickElement('[data-testid="metametrics-no-thanks"]'); - const dropdowns = await driver.findElements('select'); - const dropdownElement = dropdowns[1]; - await dropdownElement.click(); - const options = await dropdownElement.findElements( - By.tagName('option'), + const dropdownElement = await driver.findElement( + '.import-srp__number-of-words-dropdown', ); + await dropdownElement.click(); + const options = await dropdownElement.findElements(By.css('option')); const iterations = options.length; @@ -281,6 +280,7 @@ describe('MetaMask onboarding @no-mmi', function () { tag: 'button', }); + await driver.waitForSelector('[data-testid="add-network-modal"]'); const [ networkNameField, networkUrlField, @@ -293,7 +293,9 @@ describe('MetaMask onboarding @no-mmi', function () { await currencySymbolField.sendKeys(currencySymbol); await driver.clickElement({ text: 'Save', tag: 'button' }); - await driver.waitForElementNotPresent('span .modal'); + await driver.waitForElementNotPresent( + '[data-testid="add-network-modal"]', + ); await driver.clickElement({ text: 'Done', tag: 'button' }); // After login, check that notification message for added network is displayed diff --git a/test/e2e/flask/petnames-helpers.js b/test/e2e/tests/petnames-helpers.js similarity index 100% rename from test/e2e/flask/petnames-helpers.js rename to test/e2e/tests/petnames-helpers.js diff --git a/test/e2e/flask/petnames-signatures.spec.js b/test/e2e/tests/petnames-signatures.spec.js similarity index 96% rename from test/e2e/flask/petnames-signatures.spec.js rename to test/e2e/tests/petnames-signatures.spec.js index b9c513ef58d8..7960f068766c 100644 --- a/test/e2e/flask/petnames-signatures.spec.js +++ b/test/e2e/tests/petnames-signatures.spec.js @@ -167,7 +167,9 @@ describe('Petnames - Signatures', function () { ); }); - it('can propose names using installed snaps', async function () { + // TODO(dbrans): Re-enable this test when name-lookup endowment is in stable. + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('can propose names using installed snaps', async function () { await withFixtures( { dapp: true, diff --git a/test/e2e/flask/petnames-transactions.spec.js b/test/e2e/tests/petnames-transactions.spec.js similarity index 93% rename from test/e2e/flask/petnames-transactions.spec.js rename to test/e2e/tests/petnames-transactions.spec.js index bee9caa78e87..f37c38785a26 100644 --- a/test/e2e/flask/petnames-transactions.spec.js +++ b/test/e2e/tests/petnames-transactions.spec.js @@ -85,6 +85,10 @@ describe('Petnames - Transactions', function () { }); it('can save petnames for addresses in wallet send transactions', async function () { + // TODO: Update Test when Multichain Send Flow is added. + if (process.env.MULTICHAIN) { + return; + } await withFixtures( { fixtures: new FixtureBuilder() @@ -93,13 +97,15 @@ describe('Petnames - Transactions', function () { sendHexData: true, }, }) + .withNoNames() .build(), ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), }, async ({ driver }) => { await unlockWallet(driver); - createWalletSendTransaction(driver, ADDRESS_MOCK); + await createWalletSendTransaction(driver, ADDRESS_MOCK); + await expectName(driver, ABBREVIATED_ADDRESS_MOCK, false); // Test custom name. await saveName( diff --git a/test/e2e/tests/portfolio-site.spec.js b/test/e2e/tests/portfolio-site.spec.js index 41c6c29528be..cd571ee5d7cd 100644 --- a/test/e2e/tests/portfolio-site.spec.js +++ b/test/e2e/tests/portfolio-site.spec.js @@ -36,11 +36,7 @@ describe('Portfolio site', function () { await unlockWallet(driver); // Click Portfolio site - if (process.env.MULTICHAIN) { - await driver.clickElement('[data-testid="token-balance-portfolio"]'); - } else { - await driver.clickElement('[data-testid="eth-overview-portfolio"]'); - } + await driver.clickElement('[data-testid="eth-overview-portfolio"]'); await driver.waitUntilXWindowHandles(2); const windowHandles = await driver.getAllWindowHandles(); await driver.switchToWindowWithTitle('E2E Test Page', windowHandles); diff --git a/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js b/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js new file mode 100644 index 000000000000..88bd449968f8 --- /dev/null +++ b/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js @@ -0,0 +1,151 @@ +const { strict: assert } = require('assert'); +const FixtureBuilder = require('../fixture-builder'); +const { mockServerJsonRpc } = require('../mock-server-json-rpc'); +const { + WINDOW_TITLES, + defaultGanacheOptions, + openDapp, + unlockWallet, + withFixtures, +} = require('../helpers'); + +async function mockInfura(mockServer) { + await mockServerJsonRpc(mockServer, [ + ['eth_blockNumber'], + ['eth_call'], + ['eth_estimateGas'], + ['eth_feeHistory'], + ['eth_gasPrice'], + ['eth_getBalance'], + ['eth_getBlockByNumber'], + ['eth_getCode'], + ['eth_getTransactionCount'], + ]); +} + +async function mockInfuraWithMaliciousResponses(mockServer) { + await mockInfura(mockServer); + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'debug_traceCall', + params: [{ accessList: [], data: '0x00000000' }], + }) + .thenCallback(async (req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: (await req.body.getJson()).id, + error: { + message: + 'The method debug_traceCall does not exist/is not available', + }, + }, + }; + }); +} + +describe('PPOM Blockaid Alert - Multiple Networks Support @no-mmi', function () { + it('should show banner alert after switchinig to another supported network', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfuraWithMaliciousResponses, + title: this.test.fullTitle(), + }, + + async ({ driver }) => { + const expectedTitle = 'This is a deceptive request'; + const expectedDescriptionMainnet = + 'If you approve this request, you might lose your assets.'; + + const expectedDescriptionArbitrum = + 'If you approve this request, a third party known for scams will take all your assets.'; + + await unlockWallet(driver); + await openDapp(driver); + + // Click TestDapp button to send JSON-RPC request + await driver.clickElement('#maliciousTradeOrder'); + + // Wait for confirmation pop-up + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const bannerAlertSelector = + '[data-testid="security-provider-banner-alert"]'; + + let bannerAlertFoundByTitle = await driver.findElement({ + css: bannerAlertSelector, + text: expectedTitle, + }); + let bannerAlertText = await bannerAlertFoundByTitle.getText(); + + assert( + bannerAlertFoundByTitle, + `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: approval_farming\n`, + ); + assert( + bannerAlertText.includes(expectedDescriptionMainnet), + `Unexpected banner alert description. Expected: ${expectedDescriptionMainnet} \nExpected reason: approval_farming\n`, + ); + + await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // switch network to arbitrum + await driver.clickElement('[data-testid="network-display"]'); + + await driver.clickElement({ tag: 'button', text: 'Add network' }); + await driver.clickElement({ + tag: 'button', + text: 'Add', + }); + + await driver.clickElement({ tag: 'a', text: 'View all details' }); + + await driver.clickElement({ tag: 'button', text: 'Close' }); + await driver.clickElement({ tag: 'button', text: 'Approve' }); + await driver.clickElement({ + tag: 'h6', + text: 'Switch to Arbitrum One', + }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + // Click TestDapp button to send JSON-RPC request + await driver.clickElement('#maliciousRawEthButton'); + + // Wait for confirmation pop-up + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + bannerAlertFoundByTitle = await driver.findElement({ + css: bannerAlertSelector, + text: expectedTitle, + }); + bannerAlertText = await bannerAlertFoundByTitle.getText(); + + assert( + bannerAlertFoundByTitle, + `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: raw_native_token_transfer\n`, + ); + assert( + bannerAlertText.includes(expectedDescriptionArbitrum), + `Unexpected banner alert description. Expected: ${expectedDescriptionArbitrum} \nExpected reason: raw_native_token_transfer\n`, + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/send-eth.spec.js b/test/e2e/tests/send-eth.spec.js index 3a65dcedb39c..52bf8fe8dae7 100644 --- a/test/e2e/tests/send-eth.spec.js +++ b/test/e2e/tests/send-eth.spec.js @@ -7,6 +7,7 @@ const { logInWithBalanceValidation, openActionMenuAndStartSendFlow, unlockWallet, + editGasfeeForm, WINDOW_TITLES, defaultGanacheOptions, } = require('../helpers'); @@ -208,14 +209,16 @@ describe('Send ETH', function () { await openActionMenuAndStartSendFlow(driver); // choose to scan via QR code await driver.clickElement('[data-testid="ens-qr-scan-button"]'); - await driver.findVisibleElement('.modal'); + await driver.findVisibleElement('[data-testid="qr-scanner-modal"]'); // cancel action will close the dialog and shut down camera initialization await driver.waitForSelector({ css: '.qr-scanner__error', text: "We couldn't access your camera. Please give it another try.", }); await driver.clickElement({ text: 'Cancel', tag: 'button' }); - await driver.waitForElementNotPresent('.modal'); + await driver.waitForElementNotPresent( + '[data-testid="qr-scanner-modal"]', + ); }, ); }); @@ -256,14 +259,9 @@ describe('Send ETH', function () { }); await driver.waitForSelector({ text: '0.00021 ETH', + tag: 'h1', }); - const inputs = await driver.findElements('input[type="number"]'); - const gasPriceInput = inputs[1]; - await gasPriceInput.fill('100'); - await driver.waitForSelector({ - text: '0.0021 ETH', - }); - await driver.clickElement({ text: 'Save', tag: 'button' }); + await editGasfeeForm(driver, '21000', '100'); await driver.waitForSelector({ css: '.transaction-detail-item:nth-of-type(1) h6:nth-of-type(2)', text: '0.0021 ETH', @@ -349,9 +347,7 @@ describe('Send ETH', function () { // Identify the transaction in the transactions list await driver.waitForSelector( - process.env.MULTICHAIN - ? '[data-testid="token-balance-overview-currency-display"]' - : '[data-testid="eth-overview__primary-currency"]', + '[data-testid="eth-overview__primary-currency"]', ); await driver.clickElement('[data-testid="home__activity-tab"]'); @@ -401,6 +397,7 @@ describe('Send ETH', function () { sendHexData: true, }, }) + .withPreferencesControllerPetnamesDisabled() .build(), ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), diff --git a/test/e2e/tests/send-hex-address.spec.js b/test/e2e/tests/send-hex-address.spec.js index 252df9c67d64..bf6c2cb084d1 100644 --- a/test/e2e/tests/send-hex-address.spec.js +++ b/test/e2e/tests/send-hex-address.spec.js @@ -14,7 +14,9 @@ describe('Send ETH to a 40 character hexadecimal address', function () { it('should ensure the address is prefixed with 0x when pasted and should send ETH to a valid hexadecimal address', async function () { await withFixtures( { - fixtures: new FixtureBuilder().build(), + fixtures: new FixtureBuilder() + .withPreferencesControllerPetnamesDisabled() + .build(), ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), failOnConsoleError: false, @@ -59,7 +61,9 @@ describe('Send ETH to a 40 character hexadecimal address', function () { it('should ensure the address is prefixed with 0x when typed and should send ETH to a valid hexadecimal address', async function () { await withFixtures( { - fixtures: new FixtureBuilder().build(), + fixtures: new FixtureBuilder() + .withPreferencesControllerPetnamesDisabled() + .build(), ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), failOnConsoleError: false, @@ -109,7 +113,10 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { await withFixtures( { dapp: true, - fixtures: new FixtureBuilder().withTokensControllerERC20().build(), + fixtures: new FixtureBuilder() + .withPreferencesControllerPetnamesDisabled() + .withTokensControllerERC20() + .build(), ganacheOptions: defaultGanacheOptions, smartContract, title: this.test.fullTitle(), @@ -171,7 +178,10 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { await withFixtures( { dapp: true, - fixtures: new FixtureBuilder().withTokensControllerERC20().build(), + fixtures: new FixtureBuilder() + .withPreferencesControllerPetnamesDisabled() + .withTokensControllerERC20() + .build(), ganacheOptions: defaultGanacheOptions, smartContract, title: this.test.fullTitle(), 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 e596d3057b6a..b13150ab0ea7 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 @@ -3,10 +3,7 @@ "pinnedAccountList": {}, "hiddenAccountList": {} }, - "AccountTracker": { - "accounts": "object", - "accountsByChainId": "object" - }, + "AccountTracker": { "accounts": "object", "accountsByChainId": "object" }, "AccountsController": { "internalAccounts": { "accounts": "object", "selectedAccount": "string" } }, @@ -55,14 +52,14 @@ }, "CronjobController": { "jobs": "object" }, "CurrencyController": { - "currentCurrency": "usd", "currencyRates": { "ETH": { "conversionDate": "number", "conversionRate": 1700, "usdConversionRate": 1700 } - } + }, + "currentCurrency": "usd" }, "DecryptMessageController": { "unapprovedDecryptMsgs": "object", @@ -74,8 +71,8 @@ }, "EnsController": { "ensResolutionsByAddress": "object" }, "GasFeeController": { - "gasFeeEstimates": {}, "gasFeeEstimatesByChainId": {}, + "gasFeeEstimates": {}, "estimatedGasFeeTimeBounds": {}, "gasEstimateType": "none" }, @@ -90,10 +87,11 @@ "metaMetricsId": "fake-metrics-id", "eventsBeforeMetricsOptIn": "object", "traits": "object", + "previousUserTraits": "object", "fragments": "object", - "segmentApiCalls": "object", - "previousUserTraits": "object" + "segmentApiCalls": "object" }, + "NameController": { "names": "object", "nameSources": "object" }, "NetworkController": { "selectedNetworkClientId": "string", "providerConfig": { @@ -114,7 +112,7 @@ "networkConfigurations": "object" }, "NetworkOrderController": { - "orderedNetworkList": { "0": "string", "1": "string", "2": "string" } + "orderedNetworkList": { "0": "object", "1": "object", "2": "object" } }, "NftController": { "allNftContracts": "object", @@ -129,11 +127,8 @@ "onboardingTabs": "object" }, "PPOMController": { - "versionInfo": {}, "storageMetadata": {}, - "chainStatus": { - "0x539": { "chainId": "0x539", "dataFetched": false, "versionInfo": [] } - }, + "chainStatus": { "0x539": { "chainId": "0x539", "versionInfo": [] } }, "versionFileETag": "string" }, "PermissionController": { "subjects": "object" }, @@ -148,12 +143,12 @@ "dismissSeedBackUpReminder": true, "disabledRpcMethodPreferences": { "eth_sign": false }, "useMultiAccountBalanceChecker": true, - "useRequestQueue": false, "useSafeChainsListValidation": "boolean", "useTokenDetection": false, "useNftDetection": false, "use4ByteResolution": true, "useCurrencyRateCheck": true, + "useRequestQueue": false, "openSeaEnabled": false, "securityAlertsEnabled": "boolean", "addSnapAccountEnabled": "boolean", @@ -170,7 +165,8 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": false, - "useNativeCurrencyAsPrimaryCurrency": true + "useNativeCurrencyAsPrimaryCurrency": true, + "petnamesEnabled": true }, "ipfsGateway": "string", "useAddressBarEnsResolution": true, @@ -180,12 +176,11 @@ "theme": "light", "snapsAddSnapAccountModalDismissed": "boolean", "isLineaMainnetReleased": true, + "useExternalNameSources": "boolean", "selectedAddress": "string" }, "SelectedNetworkController": { - "domains": { - "metamask": "networkConfigurationId" - }, + "domains": { "metamask": "networkConfigurationId" }, "perDomainNetwork": "boolean" }, "SignatureController": { @@ -256,11 +251,9 @@ "allDetectedTokens": {} }, "TxController": { - "lastFetchedBlockNumbers": "object", "methodData": "object", - "transactions": "object" + "transactions": "object", + "lastFetchedBlockNumbers": "object" }, - "UserOperationController": { - "userOperations": "object" - } + "UserOperationController": { "userOperations": "object" } } 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 49e3c55fd957..cd5d35c09c5b 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 @@ -19,11 +19,9 @@ "addressBook": "object", "contractExchangeRates": "object", "confirmationExchangeRates": {}, - "contractExchangeRatesByChainId": "object", "pendingTokens": "object", "customNonceValue": "", "useBlockie": false, - "useRequestQueue": false, "featureFlags": {}, "welcomeScreenSeen": false, "currentLocale": "en", @@ -32,7 +30,8 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": false, - "useNativeCurrencyAsPrimaryCurrency": true + "useNativeCurrencyAsPrimaryCurrency": true, + "petnamesEnabled": true }, "firstTimeFlowType": "import", "completedOnboarding": true, @@ -40,6 +39,22 @@ "use4ByteResolution": true, "participateInMetaMetrics": true, "nextNonce": null, + "currencyRates": { + "ETH": { + "conversionDate": "number", + "conversionRate": 1300, + "usdConversionRate": 1300 + } + }, + "providerConfig": { + "chainId": "0x539", + "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", + "ticker": "ETH", + "type": "rpc", + "id": "networkConfigurationId" + }, "internalAccounts": { "accounts": "object", "selectedAccount": "string" }, "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, @@ -69,15 +84,6 @@ "previousMigrationVersion": 0, "currentMigrationVersion": "number", "selectedNetworkClientId": "string", - "providerConfig": { - "chainId": "0x539", - "nickname": "Localhost 8545", - "rpcPrefs": "object", - "rpcUrl": "string", - "ticker": "ETH", - "type": "rpc", - "id": "networkConfigurationId" - }, "networksMetadata": { "networkConfigurationId": { "EIPS": { "1559": false }, @@ -94,6 +100,7 @@ "useTokenDetection": false, "useNftDetection": false, "useCurrencyRateCheck": true, + "useRequestQueue": false, "openSeaEnabled": false, "securityAlertsEnabled": "boolean", "addSnapAccountEnabled": "boolean", @@ -109,21 +116,15 @@ "theme": "light", "snapsAddSnapAccountModalDismissed": "boolean", "isLineaMainnetReleased": true, + "useExternalNameSources": "boolean", "selectedAddress": "string", "metaMetricsId": "fake-metrics-id", "eventsBeforeMetricsOptIn": "object", "traits": "object", + "previousUserTraits": "object", "fragments": "object", "segmentApiCalls": "object", - "previousUserTraits": "object", "currentCurrency": "usd", - "currencyRates": { - "ETH": { - "conversionDate": "number", - "conversionRate": 1300, - "usdConversionRate": 1300 - } - }, "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, "unconnectedAccountAlertShownOrigins": "object", "web3ShimUsageOrigins": "object", @@ -134,11 +135,11 @@ "permissionActivityLog": "object", "subjectMetadata": "object", "announcements": "object", - "orderedNetworkList": { "0": "string", "1": "string", "2": "string" }, + "orderedNetworkList": { "0": "object", "1": "object", "2": "object" }, "pinnedAccountList": {}, "hiddenAccountList": {}, - "gasFeeEstimates": {}, "gasFeeEstimatesByChainId": {}, + "gasFeeEstimates": {}, "estimatedGasFeeTimeBounds": {}, "gasEstimateType": "none", "tokenList": "object", @@ -158,11 +159,11 @@ "allNftContracts": "object", "allNfts": "object", "ignoredNfts": "object", - "domains": { - "metamask": "networkConfigurationId" - }, + "domains": { "metamask": "networkConfigurationId" }, "perDomainNetwork": "boolean", "logs": "object", + "methodData": "object", + "lastFetchedBlockNumbers": "object", "snaps": "object", "snapStates": "object", "unencryptedSnapStates": "object", @@ -170,8 +171,12 @@ "database": "object", "lastUpdated": "object", "notifications": "object", + "names": "object", + "nameSources": "object", + "userOperations": "object", "accounts": "object", "accountsByChainId": "object", + "contractExchangeRatesByChainId": "object", "unapprovedDecryptMsgs": "object", "unapprovedDecryptMsgCount": 0, "unapprovedEncryptionPublicKeyMsgs": "object", @@ -212,15 +217,9 @@ "pendingApprovals": "object", "pendingApprovalCount": "number", "approvalFlows": "object", - "lastFetchedBlockNumbers": "object", - "methodData": "object", - "versionInfo": {}, "storageMetadata": {}, - "chainStatus": { - "0x539": { "chainId": "0x539", "dataFetched": false, "versionInfo": [] } - }, - "versionFileETag": "string", - "userOperations": "object" + "chainStatus": { "0x539": { "chainId": "0x539", "versionInfo": [] } }, + "versionFileETag": "string" }, "send": "object", "swaps": "object", diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json index 33860539633f..d7cb80ec6389 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -99,18 +99,19 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": false, - "useNativeCurrencyAsPrimaryCurrency": true + "useNativeCurrencyAsPrimaryCurrency": true, + "petnamesEnabled": true }, "selectedAddress": "string", "theme": "light", "useBlockie": false, - "useRequestQueue": false, "useNftDetection": false, "useNonceField": false, "usePhishDetect": true, "useTokenDetection": false, "useCurrencyRateCheck": true, - "useMultiAccountBalanceChecker": true + "useMultiAccountBalanceChecker": true, + "useRequestQueue": false }, "SmartTransactionsController": { "smartTransactionsState": { @@ -128,9 +129,7 @@ "ignoredTokens": "object", "tokens": "object" }, - "TransactionController": { - "transactions": "object" - }, + "TransactionController": { "transactions": "object" }, "config": "object", "firstTimeInfo": "object" } diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json index 94a686128ee7..68190850c6f3 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -99,7 +99,8 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": false, - "useNativeCurrencyAsPrimaryCurrency": true + "useNativeCurrencyAsPrimaryCurrency": true, + "petnamesEnabled": true }, "selectedAddress": "string", "theme": "light", diff --git a/test/e2e/tests/swaps/shared.js b/test/e2e/tests/swaps/shared.js index 7997c7b3c134..2c99d1ccf12f 100644 --- a/test/e2e/tests/swaps/shared.js +++ b/test/e2e/tests/swaps/shared.js @@ -18,12 +18,7 @@ const withFixturesOptions = { }; const buildQuote = async (driver, options) => { - if (process.env.MULTICHAIN) { - await driver.clickElement('[data-testid="app-footer-actions-button"]'); - await driver.clickElement('[data-testid="select-action-modal-item-swap"]'); - } else { - await driver.clickElement('[data-testid="token-overview-button-swap"]'); - } + await driver.clickElement('[data-testid="token-overview-button-swap"]'); await driver.fill( 'input[data-testid="prepare-swap-page-from-token-amount"]', options.amount, diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 90f75860fd6b..e98f068f6d5e 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -32,11 +32,26 @@ function wrapElementWithAPI(element, driver) { element.press = (key) => element.sendKeys(key); element.fill = async (input) => { // The 'fill' method in playwright replaces existing input + await driver.wait(until.elementIsVisible(element)); + + // Try 2 ways to clear input fields, first try with clear() method + // Use keyboard simulation if the input field is not empty await element.sendKeys( Key.chord(driver.Key.MODIFIER, 'a', driver.Key.BACK_SPACE), ); + // If previous methods fail, use Selenium's actions to select all text and replace it with the expected value + if ((await element.getProperty('value')) !== '') { + await driver.driver + .actions() + .click(element) + .keyDown(driver.Key.MODIFIER) + .sendKeys('a') + .keyUp(driver.Key.MODIFIER) + .perform(); + } await element.sendKeys(input); }; + element.waitForElementState = async (state, timeout) => { switch (state) { case 'hidden': @@ -54,6 +69,36 @@ function wrapElementWithAPI(element, driver) { return wrapElementWithAPI(newElement, driver); }; + // We need to hold a pointer to the original click() method so that we can call it in the replaced click() method + if (!element.originalClick) { + element.originalClick = element.click; + } + + // This special click() method waits for the loading overlay to disappear before clicking + element.click = async () => { + try { + await element.originalClick(); + } catch (e) { + if (e.name === 'ElementClickInterceptedError') { + if (e.message.includes('
')) { + // Wait for the loading overlay to disappear and try again + await driver.wait( + until.elementIsNotPresent(By.css('.loading-overlay')), + ); + } + if (e.message.includes('