1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-21 17:37:01 +01:00

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 <markjstacey@gmail.com>
This commit is contained in:
Danica Shen 2023-08-24 11:27:42 +01:00 committed by GitHub
parent a478f675f1
commit 00d155ce2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 1411 additions and 865 deletions

View File

@ -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',

View File

@ -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',

View File

@ -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: {

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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."
},

View File

@ -3019,10 +3019,6 @@
"onboardingPinExtensionTitle": {
"message": "Votre installation de MetaMask est terminée!"
},
"onboardingShowIncomingTransactionsDescription": {
"message": "Laffichage 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 dhameç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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -225,6 +225,7 @@ describe('DetectTokensController', function () {
tokenListController,
onInfuraIsBlocked: sinon.stub(),
onInfuraIsUnblocked: sinon.stub(),
networkConfigurations: {},
});
preferences.setAddresses([
'0x7e57e2',

View File

@ -53,6 +53,7 @@ describe('MMIController', function () {
onInfuraIsBlocked: jest.fn(),
onInfuraIsUnblocked: jest.fn(),
provider: {},
networkConfigurations: {},
}),
appStateController: new AppStateController({
addUnlockListener: jest.fn(),

View File

@ -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<object>} 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
//

View File

@ -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', () => {
it('defaults useBlockie to false', () => {
expect(preferencesController.store.getState().useBlockie).toStrictEqual(
false,
);
});
describe('useBlockie', function () {
it('defaults useBlockie to false', function () {
assert.equal(preferencesController.store.getState().useBlockie, 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'],
{
).toStrictEqual({
name: 'Account 1',
address: '0xda22le',
},
);
});
preferencesController.setAccountLabel('0xda22le', 'Dazzle');
assert.deepEqual(
expect(
preferencesController.store.getState().identities['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);
});
it('should set the forgottenPassword property in state', function () {
assert.equal(
describe('setPasswordForgotten', () => {
it('should default to false', () => {
expect(
preferencesController.store.getState().forgottenPassword,
false,
);
).toStrictEqual(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);
});
it('should set the setUseMultiAccountBalanceChecker property in state', function () {
assert.equal(
describe('setUseMultiAccountBalanceChecker', () => {
it('should default to true', () => {
expect(
preferencesController.store.getState().useMultiAccountBalanceChecker,
true,
);
).toStrictEqual(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);
});
it('should set the useTokenDetection property in state', function () {
assert.equal(
describe('setUseTokenDetection', () => {
it('should default to false', () => {
expect(
preferencesController.store.getState().useTokenDetection,
false,
);
).toStrictEqual(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);
});
it('should set the useNftDetection property in state', function () {
assert.equal(
describe('setUseNftDetection', () => {
it('should default to false', () => {
expect(
preferencesController.store.getState().useNftDetection,
false,
);
).toStrictEqual(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');
});
});
describe('setUseCurrencyRateCheck', function () {
it('should default to false', function () {
const state = preferencesController.store.getState();
assert.equal(state.useCurrencyRateCheck, true);
});
it('should set the useCurrencyRateCheck property in state', function () {
assert.equal(
preferencesController.store.getState().useCurrencyRateCheck,
true,
expect(preferencesController.store.getState().theme).toStrictEqual(
'dark',
);
preferencesController.setUseCurrencyRateCheck(false);
assert.equal(
});
});
describe('setUseCurrencyRateCheck', () => {
it('should default to false', () => {
expect(
preferencesController.store.getState().useCurrencyRateCheck,
).toStrictEqual(true);
});
it('should set the useCurrencyRateCheck property in state', () => {
preferencesController.setUseCurrencyRateCheck(false);
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,
});
});
});
});

View File

@ -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({

View File

@ -131,7 +131,6 @@ const jsonData = JSON.stringify({
advancedGasFee: null,
featureFlags: {
sendHexData: true,
showIncomingTransactions: true,
},
knownMethodData: {},
currentLocale: 'en',

View File

@ -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,

View File

@ -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,
},
});

View File

@ -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 },
},
});
});
});

View File

@ -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<string, any>;
};
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<VersionedData> {
const versionedData = cloneDeep(originalVersionedData);
versionedData.meta.version = version;
transformState(versionedData.data);
return versionedData;
}
interface NetworkConfiguration {
chainId: Record<string, any>;
}
function transformState(state: Record<string, unknown>) {
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<string, any> = PreferencesController;
const { showIncomingTransactions }: any = featureFlags;
const { networkConfigurations }: Record<string, any> = NetworkController;
const addedNetwork: Record<string, any>[] =
Object.values<NetworkConfiguration>(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<string, any> = [
...mainNetworks,
...addedNetwork,
...testNetworks,
];
const incomingTransactionsPreferences = fromPairs(
map(allSavedNetworks, (element) => [element, showIncomingTransactions]),
);
if (featureFlags?.showIncomingTransactions !== undefined) {
delete featureFlags.showIncomingTransactions;
}
state.PreferencesController.incomingTransactionsPreferences =
incomingTransactionsPreferences;
return state;
}

View File

@ -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;

View File

@ -7,6 +7,7 @@ module.exports = {
'<rootDir>/app/scripts/controllers/transactions/etherscan.ts',
'<rootDir>/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts',
'<rootDir>/app/scripts/controllers/transactions/IncomingTransactionHelper.ts',
'<rootDir>/app/scripts/controllers/preferences.js',
'<rootDir>/app/scripts/flask/**/*.js',
'<rootDir>/app/scripts/lib/**/*.(js|ts)',
'<rootDir>/app/scripts/lib/createRPCMethodTrackingMiddleware.js',
@ -45,6 +46,7 @@ module.exports = {
'<rootDir>/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts',
'<rootDir>/app/scripts/controllers/mmi-controller.test.js',
'<rootDir>/app/scripts/controllers/permissions/**/*.test.js',
'<rootDir>/app/scripts/controllers/preferences.test.js',
'<rootDir>/app/scripts/controllers/sign.test.ts',
'<rootDir>/app/scripts/controllers/decrypt-message.test.ts',
'<rootDir>/app/scripts/flask/**/*.test.js',

View File

@ -111,9 +111,7 @@
"alertEnabledness": {
"unconnectedAccount": true
},
"featureFlags": {
"showIncomingTransactions": true
},
"featureFlags": {},
"network": "5",
"providerConfig": {
"type": "rpc",

View File

@ -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",

View File

@ -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,

View File

@ -16,9 +16,7 @@
"advancedGasFee": null,
"currentLocale": "en",
"dismissSeedBackUpReminder": true,
"featureFlags": {
"showIncomingTransactions": true
},
"featureFlags": {},
"forgottenPassword": false,
"frequentRpcListDetail": [
{

View File

@ -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,

View File

@ -109,7 +109,8 @@
"useCurrencyRateCheck": "boolean",
"openSeaEnabled": "boolean",
"advancedGasFee": "object",
"featureFlags": { "showIncomingTransactions": true },
"featureFlags": {},
"incomingTransactionsPreferences": "object",
"knownMethodData": "object",
"currentLocale": "en",
"identities": "object",

View File

@ -20,7 +20,8 @@
"pendingTokens": "object",
"customNonceValue": "",
"useBlockie": false,
"featureFlags": { "showIncomingTransactions": true },
"featureFlags": {},
"incomingTransactionsPreferences": "object",
"welcomeScreenSeen": false,
"currentLocale": "en",
"preferences": {

View File

@ -66,7 +66,7 @@
"advancedGasFee": "object",
"currentLocale": "en",
"dismissSeedBackUpReminder": "boolean",
"featureFlags": { "showIncomingTransactions": true },
"featureFlags": {},
"forgottenPassword": false,
"identities": "object",
"infuraBlocked": "boolean",

View File

@ -66,7 +66,7 @@
"advancedGasFee": "object",
"currentLocale": "en",
"dismissSeedBackUpReminder": "boolean",
"featureFlags": { "showIncomingTransactions": true },
"featureFlags": {},
"forgottenPassword": false,
"identities": "object",
"infuraBlocked": "boolean",

View File

@ -6,23 +6,22 @@ exports[`IncomingTransactionToggle should render existing incoming transaction p
class="mm-box mm-incoming-transaction-toggle"
>
<p
class="mm-box mm-text mm-text--body-lg-medium mm-text--font-weight-bold mm-box--color-text-default"
class="mm-box mm-text mm-text--body-md-medium mm-box--color-text-default"
>
Show incoming transactions
</p>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-alternative"
class="mm-box mm-text mm-text--body-sm mm-box--color-text-alternative"
>
This relies on each network which will have access to your Ethereum address and your IP address.
</p>
<div
class="mm-box mm-box--margin-top-3 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box mm-box--margin-top-3 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="incoming-transaction-toggle-enable-all"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
class="mm-box mm-text mm-text--body-sm-bold mm-box--color-text-default"
>
Enable for all networks
</p>
<label

View File

@ -0,0 +1,26 @@
import { StoryFn } from '@storybook/react';
import React from 'react';
import IncomingTransactionToggle from './incoming-transaction-toggle';
import { ALL_NETWORKS_DATA, INCOMING_DATA } from './mock-data';
const IncomingTransactionToggleStory = {
title: 'Components/App/IncomingTransactionToggle',
component: IncomingTransactionToggle,
argTypes: {},
};
export const DefaultStory: StoryFn<typeof IncomingTransactionToggle> = () => {
return (
<IncomingTransactionToggle
setIncomingTransactionsPreferences={(chainId, value) => {
console.log(chainId, value);
}}
allNetworks={ALL_NETWORKS_DATA}
incomingTransactionsPreferences={INCOMING_DATA}
/>
);
};
DefaultStory.storyName = 'Default';
export default IncomingTransactionToggleStory;

View File

@ -6,81 +6,9 @@ import { renderWithProvider } from '../../../../test/lib/render-helpers';
import mockState from '../../../../test/data/mock-state.json';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import IncomingTransactionToggle from './incoming-transaction-toggle';
import { ALL_NETWORKS_DATA, INCOMING_DATA } from './mock-data';
const mockTrackEvent = jest.fn();
const ALL_NETWORKS_DATA = [
{
chainId: '0x1',
nickname: 'Ethereum Mainnet',
rpcUrl: 'https://mainnet.infura.io/v3/6c21df2a8dcb4a77b9bbcc1b65ee9ded',
rpcPrefs: {
imageUrl: './images/eth_logo.png',
},
providerType: 'mainnet',
ticker: 'ETH',
id: 'mainnet',
removable: false,
},
{
chainId: '0xe708',
nickname: 'Linea Mainnet',
rpcUrl:
'https://linea-mainnet.infura.io/v3/6c21df2a8dcb4a77b9bbcc1b65ee9ded',
rpcPrefs: {
imageUrl: './images/linea-logo-mainnet.png',
},
providerType: 'linea-mainnet',
id: 'linea-mainnet',
removable: false,
},
{
chainId: '0xfa',
nickname: 'FANTOM',
rpcPrefs: {},
rpcUrl: 'http://ftmscan.com5',
ticker: 'FTM',
},
{
chainId: '0x5',
nickname: 'Goerli',
rpcUrl: 'https://goerli.infura.io/v3/6c21df2a8dcb4a77b9bbcc1b65ee9ded',
providerType: 'goerli',
ticker: 'GoerliETH',
id: 'goerli',
removable: false,
},
{
chainId: '0xaa36a7',
nickname: 'Sepolia',
rpcUrl: 'https://sepolia.infura.io/v3/6c21df2a8dcb4a77b9bbcc1b65ee9ded',
providerType: 'sepolia',
ticker: 'SepoliaETH',
id: 'sepolia',
removable: false,
},
{
chainId: '0xe704',
nickname: 'Linea Goerli',
rpcUrl:
'https://linea-goerli.infura.io/v3/6c21df2a8dcb4a77b9bbcc1b65ee9ded',
rpcPrefs: {
imageUrl: './images/linea-logo-testnet.png',
},
providerType: 'linea-goerli',
ticker: 'LineaETH',
id: 'linea-goerli',
removable: false,
},
];
const INCOMING_DATA = {
'0x1': true,
'0xe708': false,
'0xfa': true,
'0x5': false,
'0xaa36a7': true,
'0xe704': true,
};
describe('IncomingTransactionToggle', () => {
const mockStore = configureMockState([thunk])(mockState);

View File

@ -10,7 +10,6 @@ import {
FlexDirection,
JustifyContent,
TextVariant,
FontWeight,
TextColor,
} from '../../../helpers/constants/design-system';
@ -19,9 +18,9 @@ import { TEST_CHAINS } from '../../../../shared/constants/network';
import NetworkToggle from './network-toggle';
interface IncomingTransactionToggleProps {
wrapperRef: PolymorphicRef<any>;
wrapperRef?: PolymorphicRef<any>;
incomingTransactionsPreferences: Record<string, boolean>;
allNetworks: [Record<string, any>];
allNetworks: Record<string, any>[];
setIncomingTransactionsPreferences: (
chainId: string,
isAllEnabledValue: boolean,
@ -74,11 +73,11 @@ const IncomingTransactionToggle = ({
return (
<Box ref={wrapperRef} className="mm-incoming-transaction-toggle">
<Text variant={TextVariant.bodyLgMedium} fontWeight={FontWeight.Bold}>
<Text variant={TextVariant.bodyMdMedium}>
{t('showIncomingTransactions')}
</Text>
<Text variant={TextVariant.bodyMd} color={TextColor.textAlternative}>
<Text variant={TextVariant.bodySm} color={TextColor.textAlternative}>
{t('showIncomingTransactionsInformation')}
</Text>
<Box
@ -86,9 +85,12 @@ const IncomingTransactionToggle = ({
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
data-testid="incoming-transaction-toggle-enable-all"
>
<Text variant={TextVariant.bodyMd}> {t('enableForAllNetworks')}</Text>
<Text variant={TextVariant.bodySmBold}>
{t('enableForAllNetworks')}
</Text>
<ToggleButton
value={isAllEnabled}
onToggle={(value) => toggleAllEnabled(!value)}

View File

@ -0,0 +1,73 @@
export const ALL_NETWORKS_DATA = [
{
chainId: '0x1',
nickname: 'Ethereum Mainnet',
rpcUrl: 'https://mainnet.infura.io/v3/6c21df2a8dcb4a77b9bbcc1b65ee9ded',
rpcPrefs: {
imageUrl: './images/eth_logo.png',
},
providerType: 'mainnet',
ticker: 'ETH',
id: 'mainnet',
removable: false,
},
{
chainId: '0xe708',
nickname: 'Linea Mainnet',
rpcUrl:
'https://linea-mainnet.infura.io/v3/6c21df2a8dcb4a77b9bbcc1b65ee9ded',
rpcPrefs: {
imageUrl: './images/linea-logo-mainnet.png',
},
providerType: 'linea-mainnet',
id: 'linea-mainnet',
removable: false,
},
{
chainId: '0xfa',
nickname: 'FANTOM',
rpcPrefs: {},
rpcUrl: 'http://ftmscan.com5',
ticker: 'FTM',
},
{
chainId: '0x5',
nickname: 'Goerli',
rpcUrl: 'https://goerli.infura.io/v3/6c21df2a8dcb4a77b9bbcc1b65ee9ded',
providerType: 'goerli',
ticker: 'GoerliETH',
id: 'goerli',
removable: false,
},
{
chainId: '0xaa36a7',
nickname: 'Sepolia',
rpcUrl: 'https://sepolia.infura.io/v3/6c21df2a8dcb4a77b9bbcc1b65ee9ded',
providerType: 'sepolia',
ticker: 'SepoliaETH',
id: 'sepolia',
removable: false,
},
{
chainId: '0xe704',
nickname: 'Linea Goerli',
rpcUrl:
'https://linea-goerli.infura.io/v3/6c21df2a8dcb4a77b9bbcc1b65ee9ded',
rpcPrefs: {
imageUrl: './images/linea-logo-testnet.png',
},
providerType: 'linea-goerli',
ticker: 'LineaETH',
id: 'linea-goerli',
removable: false,
},
];
export const INCOMING_DATA = {
'0x1': true,
'0xe708': false,
'0xfa': true,
'0x5': false,
'0xaa36a7': true,
'0xe704': true,
};

View File

@ -74,7 +74,6 @@ const customStore = ({
// pendingTransactions
featureFlags: {
...testData?.metamask?.featureFlags,
showIncomingTransactions: pendingCount > 0,
},
incomingTransactions: {
...testData?.metamask?.incomingTransactions,

View File

@ -21,28 +21,11 @@ import {
Box,
} from '../../component-library';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { getAvatarNetworkColor } from '../../../helpers/utils/accounts';
import Tooltip from '../../ui/tooltip/tooltip';
import {
GOERLI_DISPLAY_NAME,
LINEA_GOERLI_DISPLAY_NAME,
SEPOLIA_DISPLAY_NAME,
} from '../../../../shared/constants/network';
const MAXIMUM_CHARACTERS_WITHOUT_TOOLTIP = 20;
function getAvatarNetworkColor(name) {
switch (name) {
case GOERLI_DISPLAY_NAME:
return BackgroundColor.goerli;
case LINEA_GOERLI_DISPLAY_NAME:
return BackgroundColor.lineaGoerli;
case SEPOLIA_DISPLAY_NAME:
return BackgroundColor.sepolia;
default:
return undefined;
}
}
export const NetworkListItem = ({
name,
iconSrc,

View File

@ -1,7 +1,6 @@
.toggle-button {
display: flex;
cursor: pointer;
$self: &;
&__status {

View File

@ -1,4 +1,10 @@
import { InvisibleCharacter } from '../../components/component-library';
import {
GOERLI_DISPLAY_NAME,
LINEA_GOERLI_DISPLAY_NAME,
SEPOLIA_DISPLAY_NAME,
} from '../../../shared/constants/network';
import { BackgroundColor } from '../constants/design-system';
export function getAccountNameErrorMessage(
accounts,
@ -41,3 +47,16 @@ export function getAccountNameErrorMessage(
return { isValidAccountName, errorMessage };
}
export function getAvatarNetworkColor(name) {
switch (name) {
case GOERLI_DISPLAY_NAME:
return BackgroundColor.goerli;
case LINEA_GOERLI_DISPLAY_NAME:
return BackgroundColor.lineaGoerli;
case SEPOLIA_DISPLAY_NAME:
return BackgroundColor.sepolia;
default:
return undefined;
}
}

View File

@ -1,10 +1,18 @@
import { getAccountNameErrorMessage } from './accounts';
import {
GOERLI_DISPLAY_NAME,
LINEA_GOERLI_DISPLAY_NAME,
MAINNET_DISPLAY_NAME,
SEPOLIA_DISPLAY_NAME,
} from '../../../shared/constants/network';
import { BackgroundColor } from '../constants/design-system';
import { getAccountNameErrorMessage, getAvatarNetworkColor } from './accounts';
const mockAccounts = [{ name: 'Account 1' }, { name: 'Account 2' }];
const mockLocalization = { t: jest.fn().mockReturnValue('Account') };
describe('Accounts', () => {
describe('#getAccountNameErrorMessage', () => {
it('does not allow duplicate names', () => {
const { isValidAccountName } = getAccountNameErrorMessage(
mockAccounts,
@ -44,4 +52,28 @@ describe('Accounts', () => {
);
expect(isValidAccountName).toBe(true);
});
});
describe('#getAvatarNetworkColor', () => {
it('should return goerli', () => {
expect(getAvatarNetworkColor(GOERLI_DISPLAY_NAME)).toStrictEqual(
BackgroundColor.goerli,
);
});
it('should return lineaGoerli', () => {
expect(getAvatarNetworkColor(LINEA_GOERLI_DISPLAY_NAME)).toStrictEqual(
BackgroundColor.lineaGoerli,
);
});
it('should return sepolia', () => {
expect(getAvatarNetworkColor(SEPOLIA_DISPLAY_NAME)).toStrictEqual(
BackgroundColor.sepolia,
);
});
it('should return undefined', () => {
expect(getAvatarNetworkColor(MAINNET_DISPLAY_NAME)).toStrictEqual(
undefined,
);
});
});
});

View File

@ -71,8 +71,6 @@ const t = (key) => {
return 'Reveal Secret Recovery Phrase';
case 'showIncomingTransactions':
return 'Show incoming transactions';
case 'showIncomingTransactionsDescription':
return 'Select this to use Etherscan to show incoming transactions in the transactions list';
case 'usePhishingDetection':
return 'Use phishing detection';
case 'usePhishingDetectionDescription':

View File

@ -19,7 +19,7 @@ import {
ONBOARDING_PIN_EXTENSION_ROUTE,
ONBOARDING_METAMETRICS,
} from '../../helpers/constants/routes';
import { NETWORK_TYPES } from '../../../shared/constants/network';
import { CHAIN_IDS, NETWORK_TYPES } from '../../../shared/constants/network';
import {
createNewVaultAndGetSeedPhrase,
unlockAndGetSeedPhrase,
@ -41,6 +41,9 @@ describe('Onboarding Flow', () => {
type: NETWORK_TYPES.GOERLI,
chainId: '0x0',
},
incomingTransactionsPreferences: {
[CHAIN_IDS.MAINNET]: true,
},
},
localeMessages: {
currentLocale: 'en',

View File

@ -23,9 +23,7 @@
&__settings {
display: flex;
justify-content: center;
flex-direction: column;
text-align: center;
max-width: 620px;
margin-bottom: 20px;

View File

@ -27,10 +27,9 @@ import {
} from '../../../helpers/constants/design-system';
import { ONBOARDING_PIN_EXTENSION_ROUTE } from '../../../helpers/constants/routes';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { getCurrentNetwork } from '../../../selectors';
import { getAllNetworks, getCurrentNetwork } from '../../../selectors';
import {
setCompletedOnboarding,
setFeatureFlag,
setIpfsGateway,
setUseCurrencyRateCheck,
setUseMultiAccountBalanceChecker,
@ -40,19 +39,23 @@ import {
setUseAddressBarEnsResolution,
showModal,
toggleNetworkMenu,
setIncomingTransactionsPreferences,
} from '../../../store/actions';
import IncomingTransactionToggle from '../../../components/app/incoming-trasaction-toggle/incoming-transaction-toggle';
import { Setting } from './setting';
export default function PrivacySettings() {
const t = useI18nContext();
const dispatch = useDispatch();
const history = useHistory();
const { incomingTransactionsPreferences } = useSelector(
(state) => state.metamask,
);
const [usePhishingDetection, setUsePhishingDetection] = useState(true);
const [turnOn4ByteResolution, setTurnOn4ByteResolution] = useState(true);
const [turnOnTokenDetection, setTurnOnTokenDetection] = useState(true);
const [turnOnCurrencyRateCheck, setTurnOnCurrencyRateCheck] = useState(true);
const [showIncomingTransactions, setShowIncomingTransactions] =
useState(true);
const [
isMultiAccountBalanceCheckerEnabled,
setMultiAccountBalanceCheckerEnabled,
@ -63,11 +66,9 @@ export default function PrivacySettings() {
const trackEvent = useContext(MetaMetricsContext);
const currentNetwork = useSelector(getCurrentNetwork);
const allNetworks = useSelector(getAllNetworks);
const handleSubmit = () => {
dispatch(
setFeatureFlag('showIncomingTransactions', showIncomingTransactions),
);
dispatch(setUsePhishDetect(usePhishingDetection));
dispatch(setUse4ByteResolution(turnOn4ByteResolution));
dispatch(setUseTokenDetection(turnOnTokenDetection));
@ -87,7 +88,7 @@ export default function PrivacySettings() {
category: MetaMetricsEventCategory.Onboarding,
event: MetaMetricsEventName.OnboardingWalletAdvancedSettings,
properties: {
show_incoming_tx: showIncomingTransactions,
show_incoming_tx: incomingTransactionsPreferences,
use_phising_detection: usePhishingDetection,
turnon_token_detection: turnOnTokenDetection,
},
@ -124,28 +125,12 @@ export default function PrivacySettings() {
className="privacy-settings__settings"
data-testid="privacy-settings-settings"
>
<Setting
value={showIncomingTransactions}
setValue={setShowIncomingTransactions}
title={t('showIncomingTransactions')}
description={t('onboardingShowIncomingTransactionsDescription', [
<a
key="etherscan"
href="https://etherscan.io/"
target="_blank"
rel="noreferrer"
>
{t('etherscan')}
</a>,
<a
href="https://etherscan.io/privacyPolicy"
target="_blank"
rel="noreferrer"
key="privacyMsg"
>
{t('privacyMsg')}
</a>,
])}
<IncomingTransactionToggle
allNetworks={allNetworks}
setIncomingTransactionsPreferences={(chainId, value) =>
dispatch(setIncomingTransactionsPreferences(chainId, value))
}
incomingTransactionsPreferences={incomingTransactionsPreferences}
/>
<Setting
value={usePhishingDetection}

View File

@ -6,6 +6,7 @@ import {
renderWithProvider,
setBackgroundConnection,
} from '../../../../test/jest';
import { CHAIN_IDS } from '../../../../shared/constants/network';
import PrivacySettings from './privacy-settings';
describe('Privacy Settings Onboarding View', () => {
@ -15,6 +16,13 @@ describe('Privacy Settings Onboarding View', () => {
providerConfig: {
type: 'test',
},
incomingTransactionsPreferences: {
[CHAIN_IDS.MAINNET]: true,
[CHAIN_IDS.LINEA_MAINNET]: false,
[CHAIN_IDS.GOERLI]: false,
[CHAIN_IDS.SEPOLIA]: false,
[CHAIN_IDS.LINEA_GOERLI]: true,
},
},
};
@ -30,6 +38,7 @@ describe('Privacy Settings Onboarding View', () => {
.mockImplementation(() => Promise.resolve());
const setUseMultiAccountBalanceCheckerStub = jest.fn();
const setUseAddressBarEnsResolutionStub = jest.fn();
const setIncomingTransactionsPreferencesStub = jest.fn();
setBackgroundConnection({
setFeatureFlag: setFeatureFlagStub,
@ -41,6 +50,7 @@ describe('Privacy Settings Onboarding View', () => {
completeOnboarding: completeOnboardingStub,
setUseMultiAccountBalanceChecker: setUseMultiAccountBalanceCheckerStub,
setUseAddressBarEnsResolution: setUseAddressBarEnsResolutionStub,
setIncomingTransactionsPreferences: setIncomingTransactionsPreferencesStub,
});
it('should update preferences', () => {
@ -49,28 +59,28 @@ describe('Privacy Settings Onboarding View', () => {
store,
);
// All settings are initialized toggled to true
expect(setFeatureFlagStub).toHaveBeenCalledTimes(0);
expect(setUsePhishDetectStub).toHaveBeenCalledTimes(0);
expect(setUse4ByteResolutionStub).toHaveBeenCalledTimes(0);
expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(0);
expect(setUseMultiAccountBalanceCheckerStub).toHaveBeenCalledTimes(0);
expect(setUseCurrencyRateCheckStub).toHaveBeenCalledTimes(0);
expect(setUseAddressBarEnsResolutionStub).toHaveBeenCalledTimes(0);
expect(setIncomingTransactionsPreferencesStub).toHaveBeenCalledTimes(0);
const toggles = container.querySelectorAll('input[type=checkbox]');
const submitButton = getByText('Done');
// toggle to false
fireEvent.click(toggles[0]);
fireEvent.click(toggles[1]);
fireEvent.click(toggles[2]);
fireEvent.click(toggles[3]);
fireEvent.click(toggles[4]);
fireEvent.click(toggles[5]);
fireEvent.click(toggles[6]);
fireEvent.click(toggles[7]);
fireEvent.click(toggles[8]);
fireEvent.click(toggles[9]);
fireEvent.click(toggles[10]);
fireEvent.click(toggles[11]);
fireEvent.click(submitButton);
expect(setFeatureFlagStub).toHaveBeenCalledTimes(1);
expect(setIncomingTransactionsPreferencesStub).toHaveBeenCalledTimes(1);
expect(setUsePhishDetectStub).toHaveBeenCalledTimes(1);
expect(setUse4ByteResolutionStub).toHaveBeenCalledTimes(1);
expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(1);
@ -78,7 +88,12 @@ describe('Privacy Settings Onboarding View', () => {
expect(setUseCurrencyRateCheckStub).toHaveBeenCalledTimes(1);
expect(setUseAddressBarEnsResolutionStub).toHaveBeenCalledTimes(1);
expect(setFeatureFlagStub.mock.calls[0][1]).toStrictEqual(false);
expect(setIncomingTransactionsPreferencesStub).toHaveBeenCalledWith(
CHAIN_IDS.MAINNET,
false,
expect.anything(),
);
expect(setUsePhishDetectStub.mock.calls[0][0]).toStrictEqual(false);
expect(setUse4ByteResolutionStub.mock.calls[0][0]).toStrictEqual(false);
expect(setUseTokenDetectionStub.mock.calls[0][0]).toStrictEqual(false);
@ -89,35 +104,6 @@ describe('Privacy Settings Onboarding View', () => {
expect(setUseAddressBarEnsResolutionStub.mock.calls[0][0]).toStrictEqual(
false,
);
// toggle back to true
fireEvent.click(toggles[0]);
fireEvent.click(toggles[1]);
fireEvent.click(toggles[2]);
fireEvent.click(toggles[3]);
fireEvent.click(toggles[4]);
fireEvent.click(toggles[5]);
fireEvent.click(toggles[6]);
fireEvent.click(submitButton);
expect(setFeatureFlagStub).toHaveBeenCalledTimes(2);
expect(setUsePhishDetectStub).toHaveBeenCalledTimes(2);
expect(setUse4ByteResolutionStub).toHaveBeenCalledTimes(2);
expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(2);
expect(setUseMultiAccountBalanceCheckerStub).toHaveBeenCalledTimes(2);
expect(setUseCurrencyRateCheckStub).toHaveBeenCalledTimes(2);
expect(setUseAddressBarEnsResolutionStub).toHaveBeenCalledTimes(2);
expect(setFeatureFlagStub.mock.calls[1][1]).toStrictEqual(true);
expect(setUsePhishDetectStub.mock.calls[1][0]).toStrictEqual(true);
expect(setUse4ByteResolutionStub.mock.calls[1][0]).toStrictEqual(true);
expect(setUseTokenDetectionStub.mock.calls[1][0]).toStrictEqual(true);
expect(setUseMultiAccountBalanceCheckerStub.mock.calls[1][0]).toStrictEqual(
true,
);
expect(setUseCurrencyRateCheckStub.mock.calls[1][0]).toStrictEqual(true);
expect(setUseAddressBarEnsResolutionStub.mock.calls[1][0]).toStrictEqual(
true,
);
});
describe('IPFS', () => {

View File

@ -7,7 +7,6 @@ import {
TextVariant,
AlignItems,
Display,
FontWeight,
} from '../../../helpers/constants/design-system';
import { useI18nContext } from '../../../hooks/useI18nContext';
@ -30,10 +29,8 @@ export const Setting = ({
className="privacy-settings__setting__wrapper"
>
<div className="privacy-settings__setting">
<Text variant={TextVariant.bodyLgMedium} fontWeight={FontWeight.Bold}>
{title}
</Text>
<Text variant={TextVariant.bodyMd} as="div">
<Text variant={TextVariant.bodyMdMedium}>{title}</Text>
<Text variant={TextVariant.bodySm} as="div">
{description}
</Text>
</div>

View File

@ -70,7 +70,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = `
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="advanced-setting-hex-data"
>
<div
@ -136,7 +136,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = `
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="advanced-setting-show-testnet-conversion"
>
<div
@ -202,7 +202,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = `
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="advanced-setting-show-testnet-conversion"
>
<div
@ -268,7 +268,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = `
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="advanced-setting-custom-nonce"
>
<div
@ -520,7 +520,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = `
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="advanced-setting-dismiss-reminder"
>
<div

View File

@ -262,6 +262,7 @@ export default class AdvancedTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
data-testid="advanced-setting-hex-data"
>
<div className="settings-page__content-item">
@ -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"
>
<div className="settings-page__content-item">
@ -329,6 +331,7 @@ export default class AdvancedTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('showTestnetNetworks')}</span>
@ -361,6 +364,7 @@ export default class AdvancedTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('nonceField')}</span>
@ -551,6 +555,7 @@ export default class AdvancedTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('dismissReminderField')}</span>

View File

@ -273,8 +273,6 @@
min-width: 0;
display: flex;
flex-direction: column;
font-size: 16px;
font-weight: 500;
@include screen-sm-max {
height: initial;

View File

@ -47,7 +47,7 @@ exports[`Security Tab should match snapshot 1`] = `
class="settings-page__content-padded"
>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
>
<div
class="settings-page__content-item"
@ -124,7 +124,7 @@ exports[`Security Tab should match snapshot 1`] = `
class="settings-page__content-padded"
>
<div
class="settings-page__content-row"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
>
<div
class="settings-page__content-item"
@ -138,9 +138,6 @@ exports[`Security Tab should match snapshot 1`] = `
To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contract that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared.
</div>
</div>
<div
class="settings-page__content-item"
>
<div
class="settings-page__content-item-col"
data-testid="4byte-resolution-container"
@ -193,7 +190,6 @@ exports[`Security Tab should match snapshot 1`] = `
</div>
</div>
</div>
</div>
<span
class="settings-page__security-tab-sub-header"
>
@ -203,7 +199,7 @@ exports[`Security Tab should match snapshot 1`] = `
class="settings-page__content-padded"
>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
>
<div
class="settings-page__content-item"
@ -297,44 +293,27 @@ exports[`Security Tab should match snapshot 1`] = `
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box mm-incoming-transaction-toggle"
>
<div
class="settings-page__content-item"
<p
class="mm-box mm-text mm-text--body-md-medium mm-box--color-text-default"
>
<span>
Show incoming transactions
</span>
</p>
<p
class="mm-box mm-text mm-text--body-sm mm-box--color-text-alternative"
>
This relies on each network which will have access to your Ethereum address and your IP address.
</p>
<div
class="settings-page__content-description"
class="mm-box mm-box--margin-top-3 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="incoming-transaction-toggle-enable-all"
>
<span>
This relies on
<a
href="https://etherscan.io/privacyPolicy"
rel="noopener noreferrer"
target="_blank"
>
Etherscan
</a>
which will have access to your Ethereum address and your IP address.
<a
href="https://consensys.net/privacy-policy/"
rel="noopener noreferrer"
target="_blank"
>
Privacy policy
</a>
</span>
</div>
</div>
<div
class="settings-page__content-item-col"
data-testid="showIncomingTransactions"
<p
class="mm-box mm-text mm-text--body-sm-bold mm-box--color-text-default"
>
Enable for all networks
</p>
<label
class="toggle-button toggle-button--off"
tabindex="0"
@ -381,6 +360,334 @@ exports[`Security Tab should match snapshot 1`] = `
</div>
</label>
</div>
<div
class="mm-box network-toggle-wrapper mm-box--margin-top-6 mm-box--margin-bottom-6 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="network-toggle-0x1"
>
<div
class="mm-box mm-box--display-flex mm-box--gap-2 mm-box--align-items-center mm-box--width-full mm-box--background-color-transparent"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-sm mm-avatar-network mm-text--body-sm mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-transparent box--border-style-solid box--border-width-1"
>
C
</div>
<p
class="mm-box mm-text mm-text--body-md mm-text--ellipsis mm-box--margin-left-2 mm-box--color-text-default mm-box--background-color-transparent"
>
Custom Mainnet RPC
</p>
</div>
<label
class="toggle-button toggle-button--on"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 0;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(3, 125, 214); left: 18px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="true"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
<div
class="mm-box network-toggle-wrapper mm-box--margin-top-6 mm-box--margin-bottom-6 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="network-toggle-0xe708"
>
<div
class="mm-box mm-box--display-flex mm-box--gap-2 mm-box--align-items-center mm-box--width-full mm-box--background-color-transparent"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-sm mm-avatar-network mm-text--body-sm mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-transparent box--border-style-solid box--border-width-1"
>
<img
alt="Linea Mainnet logo"
class="mm-avatar-network__network-image"
src="./images/linea-logo-mainnet.png"
/>
</div>
<p
class="mm-box mm-text mm-text--body-md mm-text--ellipsis mm-box--margin-left-2 mm-box--color-text-default mm-box--background-color-transparent"
>
Linea Mainnet
</p>
</div>
<label
class="toggle-button toggle-button--off"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 1;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(106, 115, 125); left: 3px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="false"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
<div
class="mm-box network-toggle-wrapper mm-box--margin-top-6 mm-box--margin-bottom-6 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="network-toggle-0x5"
>
<div
class="mm-box mm-box--display-flex mm-box--gap-2 mm-box--align-items-center mm-box--width-full mm-box--background-color-transparent"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-sm mm-avatar-network mm-text--body-sm mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-transparent box--border-style-solid box--border-width-1"
>
G
</div>
<p
class="mm-box mm-text mm-text--body-md mm-text--ellipsis mm-box--margin-left-2 mm-box--color-text-default mm-box--background-color-transparent"
>
Goerli
</p>
</div>
<label
class="toggle-button toggle-button--off"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 1;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(106, 115, 125); left: 3px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="false"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
<div
class="mm-box network-toggle-wrapper mm-box--margin-top-6 mm-box--margin-bottom-6 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="network-toggle-0xaa36a7"
>
<div
class="mm-box mm-box--display-flex mm-box--gap-2 mm-box--align-items-center mm-box--width-full mm-box--background-color-transparent"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-sm mm-avatar-network mm-text--body-sm mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-transparent box--border-style-solid box--border-width-1"
>
S
</div>
<p
class="mm-box mm-text mm-text--body-md mm-text--ellipsis mm-box--margin-left-2 mm-box--color-text-default mm-box--background-color-transparent"
>
Sepolia
</p>
</div>
<label
class="toggle-button toggle-button--on"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 0;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(3, 125, 214); left: 18px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="true"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
<div
class="mm-box network-toggle-wrapper mm-box--margin-top-6 mm-box--margin-bottom-6 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="network-toggle-0xe704"
>
<div
class="mm-box mm-box--display-flex mm-box--gap-2 mm-box--align-items-center mm-box--width-full mm-box--background-color-transparent"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-sm mm-avatar-network mm-text--body-sm mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-transparent box--border-style-solid box--border-width-1"
>
<img
alt="Linea Goerli logo"
class="mm-avatar-network__network-image"
src="./images/linea-logo-testnet.png"
/>
</div>
<p
class="mm-box mm-text mm-text--body-md mm-text--ellipsis mm-box--margin-left-2 mm-box--color-text-default mm-box--background-color-transparent"
>
Linea Goerli
</p>
</div>
<label
class="toggle-button toggle-button--on"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 0;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(3, 125, 214); left: 18px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="true"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
</div>
</div>
<span
@ -392,7 +699,7 @@ exports[`Security Tab should match snapshot 1`] = `
class="settings-page__content-padded"
>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-column"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column"
data-testid="advanced-setting-choose-your-network"
>
<div
@ -431,8 +738,11 @@ exports[`Security Tab should match snapshot 1`] = `
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-column"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column"
data-testid="setting-ipfs-gateway"
>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
>
<div
class="settings-page__content-item"
@ -496,6 +806,7 @@ exports[`Security Tab should match snapshot 1`] = `
</div>
</label>
</div>
</div>
<div
class="settings-page__content-item"
>
@ -524,7 +835,7 @@ exports[`Security Tab should match snapshot 1`] = `
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--margin-top-3 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--margin-top-3 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
id="ens-domains"
>
<div>
@ -626,7 +937,7 @@ exports[`Security Tab should match snapshot 1`] = `
class="settings-page__content-padded"
>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
data-testid="advanced-setting-gas-fee-estimation"
id="advanced-settings-autodetect-tokens"
>
@ -706,7 +1017,7 @@ exports[`Security Tab should match snapshot 1`] = `
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
>
<div
class="settings-page__content-item"
@ -772,7 +1083,7 @@ exports[`Security Tab should match snapshot 1`] = `
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
>
<div
class="settings-page__content-item"
@ -786,9 +1097,6 @@ exports[`Security Tab should match snapshot 1`] = `
Displaying NFT media and data exposes your IP address to OpenSea or other third parties. This can allow attackers to associate your IP address with your Ethereum address. NFT autodetection relies on this setting, and won't be available when this is turned off.
</div>
</div>
<div
class="settings-page__content-item"
>
<div
class="settings-page__content-item-col"
data-testid="enableOpenSeaAPI"
@ -840,9 +1148,8 @@ exports[`Security Tab should match snapshot 1`] = `
</label>
</div>
</div>
</div>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
>
<div
class="settings-page__content-item"
@ -939,7 +1246,7 @@ exports[`Security Tab should match snapshot 1`] = `
class="settings-page__content-padded"
>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between"
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
>
<div
class="settings-page__content-item"

View File

@ -17,7 +17,6 @@ import {
COINGECKO_LINK,
CONSENSYS_PRIVACY_LINK,
CRYPTOCOMPARE_LINK,
ETHERSCAN_PRIVACY_LINK,
PRIVACY_POLICY_LINK,
} from '../../../../shared/lib/ui-utils';
import SRPQuiz from '../../../components/app/srp-quiz-modal/SRPQuiz';
@ -42,6 +41,8 @@ import {
handleSettingsRefs,
} from '../../../helpers/utils/settings-search';
import IncomingTransactionToggle from '../../../components/app/incoming-trasaction-toggle/incoming-transaction-toggle';
export default class SecurityTab extends PureComponent {
static contextTypes = {
t: PropTypes.func,
@ -57,8 +58,9 @@ export default class SecurityTab extends PureComponent {
setUseNftDetection: PropTypes.func,
participateInMetaMetrics: PropTypes.bool.isRequired,
setParticipateInMetaMetrics: PropTypes.func.isRequired,
showIncomingTransactions: PropTypes.bool.isRequired,
setShowIncomingTransactionsFeatureFlag: PropTypes.func.isRequired,
incomingTransactionsPreferences: PropTypes.object.isRequired,
allNetworks: PropTypes.array.isRequired,
setIncomingTransactionsPreferences: PropTypes.func.isRequired,
setUsePhishDetect: PropTypes.func.isRequired,
usePhishDetect: PropTypes.bool.isRequired,
setUse4ByteResolution: PropTypes.func.isRequired,
@ -170,55 +172,19 @@ export default class SecurityTab extends PureComponent {
}
renderIncomingTransactionsOptIn() {
const { t } = this.context;
const { showIncomingTransactions, setShowIncomingTransactionsFeatureFlag } =
this.props;
const {
incomingTransactionsPreferences,
allNetworks,
setIncomingTransactionsPreferences,
} = this.props;
return (
<Box
ref={this.settingsRefs[1]}
className="settings-page__content-row"
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
>
<div className="settings-page__content-item">
<span>{t('showIncomingTransactions')}</span>
<div className="settings-page__content-description">
{t('showIncomingTransactionsDescription', [
// TODO: Update to use real link
<a
href={ETHERSCAN_PRIVACY_LINK}
target="_blank"
rel="noopener noreferrer"
key="etherscan-privacy-link"
>
{t('etherscan')}
</a>,
// TODO: Update to use real link
<a
href={CONSENSYS_PRIVACY_LINK}
target="_blank"
rel="noopener noreferrer"
key="ic-consensys-privacy-link"
>
{t('privacyMsg')}
</a>,
])}
</div>
</div>
<div
className="settings-page__content-item-col"
data-testid="showIncomingTransactions"
>
<ToggleButton
value={showIncomingTransactions}
onToggle={(value) => setShowIncomingTransactionsFeatureFlag(!value)}
offLabel={t('off')}
onLabel={t('on')}
<IncomingTransactionToggle
wrapperRef={this.settingsRefs[1]}
allNetworks={allNetworks}
setIncomingTransactionsPreferences={setIncomingTransactionsPreferences}
incomingTransactionsPreferences={incomingTransactionsPreferences}
/>
</div>
</Box>
);
}
@ -233,6 +199,7 @@ export default class SecurityTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('usePhishingDetection')}</span>
@ -260,14 +227,21 @@ export default class SecurityTab extends PureComponent {
const { t } = this.context;
const { use4ByteResolution, setUse4ByteResolution } = this.props;
return (
<div ref={this.settingsRefs[3]} className="settings-page__content-row">
<Box
ref={this.settingsRefs[3]}
className="settings-page__content-row"
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('use4ByteResolution')}</span>
<div className="settings-page__content-description">
{t('use4ByteResolutionDescription')}
</div>
</div>
<div className="settings-page__content-item">
<div
className="settings-page__content-item-col"
data-testid="4byte-resolution-container"
@ -279,8 +253,7 @@ export default class SecurityTab extends PureComponent {
onLabel={t('on')}
/>
</div>
</div>
</div>
</Box>
);
}
@ -296,6 +269,7 @@ export default class SecurityTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('participateInMetaMetrics')}</span>
@ -329,6 +303,7 @@ export default class SecurityTab extends PureComponent {
data-testid="advanced-setting-choose-your-network"
display={Display.Flex}
flexDirection={FlexDirection.Column}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('chooseYourNetwork')}</span>
@ -408,6 +383,14 @@ export default class SecurityTab extends PureComponent {
data-testid="setting-ipfs-gateway"
display={Display.Flex}
flexDirection={FlexDirection.Column}
gap={4}
>
<Box
className="settings-page__content-row"
gap={4}
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
>
<div className="settings-page__content-item">
<span>{t('ipfsGateway')}</span>
@ -436,6 +419,7 @@ export default class SecurityTab extends PureComponent {
onLabel={t('on')}
/>
</div>
</Box>
{this.state.ipfsToggle && (
<div className="settings-page__content-item">
<span>{t('addIPFSGateway')}</span>
@ -456,6 +440,7 @@ export default class SecurityTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
ref={this.settingsRefs[10]}
marginTop={3}
id="ens-domains"
@ -531,6 +516,7 @@ export default class SecurityTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
id="advanced-settings-autodetect-tokens"
>
<div className="settings-page__content-item">
@ -584,6 +570,7 @@ export default class SecurityTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('useMultiAccountBalanceChecker')}</span>
@ -625,6 +612,7 @@ export default class SecurityTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('currencyRateCheckToggle')}</span>
@ -673,7 +661,7 @@ export default class SecurityTab extends PureComponent {
);
}
renderOpenSeaEnabledToggle() {
renderDisplayNftMediaToggle() {
const { t } = this.context;
const {
openSeaEnabled,
@ -689,6 +677,7 @@ export default class SecurityTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('displayNftMedia')}</span>
@ -696,7 +685,7 @@ export default class SecurityTab extends PureComponent {
{t('displayNftMediaDescription')}
</div>
</div>
<div className="settings-page__content-item">
<div
className="settings-page__content-item-col"
data-testid="enableOpenSeaAPI"
@ -722,7 +711,6 @@ export default class SecurityTab extends PureComponent {
onLabel={t('on')}
/>
</div>
</div>
</Box>
);
}
@ -742,6 +730,7 @@ export default class SecurityTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
gap={4}
>
<div className="settings-page__content-item">
<span>{t('useNftDetection')}</span>
@ -837,7 +826,7 @@ export default class SecurityTab extends PureComponent {
<div className="settings-page__content-padded">
{this.renderAutoDetectTokensToggle()}
{this.renderBatchAccountBalanceRequestsToggle()}
{this.renderOpenSeaEnabledToggle()}
{this.renderDisplayNftMediaToggle()}
{this.renderNftDetectionToggle()}
</div>
<span className="settings-page__security-tab-sub-header">

View File

@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { compose } from 'redux';
import {
setFeatureFlag,
setIncomingTransactionsPreferences,
setIpfsGateway,
setParticipateInMetaMetrics,
setUseCurrencyRateCheck,
@ -14,6 +14,7 @@ import {
setUseNftDetection,
setUse4ByteResolution,
} from '../../../store/actions';
import { getAllNetworks } from '../../../selectors';
import SecurityTab from './security-tab.component';
const mapStateToProps = (state) => {
@ -21,8 +22,9 @@ const mapStateToProps = (state) => {
appState: { warning },
metamask,
} = state;
const {
featureFlags: { showIncomingTransactions } = {},
incomingTransactionsPreferences,
participateInMetaMetrics,
usePhishDetect,
useTokenDetection,
@ -35,9 +37,12 @@ const mapStateToProps = (state) => {
use4ByteResolution,
} = metamask;
const allNetworks = getAllNetworks(state);
return {
warning,
showIncomingTransactions,
incomingTransactionsPreferences,
allNetworks,
participateInMetaMetrics,
usePhishDetect,
useTokenDetection,
@ -53,10 +58,10 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => {
return {
setIncomingTransactionsPreferences: (chainId, value) =>
dispatch(setIncomingTransactionsPreferences(chainId, value)),
setParticipateInMetaMetrics: (val) =>
dispatch(setParticipateInMetaMetrics(val)),
setShowIncomingTransactionsFeatureFlag: (shouldShow) =>
dispatch(setFeatureFlag('showIncomingTransactions', shouldShow)),
setUsePhishDetect: (val) => dispatch(setUsePhishDetect(val)),
setUseCurrencyRateCheck: (val) => dispatch(setUseCurrencyRateCheck(val)),
setUseTokenDetection: (value) => {

View File

@ -20,7 +20,6 @@ jest.mock('../../../../app/scripts/lib/util', () => {
});
describe('Security Tab', () => {
delete mockState.metamask.featureFlags; // Unset featureFlags in order to test the default value
mockState.appState.warning = 'warning'; // This tests an otherwise untested render branch
const mockStore = configureMockStore([thunk])(mockState);
@ -91,10 +90,6 @@ describe('Security Tab', () => {
expect(await toggleCheckbox('currencyRateCheckToggle', true)).toBe(true);
});
it('toggles incoming txs', async () => {
expect(await toggleCheckbox('showIncomingTransactions', false)).toBe(true);
});
it('should toggle token detection', async () => {
expect(await toggleCheckbox('autoDetectTokens', true)).toBe(true);
});

View File

@ -80,11 +80,11 @@ const getStateTree = ({
},
unapprovedMsgs,
selectedAddress: SENDERS.ONE,
featureFlags: {
showIncomingTransactions: true,
},
featureFlags: {},
transactions: [...incomingTxList],
incomingTransactions: [...incomingTxList],
currentNetworkTxList: [...txList],
incomingTransactionsPreferences: {},
},
});

View File

@ -26,9 +26,8 @@ const INVALID_INITIAL_TRANSACTION_TYPES = [
];
export const incomingTxListSelector = (state) => {
const { showIncomingTransactions } = state.metamask.featureFlags;
if (!showIncomingTransactions) {
const { incomingTransactionsPreferences } = state.metamask;
if (!incomingTransactionsPreferences) {
return [];
}

View File

@ -113,9 +113,7 @@ describe('Transaction Selectors', () => {
nickname: 'mainnet',
chainId: CHAIN_IDS.MAINNET,
},
featureFlags: {
showIncomingTransactions: false,
},
featureFlags: {},
selectedAddress: '0xAddress',
currentNetworkTxList: [
{
@ -178,9 +176,7 @@ describe('Transaction Selectors', () => {
chainId: CHAIN_IDS.MAINNET,
},
selectedAddress: '0xAddress',
featureFlags: {
showIncomingTransactions: false,
},
featureFlags: {},
currentNetworkTxList: [tx1, tx2],
},
};
@ -262,9 +258,7 @@ describe('Transaction Selectors', () => {
chainId: CHAIN_IDS.MAINNET,
},
selectedAddress: '0xAddress',
featureFlags: {
showIncomingTransactions: false,
},
featureFlags: {},
currentNetworkTxList: [
submittedTx,
unapprovedTx,

View File

@ -2693,7 +2693,7 @@ interface TemporaryFeatureFlagDef {
[feature: string]: boolean;
}
interface TemporaryPreferenceFlagDef {
[preference: string]: boolean;
[preference: string]: boolean | object;
}
export function setFeatureFlag(
@ -2729,7 +2729,7 @@ export function setFeatureFlag(
export function setPreference(
preference: string,
value: boolean | string,
value: boolean | string | object,
): ThunkAction<
Promise<TemporaryPreferenceFlagDef>,
MetaMaskReduxState,
@ -2744,13 +2744,11 @@ export function setPreference(
[preference, value],
(err, updatedPreferences) => {
dispatch(hideLoadingIndication());
if (err) {
dispatch(displayWarning(err));
reject(err);
return;
}
resolve(updatedPreferences as TemporaryPreferenceFlagDef);
},
);
@ -2789,6 +2787,21 @@ export function setAutoLockTimeLimit(value: boolean) {
return setPreference('autoLockTimeLimit', value);
}
export function setIncomingTransactionsPreferences(
chainId: string,
value: boolean,
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
return async (dispatch: MetaMaskReduxDispatch) => {
dispatch(showLoadingIndication());
log.debug(`background.setIncomingTransactionsPreferences`);
await submitRequestToBackground('setIncomingTransactionsPreferences', [
chainId,
value,
]);
dispatch(hideLoadingIndication());
};
}
export function setCompletedOnboarding(): ThunkAction<
void,
MetaMaskReduxState,

View File

@ -206,9 +206,7 @@ describe('#updateCustodyState', () => {
nickname: 'mainnet',
chainId: '0x1',
},
featureFlags: {
showIncomingTransactions: false,
},
featureFlags: {},
selectedAddress: '0xAddress',
};
@ -232,9 +230,7 @@ describe('#updateCustodyState', () => {
nickname: 'mainnet',
chainId: '0x1',
},
featureFlags: {
showIncomingTransactions: false,
},
featureFlags: {},
selectedAddress: '0xAddress',
currentNetworkTxList: [
{
@ -287,9 +283,7 @@ describe('#updateCustodyState', () => {
nickname: 'mainnet',
chainId: '0x1',
},
featureFlags: {
showIncomingTransactions: false,
},
featureFlags: {},
selectedAddress: '0xAddress',
currentNetworkTxList: [
{