From 00d155ce2fc3a176d5bbfc6643f6fe6af1563295 Mon Sep 17 00:00:00 2001 From: Danica Shen Date: Thu, 24 Aug 2023 11:27:42 +0100 Subject: [PATCH] feat(878): implement network txn toggle and new style (#20363) * feat(878): implement new incoming transaction toggle networks for setting and onboarding * Update state snapshots * feat(878): change gaps, migration types based on comment --------- Co-authored-by: Mark Stacey --- .eslintrc.js | 2 + .mocharc.js | 1 + .storybook/test-data.js | 7 + app/_locales/de/messages.json | 4 - app/_locales/el/messages.json | 4 - app/_locales/en/messages.json | 4 - app/_locales/es/messages.json | 4 - app/_locales/es_419/messages.json | 8 - app/_locales/fr/messages.json | 4 - app/_locales/hi/messages.json | 4 - app/_locales/id/messages.json | 4 - app/_locales/ja/messages.json | 4 - app/_locales/ko/messages.json | 4 - app/_locales/pt/messages.json | 4 - app/_locales/pt_BR/messages.json | 4 - app/_locales/ru/messages.json | 4 - app/_locales/tl/messages.json | 4 - app/_locales/tr/messages.json | 4 - app/_locales/vi/messages.json | 4 - app/_locales/zh_CN/messages.json | 4 - app/scripts/controllers/detect-tokens.test.js | 1 + .../controllers/mmi-controller.test.js | 1 + app/scripts/controllers/preferences.js | 46 +- app/scripts/controllers/preferences.test.js | 344 ++++----- app/scripts/controllers/transactions/index.js | 5 +- app/scripts/lib/backup.test.js | 1 - app/scripts/metamask-controller.js | 9 +- app/scripts/metamask-controller.test.js | 12 +- app/scripts/migrations/096.test.ts | 141 ++++ app/scripts/migrations/096.ts | 84 +++ app/scripts/migrations/index.js | 2 + jest.config.js | 2 + test/data/mock-send-state.json | 4 +- test/data/mock-state.json | 14 +- test/e2e/fixture-builder.js | 9 +- test/e2e/restore/MetaMaskUserData.json | 4 +- test/e2e/tests/onboarding.spec.js | 41 +- ...rs-after-init-opt-in-background-state.json | 3 +- .../errors-after-init-opt-in-ui-state.json | 3 +- ...s-before-init-opt-in-background-state.json | 2 +- .../errors-before-init-opt-in-ui-state.json | 2 +- .../incoming-transaction-toggle.test.js.snap | 9 +- .../incoming-transaction-toggle.stories.tsx | 26 + .../incoming-transaction-toggle.test.js | 74 +- .../incoming-transaction-toggle.tsx | 14 +- .../incoming-trasaction-toggle/mock-data.ts | 73 ++ .../transaction-alerts.stories.js | 1 - .../network-list-item/network-list-item.js | 19 +- ui/components/ui/toggle-button/index.scss | 1 - ui/helpers/utils/accounts.js | 19 + ui/helpers/utils/accounts.test.js | 106 ++- ui/helpers/utils/settings-search.test.js | 2 - .../onboarding-flow/onboarding-flow.test.js | 5 +- .../privacy-settings/index.scss | 2 - .../privacy-settings/privacy-settings.js | 45 +- .../privacy-settings/privacy-settings.test.js | 62 +- .../privacy-settings/setting.js | 7 +- .../advanced-tab.component.test.js.snap | 10 +- .../advanced-tab/advanced-tab.component.js | 5 + ui/pages/settings/index.scss | 2 - .../__snapshots__/security-tab.test.js.snap | 683 +++++++++++++----- .../security-tab/security-tab.component.js | 223 +++--- .../security-tab/security-tab.container.js | 15 +- .../security-tab/security-tab.test.js | 5 - ...nonce-sorted-transactions-selector.test.js | 6 +- ui/selectors/transactions.js | 5 +- ui/selectors/transactions.test.js | 12 +- ui/store/actions.ts | 21 +- .../institutional/institution-actions.test.js | 12 +- 69 files changed, 1411 insertions(+), 865 deletions(-) create mode 100644 app/scripts/migrations/096.test.ts create mode 100644 app/scripts/migrations/096.ts create mode 100644 ui/components/app/incoming-trasaction-toggle/incoming-transaction-toggle.stories.tsx create mode 100644 ui/components/app/incoming-trasaction-toggle/mock-data.ts diff --git a/.eslintrc.js b/.eslintrc.js index 8244ca4ab..e09e9badb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -239,6 +239,7 @@ module.exports = { 'app/scripts/controllers/app-state.test.js', 'app/scripts/controllers/mmi-controller.test.js', 'app/scripts/controllers/permissions/**/*.test.js', + 'app/scripts/controllers/preferences.test.js', 'app/scripts/lib/**/*.test.js', 'app/scripts/migrations/*.test.js', 'app/scripts/platforms/*.test.js', @@ -268,6 +269,7 @@ module.exports = { 'app/scripts/controllers/app-state.test.js', 'app/scripts/controllers/mmi-controller.test.js', 'app/scripts/controllers/permissions/**/*.test.js', + 'app/scripts/controllers/preferences.test.js', 'app/scripts/lib/**/*.test.js', 'app/scripts/migrations/*.test.js', 'app/scripts/platforms/*.test.js', diff --git a/.mocharc.js b/.mocharc.js index 5728dbaca..5998b524e 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -8,6 +8,7 @@ module.exports = { './app/scripts/controllers/app-state.test.js', './app/scripts/controllers/permissions/**/*.test.js', './app/scripts/controllers/mmi-controller.test.js', + './app/scripts/controllers/preferences.test.js', './app/scripts/constants/error-utils.test.js', './development/fitness-functions/**/*.test.ts', './test/e2e/helpers.test.js', diff --git a/.storybook/test-data.js b/.storybook/test-data.js index 4b60d17c9..7d0cbaff4 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -2,6 +2,7 @@ import { draftTransactionInitialState } from '../ui/ducks/send'; import { KeyringType } from '../shared/constants/keyring'; import { NetworkType } from '@metamask/controller-utils'; import { NetworkStatus } from '@metamask/network-controller'; +import { CHAIN_IDS } from '../shared/constants/network'; const state = { invalidCustomNetwork: { @@ -529,6 +530,12 @@ const state = { preferences: { useNativeCurrencyAsPrimaryCurrency: true, }, + incomingTransactionsPreferences: { + [CHAIN_IDS.MAINNET]: true, + [CHAIN_IDS.GOERLI]: false, + [CHAIN_IDS.OPTIMISM_TESTNET]: false, + [CHAIN_IDS.AVALANCHE_TESTNET]: true, + }, firstTimeFlowType: 'create', completedOnboarding: true, knownMethodData: { diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 5832c1c4c..e27188df6 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "Ihre MetaMask Installation ist abgeschlossen!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "Die Anzeige der eingehenden Transaktionen in Ihrer Wallet beruht auf der Kommunikation mit $1. Etherscan hat Zugriff auf Ihre Ethereum-Adresse und Ihre IP-Adresse. $2 anzeigen.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Phishing-Warnungen basieren auf der Kommunikation mit $1. jsDeliver hat Zugriff auf Ihre IP-Adresse. $2 ansehen.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 5586e6c95..5b26bf1ef 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "Η εγκατάσταση του MetaMask ολοκληρώθηκε!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "Η εμφάνιση των εισερχόμενων συναλλαγών στο πορτοφόλι σας βασίζεται σε επικοινωνία με το $1. Το Etherscan θα έχει πρόσβαση στη διεύθυνση Ethereum σας και τη διεύθυνση IP σας. Δείτε $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Οι ειδοποιήσεις ανίχνευσης Απάτης Ηλεκτρονικού Ψαρέματος βασίζονται στην επικοινωνία με το $1. Το jsDeliver θα έχει πρόσβαση στη διεύθυνση IP σας. Δείτε $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 0c9e060a3..b17f89e73 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -3022,10 +3022,6 @@ "onboardingPinExtensionTitle": { "message": "Your MetaMask install is complete!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "Showing incoming transactions in your wallet relies on communication with $1. Etherscan will have access to your Ethereum address and your IP address. View $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Phishing detection alerts rely on communication with $1. jsDeliver will have access to your IP address. View $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 0c81f3cfb..ccee9a497 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "¡Su instalación de MetaMask ha finalizado!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "Mostrar las transacciones entrantes en su cartera depende de la comunicación con $1. Etherscan tendrá acceso a su dirección de Ethereum y a su dirección IP. Ver 2$.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Las alertas de detección de phishing se basan en la comunicación con $1. jsDeliver tendrá acceso a su dirección IP. Ver 2$.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 1c3adff3a..0b000821a 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -1623,14 +1623,6 @@ "onboardingPinExtensionTitle": { "message": "¡Su instalación de MetaMask ha finalizado!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "Mostrar las transacciones entrantes en su cartera depende de la comunicación con $1. Etherscan tendrá acceso a su dirección de Ethereum y a su dirección IP. Ver $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, - "onboardingUsePhishingDetectionDescription": { - "message": "Las alertas de detección de phishing se basan en la comunicación con $1. jsDeliver tendrá acceso a su dirección IP. Ver $2.", - "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" - }, "onlyAddTrustedNetworks": { "message": "Un proveedor de red malintencionado puede mentir sobre el estado de la cadena de bloques y registrar su actividad de red. Agregue solo redes personalizadas de confianza." }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index a3e801697..6dc0df5db 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "Votre installation de MetaMask est terminée !" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "L’affichage des transactions entrantes dans votre portefeuille repose sur la communication avec $1. Etherscan aura accès à votre adresse Ethereum et à votre adresse IP. Voir $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Les alertes de détection d’hameçonnage reposent sur la communication avec $1. jsDeliver aura accès à votre adresse IP. Voir $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index aa2bf3c08..463bb8772 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "आपका MetaMask इंस्टॉल पूरा हो गया है!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "आपके वॉलेट में आने वाले लेन-देन को दिखाना $1 के साथ संचार पर निर्भर करता है। Etherscan की पहुंच आपके Ethereum और आपके IP पते तक होगी। $2 देखें।", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "फिशिंग डिटेक्शन अलर्ट $1 के साथ संचार पर निर्भर करते हैं। jsDeliver की पहुंच आपके IP पते तक होगी। $2 देखें।", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 19a037be0..0e946c073 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "Pemasangan MetaMask Anda selesai!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "Menampilkan transaksi masuk di dompet Anda bergantung pada komunikasi dengan $1. Etherscan akan mendapat akses ke alamat Ethereum dan alamat IP Anda. Lihat $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Peringatan deteksi pengelabuan bergantung pada komunikasi dengan $1. jsDeliver akan mendapat akses ke alamat IP Anda. Lihat $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 162e67f2e..fb5481650 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "MetaMaskのインストールが完了しました!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "ウォレットに受け取ったトランザクションを表示するには、$1との通信が必要です。EtherscanはユーザーのイーサリアムアドレスとIPアドレスにアクセスできます。$2をご覧ください。", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "フィッシング検出アラートには$1との通信が必要です。jsDeliverはユーザーのIPアドレスにアクセスします。$2をご覧ください。", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 745963142..1b054a270 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "MetaMask 설치가 완료되었습니다!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "지갑에 들어오는 거래를 표시하려면 $1 링크와의 통신이 필요합니다. Etherscan은 이더리움 주소와 IP 주소에 액세스할 수 있습니다. $2 보기.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "피싱 감지 경보는 $1과(와)의 통신에 의존합니다. jsDeliver는 귀하의 IP 주소에 액세스할 수 있습니다. $2 보기.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 638975d19..be64f6d10 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "Sua instalação da MetaMask está concluída!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "A exibição de transações recebidas na sua carteira depende de comunicação com $1. O Etherscan terá acesso ao seu endereço Ethereum e ao seu endereço IP. Veja $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Os alertas de detecção de phishing dependem de comunicação com $1. O jsDeliver terá acesso ao seu endereço IP. Veja $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index bde907241..84ff0a098 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -1623,10 +1623,6 @@ "onboardingPinExtensionTitle": { "message": "Sua instalação da MetaMask está concluída!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "A exibição de transações recebidas na sua carteira depende de comunicação com $1. O Etherscan terá acesso ao seu endereço Ethereum e ao seu endereço IP. Veja $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Os alertas de detecção de phishing dependem de comunicação com $1. O jsDeliver terá acesso ao seu endereço IP. Veja $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 5207152f1..301ccefdb 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "Установка MetaMask завершена!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "Отображение входящих транзакций в вашем кошельке зависит от связи с $1. Etherscan получит доступ к вашему адресу Ethereum и вашему IP-адресу. Посмотрите $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Оповещения об обнаружении фишинга зависят от связи с $1. jsDeliver получит доступ к вашему IP-адресу. Посмотрите $ 2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 404594107..6be369b11 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "Ang pag-install ng iyong MetaMask ay kumpleto na!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "Ang pagpapakita ng mga papasok na transaksyon sa iyong wallet ay umaasa sa pakikipag-ugnayan sa $1. Magkakaroon ng access ang Etherscan sa iyong Ethereum address at iyong IP address. Tingnan ang $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Ang mga alerto sa pagtuklas ng phishing ay umaasa sa komunikasyon sa $1. Ang jsDeliver ay magkakaroon ng access sa iyong IP address. Tingnan ang $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 6230fe974..13c116603 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "MetaMask kurulumunuz tamamlandı!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "Cüzdanınıza gelen işlemlerin gösterilmesi $1 ile iletişime bağlıdır. Etherscan, Ethereum adresinize ve IP adresinize erişim sağlayacaktır. Şunu görüntüleyin: $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Kimlik avı tespiti uyarıları $1 ile iletişime bağlıdır. jsDeliver IP adresinize erişim sağlayacaktır. Şunu görüntüleyin: $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 0641006cc..dc55f0684 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "Quá trình cài đặt MetaMask đã hoàn tất!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "Việc hiển thị các giao dịch đến trong ví của bạn tùy thuộc vào quá trình truyền tin với $1. Etherscan sẽ có quyền truy cập vào địa chỉ Ethereum và địa chỉ IP của bạn. Xem $2.", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "Thông báo phát hiện dấu hiệu lừa đảo tùy thuộc vào quá trình truyền tin với $1. jsDeliver sẽ có quyền truy cập vào địa chỉ IP của bạn. Xem $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 37411fc22..283e93d96 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -3019,10 +3019,6 @@ "onboardingPinExtensionTitle": { "message": "您的 MetaMask 安装完成!" }, - "onboardingShowIncomingTransactionsDescription": { - "message": "在您的钱包中显示传入的交易依赖于与 $1 的通信。Etherscan 将有权访问您的以太坊地址和 IP 地址。查看 $2。", - "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key." - }, "onboardingUsePhishingDetectionDescription": { "message": "网络钓鱼检测警报依赖于与 $1 的通信。jsDeliver 将有权访问您的 IP 地址。查看 $2。", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js index beb0e9913..8d2a7d4b9 100644 --- a/app/scripts/controllers/detect-tokens.test.js +++ b/app/scripts/controllers/detect-tokens.test.js @@ -225,6 +225,7 @@ describe('DetectTokensController', function () { tokenListController, onInfuraIsBlocked: sinon.stub(), onInfuraIsUnblocked: sinon.stub(), + networkConfigurations: {}, }); preferences.setAddresses([ '0x7e57e2', diff --git a/app/scripts/controllers/mmi-controller.test.js b/app/scripts/controllers/mmi-controller.test.js index de06940c8..577e2325b 100644 --- a/app/scripts/controllers/mmi-controller.test.js +++ b/app/scripts/controllers/mmi-controller.test.js @@ -53,6 +53,7 @@ describe('MMIController', function () { onInfuraIsBlocked: jest.fn(), onInfuraIsUnblocked: jest.fn(), provider: {}, + networkConfigurations: {}, }), appStateController: new AppStateController({ addUnlockListener: jest.fn(), diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 6e223825e..c0d55042d 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,6 +1,9 @@ import { ObservableStore } from '@metamask/obs-store'; import { normalize as normalizeAddress } from 'eth-sig-util'; -import { IPFS_DEFAULT_GATEWAY_URL } from '../../../shared/constants/network'; +import { + CHAIN_IDS, + IPFS_DEFAULT_GATEWAY_URL, +} from '../../../shared/constants/network'; import { LedgerTransportTypes } from '../../../shared/constants/hardware-wallets'; import { ThemeType } from '../../../shared/constants/preferences'; import { shouldShowLineaMainnet } from '../../../shared/modules/network.utils'; @@ -8,6 +11,17 @@ import { shouldShowLineaMainnet } from '../../../shared/modules/network.utils'; import { KEYRING_SNAPS_REGISTRY_URL } from '../../../shared/constants/app'; ///: END:ONLY_INCLUDE_IN +const mainNetworks = { + [CHAIN_IDS.MAINNET]: true, + [CHAIN_IDS.LINEA_MAINNET]: true, +}; + +const testNetworks = { + [CHAIN_IDS.GOERLI]: true, + [CHAIN_IDS.SEPOLIA]: true, + [CHAIN_IDS.LINEA_GOERLI]: true, +}; + export default class PreferencesController { /** * @@ -25,6 +39,13 @@ export default class PreferencesController { * @property {string} store.selectedAddress A hex string that matches the currently selected address in the app */ constructor(opts = {}) { + const addedNonMainNetwork = Object.values( + opts.networkConfigurations, + ).reduce((acc, element) => { + acc[element.chainId] = true; + return acc; + }, {}); + const initState = { useBlockie: false, useNonceField: false, @@ -51,8 +72,11 @@ export default class PreferencesController { // Feature flag toggling is available in the global namespace // for convenient testing of pre-release features, and should never // perform sensitive operations. - featureFlags: { - showIncomingTransactions: true, + featureFlags: {}, + incomingTransactionsPreferences: { + ...mainNetworks, + ...addedNonMainNetwork, + ...testNetworks, }, knownMethodData: {}, currentLocale: opts.initLangCode, @@ -84,6 +108,7 @@ export default class PreferencesController { }; this.network = opts.network; + this._onInfuraIsBlocked = opts.onInfuraIsBlocked; this._onInfuraIsUnblocked = opts.onInfuraIsUnblocked; this.store = new ObservableStore(initState); @@ -448,7 +473,7 @@ export default class PreferencesController { * found in the settings page. * * @param {string} preference - The preference to enable or disable. - * @param {boolean} value - Indicates whether or not the preference should be enabled or disabled. + * @param {boolean |object} value - Indicates whether or not the preference should be enabled or disabled. * @returns {Promise} Promises a new object; the updated preferences object. */ async setPreference(preference, value) { @@ -550,6 +575,18 @@ export default class PreferencesController { }); } + /** + * A setter for the incomingTransactions in preference to be updated + * + * @param {string} chainId - chainId of the network + * @param {bool} value - preference of certain network, true to be enabled + */ + setIncomingTransactionsPreferences(chainId, value) { + const previousValue = this.store.getState().incomingTransactionsPreferences; + const updatedValue = { ...previousValue, [chainId]: value }; + this.store.updateState({ incomingTransactionsPreferences: updatedValue }); + } + getRpcMethodPreferences() { return this.store.getState().disabledRpcMethodPreferences; } @@ -570,6 +607,7 @@ export default class PreferencesController { } this.store.updateState({ snapRegistryList: snapRegistry }); } + ///: END:ONLY_INCLUDE_IN // diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index 55caf5d7a..86fc90ac5 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -1,67 +1,85 @@ -import { strict as assert } from 'assert'; -import sinon from 'sinon'; +/** + * @jest-environment node + */ import { ControllerMessenger } from '@metamask/base-controller'; import { TokenListController } from '@metamask/assets-controllers'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import PreferencesController from './preferences'; -describe('preferences controller', function () { +const NETWORK_CONFIGURATION_DATA = { + 'test-networkConfigurationId-1': { + rpcUrl: 'https://testrpc.com', + chainId: CHAIN_IDS.GOERLI, + nickname: '0X5', + rpcPrefs: { blockExplorerUrl: 'https://etherscan.io' }, + }, + 'test-networkConfigurationId-2': { + rpcUrl: 'http://localhost:8545', + chainId: '0x539', + ticker: 'ETH', + nickname: 'Localhost 8545', + rpcPrefs: {}, + }, +}; +describe('preferences controller', () => { let preferencesController; let tokenListController; - beforeEach(function () { + beforeEach(() => { const tokenListMessenger = new ControllerMessenger().getRestricted({ name: 'TokenListController', }); tokenListController = new TokenListController({ chainId: '1', preventPollingOnNetworkRestart: false, - onNetworkStateChange: sinon.spy(), - onPreferencesStateChange: sinon.spy(), + onNetworkStateChange: jest.fn(), + onPreferencesStateChange: jest.fn(), messenger: tokenListMessenger, }); preferencesController = new PreferencesController({ initLangCode: 'en_US', tokenListController, - onInfuraIsBlocked: sinon.spy(), - onInfuraIsUnblocked: sinon.spy(), + onInfuraIsBlocked: jest.fn(), + onInfuraIsUnblocked: jest.fn(), + networkConfigurations: NETWORK_CONFIGURATION_DATA, }); }); - afterEach(function () { - sinon.restore(); - }); - - describe('useBlockie', function () { - it('defaults useBlockie to false', function () { - assert.equal(preferencesController.store.getState().useBlockie, false); + describe('useBlockie', () => { + it('defaults useBlockie to false', () => { + expect(preferencesController.store.getState().useBlockie).toStrictEqual( + false, + ); }); - it('setUseBlockie to true', function () { + it('setUseBlockie to true', () => { preferencesController.setUseBlockie(true); - assert.equal(preferencesController.store.getState().useBlockie, true); + expect(preferencesController.store.getState().useBlockie).toStrictEqual( + true, + ); }); }); - describe('setCurrentLocale', function () { - it('checks the default currentLocale', function () { + describe('setCurrentLocale', () => { + it('checks the default currentLocale', () => { const { currentLocale } = preferencesController.store.getState(); - assert.equal(currentLocale, 'en_US'); + expect(currentLocale).toStrictEqual('en_US'); }); - it('sets current locale in preferences controller', function () { + it('sets current locale in preferences controller', () => { preferencesController.setCurrentLocale('ja'); const { currentLocale } = preferencesController.store.getState(); - assert.equal(currentLocale, 'ja'); + expect(currentLocale).toStrictEqual('ja'); }); }); - describe('setAddresses', function () { - it('should keep a map of addresses to names and addresses in the store', function () { + describe('setAddresses', () => { + it('should keep a map of addresses to names and addresses in the store', () => { preferencesController.setAddresses(['0xda22le', '0x7e57e2']); const { identities } = preferencesController.store.getState(); - assert.deepEqual(identities, { + expect(identities).toStrictEqual({ '0xda22le': { name: 'Account 1', address: '0xda22le', @@ -73,12 +91,12 @@ describe('preferences controller', function () { }); }); - it('should replace its list of addresses', function () { + it('should replace its list of addresses', () => { preferencesController.setAddresses(['0xda22le', '0x7e57e2']); preferencesController.setAddresses(['0xda22le77', '0x7e57e277']); const { identities } = preferencesController.store.getState(); - assert.deepEqual(identities, { + expect(identities).toStrictEqual({ '0xda22le77': { name: 'Account 1', address: '0xda22le77', @@ -91,237 +109,235 @@ describe('preferences controller', function () { }); }); - describe('removeAddress', function () { - it('should remove an address from state', function () { + describe('removeAddress', () => { + it('should remove an address from state', () => { preferencesController.setAddresses(['0xda22le', '0x7e57e2']); preferencesController.removeAddress('0xda22le'); - assert.equal( + expect( preferencesController.store.getState().identities['0xda22le'], - undefined, - ); + ).toStrictEqual(undefined); }); - it('should switch accounts if the selected address is removed', function () { + it('should switch accounts if the selected address is removed', () => { preferencesController.setAddresses(['0xda22le', '0x7e57e2']); preferencesController.setSelectedAddress('0x7e57e2'); preferencesController.removeAddress('0x7e57e2'); - - assert.equal(preferencesController.getSelectedAddress(), '0xda22le'); + expect(preferencesController.getSelectedAddress()).toStrictEqual( + '0xda22le', + ); }); }); - describe('setAccountLabel', function () { - it('should update a label for the given account', function () { + describe('setAccountLabel', () => { + it('should update a label for the given account', () => { preferencesController.setAddresses(['0xda22le', '0x7e57e2']); - assert.deepEqual( + expect( preferencesController.store.getState().identities['0xda22le'], - { - name: 'Account 1', - address: '0xda22le', - }, - ); + ).toStrictEqual({ + name: 'Account 1', + address: '0xda22le', + }); preferencesController.setAccountLabel('0xda22le', 'Dazzle'); - assert.deepEqual( + expect( preferencesController.store.getState().identities['0xda22le'], - { - name: 'Dazzle', - address: '0xda22le', - }, - ); + ).toStrictEqual({ + name: 'Dazzle', + address: '0xda22le', + }); }); }); - describe('setPasswordForgotten', function () { - it('should default to false', function () { - const state = preferencesController.store.getState(); - assert.equal(state.forgottenPassword, false); + describe('setPasswordForgotten', () => { + it('should default to false', () => { + expect( + preferencesController.store.getState().forgottenPassword, + ).toStrictEqual(false); }); - it('should set the forgottenPassword property in state', function () { - assert.equal( - preferencesController.store.getState().forgottenPassword, - false, - ); - + it('should set the forgottenPassword property in state', () => { preferencesController.setPasswordForgotten(true); - - assert.equal( + expect( preferencesController.store.getState().forgottenPassword, - true, - ); + ).toStrictEqual(true); }); }); - describe('setUsePhishDetect', function () { - it('should default to true', function () { - const state = preferencesController.store.getState(); - assert.equal(state.usePhishDetect, true); - }); - - it('should set the usePhishDetect property in state', function () { - assert.equal(preferencesController.store.getState().usePhishDetect, true); - preferencesController.setUsePhishDetect(false); - assert.equal( + describe('setUsePhishDetect', () => { + it('should default to true', () => { + expect( preferencesController.store.getState().usePhishDetect, - false, - ); + ).toStrictEqual(true); + }); + + it('should set the usePhishDetect property in state', () => { + preferencesController.setUsePhishDetect(false); + expect( + preferencesController.store.getState().usePhishDetect, + ).toStrictEqual(false); }); }); - describe('setUseMultiAccountBalanceChecker', function () { - it('should default to true', function () { - const state = preferencesController.store.getState(); - assert.equal(state.useMultiAccountBalanceChecker, true); + describe('setUseMultiAccountBalanceChecker', () => { + it('should default to true', () => { + expect( + preferencesController.store.getState().useMultiAccountBalanceChecker, + ).toStrictEqual(true); }); - it('should set the setUseMultiAccountBalanceChecker property in state', function () { - assert.equal( - preferencesController.store.getState().useMultiAccountBalanceChecker, - true, - ); - + it('should set the setUseMultiAccountBalanceChecker property in state', () => { preferencesController.setUseMultiAccountBalanceChecker(false); - - assert.equal( + expect( preferencesController.store.getState().useMultiAccountBalanceChecker, - false, - ); + ).toStrictEqual(false); }); }); - describe('setUseTokenDetection', function () { - it('should default to false', function () { - const state = preferencesController.store.getState(); - assert.equal(state.useTokenDetection, false); + describe('setUseTokenDetection', () => { + it('should default to false', () => { + expect( + preferencesController.store.getState().useTokenDetection, + ).toStrictEqual(false); }); - it('should set the useTokenDetection property in state', function () { - assert.equal( - preferencesController.store.getState().useTokenDetection, - false, - ); + it('should set the useTokenDetection property in state', () => { preferencesController.setUseTokenDetection(true); - assert.equal( + expect( preferencesController.store.getState().useTokenDetection, - true, - ); + ).toStrictEqual(true); }); }); - describe('setUseNftDetection', function () { - it('should default to false', function () { - const state = preferencesController.store.getState(); - assert.equal(state.useNftDetection, false); + describe('setUseNftDetection', () => { + it('should default to false', () => { + expect( + preferencesController.store.getState().useNftDetection, + ).toStrictEqual(false); }); - it('should set the useNftDetection property in state', function () { - assert.equal( - preferencesController.store.getState().useNftDetection, - false, - ); + it('should set the useNftDetection property in state', () => { preferencesController.setOpenSeaEnabled(true); preferencesController.setUseNftDetection(true); - assert.equal( + expect( preferencesController.store.getState().useNftDetection, - true, - ); + ).toStrictEqual(true); }); }); describe('setUse4ByteResolution', function () { it('should default to true', function () { - const state = preferencesController.store.getState(); - assert.equal(state.use4ByteResolution, true); + expect( + preferencesController.store.getState().use4ByteResolution, + ).toStrictEqual(true); }); it('should set the use4ByteResolution property in state', function () { - assert.equal( - preferencesController.store.getState().use4ByteResolution, - true, - ); preferencesController.setUse4ByteResolution(false); - assert.equal( + expect( preferencesController.store.getState().use4ByteResolution, - false, - ); + ).toStrictEqual(false); }); }); - describe('setOpenSeaEnabled', function () { - it('should default to false', function () { - const state = preferencesController.store.getState(); - assert.equal(state.openSeaEnabled, false); - }); - - it('should set the openSeaEnabled property in state', function () { - assert.equal( + describe('setOpenSeaEnabled', () => { + it('should default to false', () => { + expect( preferencesController.store.getState().openSeaEnabled, - false, - ); + ).toStrictEqual(false); + }); + + it('should set the openSeaEnabled property in state', () => { preferencesController.setOpenSeaEnabled(true); - assert.equal(preferencesController.store.getState().openSeaEnabled, true); + expect( + preferencesController.store.getState().openSeaEnabled, + ).toStrictEqual(true); }); }); - describe('setAdvancedGasFee', function () { - it('should default to null', function () { - const state = preferencesController.store.getState(); - assert.equal(state.advancedGasFee, null); + describe('setAdvancedGasFee', () => { + it('should default to null', () => { + expect( + preferencesController.store.getState().advancedGasFee, + ).toStrictEqual(null); }); - it('should set the setAdvancedGasFee property in state', function () { - const state = preferencesController.store.getState(); - assert.equal(state.advancedGasFee, null); + it('should set the setAdvancedGasFee property in state', () => { preferencesController.setAdvancedGasFee({ maxBaseFee: '1.5', priorityFee: '2', }); - assert.equal( + expect( preferencesController.store.getState().advancedGasFee.maxBaseFee, - '1.5', - ); - assert.equal( + ).toStrictEqual('1.5'); + expect( preferencesController.store.getState().advancedGasFee.priorityFee, - '2', - ); + ).toStrictEqual('2'); }); }); - describe('setTheme', function () { - it('should default to value "OS"', function () { - const state = preferencesController.store.getState(); - assert.equal(state.theme, 'os'); + describe('setTheme', () => { + it('should default to value "OS"', () => { + expect(preferencesController.store.getState().theme).toStrictEqual('os'); }); - it('should set the setTheme property in state', function () { - const state = preferencesController.store.getState(); - assert.equal(state.theme, 'os'); + it('should set the setTheme property in state', () => { preferencesController.setTheme('dark'); - assert.equal(preferencesController.store.getState().theme, 'dark'); + expect(preferencesController.store.getState().theme).toStrictEqual( + 'dark', + ); }); }); - describe('setUseCurrencyRateCheck', function () { - it('should default to false', function () { - const state = preferencesController.store.getState(); - assert.equal(state.useCurrencyRateCheck, true); + describe('setUseCurrencyRateCheck', () => { + it('should default to false', () => { + expect( + preferencesController.store.getState().useCurrencyRateCheck, + ).toStrictEqual(true); }); - it('should set the useCurrencyRateCheck property in state', function () { - assert.equal( - preferencesController.store.getState().useCurrencyRateCheck, - true, - ); + it('should set the useCurrencyRateCheck property in state', () => { preferencesController.setUseCurrencyRateCheck(false); - assert.equal( + expect( preferencesController.store.getState().useCurrencyRateCheck, + ).toStrictEqual(false); + }); + }); + + describe('setIncomingTransactionsPreferences', () => { + const addedNonTestNetworks = Object.keys(NETWORK_CONFIGURATION_DATA); + + it('should have default value combined', () => { + const state = preferencesController.store.getState(); + expect(state.incomingTransactionsPreferences).toStrictEqual({ + [CHAIN_IDS.MAINNET]: true, + [CHAIN_IDS.LINEA_MAINNET]: true, + [NETWORK_CONFIGURATION_DATA[addedNonTestNetworks[0]].chainId]: true, + [NETWORK_CONFIGURATION_DATA[addedNonTestNetworks[1]].chainId]: true, + [CHAIN_IDS.GOERLI]: true, + [CHAIN_IDS.SEPOLIA]: true, + [CHAIN_IDS.LINEA_GOERLI]: true, + }); + }); + + it('should update incomingTransactionsPreferences with given value set', () => { + preferencesController.setIncomingTransactionsPreferences( + [CHAIN_IDS.LINEA_MAINNET], false, ); + const state = preferencesController.store.getState(); + expect(state.incomingTransactionsPreferences).toStrictEqual({ + [CHAIN_IDS.MAINNET]: true, + [CHAIN_IDS.LINEA_MAINNET]: false, + [NETWORK_CONFIGURATION_DATA[addedNonTestNetworks[0]].chainId]: true, + [NETWORK_CONFIGURATION_DATA[addedNonTestNetworks[1]].chainId]: true, + [CHAIN_IDS.GOERLI]: true, + [CHAIN_IDS.SEPOLIA]: true, + [CHAIN_IDS.LINEA_GOERLI]: true, + }); }); }); }); diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 8e30f4c5d..42b528e2e 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -228,8 +228,9 @@ export default class TransactionController extends EventEmitter { getNetworkState: () => this._getNetworkState(), isEnabled: () => Boolean( - this.preferencesStore.getState().featureFlags - ?.showIncomingTransactions && this._hasCompletedOnboarding(), + this.preferencesStore.getState().incomingTransactionsPreferences?.[ + this._getChainId() + ] && this._hasCompletedOnboarding(), ), lastFetchedBlockNumbers: opts.initState?.lastFetchedBlockNumbers || {}, remoteTransactionSource: new EtherscanRemoteTransactionSource({ diff --git a/app/scripts/lib/backup.test.js b/app/scripts/lib/backup.test.js index e1ab84c9e..93aee74bd 100644 --- a/app/scripts/lib/backup.test.js +++ b/app/scripts/lib/backup.test.js @@ -131,7 +131,6 @@ const jsonData = JSON.stringify({ advancedGasFee: null, featureFlags: { sendHexData: true, - showIncomingTransactions: true, }, knownMethodData: {}, currentLocale: 'en', diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b92ba8cd3..10ec8b1ab 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -415,6 +415,7 @@ export default class MetamaskController extends EventEmitter { ), tokenListController: this.tokenListController, provider: this.provider, + networkConfigurations: this.networkController.state.networkConfigurations, }); const tokensControllerMessenger = this.controllerMessenger.getRestricted({ @@ -1975,10 +1976,10 @@ export default class MetamaskController extends EventEmitter { this.preferencesController.store.subscribe(async (state) => { const { selectedAddress, currentLocale } = state; - + const { chainId } = this.networkController.state.providerConfig; await updateCurrentLocale(currentLocale); - if (state?.featureFlags?.showIncomingTransactions) { + if (state.incomingTransactionsPreferences?.[chainId]) { this.txController.startIncomingTransactionPolling(); } else { this.txController.stopIncomingTransactionPolling(); @@ -2280,6 +2281,10 @@ export default class MetamaskController extends EventEmitter { setCurrentLocale: preferencesController.setCurrentLocale.bind( preferencesController, ), + setIncomingTransactionsPreferences: + preferencesController.setIncomingTransactionsPreferences.bind( + preferencesController, + ), markPasswordForgotten: this.markPasswordForgotten.bind(this), unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this), getRequestAccountTabIds: this.getRequestAccountTabIds, diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index c9d0b66c6..a3ff411ce 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -1686,24 +1686,24 @@ describe('MetaMaskController', function () { controllerMessengerSpy = ControllerMessenger.prototype; }); - it('starts incoming transaction polling if show incoming transactions enabled', async function () { + it('starts incoming transaction polling if incomingTransactionsPreferences is enabled for that chainId', async function () { assert(txControllerStub.startIncomingTransactionPolling.notCalled); await preferencesControllerSpy.store.subscribe.lastCall.args[0]({ - featureFlags: { - showIncomingTransactions: true, + incomingTransactionsPreferences: { + [MAINNET_CHAIN_ID]: true, }, }); assert(txControllerStub.startIncomingTransactionPolling.calledOnce); }); - it('stops incoming transaction polling if show incoming transactions disabled', async function () { + it('stops incoming transaction polling if incomingTransactionsPreferences is disabled for that chainIdd', async function () { assert(txControllerStub.stopIncomingTransactionPolling.notCalled); await preferencesControllerSpy.store.subscribe.lastCall.args[0]({ - featureFlags: { - showIncomingTransactions: false, + incomingTransactionsPreferences: { + [MAINNET_CHAIN_ID]: false, }, }); diff --git a/app/scripts/migrations/096.test.ts b/app/scripts/migrations/096.test.ts new file mode 100644 index 000000000..45b0fb851 --- /dev/null +++ b/app/scripts/migrations/096.test.ts @@ -0,0 +1,141 @@ +import { CHAIN_IDS } from '../../../shared/constants/network'; +import { migrate, version } from './096'; + +const oldVersion = 95; +describe('migration #96', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('returns the state unaltered if it has no PreferencesController property', async () => { + const oldData = { + some: 'data', + }; + const oldStorage = { + meta: { version: oldVersion }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if it has no NetworkController property', async () => { + const oldData = { + PreferencesController: 'data', + }; + const oldStorage = { + meta: { version: oldVersion }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if the PreferencesController object has no featureFlags property', async () => { + const oldData = { + PreferencesController: 'data', + NetworkController: 'data', + }; + const oldStorage = { + meta: { version: oldVersion }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if the NetworkController object has no networkConfigurations property', async () => { + const oldData = { + PreferencesController: { + featureFlags: { + showIncomingTransactions: true, + }, + }, + NetworkController: 'data', + }; + const oldStorage = { + meta: { version: oldVersion }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('rewrites PreferencesController and delete showIncomingTransactions', async () => { + const showIncomingTransactionsValue = false; + const networkConfigurations = { + 'network-configuration-id-1': { + chainId: '0xa4b1', + nickname: 'Arbitrum One', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.arbitrum.io', + }, + rpcUrl: + 'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748', + ticker: 'ETH', + }, + 'network-configuration-id-2': { + chainId: '0x4e454152', + nickname: 'Aurora Mainnet', + rpcPrefs: { + blockExplorerUrl: 'https://aurorascan.dev/', + }, + rpcUrl: + 'https://aurora-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748', + ticker: 'Aurora ETH', + }, + }; + const oldData = { + PreferencesController: { + featureFlags: { + showIncomingTransactions: showIncomingTransactionsValue, + }, + }, + NetworkController: { + networkConfigurations, + }, + }; + const oldStorage = { + meta: { version: oldVersion }, + data: oldData, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage).toStrictEqual({ + meta: { + version, + }, + data: { + PreferencesController: { + featureFlags: {}, + incomingTransactionsPreferences: { + [CHAIN_IDS.MAINNET]: showIncomingTransactionsValue, + [CHAIN_IDS.LINEA_MAINNET]: showIncomingTransactionsValue, + [networkConfigurations['network-configuration-id-1'].chainId]: + showIncomingTransactionsValue, + [networkConfigurations['network-configuration-id-2'].chainId]: + showIncomingTransactionsValue, + [CHAIN_IDS.GOERLI]: showIncomingTransactionsValue, + [CHAIN_IDS.SEPOLIA]: showIncomingTransactionsValue, + [CHAIN_IDS.LINEA_GOERLI]: showIncomingTransactionsValue, + }, + }, + NetworkController: { networkConfigurations }, + }, + }); + }); +}); diff --git a/app/scripts/migrations/096.ts b/app/scripts/migrations/096.ts new file mode 100644 index 000000000..96d74fa1e --- /dev/null +++ b/app/scripts/migrations/096.ts @@ -0,0 +1,84 @@ +import { cloneDeep, fromPairs, map } from 'lodash'; +import { hasProperty, isObject } from '@metamask/utils'; +import { CHAIN_IDS } from '../../../shared/constants/network'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 96; + +/** + * This migration will operate the following: + * + * - Delete `showIncomingTransactions` from `featureFlags` in PreferencesController + * - Create a new object under PreferencesController named as `incomingTransactionsPreferences` + * 1. which will collect all added networks including localhost + * 2. then append the test networks + * 3. each of them would become a key coming with the value Ture/False from `showIncomingTransactions` + * + * @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; +} + +interface NetworkConfiguration { + chainId: Record; +} + +function transformState(state: Record) { + if ( + !hasProperty(state, 'PreferencesController') || + !isObject(state.PreferencesController) || + !isObject(state.NetworkController) || + !hasProperty(state.PreferencesController, 'featureFlags') || + !hasProperty(state.NetworkController, 'networkConfigurations') + ) { + return state; + } + const { PreferencesController, NetworkController } = state; + const { featureFlags }: Record = PreferencesController; + const { showIncomingTransactions }: any = featureFlags; + const { networkConfigurations }: Record = NetworkController; + + const addedNetwork: Record[] = + Object.values(networkConfigurations).map( + (network) => network.chainId, + ); + + const mainNetworks = [CHAIN_IDS.MAINNET, CHAIN_IDS.LINEA_MAINNET]; + const testNetworks = [ + CHAIN_IDS.GOERLI, + CHAIN_IDS.SEPOLIA, + CHAIN_IDS.LINEA_GOERLI, + ]; + const allSavedNetworks: Record = [ + ...mainNetworks, + ...addedNetwork, + ...testNetworks, + ]; + + const incomingTransactionsPreferences = fromPairs( + map(allSavedNetworks, (element) => [element, showIncomingTransactions]), + ); + + if (featureFlags?.showIncomingTransactions !== undefined) { + delete featureFlags.showIncomingTransactions; + } + + state.PreferencesController.incomingTransactionsPreferences = + incomingTransactionsPreferences; + + return state; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index c23ec9608..da4e1029e 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -100,6 +100,7 @@ import * as m092point1 from './092.1'; import * as m093 from './093'; import * as m094 from './094'; import * as m095 from './095'; +import * as m096 from './096'; const migrations = [ m002, @@ -197,5 +198,6 @@ const migrations = [ m093, m094, m095, + m096, ]; export default migrations; diff --git a/jest.config.js b/jest.config.js index 1b68e475a..72649eaf6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,6 +7,7 @@ module.exports = { '/app/scripts/controllers/transactions/etherscan.ts', '/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts', '/app/scripts/controllers/transactions/IncomingTransactionHelper.ts', + '/app/scripts/controllers/preferences.js', '/app/scripts/flask/**/*.js', '/app/scripts/lib/**/*.(js|ts)', '/app/scripts/lib/createRPCMethodTrackingMiddleware.js', @@ -45,6 +46,7 @@ module.exports = { '/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts', '/app/scripts/controllers/mmi-controller.test.js', '/app/scripts/controllers/permissions/**/*.test.js', + '/app/scripts/controllers/preferences.test.js', '/app/scripts/controllers/sign.test.ts', '/app/scripts/controllers/decrypt-message.test.ts', '/app/scripts/flask/**/*.test.js', diff --git a/test/data/mock-send-state.json b/test/data/mock-send-state.json index 21ab228d9..92ea465a4 100644 --- a/test/data/mock-send-state.json +++ b/test/data/mock-send-state.json @@ -111,9 +111,7 @@ "alertEnabledness": { "unconnectedAccount": true }, - "featureFlags": { - "showIncomingTransactions": true - }, + "featureFlags": {}, "network": "5", "providerConfig": { "type": "rpc", diff --git a/test/data/mock-state.json b/test/data/mock-state.json index ae58d5dba..f1caf514b 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -107,9 +107,7 @@ "alertEnabledness": { "unconnectedAccount": true }, - "featureFlags": { - "showIncomingTransactions": true - }, + "featureFlags": {}, "networkId": "5", "providerConfig": { "type": "rpc", @@ -220,7 +218,15 @@ } }, "cachedBalances": {}, - "incomingTransactions": {}, + "incomingTransactionsPreferences": { + "0x1": true, + "0xe708": false, + "0xfa": true, + "0x5": false, + "0xaa36a7": true, + "0xe704": true + }, + "unapprovedTxs": { "8393540981007587": { "chainId": "0x5", diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 79b943411..24dc9bf97 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -250,9 +250,7 @@ function defaultFixture() { advancedGasFee: null, currentLocale: 'en', dismissSeedBackUpReminder: true, - featureFlags: { - showIncomingTransactions: true, - }, + featureFlags: {}, forgottenPassword: false, identities: { '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': { @@ -322,6 +320,7 @@ function defaultFixture() { }, }; } + function onboardingFixture() { return { data: { @@ -380,9 +379,7 @@ function onboardingFixture() { advancedGasFee: null, currentLocale: 'en', dismissSeedBackUpReminder: false, - featureFlags: { - showIncomingTransactions: true, - }, + featureFlags: {}, forgottenPassword: false, identities: {}, infuraBlocked: false, diff --git a/test/e2e/restore/MetaMaskUserData.json b/test/e2e/restore/MetaMaskUserData.json index 4719406da..5215b9620 100644 --- a/test/e2e/restore/MetaMaskUserData.json +++ b/test/e2e/restore/MetaMaskUserData.json @@ -16,9 +16,7 @@ "advancedGasFee": null, "currentLocale": "en", "dismissSeedBackUpReminder": true, - "featureFlags": { - "showIncomingTransactions": true - }, + "featureFlags": {}, "forgottenPassword": false, "frequentRpcListDetail": [ { diff --git a/test/e2e/tests/onboarding.spec.js b/test/e2e/tests/onboarding.spec.js index 0f1ba862a..c81e93520 100644 --- a/test/e2e/tests/onboarding.spec.js +++ b/test/e2e/tests/onboarding.spec.js @@ -10,6 +10,7 @@ const { importWrongSRPOnboardingFlow, testSRPDropdownIterations, assertAccountBalanceForDOM, + defaultGanacheOptions, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -19,12 +20,12 @@ describe('MetaMask onboarding', function () { 'test test test test test test test test test test test test'; const wrongTestPassword = 'test test test test'; - const ganacheOptions = { + const ganacheOptions2 = { accounts: [ { secretKey: '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', - balance: convertToHexValue(25000000000000000000), + balance: convertToHexValue(10000000000000000000), }, ], }; @@ -33,7 +34,7 @@ describe('MetaMask onboarding', function () { await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }).build(), - ganacheOptions, + ganacheOptions: defaultGanacheOptions, title: this.test.title, failOnConsoleError: false, }, @@ -54,7 +55,7 @@ describe('MetaMask onboarding', function () { await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }).build(), - ganacheOptions, + ganacheOptions: defaultGanacheOptions, title: this.test.title, failOnConsoleError: false, }, @@ -79,7 +80,7 @@ describe('MetaMask onboarding', function () { await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }).build(), - ganacheOptions, + ganacheOptions: defaultGanacheOptions, title: this.test.title, failOnConsoleError: false, }, @@ -101,7 +102,7 @@ describe('MetaMask onboarding', function () { await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }).build(), - ganacheOptions, + ganacheOptions: defaultGanacheOptions, title: this.test.title, failOnConsoleError: false, }, @@ -141,7 +142,7 @@ describe('MetaMask onboarding', function () { await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }).build(), - ganacheOptions, + ganacheOptions: defaultGanacheOptions, title: this.test.title, failOnConsoleError: false, }, @@ -162,11 +163,11 @@ describe('MetaMask onboarding', function () { ); // Check that the error message is displayed for the password fields - const passwordErrorIsDisplayed = await driver.isElementPresent({ - text: "Passwords don't match", - css: 'h6', - }); - assert.equal(passwordErrorIsDisplayed, true); + await driver.isElementPresent( + { text: "Passwords don't match", tag: 'h6' }, + true, + ); + // Check that the "Confirm Password" button is disabled const confirmPasswordButton = await driver.findElement( '[data-testid="create-password-wallet"]', @@ -180,7 +181,7 @@ describe('MetaMask onboarding', function () { await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }).build(), - ganacheOptions, + ganacheOptions: defaultGanacheOptions, title: this.test.title, failOnConsoleError: false, }, @@ -204,7 +205,7 @@ describe('MetaMask onboarding', function () { await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }).build(), - ganacheOptions, + ganacheOptions: defaultGanacheOptions, title: this.test.title, failOnConsoleError: false, }, @@ -238,16 +239,6 @@ describe('MetaMask onboarding', function () { ); }); - const ganacheOptions2 = { - accounts: [ - { - secretKey: - '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', - balance: convertToHexValue(10000000000000000000), - }, - ], - }; - it(`User can add custom network during onboarding`, async function () { const networkName = 'Localhost 8546'; const networkUrl = 'http://127.0.0.1:8546'; @@ -258,7 +249,7 @@ describe('MetaMask onboarding', function () { { fixtures: new FixtureBuilder({ onboarding: true }).build(), ganacheOptions: { - ...ganacheOptions, + ...defaultGanacheOptions, concurrent: { port, chainId, ganacheOptions2 }, }, title: this.test.title, 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 839e0d76b..1129eb74c 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 @@ -109,7 +109,8 @@ "useCurrencyRateCheck": "boolean", "openSeaEnabled": "boolean", "advancedGasFee": "object", - "featureFlags": { "showIncomingTransactions": true }, + "featureFlags": {}, + "incomingTransactionsPreferences": "object", "knownMethodData": "object", "currentLocale": "en", "identities": "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 98eaf99d3..2a78c9d9f 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 @@ -20,7 +20,8 @@ "pendingTokens": "object", "customNonceValue": "", "useBlockie": false, - "featureFlags": { "showIncomingTransactions": true }, + "featureFlags": {}, + "incomingTransactionsPreferences": "object", "welcomeScreenSeen": false, "currentLocale": "en", "preferences": { 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 3dbc33e79..9d27dee3f 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 @@ -66,7 +66,7 @@ "advancedGasFee": "object", "currentLocale": "en", "dismissSeedBackUpReminder": "boolean", - "featureFlags": { "showIncomingTransactions": true }, + "featureFlags": {}, "forgottenPassword": false, "identities": "object", "infuraBlocked": "boolean", 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 ca6006c7a..897597e04 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 @@ -66,7 +66,7 @@ "advancedGasFee": "object", "currentLocale": "en", "dismissSeedBackUpReminder": "boolean", - "featureFlags": { "showIncomingTransactions": true }, + "featureFlags": {}, "forgottenPassword": false, "identities": "object", "infuraBlocked": "boolean", diff --git a/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap b/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap index e842c4c0f..a1ed4f600 100644 --- a/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap +++ b/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap @@ -6,23 +6,22 @@ exports[`IncomingTransactionToggle should render existing incoming transaction p class="mm-box mm-incoming-transaction-toggle" >

Show incoming transactions

This relies on each network which will have access to your Ethereum address and your IP address.

- Enable for all networks

@@ -294,6 +295,7 @@ export default class AdvancedTab extends PureComponent { display={Display.Flex} flexDirection={FlexDirection.Row} justifyContent={JustifyContent.spaceBetween} + gap={4} data-testid="advanced-setting-show-testnet-conversion" >
@@ -329,6 +331,7 @@ export default class AdvancedTab extends PureComponent { display={Display.Flex} flexDirection={FlexDirection.Row} justifyContent={JustifyContent.spaceBetween} + gap={4} >
{t('showTestnetNetworks')} @@ -361,6 +364,7 @@ export default class AdvancedTab extends PureComponent { display={Display.Flex} flexDirection={FlexDirection.Row} justifyContent={JustifyContent.spaceBetween} + gap={4} >
{t('nonceField')} @@ -551,6 +555,7 @@ export default class AdvancedTab extends PureComponent { display={Display.Flex} flexDirection={FlexDirection.Row} justifyContent={JustifyContent.spaceBetween} + gap={4} >
{t('dismissReminderField')} diff --git a/ui/pages/settings/index.scss b/ui/pages/settings/index.scss index 044b0fd35..dfc0c7c52 100644 --- a/ui/pages/settings/index.scss +++ b/ui/pages/settings/index.scss @@ -273,8 +273,6 @@ min-width: 0; display: flex; flex-direction: column; - font-size: 16px; - font-weight: 500; @include screen-sm-max { height: initial; diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index f4b8627f9..645173f9b 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -47,7 +47,7 @@ exports[`Security Tab should match snapshot 1`] = ` class="settings-page__content-padded" >
-
-
@@ -203,7 +199,7 @@ exports[`Security Tab should match snapshot 1`] = ` class="settings-page__content-padded" >
-
- - Show incoming transactions - -
+

+ This relies on each network which will have access to your Ethereum address and your IP address. +

+
+

- - - This relies on - - Etherscan - - which will have access to your Ethereum address and your IP address. - - Privacy policy - - - - -

-
-
+ Enable for all networks +

-
-
- - Network provider - -
-
- - Choose your network -
- - - We use Infura as our remote procedure call (RPC) provider to offer the most reliable and private access to Ethereum data we can. You can choose your own RPC, but remember that any RPC will receive your IP address and Ethereum wallet to make transactions. Read our - - Privacy policy - - to learn more about how Infura handles data. - - +
+ C +
+

+ Custom Mainnet RPC +

-
-
- -
-
-
-
- - IPFS gateway - -
- MetaMask uses third-party services to show images of your NFTs stored on IPFS, display information related to ENS addresses entered in your browser's address bar, and fetch icons for different tokens. Your IP address may be exposed to these services when you’re using them. -
-
-
+
+
+
+ Linea Mainnet logo +
+

+ Linea Mainnet +

+
+