diff --git a/.circleci/scripts/chrome-install.sh b/.circleci/scripts/chrome-install.sh index fd20877f8..ccf458111 100755 --- a/.circleci/scripts/chrome-install.sh +++ b/.circleci/scripts/chrome-install.sh @@ -5,12 +5,12 @@ set -u set -o pipefail # To get the latest version, see -CHROME_VERSION='93.0.4577.63-1' +CHROME_VERSION='95.0.4638.69-1' CHROME_BINARY="google-chrome-stable_${CHROME_VERSION}_amd64.deb" CHROME_BINARY_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/${CHROME_BINARY}" # To retrieve this checksum, run the `wget` and `shasum` commands below -CHROME_BINARY_SHA512SUM='4102ba417b41820da68b7e8e12018ed2268f30e0210f8f227aeeabf6bd9265dd95ad206993d5626ac1c70a07185fd3ed4eef8a71ee2f5b0770015302c0d26f58' +CHROME_BINARY_SHA512SUM='f07d16ec0a41120c40064d030e9e5240ed740b9b24c50eaede7b9bfd9a9678821c0252b40bfcd57e933a708b08d761482c3be5b3006eee605c41f5dc9e21f456' wget -O "${CHROME_BINARY}" -t 5 "${CHROME_BINARY_URL}" diff --git a/.depcheckrc.yml b/.depcheckrc.yml index c99ca2f85..15f3c91b9 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -1,49 +1,48 @@ # things that *are* used, that depcheck is wrong about ignores: - # + # # webapp deps - # + # - - "@babel/runtime" - - "@fortawesome/fontawesome-free" - - "punycode" - - # + - '@babel/runtime' + - '@fortawesome/fontawesome-free' + - 'punycode' + + # # dev deps # # safety fallback for npm lifecycle scripts, not used normally - - "@lavamoat/preinstall-always-fail" + - '@lavamoat/preinstall-always-fail' # used in testing + ci - - "@metamask/auto-changelog" # invoked as `auto-changelog` - - "@metamask/forwarder" - - "@metamask/test-dapp" - - "@sentry/cli" # invoked as `sentry-cli` - - "chromedriver" - - "depcheck" # ooo meta - - "ganache-cli" - - "geckodriver" - - "jest" - - "lavamoat-viz" - - "prettier-plugin-sort-json" # automatically imported by prettier - - "source-map-explorer" + - '@metamask/auto-changelog' # invoked as `auto-changelog` + - '@metamask/forwarder' + - '@metamask/test-dapp' + - '@sentry/cli' # invoked as `sentry-cli` + - 'chromedriver' + - 'depcheck' # ooo meta + - 'ganache-cli' + - 'geckodriver' + - 'jest' + - 'lavamoat-viz' + - 'prettier-plugin-sort-json' # automatically imported by prettier + - 'source-map-explorer' # development tool - - "yarn-deduplicate" - - "improved-yarn-audit" + - 'yarn-deduplicate' + - 'improved-yarn-audit' # storybook - - "@storybook/core" - - "@storybook/addon-backgrounds" - - "@storybook/addon-toolbars" - - "style-loader" - - "css-loader" - - "sass-loader" - - "resolve-url-loader" + - '@storybook/core' + - '@storybook/addon-essentials' + - '@storybook/addon-a11y' + - 'style-loader' + - 'css-loader' + - 'sass-loader' + - 'resolve-url-loader' -# files depcheck should not parse +# files depcheck should not parse ignorePatterns: # seems to incorrectly parse scss @include pragmas? - - "**/*.scss" + - '**/*.scss' # self-contained bundle used for testing - - "**/send-eth-with-private-key-test/web3js.js" - - "**/send-eth-with-private-key-test/ethereumjs-tx.js" - + - '**/send-eth-with-private-key-test/web3js.js' + - '**/send-eth-with-private-key-test/ethereumjs-tx.js' diff --git a/.eslintrc.js b/.eslintrc.js index b2967de39..17b766601 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,5 @@ +const { version: reactVersion } = require('react/package.json'); + module.exports = { root: true, parser: '@babel/eslint-parser', @@ -189,9 +191,10 @@ module.exports = { 'app/scripts/lockdown-more.js', 'development/**/*.js', 'test/e2e/**/*.js', - 'test/lib/wait-until-called.js', 'test/env.js', 'test/setup.js', + 'test/helpers/protect-intrinsics-helpers.js', + 'test/lib/wait-until-called.js', 'jest.config.js', ], parserOptions: { @@ -202,6 +205,7 @@ module.exports = { files: [ 'app/scripts/lockdown-run.js', 'app/scripts/lockdown-more.js', + 'test/helpers/protect-intrinsics-helpers.js', 'test/unit-global/protect-intrinsics.test.js', ], globals: { @@ -213,7 +217,12 @@ module.exports = { settings: { react: { - version: 'detect', + // If this is set to 'detect', ESLint will import React in order to find + // its version. Because we run ESLint in the build system under LavaMoat, + // this means that detecting the React version requires a LavaMoat policy + // for all of React, in the build system. That's a no-go, so we grab it + // from React's package.json. + version: reactVersion, }, }, }; diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..f945eb7b7 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ develop, Version-v*, cla-signatures, master, snaps ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ develop ] + schedule: + - cron: '28 12 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.storybook/i18n-party-addon/register.js b/.storybook/i18n-party-addon/register.js index 20175a80e..bfa482045 100644 --- a/.storybook/i18n-party-addon/register.js +++ b/.storybook/i18n-party-addon/register.js @@ -1,37 +1,41 @@ // import { useGlobals } from '@storybook/api'; -const { useGlobals } = require('@storybook/api') -const React = require("react") -const { addons, types } = require("@storybook/addons") -const { Icons, IconButton } = require('@storybook/components') -const localeList = require('../../app/_locales/index.json') -const { useEffect } = React +const { useGlobals } = require('@storybook/api'); +const React = require('react'); +const { addons, types } = require('@storybook/addons'); +const { Icons, IconButton } = require('@storybook/components'); +const localeList = require('../../app/_locales/index.json'); +const { useEffect } = React; -addons.register("i18n-party", () => { - - addons.add("i18n-party", { - title: "rotates through every i18n locale", +addons.register('i18n-party', () => { + addons.add('i18n-party', { + title: 'rotates through every i18n locale', //👇 Sets the type of UI element in Storybook type: types.TOOL, match: () => true, render: (...args) => { // https://github.com/storybookjs/storybook/blob/6490a0d646dbaa293b76bbde477daca615efe789/addons/toolbars/src/components/MenuToolbar.tsx#L2 - const [globals, updateGlobals] = useGlobals() + const [globals, updateGlobals] = useGlobals(); useEffect(() => { - if (!globals.localeParty) return + if (!globals.localeParty) return; const interval = setInterval((...args) => { - const currentIndex = localeList.findIndex(({ code }) => code === globals.locale) - const nextIndex = (currentIndex + 1) % localeList.length - const nextLocale = localeList[nextIndex].code - updateGlobals({ locale: nextLocale }) - }, 2000) - return () => clearInterval(interval) - }) + const currentIndex = localeList.findIndex( + ({ code }) => code === globals.locale, + ); + const nextIndex = (currentIndex + 1) % localeList.length; + const nextLocale = localeList[nextIndex].code; + updateGlobals({ locale: nextLocale }); + }, 2000); + return () => clearInterval(interval); + }); return ( - updateGlobals({ localeParty: !globals.localeParty })}> - + updateGlobals({ localeParty: !globals.localeParty })} + > + +  Shuffle i18n locale - ) + ); }, - }) -}) + }); +}); diff --git a/.storybook/main.js b/.storybook/main.js index 843c63b77..5255babda 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -3,19 +3,21 @@ const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { - stories: ['../ui/**/*.stories.js'], + stories: ['../ui/**/*.stories.js', '../ui/**/*.stories.mdx'], addons: [ - '@storybook/addon-knobs', + '@storybook/addon-essentials', '@storybook/addon-actions', - '@storybook/addon-backgrounds', - '@storybook/addon-toolbars', + '@storybook/addon-a11y', + '@storybook/addon-knobs', './i18n-party-addon/register.js', ], + // Uses babel.config.js settings and prevents "Missing class properties transform" error + babel: async (options) => ({ overrides: options.overrides }), webpackFinal: async (config) => { - config.context = process.cwd() + config.context = process.cwd(); config.node = { - __filename: true - } + __filename: true, + }; config.module.strictExportPresence = true; config.module.rules.push({ test: /\.scss$/, diff --git a/.storybook/preview.js b/.storybook/preview.js index 30f6d69ea..71fa49be3 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -8,11 +8,11 @@ import '../ui/css/index.scss'; import localeList from '../app/_locales/index.json'; import * as allLocales from './locales'; import { I18nProvider, LegacyI18nProvider } from './i18n'; -import MetaMetricsProviderStorybook from './metametrics' +import MetaMetricsProviderStorybook from './metametrics'; import testData from './test-data.js'; -import { Router } from "react-router-dom"; -import { createBrowserHistory } from "history"; -import { _setBackgroundConnection } from '../ui/store/actions' +import { Router } from 'react-router-dom'; +import { createBrowserHistory } from 'history'; +import { _setBackgroundConnection } from '../ui/store/actions'; addParameters({ backgrounds: { @@ -38,28 +38,24 @@ export const globalTypes = { }, }; -const styles = { - height: '100vh', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', -}; - export const getNewState = (state, props) => { return Object.assign(state, props); -} +}; export const store = configureStore(testData); const history = createBrowserHistory(); -const proxiedBackground = new Proxy({}, { +const proxiedBackground = new Proxy( + {}, + { get(_, method) { - return function() { - action(`Background call: ${method}`)() - return new Promise(() => {}) - } - } - }) -_setBackgroundConnection(proxiedBackground) + return function () { + action(`Background call: ${method}`)(); + return new Promise(() => {}); + }; + }, + }, +); +_setBackgroundConnection(proxiedBackground); const metamaskDecorator = (story, context) => { const currentLocale = context.globals.locale; @@ -73,9 +69,7 @@ const metamaskDecorator = (story, context) => { current={current} en={allLocales.en} > - -
{story()}
-
+ {story()} diff --git a/.storybook/test-data.js b/.storybook/test-data.js index 28c5c15c8..398ef19b1 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -1258,7 +1258,6 @@ const state = { ledger: "m/44'/60'/0'/0/0", }, networksTabSelectedRpcUrl: '', - networksTabIsInAddMode: false, loadingMethodData: false, show3BoxModalAfterImport: false, threeBoxLastUpdated: null, diff --git a/app/_locales/am/messages.json b/app/_locales/am/messages.json index cd4ff6d0d..aa341612d 100644 --- a/app/_locales/am/messages.json +++ b/app/_locales/am/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "ክፍያ መጨመር የመከወኛ ጊዜን ሊቀንስ ቢችልም ይህ ግን ዋስትና የለውም።" }, - "customRPC": { - "message": "ብጁ RPC" - }, "customToken": { "message": "ብጁ ተለዋጭ ስም" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "አስርዮሽ ቢያንስ 0 ቢበዛ ደግሞ 36 መሆን አለባቸው።" }, - "defaultNetwork": { - "message": "የ Ether ግብይቶች ንቡር አውታረ መረብ Mainnet ነው።" - }, "delete": { "message": "ሰርዝ" }, @@ -620,9 +614,6 @@ "newContract": { "message": "አዲስ ኮንትራት" }, - "newNetwork": { - "message": "አዲስ አውታረ መረብ" - }, "newPassword": { "message": "አዲስ የይለፍ ቃል (ቢያንስ 8 ቁምፊዎች)" }, @@ -671,12 +662,6 @@ "on": { "message": "በርቷል" }, - "optionalBlockExplorerUrl": { - "message": "ኤክስፕሎረር URL አግድ (አማራጭ)" - }, - "optionalCurrencySymbol": { - "message": "ምልክት (አማራጭ)" - }, "origin": { "message": "መነሻ" }, @@ -856,9 +841,6 @@ "selectAnAccount": { "message": "መለያ ይምረጡ" }, - "selectAnAccountHelp": { - "message": "መለያውን በ MetaMask ለማየት ይምረጡ" - }, "selectEachPhrase": { "message": "እባክዎ እያንዳንዱን ሐረግ በመምረጥ ትክክለኛነቱን ያረጋግጡ።" }, diff --git a/app/_locales/ar/messages.json b/app/_locales/ar/messages.json index 46f05e1ef..aa6a76276 100644 --- a/app/_locales/ar/messages.json +++ b/app/_locales/ar/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "قد تؤدي زيادة الرسوم إلى تقليل أزمنة المعالجة، ولكن ذلك غير مضمون." }, - "customRPC": { - "message": "آر بي سي مخصص" - }, "customToken": { "message": "عملة رمزية مخصصة" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "يجب أن تكون الكسور العشرية صفراً على الأقل، وألا تزيد عن 36 كسراً." }, - "defaultNetwork": { - "message": "الشبكة الافتراضية لمعاملات الأثير هي الشبكة الرئيسية." - }, "delete": { "message": "حذف" }, @@ -616,9 +610,6 @@ "newContract": { "message": "عقد جديد" }, - "newNetwork": { - "message": "شبكة جديدة" - }, "newPassword": { "message": "كلمة مرور جديدة (8 أحرف كحد أدنى)" }, @@ -667,12 +658,6 @@ "on": { "message": "تشغيل" }, - "optionalBlockExplorerUrl": { - "message": "العنوان الإلكتروني لمستكشف البلوكات (اختياري)" - }, - "optionalCurrencySymbol": { - "message": "الرمز (اختياري)" - }, "origin": { "message": "الأصل" }, @@ -852,9 +837,6 @@ "selectAnAccount": { "message": "قم بتحديد حساب" }, - "selectAnAccountHelp": { - "message": "حدد الحساب لعرضه في MetaMask" - }, "selectEachPhrase": { "message": "يُرجى تحديد كل عبارة للتأكد من صحتها." }, diff --git a/app/_locales/bg/messages.json b/app/_locales/bg/messages.json index 85db5cf66..f9cabde5d 100644 --- a/app/_locales/bg/messages.json +++ b/app/_locales/bg/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Увеличаването на таксата може да намали времето за обработка, но това не е гарантирано." }, - "customRPC": { - "message": "RPC по избор" - }, "customToken": { "message": "Персонализиран маркер" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Десетичните знаци трябва да бъдат най-малко 0 и не повече от 36." }, - "defaultNetwork": { - "message": "Мрежата по подразбиране за Ether транзакции е Mainnet." - }, "delete": { "message": "Изтриване" }, @@ -619,9 +613,6 @@ "newContract": { "message": "Нов договор" }, - "newNetwork": { - "message": "Нова мрежа" - }, "newPassword": { "message": "Нова парола (мин. 8 символа)" }, @@ -670,12 +661,6 @@ "on": { "message": "Включено" }, - "optionalBlockExplorerUrl": { - "message": "Блокиране на Explorer URL (по избор)" - }, - "optionalCurrencySymbol": { - "message": "Символ (по избор)" - }, "origin": { "message": "Произход" }, @@ -855,9 +840,6 @@ "selectAnAccount": { "message": "Изберете акаунт" }, - "selectAnAccountHelp": { - "message": "Изберете акаунта за преглед в MetaMask" - }, "selectEachPhrase": { "message": "Моля, изберете всяка фраза, за да се уверите, че е правилна." }, diff --git a/app/_locales/bn/messages.json b/app/_locales/bn/messages.json index 92f31381d..5d36aabe9 100644 --- a/app/_locales/bn/messages.json +++ b/app/_locales/bn/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "ফী বৃদ্ধি করা প্রক্রিয়াকরণের সময় কমাতে পারে, কিন্তু সেটির নিশ্চয়তা দেওয়া হয় না।" }, - "customRPC": { - "message": "কাস্টম RPC" - }, "customToken": { "message": "কাস্টম টোকেন" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "দশমিকগুলি অবশ্যই অন্তত 0 হতে হবে, এবং 36 এর উপর হবে না।" }, - "defaultNetwork": { - "message": "ইথার লেনদেনগুলির জন্য ডিফল্ট নেটওয়ার্কটি হল মেন নেট।" - }, "delete": { "message": "মুছুন" }, @@ -623,9 +617,6 @@ "newContract": { "message": "নতুন কন্ট্র্যাক্ট" }, - "newNetwork": { - "message": "নতুন নেটওয়ার্ক" - }, "newPassword": { "message": "নতুন পাসওয়ার্ড (অন্তত 8 অক্ষরের)" }, @@ -674,12 +665,6 @@ "on": { "message": "চালু" }, - "optionalBlockExplorerUrl": { - "message": "এক্সপ্লোরার URL ব্লক করুন (ঐচ্ছিক)" - }, - "optionalCurrencySymbol": { - "message": "প্রতীক (ঐচ্ছিক)" - }, "origin": { "message": "উৎস" }, @@ -859,9 +844,6 @@ "selectAnAccount": { "message": "একটি অ্যাকাউন্ট নির্বাচন করুন" }, - "selectAnAccountHelp": { - "message": "MetaMask এ দেখতে অ্যাকাউন্টটি নির্বাচন করুন" - }, "selectEachPhrase": { "message": "ফ্রেজগুলি সঠিক তা নিশ্চিত করতে অনুগ্রহ করে প্রতিটি ফ্রেজ নির্বাচন করুন।" }, diff --git a/app/_locales/ca/messages.json b/app/_locales/ca/messages.json index f0c688d8d..b29188344 100644 --- a/app/_locales/ca/messages.json +++ b/app/_locales/ca/messages.json @@ -254,9 +254,6 @@ "customGasSubTitle": { "message": "Augmentar la tarifa pot disminuir els temps de processament, però això no està garantit." }, - "customRPC": { - "message": "RPC a mida" - }, "customToken": { "message": "Fitxa a Mida" }, @@ -266,9 +263,6 @@ "decimalsMustZerotoTen": { "message": "Els decimals han de ser al menys 0, i no més de 36." }, - "defaultNetwork": { - "message": "La xarxa per defecte per a les transaccions Ether és Mainnet." - }, "delete": { "message": "Suprimeix" }, @@ -607,9 +601,6 @@ "newContract": { "message": "Nou Contracte" }, - "newNetwork": { - "message": "Nova Xarxa" - }, "newPassword": { "message": "Nova contrasenya (mínim 8 caràcters)" }, @@ -658,12 +649,6 @@ "on": { "message": "Activat" }, - "optionalBlockExplorerUrl": { - "message": "Bloqueja l'URL d'Explorer (opcional)" - }, - "optionalCurrencySymbol": { - "message": "Símbol (opcional)" - }, "origin": { "message": "Origen" }, @@ -837,9 +822,6 @@ "selectAnAccount": { "message": "Selecciona un Compte" }, - "selectAnAccountHelp": { - "message": "Selecciona el compte que vols veure a MetaMask" - }, "selectEachPhrase": { "message": "Si us plau selecciona cada frase per a assegurar-te que és correcta." }, diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index 8a9be7f3a..da4fba371 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -88,9 +88,6 @@ "customGas": { "message": "Nastavit palivo" }, - "customRPC": { - "message": "Vlastní RPC" - }, "customToken": { "message": "Vlastní token" }, @@ -100,9 +97,6 @@ "decimalsMustZerotoTen": { "message": "Desetinných míst musí být od 0 do 36." }, - "defaultNetwork": { - "message": "Výchozí síť pro Etherové transakce je Mainnet." - }, "depositEther": { "message": "Vložit Ether" }, diff --git a/app/_locales/da/messages.json b/app/_locales/da/messages.json index 11b79bc9a..8d288c33f 100644 --- a/app/_locales/da/messages.json +++ b/app/_locales/da/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Forøgelse af gebyr kan mindske bearbejdningstiden, men det er ikke en garanti." }, - "customRPC": { - "message": "Tilpasset RPC" - }, "customToken": { "message": "Brugerdefineret Token" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Decimaler skal være mindst 0 og højst 36." }, - "defaultNetwork": { - "message": "Standardnetværket for Ether-transaktioner er Mainnet." - }, "delete": { "message": "Slet" }, @@ -607,9 +601,6 @@ "newContract": { "message": "Ny Kontrakt" }, - "newNetwork": { - "message": "Nyt Netværk" - }, "newPassword": { "message": "Ny adgangskode (min. 8 tegn)" }, @@ -658,12 +649,6 @@ "on": { "message": "Til" }, - "optionalBlockExplorerUrl": { - "message": "Blok-stifinder-URL (valgfrit)" - }, - "optionalCurrencySymbol": { - "message": "Symbol (valgfrit)" - }, "parameters": { "message": "Parametre" }, @@ -840,9 +825,6 @@ "selectAnAccount": { "message": "Vælg en Konto" }, - "selectAnAccountHelp": { - "message": "Vælg kontoen der skal vises i MetaMask" - }, "selectEachPhrase": { "message": "Vælg venligst hver sætning, for at sikre at de er korrekte." }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index f5dd97621..bca6b887a 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -248,9 +248,6 @@ "customGasSubTitle": { "message": "Höhere Gebühren können Bearbeitungszeiten verkürzen, wofür es allerdings keine Garantie gibt." }, - "customRPC": { - "message": "Spezieller RPC" - }, "customToken": { "message": "Custom-Token" }, @@ -260,9 +257,6 @@ "decimalsMustZerotoTen": { "message": "Die Dezimalangabe muss mindestens 0 und nicht höher als 36 sein." }, - "defaultNetwork": { - "message": "Das Standardnetzwerk für Ether Transaktionen ist das Mainnet." - }, "delete": { "message": "Löschen" }, @@ -602,9 +596,6 @@ "newContract": { "message": "Neuer Smart Contract" }, - "newNetwork": { - "message": "Neues Netzwerk" - }, "newPassword": { "message": "Neues Passwort (min. 8 Zeichen)" }, @@ -650,9 +641,6 @@ "on": { "message": "An" }, - "optionalBlockExplorerUrl": { - "message": "Block-Explorer-URL (optional)" - }, "origin": { "message": "Ursprung" }, @@ -697,7 +685,7 @@ "message": "Datenschutzrichtlinie" }, "privateKeyWarning": { - "message": "Warnung: Niemals jemanden deinen Private Key mitteilen. Jeder der im Besitz deines Private Keys ist, kann jegliches Guthaben deines Accounts stehlen." + "message": "Warnung: Niemals jemandem deinen Private Key mitteilen. Jeder der im Besitz deines Private Keys ist, kann jegliches Guthaben deines Accounts stehlen." }, "privateNetwork": { "message": "Privates Netzwerk" @@ -828,9 +816,6 @@ "selectAnAccount": { "message": "Ein Konto auswählen" }, - "selectAnAccountHelp": { - "message": "Wählen Sie das Konto aus, das in MetaMask angezeigt werden soll." - }, "selectEachPhrase": { "message": "Bitte wählen Sie jede Phrase aus, um sicherzustellen, dass sie korrekt ist." }, @@ -964,7 +949,7 @@ "message": "Hierdurch werden ein neues Wallet und eine mnemonische Phrase erzeugt" }, "tips": { - "message": "Trinkgelder" + "message": "Tipps" }, "to": { "message": "An" diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 15071eab3..1b880074b 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -254,9 +254,6 @@ "customGasSubTitle": { "message": "Η αύξηση των τελών μπορεί να μειώσει τους χρόνους επεξεργασίας, αλλά αυτό δεν είναι εγγυημένο." }, - "customRPC": { - "message": "Προσαρμοσμένο RPC" - }, "customToken": { "message": "Προσαρμοσμένο Token" }, @@ -266,9 +263,6 @@ "decimalsMustZerotoTen": { "message": "Τα δεκαδικά πρέπει να είναι τουλάχιστον 0 και όχι πάνω από 36." }, - "defaultNetwork": { - "message": "Το προεπιλεγμένο δίκτυο για συναλλαγές Ether είναι το Mainnet." - }, "delete": { "message": "Διαγραφή" }, @@ -620,9 +614,6 @@ "newContract": { "message": "Νέα Σύμβαση" }, - "newNetwork": { - "message": "Νέο Δίκτυο" - }, "newPassword": { "message": "Νέος Κωδικός Πρόσβασης (ελάχιστο 8 χαρακτήρες)" }, @@ -671,12 +662,6 @@ "on": { "message": "Ενεργό" }, - "optionalBlockExplorerUrl": { - "message": "Διεύθυνση URL Εξερευνητή Μπλοκ (προαιρετικό)" - }, - "optionalCurrencySymbol": { - "message": "Σύμβολο (προαιρετικό)" - }, "origin": { "message": "Προέλευση" }, @@ -856,9 +841,6 @@ "selectAnAccount": { "message": "Επιλέξτε Λογαριασμό" }, - "selectAnAccountHelp": { - "message": "Επιλέξτε τον λογαριασμό για προβολή στο MetaMask" - }, "selectEachPhrase": { "message": "Παρακαλούμε επιλέξτε κάθε φράση, για να βεβαιωθείτε ότι είναι σωστή." }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 8c7a3f49d..9fc4cfa44 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -43,6 +43,9 @@ "activityLog": { "message": "activity log" }, + "addANetwork": { + "message": "Add a network" + }, "addAcquiredTokens": { "message": "Add the tokens you've acquired using MetaMask" }, @@ -79,6 +82,9 @@ "addFriendsAndAddresses": { "message": "Add friends and addresses you trust" }, + "addNFT": { + "message": "Add NFT" + }, "addNetwork": { "message": "Add Network" }, @@ -107,7 +113,7 @@ "message": "Advanced Options" }, "advancedSettingsDescription": { - "message": "Access developer features, download State Logs, Reset Account, setup testnets and custom RPC" + "message": "Access developer features, download State Logs, Reset Account, setup test networks and custom RPC" }, "affirmAgree": { "message": "I Agree" @@ -139,9 +145,9 @@ "allowExternalExtensionTo": { "message": "Allow this external extension to:" }, - "allowOriginSpendToken": { - "message": "Allow $1 to spend your $2?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" + "allowSpendToken": { + "message": "Give permission to access your $1?", + "description": "$1 is the symbol of the token that are requesting to spend" }, "allowThisSiteTo": { "message": "Allow this site to:" @@ -153,9 +159,6 @@ "amount": { "message": "Amount" }, - "amountWithColon": { - "message": "Amount:" - }, "appDescription": { "message": "An Ethereum Wallet in your Browser", "description": "The description of the application" @@ -183,6 +186,9 @@ "approved": { "message": "Approved" }, + "approvedAmountWithColon": { + "message": "Approved Amount:" + }, "asset": { "message": "Asset" }, @@ -543,21 +549,28 @@ "currentlyUnavailable": { "message": "Unavailable on this network" }, + "custom": { + "message": "Advanced" + }, "customGas": { "message": "Customize Gas" }, "customGasSubTitle": { "message": "Increasing fee may decrease processing times, but it is not guaranteed." }, - "customRPC": { - "message": "Custom RPC" - }, "customSpendLimit": { "message": "Custom Spend Limit" }, "customToken": { "message": "Custom Token" }, + "dappSuggested": { + "message": "Site suggested" + }, + "dappSuggestedTooltip": { + "message": "$1 has recommended this price.", + "description": "$1 represents the Dapp's origin" + }, "data": { "message": "Data" }, @@ -591,7 +604,8 @@ "message": "Decrypt request" }, "defaultNetwork": { - "message": "The default network for Ether transactions is Mainnet." + "message": "The default network for Ether transactions is Mainnet. You can also $1 test networks $2.", + "description": "$1 is the 'enable' or 'disable' key, depending on whether the display of test networks is enabled or not. $2 is a clickable link with text defined by the 'here' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown." }, "delete": { "message": "Delete" @@ -617,6 +631,9 @@ "directDepositEtherExplainer": { "message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit." }, + "disable": { + "message": "disable" + }, "disconnect": { "message": "Disconnect" }, @@ -768,6 +785,9 @@ "editPermission": { "message": "Edit Permission" }, + "enable": { + "message": "enable" + }, "enableFromSettings": { "message": " Enable it from Settings." }, @@ -891,6 +911,9 @@ "etherscanView": { "message": "View account on Etherscan" }, + "etherscanViewOn": { + "message": "View on Etherscan" + }, "expandView": { "message": "Expand view" }, @@ -1002,6 +1025,9 @@ "gasPriceInfoTooltipContent": { "message": "Gas price specifies the amount of Ether you are willing to pay for each unit of gas." }, + "gasPriceLabel": { + "message": "Gas price" + }, "gasTimingMinutes": { "message": "$1 minutes", "description": "$1 represents a number of minutes" @@ -1055,6 +1081,9 @@ "goerli": { "message": "Goerli Test Network" }, + "grantedToWithColon": { + "message": "Granted To:" + }, "happyToSeeYou": { "message": "We’re happy to see you." }, @@ -1097,6 +1126,9 @@ "hideZeroBalanceTokens": { "message": "Hide Tokens Without Balance" }, + "high": { + "message": "Aggressive" + }, "history": { "message": "History" }, @@ -1114,7 +1146,7 @@ "message": "import using Secret Recovery Phrase" }, "importAccountMsg": { - "message": " Imported accounts will not be associated with your originally created MetaMask account Secret Recovery Phrase. Learn more about imported accounts " + "message": "Imported accounts will not be associated with your originally created MetaMask account Secret Recovery Phrase. Learn more about imported accounts" }, "importAccountSeedPhrase": { "message": "Import a wallet with Secret Recovery Phrase" @@ -1168,6 +1200,9 @@ "insufficientFunds": { "message": "Insufficient funds." }, + "insufficientFundsForGas": { + "message": "Insufficient funds for gas" + }, "insufficientTokens": { "message": "Insufficient tokens." }, @@ -1330,6 +1365,12 @@ "lockTimeTooGreat": { "message": "Lock time is too great" }, + "low": { + "message": "Low" + }, + "lowPriorityMessage": { + "message": "Future transactions will queue after this one. This price was last seen was some time ago." + }, "mainnet": { "message": "Ethereum Mainnet" }, @@ -1343,12 +1384,18 @@ "max": { "message": "Max" }, + "maxBaseFee": { + "message": "Max base fee" + }, "maxFee": { "message": "Max fee" }, "maxPriorityFee": { "message": "Max priority fee" }, + "medium": { + "message": "Market" + }, "memo": { "message": "memo" }, @@ -1433,6 +1480,9 @@ "message": "verify the network details", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." }, + "missingNFT": { + "message": "Don't see your NFT?" + }, "missingToken": { "message": "Don't see your token?" }, @@ -1529,8 +1579,8 @@ "newContract": { "message": "New Contract" }, - "newNetwork": { - "message": "New Network" + "newNetworkAdded": { + "message": "“$1” was successfully added!" }, "newPassword": { "message": "New password (min 8 chars)" @@ -1551,6 +1601,9 @@ "message": "Nonce is higher than suggested nonce of $1", "description": "The next nonce according to MetaMask's internal logic" }, + "nfts": { + "message": "NFTs" + }, "noAccountsFound": { "message": "No accounts found for the given search query" }, @@ -1563,6 +1616,9 @@ "noConversionRateAvailable": { "message": "No Conversion Rate Available" }, + "noNFTs": { + "message": "No NFTs yet" + }, "noThanks": { "message": "No Thanks" }, @@ -1755,11 +1811,8 @@ "optional": { "message": "Optional" }, - "optionalBlockExplorerUrl": { - "message": "Block Explorer URL (optional)" - }, - "optionalCurrencySymbol": { - "message": "Currency Symbol (optional)" + "optionalWithParanthesis": { + "message": "(Optional)" }, "or": { "message": "or" @@ -1801,6 +1854,9 @@ "permissionCheckedIconDescription": { "message": "You have approved this permission" }, + "permissionRequest": { + "message": "Permission Request" + }, "permissionUncheckedIconDescription": { "message": "You have not approved this permission" }, @@ -2108,13 +2164,6 @@ "selectAnAccountAlreadyConnected": { "message": "This account has already been connected to MetaMask" }, - "selectAnAccountHelp": { - "message": "Select an account to view in MetaMask." - }, - "selectAnAccountHelpDirections": { - "message": "Don't see your account? $1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "Please select each phrase in order to make sure it is correct." }, @@ -2122,7 +2171,7 @@ "message": "Select HD Path" }, "selectPathHelp": { - "message": "If you don't see your existing Ledger accounts below, try switching paths to \"Legacy (MEW / MyCrypto)\"" + "message": "If you don't see the accounts you expect, try switching the HD path." }, "selectType": { "message": "Select Type" @@ -2172,10 +2221,10 @@ "message": "Select this to show gas price and limit controls directly on the send and confirm screens." }, "showFiatConversionInTestnets": { - "message": "Show Conversion on Testnets" + "message": "Show Conversion on test networks" }, "showFiatConversionInTestnetsDescription": { - "message": "Select this to show fiat conversion on Testnets" + "message": "Select this to show fiat conversion on test networks" }, "showHexData": { "message": "Show Hex Data" @@ -2201,6 +2250,12 @@ "showSeedPhrase": { "message": "Show Secret Recovery Phrase" }, + "showTestnetNetworks": { + "message": "Show test networks" + }, + "showTestnetNetworksDescription": { + "message": "Select this to show test networks in network list" + }, "sigRequest": { "message": "Signature Request" }, @@ -2289,6 +2344,13 @@ "statusNotConnected": { "message": "Not connected" }, + "step1LatticeWallet": { + "message": "Make sure your Lattice1 is ready to connect" + }, + "step1LatticeWalletMsg": { + "message": "You can connect MetaMask to your Lattice1 device once it is set up and online. Unlock your device and have your Device ID ready. For more on using hardware wallets, $1", + "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" + }, "step1LedgerWallet": { "message": "Download Ledger app" }, @@ -2652,12 +2714,6 @@ "switchEthereumChainConfirmationTitle": { "message": "Allow this site to switch the network?" }, - "switchLedgerPaths": { - "message": "Switch Ledger paths" - }, - "switchLedgerPathsText": { - "message": "Select the Ledger path to view other accounts" - }, "switchNetwork": { "message": "Switch network" }, @@ -2734,9 +2790,6 @@ "message": "To: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "To:" - }, "token": { "message": "Token" }, @@ -2785,6 +2838,12 @@ "transactionDetailGasHeading": { "message": "Estimated gas fee" }, + "transactionDetailGasHeadingV2": { + "message": "Gas" + }, + "transactionDetailGasInfoV2": { + "message": "estimated" + }, "transactionDetailGasTooltipConversion": { "message": "Learn more about gas fees" }, @@ -2866,14 +2925,13 @@ "description": "Followed by a link (here) to view token balances" }, "trustSiteApprovePermission": { - "message": "Do you trust this site? By granting this permission, you’re allowing $1 to withdraw your $2 and automate transactions for you.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" + "message": "By granting permission, you are allowing the following $1 to access your funds" }, "tryAgain": { "message": "Try again" }, "turnOnTokenDetection": { - "message": "Turn on Token Detection" + "message": "Turn on enhanced token detection" }, "typePassword": { "message": "Type your MetaMask password" diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 63d07557d..8418ba8e6 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -130,10 +130,6 @@ "allowExternalExtensionTo": { "message": "Permitir que esta extensión externa haga lo siguiente:" }, - "allowOriginSpendToken": { - "message": "¿Permitir que $1 gaste su $2?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "Permitir que este sitio haga lo siguiente:" }, @@ -144,9 +140,6 @@ "amount": { "message": "Importe" }, - "amountWithColon": { - "message": "Importe:" - }, "appDescription": { "message": "Una cartera de Ethereum en el explorador", "description": "The description of the application" @@ -474,9 +467,6 @@ "customGasSubTitle": { "message": "Aumentar la cuota puede disminuir los tiempos de procesamiento, pero no está garantizado." }, - "customRPC": { - "message": "RPC personalizada" - }, "customSpendLimit": { "message": "Límite de gastos personalizado" }, @@ -512,9 +502,6 @@ "decryptRequest": { "message": "Descifrar solicitud" }, - "defaultNetwork": { - "message": "La red predeterminada para las transacciones de ether es la red principal." - }, "delete": { "message": "Eliminar" }, @@ -1193,9 +1180,6 @@ "newContract": { "message": "Contrato nuevo" }, - "newNetwork": { - "message": "Red nueva" - }, "newPassword": { "message": "Contraseña nueva (mín. de 8 caracteres)" }, @@ -1334,12 +1318,6 @@ "onlyConnectTrust": { "message": "Conéctese solo con sitios de confianza." }, - "optionalBlockExplorerUrl": { - "message": "Dirección URL del explorador de bloques (opcional)" - }, - "optionalCurrencySymbol": { - "message": "Símbolo de moneda (opcional)" - }, "origin": { "message": "Origen" }, @@ -1638,13 +1616,6 @@ "selectAnAccountAlreadyConnected": { "message": "Esta cuenta ya se conectó a MetaMask." }, - "selectAnAccountHelp": { - "message": "Seleccione una cuenta para verla en MetaMask." - }, - "selectAnAccountHelpDirections": { - "message": "¿No ve su cuenta? $1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "Seleccione cada frase para garantizar que sea correcta." }, @@ -2125,12 +2096,6 @@ "switchEthereumChainConfirmationTitle": { "message": "¿Le permite a este sitio cambiar la red?" }, - "switchLedgerPaths": { - "message": "Cambiar rutas de acceso al Ledger" - }, - "switchLedgerPathsText": { - "message": "Seleccione la ruta de acceso al Ledger para ver otras cuentas" - }, "switchNetwork": { "message": "Cambiar red" }, @@ -2201,9 +2166,6 @@ "message": "Para: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "Para:" - }, "token": { "message": "Token" }, @@ -2281,10 +2243,6 @@ "message": "Tuvimos problemas al cargar los saldos de token. Puede verlos ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "¿Este sitio es de confianza? Al conceder este permiso, autoriza que $1 retire su $2 y automatice las transacciones por usted.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "Vuelva a intentarlo" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 63d07557d..8418ba8e6 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -130,10 +130,6 @@ "allowExternalExtensionTo": { "message": "Permitir que esta extensión externa haga lo siguiente:" }, - "allowOriginSpendToken": { - "message": "¿Permitir que $1 gaste su $2?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "Permitir que este sitio haga lo siguiente:" }, @@ -144,9 +140,6 @@ "amount": { "message": "Importe" }, - "amountWithColon": { - "message": "Importe:" - }, "appDescription": { "message": "Una cartera de Ethereum en el explorador", "description": "The description of the application" @@ -474,9 +467,6 @@ "customGasSubTitle": { "message": "Aumentar la cuota puede disminuir los tiempos de procesamiento, pero no está garantizado." }, - "customRPC": { - "message": "RPC personalizada" - }, "customSpendLimit": { "message": "Límite de gastos personalizado" }, @@ -512,9 +502,6 @@ "decryptRequest": { "message": "Descifrar solicitud" }, - "defaultNetwork": { - "message": "La red predeterminada para las transacciones de ether es la red principal." - }, "delete": { "message": "Eliminar" }, @@ -1193,9 +1180,6 @@ "newContract": { "message": "Contrato nuevo" }, - "newNetwork": { - "message": "Red nueva" - }, "newPassword": { "message": "Contraseña nueva (mín. de 8 caracteres)" }, @@ -1334,12 +1318,6 @@ "onlyConnectTrust": { "message": "Conéctese solo con sitios de confianza." }, - "optionalBlockExplorerUrl": { - "message": "Dirección URL del explorador de bloques (opcional)" - }, - "optionalCurrencySymbol": { - "message": "Símbolo de moneda (opcional)" - }, "origin": { "message": "Origen" }, @@ -1638,13 +1616,6 @@ "selectAnAccountAlreadyConnected": { "message": "Esta cuenta ya se conectó a MetaMask." }, - "selectAnAccountHelp": { - "message": "Seleccione una cuenta para verla en MetaMask." - }, - "selectAnAccountHelpDirections": { - "message": "¿No ve su cuenta? $1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "Seleccione cada frase para garantizar que sea correcta." }, @@ -2125,12 +2096,6 @@ "switchEthereumChainConfirmationTitle": { "message": "¿Le permite a este sitio cambiar la red?" }, - "switchLedgerPaths": { - "message": "Cambiar rutas de acceso al Ledger" - }, - "switchLedgerPathsText": { - "message": "Seleccione la ruta de acceso al Ledger para ver otras cuentas" - }, "switchNetwork": { "message": "Cambiar red" }, @@ -2201,9 +2166,6 @@ "message": "Para: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "Para:" - }, "token": { "message": "Token" }, @@ -2281,10 +2243,6 @@ "message": "Tuvimos problemas al cargar los saldos de token. Puede verlos ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "¿Este sitio es de confianza? Al conceder este permiso, autoriza que $1 retire su $2 y automatice las transacciones por usted.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "Vuelva a intentarlo" }, diff --git a/app/_locales/et/messages.json b/app/_locales/et/messages.json index de6d85299..66ce15ec5 100644 --- a/app/_locales/et/messages.json +++ b/app/_locales/et/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Tasu suurendamine võib töötlemisaegu vähendada, kuid see ei ole tagatud." }, - "customRPC": { - "message": "Kohandatud RPC" - }, "customToken": { "message": "Kohandatud luba" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Kümnendkohad peavad olema vähemalt 0 ja mitte üle 36." }, - "defaultNetwork": { - "message": "Etheri tehingute vaikevõrk on peavõrk." - }, "delete": { "message": "Kustuta" }, @@ -613,9 +607,6 @@ "newContract": { "message": "Uus kontakt" }, - "newNetwork": { - "message": "Uus võrk" - }, "newPassword": { "message": "Uus parool (vähemalt 8 tähemärki)" }, @@ -664,12 +655,6 @@ "on": { "message": "Sees" }, - "optionalBlockExplorerUrl": { - "message": "Blokeeri Exploreri URL (valikuline)" - }, - "optionalCurrencySymbol": { - "message": "Sümbol (valikuline)" - }, "origin": { "message": "Päritolu" }, @@ -849,9 +834,6 @@ "selectAnAccount": { "message": "Valige konto" }, - "selectAnAccountHelp": { - "message": "Valige konto, mida MetaMaskis vaadata" - }, "selectEachPhrase": { "message": "Valige iga fraas, veendumaks, et see on õige." }, diff --git a/app/_locales/fa/messages.json b/app/_locales/fa/messages.json index b22b198c5..f6fd7e476 100644 --- a/app/_locales/fa/messages.json +++ b/app/_locales/fa/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "اضافه نمودن فیس ممکن زمان پروسس را کاهش دهد، اما تضمین نمیشود." }, - "customRPC": { - "message": "RPC رایج" - }, "customToken": { "message": "رمزیاب دلخواه" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "اعشاریه ها باید حد اقل 0، و بیشتر از 36 نباشند." }, - "defaultNetwork": { - "message": "شبکه خودکار برای معاملات Ether عبارت است از Mainnet." - }, "delete": { "message": "حذف" }, @@ -623,9 +617,6 @@ "newContract": { "message": "قرارداد جدید" }, - "newNetwork": { - "message": "شبکه جدید" - }, "newPassword": { "message": "رمز عبور جدید (حداقل 8 حرف)" }, @@ -674,12 +665,6 @@ "on": { "message": "روشن" }, - "optionalBlockExplorerUrl": { - "message": "بلاک کردن مرورگر URL (انتخابی)" - }, - "optionalCurrencySymbol": { - "message": "سمبول (انتخابی)" - }, "origin": { "message": "مبدأ" }, @@ -859,9 +844,6 @@ "selectAnAccount": { "message": "یک حساب را انتخاب کنید" }, - "selectAnAccountHelp": { - "message": "برای مشاهده MetaMask حساب را انتخاب نمایید" - }, "selectEachPhrase": { "message": "لطفًا جهت اطمینان از درستی، هر عبارت را انتخاب کنید." }, diff --git a/app/_locales/fi/messages.json b/app/_locales/fi/messages.json index 857e496e2..8260f9ad4 100644 --- a/app/_locales/fi/messages.json +++ b/app/_locales/fi/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Taksan korottaminen saattaa vähentää käsittelyaikaa, mutta siitä ei anneta takeita." }, - "customRPC": { - "message": "Mukautettu RPC" - }, "customToken": { "message": "Mukautettu tietue" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Desimaalien on oltava vähintään 0 ja korkeintaan 36." }, - "defaultNetwork": { - "message": "Oletusverkko Ether-tapahtumille on Mainnet." - }, "delete": { "message": "Poista" }, @@ -623,9 +617,6 @@ "newContract": { "message": "Uusi sopimus" }, - "newNetwork": { - "message": "Uusi verkko" - }, "newPassword": { "message": "Uusi salasana (väh. 8 merkkiä)" }, @@ -671,12 +662,6 @@ "on": { "message": "Käytössä" }, - "optionalBlockExplorerUrl": { - "message": "Estä Explorerin URL-osoite (valinnainen)" - }, - "optionalCurrencySymbol": { - "message": "Symboli (valinnainen)" - }, "origin": { "message": "Alkuperä" }, @@ -856,9 +841,6 @@ "selectAnAccount": { "message": "Valitse tili" }, - "selectAnAccountHelp": { - "message": "Valitse MetaMaskissa näytettävä tili" - }, "selectEachPhrase": { "message": "Ole hyvä ja valitse jokainen teksti järjestyksessä varmistaaksesi, että se on oikein." }, diff --git a/app/_locales/fil/messages.json b/app/_locales/fil/messages.json index 235694a2f..23001c136 100644 --- a/app/_locales/fil/messages.json +++ b/app/_locales/fil/messages.json @@ -245,9 +245,6 @@ "decimalsMustZerotoTen": { "message": "Ang mga decimal ay hindi dapat bumaba sa 0, at hindi lumampas sa 36." }, - "defaultNetwork": { - "message": "Ang default na network para sa mga transaksyon ng Ether ay Mainnet." - }, "delete": { "message": "I-delete" }, @@ -557,9 +554,6 @@ "newContract": { "message": "Bagong Contract" }, - "newNetwork": { - "message": "Bagong Network" - }, "newPassword": { "message": "Bagong Password (min 8 char)" }, @@ -605,12 +599,6 @@ "on": { "message": "Naka-on" }, - "optionalBlockExplorerUrl": { - "message": "Block Explorer URL (opsyonal)" - }, - "optionalCurrencySymbol": { - "message": "Simbolo (opsyonal)" - }, "origin": { "message": "Pinanggalingan" }, @@ -777,9 +765,6 @@ "selectAnAccount": { "message": "Pumili ng Account" }, - "selectAnAccountHelp": { - "message": "Piliin ang account na titingnan sa MetaMask" - }, "selectEachPhrase": { "message": "Pakipili ang bawat parirala para tiyaking tama ito." }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 709ecbd83..7ed3dbcdd 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -248,9 +248,6 @@ "customGasSubTitle": { "message": "Augmenter le tarif peut faire baisser le temps de traitement, mais cela n'est pas garanti." }, - "customRPC": { - "message": "RPC personnalisé" - }, "customToken": { "message": "Jeton personnalisé" }, @@ -260,9 +257,6 @@ "decimalsMustZerotoTen": { "message": "Les décimales doivent être plus grandes que 0 et inférieures à 36." }, - "defaultNetwork": { - "message": "Le réseau par défaut pour les transactions Ether est le \"Réseau principal Ethereum\"." - }, "delete": { "message": "Supprimer" }, @@ -608,9 +602,6 @@ "newContract": { "message": "Nouveau contrat" }, - "newNetwork": { - "message": "Nouveau réseau" - }, "newPassword": { "message": "Nouveau mot de passe (min 8 caractères)" }, @@ -656,12 +647,6 @@ "on": { "message": "Activé" }, - "optionalBlockExplorerUrl": { - "message": "URL de l'explorateur de blocs (facultatif)" - }, - "optionalCurrencySymbol": { - "message": "Symbole (facultatif)" - }, "origin": { "message": "Origine" }, @@ -841,9 +826,6 @@ "selectAnAccount": { "message": "Selectionner un compte" }, - "selectAnAccountHelp": { - "message": "Selectionner le compte à afficher dans MetaMask" - }, "selectEachPhrase": { "message": "Veuillez sélectionner chaque phrase afin de vous assurer qu'elle est correcte." }, diff --git a/app/_locales/he/messages.json b/app/_locales/he/messages.json index f080f1e66..905020755 100644 --- a/app/_locales/he/messages.json +++ b/app/_locales/he/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "הגדלת התשלום עשויה לצמצם את זמני העיבוד, אבל אין ערובה לכך." }, - "customRPC": { - "message": "RPC מותאם אישית" - }, "customToken": { "message": "אסימון מותאם אישית" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "מספרים עשרוניים חייבים להיות לפחות 0 ולא מעל 36." }, - "defaultNetwork": { - "message": "רשת ברירת המחדל לעסקאות Ether היא Mainnet." - }, "delete": { "message": "מחיקה" }, @@ -620,9 +614,6 @@ "newContract": { "message": "חוזה חדש" }, - "newNetwork": { - "message": "רשת חדשה" - }, "newPassword": { "message": "ססמה חדשה (לפחות 8 תווים)" }, @@ -671,12 +662,6 @@ "on": { "message": "פועל" }, - "optionalBlockExplorerUrl": { - "message": "חסום כתובת URL של אקספלורר (אופציונלי)" - }, - "optionalCurrencySymbol": { - "message": "סמל (אופציונלי)" - }, "origin": { "message": "מקור" }, @@ -853,9 +838,6 @@ "selectAnAccount": { "message": "בחר חשבון" }, - "selectAnAccountHelp": { - "message": "בחר את החשבון לצפייה ב- MetaMask" - }, "selectEachPhrase": { "message": "נא לבחור כל צירוף מילים כדי להבטיח שהוא נכון." }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 217bdeca4..65f6640c1 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -130,10 +130,6 @@ "allowExternalExtensionTo": { "message": "इस बाहरी एक्सटेंशन को इसकी अनुमति दें:" }, - "allowOriginSpendToken": { - "message": "$1 को आपका $2 खर्च करने की अनुमति दें?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "इस साइट को इसकी अनुमति दें:" }, @@ -144,9 +140,6 @@ "amount": { "message": "राशि" }, - "amountWithColon": { - "message": "राशि:" - }, "appDescription": { "message": "आपके ब्राउज़र में एक Ethereum वॉलेट", "description": "The description of the application" @@ -474,9 +467,6 @@ "customGasSubTitle": { "message": "शुल्क बढ़ाने से प्रसंस्करण समय में कमी हो सकती है, लेकिन इसकी गारंटी नहीं होती है।" }, - "customRPC": { - "message": "कस्टम RPC" - }, "customSpendLimit": { "message": "कस्टम खर्च सीमा" }, @@ -512,9 +502,6 @@ "decryptRequest": { "message": "अनुरोध डिक्रिप्ट करें" }, - "defaultNetwork": { - "message": "Ether के लेनदेन के लिए डिफ़ॉल्ट नेटवर्क Mainnet है।" - }, "delete": { "message": "हटाएँ" }, @@ -1193,9 +1180,6 @@ "newContract": { "message": "नया अनुबंध" }, - "newNetwork": { - "message": "नया नेटवर्क" - }, "newPassword": { "message": "नया पासवर्ड (न्यूनतम 8 वर्ण)" }, @@ -1334,12 +1318,6 @@ "onlyConnectTrust": { "message": "केवल उन साइटों से कनेक्ट करें, जिन पर आप भरोसा करते हैं।" }, - "optionalBlockExplorerUrl": { - "message": "ब्लॉक एक्सप्लोरर URL (वैकल्पिक)" - }, - "optionalCurrencySymbol": { - "message": "मुद्रा प्रतीक (वैकल्पिक)" - }, "origin": { "message": "उत्पत्ति" }, @@ -1638,13 +1616,6 @@ "selectAnAccountAlreadyConnected": { "message": "यह खाता पहले ही MetaMask से जुड़ा हुआ है" }, - "selectAnAccountHelp": { - "message": "MetaMask में देखने के लिए खाते का चयन करें।" - }, - "selectAnAccountHelpDirections": { - "message": "अपना खाता नहीं देख पा रहे हैं? $1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "कृपया प्रत्येक वाक्यांश का चयन करें, ताकि यह सुनिश्चित हो सके कि यह सही है।" }, @@ -2125,12 +2096,6 @@ "switchEthereumChainConfirmationTitle": { "message": "इस साइट को नेटवर्क स्विच करने की अनुमति दें?" }, - "switchLedgerPaths": { - "message": "Ledger पथ स्विच करें" - }, - "switchLedgerPathsText": { - "message": "अन्य खाते देखने के लिए Ledger पथ का चयन करें" - }, "switchNetwork": { "message": "नेटवर्क स्विच करें" }, @@ -2201,9 +2166,6 @@ "message": "प्रति: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "प्रति:" - }, "token": { "message": "टोकन" }, @@ -2281,10 +2243,6 @@ "message": "हमें आपके टोकन की शेषराशि लोड करने में परेशानी हुई। आप उन्हें देख सकते हैं ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "क्या आप इस साइट पर भरोसा करते हैं? यह अनुमति देकर, आप $1 को अपने $2 की निकासी करने और आपके लिए लेनदेनों को स्वचालित करने की अनुमति दे रहे हैं।", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "पुनः प्रयास करें" }, diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index 73056bb06..e42222336 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -70,18 +70,12 @@ "customGas": { "message": "अनुकूलित करें गैस" }, - "customRPC": { - "message": "कस्टम RPC" - }, "decimal": { "message": "दशमलव परिशुद्धता" }, "decimalsMustZerotoTen": { "message": "दशमलव कम से कम 0 होनी चाहिए, और 36 से अधिक नहीं होनी चाहिए।" }, - "defaultNetwork": { - "message": "ईथर लेनदेन के लिए डिफ़ॉल्ट नेटवर्क मुख्य नेट है।" - }, "depositEther": { "message": "जमा - Ether" }, diff --git a/app/_locales/hr/messages.json b/app/_locales/hr/messages.json index 5a629159f..7a3a94c8f 100644 --- a/app/_locales/hr/messages.json +++ b/app/_locales/hr/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Povećavanjem se naknade može smanjiti vrijeme obrade, ali se ne jamči." }, - "customRPC": { - "message": "Prilagođeni RPC" - }, "customToken": { "message": "Prilagođeni token" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Decimalni brojevni ne smiju biti 0 i ne smiju prekoračivati 36." }, - "defaultNetwork": { - "message": "Zadana je mreža za transakcije Ether glavna mreža." - }, "delete": { "message": "Izbriši" }, @@ -616,9 +610,6 @@ "newContract": { "message": "Novi ugovor" }, - "newNetwork": { - "message": "Nova mreža" - }, "newPassword": { "message": "Nova lozinka (najmanje osam znakova)" }, @@ -667,12 +658,6 @@ "on": { "message": "Uključi" }, - "optionalBlockExplorerUrl": { - "message": "Blokiraj Explorerov URL (neobavezno)" - }, - "optionalCurrencySymbol": { - "message": "Simbol (neobavezno)" - }, "origin": { "message": "Podrijetlo" }, @@ -852,9 +837,6 @@ "selectAnAccount": { "message": "Odaberi račun" }, - "selectAnAccountHelp": { - "message": "Odaberi račun za prikaz u usluzi MetaMask" - }, "selectEachPhrase": { "message": "Odaberite svaku rečenicu kako biste provjerili je li točna." }, diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index dc3b8b90c..8013372c1 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -136,9 +136,6 @@ "customGas": { "message": "Koutim Gaz" }, - "customRPC": { - "message": "Koutim RPC" - }, "customToken": { "message": "Koutim Token" }, @@ -148,9 +145,6 @@ "decimalsMustZerotoTen": { "message": "Desimal yo dwe omwen 0, epi pa dwe plis pase 36." }, - "defaultNetwork": { - "message": "Dfo rezo a pou tranzaksyon Ether se Mainnet." - }, "depositEther": { "message": "Depo Ether" }, @@ -531,9 +525,6 @@ "selectAnAccount": { "message": "Chwazi yon kont" }, - "selectAnAccountHelp": { - "message": "Chwazi kont pou wè nan MetaMask" - }, "selectHdPath": { "message": "Chwazi chemen HD" }, diff --git a/app/_locales/hu/messages.json b/app/_locales/hu/messages.json index 4f99006d6..18f2cf317 100644 --- a/app/_locales/hu/messages.json +++ b/app/_locales/hu/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "A díj növelése csökkentheti a feldolgozási időt, de ez nem garantált." }, - "customRPC": { - "message": "Egyedi RPC" - }, "customToken": { "message": "Egyéni token" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "A tizedesjegyek száma 0 és 36 között legyen . " }, - "defaultNetwork": { - "message": "Az Ether tranzakciók alapértelmezett hálózata a Mainnet." - }, "delete": { "message": "Törlés" }, @@ -616,9 +610,6 @@ "newContract": { "message": "Új szerződés" }, - "newNetwork": { - "message": "Új hálózat" - }, "newPassword": { "message": "Új jelszó (minimum 8 karakter)" }, @@ -667,12 +658,6 @@ "on": { "message": "Be" }, - "optionalBlockExplorerUrl": { - "message": "Explorer URL letiltása (nem kötelező)" - }, - "optionalCurrencySymbol": { - "message": "Szimbólum (opcionális)" - }, "origin": { "message": "Eredet" }, @@ -852,9 +837,6 @@ "selectAnAccount": { "message": "Válasszon fiókot" }, - "selectAnAccountHelp": { - "message": "Válássza ki a MetaMask-ban megtekinteni kívánt fiókot" - }, "selectEachPhrase": { "message": "Kérjük, válassza ki az egyes mondatokat, hogy meggyőződjön azok helyességéről. " }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 915ebf125..9f0744379 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -130,10 +130,6 @@ "allowExternalExtensionTo": { "message": "Izinkan ekstensi eksternal ini untuk:" }, - "allowOriginSpendToken": { - "message": "Izinkan $1 untuk menggunakan $2 Anda?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "Izinkan situs ini untuk:" }, @@ -144,9 +140,6 @@ "amount": { "message": "Jumlah" }, - "amountWithColon": { - "message": "Jumlah:" - }, "appDescription": { "message": "Dompet Ethereum di Browser Anda", "description": "The description of the application" @@ -474,9 +467,6 @@ "customGasSubTitle": { "message": "Menaikkan biaya dapat mengurangi waktu pemrosesan, namun tidak dijamin." }, - "customRPC": { - "message": "RPC Kustom" - }, "customSpendLimit": { "message": "Batas Penggunaan Kustom" }, @@ -512,9 +502,6 @@ "decryptRequest": { "message": "Dekrip permintaan" }, - "defaultNetwork": { - "message": "Jaringan default untuk transaksi Ether adalah Jaringan Utama." - }, "delete": { "message": "Hapus" }, @@ -1193,9 +1180,6 @@ "newContract": { "message": "Kontrak Baru" }, - "newNetwork": { - "message": "Jaringan Baru" - }, "newPassword": { "message": "Kata sandi baru (min. 8 karakter)" }, @@ -1334,12 +1318,6 @@ "onlyConnectTrust": { "message": "Hanya hubungkan ke situs yang Anda percayai." }, - "optionalBlockExplorerUrl": { - "message": "URL Block Explorer (opsional)" - }, - "optionalCurrencySymbol": { - "message": "Simbol Mata Uang (opsional)" - }, "origin": { "message": "Asal" }, @@ -1638,13 +1616,6 @@ "selectAnAccountAlreadyConnected": { "message": "Akun ini sudah terhubung ke MetaMask." }, - "selectAnAccountHelp": { - "message": "Pilih akun untuk dilihat di MetaMask." - }, - "selectAnAccountHelpDirections": { - "message": "Tidak melihat akun Anda? $1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "Pilih masing-masing frasa untuk memastikan kebenarannya." }, @@ -2125,12 +2096,6 @@ "switchEthereumChainConfirmationTitle": { "message": "Izinkan situs ini untuk beralih jaringan?" }, - "switchLedgerPaths": { - "message": "Beralih jalur Ledger" - }, - "switchLedgerPathsText": { - "message": "Pilih jalur Ledger untuk melihat akun lain" - }, "switchNetwork": { "message": "Beralih jaringan" }, @@ -2201,9 +2166,6 @@ "message": "Untuk: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "Untuk:" - }, "token": { "message": "Token" }, @@ -2281,10 +2243,6 @@ "message": "Kami mengalami masalah saat memuat saldo token Anda. Anda dapat melihatnya ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "Anda percaya situs ini? Dengan memberikan izin ini, Anda mengizinkan $1 untuk menarik $2 Anda dan mengotomatiskan transaksi untuk Anda.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "Coba lagi" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index 0cd8bf51c..1e8c000de 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -103,10 +103,6 @@ "allowExternalExtensionTo": { "message": "Permetti a questa estensione di:" }, - "allowOriginSpendToken": { - "message": "Vuoi consentire a $1 di spendere $2?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "Permetti a questo sito di:" }, @@ -117,9 +113,6 @@ "amount": { "message": "Importo" }, - "amountWithColon": { - "message": "Importo:" - }, "appDescription": { "message": "Ethereum Browser Extension", "description": "The description of the application" @@ -408,9 +401,6 @@ "customGasSubTitle": { "message": "Incrementare il costo potrebbe diminuire il tempo di elaborazione, ma non è garantito." }, - "customRPC": { - "message": "RPC Personalizzata" - }, "customSpendLimit": { "message": "Limite Spesa Personalizzato" }, @@ -446,9 +436,6 @@ "decryptRequest": { "message": "Decifra richiesta" }, - "defaultNetwork": { - "message": "La rete predefinita per transazioni in Ether è la Rete Ethereum Principale." - }, "delete": { "message": "Elimina" }, @@ -1007,9 +994,6 @@ "newContract": { "message": "Nuovo Contratto" }, - "newNetwork": { - "message": "Nuova Rete" - }, "newPassword": { "message": "Nuova Password (minimo 8 caratteri)" }, @@ -1093,12 +1077,6 @@ "onlyConnectTrust": { "message": "Connettiti solo con siti di cui ti fidi." }, - "optionalBlockExplorerUrl": { - "message": "URL del Block Explorer (opzionale)" - }, - "optionalCurrencySymbol": { - "message": "Simbolo (opzionale)" - }, "origin": { "message": "Origine" }, @@ -1328,9 +1306,6 @@ "selectAnAccount": { "message": "Seleziona un Account" }, - "selectAnAccountHelp": { - "message": "Selezione l'account da visualizzare in MetaMask" - }, "selectEachPhrase": { "message": "Per favore seleziona ogni frase in ordine per assicurarti che sia corretta." }, @@ -1793,9 +1768,6 @@ "message": "A: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "To:" - }, "token": { "message": "Token" }, @@ -1867,10 +1839,6 @@ "message": "Abbiamo avuto un problema a caricare il bilancio dei tuoi token. Puoi vederlo ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "Ti fidi di questo sito? Autorizzandolo, stai consentendo a $1 di ritirare i tuoi $2 e automatizzare transazioni per te.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "Prova di nuovo" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index a3af48c46..cea2eda8c 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -130,10 +130,6 @@ "allowExternalExtensionTo": { "message": "この外部拡張機能に次の操作を許可します" }, - "allowOriginSpendToken": { - "message": "$1 に $2 の使用を許可しますか?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "このサイトに次の操作を許可します" }, @@ -144,9 +140,6 @@ "amount": { "message": "金額" }, - "amountWithColon": { - "message": "金額:" - }, "appDescription": { "message": "ブラウザーにあるイーサリアム ウォレット", "description": "The description of the application" @@ -474,9 +467,6 @@ "customGasSubTitle": { "message": "手数料を増やすと処理時間は減少する可能性がありますが、減少しない場合もあります。" }, - "customRPC": { - "message": "カスタム RPC" - }, "customSpendLimit": { "message": "カスタム使用限度額" }, @@ -512,9 +502,6 @@ "decryptRequest": { "message": "リクエストの復号" }, - "defaultNetwork": { - "message": "Ether 取引のためのデフォルトのネットワークはメインネットです。" - }, "delete": { "message": "削除" }, @@ -1193,9 +1180,6 @@ "newContract": { "message": "新しいコントラクト" }, - "newNetwork": { - "message": "新しいネットワーク" - }, "newPassword": { "message": "新しいパスワード (最低 8 文字)" }, @@ -1334,12 +1318,6 @@ "onlyConnectTrust": { "message": "信頼するサイトにのみ接続します。" }, - "optionalBlockExplorerUrl": { - "message": "ブロック エクスプローラーの URL (オプション)" - }, - "optionalCurrencySymbol": { - "message": "通貨記号 (オプション)" - }, "origin": { "message": "起点" }, @@ -1638,13 +1616,6 @@ "selectAnAccountAlreadyConnected": { "message": "このアカウントはすでに MetaMask に接続されています" }, - "selectAnAccountHelp": { - "message": "MetaMask で表示するアカウントを選択します。" - }, - "selectAnAccountHelpDirections": { - "message": "アカウントが表示されません。$1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "各フレーズを選択して、各フレーズが正しいことを確認してください。" }, @@ -2125,12 +2096,6 @@ "switchEthereumChainConfirmationTitle": { "message": "このサイトのネットワーク変更を許可しますか?" }, - "switchLedgerPaths": { - "message": "レジャー パスの切り替え" - }, - "switchLedgerPathsText": { - "message": "レジャー パスを選択して他のアカウントを表示します" - }, "switchNetwork": { "message": "ネットワークの切り替え" }, @@ -2201,9 +2166,6 @@ "message": "移動先:$1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "移動先:" - }, "token": { "message": "トークン" }, @@ -2281,10 +2243,6 @@ "message": "トークン バランスのロードに問題があります。トークン バランスを表示できます", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "このサイトを信頼しますか?許可を与えることにより、$1 は $2 を取り消して、トランザクションを自動化できます。", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "再試行" }, diff --git a/app/_locales/kn/messages.json b/app/_locales/kn/messages.json index 0cdbcab35..bf17d9ce0 100644 --- a/app/_locales/kn/messages.json +++ b/app/_locales/kn/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "ಹೆಚ್ಚುತ್ತಿರುವ ಶುಲ್ಕವು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುವ ಸಮಯದಲ್ಲಿ ಕಡಿಮೆಯಾಗುತ್ತದೆ ಆದರೆ ಇದು ಖಚಿತವಾಗಿಲ್ಲ." }, - "customRPC": { - "message": "ಕಸ್ಟಮ್ RPC" - }, "customToken": { "message": "ಕಸ್ಟಮ್ ಟೋಕನ್" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "ದಶಮಾಂಶಗಳು ಕನಿಷ್ಟ 0 ಆಗಿರಬೇಕು ಮತ್ತು 36 ಕ್ಕಿಂತ ಹೆಚ್ಚಿರಬಾರದು" }, - "defaultNetwork": { - "message": "ಎಥರ್ ವಹಿವಾಟುಗಳಿಗಾಗಿ ಡೀಫಾಲ್ಟ್ ನೆಟ್‌ವರ್ಕ್ ಪ್ರಮುಖವಾಗಿರುವ ನೆಟ್ ಆಗಿದೆ." - }, "delete": { "message": "ಅಳಿಸಿ" }, @@ -623,9 +617,6 @@ "newContract": { "message": "ಹೊಸ ಒಪ್ಪಂದ" }, - "newNetwork": { - "message": "ಹೊಸ ನೆಟ್‌ವರ್ಕ್" - }, "newPassword": { "message": "ಹೊಸ ಪಾಸ್‌ವರ್ಡ್ (ಕನಿಷ್ಟ 8 ಅಕ್ಷರಗಳು)" }, @@ -674,12 +665,6 @@ "on": { "message": "ಆನ್‌" }, - "optionalBlockExplorerUrl": { - "message": "ಅನ್ವೇಷಕ URL ಅನ್ನು ನಿರ್ಬಂಧಿಸಿ (ಐಚ್ಛಿಕ)" - }, - "optionalCurrencySymbol": { - "message": "ಚಿಹ್ನೆ (ಐಚ್ಛಿಕ)" - }, "origin": { "message": "ಮೂಲ" }, @@ -859,9 +844,6 @@ "selectAnAccount": { "message": "ಖಾತೆಯನ್ನು ಆಯ್ಕೆಮಾಡಿ" }, - "selectAnAccountHelp": { - "message": "MetaMask ನಲ್ಲಿ ವೀಕ್ಷಿಸಲು ಖಾತೆಯನ್ನು ಆಯ್ಕೆಮಾಡಿ" - }, "selectEachPhrase": { "message": "ಅದು ಸರಿಯಾಗಿದೆಯೆ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು ದಯವಿಟ್ಟು ಪ್ರತಿ ಫ್ರೇಸ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ." }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 9ba61d76e..f4c30767d 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -130,10 +130,6 @@ "allowExternalExtensionTo": { "message": "이 외부 확장을 통해 다음을 하도록 허용:" }, - "allowOriginSpendToken": { - "message": "$1에서 $2을(를) 지출하도록 허용하시겠어요?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "이 사이트에서 다음을 하도록 허용:" }, @@ -144,9 +140,6 @@ "amount": { "message": "금액" }, - "amountWithColon": { - "message": "금액:" - }, "appDescription": { "message": "브라우저의 이더리움 지갑", "description": "The description of the application" @@ -474,9 +467,6 @@ "customGasSubTitle": { "message": "수수료를 올리면 처리 시간이 단축되기도 하지만 항상 그렇지는 않습니다." }, - "customRPC": { - "message": "맞춤형 RPC" - }, "customSpendLimit": { "message": "맞춤형 지출 한도" }, @@ -512,9 +502,6 @@ "decryptRequest": { "message": "요청 암호 해독" }, - "defaultNetwork": { - "message": "Ether 거래의 기본 네트워크는 메인 넷입니다." - }, "delete": { "message": "삭제" }, @@ -1193,9 +1180,6 @@ "newContract": { "message": "새 계약" }, - "newNetwork": { - "message": "새 네트워크" - }, "newPassword": { "message": "새 암호(8자 이상)" }, @@ -1334,12 +1318,6 @@ "onlyConnectTrust": { "message": "신뢰하는 사이트만 연결하세요." }, - "optionalBlockExplorerUrl": { - "message": "블록 탐색기 URL(선택 사항)" - }, - "optionalCurrencySymbol": { - "message": "통화 기호(선택 사항)" - }, "origin": { "message": "원본" }, @@ -1638,13 +1616,6 @@ "selectAnAccountAlreadyConnected": { "message": "이 계정은 이미 MetaMask와 연결되어 있습니다." }, - "selectAnAccountHelp": { - "message": "MetaMask에서 확인할 계정을 선택하십시오." - }, - "selectAnAccountHelpDirections": { - "message": "계정을 찾을 수 없습니까? $1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "각 구문을 선택하여 구문이 올바른지 확인하세요." }, @@ -2125,12 +2096,6 @@ "switchEthereumChainConfirmationTitle": { "message": "이 사이트가 네트워크를 전환하도록 허용하시겠어요?" }, - "switchLedgerPaths": { - "message": "Ledger 경로 전환" - }, - "switchLedgerPathsText": { - "message": "다른 계정을 보려면 Ledger 경로를 선택하세요." - }, "switchNetwork": { "message": "네트워크 전환" }, @@ -2201,9 +2166,6 @@ "message": "수신: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "수신:" - }, "token": { "message": "토큰" }, @@ -2281,10 +2243,6 @@ "message": "토큰 잔액을 로드하는 도중 문제가 발생했습니다. 다음에서 잔액을 확인하세요. ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "이 사이트를 신뢰하시나요? 이 권한을 부여하면 $1에서 귀하의 $2을(를) 인출하고 거래를 자동으로 처리하게 됩니다.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "다시 시도" }, diff --git a/app/_locales/lt/messages.json b/app/_locales/lt/messages.json index 01670c783..80536f454 100644 --- a/app/_locales/lt/messages.json +++ b/app/_locales/lt/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Didinant mokestį gali mažėti apdorojimo trukmė, bet tai negarantuojama." }, - "customRPC": { - "message": "Pritaikytas RPC" - }, "customToken": { "message": "Pritaikytas žetonas" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Turi būti bent 0 skaitmenų po kablelio, bet ne daugiau kaip 36." }, - "defaultNetwork": { - "message": "Numatytasis „Ether“ operacijų tinklas yra pagrindinis tinklas." - }, "delete": { "message": "Ištrinti" }, @@ -623,9 +617,6 @@ "newContract": { "message": "Nauja sutartis" }, - "newNetwork": { - "message": "Naujas tinklas" - }, "newPassword": { "message": "Naujas slaptažodis (bent 8 ženklai)" }, @@ -674,12 +665,6 @@ "on": { "message": "Įjungta" }, - "optionalBlockExplorerUrl": { - "message": "Blokuoti naršyklės URL (pasirinktinai)" - }, - "optionalCurrencySymbol": { - "message": "Simbolis (nebūtinas)" - }, "origin": { "message": "Kilmė" }, @@ -859,9 +844,6 @@ "selectAnAccount": { "message": "Pasirinkite paskyrą" }, - "selectAnAccountHelp": { - "message": "Pasirinkite paskyrą peržiūrėti „MetaMask“" - }, "selectEachPhrase": { "message": "Pasirinkite kiekvieną frazę, kad patikrintumėte, ar ji tinkama" }, diff --git a/app/_locales/lv/messages.json b/app/_locales/lv/messages.json index ad541d917..4531820a4 100644 --- a/app/_locales/lv/messages.json +++ b/app/_locales/lv/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Nodevas palielināšana var saīsināt apstrādes laiku, bet ne garantēti." }, - "customRPC": { - "message": "Pielāgots RPC izsaukums" - }, "customToken": { "message": "Pielāgots marķieris" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Daļskaitļiem jābūt diapazonā no 0 līdz 36." }, - "defaultNetwork": { - "message": "Galvenais Ether darījumu tīkls ir Mainnet." - }, "delete": { "message": "Dzēst" }, @@ -619,9 +613,6 @@ "newContract": { "message": "Jauns līgums" }, - "newNetwork": { - "message": "Jauns tīkls" - }, "newPassword": { "message": "Jauna parole (vism. 8 rakstzīmes)" }, @@ -670,12 +661,6 @@ "on": { "message": "Iesl." }, - "optionalBlockExplorerUrl": { - "message": "Bloķēt Explorer URL (pēc izvēles)" - }, - "optionalCurrencySymbol": { - "message": "Simbols (neobligāti)" - }, "origin": { "message": "Avots" }, @@ -855,9 +840,6 @@ "selectAnAccount": { "message": "Atlasiet kontu" }, - "selectAnAccountHelp": { - "message": "Atlasiet kontu, ko skatīt MetaMask" - }, "selectEachPhrase": { "message": "Lūdzu, atlasiet katru frāzi, lai pārliecinātos par tās pareizību." }, diff --git a/app/_locales/ms/messages.json b/app/_locales/ms/messages.json index f58cb95ba..85d021235 100644 --- a/app/_locales/ms/messages.json +++ b/app/_locales/ms/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Meningkatkan fi mungkin akan mengurangkan masa pemprosesan, tetapi ia tidak dijamin." }, - "customRPC": { - "message": "RPC Tersuai" - }, "customToken": { "message": "Token Tersuai" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Perpuluhan mestilah sekurang-kurangnya 0, dan tidak melebihi 36." }, - "defaultNetwork": { - "message": "Rangkaian lalai untuk transaksi Ether ialah Net Utama." - }, "delete": { "message": "Padam" }, @@ -603,9 +597,6 @@ "newContract": { "message": "Kontrak Baru" }, - "newNetwork": { - "message": "Rangkaian Baru" - }, "newPassword": { "message": "Kata Laluan Baru (min 8 aks)" }, @@ -651,12 +642,6 @@ "on": { "message": "Hidupkan" }, - "optionalBlockExplorerUrl": { - "message": "Sekat URL Explorer (pilihan)" - }, - "optionalCurrencySymbol": { - "message": "Simbol (pilihan)" - }, "origin": { "message": "Asal" }, @@ -836,9 +821,6 @@ "selectAnAccount": { "message": "Pilih Akaun" }, - "selectAnAccountHelp": { - "message": "Pilih akaun untuk dilihat dalam MetaMask" - }, "selectEachPhrase": { "message": "Sila pilih setiap ungkapan untuk memastikan ia betul." }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index f5daf1b8b..448eef171 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -67,18 +67,12 @@ "customGas": { "message": "Pas Gas aan" }, - "customRPC": { - "message": "Aangepaste RPC" - }, "decimal": { "message": "Decimalen van precisie" }, "decimalsMustZerotoTen": { "message": "Decimalen moeten minimaal 0 en niet meer dan 36 zijn." }, - "defaultNetwork": { - "message": "Het standaardnetwerk voor Ether-transacties is Mainnet." - }, "depositEther": { "message": "Stort Ether" }, diff --git a/app/_locales/no/messages.json b/app/_locales/no/messages.json index e04e6a42c..249ea5d10 100644 --- a/app/_locales/no/messages.json +++ b/app/_locales/no/messages.json @@ -254,9 +254,6 @@ "customGasSubTitle": { "message": "Økt gebyr kan redusere behandlingstiden, men det er ikke garantert." }, - "customRPC": { - "message": "Tilpasset RPC" - }, "customToken": { "message": "Egendefinert token " }, @@ -266,9 +263,6 @@ "decimalsMustZerotoTen": { "message": "Desimaler må være minst 0, og ikke flere enn 36." }, - "defaultNetwork": { - "message": "Standardnettverket for Ether-transaksjoner er Mainnet." - }, "delete": { "message": "Slett" }, @@ -610,9 +604,6 @@ "newContract": { "message": "Ny kontaktperson" }, - "newNetwork": { - "message": "Nytt nettverk " - }, "newPassword": { "message": "Nytt passord (minimum 8 tegn)" }, @@ -661,12 +652,6 @@ "on": { "message": "På" }, - "optionalBlockExplorerUrl": { - "message": "Blokker Explorer URL (valgfritt)" - }, - "optionalCurrencySymbol": { - "message": "Symbol (valgfritt)" - }, "origin": { "message": "Opprinnelse" }, @@ -843,9 +828,6 @@ "selectAnAccount": { "message": "Velg en konto" }, - "selectAnAccountHelp": { - "message": "Velg konto for å vise i MetaMask" - }, "selectEachPhrase": { "message": "Velg hver frase for å kontrollere at den er riktig." }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index c7d40dd55..571178bc1 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -130,10 +130,6 @@ "allowExternalExtensionTo": { "message": "Payagan ang external extension na ito na:" }, - "allowOriginSpendToken": { - "message": "Payagan ang $1 na gastusin ang iyong $2?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "Payagan ang site na ito na:" }, @@ -144,9 +140,6 @@ "amount": { "message": "Halaga" }, - "amountWithColon": { - "message": "Halaga:" - }, "appDescription": { "message": "Ethereum Wallet sa iyong Browser", "description": "The description of the application" @@ -474,9 +467,6 @@ "customGasSubTitle": { "message": "Kapag dinagdagan ang bayarin, mababawasan ang mga oras ng pagproseso, pero hindi ito garantisado." }, - "customRPC": { - "message": "Custom na RPC" - }, "customSpendLimit": { "message": "Custom na Limitasyon sa Paggastos" }, @@ -512,9 +502,6 @@ "decryptRequest": { "message": "I-decrypt ang request" }, - "defaultNetwork": { - "message": "Ang default na network para sa mga transaksyon ng Ether ay ang Mainnet." - }, "delete": { "message": "I-delete" }, @@ -1193,9 +1180,6 @@ "newContract": { "message": "Bagong Kontrata" }, - "newNetwork": { - "message": "Bagong Network" - }, "newPassword": { "message": "Bagong password (min na 8 char)" }, @@ -1334,12 +1318,6 @@ "onlyConnectTrust": { "message": "Kumonekta lang sa mga site na pinagkakatiwalaan mo." }, - "optionalBlockExplorerUrl": { - "message": "URL ng Block Explorer (opsyonal)" - }, - "optionalCurrencySymbol": { - "message": "Simbolo ng Currency (opsyonal)" - }, "origin": { "message": "Pinagmulan" }, @@ -1638,13 +1616,6 @@ "selectAnAccountAlreadyConnected": { "message": "Nakakonekta na ang account na ito sa MetaMask" }, - "selectAnAccountHelp": { - "message": "Pumili ng account na titingnan sa MetaMask." - }, - "selectAnAccountHelpDirections": { - "message": "Hindi makita ang iyong account? $1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "Pakipili ang bawat phrase para matiyak na tama ito." }, @@ -2125,12 +2096,6 @@ "switchEthereumChainConfirmationTitle": { "message": "Payagang palitan ng site na ito ang network?" }, - "switchLedgerPaths": { - "message": "Magpalit ng path ng Ledger" - }, - "switchLedgerPathsText": { - "message": "Piliin ang path ng Ledger para tingnan ang iba pang account" - }, "switchNetwork": { "message": "Lumipat ng network" }, @@ -2201,9 +2166,6 @@ "message": "Para kay/sa: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "Para kay/sa:" - }, "token": { "message": "Token" }, @@ -2281,10 +2243,6 @@ "message": "Nagkaproblema kami sa pag-load ng mga balanse ng iyong token. Puwede mong tingnan ang mga iyon ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "Pinagkakatiwalaan mo ba ang site na ito? Sa pamamagitan ng pagbibigay ng pahintulot na ito, pinapayagan mo ang $1 na i-withdraw ang iyong $2 at i-automate ang mga transaksyon para sa iyo.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "Subukan ulit" }, diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index 1d415a675..0d0f59571 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Zwiększenie opłaty może skrócić czas przetwarzania transakcji, ale nie jest to gwarantowane." }, - "customRPC": { - "message": "Własne RPC" - }, "customToken": { "message": "Własny token" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Liczb po przecinku musi być co najmniej 0 i nie więcej niż 36." }, - "defaultNetwork": { - "message": "Domyślna sieć dla Eteru to Mainnet." - }, "delete": { "message": "Usuń" }, @@ -620,9 +614,6 @@ "newContract": { "message": "Nowy kontrakt" }, - "newNetwork": { - "message": "Nowa sieć" - }, "newPassword": { "message": "Nowe hasło (min. 8 znaków)" }, @@ -668,12 +659,6 @@ "on": { "message": "Włączone" }, - "optionalBlockExplorerUrl": { - "message": "Adres URL przeglądarki łańcucha bloków (opcjonalnie)" - }, - "optionalCurrencySymbol": { - "message": "Symbol (opcjonalnie)" - }, "origin": { "message": "Pochodzenie" }, @@ -853,9 +838,6 @@ "selectAnAccount": { "message": "Wybierz konto" }, - "selectAnAccountHelp": { - "message": "Wybierz konto do przeglądania w MetaMask" - }, "selectEachPhrase": { "message": "Wybierz każdą frazę, aby upewnić się, że jest poprawna." }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 283ebdfb2..c04767a2d 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -70,18 +70,12 @@ "customGas": { "message": "Customizar Gas" }, - "customRPC": { - "message": "Customizar RPC" - }, "decimal": { "message": "Precisão em Decimais" }, "decimalsMustZerotoTen": { "message": "Decimais devem ser no mínimo 0 e não passar de 36." }, - "defaultNetwork": { - "message": "A rede pré definida para transações em Ether é a Mainnet." - }, "depositEther": { "message": "Depositar Ether" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 40bde73d3..bd2e54f12 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -130,10 +130,6 @@ "allowExternalExtensionTo": { "message": "Permitir que esta extensão externa:" }, - "allowOriginSpendToken": { - "message": "Permitir que $1 gaste seus(suas) $2?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "Permitir que este site:" }, @@ -144,9 +140,6 @@ "amount": { "message": "Valor" }, - "amountWithColon": { - "message": "Valor:" - }, "appDescription": { "message": "Uma carteira do Ethereum no seu navegador", "description": "The description of the application" @@ -474,9 +467,6 @@ "customGasSubTitle": { "message": "Aumentar a taxa pode diminuir o tempo de processamento, mas não é garantia." }, - "customRPC": { - "message": "RPC personalizada" - }, "customSpendLimit": { "message": "Limite de gastos personalizado" }, @@ -512,9 +502,6 @@ "decryptRequest": { "message": "Descriptografar solicitação" }, - "defaultNetwork": { - "message": "A rede padrão das transações em ether é a Mainnet." - }, "delete": { "message": "Excluir" }, @@ -1193,9 +1180,6 @@ "newContract": { "message": "Novo contrato" }, - "newNetwork": { - "message": "Nova rede" - }, "newPassword": { "message": "Nova senha (mín. 8 caract.)" }, @@ -1334,12 +1318,6 @@ "onlyConnectTrust": { "message": "Conecte-se somente com sites em quem você confia." }, - "optionalBlockExplorerUrl": { - "message": "URL do Block Explorer (opcional)" - }, - "optionalCurrencySymbol": { - "message": "Símbolo de moeda (opcional)" - }, "origin": { "message": "Origem" }, @@ -1638,13 +1616,6 @@ "selectAnAccountAlreadyConnected": { "message": "Esta conta já foi conectada ao MetaMask" }, - "selectAnAccountHelp": { - "message": "Selecione uma conta a ser exibida no MetaMask." - }, - "selectAnAccountHelpDirections": { - "message": "Não está vendo sua conta? $1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "Selecione cada frase para garantir que esteja correta." }, @@ -2125,12 +2096,6 @@ "switchEthereumChainConfirmationTitle": { "message": "Permitir que este site mude a rede?" }, - "switchLedgerPaths": { - "message": "Mudar caminhos do Ledger" - }, - "switchLedgerPathsText": { - "message": "Selecione o caminho do Ledger para ver outras contas" - }, "switchNetwork": { "message": "Trocar de rede" }, @@ -2201,9 +2166,6 @@ "message": "Até: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "Até:" - }, "token": { "message": "Token" }, @@ -2281,10 +2243,6 @@ "message": "Tivemos dificuldade para carregar os saldos do seu token. Você pode exibi-los ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "Você confia neste site? Ao conceder esta permissão, você está permitindo que $1 saque seus(suas) $2 e automatize as transações por você.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "Tente novamente" }, diff --git a/app/_locales/ro/messages.json b/app/_locales/ro/messages.json index 97f49a7a5..167fb1c26 100644 --- a/app/_locales/ro/messages.json +++ b/app/_locales/ro/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Creșterea taxei poate reduce timpii de procesare, dar acest lucru nu este garantat." }, - "customRPC": { - "message": "RPC particularizat" - }, "customToken": { "message": "Token personalizat" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Zecimalele trebuie să fie cel puțin 0, dar nu peste 36." }, - "defaultNetwork": { - "message": "Rețeaua implicită pentru tranzacțiile cu Ether este Mainnet." - }, "delete": { "message": "Șterge" }, @@ -610,9 +604,6 @@ "newContract": { "message": "Contract nou" }, - "newNetwork": { - "message": "Rețea nouă" - }, "newPassword": { "message": "Parola Nouă (minimum 8 caractere)" }, @@ -661,12 +652,6 @@ "on": { "message": "Activat" }, - "optionalBlockExplorerUrl": { - "message": "URL explorator bloc (opțional)" - }, - "optionalCurrencySymbol": { - "message": "Simbol (opțional)" - }, "origin": { "message": "Origine" }, @@ -846,9 +831,6 @@ "selectAnAccount": { "message": "Selectați un cont" }, - "selectAnAccountHelp": { - "message": "Selectați contul de vizualizat în MetaMask" - }, "selectEachPhrase": { "message": "Vă rugăm să selectați fiecare expresie pentru a vă asigura că este corectă." }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 881ee1042..40ccc7d45 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -130,10 +130,6 @@ "allowExternalExtensionTo": { "message": "Разрешить этому внешнему расширению:" }, - "allowOriginSpendToken": { - "message": "Разрешить $1 потратить ваши $2?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "Разрешить этому сайту:" }, @@ -144,9 +140,6 @@ "amount": { "message": "Сумма" }, - "amountWithColon": { - "message": "Сумма:" - }, "appDescription": { "message": "Кошелек Ethereum в вашем браузере", "description": "The description of the application" @@ -474,9 +467,6 @@ "customGasSubTitle": { "message": "Увеличение комиссии может сократить время обработки, но это не гарантируется." }, - "customRPC": { - "message": "Пользовательский RPC" - }, "customSpendLimit": { "message": "Пользовательский предел расходов" }, @@ -512,9 +502,6 @@ "decryptRequest": { "message": "Расшифровать запрос" }, - "defaultNetwork": { - "message": "Сетью по умолчанию для транзакций Ether является Mainnet." - }, "delete": { "message": "Удалить" }, @@ -1193,9 +1180,6 @@ "newContract": { "message": "Новый контракт" }, - "newNetwork": { - "message": "Новая сеть" - }, "newPassword": { "message": "Новый пароль (мин.8 знаков)" }, @@ -1334,12 +1318,6 @@ "onlyConnectTrust": { "message": "Подключайтесь только к сайтам, которым доверяете." }, - "optionalBlockExplorerUrl": { - "message": "URL-адрес проводника блока (необязательно)" - }, - "optionalCurrencySymbol": { - "message": "Символ валюты (необязательно)" - }, "origin": { "message": "Источник" }, @@ -1638,13 +1616,6 @@ "selectAnAccountAlreadyConnected": { "message": "Этот счет уже подключен к MetaMask" }, - "selectAnAccountHelp": { - "message": "Выберите счет для просмотра в MetaMask." - }, - "selectAnAccountHelpDirections": { - "message": "Не видите свой счет? $1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "Выберите каждую фразу, чтобы убедиться, что она верна." }, @@ -2125,12 +2096,6 @@ "switchEthereumChainConfirmationTitle": { "message": "Разрешить этому сайту переключить сеть?" }, - "switchLedgerPaths": { - "message": "Переключить пути Ledger" - }, - "switchLedgerPathsText": { - "message": "Выберите путь Ledger для просмотра других счетов" - }, "switchNetwork": { "message": "Переключить сеть" }, @@ -2201,9 +2166,6 @@ "message": "Адресат $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "Адресат" - }, "token": { "message": "Токен" }, @@ -2281,10 +2243,6 @@ "message": "У нас возникли проблемы с загрузкой вашего баланса токенов. Вы можете просмотреть их ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "Вы доверяете этому сайту? Предоставляя это разрешение, вы разрешаете $1 вывести ваши $2 и автоматизировать транзакции для вас.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "Попробуйте еще раз" }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index aefb96bd1..47097b3d1 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -251,9 +251,6 @@ "customGasSubTitle": { "message": "Zvýšenie poplatku môže skrátiť dobu spracovania, nie je to však zaručené." }, - "customRPC": { - "message": "Vlastní RPC" - }, "customToken": { "message": "Vlastní token" }, @@ -263,9 +260,6 @@ "decimalsMustZerotoTen": { "message": "Desetinných míst musí být od 0 do 36." }, - "defaultNetwork": { - "message": "Výchozí síť pro Etherové transakce je Mainnet." - }, "delete": { "message": "Odstrániť" }, @@ -595,9 +589,6 @@ "newContract": { "message": "Nový kontrakt" }, - "newNetwork": { - "message": "Nová sieť" - }, "newPassword": { "message": "Nové heslo (min 8 znaků)" }, @@ -643,12 +634,6 @@ "on": { "message": "Zapnuté" }, - "optionalBlockExplorerUrl": { - "message": "Blokovať URL Explorera (voliteľné)" - }, - "optionalCurrencySymbol": { - "message": "Symbol (voliteľné)" - }, "origin": { "message": "Pôvod" }, @@ -822,9 +807,6 @@ "selectAnAccount": { "message": "Vybrať účet" }, - "selectAnAccountHelp": { - "message": "Vyberte účet, ktorý chcete zobraziť v MetaMask" - }, "selectEachPhrase": { "message": "Vyberte každú frázu, aby ste sa uistili, že je správna." }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index d9173c49a..e5b7b9e44 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Povečanje lahko skrajša čas obdelave, vendar ni zagotovljeno." }, - "customRPC": { - "message": "RPC po meri" - }, "customToken": { "message": "Žeton po meri" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Decimalk mora biti med 0 in 36." }, - "defaultNetwork": { - "message": "Privzeto omrežje za transkacije je glavno omrežje." - }, "delete": { "message": "Izbriši" }, @@ -611,9 +605,6 @@ "newContract": { "message": "Nova pogodba" }, - "newNetwork": { - "message": "Novo omrežje" - }, "newPassword": { "message": "Novo geslo (min 8 znakov)" }, @@ -662,12 +653,6 @@ "on": { "message": "Vklopljeno" }, - "optionalBlockExplorerUrl": { - "message": "Blokiraj URL Explorerja (poljubno)" - }, - "optionalCurrencySymbol": { - "message": "Simbol (nezahtevano)" - }, "origin": { "message": "Izvor" }, @@ -847,9 +832,6 @@ "selectAnAccount": { "message": "Izberi račun" }, - "selectAnAccountHelp": { - "message": "Izberi račun za prikaz v MetaMask" - }, "selectEachPhrase": { "message": "Izberite vsako geslo ter se prepričajte, da je pravilno." }, diff --git a/app/_locales/sr/messages.json b/app/_locales/sr/messages.json index 1bcfee19a..08eed9bf0 100644 --- a/app/_locales/sr/messages.json +++ b/app/_locales/sr/messages.json @@ -254,9 +254,6 @@ "customGasSubTitle": { "message": "Povećanje naknade može smanjiti vreme obrade, ali to nije zagarantovano." }, - "customRPC": { - "message": "Korisnički definisan RPC" - }, "customToken": { "message": "Prilagođeni token" }, @@ -266,9 +263,6 @@ "decimalsMustZerotoTen": { "message": "Decimalni broj mora biti najmanje 0, a ne veći od 36." }, - "defaultNetwork": { - "message": "Podrazumevana mreža za Ether transakcije je Mainnet." - }, "delete": { "message": "Избриши" }, @@ -614,9 +608,6 @@ "newContract": { "message": "Novi ugovor" }, - "newNetwork": { - "message": "Nova mreža" - }, "newPassword": { "message": "Nova lozinka (minimalno 8 karaktera)" }, @@ -665,12 +656,6 @@ "on": { "message": "Укључено" }, - "optionalBlockExplorerUrl": { - "message": "Blokirajte URL Explorer-a (opciono)" - }, - "optionalCurrencySymbol": { - "message": "Simbol (opciono)" - }, "origin": { "message": "Извор" }, @@ -850,9 +835,6 @@ "selectAnAccount": { "message": "Izaberite nalog" }, - "selectAnAccountHelp": { - "message": "Izaberite nalog za prikaz u MetaMask-u" - }, "selectEachPhrase": { "message": "Molimo vas izaberite svaki izraz kako biste proverili da je tačan." }, diff --git a/app/_locales/sv/messages.json b/app/_locales/sv/messages.json index a49e35d41..1c07f7dfc 100644 --- a/app/_locales/sv/messages.json +++ b/app/_locales/sv/messages.json @@ -251,9 +251,6 @@ "customGasSubTitle": { "message": "Att öka avgiften kan minska behandlingstiden, men det är inte garanterat att göra det." }, - "customRPC": { - "message": "Anpassad RPC" - }, "customToken": { "message": "Anpassad token" }, @@ -263,9 +260,6 @@ "decimalsMustZerotoTen": { "message": "Decimalerna måste vara minst 0 och inte över 36." }, - "defaultNetwork": { - "message": "Standardnätverket för Ether-transaktioner är Mainnet." - }, "delete": { "message": "Radera" }, @@ -607,9 +601,6 @@ "newContract": { "message": "Nytt kontrakt" }, - "newNetwork": { - "message": "Nytt nätverk" - }, "newPassword": { "message": "Nytt lösenord (minst 8 tecken)" }, @@ -658,12 +649,6 @@ "on": { "message": "På" }, - "optionalBlockExplorerUrl": { - "message": "Block Explorer URL (valfritt)" - }, - "optionalCurrencySymbol": { - "message": "Symbol (frivillig)" - }, "origin": { "message": "Ursprung" }, @@ -843,9 +828,6 @@ "selectAnAccount": { "message": "Välj ett konto" }, - "selectAnAccountHelp": { - "message": "Välj konto att visa i MetaMask" - }, "selectEachPhrase": { "message": "Välj varje fras för att säkerställa att den är korrekt." }, diff --git a/app/_locales/sw/messages.json b/app/_locales/sw/messages.json index e9eed3a4b..05436654c 100644 --- a/app/_locales/sw/messages.json +++ b/app/_locales/sw/messages.json @@ -251,9 +251,6 @@ "customGasSubTitle": { "message": "Ada iliyoongezeka inaweza kupunguza muda wa uchakataji, lakini haihakikishwi." }, - "customRPC": { - "message": "RPC Maalumu" - }, "customToken": { "message": "Kianzio Maalumu" }, @@ -263,9 +260,6 @@ "decimalsMustZerotoTen": { "message": "Desimali zinapaswa kuwa angalau 0, na si zaidi ya 36." }, - "defaultNetwork": { - "message": "Mtandao chaguomsingi wa miamala ya Ether ni Mainnet." - }, "delete": { "message": "Futa" }, @@ -601,9 +595,6 @@ "newContract": { "message": "Mkataba Mpya" }, - "newNetwork": { - "message": "Mtandao Mpya" - }, "newPassword": { "message": "Nenosiri Jipya (kiwango cha chini herufi 8)" }, @@ -652,12 +643,6 @@ "on": { "message": "Imewashwa" }, - "optionalBlockExplorerUrl": { - "message": "URL ya Block Explorer URL (hiari)" - }, - "optionalCurrencySymbol": { - "message": "Ishara (hiari)" - }, "origin": { "message": "Asili" }, @@ -837,9 +822,6 @@ "selectAnAccount": { "message": "Chagua Akaunti" }, - "selectAnAccountHelp": { - "message": "Chagua akaunti kuangalia kwenye MetaMask" - }, "selectEachPhrase": { "message": "Tafadhali chagua kila kirai ili kuhakikisha kuwa hii ni sahihi." }, diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index f754b0532..d39aa6d05 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -103,9 +103,6 @@ "customGas": { "message": "எரிவாயுவைத் தனிப்பயனாக்குங்கள்" }, - "customRPC": { - "message": "விருப்ப RPC ஐ" - }, "customToken": { "message": "தனிப்பயன் டோக்கன்" }, @@ -115,9 +112,6 @@ "decimalsMustZerotoTen": { "message": "தசமங்கள் குறைந்தபட்சம் 0, மற்றும் 36 க்கு மேல் இருக்க வேண்டும்." }, - "defaultNetwork": { - "message": "எதிர் பரிவர்த்தனைகளுக்கான முன்னிருப்பு வலையமைப்பு முதன்மை நிகரமாகும்." - }, "delete": { "message": "நீக்கு" }, diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index 43e90d04c..7f3e453c6 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -109,18 +109,12 @@ "customGasSubTitle": { "message": "การเพิ่มค่าธรรมเนียมอาจลดเวลาดำเนินการ แต่ก็ไม่แน่เสมอไป" }, - "customRPC": { - "message": "กำหนดค่า RPC เอง" - }, "decimal": { "message": "ตำแหน่งของทศนิยม" }, "decimalsMustZerotoTen": { "message": "จำนวนต้องมากกว่า 0 และไม่เกิน 36" }, - "defaultNetwork": { - "message": "ค่าเริ่มต้นของเครือข่ายสำหรับทำรายการธุรกรรมอีเธอร์คือ Mainnet" - }, "delete": { "message": "ลบ" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 55e6587b5..cbd9275ce 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -97,10 +97,6 @@ "allowExternalExtensionTo": { "message": "Payagan ang external extension na ito na:" }, - "allowOriginSpendToken": { - "message": "Payagan ang $1 na gastusin ang iyong $2?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "Payagan ang site na ito na:" }, @@ -111,9 +107,6 @@ "amount": { "message": "Halaga" }, - "amountWithColon": { - "message": "Halaga:" - }, "appDescription": { "message": "Ethereum Wallet sa iyong Browser", "description": "The description of the application" @@ -396,9 +389,6 @@ "customGasSubTitle": { "message": "Kapag dinagdagan ang bayarin, mababawasan ang mga oras ng pagproseso, pero hindi ito garantisado." }, - "customRPC": { - "message": "Custom na RPC" - }, "customSpendLimit": { "message": "Custom na Limitasyon sa Paggastos" }, @@ -434,9 +424,6 @@ "decryptRequest": { "message": "I-decrypt ang request" }, - "defaultNetwork": { - "message": "Ang default na network para sa mga transaksyon ng Ether ay ang Mainnet." - }, "delete": { "message": "I-delete" }, @@ -1001,9 +988,6 @@ "newContract": { "message": "Bagong Kontrata" }, - "newNetwork": { - "message": "Bagong Network" - }, "newPassword": { "message": "Bagong password (min na 8 char)" }, @@ -1084,12 +1068,6 @@ "onlyConnectTrust": { "message": "Kumonekta lang sa mga site na pinagkakatiwalaan mo." }, - "optionalBlockExplorerUrl": { - "message": "URL ng Block Explorer (opsyonal)" - }, - "optionalCurrencySymbol": { - "message": "Simbolo ng Currency (opsyonal)" - }, "origin": { "message": "Pinagmulan" }, @@ -1319,9 +1297,6 @@ "selectAnAccount": { "message": "Pumili ng Account" }, - "selectAnAccountHelp": { - "message": "Piliin ang account na titingnan sa MetaMask" - }, "selectEachPhrase": { "message": "Pakipili ang bawat phrase para matiyak na tama ito." }, @@ -1749,9 +1724,6 @@ "message": "Para kay/sa: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "Para kay/sa:" - }, "token": { "message": "Token" }, @@ -1823,10 +1795,6 @@ "message": "Nagkaproblema kami sa pag-load ng mga balanse ng iyong token. Puwede mong tingnan ang mga iyon ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "Pinagkakatiwalaan mo ba ang site na ito? Sa pamamagitan ng pagbibigay ng pahintulot na ito, pinapayagan mo ang $1 na i-withdraw ang iyong $2 at i-automate ang mga transaksyon para sa iyo.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "Subukan ulit" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 977162984..85714e974 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -88,9 +88,6 @@ "customGas": { "message": "Gas'i özelleştir" }, - "customRPC": { - "message": "Özel RPC" - }, "customToken": { "message": "Özel Jeton" }, @@ -100,9 +97,6 @@ "decimalsMustZerotoTen": { "message": "Ondalıklar en azından 0 olmalı ve 36'dan büyük olmamalı." }, - "defaultNetwork": { - "message": "Ether işlemleri için varsayılan ağ Mainnet." - }, "depositEther": { "message": "Ether yatır" }, diff --git a/app/_locales/uk/messages.json b/app/_locales/uk/messages.json index fe88a0e32..8968e2f2e 100644 --- a/app/_locales/uk/messages.json +++ b/app/_locales/uk/messages.json @@ -257,9 +257,6 @@ "customGasSubTitle": { "message": "Збільшення комісії може призвести до зменшення часу обробки, але це не гарантується." }, - "customRPC": { - "message": "Налаштувати RPC" - }, "customToken": { "message": "Користувацький токен" }, @@ -269,9 +266,6 @@ "decimalsMustZerotoTen": { "message": "Кількість розрядів після коми: від 0 до 36." }, - "defaultNetwork": { - "message": "Мережа для транзакцій з Ether за замовчуванням - Mainnet." - }, "delete": { "message": "Видалити" }, @@ -623,9 +617,6 @@ "newContract": { "message": "Новий контракт" }, - "newNetwork": { - "message": "Нова мережа" - }, "newPassword": { "message": "Новий пароль (мінімум 8 символів)" }, @@ -674,12 +665,6 @@ "on": { "message": "Увімкнути" }, - "optionalBlockExplorerUrl": { - "message": "Блокувати Explorer URL (не обов'язково)" - }, - "optionalCurrencySymbol": { - "message": "Символ (не обов'язково)" - }, "origin": { "message": "Походження" }, @@ -859,9 +844,6 @@ "selectAnAccount": { "message": "Виберіть обліковий запис" }, - "selectAnAccountHelp": { - "message": "Оберіть обліковий запис для перегляду в MetaMask" - }, "selectEachPhrase": { "message": "Виберіть кожну фразу, щоб переконатися, що вона правильна." }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index a1f78fa6d..584c892c3 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -130,10 +130,6 @@ "allowExternalExtensionTo": { "message": "Cho phép tiện ích bên ngoài này:" }, - "allowOriginSpendToken": { - "message": "Cho phép $1 chi tiêu $2 của bạn?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "Cho phép trang web này:" }, @@ -144,9 +140,6 @@ "amount": { "message": "Số tiền" }, - "amountWithColon": { - "message": "Số tiền:" - }, "appDescription": { "message": "Ví Ethereum trên trình duyệt của bạn", "description": "The description of the application" @@ -474,9 +467,6 @@ "customGasSubTitle": { "message": "Việc tăng phí có thể giúp giảm thời gian xử lý, nhưng điều này không được đảm bảo." }, - "customRPC": { - "message": "RPC tùy chỉnh" - }, "customSpendLimit": { "message": "Giới hạn chi tiêu tùy chỉnh" }, @@ -512,9 +502,6 @@ "decryptRequest": { "message": "Yêu cầu của Decrypt" }, - "defaultNetwork": { - "message": "Mạng mặc định cho các giao dịch Ether là Mạng chính thức." - }, "delete": { "message": "Xóa" }, @@ -1193,9 +1180,6 @@ "newContract": { "message": "Hợp đồng mới" }, - "newNetwork": { - "message": "Mạng mới" - }, "newPassword": { "message": "Mật khẩu mới (tối thiểu 8 ký tự)" }, @@ -1334,12 +1318,6 @@ "onlyConnectTrust": { "message": "Chỉ kết nối với các trang web mà bạn tin tưởng." }, - "optionalBlockExplorerUrl": { - "message": "URL trình khám phá khối (không bắt buộc)" - }, - "optionalCurrencySymbol": { - "message": "Ký hiệu tiền tệ (không bắt buộc)" - }, "origin": { "message": "Nguồn gốc" }, @@ -1638,13 +1616,6 @@ "selectAnAccountAlreadyConnected": { "message": "Tài khoản này đã được kết nối với MetaMask" }, - "selectAnAccountHelp": { - "message": "Chọn một tài khoản để xem trong MetaMask." - }, - "selectAnAccountHelpDirections": { - "message": "Bạn không thấy tài khoản của mình? $1", - "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" - }, "selectEachPhrase": { "message": "Vui lòng chọn từng cụm mật khẩu theo thứ tự để đảm bảo sự chính xác." }, @@ -2125,12 +2096,6 @@ "switchEthereumChainConfirmationTitle": { "message": "Cho phép trang web này chuyển mạng?" }, - "switchLedgerPaths": { - "message": "Chuyển đường dẫn Ledger" - }, - "switchLedgerPathsText": { - "message": "Chọn đường dẫn Ledger để xem các tài khoản khác" - }, "switchNetwork": { "message": "Chuyển mạng" }, @@ -2201,9 +2166,6 @@ "message": "Đến: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "Đến:" - }, "token": { "message": "Token" }, @@ -2281,10 +2243,6 @@ "message": "Chúng tôi đã gặp phải vấn đề khi tải số dư token của bạn. Bạn có thể xem số dư ", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "Bạn có tin tưởng trang web này không? Bằng việc cấp quyền này, bạn sẽ cho phép $1 rút $2 của bạn và tự động thực hiện các giao dịch cho bạn.", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "Thử lại" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index bbd980f1d..8555b89a4 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -103,10 +103,6 @@ "allowExternalExtensionTo": { "message": "允许这个外部扩展到:" }, - "allowOriginSpendToken": { - "message": "允许 $1 使用您的 $2?", - "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" - }, "allowThisSiteTo": { "message": "允许本网站:" }, @@ -117,9 +113,6 @@ "amount": { "message": "数额" }, - "amountWithColon": { - "message": "数额:" - }, "appDescription": { "message": "以太坊浏览器插件", "description": "The description of the application" @@ -405,9 +398,6 @@ "customGasSubTitle": { "message": "提升费用可能会缩短处理时间,但不保证绝对有效。" }, - "customRPC": { - "message": "自定义 RPC" - }, "customSpendLimit": { "message": "自定义消费限额" }, @@ -443,9 +433,6 @@ "decryptRequest": { "message": "解密请求" }, - "defaultNetwork": { - "message": "默认以太坊(Ether)交易网络为主网。" - }, "delete": { "message": "删除" }, @@ -516,7 +503,7 @@ "message": "编辑权限" }, "encryptionPublicKeyNotice": { - "message": "$1 希望得到您的加密公钥。同意后该网站将可以想您发送加密信息。", + "message": "$1 希望得到您的加密公钥。同意后该网站将可以向您发送加密信息。", "description": "$1 is the web3 site name" }, "encryptionPublicKeyRequest": { @@ -1004,9 +991,6 @@ "newContract": { "message": "新合约" }, - "newNetwork": { - "message": "新增网络" - }, "newPassword": { "message": "新密码(至少 8 个字符)" }, @@ -1087,12 +1071,6 @@ "onlyConnectTrust": { "message": "只连接您信任的网站。" }, - "optionalBlockExplorerUrl": { - "message": "区块浏览器 URL(选填)" - }, - "optionalCurrencySymbol": { - "message": "符号(选填)" - }, "origin": { "message": "来源" }, @@ -1322,9 +1300,6 @@ "selectAnAccount": { "message": "选择一个账户" }, - "selectAnAccountHelp": { - "message": "选择在 MetaMask 中查看的账户" - }, "selectEachPhrase": { "message": "请选择每个单词,以确保其正确性。" }, @@ -1773,9 +1748,6 @@ "message": "至:$1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toWithColon": { - "message": "至:" - }, "token": { "message": "代币" }, @@ -1847,10 +1819,6 @@ "message": "我们无法加载您的代币余额。您可以查看它们", "description": "Followed by a link (here) to view token balances" }, - "trustSiteApprovePermission": { - "message": "您信任这个网站吗?授权即表示您允许 $1 提取您的 $2,并为您自动进行交易。", - "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" - }, "tryAgain": { "message": "重试" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 82035e758..91a527f48 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -266,9 +266,6 @@ "customGasSubTitle": { "message": "提升費用可能會加快處理時間,但不保證" }, - "customRPC": { - "message": "自訂 RPC" - }, "customToken": { "message": "自訂代幣" }, @@ -278,9 +275,6 @@ "decimalsMustZerotoTen": { "message": "小數點後位數至少為0, 最多為36." }, - "defaultNetwork": { - "message": "預設以太幣交易網路為主網路" - }, "delete": { "message": "刪除" }, @@ -629,9 +623,6 @@ "newContract": { "message": "建立新合約" }, - "newNetwork": { - "message": "新增網路" - }, "newPassword": { "message": "新密碼(至少8個字元)" }, @@ -674,12 +665,6 @@ "on": { "message": "開啟" }, - "optionalBlockExplorerUrl": { - "message": "區塊鏈瀏覽器 URL(非必要)" - }, - "optionalCurrencySymbol": { - "message": "Symbol (可選)" - }, "origin": { "message": "來源" }, diff --git a/app/build-types/beta/beta-mascot.json b/app/build-types/beta/beta-mascot.json deleted file mode 100644 index 5f922a57e..000000000 --- a/app/build-types/beta/beta-mascot.json +++ /dev/null @@ -1,337 +0,0 @@ -{ - "chunks": [ - { - "color": [0, 0, 0], - "faces": [ - [11, 12, 13], - [36, 15, 37], - [37, 38, 36], - [31, 39, 22], - [22, 21, 31], - [31, 15, 36], - [36, 39, 31], - [64, 65, 66], - [75, 69, 26], - [26, 80, 75], - [75, 80, 38], - [38, 37, 75], - [38, 80, 39], - [39, 36, 38], - [39, 80, 26], - [26, 22, 39] - ] - }, - { - "color": [236, 229, 220], - "faces": [ - [19, 20, 21], - [21, 22, 19], - [20, 19, 23], - [23, 24, 20], - [23, 25, 24], - [19, 22, 26], - [26, 27, 19], - [23, 28, 29], - [23, 29, 30], - [25, 23, 30], - [21, 20, 24], - [24, 31, 21], - [24, 25, 30], - [29, 51, 52], - [52, 30, 29], - [27, 26, 69], - [69, 70, 27], - [70, 71, 72], - [72, 27, 70], - [72, 71, 73], - [51, 74, 72], - [52, 51, 72], - [73, 52, 72], - [69, 71, 70], - [71, 69, 75], - [52, 73, 71], - [19, 27, 74], - [74, 28, 19], - [51, 29, 28], - [28, 74, 51], - [74, 27, 72], - [28, 23, 19] - ] - }, - { - "color": [119, 228, 171], - "faces": [ - [5, 4, 35], - [57, 59, 79] - ] - }, - { - "color": [80, 157, 116], - "faces": [ - [4, 5, 2], - [2, 5, 6], - [57, 56, 55], - [58, 59, 55], - [2, 1, 4], - [55, 59, 57] - ] - }, - { - "color": [67, 127, 95], - "faces": [ - [0, 1, 2], - [2, 3, 0], - [6, 3, 2], - [7, 8, 9], - [10, 3, 6], - [10, 50, 7], - [7, 3, 10], - [7, 9, 3], - [49, 0, 9], - [3, 9, 0], - [53, 54, 55], - [55, 56, 53], - [55, 54, 58], - [60, 61, 62], - [63, 58, 54], - [63, 60, 89], - [60, 63, 54], - [60, 54, 61], - [88, 61, 53], - [54, 53, 61] - ] - }, - { - "color": [119, 228, 207], - "faces": [ - [59, 5, 35], - [35, 79, 59] - ] - }, - { - "color": [163, 230, 235], - "faces": [ - [14, 15, 11], - [11, 16, 14], - [16, 13, 12], - [17, 33, 10], - [17, 18, 34], - [34, 33, 17], - [11, 15, 31], - [18, 12, 11], - [41, 64, 37], - [64, 41, 40], - [66, 65, 40], - [67, 63, 77], - [67, 77, 76], - [76, 68, 67], - [75, 37, 64], - [68, 64, 66] - ] - }, - { - "color": [204, 237, 236], - "faces": [ - [10, 6, 17], - [31, 18, 11], - [14, 16, 40], - [40, 41, 14], - [63, 67, 58], - [64, 68, 75], - [14, 41, 37], - [37, 15, 14], - [5, 59, 40], - [40, 16, 5] - ] - }, - { - "color": [207, 248, 247], - "faces": [ - [6, 5, 16], - [16, 17, 6], - [12, 17, 16], - [58, 67, 40], - [40, 59, 58], - [40, 67, 66] - ] - }, - { - "color": [127, 185, 228], - "faces": [ - [33, 34, 24], - [71, 76, 77] - ] - }, - { - "color": [119, 200, 228], - "faces": [ - [31, 24, 18], - [24, 34, 18], - [35, 4, 42], - [4, 1, 42], - [42, 43, 44], - [44, 35, 42], - [45, 43, 42], - [42, 10, 45], - [30, 32, 24], - [30, 33, 32], - [33, 30, 10], - [44, 43, 46], - [43, 45, 47], - [47, 46, 43], - [48, 47, 45], - [45, 30, 48], - [30, 45, 10], - [49, 42, 0], - [8, 7, 42], - [50, 42, 7], - [50, 10, 42], - [1, 0, 42], - [42, 9, 8], - [42, 49, 9], - [75, 68, 71], - [71, 68, 76], - [79, 81, 57], - [57, 81, 56], - [82, 79, 35], - [35, 44, 82], - [81, 79, 82], - [82, 83, 81], - [84, 63, 81], - [81, 83, 84], - [44, 46, 85], - [85, 82, 44], - [71, 78, 52], - [52, 78, 77], - [77, 63, 52], - [82, 85, 83], - [83, 85, 86], - [86, 84, 83], - [87, 52, 84], - [84, 86, 87], - [52, 63, 84], - [88, 53, 81], - [62, 81, 60], - [89, 60, 81], - [89, 81, 63], - [56, 81, 53], - [81, 62, 61], - [81, 61, 88], - [48, 87, 86], - [86, 47, 48], - [47, 86, 85], - [85, 46, 47], - [48, 30, 52], - [52, 87, 48] - ] - }, - { - "color": [95, 167, 211], - "faces": [ - [24, 32, 33], - [77, 78, 71] - ] - }, - { - "color": [119, 222, 228], - "faces": [ - [17, 12, 18], - [13, 16, 11], - [67, 68, 66], - [65, 64, 40] - ] - } - ], - "positions": [ - [111.024597, 52.604599, 46.225899], - [114.025002, 87.673302, 58.9818], - [66.192001, 80.898003, 55.394299], - [72.113297, 35.491798, 30.871401], - [97.804497, 116.560997, 73.978798], - [16.7623, 58.010899, 58.078201], - [52.608898, 30.3641, 42.556099], - [106.881401, 31.945499, 46.9133], - [113.484596, 38.6049, 49.121498], - [108.6633, 43.2332, 46.315399], - [101.216599, 15.9822, 46.308201], - [16.6605, -16.2883, 93.618698], - [40.775002, -10.2288, 85.276398], - [23.926901, -2.5103, 86.736504], - [11.1691, -7.0037, 99.377602], - [9.5692, -34.393902, 141.671997], - [12.596, 7.1655, 88.740997], - [61.180901, 8.8142, 76.996803], - [39.719501, -28.927099, 88.963799], - [13.7962, -68.575699, 132.057007], - [15.2674, -62.32, 129.688004], - [14.8446, -52.6096, 140.113007], - [12.8917, -49.771599, 144.740997], - [35.604198, -71.758003, 81.063904], - [47.462502, -68.606102, 63.369701], - [38.2486, -64.730202, 38.909901], - [-12.8917, -49.771599, 144.740997], - [-13.7962, -68.575699, 132.057007], - [17.802099, -71.758003, 81.063904], - [19.1243, -69.0168, 49.420101], - [38.2486, -66.275597, 17.776199], - [12.8928, -36.703499, 141.671997], - [109.283997, -93.589897, 27.824301], - [122.117996, -36.8894, 35.025002], - [67.7668, -30.197001, 78.417801], - [33.180698, 101.851997, 25.3186], - [9.4063, -35.589802, 150.722], - [-9.5692, -34.393902, 141.671997], - [-9.4063, -35.589802, 150.722], - [11.4565, -37.899399, 150.722], - [-12.596, 7.1655, 88.740997], - [-11.1691, -7.0037, 99.377602], - [70.236504, 62.836201, -3.9475], - [47.263401, 54.293999, -27.414801], - [28.7302, 91.731102, -24.972601], - [69.167603, 6.5862, -12.7757], - [28.7302, 49.1003, -48.3596], - [31.903, 5.692, -47.821999], - [35.075802, -34.432899, -16.280899], - [115.284103, 48.681499, 48.684101], - [110.842796, 28.4821, 49.176201], - [-19.1243, -69.0168, 49.420101], - [-38.2486, -66.275597, 17.776199], - [-111.024597, 52.604599, 46.225899], - [-72.113297, 35.491798, 30.871401], - [-66.192001, 80.898003, 55.394299], - [-114.025002, 87.673302, 58.9818], - [-97.804497, 116.560997, 73.978798], - [-52.608898, 30.3641, 42.556099], - [-16.7623, 58.010899, 58.078201], - [-106.881401, 31.945499, 46.9133], - [-108.6633, 43.2332, 46.315399], - [-113.484596, 38.6049, 49.121498], - [-101.216599, 15.9822, 46.308201], - [-16.6605, -16.2883, 93.618698], - [-23.926901, -2.5103, 86.736504], - [-40.775002, -10.2288, 85.276398], - [-61.180901, 8.8142, 76.996803], - [-39.719501, -28.927099, 88.963799], - [-14.8446, -52.6096, 140.113007], - [-15.2674, -62.32, 129.688004], - [-47.462502, -68.606102, 63.369701], - [-35.604198, -71.758003, 81.063904], - [-38.2486, -64.730202, 38.909901], - [-17.802099, -71.758003, 81.063904], - [-12.8928, -36.703499, 141.671997], - [-67.7668, -30.197001, 78.417801], - [-122.117996, -36.8894, 35.025002], - [-109.283997, -93.589897, 27.824301], - [-33.180698, 101.851997, 25.3186], - [-11.4565, -37.899399, 150.722], - [-70.236504, 62.836201, -3.9475], - [-28.7302, 91.731102, -24.972601], - [-47.263401, 54.293999, -27.414801], - [-69.167603, 6.5862, -12.7757], - [-28.7302, 49.1003, -48.3596], - [-31.903, 5.692, -47.821999], - [-35.075802, -34.432899, -16.280899], - [-115.284103, 48.681499, 48.684101], - [-110.842796, 28.4821, 49.176201] - ] -} diff --git a/app/build-types/beta/images/beta-mascot.json b/app/build-types/beta/images/beta-mascot.json new file mode 100644 index 000000000..4db57f543 --- /dev/null +++ b/app/build-types/beta/images/beta-mascot.json @@ -0,0 +1,830 @@ +{ + "chunks": [ + { + "faces": [ + [0, 1, 2], + [2, 3, 0], + [4, 5, 2], + [6, 3, 2], + [2, 5, 6], + [7, 8, 9], + [10, 3, 6], + [10, 50, 7], + [7, 3, 10], + [7, 9, 3], + [49, 0, 9], + [3, 9, 0], + [2, 1, 4] + ], + "name": "left ear", + "gradient": "left-ear-gradient" + }, + { + "faces": [ + [53, 54, 55], + [55, 56, 53], + [57, 56, 55], + [58, 59, 55], + [55, 54, 58], + [60, 61, 62], + [63, 58, 54], + [63, 60, 89], + [60, 63, 54], + [60, 54, 61], + [88, 61, 53], + [54, 53, 61], + [55, 59, 57] + ], + "name": "right ear", + "gradient": "right-ear-gradient" + }, + { + "color": [22, 22, 22], + "faces": [[11, 12, 13]], + "name": "left eye" + }, + { + "color": [22, 22, 22], + "faces": [[64, 65, 66]], + "name": "right eye" + }, + { + "faces": [ + [14, 15, 11], + [11, 16, 14] + ], + "name": "left inner eye", + "gradient": "left-inner-eye-gradient" + }, + { + "faces": [[17, 12, 18]], + "name": "left outer eye", + "gradient": "left-outer-eye-gradient" + }, + { + "faces": [[41, 64, 37]], + "name": "right lower inner eye", + "gradient": "right-inner-eye-gradient" + }, + { + "faces": [[67, 68, 66]], + "name": "right outer eye", + "gradient": "right-outer-eye-gradient" + }, + { + "color": [192, 173, 158], + "faces": [ + [19, 20, 21], + [21, 22, 19], + [20, 19, 23], + [23, 24, 20], + [23, 25, 24], + [19, 22, 26], + [26, 27, 19], + [23, 28, 29], + [23, 29, 30], + [25, 23, 30], + [29, 51, 52], + [52, 30, 29], + [27, 26, 69], + [69, 70, 27], + [70, 71, 72], + [72, 27, 70], + [72, 71, 73], + [51, 74, 72], + [52, 51, 72], + [73, 52, 72], + [19, 27, 74], + [74, 28, 19], + [51, 29, 28], + [28, 74, 51], + [74, 27, 72], + [28, 23, 19] + ], + "name": "lower chin" + }, + { + "color": [215, 193, 179], + "faces": [ + [21, 20, 24], + [24, 31, 21] + ], + "name": "left lower snout" + }, + { + "color": [215, 193, 179], + "faces": [ + [69, 71, 70], + [71, 69, 75] + ], + "name": "right lower snout" + }, + { + "faces": [[31, 24, 18]], + "name": "left upper snout", + "gradient": "left-upper-snout-gradient" + }, + { + "faces": [ + [6, 5, 16], + [16, 17, 6] + ], + "name": "left forehead", + "gradient": "left-forehead-gradient" + }, + { + "faces": [ + [24, 32, 33], + [33, 34, 24] + ], + "name": "left lower cheek", + "gradient": "left-lower-cheek-gradient" + }, + { + "faces": [[5, 4, 35]], + "name": "left top ear", + "gradient": "left-top-ear-gradient" + }, + { + "faces": [[75, 68, 71]], + "name": "right upper snout", + "gradient": "right-upper-snout-gradient" + }, + { + "faces": [ + [58, 67, 40], + [40, 59, 58] + ], + "name": "right forhead", + "gradient": "right-forehead-gradient" + }, + { + "faces": [ + [71, 76, 77], + [77, 78, 71] + ], + "name": "right lower cheek", + "gradient": "right-lower-cheek-gradient" + }, + { + "faces": [[24, 34, 18]], + "name": "left middle cheek", + "gradient": "left-middle-cheek-gradient" + }, + { + "color": [35, 151, 119], + "faces": [ + [16, 13, 12], + [12, 17, 16], + [13, 16, 11] + ], + "name": "left above eye" + }, + { + "faces": [[71, 68, 76]], + "name": "right middle cheek", + "gradient": "right-middle-cheek-gradient" + }, + { + "color": [35, 151, 119], + "faces": [ + [40, 67, 66], + [66, 65, 40], + [65, 64, 40] + ], + "name": "right above eye" + }, + { + "color": [22, 22, 22], + "faces": [ + [36, 15, 37], + [37, 38, 36], + [31, 39, 22], + [22, 21, 31], + [31, 15, 36], + [36, 39, 31], + [75, 69, 26], + [26, 80, 75], + [75, 80, 38], + [38, 37, 75], + [38, 80, 39], + [39, 36, 38], + [39, 80, 26], + [26, 22, 39] + ], + "name": "nose" + }, + { + "faces": [ + [17, 33, 10], + [17, 18, 34], + [34, 33, 17], + [10, 6, 17] + ], + "name": "left upper cheek", + "gradient": "left-upper-cheek-gradient" + }, + { + "faces": [ + [11, 15, 31], + [31, 18, 11], + [18, 12, 11] + ], + "name": "left below eye", + "gradient": "left-below-eye-gradient" + }, + { + "faces": [ + [14, 16, 40], + [40, 41, 14], + [59, 5, 35], + [35, 79, 59], + [14, 41, 37], + [37, 15, 14], + [5, 59, 40], + [40, 16, 5] + ], + "name": "forehead", + "gradient": "forehead-gradient" + }, + { + "faces": [ + [67, 63, 77], + [67, 77, 76], + [76, 68, 67], + [63, 67, 58] + ], + "name": "right upper cheek", + "gradient": "right-upper-cheek-gradient" + }, + { + "faces": [ + [64, 68, 75], + [75, 37, 64], + [68, 64, 66] + ], + "name": "right below eye", + "gradient": "right-below-eye-gradient" + }, + { + "faces": [ + [35, 4, 42], + [4, 1, 42], + [42, 43, 44], + [44, 35, 42], + [45, 43, 42], + [42, 10, 45], + [30, 32, 24], + [24, 25, 30], + [30, 33, 32], + [33, 30, 10], + [44, 43, 46], + [43, 45, 47], + [47, 46, 43], + [48, 47, 45], + [45, 30, 48], + [30, 45, 10], + [49, 42, 0], + [8, 7, 42], + [50, 42, 7], + [50, 10, 42], + [1, 0, 42], + [42, 9, 8], + [42, 49, 9], + [79, 81, 57], + [57, 81, 56], + [82, 79, 35], + [35, 44, 82], + [81, 79, 82], + [82, 83, 81], + [84, 63, 81], + [81, 83, 84], + [44, 46, 85], + [85, 82, 44], + [52, 73, 71], + [71, 78, 52], + [52, 78, 77], + [77, 63, 52], + [82, 85, 83], + [83, 85, 86], + [86, 84, 83], + [87, 52, 84], + [84, 86, 87], + [52, 63, 84], + [88, 53, 81], + [62, 81, 60], + [89, 60, 81], + [89, 81, 63], + [56, 81, 53], + [81, 62, 61], + [81, 61, 88], + [48, 87, 86], + [86, 47, 48], + [47, 86, 85], + [85, 46, 47], + [48, 30, 52], + [52, 87, 48] + ], + "name": "back", + "gradient": "back-gradient" + }, + { + "faces": [[57, 59, 79]], + "name": "right top ear", + "gradient": "right-top-ear-gradient" + }, + { + "faces": [[64, 41, 40]], + "name": "right inner upper eye", + "gradient": "right-inner-eye-gradient" + } + ], + "gradients": { + "forehead-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#23FE4A" + }, + { + "offset": 1, + "stop-color": "#BAD8EF" + } + ], + "x1": "50%", + "y1": "20.232164948453608%", + "x2": "50%", + "y2": "74.87123711340206%", + "gradientUnits": "userSpaceOnUse" + }, + "right-upper-cheek-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#20B475" + }, + { + "offset": 1, + "stop-color": "#70BDCE" + } + ], + "x1": "77.19501199040768%", + "y1": "44.68123711340206%", + "x2": "77.19501199040768%", + "y2": "68.2861855670103%", + "gradientUnits": "userSpaceOnUse" + }, + "left-upper-cheek-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#20B475" + }, + { + "offset": 1, + "stop-color": "#70BDCE" + } + ], + "x1": "22.820719424460435%", + "y1": "44.68123711340206%", + "x2": "22.820719424460435%", + "y2": "68.2861855670103%", + "gradientUnits": "userSpaceOnUse" + }, + "right-below-eye-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#85BBE1" + }, + { + "offset": 1, + "stop-color": "#7CCACA" + } + ], + "x1": "54.34676258992806%", + "y1": "68.26917525773197%", + "x2": "65.3001438848921%", + "y2": "68.26917525773197%", + "gradientUnits": "userSpaceOnUse" + }, + "left-below-eye-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#7CCACA" + }, + { + "offset": 1, + "stop-color": "#85BBE1" + } + ], + "x1": "34.731223021582736%", + "y1": "68.26917525773197%", + "x2": "45.65323741007194%", + "y2": "68.26917525773197%", + "gradientUnits": "userSpaceOnUse" + }, + "right-ear-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#074F1E" + }, + { + "offset": 0.4286, + "stop-color": "#05541C" + }, + { + "offset": 0.62, + "stop-color": "#006A13" + }, + { + "offset": 1, + "stop-color": "#007514" + } + ], + "x1": "61.443549160671466%", + "y1": "44.51773195876289%", + "x2": "93.802206235012%", + "y2": "24.439072164948456%", + "gradientUnits": "userSpaceOnUse" + }, + "left-ear-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#074F1E" + }, + { + "offset": 0.4286, + "stop-color": "#05541C" + }, + { + "offset": 0.62, + "stop-color": "#006A13" + }, + { + "offset": 1, + "stop-color": "#007514" + } + ], + "x1": "32.7432134292566%", + "y1": "44.33329896907217%", + "x2": "4.853390887290168%", + "y2": "19.18181443298969%", + "gradientUnits": "userSpaceOnUse" + }, + "left-outer-eye-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#43C3A2" + }, + { + "offset": 1, + "stop-color": "#4FAFC0" + }, + { + "offset": 1, + "stop-color": "#4FAFC0" + } + ], + "x1": "27.575539568345324%", + "y1": "60.519278350515464%", + "x2": "34.982350119904076%", + "y2": "60.519278350515464%", + "gradientUnits": "userSpaceOnUse" + }, + "right-outer-eye-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#4FAFC0" + }, + { + "offset": 1, + "stop-color": "#43C3A2" + } + ], + "x1": "65.01764988009592%", + "y1": "60.519278350515464%", + "x2": "72.42446043165468%", + "y2": "60.519278350515464%", + "gradientUnits": "userSpaceOnUse" + }, + "right-lower-cheek-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#59ADCB" + }, + { + "offset": 1, + "stop-color": "#436CC8" + } + ], + "x1": "77.93247002398083%", + "y1": "68.15113402061857%", + "x2": "77.93247002398083%", + "y2": "86.82577319587631%", + "gradientUnits": "userSpaceOnUse" + }, + "left-lower-cheek-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#59ADCB" + }, + { + "offset": 1, + "stop-color": "#436CC8" + } + ], + "x1": "22.083165467625896%", + "y1": "68.15113402061857%", + "x2": "22.083165467625896%", + "y2": "86.82577319587631%", + "gradientUnits": "userSpaceOnUse" + }, + "left-top-ear-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#0ED54A" + }, + { + "offset": 1, + "stop-color": "#0ED54A" + } + ], + "x1": "13.954513189448441%", + "y1": "22.055670103092787%", + "x2": "44.146762589928066%", + "y2": "22.055670103092787%", + "gradientUnits": "userSpaceOnUse" + }, + "right-top-ear-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#0ED54A" + }, + { + "offset": 1, + "stop-color": "#11EB36" + } + ], + "x1": "55.85333333333334%", + "y1": "22.055670103092787%", + "x2": "86.04556354916068%", + "y2": "22.055670103092787%", + "gradientUnits": "userSpaceOnUse" + }, + "left-forehead-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#15DC5D" + }, + { + "offset": 1, + "stop-color": "#48CA9F" + } + ], + "x1": "36.3947242206235%", + "y1": "34.11144329896908%", + "x2": "36.3947242206235%", + "y2": "53.59649484536083%", + "gradientUnits": "userSpaceOnUse" + }, + "right-forehead-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#15DC5D" + }, + { + "offset": 1, + "stop-color": "#48CA9F" + } + ], + "x1": "63.6052757793765%", + "y1": "34.11144329896908%", + "x2": "63.6052757793765%", + "y2": "53.59649484536083%", + "gradientUnits": "userSpaceOnUse" + }, + "left-upper-snout-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#54A8CF" + }, + { + "offset": 1, + "stop-color": "#5393E3" + } + ], + "x1": "38.829736211031175%", + "y1": "68.28865979381443%", + "x2": "38.829736211031175%", + "y2": "81.55670103092784%", + "gradientUnits": "userSpaceOnUse" + }, + "right-upper-snout-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#54A8CF" + }, + { + "offset": 1, + "stop-color": "#5393E3" + } + ], + "x1": "61.17026378896883%", + "y1": "68.28865979381443%", + "x2": "61.17026378896883%", + "y2": "81.55670103092784%", + "gradientUnits": "userSpaceOnUse" + }, + "right-middle-cheek-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#32819D" + }, + { + "offset": 0.3363, + "stop-color": "#447DCD" + } + ], + "x1": "69.9137649880096%", + "y1": "51.063505154639174%", + "x2": "69.9137649880096%", + "y2": "85.81041237113402%", + "gradientUnits": "userSpaceOnUse" + }, + "left-middle-cheek-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#32819D" + }, + { + "offset": 0.3363, + "stop-color": "#447DCD" + } + ], + "x1": "30.086330935251798%", + "y1": "68.15092783505153%", + "x2": "30.086330935251798%", + "y2": "81.55752577319588%", + "gradientUnits": "userSpaceOnUse" + }, + "right-inner-eye-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#53A9CB" + }, + { + "offset": 1, + "stop-color": "#44C0A6" + } + ], + "x1": "55.38244604316547%", + "y1": "74.87123711340206%", + "x2": "55.38244604316547%", + "y2": "53.59659793814433%", + "gradientUnits": "userSpaceOnUse" + }, + "left-inner-eye-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#53A9CB" + }, + { + "offset": 1, + "stop-color": "#44C0A6" + } + ], + "x1": "43.58177458033573%", + "y1": "64.2339175257732%", + "x2": "45.65323741007194%", + "y2": "64.2339175257732%", + "gradientUnits": "userSpaceOnUse" + }, + "back-gradient": { + "type": "linear", + "stops": [ + { + "stop-color": "#27FC4E" + }, + { + "offset": 1, + "stop-color": "#446FC9" + } + ], + "x1": "50%", + "y1": "0%", + "x2": "50%", + "y2": "100%", + "gradientUnits": "userSpaceOnUse" + } + }, + "positions": [ + [111.024597, 52.604599, 46.225899], + [114.025002, 87.673302, 58.9818], + [66.192001, 80.898003, 55.394299], + [72.113297, 35.491798, 30.871401], + [97.804497, 116.560997, 73.978798], + [16.7623, 58.010899, 58.078201], + [52.608898, 30.3641, 42.556099], + [106.881401, 31.945499, 46.9133], + [113.484596, 38.6049, 49.121498], + [108.6633, 43.2332, 46.315399], + [101.216599, 15.9822, 46.308201], + [16.6605, -16.2883, 93.618698], + [40.775002, -10.2288, 85.276398], + [23.926901, -2.5103, 86.736504], + [11.1691, -7.0037, 99.377602], + [9.5692, -34.393902, 141.671997], + [12.596, 7.1655, 88.740997], + [61.180901, 8.8142, 76.996803], + [39.719501, -28.927099, 88.963799], + [13.7962, -68.575699, 132.057007], + [15.2674, -62.32, 129.688004], + [14.8446, -52.6096, 140.113007], + [12.8917, -49.771599, 144.740997], + [35.604198, -71.758003, 81.063904], + [47.462502, -68.606102, 63.369701], + [38.2486, -64.730202, 38.909901], + [-12.8917, -49.771599, 144.740997], + [-13.7962, -68.575699, 132.057007], + [17.802099, -71.758003, 81.063904], + [19.1243, -69.0168, 49.420101], + [38.2486, -66.275597, 17.776199], + [12.8928, -36.703499, 141.671997], + [109.283997, -93.589897, 27.824301], + [122.117996, -36.8894, 35.025002], + [67.7668, -30.197001, 78.417801], + [33.180698, 101.851997, 25.3186], + [9.4063, -35.589802, 150.722], + [-9.5692, -34.393902, 141.671997], + [-9.4063, -35.589802, 150.722], + [11.4565, -37.899399, 150.722], + [-12.596, 7.1655, 88.740997], + [-11.1691, -7.0037, 99.377602], + [70.236504, 62.836201, -3.9475], + [47.263401, 54.293999, -27.414801], + [28.7302, 91.731102, -24.972601], + [69.167603, 6.5862, -12.7757], + [28.7302, 49.1003, -48.3596], + [31.903, 5.692, -47.821999], + [35.075802, -34.432899, -16.280899], + [115.284103, 48.681499, 48.684101], + [110.842796, 28.4821, 49.176201], + [-19.1243, -69.0168, 49.420101], + [-38.2486, -66.275597, 17.776199], + [-111.024597, 52.604599, 46.225899], + [-72.113297, 35.491798, 30.871401], + [-66.192001, 80.898003, 55.394299], + [-114.025002, 87.673302, 58.9818], + [-97.804497, 116.560997, 73.978798], + [-52.608898, 30.3641, 42.556099], + [-16.7623, 58.010899, 58.078201], + [-106.881401, 31.945499, 46.9133], + [-108.6633, 43.2332, 46.315399], + [-113.484596, 38.6049, 49.121498], + [-101.216599, 15.9822, 46.308201], + [-16.6605, -16.2883, 93.618698], + [-23.926901, -2.5103, 86.736504], + [-40.775002, -10.2288, 85.276398], + [-61.180901, 8.8142, 76.996803], + [-39.719501, -28.927099, 88.963799], + [-14.8446, -52.6096, 140.113007], + [-15.2674, -62.32, 129.688004], + [-47.462502, -68.606102, 63.369701], + [-35.604198, -71.758003, 81.063904], + [-38.2486, -64.730202, 38.909901], + [-17.802099, -71.758003, 81.063904], + [-12.8928, -36.703499, 141.671997], + [-67.7668, -30.197001, 78.417801], + [-122.117996, -36.8894, 35.025002], + [-109.283997, -93.589897, 27.824301], + [-33.180698, 101.851997, 25.3186], + [-11.4565, -37.899399, 150.722], + [-70.236504, 62.836201, -3.9475], + [-28.7302, 91.731102, -24.972601], + [-47.263401, 54.293999, -27.414801], + [-69.167603, 6.5862, -12.7757], + [-28.7302, 49.1003, -48.3596], + [-31.903, 5.692, -47.821999], + [-35.075802, -34.432899, -16.280899], + [-115.284103, 48.681499, 48.684101], + [-110.842796, 28.4821, 49.176201] + ] +} diff --git a/app/build-types/beta/icon-128.png b/app/build-types/beta/images/icon-128.png similarity index 100% rename from app/build-types/beta/icon-128.png rename to app/build-types/beta/images/icon-128.png diff --git a/app/build-types/beta/icon-16.png b/app/build-types/beta/images/icon-16.png similarity index 100% rename from app/build-types/beta/icon-16.png rename to app/build-types/beta/images/icon-16.png diff --git a/app/build-types/beta/icon-19.png b/app/build-types/beta/images/icon-19.png similarity index 100% rename from app/build-types/beta/icon-19.png rename to app/build-types/beta/images/icon-19.png diff --git a/app/build-types/beta/icon-32.png b/app/build-types/beta/images/icon-32.png similarity index 100% rename from app/build-types/beta/icon-32.png rename to app/build-types/beta/images/icon-32.png diff --git a/app/build-types/beta/icon-38.png b/app/build-types/beta/images/icon-38.png similarity index 100% rename from app/build-types/beta/icon-38.png rename to app/build-types/beta/images/icon-38.png diff --git a/app/build-types/beta/icon-48.png b/app/build-types/beta/images/icon-48.png similarity index 100% rename from app/build-types/beta/icon-48.png rename to app/build-types/beta/images/icon-48.png diff --git a/app/build-types/beta/icon-512.png b/app/build-types/beta/images/icon-512.png similarity index 100% rename from app/build-types/beta/icon-512.png rename to app/build-types/beta/images/icon-512.png diff --git a/app/build-types/beta/icon-64.png b/app/build-types/beta/images/icon-64.png similarity index 100% rename from app/build-types/beta/icon-64.png rename to app/build-types/beta/images/icon-64.png diff --git a/app/build-types/beta/info-logo.png b/app/build-types/beta/images/info-logo.png similarity index 100% rename from app/build-types/beta/info-logo.png rename to app/build-types/beta/images/info-logo.png diff --git a/app/build-types/beta/logo/metamask-fox.svg b/app/build-types/beta/images/logo/metamask-fox.svg similarity index 100% rename from app/build-types/beta/logo/metamask-fox.svg rename to app/build-types/beta/images/logo/metamask-fox.svg diff --git a/app/build-types/beta/logo/metamask-logo-horizontal.svg b/app/build-types/beta/images/logo/metamask-logo-horizontal-dark.svg similarity index 100% rename from app/build-types/beta/logo/metamask-logo-horizontal.svg rename to app/build-types/beta/images/logo/metamask-logo-horizontal-dark.svg diff --git a/app/build-types/beta/logo/metamask-logo-horizontal-dark.svg b/app/build-types/beta/images/logo/metamask-logo-horizontal.svg similarity index 100% rename from app/build-types/beta/logo/metamask-logo-horizontal-dark.svg rename to app/build-types/beta/images/logo/metamask-logo-horizontal.svg diff --git a/app/manifest/_beta_modifications.json b/app/build-types/beta/manifest/_base.json similarity index 100% rename from app/manifest/_beta_modifications.json rename to app/build-types/beta/manifest/_base.json diff --git a/app/build-types/beta/manifest/firefox.json b/app/build-types/beta/manifest/firefox.json new file mode 100644 index 000000000..79e3cda65 --- /dev/null +++ b/app/build-types/beta/manifest/firefox.json @@ -0,0 +1,7 @@ +{ + "applications": { + "gecko": { + "id": "webextension-beta@metamask.io" + } + } +} diff --git a/app/build-types/flask/images/flask-mascot.json b/app/build-types/flask/images/flask-mascot.json new file mode 100644 index 000000000..43b200544 --- /dev/null +++ b/app/build-types/flask/images/flask-mascot.json @@ -0,0 +1,824 @@ +{ + "chunks": [ + { + "faces": [ + [0, 1, 2], + [2, 3, 0], + [4, 5, 2], + [6, 3, 2], + [2, 5, 6], + [7, 8, 9], + [10, 3, 6], + [10, 50, 7], + [7, 3, 10], + [7, 9, 3], + [49, 0, 9], + [3, 9, 0], + [2, 1, 4] + ], + "name": "left ear", + "gradient": "left-ear-gradient" + }, + { + "faces": [ + [53, 54, 55], + [55, 56, 53], + [57, 56, 55], + [58, 59, 55], + [55, 54, 58], + [60, 61, 62], + [63, 58, 54], + [63, 60, 89], + [60, 63, 54], + [60, 54, 61], + [88, 61, 53], + [54, 53, 61], + [55, 59, 57] + ], + "name": "right ear", + "gradient": "right-ear-gradient" + }, + { + "color": [22, 22, 22], + "faces": [[11, 12, 13]], + "name": "left eye" + }, + { + "color": [22, 22, 22], + "faces": [[64, 65, 66]], + "name": "right eye" + }, + { + "faces": [ + [14, 15, 11], + [11, 16, 14] + ], + "name": "left inner eye", + "gradient": "left-inner-eye-gradient" + }, + { + "faces": [[17, 12, 18]], + "name": "left outer eye", + "gradient": "left-outer-eye-gradient" + }, + { + "faces": [[41, 64, 37]], + "name": "right lower inner eye", + "gradient": "right-inner-eye-gradient" + }, + { + "faces": [[67, 68, 66]], + "name": "right outer eye", + "gradient": "right-outer-eye-gradient" + }, + { + "color": [223, 117, 84], + "faces": [ + [19, 20, 21], + [21, 22, 19], + [20, 19, 23], + [23, 24, 20], + [23, 25, 24], + [19, 22, 26], + [26, 27, 19], + [23, 28, 29], + [23, 29, 30], + [25, 23, 30], + [29, 51, 52], + [52, 30, 29], + [27, 26, 69], + [69, 70, 27], + [70, 71, 72], + [72, 27, 70], + [72, 71, 73], + [51, 74, 72], + [52, 51, 72], + [73, 52, 72], + [19, 27, 74], + [74, 28, 19], + [51, 29, 28], + [28, 74, 51], + [74, 27, 72], + [28, 23, 19] + ], + "name": "lower chin" + }, + { + "color": [255, 159, 90], + "faces": [ + [21, 20, 24], + [24, 31, 21] + ], + "name": "left lower snout" + }, + { + "color": [255, 159, 90], + "faces": [ + [69, 71, 70], + [71, 69, 75] + ], + "name": "right lower snout" + }, + { + "color": [147, 131, 250], + "faces": [[31, 24, 18]], + "name": "left upper snout" + }, + { + "faces": [ + [6, 5, 16], + [16, 17, 6] + ], + "name": "left forehead", + "gradient": "left-forehead-gradient" + }, + { + "faces": [ + [24, 32, 33], + [33, 34, 24] + ], + "name": "left lower cheek", + "gradient": "left-lower-cheek-gradient" + }, + { + "faces": [[5, 4, 35]], + "name": "left top ear", + "gradient": "left-top-ear-gradient" + }, + { + "color": [147, 131, 250], + "faces": [[75, 68, 71]], + "name": "right upper snout" + }, + { + "faces": [ + [58, 67, 40], + [40, 59, 58] + ], + "name": "right forhead", + "gradient": "right-forehead-gradient" + }, + { + "faces": [ + [71, 76, 77], + [77, 78, 71] + ], + "name": "right lower cheek", + "gradient": "right-lower-cheek-gradient" + }, + { + "faces": [[24, 34, 18]], + "name": "left middle cheek", + "gradient": "left-middle-cheek-gradient" + }, + { + "color": [156, 90, 221], + "faces": [ + [16, 13, 12], + [12, 17, 16], + [13, 16, 11] + ], + "name": "left above eye" + }, + { + "faces": [[71, 68, 76]], + "name": "right middle cheek", + "gradient": "right-middle-cheek-gradient" + }, + { + "color": [156, 90, 221], + "faces": [ + [40, 67, 66], + [66, 65, 40], + [65, 64, 40] + ], + "name": "right above eye" + }, + { + "color": [22, 22, 22], + "faces": [ + [36, 15, 37], + [37, 38, 36], + [31, 39, 22], + [22, 21, 31], + [31, 15, 36], + [36, 39, 31], + [75, 69, 26], + [26, 80, 75], + [75, 80, 38], + [38, 37, 75], + [38, 80, 39], + [39, 36, 38], + [39, 80, 26], + [26, 22, 39] + ], + "name": "nose" + }, + { + "faces": [ + [17, 33, 10], + [17, 18, 34], + [34, 33, 17], + [10, 6, 17] + ], + "name": "left upper cheek", + "gradient": "left-upper-cheek-gradient" + }, + { + "faces": [ + [11, 15, 31], + [31, 18, 11], + [18, 12, 11] + ], + "name": "left below eye", + "gradient": "left-below-eye-gradient" + }, + { + "faces": [ + [14, 16, 40], + [40, 41, 14], + [59, 5, 35], + [35, 79, 59], + [14, 41, 37], + [37, 15, 14], + [5, 59, 40], + [40, 16, 5] + ], + "name": "forehead", + "gradient": "forehead-gradient" + }, + { + "faces": [ + [67, 63, 77], + [67, 77, 76], + [76, 68, 67], + [63, 67, 58] + ], + "name": "right upper cheek", + "gradient": "right-upper-cheek-gradient" + }, + { + "faces": [ + [64, 68, 75], + [75, 37, 64], + [68, 64, 66] + ], + "name": "right below eye", + "gradient": "right-below-eye-gradient" + }, + { + "faces": [ + [35, 4, 42], + [4, 1, 42], + [42, 43, 44], + [44, 35, 42], + [45, 43, 42], + [42, 10, 45], + [30, 32, 24], + [24, 25, 30], + [30, 33, 32], + [33, 30, 10], + [44, 43, 46], + [43, 45, 47], + [47, 46, 43], + [48, 47, 45], + [45, 30, 48], + [30, 45, 10], + [49, 42, 0], + [8, 7, 42], + [50, 42, 7], + [50, 10, 42], + [1, 0, 42], + [42, 9, 8], + [42, 49, 9], + [79, 81, 57], + [57, 81, 56], + [82, 79, 35], + [35, 44, 82], + [81, 79, 82], + [82, 83, 81], + [84, 63, 81], + [81, 83, 84], + [44, 46, 85], + [85, 82, 44], + [52, 73, 71], + [71, 78, 52], + [52, 78, 77], + [77, 63, 52], + [82, 85, 83], + [83, 85, 86], + [86, 84, 83], + [87, 52, 84], + [84, 86, 87], + [52, 63, 84], + [88, 53, 81], + [62, 81, 60], + [89, 60, 81], + [89, 81, 63], + [56, 81, 53], + [81, 62, 61], + [81, 61, 88], + [48, 87, 86], + [86, 47, 48], + [47, 86, 85], + [85, 46, 47], + [48, 30, 52], + [52, 87, 48] + ], + "name": "back", + "gradient": "back-gradient" + }, + { + "faces": [[57, 59, 79]], + "name": "right top ear", + "gradient": "right-top-ear-gradient" + }, + { + "faces": [[64, 41, 40]], + "name": "right inner upper eye", + "gradient": "right-inner-eye-gradient" + } + ], + "gradients": { + "left-inner-eye-gradient": { + "type": "linear", + "x1": "41.97721822541966%", + "y1": "67.79239690721649%", + "x2": "44.56654676258992%", + "y2": "67.79239690721649%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#BA86F3" + }, + { + "offset": "0.5281", + "stop-color": "#B786F4" + }, + { + "offset": "0.8987", + "stop-color": "#AE86F5" + }, + { + "offset": "1", + "stop-color": "#AA86F6" + } + ] + }, + "right-inner-eye-gradient": { + "type": "linear", + "x1": "56.72805755395684%", + "y1": "81.08904639175258%", + "x2": "56.72805755395684%", + "y2": "54.49574742268041%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#BA86F3" + }, + { + "offset": "0.5281", + "stop-color": "#B786F4" + }, + { + "offset": "0.8987", + "stop-color": "#AE86F5" + }, + { + "offset": "1", + "stop-color": "#AA86F6" + } + ] + }, + "left-middle-cheek-gradient": { + "type": "linear", + "x1": "25.107913669064747%", + "y1": "72.68865979381442%", + "x2": "25.107913669064747%", + "y2": "89.44690721649484%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6848BA" + }, + { + "offset": "0.3363", + "stop-color": "#6356D5" + } + ] + }, + "right-middle-cheek-gradient": { + "type": "linear", + "x1": "74.89208633093526%", + "y1": "51.32938144329896%", + "x2": "74.89208633093526%", + "y2": "94.76301546391753%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6848BA" + }, + { + "offset": "0.3363", + "stop-color": "#6356D5" + } + ] + }, + "right-forehead-gradient": { + "type": "linear", + "x1": "67.00671462829736%", + "y1": "30.13930412371134%", + "x2": "67.00671462829736%", + "y2": "54.49561855670103%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#DC69E6" + }, + { + "offset": "1", + "stop-color": "#C289F3" + } + ] + }, + "left-forehead-gradient": { + "type": "linear", + "x1": "32.99340527577938%", + "y1": "30.13930412371134%", + "x2": "32.99340527577938%", + "y2": "54.49561855670103%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#DC69E6" + }, + { + "offset": "1", + "stop-color": "#C289F3" + } + ] + }, + "right-top-ear-gradient": { + "type": "linear", + "x1": "95.056858513189448%", + "y1": "15.06958762886598%", + "x2": "57.31654676258992%", + "y2": "15.06958762886598%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#BB65ED" + }, + { + "offset": "1", + "stop-color": "#E560E3" + } + ] + }, + "left-top-ear-gradient": { + "type": "linear", + "x1": "4.943141486810552%", + "y1": "15.06958762886598%", + "x2": "42.68345323741008%", + "y2": "15.06958762886598%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#BB65ED" + }, + { + "offset": "1", + "stop-color": "#E560E3" + } + ] + }, + "left-lower-cheek-gradient": { + "type": "linear", + "x1": "15.103956834532372%", + "y1": "72.6889175257732%", + "x2": "15.103956834532372%", + "y2": "96.03221649484537%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#906EF7" + }, + { + "offset": "1", + "stop-color": "#575ADE" + } + ] + }, + "right-lower-cheek-gradient": { + "type": "linear", + "x1": "84.91570743405276%", + "y1": "72.6889175257732%", + "x2": "84.91570743405276%", + "y2": "96.03221649484537%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#906EF7" + }, + { + "offset": "1", + "stop-color": "#575ADE" + } + ] + }, + "right-outer-eye-gradient": { + "type": "linear", + "x1": "68.7720623501199%", + "y1": "63.14909793814433%", + "x2": "78.03057553956835%", + "y2": "63.14909793814433%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#BA86F3" + }, + { + "offset": "0.5281", + "stop-color": "#B786F4" + }, + { + "offset": "0.8987", + "stop-color": "#AE86F5" + }, + { + "offset": "1", + "stop-color": "#AA86F6" + } + ] + }, + "left-outer-eye-gradient": { + "type": "linear", + "x1": "21.969424460431654%", + "y1": "63.14909793814433%", + "x2": "31.227937649880094%", + "y2": "63.14909793814433%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#BA86F3" + }, + { + "offset": "0.5281", + "stop-color": "#B786F4" + }, + { + "offset": "0.8987", + "stop-color": "#AE86F5" + }, + { + "offset": "1", + "stop-color": "#AA86F6" + } + ] + }, + "left-ear-gradient": { + "type": "linear", + "x1": "50%", + "y1": "30%", + "x2": "4%", + "y2": "4%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#541758" + }, + { + "offset": "0.4286", + "stop-color": "#4F206C" + }, + { + "offset": "0.62", + "stop-color": "#4D2577" + }, + { + "offset": "1", + "stop-color": "#8B45B6" + } + ] + }, + "right-ear-gradient": { + "type": "linear", + "x1": "50%", + "y1": "30%", + "x2": "96%", + "y2": "4%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#541758" + }, + { + "offset": "0.4286", + "stop-color": "#4F206C" + }, + { + "offset": "0.62", + "stop-color": "#4D2577" + }, + { + "offset": "1", + "stop-color": "#8B45B6" + } + ] + }, + "left-below-eye-gradient": { + "type": "linear", + "x1": "30.914028776978412%", + "y1": "72.83646907216496%", + "x2": "44.56654676258992%", + "y2": "72.83646907216496%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#C8A8F7" + }, + { + "offset": "1", + "stop-color": "#BAAAFB" + } + ] + }, + "right-below-eye-gradient": { + "type": "linear", + "x1": "55.43345323741007%", + "y1": "72.83646907216496%", + "x2": "69.12517985611511%", + "y2": "72.83646907216496%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#C8A8F7" + }, + { + "offset": "1", + "stop-color": "#BAAAFB" + } + ] + }, + "left-upper-cheek-gradient": { + "type": "linear", + "x1": "16.02589928057554%", + "y1": "43.35154639175258%", + "x2": "16.02589928057554%", + "y2": "72.85773195876288%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#B65FE5" + }, + { + "offset": "1", + "stop-color": "#ADA2FC" + } + ] + }, + "right-upper-cheek-gradient": { + "type": "linear", + "x1": "83.99364508393285%", + "y1": "43.35154639175258%", + "x2": "83.99364508393285%", + "y2": "72.85773195876288%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#B65FE5" + }, + { + "offset": "1", + "stop-color": "#ADA2FC" + } + ] + }, + "forehead-gradient": { + "type": "linear", + "x1": "50%", + "y1": "12.790180412371136%", + "x2": "50%", + "y2": "81.08904639175258%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#FB7FE4" + }, + { + "offset": "1", + "stop-color": "#BCABFB" + } + ] + }, + "back-gradient": { + "type": "linear", + "x1": "50%", + "y1": "12.790180412371136%", + "x2": "50%", + "y2": "81.08904639175258%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#FB7FE4" + }, + { + "offset": "1", + "stop-color": "#5C5CE0" + } + ] + } + }, + "positions": [ + [111.024597, 52.604599, 46.225899], + [114.025002, 87.673302, 58.9818], + [66.192001, 80.898003, 55.394299], + [72.113297, 35.491798, 30.871401], + [97.804497, 116.560997, 73.978798], + [16.7623, 58.010899, 58.078201], + [52.608898, 30.3641, 42.556099], + [106.881401, 31.945499, 46.9133], + [113.484596, 38.6049, 49.121498], + [108.6633, 43.2332, 46.315399], + [101.216599, 15.9822, 46.308201], + [16.6605, -16.2883, 93.618698], + [40.775002, -10.2288, 85.276398], + [23.926901, -2.5103, 86.736504], + [11.1691, -7.0037, 99.377602], + [9.5692, -34.393902, 141.671997], + [12.596, 7.1655, 88.740997], + [61.180901, 8.8142, 76.996803], + [39.719501, -28.927099, 88.963799], + [13.7962, -68.575699, 132.057007], + [15.2674, -62.32, 129.688004], + [14.8446, -52.6096, 140.113007], + [12.8917, -49.771599, 144.740997], + [35.604198, -71.758003, 81.063904], + [47.462502, -68.606102, 63.369701], + [38.2486, -64.730202, 38.909901], + [-12.8917, -49.771599, 144.740997], + [-13.7962, -68.575699, 132.057007], + [17.802099, -71.758003, 81.063904], + [19.1243, -69.0168, 49.420101], + [38.2486, -66.275597, 17.776199], + [12.8928, -36.703499, 141.671997], + [109.283997, -93.589897, 27.824301], + [122.117996, -36.8894, 35.025002], + [67.7668, -30.197001, 78.417801], + [33.180698, 101.851997, 25.3186], + [9.4063, -35.589802, 150.722], + [-9.5692, -34.393902, 141.671997], + [-9.4063, -35.589802, 150.722], + [11.4565, -37.899399, 150.722], + [-12.596, 7.1655, 88.740997], + [-11.1691, -7.0037, 99.377602], + [70.236504, 62.836201, -3.9475], + [47.263401, 54.293999, -27.414801], + [28.7302, 91.731102, -24.972601], + [69.167603, 6.5862, -12.7757], + [28.7302, 49.1003, -48.3596], + [31.903, 5.692, -47.821999], + [35.075802, -34.432899, -16.280899], + [115.284103, 48.681499, 48.684101], + [110.842796, 28.4821, 49.176201], + [-19.1243, -69.0168, 49.420101], + [-38.2486, -66.275597, 17.776199], + [-111.024597, 52.604599, 46.225899], + [-72.113297, 35.491798, 30.871401], + [-66.192001, 80.898003, 55.394299], + [-114.025002, 87.673302, 58.9818], + [-97.804497, 116.560997, 73.978798], + [-52.608898, 30.3641, 42.556099], + [-16.7623, 58.010899, 58.078201], + [-106.881401, 31.945499, 46.9133], + [-108.6633, 43.2332, 46.315399], + [-113.484596, 38.6049, 49.121498], + [-101.216599, 15.9822, 46.308201], + [-16.6605, -16.2883, 93.618698], + [-23.926901, -2.5103, 86.736504], + [-40.775002, -10.2288, 85.276398], + [-61.180901, 8.8142, 76.996803], + [-39.719501, -28.927099, 88.963799], + [-14.8446, -52.6096, 140.113007], + [-15.2674, -62.32, 129.688004], + [-47.462502, -68.606102, 63.369701], + [-35.604198, -71.758003, 81.063904], + [-38.2486, -64.730202, 38.909901], + [-17.802099, -71.758003, 81.063904], + [-12.8928, -36.703499, 141.671997], + [-67.7668, -30.197001, 78.417801], + [-122.117996, -36.8894, 35.025002], + [-109.283997, -93.589897, 27.824301], + [-33.180698, 101.851997, 25.3186], + [-11.4565, -37.899399, 150.722], + [-70.236504, 62.836201, -3.9475], + [-28.7302, 91.731102, -24.972601], + [-47.263401, 54.293999, -27.414801], + [-69.167603, 6.5862, -12.7757], + [-28.7302, 49.1003, -48.3596], + [-31.903, 5.692, -47.821999], + [-35.075802, -34.432899, -16.280899], + [-115.284103, 48.681499, 48.684101], + [-110.842796, 28.4821, 49.176201] + ] +} diff --git a/app/build-types/flask/images/icon-128.png b/app/build-types/flask/images/icon-128.png new file mode 100644 index 000000000..4dddd86c3 Binary files /dev/null and b/app/build-types/flask/images/icon-128.png differ diff --git a/app/build-types/flask/images/icon-16.png b/app/build-types/flask/images/icon-16.png new file mode 100644 index 000000000..f67228dbc Binary files /dev/null and b/app/build-types/flask/images/icon-16.png differ diff --git a/app/build-types/flask/images/icon-19.png b/app/build-types/flask/images/icon-19.png new file mode 100644 index 000000000..40ff28ba7 Binary files /dev/null and b/app/build-types/flask/images/icon-19.png differ diff --git a/app/build-types/flask/images/icon-32.png b/app/build-types/flask/images/icon-32.png new file mode 100644 index 000000000..79918f98f Binary files /dev/null and b/app/build-types/flask/images/icon-32.png differ diff --git a/app/build-types/flask/images/icon-38.png b/app/build-types/flask/images/icon-38.png new file mode 100644 index 000000000..05f0b4b51 Binary files /dev/null and b/app/build-types/flask/images/icon-38.png differ diff --git a/app/build-types/flask/images/icon-48.png b/app/build-types/flask/images/icon-48.png new file mode 100644 index 000000000..5e280b4e0 Binary files /dev/null and b/app/build-types/flask/images/icon-48.png differ diff --git a/app/build-types/flask/images/icon-512.png b/app/build-types/flask/images/icon-512.png new file mode 100644 index 000000000..eae504a57 Binary files /dev/null and b/app/build-types/flask/images/icon-512.png differ diff --git a/app/build-types/flask/images/icon-64.png b/app/build-types/flask/images/icon-64.png new file mode 100644 index 000000000..680a96b10 Binary files /dev/null and b/app/build-types/flask/images/icon-64.png differ diff --git a/app/build-types/flask/images/logo/metamask-fox.svg b/app/build-types/flask/images/logo/metamask-fox.svg new file mode 100644 index 000000000..094aebc36 --- /dev/null +++ b/app/build-types/flask/images/logo/metamask-fox.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build-types/flask/images/logo/metamask-logo-horizontal-dark.svg b/app/build-types/flask/images/logo/metamask-logo-horizontal-dark.svg new file mode 100644 index 000000000..c38ba8c45 --- /dev/null +++ b/app/build-types/flask/images/logo/metamask-logo-horizontal-dark.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build-types/flask/images/logo/metamask-logo-horizontal.svg b/app/build-types/flask/images/logo/metamask-logo-horizontal.svg new file mode 100644 index 000000000..450ac8434 --- /dev/null +++ b/app/build-types/flask/images/logo/metamask-logo-horizontal.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build-types/flask/manifest/_base.json b/app/build-types/flask/manifest/_base.json new file mode 100644 index 000000000..d9c8ec220 --- /dev/null +++ b/app/build-types/flask/manifest/_base.json @@ -0,0 +1,26 @@ +{ + "browser_action": { + "default_icon": { + "16": "images/icon-16.png", + "19": "images/icon-19.png", + "32": "images/icon-32.png", + "38": "images/icon-38.png", + "64": "images/icon-64.png", + "128": "images/icon-128.png", + "512": "images/icon-512.png" + }, + "default_title": "MetaMask Flask" + }, + "icons": { + "16": "images/icon-16.png", + "19": "images/icon-19.png", + "32": "images/icon-32.png", + "38": "images/icon-38.png", + "48": "images/icon-48.png", + "64": "images/icon-64.png", + "128": "images/icon-128.png", + "512": "images/icon-512.png" + }, + "name": "__MSG_appName__ Flask", + "short_name": "__MSG_appName__ Flask" +} diff --git a/app/build-types/flask/manifest/firefox.json b/app/build-types/flask/manifest/firefox.json new file mode 100644 index 000000000..7c8fb8f13 --- /dev/null +++ b/app/build-types/flask/manifest/firefox.json @@ -0,0 +1,7 @@ +{ + "applications": { + "gecko": { + "id": "webextension-flask@metamask.io" + } + } +} diff --git a/app/images/caret-right.svg b/app/images/caret-right.svg index b1a990ad1..1308f59ce 100644 --- a/app/images/caret-right.svg +++ b/app/images/caret-right.svg @@ -1,3 +1,3 @@ - - + + diff --git a/app/images/check_circle.svg b/app/images/check_circle.svg new file mode 100644 index 000000000..514183743 --- /dev/null +++ b/app/images/check_circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/connect-lattice.svg b/app/images/connect-lattice.svg new file mode 100644 index 000000000..b58f5845a --- /dev/null +++ b/app/images/connect-lattice.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/images/lattice-logo.png b/app/images/lattice-logo.png new file mode 100644 index 000000000..0100aba0f Binary files /dev/null and b/app/images/lattice-logo.png differ diff --git a/app/images/no-nfts.svg b/app/images/no-nfts.svg new file mode 100644 index 000000000..220eb4790 --- /dev/null +++ b/app/images/no-nfts.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/manifest/_base.json b/app/manifest/_base.json index e4f55da26..bc63ce27d 100644 --- a/app/manifest/_base.json +++ b/app/manifest/_base.json @@ -66,6 +66,7 @@ "clipboardWrite", "http://localhost:8545/", "https://*.infura.io/", + "https://wallet.gridplus.io/*", "activeTab", "webRequest", "*://*.eth/", diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 1539cbcba..28e84d7b0 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -7,6 +7,14 @@ import { METAMETRICS_BACKGROUND_PAGE_OBJECT, } from '../../../shared/constants/metametrics'; +const defaultCaptureException = (err) => { + // throw error on clean stack so its captured by platform integrations (eg sentry) + // but does not interupt the call stack + setTimeout(() => { + throw err; + }); +}; + /** * @typedef {import('../../../shared/constants/metametrics').MetaMetricsContext} MetaMetricsContext * @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventPayload} MetaMetricsEventPayload @@ -51,7 +59,9 @@ export default class MetaMetricsController { version, environment, initState, + captureException = defaultCaptureException, }) { + this._captureException = captureException; const prefState = preferencesStore.getState(); this.chainId = getCurrentChainId(); this.network = getNetworkIdentifier(); @@ -258,32 +268,52 @@ export default class MetaMetricsController { * view */ trackPage({ name, params, environmentType, page, referrer }, options) { - if (this.state.participateInMetaMetrics === false) { - return; - } + try { + if (this.state.participateInMetaMetrics === false) { + return; + } - if (this.state.participateInMetaMetrics === null && !options?.isOptInPath) { - return; + if ( + this.state.participateInMetaMetrics === null && + !options?.isOptInPath + ) { + return; + } + const { metaMetricsId } = this.state; + const idTrait = metaMetricsId ? 'userId' : 'anonymousId'; + const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID; + this.segment.page({ + [idTrait]: idValue, + name, + properties: { + params, + locale: this.locale, + network: this.network, + chain_id: this.chainId, + environment_type: environmentType, + }, + context: this._buildContext(referrer, page), + }); + } catch (err) { + this._captureException(err); } - const { metaMetricsId } = this.state; - const idTrait = metaMetricsId ? 'userId' : 'anonymousId'; - const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID; - this.segment.page({ - [idTrait]: idValue, - name, - properties: { - params, - locale: this.locale, - network: this.network, - chain_id: this.chainId, - environment_type: environmentType, - }, - context: this._buildContext(referrer, page), - }); } /** - * track a metametrics event, performing necessary payload manipulation and + * submits a metametrics event, not waiting for it to complete or allowing its error to bubble up + * @param {MetaMetricsEventPayload} payload - details of the event + * @param {MetaMetricsEventOptions} [options] - options for handling/routing the event + */ + trackEvent(payload, options) { + // validation is not caught and handled + this.validatePayload(payload); + this.submitEvent(payload, options).catch((err) => + this._captureException(err), + ); + } + + /** + * submits (or queues for submission) a metametrics event, performing necessary payload manipulation and * routing the event to the appropriate segment source. Will split events * with sensitiveProperties into two events, tracking the sensitiveProperties * with the anonymousId only. @@ -291,21 +321,8 @@ export default class MetaMetricsController { * @param {MetaMetricsEventOptions} [options] - options for handling/routing the event * @returns {Promise} */ - async trackEvent(payload, options) { - // event and category are required fields for all payloads - if (!payload.event || !payload.category) { - throw new Error( - `Must specify event and category. Event was: ${ - payload.event - }. Category was: ${payload.category}. Payload keys were: ${Object.keys( - payload, - )}. ${ - typeof payload.properties === 'object' - ? `Payload property keys were: ${Object.keys(payload.properties)}` - : '' - }`, - ); - } + async submitEvent(payload, options) { + this.validatePayload(payload); if (!this.state.participateInMetaMetrics && !options?.isOptIn) { return; @@ -345,4 +362,25 @@ export default class MetaMetricsController { await Promise.all(events); } + + /** + * validates a metametrics event + * @param {MetaMetricsEventPayload} payload - details of the event + */ + validatePayload(payload) { + // event and category are required fields for all payloads + if (!payload.event || !payload.category) { + throw new Error( + `Must specify event and category. Event was: ${ + payload.event + }. Category was: ${payload.category}. Payload keys were: ${Object.keys( + payload, + )}. ${ + typeof payload.properties === 'object' + ? `Payload property keys were: ${Object.keys(payload.properties)}` + : '' + }`, + ); + } + } } diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index bb0d15bed..9918d6319 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -196,14 +196,14 @@ describe('MetaMetricsController', function () { }); }); - describe('trackEvent', function () { + describe('submitEvent', function () { it('should not track an event if user is not participating in metametrics', function () { const mock = sinon.mock(segment); const metaMetricsController = getMetaMetricsController({ participateInMetaMetrics: false, }); mock.expects('track').never(); - metaMetricsController.trackEvent({ + metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', properties: { @@ -230,7 +230,7 @@ describe('MetaMetricsController', function () { ...DEFAULT_EVENT_PROPERTIES, }, }); - metaMetricsController.trackEvent( + metaMetricsController.submitEvent( { event: 'Fake Event', category: 'Unit Test', @@ -260,7 +260,7 @@ describe('MetaMetricsController', function () { ...DEFAULT_EVENT_PROPERTIES, }, }); - metaMetricsController.trackEvent( + metaMetricsController.submitEvent( { event: 'Fake Event', category: 'Unit Test', @@ -289,7 +289,7 @@ describe('MetaMetricsController', function () { ...DEFAULT_EVENT_PROPERTIES, }, }); - metaMetricsController.trackEvent( + metaMetricsController.submitEvent( { event: 'Fake Event', category: 'Unit Test', @@ -317,7 +317,7 @@ describe('MetaMetricsController', function () { ...DEFAULT_EVENT_PROPERTIES, }, }); - metaMetricsController.trackEvent({ + metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', properties: { @@ -331,7 +331,7 @@ describe('MetaMetricsController', function () { const metaMetricsController = getMetaMetricsController(); const flushStub = sinon.stub(segment, 'flush'); const flushCalled = waitUntilCalled(flushStub, segment); - metaMetricsController.trackEvent( + metaMetricsController.submitEvent( { event: 'Fake Event', category: 'Unit Test', @@ -344,13 +344,13 @@ describe('MetaMetricsController', function () { it('should throw if event or category not provided', function () { const metaMetricsController = getMetaMetricsController(); assert.rejects( - () => metaMetricsController.trackEvent({ event: 'test' }), + () => metaMetricsController.submitEvent({ event: 'test' }), /Must specify event and category\./u, 'must specify category', ); assert.rejects( - () => metaMetricsController.trackEvent({ category: 'test' }), + () => metaMetricsController.submitEvent({ category: 'test' }), /Must specify event and category\./u, 'must specify event', ); @@ -360,7 +360,7 @@ describe('MetaMetricsController', function () { const metaMetricsController = getMetaMetricsController(); assert.rejects( () => - metaMetricsController.trackEvent( + metaMetricsController.submitEvent( { event: 'Fake Event', category: 'Unit Test', @@ -375,7 +375,7 @@ describe('MetaMetricsController', function () { it('should track sensitiveProperties in a separate, anonymous event', function () { const metaMetricsController = getMetaMetricsController(); const spy = sinon.spy(segment, 'track'); - metaMetricsController.trackEvent({ + metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', sensitiveProperties: { foo: 'bar' }, diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index 772a9ebde..ff143bff8 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -1,10 +1,13 @@ import { createScaffoldMiddleware, mergeMiddleware } from 'json-rpc-engine'; -import createBlockRefMiddleware from 'eth-json-rpc-middleware/block-ref'; -import createRetryOnEmptyMiddleware from 'eth-json-rpc-middleware/retryOnEmpty'; -import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'; -import createInflightCacheMiddleware from 'eth-json-rpc-middleware/inflight-cache'; -import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector'; -import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'; +import { + createBlockRefMiddleware, + createRetryOnEmptyMiddleware, + createBlockCacheMiddleware, + createInflightCacheMiddleware, + createBlockTrackerInspectorMiddleware, + providerFromMiddleware, +} from 'eth-json-rpc-middleware'; + import createInfuraMiddleware from 'eth-json-rpc-infura'; import { PollingBlockTracker } from 'eth-block-tracker'; diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js index 050430740..f4ca59157 100644 --- a/app/scripts/controllers/network/createJsonRpcClient.js +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -1,10 +1,12 @@ import { createAsyncMiddleware, mergeMiddleware } from 'json-rpc-engine'; -import createFetchMiddleware from 'eth-json-rpc-middleware/fetch'; -import createBlockRefRewriteMiddleware from 'eth-json-rpc-middleware/block-ref-rewrite'; -import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'; -import createInflightMiddleware from 'eth-json-rpc-middleware/inflight-cache'; -import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector'; -import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'; +import { + createFetchMiddleware, + createBlockRefRewriteMiddleware, + createBlockCacheMiddleware, + createInflightCacheMiddleware, + createBlockTrackerInspectorMiddleware, + providerFromMiddleware, +} from 'eth-json-rpc-middleware'; import { PollingBlockTracker } from 'eth-block-tracker'; import { SECOND } from '../../../../shared/constants/time'; @@ -27,7 +29,7 @@ export default function createJsonRpcClient({ rpcUrl, chainId }) { createChainIdMiddleware(chainId), createBlockRefRewriteMiddleware({ blockTracker }), createBlockCacheMiddleware({ blockTracker }), - createInflightMiddleware(), + createInflightCacheMiddleware(), createBlockTrackerInspectorMiddleware({ blockTracker }), fetchMiddleware, ]); diff --git a/app/scripts/controllers/network/createMetamaskMiddleware.js b/app/scripts/controllers/network/createMetamaskMiddleware.js index c3803a061..cee6b9b95 100644 --- a/app/scripts/controllers/network/createMetamaskMiddleware.js +++ b/app/scripts/controllers/network/createMetamaskMiddleware.js @@ -1,5 +1,5 @@ import { createScaffoldMiddleware, mergeMiddleware } from 'json-rpc-engine'; -import createWalletSubprovider from 'eth-json-rpc-middleware/wallet'; +import { createWalletMiddleware } from 'eth-json-rpc-middleware'; import { createPendingNonceMiddleware, createPendingTxMiddleware, @@ -21,11 +21,10 @@ export default function createMetamaskMiddleware({ }) { const metamaskMiddleware = mergeMiddleware([ createScaffoldMiddleware({ - // staticSubprovider eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, }), - createWalletSubprovider({ + createWalletMiddleware({ getAccounts, processTransaction, processEthSignMessage, diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 543649d35..d581c9581 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -2,7 +2,7 @@ import { strict as assert } from 'assert'; import EventEmitter from 'events'; import { ComposedStore, ObservableStore } from '@metamask/obs-store'; import { JsonRpcEngine } from 'json-rpc-engine'; -import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'; +import { providerFromEngine } from 'eth-json-rpc-middleware'; import log from 'loglevel'; import { createSwappableProxy, diff --git a/app/scripts/controllers/network/pending-middleware.test.js b/app/scripts/controllers/network/pending-middleware.test.js index ca97063b7..0c8d9bc04 100644 --- a/app/scripts/controllers/network/pending-middleware.test.js +++ b/app/scripts/controllers/network/pending-middleware.test.js @@ -1,5 +1,6 @@ import { strict as assert } from 'assert'; import { GAS_LIMITS } from '../../../../shared/constants/gas'; +import { TRANSACTION_ENVELOPE_TYPES } from '../../../../shared/constants/transaction'; import { txMetaStub } from '../../../../test/stub/tx-meta-stub'; import { createPendingNonceMiddleware, @@ -63,7 +64,7 @@ describe('PendingNonceMiddleware', function () { '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09', input: '0x', nonce: '0x4', - type: '0x0', + type: TRANSACTION_ENVELOPE_TYPES.LEGACY, to: '0xf231d46dd78806e1dd93442cf33c7671f8538748', transactionIndex: null, value: '0x0', diff --git a/app/scripts/controllers/network/util.test.js b/app/scripts/controllers/network/util.test.js index f8e03ff0f..a1b7097b8 100644 --- a/app/scripts/controllers/network/util.test.js +++ b/app/scripts/controllers/network/util.test.js @@ -2,7 +2,9 @@ import { strict as assert } from 'assert'; import { TRANSACTION_STATUSES, TRANSACTION_TYPES, + TRANSACTION_ENVELOPE_TYPES, } from '../../../../shared/constants/transaction'; + import { formatTxMetaForRpcResult } from './util'; describe('network utils', function () { @@ -92,7 +94,7 @@ describe('network utils', function () { s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff', to: '0x1678a085c290ebd122dc42cba69373b5953b831d', transactionIndex: null, - type: '0x0', + type: TRANSACTION_ENVELOPE_TYPES.LEGACY, v: '0x29', value: '0x0', }; diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 41630cce3..a53cf8993 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -37,6 +37,7 @@ export default class PreferencesController { // set to true means the dynamic list from the API is being used // set to false will be using the static list from contract-metadata useTokenDetection: false, + advancedGasFee: null, // WARNING: Do not use feature flags for security-sensitive things. // Feature flag toggling is available in the global namespace @@ -53,6 +54,7 @@ export default class PreferencesController { preferences: { autoLockTimeLimit: undefined, showFiatInTestnets: false, + showTestNetworks: false, useNativeCurrencyAsPrimaryCurrency: true, hideZeroBalanceTokens: false, }, @@ -128,6 +130,16 @@ export default class PreferencesController { this.store.updateState({ useTokenDetection: val }); } + /** + * Setter for the `advancedGasFee` property + * + * @param {object} val - holds the maxBaseFee and PriorityFee that the user set as default advanced settings. + * + */ + setAdvancedGasFee(val) { + this.store.updateState({ advancedGasFee: val }); + } + /** * Add new methodData to state, to avoid requesting this information again through Infura * diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index 51277bce2..bce64e1a9 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -266,4 +266,28 @@ describe('preferences controller', function () { ); }); }); + + describe('setAdvancedGasFee', function () { + it('should default to null', function () { + const state = preferencesController.store.getState(); + assert.equal(state.advancedGasFee, null); + }); + + it('should set the setAdvancedGasFee property in state', function () { + const state = preferencesController.store.getState(); + assert.equal(state.advancedGasFee, null); + preferencesController.setAdvancedGasFee({ + maxBaseFee: '1.5', + priorityFee: '2', + }); + assert.equal( + preferencesController.store.getState().advancedGasFee.maxBaseFee, + '1.5', + ); + assert.equal( + preferencesController.store.getState().advancedGasFee.priorityFee, + '2', + ); + }); + }); }); diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index 0d69dd77c..57cd11b9c 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -280,7 +280,7 @@ export default class SwapsController { // For a user to be able to swap a token, they need to have approved the MetaSwap contract to withdraw that token. // _getERC20Allowance() returns the amount of the token they have approved for withdrawal. If that amount is greater - // than 0, it means that approval has already occured and is not needed. Otherwise, for tokens to be swapped, a new + // than 0, it means that approval has already occurred and is not needed. Otherwise, for tokens to be swapped, a new // call of the ERC-20 approve method is required. approvalRequired = allowance.eq(0) && diff --git a/app/scripts/controllers/threebox.js b/app/scripts/controllers/threebox.js index 10b231b38..fc278d0b2 100644 --- a/app/scripts/controllers/threebox.js +++ b/app/scripts/controllers/threebox.js @@ -8,7 +8,7 @@ const Box = process.env.IN_TEST import log from 'loglevel'; import { JsonRpcEngine } from 'json-rpc-engine'; -import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'; +import { providerFromEngine } from 'eth-json-rpc-middleware'; import Migrator from '../lib/migrator'; import migrations from '../migrations'; import createOriginMiddleware from '../lib/createOriginMiddleware'; diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 4c95556ee..0f1d15961 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -26,10 +26,13 @@ import { TRANSACTION_TYPES, TRANSACTION_ENVELOPE_TYPES, } from '../../../../shared/constants/transaction'; +import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../ui/helpers/constants/transactions'; import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; import { GAS_LIMITS, GAS_ESTIMATE_TYPES, + GAS_RECOMMENDATIONS, + CUSTOM_GAS_ESTIMATE, } from '../../../../shared/constants/gas'; import { decGWEIToHexWEI } from '../../../../shared/modules/conversion.utils'; import { @@ -435,7 +438,7 @@ export default class TransactionController extends EventEmitter { ) { txMeta.txParams.maxFeePerGas = txMeta.txParams.gasPrice; txMeta.txParams.maxPriorityFeePerGas = txMeta.txParams.gasPrice; - txMeta.userFeeLevel = 'custom'; + txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE; } else { if ( (defaultMaxFeePerGas && @@ -444,9 +447,9 @@ export default class TransactionController extends EventEmitter { !txMeta.txParams.maxPriorityFeePerGas) || txMeta.origin === 'metamask' ) { - txMeta.userFeeLevel = 'medium'; + txMeta.userFeeLevel = GAS_RECOMMENDATIONS.MEDIUM; } else { - txMeta.userFeeLevel = 'custom'; + txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE; } if (defaultMaxFeePerGas && !txMeta.txParams.maxFeePerGas) { @@ -1445,8 +1448,8 @@ export default class TransactionController extends EventEmitter { sensitiveProperties: { status, transaction_envelope_type: isEIP1559Transaction(txMeta) - ? 'fee-market' - : 'legacy', + ? TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET + : TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, first_seen: time, gas_limit: gasLimit, ...gasParamsInGwei, diff --git a/app/scripts/controllers/transactions/index.test.js b/app/scripts/controllers/transactions/index.test.js index 3416d47ae..c2f7a344b 100644 --- a/app/scripts/controllers/transactions/index.test.js +++ b/app/scripts/controllers/transactions/index.test.js @@ -12,9 +12,15 @@ import { import { TRANSACTION_STATUSES, TRANSACTION_TYPES, + TRANSACTION_ENVELOPE_TYPES, } from '../../../../shared/constants/transaction'; + import { SECOND } from '../../../../shared/constants/time'; -import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas'; +import { + GAS_ESTIMATE_TYPES, + GAS_RECOMMENDATIONS, +} from '../../../../shared/constants/gas'; +import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../ui/helpers/constants/transactions'; import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; import TransactionController, { TRANSACTION_EVENTS } from '.'; @@ -544,7 +550,7 @@ describe('Transaction Controller', function () { txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, - type: '0x0', + type: TRANSACTION_ENVELOPE_TYPES.LEGACY, }, history: [{}], }, @@ -554,7 +560,7 @@ describe('Transaction Controller', function () { txParams: { from: '0xc684832530fcbddae4b4230a47e991ddcec2831d', to: '0xc684832530fcbddae4b4230a47e991ddcec2831d', - type: '0x0', + type: TRANSACTION_ENVELOPE_TYPES.LEGACY, }, history: [{}], }; @@ -769,7 +775,7 @@ describe('Transaction Controller', function () { nonce: '0x4b', }, type: TRANSACTION_TYPES.SIMPLE_SEND, - transaction_envelope_type: 'legacy', + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, origin: 'metamask', chainId: currentChainId, time: 1624408066355, @@ -999,8 +1005,8 @@ describe('Transaction Controller', function () { to: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4', gas: '0x5209', gasPrice: '0xa', - estimateSuggested: 'medium', - estimateUsed: 'high', + estimateSuggested: GAS_RECOMMENDATIONS.MEDIUM, + estimateUsed: GAS_RECOMMENDATIONS.HIGH, }; txController.txStateManager._addTransactionsToState([ { @@ -1573,7 +1579,7 @@ describe('Transaction Controller', function () { gas_price: '2', gas_limit: '0x7b0d', first_seen: 1624408066355, - transaction_envelope_type: 'legacy', + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, status: 'unapproved', }, }; @@ -1620,7 +1626,7 @@ describe('Transaction Controller', function () { gas_price: '2', gas_limit: '0x7b0d', first_seen: 1624408066355, - transaction_envelope_type: 'legacy', + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, status: 'unapproved', }, }; @@ -1669,7 +1675,7 @@ describe('Transaction Controller', function () { gas_price: '2', gas_limit: '0x7b0d', first_seen: 1624408066355, - transaction_envelope_type: 'legacy', + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, status: 'unapproved', }, }; @@ -1700,8 +1706,8 @@ describe('Transaction Controller', function () { maxPriorityFeePerGas: '0x77359400', gas: '0x7b0d', nonce: '0x4b', - estimateSuggested: 'medium', - estimateUsed: 'high', + estimateSuggested: GAS_RECOMMENDATIONS.MEDIUM, + estimateUsed: GAS_RECOMMENDATIONS.HIGH, }, type: TRANSACTION_TYPES.SIMPLE_SEND, origin: 'other', @@ -1726,10 +1732,10 @@ describe('Transaction Controller', function () { max_priority_fee_per_gas: '2', gas_limit: '0x7b0d', first_seen: 1624408066355, - transaction_envelope_type: 'fee-market', + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET, status: 'unapproved', - estimate_suggested: 'medium', - estimate_used: 'high', + estimate_suggested: GAS_RECOMMENDATIONS.MEDIUM, + estimate_used: GAS_RECOMMENDATIONS.HIGH, }, }; @@ -1802,14 +1808,14 @@ describe('Transaction Controller', function () { const params = { max_fee_per_gas: '0x77359400', max_priority_fee_per_gas: '0x77359400', - estimate_suggested: 'medium', - estimate_used: 'high', + estimate_suggested: GAS_RECOMMENDATIONS.MEDIUM, + estimate_used: GAS_RECOMMENDATIONS.HIGH, }; const expectedParams = { max_fee_per_gas: '2', max_priority_fee_per_gas: '2', - estimate_suggested: 'medium', - estimate_used: 'high', + estimate_suggested: GAS_RECOMMENDATIONS.MEDIUM, + estimate_used: GAS_RECOMMENDATIONS.HIGH, }; const result = txController._getGasValuesInGWEI(params); assert.deepEqual(result, expectedParams); diff --git a/app/scripts/controllers/transactions/lib/util.test.js b/app/scripts/controllers/transactions/lib/util.test.js index b5b94d5fc..6c162ef4e 100644 --- a/app/scripts/controllers/transactions/lib/util.test.js +++ b/app/scripts/controllers/transactions/lib/util.test.js @@ -1,6 +1,7 @@ import { strict as assert } from 'assert'; import { TRANSACTION_ENVELOPE_TYPES } from '../../../../../shared/constants/transaction'; import { BURN_ADDRESS } from '../../../../../shared/modules/hexstring-utils'; +import { GAS_RECOMMENDATIONS } from '../../../../../shared/constants/gas'; import * as txUtils from './util'; describe('txUtils', function () { @@ -323,8 +324,8 @@ describe('txUtils', function () { gasPrice: '1', maxFeePerGas: '1', maxPriorityFeePerGas: '1', - estimateSuggested: 'medium', - estimateUsed: 'high', + estimateSuggested: GAS_RECOMMENDATIONS.MEDIUM, + estimateUsed: GAS_RECOMMENDATIONS.HIGH, type: '1', }; @@ -382,12 +383,12 @@ describe('txUtils', function () { assert.equal( normalizedTxParams.estimateSuggested, - 'medium', + GAS_RECOMMENDATIONS.MEDIUM, 'estimateSuggested should be the string originally provided', ); assert.equal( normalizedTxParams.estimateUsed, - 'high', + GAS_RECOMMENDATIONS.HIGH, 'estimateSuggested should be the string originally provided', ); }); diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js index 857921889..34969b58f 100644 --- a/app/scripts/first-time-state.js +++ b/app/scripts/first-time-state.js @@ -10,15 +10,7 @@ const initialState = { config: {}, PreferencesController: { - frequentRpcListDetail: [ - { - rpcUrl: 'http://localhost:8545', - chainId: '0x539', - ticker: 'ETH', - nickname: 'Localhost 8545', - rpcPrefs: {}, - }, - ], + frequentRpcListDetail: [], }, }; diff --git a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js index ea2370217..fa7798d0d 100644 --- a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js +++ b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js @@ -1,3 +1,5 @@ +import { ethErrors } from 'eth-rpc-errors'; +import { UNSUPPORTED_RPC_METHODS } from '../../../../shared/constants/network'; import handlers from './handlers'; const handlerMap = handlers.reduce((map, handler) => { @@ -26,6 +28,11 @@ const handlerMap = handlers.reduce((map, handler) => { */ export default function createMethodMiddleware(opts) { return function methodMiddleware(req, res, next, end) { + // Reject unsupported methods. + if (UNSUPPORTED_RPC_METHODS.has(req.method)) { + return end(ethErrors.rpc.methodNotSupported()); + } + if (handlerMap.has(req.method)) { return handlerMap.get(req.method)(req, res, next, end, opts); } diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index 9467218ca..c5c35a7d5 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -56,21 +56,20 @@ const getEnvironmentType = (url = window.location.href) => * @returns {string} the platform ENUM * */ -const getPlatform = (_) => { - const ua = window.navigator.userAgent; - if (ua.search('Firefox') === -1) { - if (window && window.chrome && window.chrome.ipcRenderer) { - return PLATFORM_BRAVE; - } - if (ua.search('Edge') !== -1) { - return PLATFORM_EDGE; - } - if (ua.search('OPR') !== -1) { - return PLATFORM_OPERA; - } - return PLATFORM_CHROME; +const getPlatform = () => { + const { navigator } = window; + const { userAgent } = navigator; + + if (userAgent.includes('Firefox')) { + return PLATFORM_FIREFOX; + } else if ('brave' in navigator) { + return PLATFORM_BRAVE; + } else if (userAgent.includes('Edg/')) { + return PLATFORM_EDGE; + } else if (userAgent.includes('OPR')) { + return PLATFORM_OPERA; } - return PLATFORM_FIREFOX; + return PLATFORM_CHROME; }; /** diff --git a/app/scripts/lib/util.test.js b/app/scripts/lib/util.test.js index f21c10d5e..d25949bc4 100644 --- a/app/scripts/lib/util.test.js +++ b/app/scripts/lib/util.test.js @@ -1,13 +1,17 @@ import { strict as assert } from 'assert'; +import sinon from 'sinon'; import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils'; - import { ENVIRONMENT_TYPE_POPUP, ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_FULLSCREEN, ENVIRONMENT_TYPE_BACKGROUND, + PLATFORM_FIREFOX, + PLATFORM_OPERA, + PLATFORM_CHROME, + PLATFORM_EDGE, } from '../../../shared/constants/app'; -import { getEnvironmentType } from './util'; +import { getEnvironmentType, getPlatform } from './util'; describe('app utils', function () { describe('getEnvironmentType', function () { @@ -151,4 +155,59 @@ describe('app utils', function () { ); }); }); + + describe('getPlatform', function () { + const setBrowserSpecificWindow = (browser) => { + switch (browser) { + case 'firefox': { + sinon.stub(window, 'navigator').value({ + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0', + }); + break; + } + case 'edge': { + sinon.stub(window, 'navigator').value({ + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30', + }); + break; + } + case 'opera': { + sinon.stub(window, 'navigator').value({ + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 OPR/80.0.4170.63', + }); + break; + } + default: { + sinon.stub(window, 'navigator').value({ + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36', + }); + break; + } + } + }; + + it('should detect Firefox', function () { + setBrowserSpecificWindow('firefox'); + assert.equal(getPlatform(), PLATFORM_FIREFOX); + }); + + it('should detect Edge', function () { + setBrowserSpecificWindow('edge'); + assert.equal(getPlatform(), PLATFORM_EDGE); + }); + + it('should detect Opera', function () { + setBrowserSpecificWindow('opera'); + assert.equal(getPlatform(), PLATFORM_OPERA); + }); + + it('should detect Chrome', function () { + setBrowserSpecificWindow('chrome'); + assert.equal(getPlatform(), PLATFORM_CHROME); + }); + }); }); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 106317bdd..531cdfb7a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -7,15 +7,17 @@ import { debounce } from 'lodash'; import createEngineStream from 'json-rpc-middleware-stream/engineStream'; import createFilterMiddleware from 'eth-json-rpc-filters'; import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager'; -import providerAsMiddleware from 'eth-json-rpc-middleware/providerAsMiddleware'; +import { providerAsMiddleware } from 'eth-json-rpc-middleware'; import KeyringController from 'eth-keyring-controller'; import { Mutex } from 'await-semaphore'; import { stripHexPrefix } from 'ethereumjs-util'; import log from 'loglevel'; import TrezorKeyring from 'eth-trezor-keyring'; import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring'; +import LatticeKeyring from 'eth-lattice-keyring'; import EthQuery from 'eth-query'; import nanoid from 'nanoid'; +import { captureException } from '@sentry/browser'; import { AddressBookController, ApprovalController, @@ -189,6 +191,7 @@ export default class MetamaskController extends EventEmitter { version: this.platform.getVersion(), environment: process.env.METAMASK_ENVIRONMENT, initState: initState.MetaMetricsController, + captureException, }); const gasFeeMessenger = this.controllerMessenger.getRestricted({ @@ -370,7 +373,11 @@ export default class MetamaskController extends EventEmitter { await opts.openPopup(); }); - const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]; + const additionalKeyrings = [ + TrezorKeyring, + LedgerBridgeKeyring, + LatticeKeyring, + ]; this.keyringController = new KeyringController({ keyringTypes: additionalKeyrings, initState: initState.KeyringController, @@ -917,6 +924,10 @@ export default class MetamaskController extends EventEmitter { this.preferencesController.setDismissSeedBackUpReminder, this.preferencesController, ), + setAdvancedGasFee: nodeify( + preferencesController.setAdvancedGasFee, + preferencesController, + ), // AddressController setAddressBook: nodeify( @@ -1445,6 +1456,7 @@ export default class MetamaskController extends EventEmitter { .map((address) => toChecksumHexAddress(address)), ledger: [], trezor: [], + lattice: [], }; // transactions @@ -1545,6 +1557,9 @@ export default class MetamaskController extends EventEmitter { case 'ledger': keyringName = LedgerBridgeKeyring.type; break; + case 'lattice': + keyringName = LatticeKeyring.type; + break; default: throw new Error( 'MetamaskController:getKeyringForDevice - Unknown device', @@ -1559,7 +1574,9 @@ export default class MetamaskController extends EventEmitter { if (hdPath && keyring.setHdPath) { keyring.setHdPath(hdPath); } - + if (deviceName === 'lattice') { + keyring.appName = 'MetaMask'; + } keyring.network = this.networkController.getProviderConfig().type; return keyring; @@ -2047,6 +2064,14 @@ export default class MetamaskController extends EventEmitter { }); } + case KEYRING_TYPES.LATTICE: { + return new Promise((_, reject) => { + reject( + new Error('Lattice does not support eth_getEncryptionPublicKey.'), + ); + }); + } + default: { const promise = this.encryptionPublicKeyManager.addUnapprovedMessageAsync( msgParams, diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index eeaea008d..4f7705a95 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -262,9 +262,9 @@ export default class ExtensionPlatform { } } - _viewOnEtherscan(txId) { - if (txId.startsWith('https://')) { - extension.tabs.create({ url: txId }); + _viewOnEtherscan(url) { + if (url.startsWith('https://')) { + extension.tabs.create({ url }); } } } diff --git a/development/build/index.js b/development/build/index.js index fd0ffffb3..7ee92f672 100755 --- a/development/build/index.js +++ b/development/build/index.js @@ -3,8 +3,10 @@ // // run any task with "yarn build ${taskName}" // +const path = require('path'); const livereload = require('gulp-livereload'); const minimist = require('minimist'); +const { sync: globby } = require('globby'); const { createTask, composeSeries, @@ -18,7 +20,8 @@ const createStaticAssetTasks = require('./static'); const createEtcTasks = require('./etc'); const { BuildType, getBrowserVersionMap } = require('./utils'); -// packages required dynamically via browserify configuration in dependencies +// Packages required dynamically via browserify configuration in dependencies +// Required for LavaMoat policy generation require('loose-envify'); require('@babel/plugin-proposal-object-rest-spread'); require('@babel/plugin-transform-runtime'); @@ -28,6 +31,19 @@ require('@babel/plugin-proposal-nullish-coalescing-operator'); require('@babel/preset-env'); require('@babel/preset-react'); require('@babel/core'); +// ESLint-related +require('@babel/eslint-parser'); +require('@babel/eslint-plugin'); +require('@metamask/eslint-config'); +require('@metamask/eslint-config-nodejs'); +require('eslint'); +require('eslint-config-prettier'); +require('eslint-import-resolver-node'); +require('eslint-plugin-import'); +require('eslint-plugin-node'); +require('eslint-plugin-prettier'); +require('eslint-plugin-react'); +require('eslint-plugin-react-hooks'); defineAndRunBuildTasks(); @@ -45,6 +61,8 @@ function defineAndRunBuildTasks() { const browserVersionMap = getBrowserVersionMap(browserPlatforms); + const ignoredFiles = getIgnoredFiles(buildType); + const staticTasks = createStaticAssetTasks({ livereload, browserPlatforms, @@ -63,6 +81,7 @@ function defineAndRunBuildTasks() { const scriptTasks = createScriptTasks({ browserPlatforms, buildType, + ignoredFiles, isLavaMoat, livereload, shouldLintFenceFiles, @@ -188,3 +207,35 @@ function parseArgv() { skipStats: argv[NamedArgs.SkipStats], }; } + +/** + * Gets the files to be ignored by the current build, if any. + * + * @param {string} buildType - The type of the current build. + * @returns {string[] | null} The array of files to be ignored by the current + * build, or `null` if no files are to be ignored. + */ +function getIgnoredFiles(currentBuildType) { + const excludedFiles = Object.values(BuildType) + // This filter removes "main" and the current build type. The files of any + // build types that remain in the array will be excluded. "main" is the + // default build type, and has no files that are excluded from other builds. + .filter( + (buildType) => + buildType !== BuildType.main && buildType !== currentBuildType, + ) + // Compute globs targeting files for exclusion for each excluded build + // type. + .reduce((excludedGlobs, excludedBuildType) => { + return excludedGlobs.concat([ + `../../app/**/${excludedBuildType}/**`, + `../../shared/**/${excludedBuildType}/**`, + `../../ui/**/${excludedBuildType}/**`, + ]); + }, []) + // This creates absolute paths of the form: + // PATH_TO_REPOSITORY_ROOT/app/**/${excludedBuildType}/** + .map((pathGlob) => path.resolve(__dirname, pathGlob)); + + return globby(excludedFiles); +} diff --git a/development/build/manifest.js b/development/build/manifest.js index ebca66f16..a87fd53de 100644 --- a/development/build/manifest.js +++ b/development/build/manifest.js @@ -3,7 +3,6 @@ const path = require('path'); const { merge, cloneDeep } = require('lodash'); const baseManifest = require('../../app/manifest/_base.json'); -const betaManifestModifications = require('../../app/manifest/_beta_modifications.json'); const { createTask, composeSeries } = require('./task'); const { BuildType } = require('./utils'); @@ -33,7 +32,7 @@ function createManifestTasks({ cloneDeep(baseManifest), platformModifications, browserVersionMap[platform], - getBuildModifications(buildType), + await getBuildModifications(buildType, platform), ); const dir = path.join('.', 'dist', platform); await fs.mkdir(dir, { recursive: true }); @@ -112,10 +111,53 @@ async function writeJson(obj, file) { return fs.writeFile(file, JSON.stringify(obj, null, 2)); } -function getBuildModifications(buildType) { - const buildModifications = {}; - if (buildType === BuildType.beta) { - Object.assign(buildModifications, betaManifestModifications); +/** + * Get manifest modifications for the given build type, including modifications specific to the + * given platform. + * + * @param {BuildType} buildType - The build type. + * @param {string} platform - The platform (i.e. the browser). + * @returns {Object} The build modificantions for the given build type and platform. + */ +async function getBuildModifications(buildType, platform) { + if (!Object.values(BuildType).includes(buildType)) { + throw new Error(`Invalid build type: ${buildType}`); + } else if (buildType === BuildType.main) { + return {}; } + + const builtTypeManifestDirectoryPath = path.resolve( + __dirname, + '..', + '..', + 'app', + 'build-types', + buildType, + 'manifest', + ); + + const baseBuildTypeModificationsPath = path.join( + builtTypeManifestDirectoryPath, + '_base.json', + ); + const buildModifications = await readJson(baseBuildTypeModificationsPath); + + const platformBuildTypeModificationsPath = path.join( + builtTypeManifestDirectoryPath, + `${platform}.json`, + ); + try { + const platformBuildTypeModifications = await readJson( + platformBuildTypeModificationsPath, + ); + Object.assign(buildModifications, platformBuildTypeModifications); + } catch (error) { + // Suppress 'ENOENT' error because it indicates there are no platform-specific manifest + // modifications for this build type. + if (error.code !== 'ENOENT') { + throw error; + } + } + return buildModifications; } diff --git a/development/build/scripts.js b/development/build/scripts.js index 295590f1a..8aefbad6b 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -29,10 +29,17 @@ const bifyModuleGroups = require('bify-module-groups'); const metamaskrc = require('rc')('metamask', { INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID, + INFURA_BETA_PROJECT_ID: process.env.INFURA_BETA_PROJECT_ID, + INFURA_FLASK_PROJECT_ID: process.env.INFURA_FLASK_PROJECT_ID, INFURA_PROD_PROJECT_ID: process.env.INFURA_PROD_PROJECT_ID, ONBOARDING_V2: process.env.ONBOARDING_V2, + COLLECTIBLES_V1: process.env.COLLECTIBLES_V1, + EIP_1559_V2: process.env.EIP_1559_V2, SEGMENT_HOST: process.env.SEGMENT_HOST, SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY, + SEGMENT_BETA_WRITE_KEY: process.env.SEGMENT_BETA_WRITE_KEY, + SEGMENT_FLASK_WRITE_KEY: process.env.SEGMENT_FLASK_WRITE_KEY, + SEGMENT_PROD_WRITE_KEY: process.env.SEGMENT_PROD_WRITE_KEY, SENTRY_DSN_DEV: process.env.SENTRY_DSN_DEV || 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496', @@ -50,6 +57,7 @@ const { const { createRemoveFencedCodeTransform, } = require('./transforms/remove-fenced-code'); +const { BuildType } = require('./utils'); /** * The build environment. This describes the environment this build was produced in. @@ -83,17 +91,47 @@ function getConfigValue(key) { * Get the appropriate Infura project ID. * * @param {object} options - The Infura project ID options. + * @param {BuildType} options.buildType - The current build type. * @param {ENVIRONMENT[keyof ENVIRONMENT]} options.environment - The build environment. * @param {boolean} options.testing - Whether the current build is a test build or not. * @returns {string} The Infura project ID. */ -function getInfuraProjectId({ environment, testing }) { +function getInfuraProjectId({ buildType, environment, testing }) { if (testing) { return '00000000000000000000000000000000'; - } else if (environment === ENVIRONMENT.PRODUCTION) { + } else if (environment !== ENVIRONMENT.PRODUCTION) { + // Skip validation because this is unset on PRs from forks. + return metamaskrc.INFURA_PROJECT_ID; + } else if (buildType === BuildType.main) { return getConfigValue('INFURA_PROD_PROJECT_ID'); + } else if (buildType === BuildType.beta) { + return getConfigValue('INFURA_BETA_PROJECT_ID'); + } else if (buildType === BuildType.flask) { + return getConfigValue('INFURA_FLASK_PROJECT_ID'); } - return getConfigValue('INFURA_PROJECT_ID'); + throw new Error(`Invalid build type: '${buildType}'`); +} + +/** + * Get the appropriate Segment write key. + * + * @param {object} options - The Segment write key options. + * @param {BuildType} options.buildType - The current build type. + * @param {keyof ENVIRONMENT} options.enviroment - The current build environment. + * @returns {string} The Segment write key. + */ +function getSegmentWriteKey({ buildType, environment }) { + if (environment !== ENVIRONMENT.PRODUCTION) { + // Skip validation because this is unset on PRs from forks, and isn't necessary for development builds. + return metamaskrc.SEGMENT_WRITE_KEY; + } else if (buildType === BuildType.main) { + return getConfigValue('SEGMENT_PROD_WRITE_KEY'); + } else if (buildType === BuildType.beta) { + return getConfigValue('SEGMENT_BETA_WRITE_KEY'); + } else if (buildType === BuildType.flask) { + return getConfigValue('SEGMENT_FLASK_WRITE_KEY'); + } + throw new Error(`Invalid build type: '${buildType}'`); } module.exports = createScriptTasks; @@ -101,6 +139,7 @@ module.exports = createScriptTasks; function createScriptTasks({ browserPlatforms, buildType, + ignoredFiles, isLavaMoat, livereload, shouldLintFenceFiles, @@ -145,8 +184,9 @@ function createScriptTasks({ } return `./app/scripts/${label}.js`; }), - testing, + ignoredFiles, shouldLintFenceFiles, + testing, }), ); @@ -216,6 +256,7 @@ function createScriptTasks({ destFilepath: `${label}.js`, devMode, entryFilepath: `./app/scripts/${label}.js`, + ignoredFiles, label, shouldLintFenceFiles, }); @@ -229,6 +270,7 @@ function createScriptTasks({ destFilepath: `${label}.js`, devMode, entryFilepath: `./app/scripts/${label}.js`, + ignoredFiles, label, shouldLintFenceFiles, }); @@ -242,6 +284,7 @@ function createScriptTasks({ destFilepath: `${label}.js`, devMode, entryFilepath: `./app/scripts/${label}.js`, + ignoredFiles, label, shouldLintFenceFiles, }); @@ -259,8 +302,9 @@ function createScriptTasks({ devMode, entryFilepath: `./app/scripts/${inpage}.js`, label: inpage, - testing, + ignoredFiles, shouldLintFenceFiles, + testing, }), createNormalBundle({ buildType, @@ -269,8 +313,9 @@ function createScriptTasks({ devMode, entryFilepath: `./app/scripts/${contentscript}.js`, label: contentscript, - testing, + ignoredFiles, shouldLintFenceFiles, + testing, }), ); } @@ -281,8 +326,9 @@ function createFactoredBuild({ buildType, devMode, entryFiles, - testing, + ignoredFiles, shouldLintFenceFiles, + testing, }) { return async function () { // create bundler setup and apply defaults @@ -299,6 +345,7 @@ function createFactoredBuild({ buildType, devMode, envVars, + ignoredFiles, minify, reloadOnChange, shouldLintFenceFiles, @@ -409,7 +456,7 @@ function createFactoredBuild({ groupSet, commonSet, browserPlatforms, - useLavamoat: false, + useLavamoat: true, }); break; } @@ -443,6 +490,7 @@ function createNormalBundle({ devMode, entryFilepath, extraEntries = [], + ignoredFiles, label, modulesToExpose, shouldLintFenceFiles, @@ -463,6 +511,7 @@ function createNormalBundle({ buildType, devMode, envVars, + ignoredFiles, minify, reloadOnChange, shouldLintFenceFiles, @@ -507,12 +556,20 @@ function createBuildConfiguration() { manualExternal: [], manualIgnore: [], }; - return { label, bundlerOpts, events }; + return { bundlerOpts, events, label }; } function setupBundlerDefaults( buildConfiguration, - { buildType, devMode, envVars, minify, reloadOnChange, shouldLintFenceFiles }, + { + buildType, + devMode, + envVars, + ignoredFiles, + minify, + reloadOnChange, + shouldLintFenceFiles, + }, ) { const { bundlerOpts } = buildConfiguration; @@ -542,6 +599,11 @@ function setupBundlerDefaults( bundlerOpts.transform.push([envify(envVars), { global: true }]); } + // Ensure that any files that should be ignored are excluded from the build + if (ignoredFiles) { + bundlerOpts.manualExclude = ignoredFiles; + } + // Setup reload on change if (reloadOnChange) { setupReloadOnChange(buildConfiguration); @@ -624,11 +686,17 @@ function setupSourcemaps(buildConfiguration, { devMode }) { async function bundleIt(buildConfiguration) { const { label, bundlerOpts, events } = buildConfiguration; const bundler = browserify(bundlerOpts); + // manually apply non-standard options bundler.external(bundlerOpts.manualExternal); bundler.ignore(bundlerOpts.manualIgnore); + if (Array.isArray(bundlerOpts.manualExclude)) { + bundler.exclude(bundlerOpts.manualExclude); + } + // output build logs to terminal bundler.on('log', log); + // forward update event (used by watchify) bundler.on('update', () => performBundle()); @@ -685,19 +753,13 @@ function getEnvironmentVariables({ buildType, devMode, testing }) { CONF: devMode ? metamaskrc : {}, SENTRY_DSN: process.env.SENTRY_DSN, SENTRY_DSN_DEV: metamaskrc.SENTRY_DSN_DEV, - INFURA_PROJECT_ID: getInfuraProjectId({ environment, testing }), + INFURA_PROJECT_ID: getInfuraProjectId({ buildType, environment, testing }), SEGMENT_HOST: metamaskrc.SEGMENT_HOST, - // When we're in the 'production' environment we will use a specific key only set in CI - // Otherwise we'll use the key from .metamaskrc or from the environment variable. If - // the value of SEGMENT_WRITE_KEY that we envify is undefined then no events will be tracked - // in the build. This is intentional so that developers can contribute to MetaMask without - // inflating event volume. - SEGMENT_WRITE_KEY: - environment === ENVIRONMENT.PRODUCTION - ? process.env.SEGMENT_PROD_WRITE_KEY - : metamaskrc.SEGMENT_WRITE_KEY, + SEGMENT_WRITE_KEY: getSegmentWriteKey({ buildType, environment }), SWAPS_USE_DEV_APIS: process.env.SWAPS_USE_DEV_APIS === '1', ONBOARDING_V2: metamaskrc.ONBOARDING_V2 === '1', + COLLECTIBLES_V1: metamaskrc.COLLECTIBLES_V1 === '1', + EIP_1559_V2: metamaskrc.EIP_1559_V2 === '1', }; } diff --git a/development/build/static.js b/development/build/static.js index 8c1961973..1ec935502 100644 --- a/development/build/static.js +++ b/development/build/static.js @@ -23,7 +23,13 @@ module.exports = function createStaticAssetTasks({ const additionalBuildTargets = { [BuildType.beta]: [ { - src: './app/build-types/beta/', + src: './app/build-types/beta/images/', + dest: `images`, + }, + ], + [BuildType.flask]: [ + { + src: './app/build-types/flask/images/', dest: `images`, }, ], diff --git a/development/build/transforms/remove-fenced-code.js b/development/build/transforms/remove-fenced-code.js index 7f26ad6cf..200233fe8 100644 --- a/development/build/transforms/remove-fenced-code.js +++ b/development/build/transforms/remove-fenced-code.js @@ -408,7 +408,9 @@ function multiSplice(toSplice, splicingIndices) { // pushes the substring between each "end" index and the next "begin" index // to the array of retained substrings. if (splicingIndices.length > 2) { - for (let i = 1; i < splicingIndices.length; i += 2) { + // Note the boundary index of "splicingIndices.length - 1". This loop must + // not iterate over the last element of the array. + for (let i = 1; i < splicingIndices.length - 1; i += 2) { retainedSubstrings.push( toSplice.substring(splicingIndices[i], splicingIndices[i + 1]), ); diff --git a/development/build/transforms/remove-fenced-code.test.js b/development/build/transforms/remove-fenced-code.test.js index 33461b89d..5eebc1135 100644 --- a/development/build/transforms/remove-fenced-code.test.js +++ b/development/build/transforms/remove-fenced-code.test.js @@ -201,6 +201,14 @@ describe('build/transforms/remove-fenced-code', () => { ), ).toStrictEqual(testData.validOutputs[buildType]); + expect( + removeFencedCode( + mockFileName, + buildType, + testData.validInputs.extraContentWithFences, + ), + ).toStrictEqual(testData.validOutputsWithExtraContent[buildType]); + // Ensure that the minimal input template is in fact valid const minimalInput = getMinimalFencedCode(buildType); expect( @@ -216,6 +224,17 @@ describe('build/transforms/remove-fenced-code', () => { testData.validInputs.withoutFences, ), ).toStrictEqual([testData.validInputs.withoutFences, false]); + + expect( + removeFencedCode( + mockFileName, + buildType, + testData.validInputs.extraContentWithoutFences, + ), + ).toStrictEqual([ + testData.validInputs.extraContentWithoutFences, + false, + ]); }); }); @@ -611,6 +630,43 @@ Conditionally_Included ///: END:ONLY_INCLUDE_IN `, + extraContentWithFences: ` +///: BEGIN:ONLY_INCLUDE_IN(flask,beta) +Conditionally_Included +///: END:ONLY_INCLUDE_IN + Always_Included +Always_Included + Always_Included +Always_Included + ///: BEGIN:ONLY_INCLUDE_IN(flask,beta) + Conditionally_Included + + Conditionally_Included + Conditionally_Included + ///: END:ONLY_INCLUDE_IN +Always_Included + +Always_Included + Always_Included + ///: BEGIN:ONLY_INCLUDE_IN(flask) + + Conditionally_Included + Conditionally_Included + ///: END:ONLY_INCLUDE_IN +Always_Included + Always_Included +Always_Included + +///: BEGIN:ONLY_INCLUDE_IN(flask) + Conditionally_Included +Conditionally_Included + + ///: END:ONLY_INCLUDE_IN + Always_Included + Always_Included +Always_Included +`, + withoutFences: ` Always_Included Always_Included @@ -624,6 +680,24 @@ Always_Included Always_Included Always_Included +`, + + extraContentWithoutFences: ` + Always_Included +Always_Included + Always_Included +Always_Included +Always_Included + +Always_Included + Always_Included +Always_Included + Always_Included +Always_Included + + Always_Included + Always_Included +Always_Included `, }, @@ -651,6 +725,38 @@ Always_Included Always_Included Always_Included +`, + true, + ], + }, + + validOutputsWithExtraContent: { + beta: [ + ` +///: BEGIN:ONLY_INCLUDE_IN(flask,beta) +Conditionally_Included +///: END:ONLY_INCLUDE_IN + Always_Included +Always_Included + Always_Included +Always_Included + ///: BEGIN:ONLY_INCLUDE_IN(flask,beta) + Conditionally_Included + + Conditionally_Included + Conditionally_Included + ///: END:ONLY_INCLUDE_IN +Always_Included + +Always_Included + Always_Included +Always_Included + Always_Included +Always_Included + + Always_Included + Always_Included +Always_Included `, true, ], @@ -659,5 +765,14 @@ Always_Included data.validOutputs.flask = [data.validInputs.withFences, false]; data.validOutputs.main = [data.validInputs.withoutFences, true]; + + data.validOutputsWithExtraContent.flask = [ + data.validInputs.extraContentWithFences, + false, + ]; + data.validOutputsWithExtraContent.main = [ + data.validInputs.extraContentWithoutFences, + true, + ]; return deepFreeze(data); } diff --git a/development/build/transforms/utils.js b/development/build/transforms/utils.js index c438d5351..171c38eae 100644 --- a/development/build/transforms/utils.js +++ b/development/build/transforms/utils.js @@ -1,6 +1,22 @@ const { ESLint } = require('eslint'); const eslintrc = require('../../../.eslintrc.js'); +// We don't want linting to fail for purely stylistic reasons. +eslintrc.rules['prettier/prettier'] = 'off'; + +// Remove all test-related overrides. We will never lint test files here. +eslintrc.overrides = eslintrc.overrides.filter((override) => { + return !( + (override.extends && + override.extends.find( + (configName) => + configName.includes('jest') || configName.includes('mocha'), + )) || + (override.plugins && + override.plugins.find((pluginName) => pluginName.includes('jest'))) + ); +}); + /** * The singleton ESLint instance. * diff --git a/development/states/navigate-txs.json b/development/states/navigate-txs.json new file mode 100644 index 000000000..18281fd35 --- /dev/null +++ b/development/states/navigate-txs.json @@ -0,0 +1,320 @@ +{ + "appState": { + "shouldClose": false, + "menuOpen": false, + "modal": { + "open": false, + "modalState": { + "name": null, + "props": {} + }, + "previousModalState": { + "name": null + } + }, + "sidebar": { + "isOpen": false, + "transitionName": "", + "type": "" + }, + "alertOpen": false, + "alertMessage": null, + "qrCodeData": null, + "networkDropdownOpen": false, + "currentView": { + "name": "confTx", + "context": 0 + }, + "accountDetail": { + "subview": "transactions" + }, + "transForward": false, + "isLoading": false, + "warning": null, + "buyView": {}, + "isMouseUser": true, + "gasIsLoading": false, + "networkNonce": "0x92", + "defaultHdPaths": { + "trezor": "m/44'/60'/0'/0", + "ledger": "m/44'/60'/0'/0/0", + "lattice": "m/44'/60'/0'/0" + } + }, + "confirmTransaction": { + "txData": { + "estimatedGas": "0x38f53", + "gasLimitSpecified": true, + "gasPriceSpecified": false, + "history": [], + "id": 2389644572638774, + "loadingDefaults": false, + "metamaskNetworkId": "4", + "origin": "remix.ethereum.org", + "status": "unapproved", + "time": 1538844223352, + "txParams": { + "data": "0x608060405234801561001057600080fd5b506102a7806100206000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663d13319c48114610050578063dfb29935146100da575b600080fd5b34801561005c57600080fd5b50610065610135565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009f578181015183820152602001610087565b50505050905090810190601f1680156100cc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100e657600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526101339436949293602493928401919081908401838280828437509497506101cc9650505050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101c15780601f10610196576101008083540402835291602001916101c1565b820191906000526020600020905b8154815290600101906020018083116101a457829003601f168201915b505050505090505b90565b80516101df9060009060208401906101e3565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b82800160010185558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101c991905b8082111561025d57600081556001016102675600a165627a7a72305820cf4282c534b8f2faad947d592afa109b907e4e6b2f52335b361b69c24fedb9580029", + "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "gas": "0x38f53", + "gasPrice": "0x3b9aca00", + "value": "0x0" + }, + "type": "standard" + }, + "tokenData": {}, + "methodData": {}, + "tokenProps": { + "tokenDecimals": "", + "tokenSymbol": "" + }, + "fiatTransactionAmount": "0", + "fiatTransactionFee": "0.05", + "fiatTransactionTotal": "0.05", + "ethTransactionAmount": "0", + "ethTransactionFee": "0.000233", + "ethTransactionTotal": "0.000233", + "hexGasTotal": "0xd42f28057e00", + "nonce": "", + "toSmartContract": false, + "fetchingData": false + }, + "localeMessages": {}, + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "isAccountMenuOpen": false, + "isPopup": false, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0x8cf82b5aa41ff2282427be151dd328568684007a": { + "address": "0x8cf82b5aa41ff2282427be151dd328568684007a", + "name": "Account 3" + }, + "0xbe1a00e10ec68b154adb84e8119167146a71c9a2": { + "address": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2", + "name": "Account 2" + }, + "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": { + "address": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "name": "Account 1" + } + }, + "unapprovedTxs": { + "2389644572638771": { + "estimatedGas": "0x8544", + "gasLimitSpecified": true, + "gasPriceSpecified": true, + "history": [], + "id": 2389644572638771, + "loadingDefaults": false, + "metamaskNetworkId": "4", + "origin": "MetaMask", + "status": "unapproved", + "time": 1538844175144, + "txParams": { + "data": "0xa9059cbb000000000000000000000000be1a00e10ec68b154adb84e8119167146a71c9a20000000000000000000000000000000000000000000000000000000000000000", + "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "gas": "0x8544", + "gasPrice": "0x3b9aca00", + "to": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a", + "value": "0x0" + }, + "type": "standard" + }, + "2389644572638772": { + "estimatedGas": "0x5208", + "gasLimitSpecified": true, + "gasPriceSpecified": true, + "history": [], + "id": 2389644572638772, + "loadingDefaults": false, + "metamaskNetworkId": "4", + "origin": "MetaMask", + "status": "unapproved", + "time": 1538844178492, + "txParams": { + "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "gas": "0x5208", + "gasPrice": "0x3b9aca00", + "to": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2", + "value": "0x0" + }, + "type": "standard" + }, + "2389644572638773": { + "estimatedGas": { + "length": 1, + "negative": 0, + "red": null, + "words": [34061, null] + }, + "gasLimitSpecified": false, + "gasPriceSpecified": true, + "history": [], + "id": 2389644572638773, + "loadingDefaults": false, + "metamaskNetworkId": "4", + "origin": "localhost", + "status": "unapproved", + "time": 1538844204724, + "txParams": { + "data": "0xdfb29935000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000155468697320697320746865206970667320686173680000000000000000000000", + "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "gas": "0xc793", + "gasPrice": "0x3b9aca00", + "to": "0xb7ec370c889b3b48ec537e0b2c887faedceb254a", + "value": "0x0" + }, + "type": "standard" + }, + "2389644572638774": { + "estimatedGas": "0x38f53", + "gasLimitSpecified": true, + "gasPriceSpecified": false, + "history": [], + "id": 2389644572638774, + "loadingDefaults": false, + "metamaskNetworkId": "4", + "origin": "remix.ethereum.org", + "status": "unapproved", + "time": 1538844223352, + "txParams": { + "data": "0x608060405234801561001057600080fd5b506102a7806100206000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663d13319c48114610050578063dfb29935146100da575b600080fd5b34801561005c57600080fd5b50610065610135565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009f578181015183820152602001610087565b50505050905090810190601f1680156100cc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100e657600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526101339436949293602493928401919081908401838280828437509497506101cc9650505050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101c15780601f10610196576101008083540402835291602001916101c1565b820191906000526020600020905b8154815290600101906020018083116101a457829003601f168201915b505050505090505b90565b80516101df9060009060208401906101e3565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b82800160010185558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101c991905b8082111561025d57600081556001016102675600a165627a7a72305820cf4282c534b8f2faad947d592afa109b907e4e6b2f52335b361b69c24fedb9580029", + "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "gas": "0x38f53", + "gasPrice": "0x3b9aca00", + "value": "0x0" + }, + "type": "standard" + } + }, + "noActiveNotices": true, + "frequentRpcList": [], + "addressBook": [], + "selectedTokenAddress": null, + "contractExchangeRates": {}, + "tokenExchangeRates": {}, + "tokens": [ + { + "address": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a", + "decimals": 9, + "symbol": "DGD" + } + ], + "pendingTokens": {}, + "send": { + "gasLimit": null, + "gasPrice": null, + "gasTotal": null, + "tokenBalance": null, + "from": "", + "to": "", + "amount": "0x0", + "memo": "", + "errors": {}, + "editingTransactionId": null, + "forceGasMin": null + }, + "coinOptions": {}, + "useBlockie": false, + "featureFlags": { + "betaUI": true, + "skipAnnounceBetaUI": true + }, + "isRevealingSeedWords": false, + "welcomeScreenSeen": false, + "currentLocale": "en", + "preferences": { + "useETHAsPrimaryCurrency": true + }, + "provider": { + "type": "rinkeby" + }, + "network": "4", + "accounts": { + "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": { + "address": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "balance": "0x36aabfb2a0190c00" + }, + "0xbe1a00e10ec68b154adb84e8119167146a71c9a2": { + "address": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2", + "balance": "0x7b3ef08c294a000" + }, + "0x8cf82b5aa41ff2282427be151dd328568684007a": { + "address": "0x8cf82b5aa41ff2282427be151dd328568684007a", + "balance": "0x0" + } + }, + "currentBlockGasLimit": "0x731e25", + "selectedAddressTxList": [], + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": {}, + "unapprovedPersonalMsgCount": 0, + "unapprovedTypedMessages": {}, + "unapprovedTypedMessagesCount": 0, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree", + "Trezor Hardware", + "Ledger Hardware", + "Lattice Hardware" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "0xbe1a00e10ec68b154adb84e8119167146a71c9a2", + "0x8cf82b5aa41ff2282427be151dd328568684007a" + ] + } + ], + "currentAccountTab": "history", + "accountTokens": { + "0x8cf82b5aa41ff2282427be151dd328568684007a": {}, + "0xbe1a00e10ec68b154adb84e8119167146a71c9a2": {}, + "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": { + "rinkeby": [ + { + "address": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a", + "decimals": 9, + "symbol": "DGD" + }, + { + "address": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", + "decimals": 18, + "symbol": "DAI" + } + ] + } + }, + "assetImages": { + "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359": null, + "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a": null + }, + "suggestedTokens": {}, + "lostIdentities": {}, + "seedWords": null, + "forgottenPassword": false, + "selectedAddress": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "recentBlocks": [], + "currentCurrency": "usd", + "conversionRate": 225.23, + "conversionDate": 1538859376, + "shapeShiftTxList": [], + "infuraNetworkStatus": { + "kovan": "ok", + "mainnet": "ok", + "rinkeby": "ok", + "ropsten": "ok", + "goerli": "ok" + } + }, + "send": { + "toDropdownOpen": false, + "errors": {}, + "warnings": {} + } +} diff --git a/lavamoat/browserify/policy.json b/lavamoat/browserify/policy.json index 1cd3d2110..3cfd712fa 100644 --- a/lavamoat/browserify/policy.json +++ b/lavamoat/browserify/policy.json @@ -325,6 +325,7 @@ "@ethersproject/bignumber": true, "@ethersproject/bytes": true, "@ethersproject/keccak256": true, + "@ethersproject/logger": true, "@ethersproject/sha2": true, "@ethersproject/strings": true } @@ -655,9 +656,6 @@ "@sentry/browser": { "globals": { "XMLHttpRequest": true, - "document.body": true, - "document.createElement": true, - "document.head": true, "setTimeout": true }, "packages": { @@ -682,9 +680,11 @@ }, "@sentry/hub": { "globals": { - "setTimeout": true + "clearInterval": true, + "setInterval": true }, "packages": { + "@sentry/types": true, "@sentry/utils": true, "tslib": true } @@ -945,6 +945,11 @@ "safe-buffer": true } }, + "bitwise": { + "packages": { + "buffer": true + } + }, "bl": { "packages": { "buffer": true, @@ -1551,11 +1556,15 @@ }, "eth-json-rpc-middleware": { "globals": { + "URL": true, + "btoa": true, "console.error": true, "fetch": true, "setTimeout": true }, "packages": { + "@metamask/safe-event-emitter": true, + "browser-resolve": true, "btoa": true, "clone": true, "eth-rpc-errors": true, @@ -1581,6 +1590,26 @@ "obs-store": true } }, + "eth-lattice-keyring": { + "globals": { + "addEventListener": true, + "browser": true, + "clearInterval": true, + "console.warn": true, + "open": true, + "setInterval": true + }, + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx": true, + "bignumber.js": true, + "buffer": true, + "crypto-browserify": true, + "ethereumjs-util": true, + "events": true, + "gridplus-sdk": true + } + }, "eth-method-registry": { "packages": { "ethjs": true @@ -1736,6 +1765,11 @@ "@ethersproject/wordlists": true } }, + "ethers-eip712": { + "packages": { + "ethers": true + } + }, "ethjs": { "globals": { "clearInterval": true, @@ -1912,6 +1946,29 @@ "cross-fetch": true } }, + "gridplus-sdk": { + "globals": { + "console.error": true, + "setTimeout": true + }, + "packages": { + "aes-js": true, + "bech32": true, + "bignumber.js": true, + "bitwise": true, + "borc": true, + "bs58check": true, + "buffer": true, + "crc-32": true, + "elliptic": true, + "ethers": true, + "ethers-eip712": true, + "js-sha3": true, + "rlp-browser": true, + "secp256k1": true, + "superagent": true + } + }, "hamt-sharding": { "packages": { "is-buffer": true, @@ -4100,6 +4157,12 @@ "buffer": true } }, + "rlp-browser": { + "packages": { + "assert": true, + "buffer": true + } + }, "rn-host-detect": { "globals": { "__DEV__": true, @@ -4379,6 +4442,21 @@ "is-hex-prefixed": true } }, + "superagent": { + "globals": { + "ActiveXObject": true, + "XMLHttpRequest": true, + "btoa": true, + "clearTimeout": true, + "console.error": true, + "console.trace": true, + "console.warn": true, + "setTimeout": true + }, + "packages": { + "component-emitter": true + } + }, "textarea-caret": { "globals": { "document.body.appendChild": true, diff --git a/lavamoat/node/policy-override.json b/lavamoat/node/policy-override.json index 85e84f001..eaa6b9b26 100644 --- a/lavamoat/node/policy-override.json +++ b/lavamoat/node/policy-override.json @@ -1,13 +1,5 @@ { "resources": { - "node-sass": { - "native": true - }, - "module-deps": { - "packages": { - "loose-envify": true - } - }, "@babel/core": { "packages": { "": true, @@ -20,6 +12,34 @@ "@babel/plugin-proposal-nullish-coalescing-operator": true } }, + "@eslint/eslintrc": { + "packages": { + "@babel/eslint-parser": true, + "@babel/eslint-plugin": true, + "@metamask/eslint-config": true, + "@metamask/eslint-config-nodejs": true, + "eslint": true, + "eslint-config-prettier": true, + "eslint-plugin-import": true, + "eslint-plugin-node": true, + "eslint-plugin-prettier": true, + "eslint-plugin-react": true, + "eslint-plugin-react-hooks": true + } + }, + "eslint-module-utils": { + "packages": { + "eslint-import-resolver-node": true + } + }, + "node-sass": { + "native": true + }, + "module-deps": { + "packages": { + "loose-envify": true + } + }, "sass": { "env": "unfrozen", "builtin": { diff --git a/lavamoat/node/policy.json b/lavamoat/node/policy.json index f12ad2325..a9073f4cd 100644 --- a/lavamoat/node/policy.json +++ b/lavamoat/node/policy.json @@ -41,6 +41,20 @@ "source-map": true } }, + "@babel/eslint-parser": { + "packages": { + "@babel/core": true, + "eslint-scope": true, + "eslint-visitor-keys": true, + "semver": true + } + }, + "@babel/eslint-plugin": { + "packages": { + "eslint": true, + "eslint-rule-composer": true + } + }, "@babel/generator": { "globals": { "console.error": true @@ -815,7 +829,6 @@ "espree": true, "globals": true, "ignore": true, - "import-fresh": true, "js-yaml": true, "minimatch": true, "strip-json-comments": true @@ -1028,6 +1041,15 @@ "make-iterator": true } }, + "array-includes": { + "packages": { + "call-bind": true, + "define-properties": true, + "es-abstract": true, + "get-intrinsic": true, + "is-string": true + } + }, "array-initial": { "packages": { "array-slice": true, @@ -1044,6 +1066,19 @@ "array-uniq": true } }, + "array.prototype.flat": { + "packages": { + "define-properties": true, + "es-abstract": true + } + }, + "array.prototype.flatmap": { + "packages": { + "call-bind": true, + "define-properties": true, + "es-abstract": true + } + }, "async-done": { "builtin": { "domain.create": true @@ -1330,6 +1365,12 @@ "process.cwd": true } }, + "call-bind": { + "packages": { + "function-bind": true, + "get-intrinsic": true + } + }, "chalk": { "globals": { "process.env.TERM": true, @@ -1345,6 +1386,7 @@ "builtin": { "events.EventEmitter": true, "fs": true, + "os.type": true, "path.basename": true, "path.dirname": true, "path.extname": true, @@ -1479,6 +1521,11 @@ "typedarray": true } }, + "contains-path": { + "builtin": { + "path.normalize": true + } + }, "convert-source-map": { "builtin": { "fs.readFileSync": true, @@ -1657,7 +1704,8 @@ "assert": true }, "packages": { - "esutils": true + "esutils": true, + "isarray": true } }, "dom-serializer": { @@ -1731,9 +1779,22 @@ "WeakRef": true }, "packages": { + "call-bind": true, + "es-to-primitive": true, "function-bind": true, + "get-intrinsic": true, "has": true, - "has-symbols": true + "has-symbols": true, + "is-callable": true, + "is-regex": true, + "object-inspect": true + } + }, + "es-to-primitive": { + "packages": { + "is-callable": true, + "is-date-object": true, + "is-symbol": true } }, "es5-ext": { @@ -1844,6 +1905,141 @@ "regexpp": true } }, + "eslint-config-prettier": { + "globals": { + "process.env.ESLINT_CONFIG_PRETTIER_NO_DEPRECATED": true + } + }, + "eslint-import-resolver-node": { + "builtin": { + "path.dirname": true, + "path.resolve": true + }, + "packages": { + "debug": true, + "resolve": true + } + }, + "eslint-module-utils": { + "builtin": { + "crypto.createHash": true, + "fs.existsSync": true, + "fs.readdirSync": true, + "module": true, + "path.dirname": true, + "path.extname": true, + "path.join": true, + "path.parse": true, + "path.resolve": true + }, + "globals": { + "__dirname": true, + "console.warn": true, + "process.cwd": true, + "process.hrtime": true + }, + "packages": { + "debug": true, + "pkg-dir": true + } + }, + "eslint-plugin-es": { + "packages": { + "eslint-utils": true, + "regexpp": true + } + }, + "eslint-plugin-import": { + "builtin": { + "fs": true, + "path": true, + "vm": true + }, + "globals": { + "process.cwd": true, + "process.env": true + }, + "packages": { + "array-includes": true, + "array.prototype.flat": true, + "contains-path": true, + "debug": true, + "doctrine": true, + "eslint": true, + "eslint-module-utils": true, + "has": true, + "minimatch": true, + "object.values": true, + "read-pkg-up": true, + "resolve": true, + "tsconfig-paths": true, + "typescript": true + } + }, + "eslint-plugin-node": { + "builtin": { + "fs.readFileSync": true, + "fs.readdirSync": true, + "fs.statSync": true, + "path.basename": true, + "path.dirname": true, + "path.extname": true, + "path.isAbsolute": true, + "path.join": true, + "path.relative": true, + "path.resolve": true, + "path.sep": true + }, + "globals": { + "process.cwd": true + }, + "packages": { + "eslint-plugin-es": true, + "eslint-utils": true, + "ignore": true, + "minimatch": true, + "resolve": true, + "semver": true + } + }, + "eslint-plugin-prettier": { + "packages": { + "prettier": true, + "prettier-linter-helpers": true + } + }, + "eslint-plugin-react": { + "builtin": { + "fs.statSync": true, + "path.dirname": true, + "path.extname": true + }, + "globals": { + "console.error": true, + "console.log": true, + "process.argv.join": true, + "process.cwd": true + }, + "packages": { + "array-includes": true, + "array.prototype.flatmap": true, + "doctrine": true, + "has": true, + "jsx-ast-utils": true, + "minimatch": true, + "object.entries": true, + "object.fromentries": true, + "object.values": true, + "prop-types": true, + "resolve": true, + "string.prototype.matchall": true + } + }, + "eslint-plugin-react-hooks": { + "globals": { + "process.env.NODE_ENV": true + } + }, "eslint-scope": { "builtin": { "assert": true @@ -2042,6 +2238,17 @@ "to-regex-range": true } }, + "find-up": { + "builtin": { + "path.dirname": true, + "path.join": true, + "path.parse": true, + "path.resolve": true + }, + "packages": { + "locate-path": true + } + }, "first-chunk-stream": { "builtin": { "util.inherits": true @@ -2173,6 +2380,18 @@ "assert.equal": true } }, + "get-intrinsic": { + "globals": { + "AggregateError": true, + "FinalizationRegistry": true, + "WeakRef": true + }, + "packages": { + "function-bind": true, + "has": true, + "has-symbols": true + } + }, "get-stream": { "builtin": { "stream.PassThrough": true @@ -2542,6 +2761,12 @@ "kind-of": true } }, + "hosted-git-info": { + "builtin": { + "url.URL": true, + "url.parse": true + } + }, "htmlparser2": { "builtin": { "buffer.Buffer": true, @@ -2627,6 +2852,13 @@ "xtend": true } }, + "internal-slot": { + "packages": { + "get-intrinsic": true, + "has": true, + "side-channel": true + } + }, "is-absolute": { "packages": { "is-relative": true, @@ -2652,6 +2884,11 @@ "binary-extensions": true } }, + "is-callable": { + "globals": { + "document": true + } + }, "is-core-module": { "globals": { "process.versions": true @@ -2726,11 +2963,22 @@ "isobject": true } }, + "is-regex": { + "packages": { + "call-bind": true, + "has-symbols": true + } + }, "is-relative": { "packages": { "is-unc-path": true } }, + "is-symbol": { + "packages": { + "has-symbols": true + } + }, "is-unc-path": { "packages": { "unc-path-regex": true @@ -2796,6 +3044,14 @@ "Buffer": true } }, + "jsx-ast-utils": { + "globals": { + "console.error": true + }, + "packages": { + "object.assign": true + } + }, "just-debounce": { "globals": { "clearTimeout": true, @@ -2912,6 +3168,29 @@ "type-check": true } }, + "load-json-file": { + "builtin": { + "path.relative": true + }, + "packages": { + "graceful-fs": true, + "parse-json": true, + "pify": true, + "strip-bom": true + } + }, + "locate-path": { + "builtin": { + "path.resolve": true + }, + "globals": { + "process.cwd": true + }, + "packages": { + "p-locate": true, + "path-exists": true + } + }, "lodash": { "globals": { "define": true @@ -3159,6 +3438,18 @@ "setTimeout": true } }, + "normalize-package-data": { + "builtin": { + "url.parse": true, + "util.format": true + }, + "packages": { + "hosted-git-info": true, + "resolve": true, + "semver": true, + "validate-npm-package-license": true + } + }, "normalize-path": { "packages": { "remove-trailing-separator": true @@ -3196,6 +3487,7 @@ }, "object.assign": { "packages": { + "call-bind": true, "define-properties": true, "es-abstract": true, "has-symbols": true, @@ -3210,6 +3502,21 @@ "isobject": true } }, + "object.entries": { + "packages": { + "call-bind": true, + "define-properties": true, + "es-abstract": true, + "has": true + } + }, + "object.fromentries": { + "packages": { + "call-bind": true, + "define-properties": true, + "es-abstract": true + } + }, "object.omit": { "packages": { "for-own": true, @@ -3227,6 +3534,14 @@ "make-iterator": true } }, + "object.values": { + "packages": { + "call-bind": true, + "define-properties": true, + "es-abstract": true, + "has": true + } + }, "once": { "packages": { "wrappy": true @@ -3240,6 +3555,16 @@ "readable-stream": true } }, + "p-limit": { + "packages": { + "p-try": true + } + }, + "p-locate": { + "packages": { + "p-limit": true + } + }, "parent-module": { "packages": { "callsites": true @@ -3289,6 +3614,12 @@ "process.platform": true } }, + "path-exists": { + "builtin": { + "fs.access": true, + "fs.accessSync": true + } + }, "path-is-absolute": { "globals": { "process.platform": true @@ -3329,6 +3660,9 @@ "builtin": { "fs": true, "util.promisify": true + }, + "packages": { + "pify": true } }, "pause-stream": { @@ -3358,6 +3692,14 @@ "pinkie": true } }, + "pkg-dir": { + "builtin": { + "path.dirname": true + }, + "packages": { + "find-up": true + } + }, "plugin-error": { "builtin": { "util.inherits": true @@ -3444,11 +3786,62 @@ "postcss": true } }, + "prettier": { + "builtin": { + "assert": true, + "events": true, + "fs": true, + "module": true, + "os": true, + "path": true, + "stream": true, + "tty": true, + "util": true + }, + "globals": { + "Buffer": true, + "Intl": true, + "PerformanceObserver": true, + "WorkerGlobalScope": true, + "XMLHttpRequest": true, + "YAML_SILENCE_DEPRECATION_WARNINGS": true, + "YAML_SILENCE_WARNINGS": true, + "__dirname": true, + "__filename": true, + "atob": true, + "btoa": true, + "clearTimeout": true, + "console": true, + "define": true, + "document": true, + "localStorage": true, + "navigator": true, + "performance": true, + "process": true, + "setImmediate": true, + "setTimeout": true + } + }, + "prettier-linter-helpers": { + "packages": { + "fast-diff": true + } + }, "process-nextick-args": { "globals": { "process": true } }, + "prop-types": { + "globals": { + "console": true, + "process.env.NODE_ENV": true + }, + "packages": { + "object-assign": true, + "react-is": true + } + }, "pump": { "builtin": { "fs": true @@ -3519,11 +3912,33 @@ "strip-json-comments": true } }, + "react-is": { + "globals": { + "console": true, + "process.env.NODE_ENV": true + } + }, "read-only-stream": { "packages": { "readable-stream": true } }, + "read-pkg": { + "builtin": { + "path.join": true + }, + "packages": { + "load-json-file": true, + "normalize-package-data": true, + "path-type": true + } + }, + "read-pkg-up": { + "packages": { + "find-up": true, + "read-pkg": true + } + }, "readable-stream": { "builtin": { "buffer.Buffer": true, @@ -3563,6 +3978,7 @@ }, "globals": { "process.platform": true, + "process.versions.node.split": true, "setImmediate": true }, "packages": { @@ -3597,6 +4013,12 @@ "safe-regex": true } }, + "regexp.prototype.flags": { + "packages": { + "call-bind": true, + "define-properties": true + } + }, "regexpu-core": { "packages": { "regenerate": true, @@ -3855,6 +4277,13 @@ "shebang-regex": true } }, + "side-channel": { + "packages": { + "call-bind": true, + "get-intrinsic": true, + "object-inspect": true + } + }, "signal-exit": { "builtin": { "assert.equal": true, @@ -3933,6 +4362,22 @@ "urix": true } }, + "source-map-support": { + "builtin": { + "fs": true, + "path.dirname": true, + "path.resolve": true + }, + "globals": { + "XMLHttpRequest": true, + "console.error": true, + "process": true + }, + "packages": { + "buffer-from": true, + "source-map": true + } + }, "source-map-url": { "globals": { "define": true @@ -3943,6 +4388,22 @@ "define": true } }, + "spdx-correct": { + "packages": { + "spdx-license-ids": true + } + }, + "spdx-expression-parse": { + "builtin": { + "fs.readFileSync": true, + "path.normalize": true + }, + "globals": { + "console.log": true, + "process.argv.slice": true, + "process.exit": true + } + }, "specificity": { "globals": { "define": true @@ -4039,6 +4500,16 @@ "strip-ansi": true } }, + "string.prototype.matchall": { + "packages": { + "call-bind": true, + "define-properties": true, + "es-abstract": true, + "has-symbols": true, + "internal-slot": true, + "regexp.prototype.flags": true + } + }, "string_decoder": { "builtin": { "buffer.Buffer": true @@ -4292,6 +4763,21 @@ "through2": true } }, + "tsconfig-paths": { + "builtin": { + "fs.existsSync": true, + "fs.lstatSync": true, + "fs.readFileSync": true, + "fs.statSync": true, + "path.dirname": true, + "path.join": true, + "path.resolve": true + }, + "packages": { + "json5": true, + "strip-bom": true + } + }, "type-check": { "packages": { "prelude-ls": true @@ -4305,6 +4791,51 @@ "is-typedarray": true } }, + "typescript": { + "builtin": { + "buffer.Buffer": true, + "crypto": true, + "fs.closeSync": true, + "fs.mkdirSync": true, + "fs.openSync": true, + "fs.readFileSync": true, + "fs.readdirSync": true, + "fs.realpathSync": true, + "fs.statSync": true, + "fs.unlinkSync": true, + "fs.unwatchFile": true, + "fs.utimesSync": true, + "fs.watch": true, + "fs.watchFile": true, + "fs.writeFileSync": true, + "fs.writeSync": true, + "inspector": true, + "os.EOL": true, + "os.platform": true, + "path.dirname": true, + "path.join": true, + "path.resolve": true + }, + "globals": { + "Intl": true, + "TypeScript": "write", + "__dirname": true, + "__filename": true, + "__magic__": true, + "clearTimeout": true, + "console.log": true, + "gc": true, + "globalThis": "write", + "onProfilerEvent": true, + "performance": true, + "process": true, + "setTimeout": true, + "toolsVersion": "write" + }, + "packages": { + "source-map-support": true + } + }, "undeclared-identifiers": { "packages": { "acorn-node": true, @@ -4422,6 +4953,12 @@ "util.deprecate": true } }, + "validate-npm-package-license": { + "packages": { + "spdx-correct": true, + "spdx-expression-parse": true + } + }, "vfile": { "builtin": { "path.basename": true, diff --git a/package.json b/package.json index 7185aa783..1d8b2c01f 100644 --- a/package.json +++ b/package.json @@ -110,14 +110,14 @@ "@metamask/eth-token-tracker": "^3.0.1", "@metamask/etherscan-link": "^2.1.0", "@metamask/jazzicon": "^2.0.0", - "@metamask/logo": "^3.0.1", + "@metamask/logo": "^3.1.0", "@metamask/obs-store": "^5.0.0", "@metamask/post-message-stream": "^4.0.0", "@metamask/providers": "^8.1.1", "@popperjs/core": "^2.4.0", "@reduxjs/toolkit": "^1.6.2", - "@sentry/browser": "^5.26.0", - "@sentry/integrations": "^5.26.0", + "@sentry/browser": "^6.0.0", + "@sentry/integrations": "^6.0.0", "@zxing/library": "^0.8.0", "analytics-node": "^3.4.0-beta.3", "await-semaphore": "^0.1.1", @@ -135,9 +135,10 @@ "eth-ens-namehash": "^2.0.8", "eth-json-rpc-filters": "^4.2.1", "eth-json-rpc-infura": "^5.1.0", - "eth-json-rpc-middleware": "^6.0.0", + "eth-json-rpc-middleware": "^8.0.0", "eth-keyring-controller": "^6.2.0", "eth-method-registry": "^2.0.0", + "eth-lattice-keyring": "^0.3.0", "eth-query": "^2.1.2", "eth-rpc-errors": "^4.0.2", "eth-sig-util": "^3.0.0", @@ -229,16 +230,18 @@ "@metamask/forwarder": "^1.1.0", "@metamask/test-dapp": "^4.0.1", "@sentry/cli": "^1.58.0", - "@storybook/addon-actions": "^6.1.17", - "@storybook/addon-backgrounds": "^6.1.17", - "@storybook/addon-knobs": "^6.1.17", - "@storybook/addon-toolbars": "^6.1.17", - "@storybook/addons": "^6.1.7", - "@storybook/api": "^6.1.7", - "@storybook/components": "^6.1.7", - "@storybook/core": "^6.1.17", - "@storybook/react": "^6.1.17", - "@storybook/storybook-deployer": "^2.8.7", + "@storybook/addon-a11y": "^6.3.12", + "@storybook/addon-actions": "^6.3.12", + "@storybook/addon-essentials": "^6.3.12", + "@storybook/addon-knobs": "^6.3.1", + "@storybook/addons": "^6.3.12", + "@storybook/api": "^6.3.12", + "@storybook/components": "^6.3.12", + "@storybook/core": "^6.3.12", + "@storybook/core-events": "^6.3.0", + "@storybook/react": "^6.3.12", + "@storybook/storybook-deployer": "^2.8.10", + "@storybook/theming": "^6.3.0", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^10.4.8", "@testing-library/react-hooks": "^3.2.1", @@ -249,7 +252,7 @@ "brfs": "^2.0.2", "browserify": "^16.5.1", "chalk": "^3.0.0", - "chromedriver": "^93.0.1", + "chromedriver": "^95.0.0", "concurrently": "^5.2.0", "copy-webpack-plugin": "^6.0.3", "cross-spawn": "^7.0.3", @@ -263,6 +266,7 @@ "enzyme-adapter-react-16": "^1.15.1", "eslint": "^7.23.0", "eslint-config-prettier": "^8.1.0", + "eslint-import-resolver-node": "^0.3.4", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jest": "^24.3.4", "eslint-plugin-mocha": "^8.1.0", @@ -276,6 +280,7 @@ "ganache-cli": "^6.12.1", "ganache-core": "^2.13.1", "geckodriver": "^1.21.0", + "globby": "^11.0.4", "gulp": "^4.0.2", "gulp-autoprefixer": "^5.0.0", "gulp-dart-sass": "^1.0.2", @@ -333,7 +338,7 @@ "vinyl-buffer": "^1.0.1", "vinyl-source-stream": "^2.0.0", "vinyl-sourcemaps-apply": "^0.2.1", - "watchify": "^3.11.1", + "watchify": "^4.0.0", "webpack": "^4.41.6", "yargs": "^17.0.1", "yarn-deduplicate": "^3.1.0" @@ -344,6 +349,7 @@ }, "lavamoat": { "allowScripts": { + "gridplus-sdk": false, "chromedriver": true, "geckodriver": true, "@sentry/cli": true, diff --git a/patches/@eslint+eslintrc+0.4.0.patch b/patches/@eslint+eslintrc+0.4.0.patch new file mode 100644 index 000000000..c469c7c84 --- /dev/null +++ b/patches/@eslint+eslintrc+0.4.0.patch @@ -0,0 +1,21 @@ +diff --git a/node_modules/@eslint/eslintrc/lib/config-array-factory.js b/node_modules/@eslint/eslintrc/lib/config-array-factory.js +index c7ff6a0..6a88c6d 100644 +--- a/node_modules/@eslint/eslintrc/lib/config-array-factory.js ++++ b/node_modules/@eslint/eslintrc/lib/config-array-factory.js +@@ -41,7 +41,6 @@ + + const fs = require("fs"); + const path = require("path"); +-const importFresh = require("import-fresh"); + const stripComments = require("strip-json-comments"); + const ConfigValidator = require("./shared/config-validator"); + const naming = require("./shared/naming"); +@@ -222,7 +221,7 @@ function loadLegacyConfigFile(filePath) { + function loadJSConfigFile(filePath) { + debug(`Loading JS config file: ${filePath}`); + try { +- return importFresh(filePath); ++ return require(filePath); + } catch (e) { + debug(`Error reading JavaScript file: ${filePath}`); + e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`; diff --git a/patches/eslint+7.23.0.patch b/patches/eslint+7.23.0.patch new file mode 100644 index 000000000..8582fadc6 --- /dev/null +++ b/patches/eslint+7.23.0.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/eslint/lib/linter/linter.js b/node_modules/eslint/lib/linter/linter.js +index adb5c21..4a4be92 100644 +--- a/node_modules/eslint/lib/linter/linter.js ++++ b/node_modules/eslint/lib/linter/linter.js +@@ -560,7 +560,7 @@ function resolveParserOptions(parserName, providedOptions, enabledEnvironments) + */ + function resolveGlobals(providedGlobals, enabledEnvironments) { + return Object.assign( +- {}, ++ Object.create(null), + ...enabledEnvironments.filter(env => env.globals).map(env => env.globals), + providedGlobals + ); diff --git a/patches/object.values+1.1.1.patch b/patches/object.values+1.1.1.patch new file mode 100644 index 000000000..f0323c184 --- /dev/null +++ b/patches/object.values+1.1.1.patch @@ -0,0 +1,23 @@ +diff --git a/node_modules/object.values/index.js b/node_modules/object.values/index.js +index b8ba091..2dc8083 100644 +--- a/node_modules/object.values/index.js ++++ b/node_modules/object.values/index.js +@@ -1,17 +1,3 @@ + 'use strict'; + +-var define = require('define-properties'); +- +-var implementation = require('./implementation'); +-var getPolyfill = require('./polyfill'); +-var shim = require('./shim'); +- +-var polyfill = getPolyfill(); +- +-define(polyfill, { +- getPolyfill: getPolyfill, +- implementation: implementation, +- shim: shim +-}); +- +-module.exports = polyfill; ++module.exports = Object.values; diff --git a/patches/watchify+3.11.1.patch b/patches/watchify+4.0.0.patch similarity index 96% rename from patches/watchify+3.11.1.patch rename to patches/watchify+4.0.0.patch index cf3e489ad..a5eb2b322 100644 --- a/patches/watchify+3.11.1.patch +++ b/patches/watchify+4.0.0.patch @@ -1,11 +1,11 @@ diff --git a/node_modules/watchify/index.js b/node_modules/watchify/index.js -index 0753b9f..4fea9e1 100644 +index 0753b9f..05efb1b 100644 --- a/node_modules/watchify/index.js +++ b/node_modules/watchify/index.js @@ -58,33 +58,6 @@ function watchify (b, opts) { if (pkgcache) pkgcache[file] = pkg; }); - + - b.on('reset', reset); - reset(); - diff --git a/shared/constants/gas.js b/shared/constants/gas.js index ebae444a4..1c0bc8ae7 100644 --- a/shared/constants/gas.js +++ b/shared/constants/gas.js @@ -30,6 +30,11 @@ export const GAS_RECOMMENDATIONS = { HIGH: 'high', }; +/** + * Represents the user customizing their gas preference + */ +export const CUSTOM_GAS_ESTIMATE = 'custom'; + /** * These represent the different edit modes presented in the UI */ diff --git a/shared/constants/hardware-wallets.js b/shared/constants/hardware-wallets.js index 705754fe7..f32306472 100644 --- a/shared/constants/hardware-wallets.js +++ b/shared/constants/hardware-wallets.js @@ -6,6 +6,7 @@ export const KEYRING_TYPES = { LEDGER: 'Ledger Hardware', TREZOR: 'Trezor Hardware', + LATTICE: 'Lattice Hardware', }; /** diff --git a/shared/constants/metametrics.js b/shared/constants/metametrics.js index e9262d750..7f0a3804f 100644 --- a/shared/constants/metametrics.js +++ b/shared/constants/metametrics.js @@ -110,9 +110,9 @@ * the page view */ -// An empty string "" is a, currently undocumented, way of telling mixpanel +// An empty string is a, currently undocumented, way of telling mixpanel // that these events are meant to be anonymous and not identified to any user -export const METAMETRICS_ANONYMOUS_ID = '""'; +export const METAMETRICS_ANONYMOUS_ID = ''; /** * This object is used to identify events that are triggered by the background diff --git a/shared/constants/network.js b/shared/constants/network.js index 3087a68e0..2210605d0 100644 --- a/shared/constants/network.js +++ b/shared/constants/network.js @@ -3,6 +3,7 @@ export const RINKEBY = 'rinkeby'; export const KOVAN = 'kovan'; export const MAINNET = 'mainnet'; export const GOERLI = 'goerli'; +export const LOCALHOST = 'localhost'; export const NETWORK_TYPE_RPC = 'rpc'; export const MAINNET_NETWORK_ID = '1'; @@ -34,6 +35,7 @@ export const RINKEBY_DISPLAY_NAME = 'Rinkeby'; export const KOVAN_DISPLAY_NAME = 'Kovan'; export const MAINNET_DISPLAY_NAME = 'Ethereum Mainnet'; export const GOERLI_DISPLAY_NAME = 'Goerli'; +export const LOCALHOST_DISPLAY_NAME = 'Localhost 8545'; const infuraProjectId = process.env.INFURA_PROJECT_ID; const getRpcUrl = (network) => @@ -44,6 +46,7 @@ export const RINKEBY_RPC_URL = getRpcUrl('rinkeby'); export const KOVAN_RPC_URL = getRpcUrl('kovan'); export const MAINNET_RPC_URL = getRpcUrl('mainnet'); export const GOERLI_RPC_URL = getRpcUrl('goerli'); +export const LOCALHOST_RPC_URL = 'http://localhost:8545'; export const ETH_SYMBOL = 'ETH'; export const WETH_SYMBOL = 'WETH'; @@ -63,6 +66,7 @@ export const TEST_CHAINS = [ RINKEBY_CHAIN_ID, GOERLI_CHAIN_ID, KOVAN_CHAIN_ID, + LOCALHOST_CHAIN_ID, ]; /** @@ -74,6 +78,7 @@ export const NETWORK_TYPE_TO_ID_MAP = { [KOVAN]: { networkId: KOVAN_NETWORK_ID, chainId: KOVAN_CHAIN_ID }, [GOERLI]: { networkId: GOERLI_NETWORK_ID, chainId: GOERLI_CHAIN_ID }, [MAINNET]: { networkId: MAINNET_NETWORK_ID, chainId: MAINNET_CHAIN_ID }, + [LOCALHOST]: { networkId: LOCALHOST_NETWORK_ID, chainId: LOCALHOST_CHAIN_ID }, }; export const NETWORK_TO_NAME_MAP = { @@ -82,18 +87,21 @@ export const NETWORK_TO_NAME_MAP = { [KOVAN]: KOVAN_DISPLAY_NAME, [MAINNET]: MAINNET_DISPLAY_NAME, [GOERLI]: GOERLI_DISPLAY_NAME, + [LOCALHOST]: LOCALHOST_DISPLAY_NAME, [ROPSTEN_NETWORK_ID]: ROPSTEN_DISPLAY_NAME, [RINKEBY_NETWORK_ID]: RINKEBY_DISPLAY_NAME, [KOVAN_NETWORK_ID]: KOVAN_DISPLAY_NAME, [GOERLI_NETWORK_ID]: GOERLI_DISPLAY_NAME, [MAINNET_NETWORK_ID]: MAINNET_DISPLAY_NAME, + [LOCALHOST_NETWORK_ID]: LOCALHOST_DISPLAY_NAME, [ROPSTEN_CHAIN_ID]: ROPSTEN_DISPLAY_NAME, [RINKEBY_CHAIN_ID]: RINKEBY_DISPLAY_NAME, [KOVAN_CHAIN_ID]: KOVAN_DISPLAY_NAME, [GOERLI_CHAIN_ID]: GOERLI_DISPLAY_NAME, [MAINNET_CHAIN_ID]: MAINNET_DISPLAY_NAME, + [LOCALHOST_CHAIN_ID]: LOCALHOST_DISPLAY_NAME, }; export const CHAIN_ID_TO_TYPE_MAP = Object.entries( @@ -109,6 +117,7 @@ export const CHAIN_ID_TO_RPC_URL_MAP = { [KOVAN_CHAIN_ID]: KOVAN_RPC_URL, [GOERLI_CHAIN_ID]: GOERLI_RPC_URL, [MAINNET_CHAIN_ID]: MAINNET_RPC_URL, + [LOCALHOST_CHAIN_ID]: LOCALHOST_RPC_URL, }; export const CHAIN_ID_TO_NETWORK_ID_MAP = Object.values( @@ -152,3 +161,13 @@ export const CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP = { [OPTIMISM_CHAIN_ID]: 1, [OPTIMISM_TESTNET_CHAIN_ID]: 1, }; + +/** + * Ethereum JSON-RPC methods that are known to exist but that we intentionally + * do not support. + */ +export const UNSUPPORTED_RPC_METHODS = new Set([ + // This is implemented later in our middleware stack – specifically, in + // eth-json-rpc-middleware – but our UI does not support it. + 'eth_signTransaction', +]); diff --git a/test/data/mock-state.json b/test/data/mock-state.json index e8c979fd1..9adeaea9b 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -160,6 +160,10 @@ "toNickname": "" }, "useTokenDetection": true, + "advancedGasFee": { + "maxBaseFee": "1.5", + "priorityFee": "2" + }, "tokenList": { "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", diff --git a/test/e2e/tests/custom-rpc-history.spec.js b/test/e2e/tests/custom-rpc-history.spec.js index 078cb124f..687b64f96 100644 --- a/test/e2e/tests/custom-rpc-history.spec.js +++ b/test/e2e/tests/custom-rpc-history.spec.js @@ -30,9 +30,9 @@ describe('Stores custom RPC history', function () { await driver.clickElement('.network-display'); - await driver.clickElement({ text: 'Custom RPC', tag: 'span' }); + await driver.clickElement({ text: 'Add Network', tag: 'button' }); - await driver.findElement('.settings-page__sub-header-text'); + await driver.findElement('.networks-tab__sub-header-text'); const customRpcInputs = await driver.findElements('input[type="text"]'); const networkNameInput = customRpcInputs[0]; @@ -48,7 +48,10 @@ describe('Stores custom RPC history', function () { await chainIdInput.clear(); await chainIdInput.sendKeys(chainId.toString()); - await driver.clickElement('.network-form__footer .btn-primary'); + await driver.clickElement( + '.networks-tab__add-network-form-footer .btn-primary', + ); + await driver.findElement({ text: networkName, tag: 'span' }); }, ); @@ -71,9 +74,9 @@ describe('Stores custom RPC history', function () { await driver.clickElement('.network-display'); - await driver.clickElement({ text: 'Custom RPC', tag: 'span' }); + await driver.clickElement({ text: 'Add Network', tag: 'button' }); - await driver.findElement('.settings-page__sub-header-text'); + await driver.findElement('.networks-tab__sub-header-text'); const customRpcInputs = await driver.findElements('input[type="text"]'); const rpcUrlInput = customRpcInputs[1]; @@ -81,8 +84,8 @@ describe('Stores custom RPC history', function () { await rpcUrlInput.clear(); await rpcUrlInput.sendKeys(duplicateRpcUrl); await driver.findElement({ - text: 'This URL is currently used by the Localhost 8545 network.', - tag: 'p', + text: 'This URL is currently used by the localhost network.', + tag: 'h6', }); }, ); @@ -106,9 +109,9 @@ describe('Stores custom RPC history', function () { await driver.clickElement('.network-display'); - await driver.clickElement({ text: 'Custom RPC', tag: 'span' }); + await driver.clickElement({ text: 'Add Network', tag: 'button' }); - await driver.findElement('.settings-page__sub-header-text'); + await driver.findElement('.networks-tab__sub-header-text'); const customRpcInputs = await driver.findElements('input[type="text"]'); const rpcUrlInput = customRpcInputs[1]; @@ -120,9 +123,8 @@ describe('Stores custom RPC history', function () { await chainIdInput.clear(); await chainIdInput.sendKeys(duplicateChainId); await driver.findElement({ - text: - 'This Chain ID is currently used by the Localhost 8545 network.', - tag: 'p', + text: 'This Chain ID is currently used by the localhost network.', + tag: 'h6', }); }, ); @@ -189,10 +191,13 @@ describe('Stores custom RPC history', function () { await driver.clickElement('.network-display'); - await driver.clickElement({ text: 'Custom RPC', tag: 'span' }); + await driver.clickElement({ text: 'Add Network', tag: 'button' }); - // cancel new custom rpc - await driver.clickElement('.network-form__footer button.btn-secondary'); + await driver.findVisibleElement('.settings-page__content'); + // // cancel new custom rpc + await driver.clickElement( + '.networks-tab__add-network-form-footer button.btn-secondary', + ); const networkListItems = await driver.findClickableElements( '.networks-tab__networks-list-name', diff --git a/test/e2e/tests/from-import-ui.spec.js b/test/e2e/tests/from-import-ui.spec.js index 0bd3b683a..a83a9bbbb 100644 --- a/test/e2e/tests/from-import-ui.spec.js +++ b/test/e2e/tests/from-import-ui.spec.js @@ -27,38 +27,72 @@ describe('Metamask Import UI', function () { async ({ driver }) => { await driver.navigate(); - // clicks the continue button on the welcome screen - await driver.findElement('.welcome-page__header'); - await driver.clickElement({ - text: enLocaleMessages.getStarted.message, - tag: 'button', - }); + if (process.env.ONBOARDING_V2 === '1') { + // welcome + await driver.clickElement('[data-testid="onboarding-import-wallet"]'); - // clicks the "Import Wallet" option - await driver.clickElement({ text: 'Import wallet', tag: 'button' }); + // metrics + await driver.clickElement('[data-testid="metametrics-no-thanks"]'); - // clicks the "No thanks" option on the metametrics opt-in screen - await driver.clickElement('.btn-secondary'); + // import with recovery phrase + await driver.fill('[data-testid="import-srp-text"]', testSeedPhrase); + await driver.clickElement('[data-testid="import-srp-confirm"]'); - // Import Secret Recovery Phrase - await driver.fill( - 'input[placeholder="Paste Secret Recovery Phrase from clipboard"]', - testSeedPhrase, - ); + // create password + await driver.fill( + '[data-testid="create-password-new"]', + 'correct horse battery staple', + ); + await driver.fill( + '[data-testid="create-password-confirm"]', + 'correct horse battery staple', + ); + await driver.clickElement('[data-testid="create-password-terms"]'); + await driver.clickElement('[data-testid="create-password-import"]'); + + // complete + await driver.clickElement('[data-testid="onboarding-complete-done"]'); + + // pin extension + await driver.clickElement('[data-testid="pin-extension-next"]'); + await driver.clickElement('[data-testid="pin-extension-done"]'); + } else { + // clicks the continue button on the welcome screen + await driver.findElement('.welcome-page__header'); + await driver.clickElement({ + text: enLocaleMessages.getStarted.message, + tag: 'button', + }); + + // clicks the "Import Wallet" option + await driver.clickElement({ text: 'Import wallet', tag: 'button' }); + + // clicks the "No thanks" option on the metametrics opt-in screen + await driver.clickElement('.btn-secondary'); + + // Import Secret Recovery Phrase + await driver.fill( + 'input[placeholder="Paste Secret Recovery Phrase from clipboard"]', + testSeedPhrase, + ); - await driver.fill('#password', 'correct horse battery staple'); - await driver.fill('#confirm-password', 'correct horse battery staple'); + await driver.fill('#password', 'correct horse battery staple'); + await driver.fill( + '#confirm-password', + 'correct horse battery staple', + ); - await driver.clickElement('.first-time-flow__terms'); + await driver.clickElement('.first-time-flow__terms'); - await driver.clickElement({ text: 'Import', tag: 'button' }); + await driver.clickElement({ text: 'Import', tag: 'button' }); - // clicks through the success screen - await driver.findElement({ text: 'Congratulations', tag: 'div' }); - await driver.clickElement({ - text: enLocaleMessages.endOfFlowMessage10.message, - tag: 'button', - }); + // clicks through the success screen + await driver.findElement({ text: 'Congratulations', tag: 'div' }); + await driver.clickElement({ + text: enLocaleMessages.endOfFlowMessage10.message, + tag: 'button', + }); + } // Show account information await driver.clickElement( @@ -233,10 +267,15 @@ describe('Metamask Import UI', function () { // should remove the account await driver.clickElement({ text: 'Remove', tag: 'button' }); - const currentActiveAccountName = await driver.findElement( - '.selected-account__name', + // Wait until selected account switches away from removed account to first account + await driver.waitForSelector( + { + css: '.selected-account__name', + text: 'Account 1', + }, + { timeout: 10000 }, ); - assert.equal(await currentActiveAccountName.getText(), 'Account 1'); + await driver.delay(regularDelayMs); await driver.clickElement('.account-menu__icon'); diff --git a/test/e2e/tests/lockdown.spec.js b/test/e2e/tests/lockdown.spec.js new file mode 100644 index 000000000..a1d0db969 --- /dev/null +++ b/test/e2e/tests/lockdown.spec.js @@ -0,0 +1,89 @@ +const { strict: assert } = require('assert'); +const { Browser } = require('selenium-webdriver'); +const { + getGlobalProperties, + testIntrinsic, +} = require('../../helpers/protect-intrinsics-helpers'); +const { withFixtures } = require('../helpers'); +const { PAGES } = require('../webdriver/driver'); + +const isFirefox = process.env.SELENIUM_BROWSER === Browser.FIREFOX; + +/** + * This script iterates over all named intrinsics and tests that they are locked + * down per ses/lockdown. + * + * We set globalThis to window in Firefox because the test fails otherwise. + * We believe this is due to some Selenium-related shenanigans. In the browser, + * this behavior is not a problem. + */ +const lockdownTestScript = ` +${isFirefox ? 'globalThis = window;' : ''} + +const assert = { + equal: (value, comparison, message) => { + if (value !== comparison) { + throw new Error(message || 'not equal'); + } + }, + ok: (value, message) => { + if (!value) { + throw new Error(message || 'not ok'); + } + }, +}; + +${getGlobalProperties.toString()} + +${testIntrinsic.toString()} + +try { + getGlobalProperties().forEach((propertyName) => { + console.log('Testing intrinsic:', propertyName); + testIntrinsic(propertyName); + }) + console.log('Lockdown test successful!'); + return true; +} catch (error) { + console.log('Lockdown test failed.', error); + return false; +} +`; + +describe('lockdown', function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: 25000000000000000000, + }, + ], + }; + + it('the UI and background environments are locked down', async function () { + await withFixtures( + { + // The fixtures used here is arbitrary. Any fixture would do. + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(PAGES.HOME); + assert.equal( + await driver.executeScript(lockdownTestScript), + true, + 'The UI environment should be locked down.', + ); + + await driver.navigate(PAGES.BACKGROUND); + assert.equal( + await driver.executeScript(lockdownTestScript), + true, + 'The background environment should be locked down.', + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/metamask-responsive-ui.spec.js b/test/e2e/tests/metamask-responsive-ui.spec.js index e512ce9f4..adb5ad644 100644 --- a/test/e2e/tests/metamask-responsive-ui.spec.js +++ b/test/e2e/tests/metamask-responsive-ui.spec.js @@ -16,77 +16,133 @@ describe('Metamask Responsive UI', function () { async ({ driver }) => { await driver.navigate(); - // clicks the continue button on the welcome screen - await driver.findElement('.welcome-page__header'); - await driver.clickElement({ - text: enLocaleMessages.getStarted.message, - tag: 'button', - }); - await driver.delay(tinyDelayMs); + async function clickWordAndWait(word) { + await driver.clickElement( + `[data-testid="seed-phrase-sorted"] [data-testid="draggable-seed-${word}"]`, + ); + await driver.delay(tinyDelayMs); + } - // clicks the "Create New Wallet" option - await driver.clickElement({ text: 'Create a Wallet', tag: 'button' }); + if (process.env.ONBOARDING_V2 === '1') { + // welcome + await driver.clickElement('[data-testid="onboarding-create-wallet"]'); - // clicks the "I Agree" option on the metametrics opt-in screen - await driver.clickElement('.btn-primary'); + // metrics + await driver.clickElement('[data-testid="metametrics-no-thanks"]'); - // accepts a secure password - await driver.fill( - '.first-time-flow__form #create-password', - 'correct horse battery staple', - ); - await driver.fill( - '.first-time-flow__form #confirm-password', - 'correct horse battery staple', - ); - await driver.clickElement('.first-time-flow__checkbox'); - await driver.clickElement('.first-time-flow__form button'); + // create password + await driver.fill( + '[data-testid="create-password-new"]', + 'correct horse battery staple', + ); + await driver.fill( + '[data-testid="create-password-confirm"]', + 'correct horse battery staple', + ); + await driver.clickElement('[data-testid="create-password-terms"]'); + await driver.clickElement('[data-testid="create-password-wallet"]'); - // renders the Secret Recovery Phrase intro screen - await driver.clickElement('.seed-phrase-intro__left button'); + // secure wallet + await driver.clickElement( + '[data-testid="secure-wallet-recommended"]', + ); - // reveals the Secret Recovery Phrase - await driver.clickElement( - '.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button', - ); - const revealedSeedPhrase = await driver.findElement( - '.reveal-seed-phrase__secret-words', - ); - const seedPhrase = await revealedSeedPhrase.getText(); - assert.equal(seedPhrase.split(' ').length, 12); + // review + await driver.clickElement('[data-testid="recovery-phrase-reveal"]'); + const chipTwo = await ( + await driver.findElement('[data-testid="recovery-phrase-chip-2"]') + ).getText(); + const chipThree = await ( + await driver.findElement('[data-testid="recovery-phrase-chip-3"]') + ).getText(); + const chipSeven = await ( + await driver.findElement('[data-testid="recovery-phrase-chip-7"]') + ).getText(); + await driver.clickElement('[data-testid="recovery-phrase-next"]'); + + // confirm + await driver.fill('[data-testid="recovery-phrase-input-2"]', chipTwo); + await driver.fill( + '[data-testid="recovery-phrase-input-3"]', + chipThree, + ); + await driver.fill( + '[data-testid="recovery-phrase-input-7"]', + chipSeven, + ); + await driver.clickElement('[data-testid="recovery-phrase-confirm"]'); + + // complete + await driver.clickElement('[data-testid="onboarding-complete-done"]'); + + // pin extension + await driver.clickElement('[data-testid="pin-extension-next"]'); + await driver.clickElement('[data-testid="pin-extension-done"]'); + } else { + // clicks the continue button on the welcome screen + await driver.findElement('.welcome-page__header'); + await driver.clickElement({ + text: enLocaleMessages.getStarted.message, + tag: 'button', + }); + await driver.delay(tinyDelayMs); - await driver.clickElement({ - text: enLocaleMessages.next.message, - tag: 'button', - }); + // clicks the "Create New Wallet" option + await driver.clickElement({ text: 'Create a Wallet', tag: 'button' }); - async function clickWordAndWait(word) { - await driver.clickElement( - `[data-testid="seed-phrase-sorted"] [data-testid="draggable-seed-${word}"]`, + // clicks the "I Agree" option on the metametrics opt-in screen + await driver.clickElement('.btn-primary'); + + // accepts a secure password + await driver.fill( + '.first-time-flow__form #create-password', + 'correct horse battery staple', ); - await driver.delay(tinyDelayMs); - } + await driver.fill( + '.first-time-flow__form #confirm-password', + 'correct horse battery staple', + ); + await driver.clickElement('.first-time-flow__checkbox'); + await driver.clickElement('.first-time-flow__form button'); - // can retype the Secret Recovery Phrase - const words = seedPhrase.split(' '); - for (const word of words) { - await clickWordAndWait(word); - } - await driver.clickElement({ text: 'Confirm', tag: 'button' }); + // renders the Secret Recovery Phrase intro screen + await driver.clickElement('.seed-phrase-intro__left button'); - // clicks through the success screen - await driver.findElement({ text: 'Congratulations', tag: 'div' }); - await driver.clickElement({ - text: enLocaleMessages.endOfFlowMessage10.message, - tag: 'button', - }); + // reveals the Secret Recovery Phrase + await driver.clickElement( + '.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button', + ); + const revealedSeedPhrase = await driver.findElement( + '.reveal-seed-phrase__secret-words', + ); + const seedPhrase = await revealedSeedPhrase.getText(); + assert.equal(seedPhrase.split(' ').length, 12); + + await driver.clickElement({ + text: enLocaleMessages.next.message, + tag: 'button', + }); + + // can retype the Secret Recovery Phrase + const words = seedPhrase.split(' '); + for (const word of words) { + await clickWordAndWait(word); + } + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // clicks through the success screen + await driver.findElement({ text: 'Congratulations', tag: 'div' }); + await driver.clickElement({ + text: enLocaleMessages.endOfFlowMessage10.message, + tag: 'button', + }); + } - // Show account information - // balance renders - await driver.waitForSelector({ - css: '[data-testid="eth-overview__primary-currency"]', - text: '0 ETH', - }); + // assert balance + const balance = await driver.findElement( + '[data-testid="wallet-balance"]', + ); + assert.ok(/^0\sETH$/u.test(await balance.getText())); }, ); }); diff --git a/test/e2e/tests/provider-api.spec.js b/test/e2e/tests/provider-api.spec.js new file mode 100644 index 000000000..c2c5928f7 --- /dev/null +++ b/test/e2e/tests/provider-api.spec.js @@ -0,0 +1,111 @@ +const { strict: assert } = require('assert'); +const { errorCodes } = require('eth-rpc-errors'); +const { withFixtures } = require('../helpers'); + +describe('MetaMask', function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: 25000000000000000000, + }, + ], + }; + + it('provider should inform dapp when switching networks', async function () { + await withFixtures( + { + dapp: true, + fixtures: 'connected-state', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.openNewPage('http://127.0.0.1:8080/'); + const networkDiv = await driver.waitForSelector({ + css: '#network', + text: '1337', + }); + const chainIdDiv = await driver.waitForSelector({ + css: '#chainId', + text: '0x539', + }); + assert.equal(await networkDiv.getText(), '1337'); + assert.equal(await chainIdDiv.getText(), '0x539'); + + const windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindow(windowHandles[0]); + + await driver.clickElement('.network-display'); + await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'span' }); + + await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + const switchedNetworkDiv = await driver.waitForSelector({ + css: '#network', + text: '1', + }); + const switchedChainIdDiv = await driver.waitForSelector({ + css: '#chainId', + text: '0x1', + }); + const accountsDiv = await driver.findElement('#accounts'); + + assert.equal(await switchedNetworkDiv.getText(), '1'); + assert.equal(await switchedChainIdDiv.getText(), '0x1'); + assert.equal( + await accountsDiv.getText(), + '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + ); + }, + ); + }); + + it('should reject unsupported methods', async function () { + await withFixtures( + { + dapp: true, + failOnConsoleError: false, + fixtures: 'connected-state', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.openNewPage('http://127.0.0.1:8080/'); + for (const unsupportedMethod of ['eth_signTransaction']) { + assert.equal( + await driver.executeAsyncScript(` + const webDriverCallback = arguments[arguments.length - 1]; + window.ethereum.request({ method: '${unsupportedMethod}' }) + .then(() => { + console.error('The unsupported method "${unsupportedMethod}" was not rejected.'); + webDriverCallback(false); + }) + .catch((error) => { + if (error.code === ${errorCodes.rpc.methodNotSupported}) { + webDriverCallback(true); + } + + console.error( + 'The unsupported method "${unsupportedMethod}" was rejected with an unexpected error.', + error, + ); + webDriverCallback(false); + }) + `), + true, + `The unsupported method "${unsupportedMethod}" should be rejected by the provider.`, + ); + } + }, + ); + }); +}); diff --git a/test/e2e/tests/provider-events.spec.js b/test/e2e/tests/provider-events.spec.js deleted file mode 100644 index effe3e717..000000000 --- a/test/e2e/tests/provider-events.spec.js +++ /dev/null @@ -1,65 +0,0 @@ -const { strict: assert } = require('assert'); -const { withFixtures } = require('../helpers'); - -describe('MetaMask', function () { - it('provider should inform dapp when switching networks', async function () { - const ganacheOptions = { - accounts: [ - { - secretKey: - '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', - balance: 25000000000000000000, - }, - ], - }; - await withFixtures( - { - dapp: true, - fixtures: 'connected-state', - ganacheOptions, - title: this.test.title, - }, - async ({ driver }) => { - await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - await driver.openNewPage('http://127.0.0.1:8080/'); - const networkDiv = await driver.waitForSelector({ - css: '#network', - text: '1337', - }); - const chainIdDiv = await driver.waitForSelector({ - css: '#chainId', - text: '0x539', - }); - assert.equal(await networkDiv.getText(), '1337'); - assert.equal(await chainIdDiv.getText(), '0x539'); - - const windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindow(windowHandles[0]); - - await driver.clickElement('.network-display'); - await driver.clickElement({ text: 'Ropsten', tag: 'span' }); - - await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - const switchedNetworkDiv = await driver.waitForSelector({ - css: '#network', - text: '3', - }); - const switchedChainIdDiv = await driver.waitForSelector({ - css: '#chainId', - text: '0x3', - }); - const accountsDiv = await driver.findElement('#accounts'); - - assert.equal(await switchedNetworkDiv.getText(), '3'); - assert.equal(await switchedChainIdDiv.getText(), '0x3'); - assert.equal( - await accountsDiv.getText(), - '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - ); - }, - ); - }); -}); diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index ad8430794..56065ea7f 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -29,6 +29,10 @@ function wrapElementWithAPI(element, driver) { return element; } +/** + * For Selenium WebDriver API documentation, see: + * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html + */ class Driver { /** * @param {!ThenableWebDriver} driver - A {@code WebDriver} instance @@ -49,6 +53,14 @@ class Driver { }; } + async executeAsyncScript(script, ...args) { + return this.driver.executeAsyncScript(script, args); + } + + async executeScript(script, ...args) { + return this.driver.executeScript(script, args); + } + buildLocator(locator) { if (typeof locator === 'string') { // If locator is a string we assume its a css selector @@ -388,6 +400,7 @@ function collectMetrics() { } Driver.PAGES = { + BACKGROUND: 'background', HOME: 'home', NOTIFICATION: 'notification', POPUP: 'popup', diff --git a/test/helpers/protect-intrinsics-helpers.js b/test/helpers/protect-intrinsics-helpers.js new file mode 100644 index 000000000..d3e046e08 --- /dev/null +++ b/test/helpers/protect-intrinsics-helpers.js @@ -0,0 +1,81 @@ +const { strict: assert } = require('assert'); + +module.exports = { + getGlobalProperties, + testIntrinsic, +}; + +/** + * Gets the global intrinsic property names in a locked down environemnt. + * + * @returns {Set} All global intrinsic property names. + */ +function getGlobalProperties() { + // These are Agoric inventions, and we don't care about them. + const ignoreList = new Set([ + 'Compartment', + 'HandledPromise', + 'StaticModuleRecord', + ]); + + const namedIntrinsics = Reflect.ownKeys(new Compartment().globalThis); + + return new Set( + [ + // Added to global scope by ses/dist/lockdown.cjs. + ...namedIntrinsics, + + // TODO: Also include the named platform globals + // This grabs every enumerable property on globalThis. + // ...Object.keys(globalThis), + ].filter((propertyName) => !ignoreList.has(propertyName)), + ); +} + +/** + * Performs a number of assertions on the specified intrinsic property to + * ensure that the environment is locked down properly. + * Throws if any assertion fails. + * + * @param {string} propertyName - The name of the intrinsic property to test. + */ +function testIntrinsic(propertyName) { + const descriptor = Reflect.getOwnPropertyDescriptor(globalThis, propertyName); + + assert.ok( + descriptor, + `globalThis["${propertyName}"] should have a descriptor`, + ); + + // As long as Object.isFrozen is the true Object.isFrozen, the object + // it is called with cannot lie about being frozen. + const value = globalThis[propertyName]; + if (value !== globalThis) { + assert.equal( + Object.isFrozen(value), + true, + `value of universal property globalThis["${propertyName}"] should be frozen`, + ); + } + + // The writability of properties with accessors cannot be modified. + if ('set' in descriptor || 'get' in descriptor) { + assert.equal( + descriptor.configurable, + false, + `globalThis["${propertyName}"] should be non-configurable`, + ); + } else { + assert.equal( + descriptor.configurable, + false, + `globalThis["${propertyName}"] should be non-configurable`, + ); + + assert.equal( + descriptor.writable, + false, + `globalThis["${propertyName}"] should be non-writable`, + ); + } +} diff --git a/test/stub/provider.js b/test/stub/provider.js index 7f84e795f..da618f4c7 100644 --- a/test/stub/provider.js +++ b/test/stub/provider.js @@ -1,6 +1,5 @@ -import { JsonRpcEngine } from 'json-rpc-engine'; -import scaffoldMiddleware from 'eth-json-rpc-middleware/scaffold'; -import providerAsMiddleware from 'eth-json-rpc-middleware/providerAsMiddleware'; +import { JsonRpcEngine, createScaffoldMiddleware } from 'json-rpc-engine'; +import { providerAsMiddleware } from 'eth-json-rpc-middleware'; import GanacheCore from 'ganache-core'; export function getTestSeed() { @@ -45,7 +44,7 @@ export function providerFromEngine(engine) { export function createTestProviderTools(opts = {}) { const engine = createEngineForTestData(); // handle provided hooks - engine.push(scaffoldMiddleware(opts.scaffold || {})); + engine.push(createScaffoldMiddleware(opts.scaffold || {})); // handle block tracker methods engine.push( providerAsMiddleware( diff --git a/test/unit-global/protect-intrinsics.test.js b/test/unit-global/protect-intrinsics.test.js index 3958d7762..c3cd02a29 100644 --- a/test/unit-global/protect-intrinsics.test.js +++ b/test/unit-global/protect-intrinsics.test.js @@ -1,72 +1,15 @@ import 'ses/lockdown'; import '../../app/scripts/lockdown-run'; import '../../app/scripts/lockdown-more'; -import { strict as assert } from 'assert'; - -// These are Agoric inventions, and we don't care about them. -const ignoreList = new Set([ - 'Compartment', - 'HandledPromise', - 'StaticModuleRecord', -]); +import { + getGlobalProperties, + testIntrinsic, +} from '../helpers/protect-intrinsics-helpers'; describe('non-modifiable intrinsics', function () { - const namedIntrinsics = Reflect.ownKeys(new Compartment().globalThis); - - const globalProperties = new Set( - [ - // Added to global scope by ses/dist/lockdown.cjs. - ...namedIntrinsics, - - // TODO: Also include the named platform globals - // This grabs every enumerable property on globalThis. - // ...Object.keys(globalThis), - ].filter((propertyName) => !ignoreList.has(propertyName)), - ); - - globalProperties.forEach((propertyName) => { + getGlobalProperties().forEach((propertyName) => { it(`intrinsic globalThis["${propertyName}"]`, function () { - const descriptor = Reflect.getOwnPropertyDescriptor( - globalThis, - propertyName, - ); - - assert.ok( - descriptor, - `globalThis["${propertyName}"] should have a descriptor`, - ); - - // As long as Object.isFrozen is the true Object.isFrozen, the object - // it is called with cannot lie about being frozen. - const value = globalThis[propertyName]; - if (value !== globalThis) { - assert.equal( - Object.isFrozen(value), - true, - `value of universal property globalThis["${propertyName}"] should be frozen`, - ); - } - - // The writability of properties with accessors cannot be modified. - if ('set' in descriptor || 'get' in descriptor) { - assert.equal( - descriptor.configurable, - false, - `globalThis["${propertyName}"] should be non-configurable`, - ); - } else { - assert.equal( - descriptor.configurable, - false, - `globalThis["${propertyName}"] should be non-configurable`, - ); - - assert.equal( - descriptor.writable, - false, - `globalThis["${propertyName}"] should be non-writable`, - ); - } + testIntrinsic(propertyName); }); }); }); diff --git a/ui/1.INTRODUCTION.stories.mdx b/ui/1.INTRODUCTION.stories.mdx new file mode 100644 index 000000000..23ba7e8f7 --- /dev/null +++ b/ui/1.INTRODUCTION.stories.mdx @@ -0,0 +1,15 @@ +import { Meta } from '@storybook/addon-docs'; + + + +# Introduction + +Welcome to the MetaMask Browser Extension Storybook. + +## Building locally and Contributing + +This document is currently only specific to storybook best practices and component documentation guidelines. This may change in future but for now if you are looking to get a local build of Metamask up and running or contribute to the extension codebase please read the Metamask [README.md](https://github.com/MetaMask/metamask-extension) + +## Documentation Guidelines + +If you're here to improve our storybook documentation you're in the right place! Please head over to the [Documentation Guidelines](./?path=/story/getting-started-documentation-guidelines--page) for contributing to component documentation in storybook. diff --git a/ui/2.DOCUMENTATION.stories.mdx b/ui/2.DOCUMENTATION.stories.mdx new file mode 100644 index 000000000..056edb647 --- /dev/null +++ b/ui/2.DOCUMENTATION.stories.mdx @@ -0,0 +1,182 @@ +import { Meta } from '@storybook/addon-docs'; + + + +# Documentation Guidelines + +> 💡 To improve the quality of our component documentation we are currently in the process of updating our storybook to use Storyboook's [controls](https://storybook.js.org/addons/@storybook/addon-controls/), [a11y](https://storybook.js.org/addons/@storybook/addon-a11y/) and [docs](https://storybook.js.org/addons/@storybook/addon-docs/) plugins. You will find most components currently without documentation and use knobs for their primary interactivity. These will eventually be updated. + +## General Guidelines + +Thorough documentation makes it much easier for a component to be found, adapted and reused. It also provides space for explanation and reasoning for a component. This is useful as components become more complex. + +Some general documentation best practices to follow: + +- Put yourself in the shoes of another developer trying to use the component you just created for the first time +- Write a brief description of the component and what it's used for in the `README.mdx` file +- Display the component's api using the `` component from storybook docs +- Use the [controls](https://storybook.js.org/addons/@storybook/addon-controls/) api over knobs +- Use the updated `argType` [actions](https://storybook.js.org/docs/react/essentials/actions) api over importing the actions plugin directly +- Check the accessibility of the component using the **Accessibility** tab + +See the [Button](https://metamask.github.io/metamask-storybook/index.html?path=/story/ui-components-ui-button-button-stories-js--default-story)(`ui/components/ui/button/button.stories.js`) component for reference + +## Creating a Story + +[Component Story Format (CSF)](https://storybook.js.org/docs/react/api/csf) is the recommended way to write stories. It's an open standard based on ES6 modules. The below example is of the Button component and it explains how we should write our stories. + +```jsx +// Button component example story + +import React from 'react'; + +import BuyIcon from '../icon/overview-buy-icon.component'; + +// The mdx file to document component api and usage +import README from './README.mdx'; +import Button from '.'; + +// The default storybook component export should always follow the same template +export default { + title: 'Button', // The `title` effects the components tile and location in storybook + id: __filename, // The file name id is used to track what storybook files have changed in CI + component: Button, // The component you are documenting + parameters: { + docs: { + page: README, // Reference to the mdx file docs page + }, + }, + // the controls plugin argTypes are used for the interactivity of the component + argTypes: { + children: { control: 'text' }, + disabled: { control: 'boolean' }, + // use the updated action api to log actions in the actions tab + onClick: { action: 'clicked' }, + type: { + control: { + type: 'select', + }, + options: [ + 'default', + 'primary', + 'secondary', + 'warning', + 'danger', + 'danger-primary', + 'link', + ], + }, + submit: { control: 'boolean' }, + large: { control: 'boolean' }, + className: { control: 'text' }, + icon: { + control: { + type: 'select', + }, + options: ['BuyIcon'], + mapping: { + BuyIcon: , + }, + }, + }, +}; + +// First story component should always be called "DefaultStory" +// The `DefaultStory` should include argTypes and controls where appropriate +export const DefaultStory = (args) => ( + +); + +// The name of the DefaultStory component can be renamed to simply "Default" +DefaultStory.storyName = 'Default'; + +// More stories should be added for different usage examples +// You can add as many stories as you think appropriate to comprehensively document the component +export const Types = (args) => ( + <> + + + + + + + + +); +``` + +## Writing MDX Documentation + +Now the storybook components are complete, the `README.mdx` documentation should explain the component in detail. [MDX](https://mdxjs.com/) format lets you seamlessly use `JSX` in your markdown documents. You can import react components and stories into your documentation to enhance the developer experience. + +```md + + +import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; + + + +import Button from '.'; + + + +# Button + + + +Buttons communicate actions that users can take. + + + + + + + +## Component API + + + + + +## Usage + + + +The following describes the props and example usage for this component. + +### Type + +By changing the `type` prop you can use different styles of the button. + +| type | Description | +| ----------------- | ----------------------------------------------------------------------------------------------------- | +| `default` | default style of the button | +| `primary` | the principle call to action on the page | +| `secondary` | secondary actions on each page | +| `warning` | a warning action in the page | +| `danger` | a negative action (such as Delete) on the page | +| `danger--primary` | a negative principle call to action (such as Delete) on the page | +| `link` | an optional action or navigation action out of the app changes root html tag from ` + + + + {t('missingNFT')} + + + + + )} + + ); +} + +CollectiblesList.propTypes = { + onAddNFT: PropTypes.func.isRequired, +}; diff --git a/ui/components/app/collectibles-list/index.js b/ui/components/app/collectibles-list/index.js new file mode 100644 index 000000000..ab3fb9b56 --- /dev/null +++ b/ui/components/app/collectibles-list/index.js @@ -0,0 +1 @@ +export { default } from './collectibles-list.component'; diff --git a/ui/components/app/confirm-page-container/confirm-page-container-container.test.js b/ui/components/app/confirm-page-container/confirm-page-container-container.test.js index 9ddf77de8..fc0a5f669 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-container.test.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-container.test.js @@ -10,6 +10,14 @@ import ConfirmPageContainer, { ConfirmPageContainerNavigation, } from '.'; +jest.mock('../../../store/actions', () => ({ + disconnectGasFeeEstimatePoller: jest.fn(), + getGasFeeEstimatesAndStartPolling: jest + .fn() + .mockImplementation(() => Promise.resolve()), + addPollingTokenToAppState: jest.fn(), +})); + describe('Confirm Page Container Container Test', () => { let wrapper; @@ -31,6 +39,8 @@ describe('Confirm Page Container Container Test', () => { selectedAddress: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', addressBook: [], chainId: 'test', + identities: [], + featureFlags: {}, }, }; diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index 509e4ebee..574967420 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -35,6 +35,7 @@ export default class ConfirmPageContainerContent extends Component { disabled: PropTypes.bool, unapprovedTxCount: PropTypes.number, rejectNText: PropTypes.string, + hideTitle: PropTypes.boolean, }; renderContent() { @@ -89,6 +90,7 @@ export default class ConfirmPageContainerContent extends Component { rejectNText, origin, ethGasPriceWarning, + hideTitle, } = this.props; return ( @@ -110,6 +112,7 @@ export default class ConfirmPageContainerContent extends Component { identiconAddress={identiconAddress} nonce={nonce} origin={origin} + hideTitle={hideTitle} /> {this.renderContent()} {(errorKey || errorMessage) && ( diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js index 25ab144a0..1ae028115 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js @@ -1,3 +1,4 @@ +/* eslint-disable no-negated-condition */ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; @@ -14,6 +15,7 @@ const ConfirmPageContainerSummary = (props) => { identiconAddress, nonce, origin, + hideTitle, } = props; return ( @@ -37,9 +39,11 @@ const ConfirmPageContainerSummary = (props) => { address={identiconAddress} /> )} -
- {titleComponent || title} -
+ {!hideTitle ? ( +
+ {titleComponent || title} +
+ ) : null} {hideSubtitle || (
@@ -60,6 +64,7 @@ ConfirmPageContainerSummary.propTypes = { identiconAddress: PropTypes.string, nonce: PropTypes.string, origin: PropTypes.string.isRequired, + hideTitle: PropTypes.boolean, }; export default ConfirmPageContainerSummary; diff --git a/ui/components/app/confirm-page-container/confirm-page-container.component.js b/ui/components/app/confirm-page-container/confirm-page-container.component.js index 397fc7ff0..117318e3d 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container.component.js @@ -4,6 +4,9 @@ import SenderToRecipient from '../../ui/sender-to-recipient'; import { PageContainerFooter } from '../../ui/page-container'; import EditGasPopover from '../edit-gas-popover'; import { EDIT_GAS_MODES } from '../../../../shared/constants/gas'; +import { GasFeeContextProvider } from '../../../contexts/gasFee'; +import ErrorMessage from '../../ui/error-message'; +import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction'; import Dialog from '../../ui/dialog'; import { ConfirmPageContainerHeader, @@ -124,97 +127,113 @@ export default class ConfirmPageContainer extends Component { const showAddToAddressDialog = !contact.name && toAddress && !isOwnedAccount && !hideSenderToRecipient; + const shouldDisplayWarning = + contentComponent && disabled && (errorKey || errorMessage); + + const hideTitle = + (currentTransaction.type === TRANSACTION_TYPES.CONTRACT_INTERACTION || + currentTransaction.type === TRANSACTION_TYPES.DEPLOY_CONTRACT) && + currentTransaction.txParams?.value === '0x0'; + return ( -
- onNextTx(txId)} - firstTx={firstTx} - lastTx={lastTx} - ofText={ofText} - requestsWaitingText={requestsWaitingText} - /> - onEdit()} - showAccountInHeader={showAccountInHeader} - accountAddress={fromAddress} - > - {hideSenderToRecipient ? null : ( - +
+ onNextTx(txId)} + firstTx={firstTx} + lastTx={lastTx} + ofText={ofText} + requestsWaitingText={requestsWaitingText} + /> + onEdit()} + showAccountInHeader={showAccountInHeader} + accountAddress={fromAddress} + > + {hideSenderToRecipient ? null : ( + + )} + +
+ {showAddToAddressDialog && ( + showAddToAddressBookModal()} + > + {this.context.t('newAccountDetectedDialogMessage')} + + )} +
+ {contentComponent || ( + )} - -
- {contentComponent || ( - - )} - {contentComponent && ( - - {unapprovedTxCount > 1 && ( - - {this.context.t('rejectTxsN', [unapprovedTxCount])} - - )} - - )} - {editingGas && ( - - )} -
+ ); } } diff --git a/ui/components/app/dropdowns/network-dropdown.js b/ui/components/app/dropdowns/network-dropdown.js index 66e44bd49..4dd785874 100644 --- a/ui/components/app/dropdowns/network-dropdown.js +++ b/ui/components/app/dropdowns/network-dropdown.js @@ -3,19 +3,22 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { compose } from 'redux'; +import classnames from 'classnames'; +import Button from '../../ui/button'; import * as actions from '../../../store/actions'; import { openAlert as displayInvalidCustomNetworkAlert } from '../../../ducks/alerts/invalid-custom-network'; -import { - NETWORKS_ROUTE, - NETWORKS_FORM_ROUTE, -} from '../../../helpers/constants/routes'; -import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../shared/constants/app'; import { NETWORK_TYPE_RPC } from '../../../../shared/constants/network'; import { isPrefixedFormattedHexString } from '../../../../shared/modules/network.utils'; -import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import ColorIndicator from '../../ui/color-indicator'; import { COLORS, SIZES } from '../../../helpers/constants/design-system'; +import { getShowTestNetworks } from '../../../selectors'; +import { getEnvironmentType } from '../../../../app/scripts/lib/util'; +import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; +import { + ADD_NETWORK_ROUTE, + ADVANCED_ROUTE, +} from '../../../helpers/constants/routes'; import { Dropdown, DropdownMenuItem } from './dropdown'; // classes from nodes of the toggle element. @@ -37,8 +40,10 @@ const DROP_DOWN_MENU_ITEM_STYLE = { function mapStateToProps(state) { return { provider: state.metamask.provider, + shouldShowTestNetworks: getShowTestNetworks(state), frequentRpcListDetail: state.metamask.frequentRpcListDetail || [], networkDropdownOpen: state.appState.networkDropdownOpen, + showTestnetMessageInDropdown: state.appState.showTestnetMessageInDropdown, }; } @@ -51,12 +56,6 @@ function mapDispatchToProps(dispatch) { dispatch(actions.setRpcTarget(target, chainId, ticker, nickname)); }, hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), - setNetworksTabAddMode: (isInAddMode) => { - dispatch(actions.setNetworksTabAddMode(isInAddMode)); - }, - setSelectedSettingsRpcUrl: (url) => { - dispatch(actions.setSelectedSettingsRpcUrl(url)); - }, displayInvalidCustomNetworkAlert: (networkName) => { dispatch(displayInvalidCustomNetworkAlert(networkName)); }, @@ -69,6 +68,7 @@ function mapDispatchToProps(dispatch) { }), ); }, + hideTestNetMessage: () => dispatch(actions.hideTestNetMessage()), }; } @@ -88,13 +88,14 @@ class NetworkDropdown extends Component { setProviderType: PropTypes.func.isRequired, setRpcTarget: PropTypes.func.isRequired, hideNetworkDropdown: PropTypes.func.isRequired, - setNetworksTabAddMode: PropTypes.func.isRequired, - setSelectedSettingsRpcUrl: PropTypes.func.isRequired, frequentRpcListDetail: PropTypes.array.isRequired, + shouldShowTestNetworks: PropTypes.bool, networkDropdownOpen: PropTypes.bool.isRequired, - history: PropTypes.object.isRequired, displayInvalidCustomNetworkAlert: PropTypes.func.isRequired, showConfirmDeleteNetworkModal: PropTypes.func.isRequired, + showTestnetMessageInDropdown: PropTypes.bool.isRequired, + hideTestNetMessage: PropTypes.func.isRequired, + history: PropTypes.object, }; handleClick(newProviderType) { @@ -118,6 +119,36 @@ class NetworkDropdown extends Component { setProviderType(newProviderType); } + renderAddCustomButton() { + const style = { + width: '100%', + left: '40px', + color: 'white', + background: 'rgba(0, 0, 0, 0.75)', + borderRadius: '20px', + textTransform: 'none', + }; + + return ( + + ); + } + renderCustomRpcList(rpcListDetail, provider) { const reversedRpcListDetail = rpcListDetail.slice().reverse(); @@ -195,6 +226,8 @@ class NetworkDropdown extends Component { name = this.context.t('rinkeby'); } else if (providerName === 'goerli') { name = this.context.t('goerli'); + } else if (providerName === 'localhost') { + name = this.context.t('localhost'); } else { name = provider.nickname || this.context.t('unknownNetwork'); } @@ -238,12 +271,15 @@ class NetworkDropdown extends Component { render() { const { - provider: { rpcUrl: activeNetwork }, - setNetworksTabAddMode, - setSelectedSettingsRpcUrl, + history, + hideNetworkDropdown, + shouldShowTestNetworks, + showTestnetMessageInDropdown, + hideTestNetMessage, } = this.props; const rpcListDetail = this.props.frequentRpcListDetail; const isOpen = this.props.networkDropdownOpen; + const { t } = this.context; return (
-
- {this.context.t('networks')} -
+
{t('networks')}
-
- {this.context.t('defaultNetwork')} -
+ {showTestnetMessageInDropdown ? ( +
+ {t('defaultNetwork', [ + + {shouldShowTestNetworks ? t('disable') : t('enable')} + , + { + e.preventDefault(); + hideNetworkDropdown(); + history.push(ADVANCED_ROUTE); + }} + > + {t('here')} + , + ])} +
+ ) : null}
{this.renderNetworkEntry('mainnet')} - {this.renderNetworkEntry('ropsten')} - {this.renderNetworkEntry('kovan')} - {this.renderNetworkEntry('rinkeby')} - {this.renderNetworkEntry('goerli')} {this.renderCustomRpcList(rpcListDetail, this.props.provider)} - this.props.hideNetworkDropdown()} - onClick={() => { - this.props.history.push( - getEnvironmentType() === ENVIRONMENT_TYPE_FULLSCREEN - ? NETWORKS_ROUTE - : NETWORKS_FORM_ROUTE, - ); - setSelectedSettingsRpcUrl(''); - setNetworksTabAddMode(true); - }} - style={DROP_DOWN_MENU_ITEM_STYLE} + +
- {activeNetwork === 'custom' ? ( - - ) : ( -
- )} - - - {this.context.t('customRPC')} - - + {this.renderNetworkEntry('ropsten')} + {this.renderNetworkEntry('kovan')} + {this.renderNetworkEntry('rinkeby')} + {this.renderNetworkEntry('goerli')} + {this.renderNetworkEntry('localhost')} +
+ + {this.renderAddCustomButton()} ); } diff --git a/ui/components/app/dropdowns/network-dropdown.test.js b/ui/components/app/dropdowns/network-dropdown.test.js index f68fddd93..263460b85 100644 --- a/ui/components/app/dropdowns/network-dropdown.test.js +++ b/ui/components/app/dropdowns/network-dropdown.test.js @@ -1,6 +1,7 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; +import Button from '../../ui/button'; import { mountWithRouter } from '../../../../test/lib/render-helpers'; import ColorIndicator from '../../ui/color-indicator'; import NetworkDropdown from './network-dropdown'; @@ -17,6 +18,9 @@ describe('Network Dropdown', () => { provider: { type: 'test', }, + preferences: { + showTestNetworks: true, + }, }, appState: { networkDropdownOpen: false, @@ -38,13 +42,16 @@ describe('Network Dropdown', () => { }); }); - describe('NetworkDropdown in appState is true', () => { + describe('NetworkDropdown in appState is true and show test networks is true', () => { const mockState = { metamask: { network: '1', provider: { type: 'test', }, + preferences: { + showTestNetworks: true, + }, frequentRpcListDetail: [ { chainId: '0x1a', rpcUrl: 'http://localhost:7545' }, { rpcUrl: 'http://localhost:7546' }, @@ -54,14 +61,32 @@ describe('Network Dropdown', () => { networkDropdownOpen: true, }, }; + + global.platform = { + openExtensionInBrowser: jest.fn(), + }; + const store = createMockStore(mockState); - beforeEach(() => { - wrapper = mountWithRouter(); - }); + let testNetworkIndex = 1; + + const findTestNetworkFirstIndex = (_wrapper) => { + let i = 1; + let found = false; + while (!found) { + if (_wrapper.find(ColorIndicator).at(i).prop('color') === 'ui-2') { + i += 1; + } else { + found = true; + } + } - it('renders 8 DropDownMenuItems', () => { - expect(wrapper.find(DropdownMenuItem)).toHaveLength(8); + testNetworkIndex = i; + }; + + beforeAll(() => { + wrapper = mountWithRouter(); + findTestNetworkFirstIndex(wrapper); }); it('checks background color for first ColorIndicator', () => { @@ -71,41 +96,99 @@ describe('Network Dropdown', () => { }); it('checks background color for second ColorIndicator', () => { - const colorIndicator = wrapper.find(ColorIndicator).at(1); + // find where test networks start in case there are custom RPCs + const colorIndicator = wrapper.find(ColorIndicator).at(testNetworkIndex); expect(colorIndicator.prop('color')).toStrictEqual('ropsten'); expect(colorIndicator.prop('borderColor')).toStrictEqual('ropsten'); }); it('checks background color for third ColorIndicator', () => { - const colorIndicator = wrapper.find(ColorIndicator).at(2); + const colorIndicator = wrapper + .find(ColorIndicator) + .at(testNetworkIndex + 1); expect(colorIndicator.prop('color')).toStrictEqual('kovan'); expect(colorIndicator.prop('borderColor')).toStrictEqual('kovan'); }); it('checks background color for fourth ColorIndicator', () => { - const colorIndicator = wrapper.find(ColorIndicator).at(3); + const colorIndicator = wrapper + .find(ColorIndicator) + .at(testNetworkIndex + 2); expect(colorIndicator.prop('color')).toStrictEqual('rinkeby'); expect(colorIndicator.prop('borderColor')).toStrictEqual('rinkeby'); }); it('checks background color for fifth ColorIndicator', () => { - const colorIndicator = wrapper.find(ColorIndicator).at(4); + const colorIndicator = wrapper + .find(ColorIndicator) + .at(testNetworkIndex + 3); expect(colorIndicator.prop('color')).toStrictEqual('goerli'); expect(colorIndicator.prop('borderColor')).toStrictEqual('goerli'); }); it('checks background color for sixth ColorIndicator', () => { - const colorIndicator = wrapper.find(ColorIndicator).at(5); - const customNetworkGray = 'ui-2'; - expect(colorIndicator.prop('color')).toStrictEqual(customNetworkGray); - expect(colorIndicator.prop('borderColor')).toStrictEqual( - customNetworkGray, + const colorIndicator = wrapper + .find(ColorIndicator) + .at(testNetworkIndex + 4); + expect(colorIndicator.prop('color')).toStrictEqual('localhost'); + expect(colorIndicator.prop('borderColor')).toStrictEqual('localhost'); + expect( + wrapper + .find(DropdownMenuItem) + .at(testNetworkIndex + 4) + .text(), + ).toStrictEqual('✓localhost'); + }); + + it('checks that Add Network button is rendered', () => { + expect(wrapper.find(Button).at(0).children().text()).toStrictEqual( + 'addNetwork', + ); + }); + }); + + describe('NetworkDropdown in appState is true and show test networks is false', () => { + const mockState = { + metamask: { + network: '1', + provider: { + type: 'test', + }, + preferences: { + showTestNetworks: false, + }, + frequentRpcListDetail: [ + { chainId: '0x1a', rpcUrl: 'http://localhost:7545' }, + { rpcUrl: 'http://localhost:7546' }, + ], + }, + appState: { + networkDropdownOpen: true, + }, + }; + + global.platform = { + openExtensionInBrowser: jest.fn(), + }; + + const store = createMockStore(mockState); + + beforeAll(() => { + wrapper = mountWithRouter(); + }); + + it('checks background color for first ColorIndicator', () => { + const colorIndicator = wrapper.find(ColorIndicator).at(0); + expect(colorIndicator.prop('color')).toStrictEqual('mainnet'); + expect(colorIndicator.prop('borderColor')).toStrictEqual('mainnet'); + expect(wrapper.find(DropdownMenuItem).at(0).text()).toStrictEqual( + '✓mainnet', ); }); - it('checks dropdown for frequestRPCList from state', () => { - expect(wrapper.find(DropdownMenuItem).at(6).text()).toStrictEqual( - '✓http://localhost:7545', + it('checks that Add Network button is rendered', () => { + expect(wrapper.find(Button).at(0).children().text()).toStrictEqual( + 'addNetwork', ); }); }); diff --git a/ui/components/app/edit-gas-display/edit-gas-display.component.js b/ui/components/app/edit-gas-display/edit-gas-display.component.js index f95f6ebc7..99e7e17f6 100644 --- a/ui/components/app/edit-gas-display/edit-gas-display.component.js +++ b/ui/components/app/edit-gas-display/edit-gas-display.component.js @@ -6,6 +6,7 @@ import { GAS_RECOMMENDATIONS, EDIT_GAS_MODES, GAS_ESTIMATE_TYPES, + CUSTOM_GAS_ESTIMATE, } from '../../../../shared/constants/gas'; import Button from '../../ui/button'; @@ -34,6 +35,8 @@ import ActionableMessage from '../../ui/actionable-message/actionable-message'; import { I18nContext } from '../../../contexts/i18n'; import GasTiming from '../gas-timing'; +import { useMetricEvent } from '../../../hooks/useMetricEvent'; + export default function EditGasDisplay({ mode = EDIT_GAS_MODES.MODIFY_IN_PLACE, showEducationButton = false, @@ -82,7 +85,7 @@ export default function EditGasDisplay({ ); const [showAdvancedForm, setShowAdvancedForm] = useState( - !estimateToUse || estimateToUse === 'custom' || !supportsEIP1559, + !estimateToUse || estimateToUse === CUSTOM_GAS_ESTIMATE || !supportsEIP1559, ); const [hideRadioButtons, setHideRadioButtons] = useState( showAdvancedInlineGasIfPossible, @@ -119,6 +122,14 @@ export default function EditGasDisplay({ errorKey = 'gasEstimatesUnavailableWarning'; } + const clickedAdvancedOptionsMetricsEvent = useMetricEvent({ + eventOpts: { + category: 'Transactions', + action: 'Edit Screen', + name: 'Clicked "Advanced Options"', + }, + }); + return (
@@ -250,7 +261,10 @@ export default function EditGasDisplay({ !showAdvancedInlineGasIfPossible && ( {exportPrivateKeyFeatureEnabled ? ( diff --git a/ui/components/app/modals/account-details-modal/index.scss b/ui/components/app/modals/account-details-modal/index.scss index ecab51311..3eedab80c 100644 --- a/ui/components/app/modals/account-details-modal/index.scss +++ b/ui/components/app/modals/account-details-modal/index.scss @@ -8,13 +8,13 @@ & &__button { margin-top: 17px; padding: 10px 22px; - width: 286px; + width: 284px; } &__divider { width: 100%; height: 1px; - margin: 19px 0 8px 0; + margin: 16px 0 8px 0; background-color: $alto; } diff --git a/ui/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/components/app/modals/edit-approval-permission/edit-approval-permission.component.js index b6cfec569..373d2c1d3 100644 --- a/ui/components/app/modals/edit-approval-permission/edit-approval-permission.component.js +++ b/ui/components/app/modals/edit-approval-permission/edit-approval-permission.component.js @@ -103,11 +103,11 @@ export default class EditApprovalPermission extends PureComponent { 'edit-approval-permission__edit-section__option-label--selected': selectedOptionIsUnlimited, })} > - {new BigNumber(tokenAmount).lessThan( - new BigNumber(tokenBalance), + {new BigNumber(tokenAmount).equals( + new BigNumber(MAX_UNSIGNED_256_INT), ) - ? t('proposedApprovalLimit') - : t('unlimited')} + ? t('unlimited') + : t('proposedApprovalLimit')}
{t('spendLimitRequestedBy', [origin])} diff --git a/ui/components/app/modals/modal.js b/ui/components/app/modals/modal.js index 16a5d4807..c31cba238 100644 --- a/ui/components/app/modals/modal.js +++ b/ui/components/app/modals/modal.js @@ -64,7 +64,7 @@ const accountModalStyle = { margin: '0 auto', }, laptopModalStyle: { - width: '360px', + width: '335px', // top: 'calc(33% + 45px)', boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', borderRadius: '4px', diff --git a/ui/components/app/network-display/index.scss b/ui/components/app/network-display/index.scss index 51ce5bcdc..9e0a0df9b 100644 --- a/ui/components/app/network-display/index.scss +++ b/ui/components/app/network-display/index.scss @@ -35,6 +35,10 @@ background-color: lighten($dodger-blue, 35%); } + &--localhost { + background-color: lighten($blue-lagoon, 68%); + } + &.chip { margin: 0; max-width: 100%; diff --git a/ui/components/app/tab-bar/index.scss b/ui/components/app/tab-bar/index.scss index c6bac6edc..6160f64c2 100644 --- a/ui/components/app/tab-bar/index.scss +++ b/ui/components/app/tab-bar/index.scss @@ -61,9 +61,8 @@ @media screen and (max-width: $break-small) { display: block; background-image: url('/images/caret-right.svg'); - width: 36px; - height: 36px; - opacity: 0.5; + width: 8.27px; + height: 13.64px; background-size: contain; background-repeat: no-repeat; background-position: center; diff --git a/ui/components/app/transaction-breakdown/transaction-breakdown.test.js b/ui/components/app/transaction-breakdown/transaction-breakdown.test.js index 6fec2e3ca..f1f9645eb 100644 --- a/ui/components/app/transaction-breakdown/transaction-breakdown.test.js +++ b/ui/components/app/transaction-breakdown/transaction-breakdown.test.js @@ -51,7 +51,7 @@ describe('TransactionBreakdown', () => { ['Amount', '-0.01 ETH'], ['Gas Limit (units)', '46890'], ['Gas price', '2.467043803'], - ['Total', '0.010116ETH'], + ['Total', '0.01011568ETH'], ]); }); }); @@ -90,7 +90,7 @@ describe('TransactionBreakdown', () => { ['Priority Fee (GWEI)', '2.467043796'], ['Total Gas Fee', '0.000077ETH'], ['Max Fee Per Gas', '0.000000003ETH'], - ['Total', '0.010077ETH'], + ['Total', '0.01007712ETH'], ]); }); }); diff --git a/ui/components/app/transaction-detail-item/index.scss b/ui/components/app/transaction-detail-item/index.scss index a2dc8e4cd..8d6c1bcd7 100644 --- a/ui/components/app/transaction-detail-item/index.scss +++ b/ui/components/app/transaction-detail-item/index.scss @@ -1,5 +1,7 @@ .transaction-detail-item { color: $ui-4; + padding: 20px 0; + border-bottom: 1px solid $ui-3; &__row { display: flex; @@ -36,4 +38,21 @@ .currency-display-component { display: inline; } + + &:first-of-type { + padding-top: 0; + } + + &:last-of-type { + margin-bottom: 20px; + } + + &:first-child { + padding-top: 0; + } + + &:last-child { + padding-bottom: 0; + border-bottom: 0; + } } diff --git a/ui/components/app/transaction-detail/index.scss b/ui/components/app/transaction-detail/index.scss index 13c2601c8..35bcf27c9 100644 --- a/ui/components/app/transaction-detail/index.scss +++ b/ui/components/app/transaction-detail/index.scss @@ -16,21 +16,54 @@ } } - &-rows &-item:first-child { - padding-top: 0; - } + &-edit-V2 { + margin-bottom: 10px; + display: flex; + align-items: baseline; + justify-content: flex-end; + padding-top: 20px; + + button { + @include H7; + + display: flex; + align-items: baseline; + color: $primary-1; + background: transparent; + border: 0; + padding-inline-end: 0; + white-space: pre; + } - &-item { - padding: 20px 0; - border-bottom: 1px solid $ui-3; + i { + color: $primary-1; + margin-right: 2px; + } - &:first-child { - padding-top: 0; + &__icon { + font-size: 1rem; } - &:last-child { - padding-bottom: 0; - border-bottom: 0; + &__label { + font-size: 12px; + margin-right: 8px; + } + + .info-tooltip { + align-self: center; + margin-left: 6px; + } + + &__tooltip { + p { + color: $Grey-500; + } + + b { + color: $neutral-black; + display: inline-block; + min-width: 60%; + } } } } diff --git a/ui/components/app/transaction-detail/transaction-detail.component.js b/ui/components/app/transaction-detail/transaction-detail.component.js index 0829df684..b06d43af8 100644 --- a/ui/components/app/transaction-detail/transaction-detail.component.js +++ b/ui/components/app/transaction-detail/transaction-detail.component.js @@ -2,11 +2,94 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { I18nContext } from '../../../contexts/i18n'; +import { useGasFeeContext } from '../../../contexts/gasFee'; +import InfoTooltip from '../../ui/info-tooltip/info-tooltip'; +import Typography from '../../ui/typography/typography'; import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component'; +import { COLORS } from '../../../helpers/constants/design-system'; + +const GasLevelIconMap = { + low: '🐢', + medium: '🦊', + high: '🦍', + dappSuggested: '🌐', + custom: '⚙', +}; export default function TransactionDetail({ rows = [], onEdit }) { + // eslint-disable-next-line prefer-destructuring + const EIP_1559_V2 = process.env.EIP_1559_V2; + const t = useContext(I18nContext); + const { + estimateToUse, + gasLimit, + gasPrice, + isUsingDappSuggestedGasFees, + maxFeePerGas, + maxPriorityFeePerGas, + transaction, + supportsEIP1559, + } = useGasFeeContext(); + const estimateUsed = isUsingDappSuggestedGasFees + ? 'dappSuggested' + : estimateToUse; + + if (EIP_1559_V2 && estimateUsed) { + return ( +
+
+ + {estimateUsed === 'custom' && onEdit && ( + + )} + {estimateUsed === 'dappSuggested' && ( + + + {t('dappSuggestedTooltip', [transaction.origin])} + + {supportsEIP1559 ? ( + <> + + {t('maxBaseFee')} + {maxFeePerGas} + + + {t('maxPriorityFee')} + {maxPriorityFeePerGas} + + + ) : ( + + {t('gasPriceLabel')} + {gasPrice} + + )} + + {t('gasLimit')} + {gasLimit} + +
+ } + position="top" + /> + )} +
+
{rows}
+
+ ); + } return (
diff --git a/ui/components/app/transaction-detail/transaction-detail.component.test.js b/ui/components/app/transaction-detail/transaction-detail.component.test.js new file mode 100644 index 000000000..a95cf1565 --- /dev/null +++ b/ui/components/app/transaction-detail/transaction-detail.component.test.js @@ -0,0 +1,94 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; + +import { ETH } from '../../../helpers/constants/common'; +import { GasFeeContextProvider } from '../../../contexts/gasFee'; +import { renderWithProvider } from '../../../../test/jest'; +import configureStore from '../../../store/store'; + +import TransactionDetail from './transaction-detail.component'; + +jest.mock('../../../store/actions', () => ({ + disconnectGasFeeEstimatePoller: jest.fn(), + getGasFeeEstimatesAndStartPolling: jest + .fn() + .mockImplementation(() => Promise.resolve()), + addPollingTokenToAppState: jest.fn(), +})); + +const render = (props) => { + const store = configureStore({ + metamask: { + nativeCurrency: ETH, + preferences: { + useNativeCurrencyAsPrimaryCurrency: true, + }, + provider: {}, + cachedBalances: {}, + accounts: { + '0xAddress': { + address: '0xAddress', + balance: '0x176e5b6f173ebe66', + }, + }, + selectedAddress: '0xAddress', + featureFlags: { advancedInlineGas: true }, + }, + }); + + return renderWithProvider( + + { + console.log('on edit'); + }} + rows={[]} + {...props} + /> + , + store, + ); +}; + +describe('TransactionDetail', () => { + beforeEach(() => { + process.env.EIP_1559_V2 = true; + }); + afterEach(() => { + process.env.EIP_1559_V2 = false; + }); + it('should render edit link with text low if low gas estimates are selected', () => { + render({ transaction: { userFeeLevel: 'low' } }); + expect(screen.queryByText('🐢')).toBeInTheDocument(); + expect(screen.queryByText('Low')).toBeInTheDocument(); + }); + it('should render edit link with text markey if medium gas estimates are selected', () => { + render({ transaction: { userFeeLevel: 'medium' } }); + expect(screen.queryByText('🦊')).toBeInTheDocument(); + expect(screen.queryByText('Market')).toBeInTheDocument(); + }); + it('should render edit link with text agressive if high gas estimates are selected', () => { + render({ transaction: { userFeeLevel: 'high' } }); + expect(screen.queryByText('🦍')).toBeInTheDocument(); + expect(screen.queryByText('Aggressive')).toBeInTheDocument(); + }); + it('should render edit link with text Site suggested if site suggested estimated are used', () => { + render({ + transaction: { + dappSuggestedGasFees: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 }, + txParams: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 }, + }, + }); + expect(screen.queryByText('🌐')).toBeInTheDocument(); + expect(screen.queryByText('Site suggested')).toBeInTheDocument(); + expect(document.getElementsByClassName('info-tooltip')).toHaveLength(1); + }); + it('should render edit link with text advance if custom gas estimates are used', () => { + render({ + defaultEstimateToUse: 'custom', + }); + expect(screen.queryByText('⚙')).toBeInTheDocument(); + expect(screen.queryByText('Advanced')).toBeInTheDocument(); + expect(screen.queryByText('Edit')).toBeInTheDocument(); + }); +}); diff --git a/ui/components/app/transaction-list-item-details/index.scss b/ui/components/app/transaction-list-item-details/index.scss index 27a64f2e9..a0f95f470 100644 --- a/ui/components/app/transaction-list-item-details/index.scss +++ b/ui/components/app/transaction-list-item-details/index.scss @@ -6,11 +6,6 @@ align-items: center; } - &__body { - background: #fafbfc; - padding: 8px 16px; - } - &__header-buttons { display: flex; flex-direction: row; @@ -41,6 +36,7 @@ &__cards-container { display: flex; flex-direction: column; + padding: 8px 16px; } &__transaction-breakdown { @@ -53,6 +49,5 @@ &__transaction-activity-log { flex: 2; min-width: 0; - padding-left: 12px; } } diff --git a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js index a940cfcaa..335859aa3 100644 --- a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import copyToClipboard from 'copy-to-clipboard'; import { getBlockExplorerLink } from '@metamask/etherscan-link'; import SenderToRecipient from '../../ui/sender-to-recipient'; -import { FLAT_VARIANT } from '../../ui/sender-to-recipient/sender-to-recipient.constants'; +import { DEFAULT_VARIANT } from '../../ui/sender-to-recipient/sender-to-recipient.constants'; import TransactionActivityLog from '../transaction-activity-log'; import TransactionBreakdown from '../transaction-breakdown'; import Button from '../../ui/button'; @@ -215,7 +215,7 @@ export default class TransactionListItemDetails extends PureComponent {
{ @@ -55,7 +56,7 @@ const EthOverview = ({ className }) => { }); const history = useHistory(); const keyring = useSelector(getCurrentKeyring); - const usingHardwareWallet = keyring.type.search('Hardware') !== -1; + const usingHardwareWallet = isHardwareKeyring(keyring.type); const balanceIsCached = useSelector(isBalanceCached); const showFiat = useSelector(getShouldShowFiat); const selectedAccount = useSelector(getSelectedAccount); diff --git a/ui/components/app/wallet-overview/token-overview.js b/ui/components/app/wallet-overview/token-overview.js index 0b5476261..1db872854 100644 --- a/ui/components/app/wallet-overview/token-overview.js +++ b/ui/components/app/wallet-overview/token-overview.js @@ -7,6 +7,7 @@ import Identicon from '../../ui/identicon'; import Tooltip from '../../ui/tooltip'; import CurrencyDisplay from '../../ui/currency-display'; import { I18nContext } from '../../../contexts/i18n'; +import { isHardwareKeyring } from '../../../helpers/utils/hardware'; import { SEND_ROUTE, BUILD_QUOTE_ROUTE, @@ -42,7 +43,7 @@ const TokenOverview = ({ className, token }) => { }); const history = useHistory(); const keyring = useSelector(getCurrentKeyring); - const usingHardwareWallet = keyring.type.search('Hardware') !== -1; + const usingHardwareWallet = isHardwareKeyring(keyring.type); const { tokensWithBalances } = useTokenTracker([token]); const balanceToRender = tokensWithBalances[0]?.string; const balance = tokensWithBalances[0]?.balance; diff --git a/ui/components/ui/actionable-message/actionable-message.js b/ui/components/ui/actionable-message/actionable-message.js index 746a9d2a4..8af6646c7 100644 --- a/ui/components/ui/actionable-message/actionable-message.js +++ b/ui/components/ui/actionable-message/actionable-message.js @@ -6,11 +6,13 @@ import InfoTooltipIcon from '../info-tooltip/info-tooltip-icon'; const CLASSNAME_WARNING = 'actionable-message--warning'; const CLASSNAME_DANGER = 'actionable-message--danger'; +const CLASSNAME_INFO = 'actionable-message--info'; const CLASSNAME_WITH_RIGHT_BUTTON = 'actionable-message--with-right-button'; const typeHash = { warning: CLASSNAME_WARNING, danger: CLASSNAME_DANGER, + info: CLASSNAME_INFO, default: '', }; diff --git a/ui/components/ui/actionable-message/index.scss b/ui/components/ui/actionable-message/index.scss index cbb1f8ba6..942123b4e 100644 --- a/ui/components/ui/actionable-message/index.scss +++ b/ui/components/ui/actionable-message/index.scss @@ -85,6 +85,15 @@ } } + &--info { + background: $Green-000; + border: 1px solid $Green-200; + + .actionable-message__message { + color: $Black-100; + } + } + &--left-aligned { .actionable-message__message, .actionable-message__actions { diff --git a/ui/components/ui/box/box.js b/ui/components/ui/box/box.js index b720ff2fc..b9fc2f5ce 100644 --- a/ui/components/ui/box/box.js +++ b/ui/components/ui/box/box.js @@ -11,6 +11,7 @@ import { SIZES, TEXT_ALIGN, FLEX_DIRECTION, + FLEX_WRAP, } from '../../../helpers/constants/design-system'; const ValidSize = PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); @@ -77,11 +78,14 @@ export default function Box({ justifyContent, textAlign, flexDirection = FLEX_DIRECTION.ROW, + flexWrap, + gap, display, width, height, children, className, + backgroundColor, }) { const boxClassName = classnames('box', className, { // ---Borders--- @@ -121,6 +125,7 @@ export default function Box({ [`box--justify-content-${justifyContent}`]: Boolean(justifyContent), [`box--align-items-${alignItems}`]: Boolean(alignItems), [`box--flex-direction-${flexDirection}`]: Boolean(flexDirection), + [`box--flex-wrap-${flexWrap}`]: Boolean(flexWrap), // text align [`box--text-align-${textAlign}`]: Boolean(textAlign), // display @@ -128,6 +133,9 @@ export default function Box({ // width & height [`box--width-${width}`]: Boolean(width), [`box--height-${height}`]: Boolean(height), + // background + [`box--background-color-${backgroundColor}`]: Boolean(backgroundColor), + ...generateSizeClasses('box', 'gap', gap), }); // Apply Box styles to any other component using function pattern if (typeof children === 'function') { @@ -139,6 +147,8 @@ export default function Box({ Box.propTypes = { children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), flexDirection: PropTypes.oneOf(Object.values(FLEX_DIRECTION)), + flexWrap: PropTypes.oneOf(Object.values(FLEX_WRAP)), + gap: ValidSize, margin: MultipleSizes, marginTop: ValidSize, marginBottom: ValidSize, @@ -159,5 +169,6 @@ Box.propTypes = { display: PropTypes.oneOf(Object.values(DISPLAY)), width: PropTypes.oneOf(Object.values(BLOCK_SIZES)), height: PropTypes.oneOf(Object.values(BLOCK_SIZES)), + backgroundColor: PropTypes.oneOf(Object.values(COLORS)), className: PropTypes.string, }; diff --git a/ui/components/ui/box/box.scss b/ui/components/ui/box/box.scss index 59865bbf7..27d146c84 100644 --- a/ui/components/ui/box/box.scss +++ b/ui/components/ui/box/box.scss @@ -2,10 +2,10 @@ @use "design-system"; @use "utilities"; -$attributes: padding, margin; +$attributes: padding, margin, gap; .box { - // Padding and Margin + // Padding, Margin, and Gap @each $attribute in $attributes { @each $size in design-system.$sizes-numeric { &--#{$attribute}-#{$size} { @@ -90,6 +90,12 @@ $attributes: padding, margin; } } + @each $wrap in design-system.$flex-wrap { + &--flex-wrap-#{$wrap} { + flex-wrap: $wrap; + } + } + // Width and Height &--width-full { width: 100%; @@ -139,4 +145,11 @@ $attributes: padding, margin; text-align: $alignment; } } + + // background + @each $variant, $color in design-system.$color-map { + &--background-color-#{$variant} { + background-color: $color; + } + } } diff --git a/ui/components/ui/box/box.stories.js b/ui/components/ui/box/box.stories.js index 25b17838b..b7168786b 100644 --- a/ui/components/ui/box/box.stories.js +++ b/ui/components/ui/box/box.stories.js @@ -75,6 +75,12 @@ export const box = () => { )} borderWidth={number('borderWidth', 1, sizeKnobOptions, 'border')} borderColor={select('borderColor', COLORS, COLORS.BLACK, 'border')} + backgroundColor={select( + 'backgroundColor', + COLORS, + COLORS.WHITE, + 'background', + )} > {items} diff --git a/ui/components/ui/button/README.mdx b/ui/components/ui/button/README.mdx new file mode 100644 index 000000000..7551beccb --- /dev/null +++ b/ui/components/ui/button/README.mdx @@ -0,0 +1,59 @@ +import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; + +import Button from '.'; + +# Button + +Buttons communicate actions that users can take. + + + + + +## Component API + + + +## Usage + +The following describes the props and example usage for this component. + +### Type + +By changing the `type` prop you can use different styles of the button. + +| type | Description | +| ----------------- | ----------------------------------------------------------------------------------------------------- | +| `default` | default style of the button | +| `primary` | the principle call to action on the page | +| `secondary` | secondary actions on each page | +| `warning` | a warning action in the page | +| `danger` | a negative action (such as Delete) on the page | +| `danger--primary` | a negative principle call to action (such as Delete) on the page | +| `link` | an optional action or navigation action out of the app changes root html tag from ` +export const DefaultStory = (args) => ( + ); -export const secondaryType = () => ( - -); +DefaultStory.storyName = 'Default'; -export const defaultType = () => ( - -); +DefaultStory.args = { + children: 'Default', +}; -export const warningType = () => ( - +export const Types = (args) => ( + <> + + + + + + + + ); -export const dangerType = () => ( - -); +export const LinkType = (args) => ); diff --git a/ui/components/ui/chip/chip-with-input.js b/ui/components/ui/chip/chip-with-input.js index c6fad92d9..8a5e30cc9 100644 --- a/ui/components/ui/chip/chip-with-input.js +++ b/ui/components/ui/chip/chip-with-input.js @@ -5,6 +5,7 @@ import { COLORS } from '../../../helpers/constants/design-system'; import Chip from '.'; export function ChipWithInput({ + dataTestId, className, borderColor = COLORS.UI1, inputValue, @@ -17,6 +18,7 @@ export function ChipWithInput({ > {setInputValue && ( { @@ -30,6 +32,7 @@ export function ChipWithInput({ } ChipWithInput.propTypes = { + dataTestId: PropTypes.string, borderColor: PropTypes.oneOf(Object.values(COLORS)), className: PropTypes.string, inputValue: PropTypes.string, diff --git a/ui/components/ui/chip/chip.js b/ui/components/ui/chip/chip.js index 6311ea72e..45157fd51 100644 --- a/ui/components/ui/chip/chip.js +++ b/ui/components/ui/chip/chip.js @@ -6,6 +6,7 @@ import Typography from '../typography'; import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system'; export default function Chip({ + dataTestId, className, children, borderColor = COLORS.UI1, @@ -25,6 +26,7 @@ export default function Chip({ return (
{ expect(wrapper.find('.unit-input__suffix').text()).toStrictEqual('USD'); expect(wrapper.find('.unit-input__input').props().value).toStrictEqual(1); expect(wrapper.find('.currency-display-component').text()).toStrictEqual( - '0.004328ETH', + '0.00432788ETH', ); }); @@ -159,14 +159,16 @@ describe('CurrencyInput Component', () => { .find(CurrencyInput) .at(0) .instance(); - expect(currencyInputInstance.state.decimalValue).toStrictEqual(0.004328); + expect(currencyInputInstance.state.decimalValue).toStrictEqual( + 0.00432788, + ); expect(currencyInputInstance.state.hexValue).toStrictEqual( 'f602f2234d0ea', ); expect(wrapper.find('.unit-input__suffix')).toHaveLength(1); expect(wrapper.find('.unit-input__suffix').text()).toStrictEqual('ETH'); expect(wrapper.find('.unit-input__input').props().value).toStrictEqual( - 0.004328, + 0.00432788, ); expect( wrapper.find('.currency-input__conversion-component').text(), @@ -274,7 +276,7 @@ describe('CurrencyInput Component', () => { expect(handleChangeSpy.callCount).toStrictEqual(1); expect(handleChangeSpy.calledWith('f602f2234d0ea')).toStrictEqual(true); expect(wrapper.find('.currency-display-component').text()).toStrictEqual( - '0.004328ETH', + '0.00432788ETH', ); expect(currencyInputInstance.state.decimalValue).toStrictEqual(1); expect(currencyInputInstance.state.hexValue).toStrictEqual( @@ -375,7 +377,7 @@ describe('CurrencyInput Component', () => { const swap = wrapper.find('.currency-input__swap-component'); swap.simulate('click'); expect(wrapper.find('.currency-display-component').text()).toStrictEqual( - '0.004328ETH', + '0.00432788ETH', ); }); }); diff --git a/ui/components/ui/form-field/form-field.js b/ui/components/ui/form-field/form-field.js index d735636ac..5cd1fd5b6 100644 --- a/ui/components/ui/form-field/form-field.js +++ b/ui/components/ui/form-field/form-field.js @@ -16,6 +16,7 @@ import NumericInput from '../numeric-input/numeric-input.component'; import InfoTooltip from '../info-tooltip/info-tooltip'; export default function FormField({ + dataTestId, titleText, titleUnit, tooltipText, @@ -94,6 +95,7 @@ export default function FormField({ type={password ? 'password' : 'text'} autoFocus={autoFocus} disabled={disabled} + data-testid={dataTestId} /> )} {error && ( @@ -111,13 +113,14 @@ export default function FormField({ } FormField.propTypes = { + dataTestId: PropTypes.string, titleText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), titleUnit: PropTypes.string, tooltipText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), titleDetail: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), error: PropTypes.string, onChange: PropTypes.func, - value: PropTypes.number, + value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), detailText: PropTypes.string, autoFocus: PropTypes.bool, numeric: PropTypes.bool, diff --git a/ui/components/ui/i18n-value/i18n-value.component.js b/ui/components/ui/i18n-value/i18n-value.component.js new file mode 100644 index 000000000..d321df7e9 --- /dev/null +++ b/ui/components/ui/i18n-value/i18n-value.component.js @@ -0,0 +1,15 @@ +import PropTypes from 'prop-types'; + +import { useI18nContext } from '../../../hooks/useI18nContext'; + +const I18nValue = ({ messageKey, options }) => { + const t = useI18nContext(); + return t(messageKey, options); +}; + +I18nValue.propTypes = { + messageKey: PropTypes.string.isRequired, + options: PropTypes.array, +}; + +export default I18nValue; diff --git a/ui/components/ui/i18n-value/index.js b/ui/components/ui/i18n-value/index.js new file mode 100644 index 000000000..190d57ab0 --- /dev/null +++ b/ui/components/ui/i18n-value/index.js @@ -0,0 +1 @@ +export { default } from './i18n-value.component'; diff --git a/ui/components/ui/metafox-logo/metafox-logo.component.js b/ui/components/ui/metafox-logo/metafox-logo.component.js index 4a3812dd5..04a626fd0 100644 --- a/ui/components/ui/metafox-logo/metafox-logo.component.js +++ b/ui/components/ui/metafox-logo/metafox-logo.component.js @@ -1,23 +1,20 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { getBuildSpecificAsset } from '../../../helpers/utils/build-types'; export default class MetaFoxLogo extends PureComponent { static propTypes = { onClick: PropTypes.func, unsetIconHeight: PropTypes.bool, - useDark: PropTypes.bool, isOnboarding: PropTypes.bool, }; static defaultProps = { onClick: undefined, - useDark: false, }; render() { - const { onClick, unsetIconHeight, useDark, isOnboarding } = this.props; + const { onClick, unsetIconHeight, isOnboarding } = this.props; const iconProps = unsetIconHeight ? {} : { height: 42, width: 42 }; return ( @@ -31,11 +28,7 @@ export default class MetaFoxLogo extends PureComponent { > -
{toChecksumHexAddress(data)}
+ +
{ + handleCopy(toChecksumHexAddress(data)); + }} + > +
{toChecksumHexAddress(data)}
+
+ +
+
+
); } diff --git a/ui/components/ui/radio-group/radio-group.stories.js b/ui/components/ui/radio-group/radio-group.stories.js index d14435301..c614e7bad 100644 --- a/ui/components/ui/radio-group/radio-group.stories.js +++ b/ui/components/ui/radio-group/radio-group.stories.js @@ -1,4 +1,5 @@ import React from 'react'; +import { GAS_RECOMMENDATIONS } from '../../../../shared/constants/gas'; import RadioGroup from '.'; export default { @@ -12,11 +13,15 @@ export const radioGroup = () => {
); diff --git a/ui/components/ui/sender-to-recipient/index.scss b/ui/components/ui/sender-to-recipient/index.scss index edd50cd4a..450212939 100644 --- a/ui/components/ui/sender-to-recipient/index.scss +++ b/ui/components/ui/sender-to-recipient/index.scss @@ -7,7 +7,6 @@ flex: 0 0 auto; &--default { - border-bottom: 1px solid $geyser; height: 42px; .sender-to-recipient { @@ -28,6 +27,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + border: 1px solid #d8d8d8; &--sender { padding-right: 30px; @@ -51,7 +51,7 @@ align-items: center; justify-content: center; - [dir='rtl'] & { + [dir="rtl"] & { transform: rotate(180deg); } } @@ -69,14 +69,14 @@ } &__name { - @include H6; + @include H7; padding-left: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - [dir='rtl'] & { + [dir="rtl"] & { /*rtl:ignore*/ direction: ltr; @@ -109,6 +109,7 @@ cursor: pointer; min-width: 0; color: $dusty-gray; + border: 1px solid #d8d8d8; } &__tooltip-wrapper { @@ -124,7 +125,7 @@ text-overflow: ellipsis; white-space: nowrap; - [dir='rtl'] & { + [dir="rtl"] & { /*rtl:ignore*/ direction: ltr; @@ -145,7 +146,7 @@ justify-content: center; align-items: center; - [dir='rtl'] & { + [dir="rtl"] & { transform: rotate(180deg); } } @@ -164,6 +165,7 @@ cursor: pointer; min-width: 0; color: $dusty-gray; + border: 1px solid #d8d8d8; } &__tooltip-wrapper { @@ -180,7 +182,7 @@ white-space: nowrap; margin-inline-start: 8px; - [dir='rtl'] & { + [dir="rtl"] & { /*rtl:ignore*/ direction: ltr; @@ -201,7 +203,7 @@ justify-content: center; align-items: center; - [dir='rtl'] & { + [dir="rtl"] & { transform: rotate(180deg); } } diff --git a/ui/components/ui/sender-to-recipient/sender-to-recipient.component.js b/ui/components/ui/sender-to-recipient/sender-to-recipient.component.js index fe6655cc8..4f92a3016 100644 --- a/ui/components/ui/sender-to-recipient/sender-to-recipient.component.js +++ b/ui/components/ui/sender-to-recipient/sender-to-recipient.component.js @@ -71,9 +71,7 @@ function SenderAddress({
{addressOnly ? ( - {`${t('from')}: ${ - senderName || shortenAddress(checksummedSenderAddress) - }`} + {`${senderName || shortenAddress(checksummedSenderAddress)}`} ) : ( senderName @@ -144,7 +142,6 @@ function RecipientWithAddress({ onHidden={() => setAddressCopied(false)} >
- {addressOnly ? `${t('to')}: ` : ''} {addressOnly ? recipientNickname || recipientEns || diff --git a/ui/components/ui/tabs/tabs.component.js b/ui/components/ui/tabs/tabs.component.js index 6c091e5a2..c0f9dc923 100644 --- a/ui/components/ui/tabs/tabs.component.js +++ b/ui/components/ui/tabs/tabs.component.js @@ -42,9 +42,9 @@ export default class Tabs extends Component { } renderTabs() { - const numberOfTabs = React.Children.count(this.props.children); + const numberOfTabs = React.Children.count(this._getValidChildren()); - return React.Children.map(this.props.children, (child, index) => { + return React.Children.map(this._getValidChildren(), (child, index) => { const tabName = child?.props.name; return ( child && @@ -58,7 +58,7 @@ export default class Tabs extends Component { } renderActiveTabContent() { - const { children } = this.props; + const children = this._getValidChildren(); const { activeTabIndex } = this.state; if ( @@ -92,8 +92,12 @@ export default class Tabs extends Component { * @private */ _findChildByName(name) { - return React.Children.toArray(this.props.children).findIndex( - (c) => c?.props.name === name, - ); + return this._getValidChildren().findIndex((c) => c?.props.name === name); + } + + // This ignores any 'null' child elements that are a result of a conditional + // based on a feature flag setting. + _getValidChildren() { + return React.Children.toArray(this.props.children).filter(Boolean); } } diff --git a/ui/components/ui/toggle-button/toggle-button.stories.js b/ui/components/ui/toggle-button/toggle-button.stories.js index 64f1a7acb..dd1e31c52 100644 --- a/ui/components/ui/toggle-button/toggle-button.stories.js +++ b/ui/components/ui/toggle-button/toggle-button.stories.js @@ -27,6 +27,4 @@ export const DefaultStory = () => { ); }; -DefaultStory.story = { - name: 'Default', -}; +DefaultStory.storyName = 'Default'; diff --git a/ui/components/ui/token-input/token-input.component.js b/ui/components/ui/token-input/token-input.component.js index 60665ac20..763bc5824 100644 --- a/ui/components/ui/token-input/token-input.component.js +++ b/ui/components/ui/token-input/token-input.component.js @@ -75,11 +75,12 @@ export default class TokenInput extends PureComponent { return Number(decimalValueString) ? decimalValueString : ''; } - handleChange = (decimalValue) => { + handleChange = (decimalValue, applyDecimals = false) => { const { token: { decimals } = {}, onChange } = this.props; let newDecimalValue = decimalValue; - if (decimals) { + + if (decimals && applyDecimals) { newDecimalValue = parseFloat(decimalValue).toFixed(decimals); } @@ -94,6 +95,10 @@ export default class TokenInput extends PureComponent { onChange(hexValue); }; + handleBlur = (decimalValue) => { + this.handleChange(decimalValue, true); + }; + renderConversionComponent() { const { tokenExchangeRates, @@ -155,6 +160,7 @@ export default class TokenInput extends PureComponent { {...restProps} suffix={token.symbol} onChange={this.handleChange} + onBlur={this.handleBlur} value={decimalValue} > {this.renderConversionComponent()} diff --git a/ui/components/ui/tooltip/tooltip.js b/ui/components/ui/tooltip/tooltip.js index 19d244737..84afe57ed 100644 --- a/ui/components/ui/tooltip/tooltip.js +++ b/ui/components/ui/tooltip/tooltip.js @@ -80,7 +80,7 @@ export default class Tooltip extends PureComponent { size={size} offset={offset} style={style} - title={title} + title={disabled ? '' : title} trigger={trigger} theme={theme} tabIndex={tabIndex || 0} diff --git a/ui/components/ui/typography/typography.js b/ui/components/ui/typography/typography.js index 02f9a3015..0af48e699 100644 --- a/ui/components/ui/typography/typography.js +++ b/ui/components/ui/typography/typography.js @@ -20,6 +20,7 @@ export default function Typography({ children, fontWeight = 'normal', fontStyle = 'normal', + fontSize, align, boxProps = {}, margin = [1, 0], @@ -33,6 +34,7 @@ export default function Typography({ { [`typography--align-${align}`]: Boolean(align), [`typography--color-${color}`]: Boolean(color), + [`typography--size-${fontSize}`]: Boolean(fontSize), }, ); @@ -67,6 +69,7 @@ Typography.propTypes = { margin: MultipleSizes, fontWeight: PropTypes.oneOf(Object.values(FONT_WEIGHT)), fontStyle: PropTypes.oneOf(Object.values(FONT_STYLE)), + fontSize: PropTypes.string, tag: PropTypes.oneOf([ 'p', 'h1', diff --git a/ui/components/ui/typography/typography.scss b/ui/components/ui/typography/typography.scss index 612bfc1cc..c92fc6eb2 100644 --- a/ui/components/ui/typography/typography.scss +++ b/ui/components/ui/typography/typography.scss @@ -32,6 +32,12 @@ } } + @each $size in design-system.$font-size { + &--size-#{$size} { + font-size: $size; + } + } + @each $alignment in design-system.$text-align { &--align-#{$alignment} { text-align: $alignment; diff --git a/ui/components/ui/unit-input/unit-input.component.js b/ui/components/ui/unit-input/unit-input.component.js index 8802519fb..10e14b12f 100644 --- a/ui/components/ui/unit-input/unit-input.component.js +++ b/ui/components/ui/unit-input/unit-input.component.js @@ -17,6 +17,7 @@ export default class UnitInput extends PureComponent { actionComponent: PropTypes.node, error: PropTypes.bool, onChange: PropTypes.func, + onBlur: PropTypes.func, placeholder: PropTypes.string, suffix: PropTypes.string, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), @@ -55,6 +56,8 @@ export default class UnitInput extends PureComponent { if (value === '') { this.setState({ value: '0' }); } + + this.props.onBlur && this.props.onBlur(value); }; handleChange = (event) => { diff --git a/ui/contexts/gasFee.js b/ui/contexts/gasFee.js new file mode 100644 index 000000000..c7fe7f092 --- /dev/null +++ b/ui/contexts/gasFee.js @@ -0,0 +1,37 @@ +import React, { createContext, useContext } from 'react'; +import PropTypes from 'prop-types'; +import { useGasFeeInputs } from '../hooks/gasFeeInput/useGasFeeInputs'; + +export const GasFeeContext = createContext({}); + +export const GasFeeContextProvider = ({ + children, + defaultEstimateToUse, + transaction, + minimumGasLimit, + editGasMode, +}) => { + const gasFeeDetails = useGasFeeInputs( + defaultEstimateToUse, + transaction, + minimumGasLimit, + editGasMode, + ); + return ( + + {children} + + ); +}; + +export function useGasFeeContext() { + return useContext(GasFeeContext); +} + +GasFeeContextProvider.propTypes = { + children: PropTypes.node.isRequired, + defaultEstimateToUse: PropTypes.string, + transaction: PropTypes.object.isRequired, + minimumGasLimit: PropTypes.string, + editGasMode: PropTypes.string, +}; diff --git a/ui/css/design-system/attributes.scss b/ui/css/design-system/attributes.scss index 7373c7c3b..8b920b7eb 100644 --- a/ui/css/design-system/attributes.scss +++ b/ui/css/design-system/attributes.scss @@ -19,6 +19,11 @@ $flex-direction: column, column-reverse; +$flex-wrap: + wrap, + wrap-reverse, + nowrap; + $fractions: ( 1\/2: 50%, 1\/3: 33.333333%, @@ -77,3 +82,4 @@ $display: block, grid, flex, inline-block, inline-grid, inline-flex, list-item; $text-align: left, right, center, justify, end; $font-weight: bold, normal, 100, 200, 300, 400, 500, 600, 700, 800, 900; $font-style: normal, italic, oblique; +$font-size: 12px; diff --git a/ui/css/design-system/colors.scss b/ui/css/design-system/colors.scss index b447315f8..d76de5ef6 100644 --- a/ui/css/design-system/colors.scss +++ b/ui/css/design-system/colors.scss @@ -97,25 +97,30 @@ $success-2: #caf4d1; $success-3: #219e37; $ui-black: #24292e; +$ui-grey: #d6d9dc; $ui-white: #fff; $ui-1: #f2f3f4; $ui-2: #d6d9dc; $ui-3: #bbc0c5; $ui-4: #6a737d; +$ui-5: #c4c4c4; $mainnet: #29b6af; $ropsten: #ff4a8d; $kovan: #9064ff; $rinkeby: #f6c343; $goerli: #3099f2; +$localhost: #29b6af; $color-map: ( 'ui-1': $ui-1, 'ui-2': $ui-2, 'ui-3': $ui-3, 'ui-4': $ui-4, + 'ui-5': $ui-5, 'white': $ui-white, 'black': $ui-black, + 'grey': $ui-grey, 'primary-1': $primary-1, 'primary-2': $primary-2, 'primary-3': $primary-3, @@ -136,5 +141,6 @@ $color-map: ( 'kovan': $kovan, 'rinkeby': $rinkeby, 'goerli': $goerli, + 'localhost': $localhost, 'transparent': transparent, ); diff --git a/ui/css/itcss/components/network.scss b/ui/css/itcss/components/network.scss index 31137ac43..f7355fc1b 100644 --- a/ui/css/itcss/components/network.scss +++ b/ui/css/itcss/components/network.scss @@ -32,6 +32,10 @@ &.goerli-test-network .menu-icon-circle div { background-color: rgba(48, 153, 242, 0.7) !important; } + + &.localhost-network .menu-icon-circle div { + background-color: rgba(3, 135, 137, 0.7) !important; + } } .dropdown-menu-item { @@ -131,6 +135,12 @@ background-color: $scorpion; } +.network-dropdown-testnets { + &--no-visibility { + visibility: hidden; + } +} + .network-dropdown-title { @include H4; @@ -146,6 +156,25 @@ min-height: 36px; width: 265px; color: $dusty-gray; + + &--link { + color: $white; + cursor: pointer; + font-weight: bold; + text-decoration: underline; + + &:hover { + color: $white; + } + } + + &--close { + color: $white; + background: inherit; + position: absolute; + top: 64px; + right: 10px; + } } .network-caret { diff --git a/ui/ducks/app/app.js b/ui/ducks/app/app.js index f9ea58daa..e7b4c2c9f 100644 --- a/ui/ducks/app/app.js +++ b/ui/ducks/app/app.js @@ -38,9 +38,9 @@ export default function reduceApp(state = {}, action) { defaultHdPaths: { trezor: `m/44'/60'/0'/0`, ledger: `m/44'/60'/0'/0/0`, + lattice: `m/44'/60'/0'/0`, }, networksTabSelectedRpcUrl: '', - networksTabIsInAddMode: false, loadingMethodData: false, show3BoxModalAfterImport: false, threeBoxLastUpdated: null, @@ -54,6 +54,8 @@ export default function reduceApp(state = {}, action) { gasLoadingAnimationIsShowing: false, ledgerWebHidConnectedStatus: WEBHID_CONNECTED_STATUSES.UNKNOWN, ledgerTransportStatus: TRANSPORT_STATES.NONE, + newNetworkAdded: '', + showTestnetMessageInDropdown: true, ...state, }; @@ -71,6 +73,12 @@ export default function reduceApp(state = {}, action) { networkDropdownOpen: false, }; + case actionConstants.HIDE_TESTNET_MESSAGE: + return { + ...appState, + showTestnetMessageInDropdown: false, + }; + // alert methods case actionConstants.ALERT_OPEN: return { @@ -283,10 +291,10 @@ export default function reduceApp(state = {}, action) { networksTabSelectedRpcUrl: action.value, }; - case actionConstants.SET_NETWORKS_TAB_ADD_MODE: + case actionConstants.SET_NEW_NETWORK_ADDED: return { ...appState, - networksTabIsInAddMode: action.value, + newNetworkAdded: action.value, }; case actionConstants.LOADING_METHOD_DATA_STARTED: diff --git a/ui/ducks/app/app.test.js b/ui/ducks/app/app.test.js index e88ac1b9b..2ad0222d5 100644 --- a/ui/ducks/app/app.test.js +++ b/ui/ducks/app/app.test.js @@ -38,6 +38,16 @@ describe('App State', () => { expect(newState.networkDropdownOpen).toStrictEqual(false); }); + it('sets showTestnetMessageInDropdown dropdown to false', () => { + const testnetMessage = { showTestnetMessageInDropdown: true }; + const state = { ...metamaskState, ...testnetMessage }; + const newState = reduceApp(state, { + type: actions.HIDE_TESTNET_MESSAGE, + }); + + expect(newState.showTestnetMessageInDropdown).toStrictEqual(false); + }); + it('opens alert', () => { const state = reduceApp(metamaskState, { type: actions.ALERT_OPEN, @@ -255,6 +265,7 @@ describe('App State', () => { const hdPaths = { trezor: "m/44'/60'/0'/0", ledger: "m/44'/60'/0'", + lattice: "m/44'/60'/0'/0", }; const state = reduceApp(metamaskState, { type: actions.SET_HARDWARE_WALLET_DEFAULT_HD_PATH, diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index 452acbd60..9578826e1 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -34,6 +34,7 @@ export default function reduceMetamask(state = {}, action) { preferences: { autoLockTimeLimit: undefined, showFiatInTestnets: false, + showTestNetworks: false, useNativeCurrencyAsPrimaryCurrency: true, }, firstTimeFlowType: null, diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 86f385747..690092173 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -40,6 +40,7 @@ import { checkNetworkAndAccountSupports1559, getUseTokenDetection, getTokenList, + getAddressBookEntryOrAccountName, } from '../../selectors'; import { disconnectGasFeeEstimatePoller, @@ -1444,12 +1445,12 @@ export function useMyAccountsForRecipientSearch() { export function updateRecipient({ address, nickname }) { return async (dispatch, getState) => { const state = getState(); - const nicknameFromAddressBook = - getAddressBookEntry(state, address)?.name ?? ''; + const nicknameFromAddressBookEntryOrAccountName = + getAddressBookEntryOrAccountName(state, address) ?? ''; await dispatch( actions.updateRecipient({ address, - nickname: nickname || nicknameFromAddressBook, + nickname: nickname || nicknameFromAddressBookEntryOrAccountName, }), ); await dispatch(computeEstimatedGasLimit()); diff --git a/ui/ducks/send/send.test.js b/ui/ducks/send/send.test.js index 9e272b9b3..aa7f7e25c 100644 --- a/ui/ducks/send/send.test.js +++ b/ui/ducks/send/send.test.js @@ -1626,6 +1626,7 @@ describe('Send Slice', () => { const updateRecipientState = { metamask: { addressBook: {}, + identities: {}, provider: { chainId: '0x1', }, @@ -1740,6 +1741,7 @@ describe('Send Slice', () => { const tokenState = { metamask: { addressBook: {}, + identities: {}, blockGasLimit: '', selectedAddress: '', provider: { @@ -1794,6 +1796,7 @@ describe('Send Slice', () => { const updateRecipientState = { metamask: { addressBook: {}, + identities: {}, provider: { chainId: '', }, diff --git a/ui/helpers/constants/design-system.js b/ui/helpers/constants/design-system.js index 2f3b4bdd9..ae26f9d28 100644 --- a/ui/helpers/constants/design-system.js +++ b/ui/helpers/constants/design-system.js @@ -10,7 +10,7 @@ export const COLORS = { UI3: 'ui-3', UI4: 'ui-4', BLACK: 'black', - GRAY: 'gray', + GREY: 'grey', WHITE: 'white', PRIMARY1: 'primary-1', PRIMARY2: 'primary-2', @@ -33,6 +33,7 @@ export const COLORS = { RINKEBY: 'rinkeby', GOERLI: 'goerli', TRANSPARENT: 'transparent', + LOCALHOST: 'localhost', }; export const TYPOGRAPHY = { @@ -95,6 +96,12 @@ export const FLEX_DIRECTION = { COLUMN_REVERSE: 'column-reverse', }; +export const FLEX_WRAP = { + WRAP: 'wrap', + WRAP_REVERSE: 'wrap-reverse', + NO_WRAP: 'nowrap', +}; + export const DISPLAY = { BLOCK: 'block', FLEX: 'flex', diff --git a/ui/helpers/constants/error-keys.js b/ui/helpers/constants/error-keys.js index e51b87736..8372a3f1e 100644 --- a/ui/helpers/constants/error-keys.js +++ b/ui/helpers/constants/error-keys.js @@ -6,3 +6,4 @@ export const ETH_GAS_PRICE_FETCH_WARNING_KEY = 'ethGasPriceFetchWarning'; export const GAS_PRICE_FETCH_FAILURE_ERROR_KEY = 'gasPriceFetchFailed'; export const GAS_PRICE_EXCESSIVE_ERROR_KEY = 'gasPriceExcessive'; export const UNSENDABLE_ASSET_ERROR_KEY = 'unsendableAsset'; +export const INSUFFICIENT_FUNDS_FOR_GAS_ERROR_KEY = 'insufficientFundsForGas'; diff --git a/ui/helpers/constants/routes.js b/ui/helpers/constants/routes.js index 8b10ea77a..7883647de 100644 --- a/ui/helpers/constants/routes.js +++ b/ui/helpers/constants/routes.js @@ -11,6 +11,7 @@ const ABOUT_US_ROUTE = '/settings/about-us'; const ALERTS_ROUTE = '/settings/alerts'; const NETWORKS_ROUTE = '/settings/networks'; const NETWORKS_FORM_ROUTE = '/settings/networks/form'; +const ADD_NETWORK_ROUTE = '/settings/networks/add-network'; const CONTACT_LIST_ROUTE = '/settings/contact-list'; const CONTACT_EDIT_ROUTE = '/settings/contact-list/edit-contact'; const CONTACT_ADD_ROUTE = '/settings/contact-list/add-contact'; @@ -96,6 +97,7 @@ const PATH_NAME_MAP = { [ALERTS_ROUTE]: 'Alerts Settings Page', [NETWORKS_ROUTE]: 'Network Settings Page', [NETWORKS_FORM_ROUTE]: 'Network Settings Page Form', + [ADD_NETWORK_ROUTE]: 'Add Network From Settings Page Form', [CONTACT_LIST_ROUTE]: 'Contact List Settings Page', [`${CONTACT_EDIT_ROUTE}/:address`]: 'Edit Contact Settings Page', [CONTACT_ADD_ROUTE]: 'Add Contact Settings Page', @@ -200,6 +202,7 @@ export { CONTACT_VIEW_ROUTE, NETWORKS_ROUTE, NETWORKS_FORM_ROUTE, + ADD_NETWORK_ROUTE, INITIALIZE_BACKUP_SEED_PHRASE_ROUTE, INITIALIZE_SEED_PHRASE_INTRO_ROUTE, CONNECT_ROUTE, diff --git a/ui/helpers/constants/transactions.js b/ui/helpers/constants/transactions.js index 614f0329d..aeeda8bdf 100644 --- a/ui/helpers/constants/transactions.js +++ b/ui/helpers/constants/transactions.js @@ -19,3 +19,8 @@ export const TOKEN_CATEGORY_HASH = { [TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER]: true, [TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM]: true, }; + +export const TRANSACTION_ENVELOPE_TYPE_NAMES = { + FEE_MARKET: 'fee-market', + LEGACY: 'legacy', +}; diff --git a/ui/helpers/utils/build-types.js b/ui/helpers/utils/build-types.js index 37b4c139d..8577b3905 100644 --- a/ui/helpers/utils/build-types.js +++ b/ui/helpers/utils/build-types.js @@ -1,15 +1,25 @@ -import betaJson from '../../../app/build-types/beta/beta-mascot.json'; +///: BEGIN:ONLY_INCLUDE_IN(beta) +import betaJson from '../../../app/build-types/beta/images/beta-mascot.json'; +///: END:ONLY_INCLUDE_IN +///: BEGIN:ONLY_INCLUDE_IN(flask) +import flaskJson from '../../../app/build-types/flask/images/flask-mascot.json'; +///: END:ONLY_INCLUDE_IN const assetList = { main: { - metafoxLogoHorizontalDark: '/images/logo/metamask-logo-horizontal.svg', // Will use default provided by the @metamask/logo library foxMeshJson: undefined, }, + ///: BEGIN:ONLY_INCLUDE_IN(beta) beta: { - metafoxLogoHorizontalDark: '/images/logo/metamask-logo-horizontal-dark.svg', foxMeshJson: betaJson, }, + ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(flask) + flask: { + foxMeshJson: flaskJson, + }, + ///: END:ONLY_INCLUDE_IN }; export function isBeta() { @@ -24,8 +34,8 @@ export function getBuildSpecificAsset(assetName) { !assetList[buildType] || !Object.keys(assetList[buildType]).includes(assetName) ) { - console.warn( - `Cannot find asset for build ${buildType}: ${assetName}, returning main build asset`, + console.error( + `Cannot find asset "${assetName}" for build "${buildType}", returning main build asset.`, ); return assetList.main[assetName]; } diff --git a/ui/helpers/utils/hardware.js b/ui/helpers/utils/hardware.js new file mode 100644 index 000000000..b011f7879 --- /dev/null +++ b/ui/helpers/utils/hardware.js @@ -0,0 +1,3 @@ +export function isHardwareKeyring(keyringType = '') { + return keyringType.includes('Hardware'); +} diff --git a/ui/helpers/utils/transactions.util.js b/ui/helpers/utils/transactions.util.js index 17a27c81a..0613159bc 100644 --- a/ui/helpers/utils/transactions.util.js +++ b/ui/helpers/utils/transactions.util.js @@ -145,7 +145,7 @@ export function getLatestSubmittedTxWithNonce( } export async function isSmartContractAddress(address) { - const { isContractCode } = readAddressAsContract(global.eth, address); + const { isContractCode } = await readAddressAsContract(global.eth, address); return isContractCode; } diff --git a/ui/hooks/gasFeeInput/useGasFeeErrors.js b/ui/hooks/gasFeeInput/useGasFeeErrors.js index d477087d0..0300feec0 100644 --- a/ui/hooks/gasFeeInput/useGasFeeErrors.js +++ b/ui/hooks/gasFeeInput/useGasFeeErrors.js @@ -130,7 +130,10 @@ const getMaxFeeWarning = ( return undefined; }; -const getBalanceError = (minimumCostInHexWei, transaction, ethBalance) => { +const hasBalanceError = (minimumCostInHexWei, transaction, ethBalance) => { + if (minimumCostInHexWei === undefined || ethBalance === undefined) { + return false; + } const minimumTxCostInHexWei = addHexes( minimumCostInHexWei, transaction?.txParams?.value || '0x0', @@ -247,7 +250,7 @@ export function useGasFeeErrors({ ); const { balance: ethBalance } = useSelector(getSelectedAccount); - const balanceError = getBalanceError( + const balanceError = hasBalanceError( minimumCostInHexWei, transaction, ethBalance, diff --git a/ui/hooks/gasFeeInput/useGasFeeInputs.js b/ui/hooks/gasFeeInput/useGasFeeInputs.js index 25f6d5c76..51021cc42 100644 --- a/ui/hooks/gasFeeInput/useGasFeeInputs.js +++ b/ui/hooks/gasFeeInput/useGasFeeInputs.js @@ -1,9 +1,19 @@ import { useCallback, useState } from 'react'; import { useSelector } from 'react-redux'; -import { getAdvancedInlineGasShown } from '../../selectors'; -import { hexToDecimal } from '../../helpers/utils/conversions.util'; +import { + CUSTOM_GAS_ESTIMATE, + GAS_RECOMMENDATIONS, + EDIT_GAS_MODES, +} from '../../../shared/constants/gas'; import { GAS_FORM_ERRORS } from '../../helpers/constants/gas'; +import { areDappSuggestedAndTxParamGasFeesTheSame } from '../../helpers/utils/confirm-tx.util'; +import { + checkNetworkAndAccountSupports1559, + getAdvancedInlineGasShown, +} from '../../selectors'; +import { hexToDecimal } from '../../helpers/utils/conversions.util'; +import { isLegacyTransaction } from '../../helpers/utils/transactions.util'; import { useGasFeeEstimates } from '../useGasFeeEstimates'; import { useGasFeeErrors } from './useGasFeeErrors'; @@ -58,11 +68,15 @@ import { useGasEstimates } from './useGasEstimates'; * ).GasEstimates} - gas fee input state and the GasFeeEstimates object */ export function useGasFeeInputs( - defaultEstimateToUse = 'medium', + defaultEstimateToUse = GAS_RECOMMENDATIONS.MEDIUM, transaction, minimumGasLimit = '0x5208', - editGasMode, + editGasMode = EDIT_GAS_MODES.MODIFY_IN_PLACE, ) { + const supportsEIP1559 = + useSelector(checkNetworkAndAccountSupports1559) && + !isLegacyTransaction(transaction?.txParams); + // We need the gas estimates from the GasFeeController in the background. // Calling this hooks initiates polling for new gas estimates and returns the // current estimate. @@ -86,6 +100,13 @@ export function useGasFeeInputs( return defaultEstimateToUse; }); + const [ + isUsingDappSuggestedGasFees, + setIsUsingDappSuggestedGasFees, + ] = useState(() => + Boolean(areDappSuggestedAndTxParamGasFeesTheSame(transaction)), + ); + const [gasLimit, setGasLimit] = useState( Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')), ); @@ -187,6 +208,7 @@ export function useGasFeeInputs( setMaxPriorityFeePerGas(null); setGasPrice(null); setGasPriceHasBeenManuallySet(false); + setIsUsingDappSuggestedGasFees(false); }, [ setInternalEstimateToUse, @@ -195,11 +217,12 @@ export function useGasFeeInputs( setMaxPriorityFeePerGas, setGasPrice, setGasPriceHasBeenManuallySet, + setIsUsingDappSuggestedGasFees, ], ); const onManualChange = useCallback(() => { - setInternalEstimateToUse('custom'); + setInternalEstimateToUse(CUSTOM_GAS_ESTIMATE); handleGasLimitOutOfBoundError(); // Restore existing values setGasPrice(gasPrice); @@ -222,6 +245,7 @@ export function useGasFeeInputs( ]); return { + transaction, maxFeePerGas, maxFeePerGasFiat, setMaxFeePerGas, @@ -239,6 +263,7 @@ export function useGasFeeInputs( estimatedMaximumNative, estimatedMinimumNative, isGasEstimatesLoading, + isUsingDappSuggestedGasFees, gasFeeEstimates, gasEstimateType, estimatedGasFeeTimeBounds, @@ -250,5 +275,6 @@ export function useGasFeeInputs( gasErrors, gasWarnings, hasGasErrors, + supportsEIP1559, }; } diff --git a/ui/hooks/gasFeeInput/useGasFeeInputs.test.js b/ui/hooks/gasFeeInput/useGasFeeInputs.test.js index 277812185..5127c61b7 100644 --- a/ui/hooks/gasFeeInput/useGasFeeInputs.test.js +++ b/ui/hooks/gasFeeInput/useGasFeeInputs.test.js @@ -1,6 +1,10 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { useSelector } from 'react-redux'; import { TRANSACTION_ENVELOPE_TYPES } from '../../../shared/constants/transaction'; +import { + GAS_RECOMMENDATIONS, + CUSTOM_GAS_ESTIMATE, +} from '../../../shared/constants/gas'; import { ETH, PRIMARY } from '../../helpers/constants/common'; @@ -116,7 +120,7 @@ describe('useGasFeeInputs', () => { it('returns gasPrice appropriately, and "0" for EIP1559 fields', () => { const { result } = renderHook(() => - useGasFeeInputs('medium', { + useGasFeeInputs(GAS_RECOMMENDATIONS.MEDIUM, { txParams: { value: '3782DACE9D90000', gasLimit: '0x5028', @@ -167,7 +171,10 @@ describe('useGasFeeInputs', () => { }), ); const { result } = renderHook(() => - useGasFeeInputs(null, { txParams: {}, userFeeLevel: 'medium' }), + useGasFeeInputs(null, { + txParams: {}, + userFeeLevel: GAS_RECOMMENDATIONS.MEDIUM, + }), ); expect(result.current.maxFeePerGas).toBe( FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium @@ -226,7 +233,7 @@ describe('useGasFeeInputs', () => { it('should return true', () => { const { result } = renderHook(() => useGasFeeInputs(null, { - userFeeLevel: 'medium', + userFeeLevel: GAS_RECOMMENDATIONS.MEDIUM, txParams: { gas: '0x5208' }, }), ); @@ -242,14 +249,14 @@ describe('useGasFeeInputs', () => { it('should change estimateToUse value', () => { const { result } = renderHook(() => useGasFeeInputs(null, { - userFeeLevel: 'medium', + userFeeLevel: GAS_RECOMMENDATIONS.MEDIUM, txParams: { gas: '0x5208' }, }), ); act(() => { - result.current.setEstimateToUse('high'); + result.current.setEstimateToUse(GAS_RECOMMENDATIONS.HIGH); }); - expect(result.current.estimateToUse).toBe('high'); + expect(result.current.estimateToUse).toBe(GAS_RECOMMENDATIONS.HIGH); expect(result.current.maxFeePerGas).toBe( FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.high .suggestedMaxFeePerGas, @@ -269,7 +276,7 @@ describe('useGasFeeInputs', () => { it('should change estimateToUse value to custom', () => { const { result } = renderHook(() => useGasFeeInputs(null, { - userFeeLevel: 'medium', + userFeeLevel: GAS_RECOMMENDATIONS.MEDIUM, txParams: { gas: '0x5208' }, }), ); @@ -278,7 +285,7 @@ describe('useGasFeeInputs', () => { result.current.setMaxFeePerGas('100'); result.current.setMaxPriorityFeePerGas('10'); }); - expect(result.current.estimateToUse).toBe('custom'); + expect(result.current.estimateToUse).toBe(CUSTOM_GAS_ESTIMATE); expect(result.current.maxFeePerGas).toBe('100'); expect(result.current.maxPriorityFeePerGas).toBe('10'); }); @@ -298,7 +305,7 @@ describe('useGasFeeInputs', () => { it('does not return fiat values', () => { const { result } = renderHook(() => useGasFeeInputs(null, { - userFeeLevel: 'medium', + userFeeLevel: GAS_RECOMMENDATIONS.MEDIUM, txParams: { gas: '0x5208' }, }), ); diff --git a/ui/hooks/gasFeeInput/useGasPriceInput.js b/ui/hooks/gasFeeInput/useGasPriceInput.js index 2e278da00..acc12207e 100644 --- a/ui/hooks/gasFeeInput/useGasPriceInput.js +++ b/ui/hooks/gasFeeInput/useGasPriceInput.js @@ -1,7 +1,10 @@ import { useState } from 'react'; import { isEqual } from 'lodash'; -import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas'; +import { + GAS_ESTIMATE_TYPES, + CUSTOM_GAS_ESTIMATE, +} from '../../../shared/constants/gas'; import { hexWEIToDecGWEI } from '../../helpers/utils/conversions.util'; import { isLegacyTransaction } from '../../helpers/utils/transactions.util'; @@ -30,7 +33,7 @@ export function useGasPriceInput({ transaction, }) { const [gasPriceHasBeenManuallySet, setGasPriceHasBeenManuallySet] = useState( - transaction?.userFeeLevel === 'custom', + transaction?.userFeeLevel === CUSTOM_GAS_ESTIMATE, ); const [gasPrice, setGasPrice] = useState(() => { diff --git a/ui/hooks/gasFeeInput/useGasPriceInput.test.js b/ui/hooks/gasFeeInput/useGasPriceInput.test.js index 9514f969a..9f1d27431 100644 --- a/ui/hooks/gasFeeInput/useGasPriceInput.test.js +++ b/ui/hooks/gasFeeInput/useGasPriceInput.test.js @@ -1,5 +1,9 @@ import { act, renderHook } from '@testing-library/react-hooks'; +import { + GAS_RECOMMENDATIONS, + CUSTOM_GAS_ESTIMATE, +} from '../../../shared/constants/gas'; import { FEE_MARKET_ESTIMATE_RETURN_VALUE, LEGACY_GAS_ESTIMATE_RETURN_VALUE, @@ -30,7 +34,7 @@ describe('useGasPriceInput', () => { const { result } = renderHook(() => useGasPriceInput({ transaction: { - userFeeLevel: 'custom', + userFeeLevel: CUSTOM_GAS_ESTIMATE, txParams: { gasPrice: '0x5028' }, }, }), @@ -42,9 +46,9 @@ describe('useGasPriceInput', () => { configure(); const { result } = renderHook(() => useGasPriceInput({ - estimateToUse: 'high', + estimateToUse: GAS_RECOMMENDATIONS.HIGH, transaction: { - userFeeLevel: 'high', + userFeeLevel: GAS_RECOMMENDATIONS.HIGH, txParams: { gasPrice: '0x5028' }, }, ...LEGACY_GAS_ESTIMATE_RETURN_VALUE, @@ -56,7 +60,7 @@ describe('useGasPriceInput', () => { it('if no gasPrice is provided returns default estimate for legacy transaction', () => { const { result } = renderHook(() => useGasPriceInput({ - estimateToUse: 'medium', + estimateToUse: GAS_RECOMMENDATIONS.MEDIUM, ...LEGACY_GAS_ESTIMATE_RETURN_VALUE, }), ); @@ -66,7 +70,7 @@ describe('useGasPriceInput', () => { it('for legacy transaction if estimateToUse is high and no gasPrice is provided returns high estimate value', () => { const { result } = renderHook(() => useGasPriceInput({ - estimateToUse: 'high', + estimateToUse: GAS_RECOMMENDATIONS.HIGH, ...LEGACY_GAS_ESTIMATE_RETURN_VALUE, }), ); @@ -76,7 +80,7 @@ describe('useGasPriceInput', () => { it('returns 0 if gasPrice is not present in transaction and estimates are also not legacy', () => { const { result } = renderHook(() => useGasPriceInput({ - estimateToUse: 'medium', + estimateToUse: GAS_RECOMMENDATIONS.MEDIUM, ...FEE_MARKET_ESTIMATE_RETURN_VALUE, }), ); @@ -85,7 +89,7 @@ describe('useGasPriceInput', () => { it('returns gasPrice set by user if gasPriceHasBeenManuallySet is true', () => { const { result } = renderHook(() => - useGasPriceInput({ estimateToUse: 'medium' }), + useGasPriceInput({ estimateToUse: GAS_RECOMMENDATIONS.MEDIUM }), ); act(() => { result.current.setGasPriceHasBeenManuallySet(true); diff --git a/ui/hooks/gasFeeInput/useMaxFeePerGasInput.test.js b/ui/hooks/gasFeeInput/useMaxFeePerGasInput.test.js index d51c57c0e..318e9d7e9 100644 --- a/ui/hooks/gasFeeInput/useMaxFeePerGasInput.test.js +++ b/ui/hooks/gasFeeInput/useMaxFeePerGasInput.test.js @@ -3,6 +3,10 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { getMaximumGasTotalInHexWei } from '../../../shared/modules/gas.utils'; import { decimalToHex } from '../../helpers/utils/conversions.util'; +import { + GAS_RECOMMENDATIONS, + CUSTOM_GAS_ESTIMATE, +} from '../../../shared/constants/gas'; import { FEE_MARKET_ESTIMATE_RETURN_VALUE, @@ -31,9 +35,9 @@ const renderUseMaxFeePerGasInputHook = (props) => renderHook(() => useMaxFeePerGasInput({ gasLimit: '21000', - estimateToUse: 'medium', + estimateToUse: GAS_RECOMMENDATIONS.MEDIUM, transaction: { - userFeeLevel: 'custom', + userFeeLevel: CUSTOM_GAS_ESTIMATE, txParams: { maxFeePerGas: '0x5028' }, }, ...FEE_MARKET_ESTIMATE_RETURN_VALUE, @@ -55,7 +59,7 @@ describe('useMaxFeePerGasInput', () => { it('returns gasPrice values from transaction if transaction.userFeeLevel is custom and maxFeePerGas is not provided', () => { const { result } = renderUseMaxFeePerGasInputHook({ transaction: { - userFeeLevel: 'custom', + userFeeLevel: CUSTOM_GAS_ESTIMATE, txParams: { gasPrice: '0x5028' }, }, }); @@ -64,9 +68,9 @@ describe('useMaxFeePerGasInput', () => { it('does not returns maxFeePerGas values from transaction if transaction.userFeeLevel is not custom', () => { const { result } = renderUseMaxFeePerGasInputHook({ - estimateToUse: 'high', + estimateToUse: GAS_RECOMMENDATIONS.HIGH, transaction: { - userFeeLevel: 'high', + userFeeLevel: GAS_RECOMMENDATIONS.HIGH, txParams: { maxFeePerGas: '0x5028' }, }, }); @@ -79,7 +83,7 @@ describe('useMaxFeePerGasInput', () => { it('if no maxFeePerGas is provided by user or in transaction it returns value from fee market estimate', () => { const { result } = renderUseMaxFeePerGasInputHook({ transaction: { - userFeeLevel: 'high', + userFeeLevel: GAS_RECOMMENDATIONS.HIGH, txParams: {}, }, }); diff --git a/ui/hooks/gasFeeInput/useMaxPriorityFeePerGasInput.test.js b/ui/hooks/gasFeeInput/useMaxPriorityFeePerGasInput.test.js index 4539a2363..5601ef98a 100644 --- a/ui/hooks/gasFeeInput/useMaxPriorityFeePerGasInput.test.js +++ b/ui/hooks/gasFeeInput/useMaxPriorityFeePerGasInput.test.js @@ -1,6 +1,10 @@ import { useSelector } from 'react-redux'; import { act, renderHook } from '@testing-library/react-hooks'; +import { + GAS_RECOMMENDATIONS, + CUSTOM_GAS_ESTIMATE, +} from '../../../shared/constants/gas'; import { FEE_MARKET_ESTIMATE_RETURN_VALUE, LEGACY_GAS_ESTIMATE_RETURN_VALUE, @@ -28,9 +32,9 @@ const renderUseMaxPriorityFeePerGasInputHook = (props) => { return renderHook(() => useMaxPriorityFeePerGasInput({ gasLimit: '21000', - estimateToUse: 'medium', + estimateToUse: GAS_RECOMMENDATIONS.MEDIUM, transaction: { - userFeeLevel: 'custom', + userFeeLevel: CUSTOM_GAS_ESTIMATE, txParams: { maxPriorityFeePerGas: '0x5028' }, }, ...FEE_MARKET_ESTIMATE_RETURN_VALUE, @@ -56,7 +60,7 @@ describe('useMaxPriorityFeePerGasInput', () => { it('returns maxFeePerGas values from transaction if transaction.userFeeLevel is custom and maxPriorityFeePerGas is not provided', () => { const { result } = renderUseMaxPriorityFeePerGasInputHook({ transaction: { - userFeeLevel: 'custom', + userFeeLevel: CUSTOM_GAS_ESTIMATE, txParams: { maxFeePerGas: '0x5028' }, }, }); @@ -65,9 +69,9 @@ describe('useMaxPriorityFeePerGasInput', () => { it('does not returns maxPriorityFeePerGas values from transaction if transaction.userFeeLevel is not custom', () => { const { result } = renderUseMaxPriorityFeePerGasInputHook({ - estimateToUse: 'high', + estimateToUse: GAS_RECOMMENDATIONS.HIGH, transaction: { - userFeeLevel: 'high', + userFeeLevel: GAS_RECOMMENDATIONS.HIGH, txParams: { maxPriorityFeePerGas: '0x5028' }, }, }); diff --git a/ui/hooks/gasFeeInput/utils.js b/ui/hooks/gasFeeInput/utils.js index ce8b76ac7..3c27f6416 100644 --- a/ui/hooks/gasFeeInput/utils.js +++ b/ui/hooks/gasFeeInput/utils.js @@ -1,4 +1,7 @@ -import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas'; +import { + GAS_ESTIMATE_TYPES, + CUSTOM_GAS_ESTIMATE, +} from '../../../shared/constants/gas'; export function getGasFeeEstimate( field, @@ -14,4 +17,5 @@ export function getGasFeeEstimate( } export const feeParamsAreCustom = (transaction) => - !transaction?.userFeeLevel || transaction?.userFeeLevel === 'custom'; + !transaction?.userFeeLevel || + transaction?.userFeeLevel === CUSTOM_GAS_ESTIMATE; diff --git a/ui/hooks/useGasFeeInputs.js b/ui/hooks/useGasFeeInputs.js deleted file mode 100644 index 6f52425bf..000000000 --- a/ui/hooks/useGasFeeInputs.js +++ /dev/null @@ -1,446 +0,0 @@ -import { addHexPrefix } from 'ethereumjs-util'; -import { useCallback, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { isEqual } from 'lodash'; -import { GAS_ESTIMATE_TYPES, EDIT_GAS_MODES } from '../../shared/constants/gas'; -import { multiplyCurrencies } from '../../shared/modules/conversion.utils'; -import { - getMaximumGasTotalInHexWei, - getMinimumGasTotalInHexWei, -} from '../../shared/modules/gas.utils'; -import { PRIMARY, SECONDARY } from '../helpers/constants/common'; -import { - checkNetworkAndAccountSupports1559, - getShouldShowFiat, - getAdvancedInlineGasShown, -} from '../selectors'; - -import { - hexWEIToDecGWEI, - decGWEIToHexWEI, - decimalToHex, - hexToDecimal, -} from '../helpers/utils/conversions.util'; -import { GAS_FORM_ERRORS } from '../helpers/constants/gas'; -import { isLegacyTransaction } from '../helpers/utils/transactions.util'; - -import { useCurrencyDisplay } from './useCurrencyDisplay'; -import { useGasFeeEstimates } from './useGasFeeEstimates'; -import { useUserPreferencedCurrency } from './useUserPreferencedCurrency'; -import { useGasFeeErrors } from './useGasFeeErrors'; - -/** - * Opaque string type representing a decimal (base 10) number in GWEI - * @typedef {`${number}`} DecGweiString - */ - -/** - * String value representing the active estimate level to use - * @typedef {'low' | 'medium' | 'high'} EstimateLevel - */ - -/** - * Pulls out gasPrice estimate from either of the two gasPrice estimation - * sources, based on the gasEstimateType and current estimateToUse. - * @param {{import( - * '@metamask/controllers' - * ).GasFeeState['gasFeeEstimates']}} gasFeeEstimates - estimates returned from - * the controller - * @param {import( - * './useGasFeeEstimates' - * ).GasEstimates} gasEstimateType - type of estimate returned from controller - * @param {EstimateLevel} estimateToUse - current estimate level to use - * @returns {[DecGweiString]} - gasPrice estimate to use or null - */ -function getGasPriceEstimate(gasFeeEstimates, gasEstimateType, estimateToUse) { - if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) { - return gasFeeEstimates?.[estimateToUse] ?? '0'; - } else if (gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE) { - return gasFeeEstimates?.gasPrice ?? '0'; - } - return '0'; -} - -/** - * Pulls out gas fee estimate from the estimates returned from controller, - * based on the gasEstimateType and current estimateToUse. - * @param {'maxFeePerGas' | 'maxPriorityFeePerGas'} field - field to select - * @param {{import( - * '@metamask/controllers' - * ).GasFeeState['gasFeeEstimates']}} gasFeeEstimates - estimates returned from - * the controller - * @param {import( - * './useGasFeeEstimates' - * ).GasEstimates} gasEstimateType - type of estimate returned from controller - * @param {EstimateLevel} estimateToUse - current estimate level to use - * @returns {[DecGweiString]} - gas fee estimate to use or null - */ -function getGasFeeEstimate( - field, - gasFeeEstimates, - gasEstimateType, - estimateToUse, - fallback = '0', -) { - if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) { - return gasFeeEstimates?.[estimateToUse]?.[field] ?? String(fallback); - } - return String(fallback); -} - -/** - * @typedef {Object} GasFeeInputReturnType - * @property {DecGweiString} [maxFeePerGas] - the maxFeePerGas input value. - * @property {string} [maxFeePerGasFiat] - the maxFeePerGas converted to the - * user's preferred currency. - * @property {(DecGweiString) => void} setMaxFeePerGas - state setter method to - * update the maxFeePerGas. - * @property {DecGweiString} [maxPriorityFeePerGas] - the maxPriorityFeePerGas - * input value. - * @property {string} [maxPriorityFeePerGasFiat] - the maxPriorityFeePerGas - * converted to the user's preferred currency. - * @property {(DecGweiString) => void} setMaxPriorityFeePerGas - state setter - * method to update the maxPriorityFeePerGas. - * @property {DecGweiString} [gasPrice] - the gasPrice input value. - * @property {(DecGweiString) => void} setGasPrice - state setter method to - * update the gasPrice. - * @property {DecGweiString} gasLimit - the gasLimit input value. - * @property {(DecGweiString) => void} setGasLimit - state setter method to - * update the gasLimit. - * @property {EstimateLevel} [estimateToUse] - the estimate level currently - * selected. This will be null if the user has ejected from using the - * estimates. - * @property {([EstimateLevel]) => void} setEstimateToUse - Setter method for - * choosing which EstimateLevel to use. - * @property {string} [estimatedMinimumFiat] - The amount estimated to be paid - * based on current network conditions. Expressed in user's preferred - * currency. - * @property {string} [estimatedMaximumFiat] - the maximum amount estimated to be - * paid if current network transaction volume increases. Expressed in user's - * preferred currency. - * @property {string} [estimatedMaximumNative] - the maximum amount estimated to - * be paid if the current network transaction volume increases. Expressed in - * the network's native currency. - */ - -/** - * Uses gasFeeEstimates and state to keep track of user gas fee inputs. - * Will update the gas fee state when estimates update if the user has not yet - * modified the fields. - * @param {EstimateLevel} defaultEstimateToUse - which estimate - * level to default the 'estimateToUse' state variable to. - * @returns {GasFeeInputReturnType & import( - * './useGasFeeEstimates' - * ).GasEstimates} - gas fee input state and the GasFeeEstimates object - */ -export function useGasFeeInputs( - defaultEstimateToUse = 'medium', - transaction, - minimumGasLimit = '0x5208', - editGasMode, -) { - const supportsEIP1559 = - useSelector(checkNetworkAndAccountSupports1559) && - !isLegacyTransaction(transaction?.txParams); - // We need to know whether to show fiat conversions or not, so that we can - // default our fiat values to empty strings if showing fiat is not wanted or - // possible. - const showFiat = useSelector(getShouldShowFiat); - - // We need to know the current network's currency and its decimal precision - // to calculate the amount to display to the user. - const { - currency: primaryCurrency, - numberOfDecimals: primaryNumberOfDecimals, - } = useUserPreferencedCurrency(PRIMARY); - - // For calculating the value of gas fees in the user's preferred currency we - // first have to know what that currency is and its decimal precision - const { - currency: fiatCurrency, - numberOfDecimals: fiatNumberOfDecimals, - } = useUserPreferencedCurrency(SECONDARY); - - // We need the gas estimates from the GasFeeController in the background. - // Calling this hooks initiates polling for new gas estimates and returns the - // current estimate. - const { - gasEstimateType, - gasFeeEstimates, - isGasEstimatesLoading, - estimatedGasFeeTimeBounds, - } = useGasFeeEstimates(); - - const [initialMaxFeePerGas] = useState( - supportsEIP1559 && !transaction?.txParams?.maxFeePerGas - ? Number(hexWEIToDecGWEI(transaction?.txParams?.gasPrice)) - : Number(hexWEIToDecGWEI(transaction?.txParams?.maxFeePerGas)), - ); - - const [initialMaxPriorityFeePerGas] = useState( - supportsEIP1559 && !transaction?.txParams?.maxPriorityFeePerGas - ? initialMaxFeePerGas - : Number(hexWEIToDecGWEI(transaction?.txParams?.maxPriorityFeePerGas)), - ); - const [initialGasPrice] = useState( - Number(hexWEIToDecGWEI(transaction?.txParams?.gasPrice)), - ); - - const [initialMatchingEstimateLevel] = useState( - transaction?.userFeeLevel || null, - ); - const initialFeeParamsAreCustom = - initialMatchingEstimateLevel === 'custom' || - initialMatchingEstimateLevel === null; - - // This hook keeps track of a few pieces of transitional state. It is - // transitional because it is only used to modify a transaction in the - // metamask (background) state tree. - const [maxFeePerGas, setMaxFeePerGas] = useState( - initialMaxFeePerGas && initialFeeParamsAreCustom - ? initialMaxFeePerGas - : null, - ); - const [maxPriorityFeePerGas, setMaxPriorityFeePerGas] = useState( - initialMaxPriorityFeePerGas && initialFeeParamsAreCustom - ? initialMaxPriorityFeePerGas - : null, - ); - const [gasPriceHasBeenManuallySet, setGasPriceHasBeenManuallySet] = useState( - initialMatchingEstimateLevel === 'custom', - ); - const [gasPrice, setGasPrice] = useState( - initialGasPrice && initialFeeParamsAreCustom ? initialGasPrice : null, - ); - const [gasLimit, setGasLimit] = useState( - Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')), - ); - - const userPrefersAdvancedGas = useSelector(getAdvancedInlineGasShown); - const dontDefaultToAnEstimateLevel = - userPrefersAdvancedGas && - transaction?.txParams?.maxPriorityFeePerGas && - transaction?.txParams?.maxFeePerGas; - - const initialEstimateToUse = transaction - ? initialMatchingEstimateLevel - : defaultEstimateToUse; - - const [estimateToUse, setInternalEstimateToUse] = useState( - dontDefaultToAnEstimateLevel ? null : initialEstimateToUse, - ); - - // We specify whether to use the estimate value by checking if the state - // value has been set. The state value is only set by user input and is wiped - // when the user selects an estimate. Default here is '0' to avoid bignumber - // errors in later calculations for nullish values. - const maxFeePerGasToUse = - maxFeePerGas ?? - getGasFeeEstimate( - 'suggestedMaxFeePerGas', - gasFeeEstimates, - gasEstimateType, - estimateToUse, - initialMaxFeePerGas, - ); - - const maxPriorityFeePerGasToUse = - maxPriorityFeePerGas ?? - getGasFeeEstimate( - 'suggestedMaxPriorityFeePerGas', - gasFeeEstimates, - gasEstimateType, - estimateToUse, - initialMaxPriorityFeePerGas, - ); - - const [initialGasPriceEstimates] = useState(gasFeeEstimates); - const gasPriceEstimatesHaveNotChanged = isEqual( - initialGasPriceEstimates, - gasFeeEstimates, - ); - const gasPriceToUse = - gasPrice !== null && - (gasPriceHasBeenManuallySet || - gasPriceEstimatesHaveNotChanged || - isLegacyTransaction(transaction?.txParams)) - ? gasPrice - : getGasPriceEstimate( - gasFeeEstimates, - gasEstimateType, - estimateToUse || defaultEstimateToUse, - ); - - // We have two helper methods that take an object that can have either - // gasPrice OR the EIP-1559 fields on it, plus gasLimit. This object is - // conditionally set to the appropriate fields to compute the minimum - // and maximum cost of a transaction given the current estimates or selected - // gas fees. - - const gasSettings = { - gasLimit: decimalToHex(gasLimit), - }; - if (supportsEIP1559) { - gasSettings.maxFeePerGas = maxFeePerGasToUse - ? decGWEIToHexWEI(maxFeePerGasToUse) - : decGWEIToHexWEI(gasPriceToUse || '0'); - gasSettings.maxPriorityFeePerGas = maxPriorityFeePerGasToUse - ? decGWEIToHexWEI(maxPriorityFeePerGasToUse) - : gasSettings.maxFeePerGas; - gasSettings.baseFeePerGas = decGWEIToHexWEI( - gasFeeEstimates.estimatedBaseFee ?? '0', - ); - } else if (gasEstimateType === GAS_ESTIMATE_TYPES.NONE) { - gasSettings.gasPrice = '0x0'; - } else { - gasSettings.gasPrice = decGWEIToHexWEI(gasPriceToUse); - } - - // The maximum amount this transaction will cost - const maximumCostInHexWei = getMaximumGasTotalInHexWei(gasSettings); - - // If in swaps, we want to calculate the minimum gas fee differently than the max - const minGasSettings = {}; - if (editGasMode === EDIT_GAS_MODES.SWAPS) { - minGasSettings.gasLimit = decimalToHex(minimumGasLimit); - } - - // The minimum amount this transaction will cost's - const minimumCostInHexWei = getMinimumGasTotalInHexWei({ - ...gasSettings, - ...minGasSettings, - }); - - // We need to display the estimated fiat currency impact of the - // maxPriorityFeePerGas field to the user. This hook calculates that amount. - const [, { value: maxPriorityFeePerGasFiat }] = useCurrencyDisplay( - addHexPrefix( - multiplyCurrencies(maxPriorityFeePerGasToUse, gasLimit, { - toNumericBase: 'hex', - fromDenomination: 'GWEI', - toDenomination: 'WEI', - multiplicandBase: 10, - multiplierBase: 10, - }), - ), - { - numberOfDecimals: fiatNumberOfDecimals, - currency: fiatCurrency, - }, - ); - - // We need to display thee estimated fiat currency impact of the maxFeePerGas - // field to the user. This hook calculates that amount. This also works for - // the gasPrice amount because in legacy transactions cost is always gasPrice - // * gasLimit. - const [, { value: maxFeePerGasFiat }] = useCurrencyDisplay( - maximumCostInHexWei, - { - numberOfDecimals: fiatNumberOfDecimals, - currency: fiatCurrency, - }, - ); - - // We need to display the total amount of native currency will be expended - // given the selected gas fees. - const [estimatedMaximumNative] = useCurrencyDisplay(maximumCostInHexWei, { - numberOfDecimals: primaryNumberOfDecimals, - currency: primaryCurrency, - }); - - const [estimatedMinimumNative] = useCurrencyDisplay(minimumCostInHexWei, { - numberOfDecimals: primaryNumberOfDecimals, - currency: primaryCurrency, - }); - - // We also need to display our closest estimate of the low end of estimation - // in fiat. - const [, { value: estimatedMinimumFiat }] = useCurrencyDisplay( - minimumCostInHexWei, - { - numberOfDecimals: fiatNumberOfDecimals, - currency: fiatCurrency, - }, - ); - - const { - gasErrors, - hasGasErrors, - gasWarnings, - balanceError, - estimatesUnavailableWarning, - } = useGasFeeErrors({ - transaction, - gasEstimateType, - gasFeeEstimates, - gasLimit, - gasPriceToUse, - isGasEstimatesLoading, - maxPriorityFeePerGasToUse, - maxFeePerGasToUse, - minimumCostInHexWei, - minimumGasLimit, - }); - - const handleGasLimitOutOfBoundError = useCallback(() => { - if (gasErrors.gasLimit === GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS) { - const transactionGasLimitDec = hexToDecimal(transaction?.txParams?.gas); - const minimumGasLimitDec = hexToDecimal(minimumGasLimit); - setGasLimit( - transactionGasLimitDec > minimumGasLimitDec - ? transactionGasLimitDec - : minimumGasLimitDec, - ); - } - }, [minimumGasLimit, gasErrors.gasLimit, transaction]); - // When a user selects an estimate level, it will wipe out what they have - // previously put in the inputs. This returns the inputs to the estimated - // values at the level specified. - const setEstimateToUse = (estimateLevel) => { - setInternalEstimateToUse(estimateLevel); - handleGasLimitOutOfBoundError(); - setMaxFeePerGas(null); - setMaxPriorityFeePerGas(null); - setGasPrice(null); - setGasPriceHasBeenManuallySet(false); - }; - - return { - maxFeePerGas: maxFeePerGasToUse, - maxFeePerGasFiat: showFiat ? maxFeePerGasFiat : '', - setMaxFeePerGas, - maxPriorityFeePerGas: maxPriorityFeePerGasToUse, - maxPriorityFeePerGasFiat: showFiat ? maxPriorityFeePerGasFiat : '', - setMaxPriorityFeePerGas, - gasPrice: gasPriceToUse, - setGasPrice, - gasLimit, - setGasLimit, - estimateToUse, - setEstimateToUse, - estimatedMinimumFiat: showFiat ? estimatedMinimumFiat : '', - estimatedMaximumFiat: showFiat ? maxFeePerGasFiat : '', - estimatedMaximumNative, - estimatedMinimumNative, - isGasEstimatesLoading, - gasFeeEstimates, - gasEstimateType, - estimatedGasFeeTimeBounds, - onManualChange: () => { - setInternalEstimateToUse('custom'); - handleGasLimitOutOfBoundError(); - // Restore existing values - setGasPrice(gasPriceToUse); - setGasLimit(gasLimit); - setMaxFeePerGas(maxFeePerGasToUse); - setMaxPriorityFeePerGas(maxPriorityFeePerGasToUse); - setGasPriceHasBeenManuallySet(true); - }, - estimatedBaseFee: gasSettings.baseFeePerGas, - gasErrors, - hasGasErrors, - gasWarnings, - balanceError, - estimatesUnavailableWarning, - }; -} diff --git a/ui/hooks/useUserPreferencedCurrency.js b/ui/hooks/useUserPreferencedCurrency.js index c41195345..a4716e43f 100644 --- a/ui/hooks/useUserPreferencedCurrency.js +++ b/ui/hooks/useUserPreferencedCurrency.js @@ -49,7 +49,7 @@ export function useUserPreferencedCurrency(type, opts = {}) { ) { // Display ETH currency = nativeCurrency || ETH; - numberOfDecimals = opts.numberOfDecimals || opts.ethNumberOfDecimals || 6; + numberOfDecimals = opts.numberOfDecimals || opts.ethNumberOfDecimals || 8; } else if ( (type === SECONDARY && useNativeCurrencyAsPrimaryCurrency) || (type === PRIMARY && !useNativeCurrencyAsPrimaryCurrency) diff --git a/ui/hooks/useUserPreferencedCurrency.test.js b/ui/hooks/useUserPreferencedCurrency.test.js index 62030b7e3..32f987340 100644 --- a/ui/hooks/useUserPreferencedCurrency.test.js +++ b/ui/hooks/useUserPreferencedCurrency.test.js @@ -20,7 +20,7 @@ const tests = [ }, result: { currency: 'ETH', - numberOfDecimals: 6, + numberOfDecimals: 8, }, }, { @@ -82,7 +82,7 @@ const tests = [ }, result: { currency: 'ETH', - numberOfDecimals: 6, + numberOfDecimals: 8, }, }, { diff --git a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js index 6226f6e22..a00a46e0d 100644 --- a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js +++ b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js @@ -1,19 +1,29 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; +import copyToClipboard from 'copy-to-clipboard'; +import { getTokenTrackerLink, getAccountLink } from '@metamask/etherscan-link'; import UrlIcon from '../../../components/ui/url-icon'; import { addressSummary, getURLHostName } from '../../../helpers/utils/util'; import { formatCurrency } from '../../../helpers/utils/confirm-tx.util'; -import { ConfirmPageContainerWarning } from '../../../components/app/confirm-page-container/confirm-page-container-content'; +import { isBeta } from '../../../helpers/utils/build-types'; +import { ellipsify } from '../../send/send.utils'; import Typography from '../../../components/ui/typography'; +import Box from '../../../components/ui/box'; +import Button from '../../../components/ui/button'; +import MetaFoxLogo from '../../../components/ui/metafox-logo'; +import Identicon from '../../../components/ui/identicon'; +import CopyIcon from '../../../components/ui/icon/copy-icon.component'; import { TYPOGRAPHY, FONT_WEIGHT, BLOCK_SIZES, JUSTIFY_CONTENT, + COLORS, + DISPLAY, } from '../../../helpers/constants/design-system'; -import Box from '../../../components/ui/box'; -import Button from '../../../components/ui/button'; +import { SECOND } from '../../../../shared/constants/time'; +import { ConfirmPageContainerWarning } from '../../../components/app/confirm-page-container/confirm-page-container-content'; import LedgerInstructionField from '../../../components/app/ledger-instruction-field'; export default class ConfirmApproveContent extends Component { @@ -47,10 +57,15 @@ export default class ConfirmApproveContent extends Component { warning: PropTypes.string, txData: PropTypes.object, fromAddressIsLedger: PropTypes.bool, + tokenImage: PropTypes.string, + chainId: PropTypes.string, + rpcPrefs: PropTypes.object, + isContract: PropTypes.bool, }; state = { showFullTxDetails: false, + copied: false, }; renderApproveContentCard({ @@ -132,8 +147,11 @@ export default class ConfirmApproveContent extends Component { tokenSymbol, origin, toAddress, + isContract, } = this.props; - + const displayedAddress = isContract + ? `${t('contract')} (${addressSummary(toAddress)})` + : addressSummary(toAddress); return (
@@ -141,7 +159,7 @@ export default class ConfirmApproveContent extends Component {
- {t('amountWithColon')} + {t('approvedAmountWithColon')}
{`${Number(customTokenAmount || tokenAmount)} ${tokenSymbol}`} @@ -149,10 +167,31 @@ export default class ConfirmApproveContent extends Component {
- {t('toWithColon')} + {t('grantedToWithColon')}
- {addressSummary(toAddress)} + {`${displayedAddress}`} +
+
+
@@ -243,6 +282,11 @@ export default class ConfirmApproveContent extends Component { warning, txData, fromAddressIsLedger, + tokenImage, + toAddress, + chainId, + rpcPrefs, + isContract, } = this.props; const { showFullTxDetails } = this.state; @@ -257,20 +301,104 @@ export default class ConfirmApproveContent extends Component {
)} -
- -
+ + + + + + + + {getURLHostName(origin)} + + +
- {t('allowOriginSpendToken', [origin, tokenSymbol])} + {t('allowSpendToken', [tokenSymbol])}
- {t('trustSiteApprovePermission', [origin, tokenSymbol])} + {t('trustSiteApprovePermission', [ + isContract + ? t('contract').toLowerCase() + : t('account').toLowerCase(), + ])}
+ + + + + {ellipsify(toAddress)} + + + + +
{this.renderApproveContentCard({ symbol: , - title: 'Transaction Fee', + title: t('transactionFee'), showEdit: true, onEditClick: showCustomizeGasModal, content: this.renderTransactionDetailsContent(), @@ -364,7 +492,7 @@ export default class ConfirmApproveContent extends Component {
{this.renderApproveContentCard({ symbol: , - title: 'Permission', + title: t('permissionRequest'), content: this.renderPermissionContent(), showEdit: true, onEditClick: () => diff --git a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js new file mode 100644 index 000000000..ab3b94b20 --- /dev/null +++ b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js @@ -0,0 +1,84 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; +import { fireEvent } from '@testing-library/react'; +import { renderWithProvider } from '../../../../test/jest/rendering'; +import ConfirmApproveContent from '.'; + +const renderComponent = (props) => { + const store = configureMockStore([])({ metamask: {} }); + return renderWithProvider(, store); +}; + +const props = { + decimals: 16, + siteImage: 'https://metamask.github.io/test-dapp/metamask-fox.svg', + customTokenAmount: '10', + tokenAmount: '10', + origin: 'https://metamask.github.io/test-dapp/', + tokenSymbol: 'TST', + tokenImage: 'https://metamask.github.io/test-dapp/metamask-fox.svg', + tokenBalance: '15', + showCustomizeGasModal: jest.fn(), + showEditApprovalPermissionModal: jest.fn(), + data: + '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170', + toAddress: '0x9bc5baf874d2da8d216ae9f137804184ee5afef4', + currentCurrency: 'TST', + nativeCurrency: 'ETH', + ethTransactionTotal: '20', + fiatTransactionTotal: '10', + useNonceField: true, + nextNonce: 1, + customNonceValue: '2', + showCustomizeNonceModal: jest.fn(), + chainId: '1337', + rpcPrefs: {}, + isContract: true, +}; + +describe('ConfirmApproveContent Component', () => { + it('should render Confirm approve page correctly', () => { + const { queryByText, getByText, getAllByText } = renderComponent(props); + expect(queryByText('metamask.github.io')).toBeInTheDocument(); + expect( + queryByText('Give permission to access your TST?'), + ).toBeInTheDocument(); + expect( + queryByText( + 'By granting permission, you are allowing the following contract to access your funds', + ), + ).toBeInTheDocument(); + expect(queryByText('0x9bc5...fef4')).toBeInTheDocument(); + expect(queryByText('View full transaction details')).toBeInTheDocument(); + + expect(queryByText('Edit Permission')).toBeInTheDocument(); + const editPermission = getByText('Edit Permission'); + fireEvent.click(editPermission); + expect(props.showEditApprovalPermissionModal).toHaveBeenCalledTimes(1); + + const editButtons = getAllByText('Edit'); + + expect(queryByText('Transaction Fee')).toBeInTheDocument(); + expect( + queryByText('A fee is associated with this request.'), + ).toBeInTheDocument(); + expect(queryByText(`${props.ethTransactionTotal} ETH`)).toBeInTheDocument(); + fireEvent.click(editButtons[0]); + expect(props.showCustomizeGasModal).toHaveBeenCalledTimes(1); + + expect(queryByText('Nonce')).toBeInTheDocument(); + expect(queryByText('2')).toBeInTheDocument(); + fireEvent.click(editButtons[1]); + expect(props.showCustomizeNonceModal).toHaveBeenCalledTimes(1); + + const showHideTxDetails = getByText('View full transaction details'); + expect(queryByText('Permission Request')).not.toBeInTheDocument(); + expect(queryByText('Approved Amount:')).not.toBeInTheDocument(); + expect(queryByText('Granted To:')).not.toBeInTheDocument(); + fireEvent.click(showHideTxDetails); + expect(getByText('Permission Request')).toBeInTheDocument(); + expect(getByText('Approved Amount:')).toBeInTheDocument(); + expect(getByText('Granted To:')).toBeInTheDocument(); + expect(getByText('0x9bc5...fef4')).toBeInTheDocument(); + }); +}); diff --git a/ui/pages/confirm-approve/confirm-approve-content/index.scss b/ui/pages/confirm-approve/confirm-approve-content/index.scss index b74a1350d..6422693e7 100644 --- a/ui/pages/confirm-approve/confirm-approve-content/index.scss +++ b/ui/pages/confirm-approve/confirm-approve-content/index.scss @@ -5,18 +5,83 @@ width: 100%; font-style: normal; - &__identicon-wrapper { + &__warning { + padding: 0 24px 16px 24px; + } + + &__icon-display-content { display: flex; - width: 100%; + height: 51px; + width: 65%; + margin-top: 16px; + padding: 12px 12px 14px 12px; + border: 1px solid $ui-1; + box-sizing: border-box; + border-radius: 100px; + align-items: center; + position: relative; + } + + &__metafoxlogo, + &__siteinfo { + position: absolute; + } + + &__metafoxlogo { + .app-header { + &__metafox-logo--horizontal { + display: none; + } + + &__metafox-logo--icon { + display: block; + } + } + } + + &__siteinfo { + left: 39px; + } + + &__address-display-content { + display: flex; + height: 27px; + padding: 12px 12px 14px 12px; + background-color: $ui-1; + border-radius: 100px; + align-items: center; justify-content: center; - margin-top: 22px; - padding-left: 24px; - padding-right: 24px; } - &__identicon { - width: 48px; - height: 48px; + &__siteimage-identicon { + width: 33px; + height: 33px; + border: solid #fff; + } + + &__address-identicon { + margin: 4px 8px 0 4px; + } + + &__copy-address, + &__etherscan-link { + padding: 0 0 0 8px; + } + + &__etherscan-link { + img { + width: 9px; + height: 9px; + } + } + + .app-header__logo-container { + margin-right: 0; + } + + .app-header__metafox-logo--icon { + height: 33px; + width: 33px; } &__full-tx-content { diff --git a/ui/pages/confirm-approve/confirm-approve.js b/ui/pages/confirm-approve/confirm-approve.js index 11ee15cdc..b0e0707bf 100644 --- a/ui/pages/confirm-approve/confirm-approve.js +++ b/ui/pages/confirm-approve/confirm-approve.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import ConfirmTransactionBase from '../confirm-transaction-base'; @@ -14,6 +14,7 @@ import { getTokenAddressParam, getTokenValueParam, } from '../../helpers/utils/token-util'; +import { readAddressAsContract } from '../../../shared/modules/contract-utils'; import { useTokenTracker } from '../../hooks/useTokenTracker'; import { getTokens, @@ -28,6 +29,8 @@ import { getUseNonceField, getCustomNonceValue, getNextSuggestedNonce, + getCurrentChainId, + getRpcPrefsForCurrentProvider, } from '../../selectors'; import { useApproveTransaction } from '../../hooks/useApproveTransaction'; @@ -59,6 +62,8 @@ export default function ConfirmApprove() { const useNonceField = useSelector(getUseNonceField); const nextNonce = useSelector(getNextSuggestedNonce); const customNonceValue = useSelector(getCustomNonceValue); + const chainId = useSelector(getCurrentChainId); + const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider); const fromAddressIsLedger = useSelector(isAddressLedgerByFromAddress(from)); @@ -82,6 +87,7 @@ export default function ConfirmApprove() { const tokenSymbol = currentToken?.symbol; const decimals = Number(currentToken?.decimals); + const tokenImage = currentToken?.image; const tokenData = getTokenData(data); const tokenValue = getTokenValueParam(tokenData); const toAddress = getTokenAddressParam(tokenData); @@ -124,6 +130,19 @@ export default function ConfirmApprove() { prevCustomNonce.current = customNonceValue; prevNonce.current = nextNonce; }, [customNonceValue, nextNonce]); + + const [isContract, setIsContract] = useState(false); + const checkIfContract = useCallback(async () => { + const { isContractAddress } = await readAddressAsContract( + global.eth, + toAddress, + ); + setIsContract(isContractAddress); + }, [setIsContract, toAddress]); + useEffect(() => { + checkIfContract(); + }, [checkIfContract]); + const { origin } = transaction; const formattedOrigin = origin ? origin[0].toUpperCase() + origin.slice(1) @@ -157,6 +176,7 @@ export default function ConfirmApprove() { tokenAmount={tokenAmount} origin={formattedOrigin} tokenSymbol={tokenSymbol} + tokenImage={tokenImage} tokenBalance={tokenBalance} showCustomizeGasModal={approveTransaction} showEditApprovalPermissionModal={({ @@ -219,6 +239,9 @@ export default function ConfirmApprove() { warning={submitWarning} txData={transaction} fromAddressIsLedger={fromAddressIsLedger} + chainId={chainId} + rpcPrefs={rpcPrefs} + isContract={isContract} /> {showCustomizeGasPopover && ( process.env.IN_TEST === 'true' ? null : ; @@ -409,127 +415,144 @@ export default class ConfirmTransactionBase extends Component { return (
+ {EIP_1559_V2 && } this.handleEditGas()} rows={[ - - {isMultiLayerFeeNetwork - ? t('transactionDetailLayer2GasHeading') - : t('transactionDetailGasHeading')} - - - - - ) : ( - <> - {isMultiLayerFeeNetwork - ? t('transactionDetailLayer2GasHeading') - : t('transactionDetailGasHeading')} - -

- {t('transactionDetailGasTooltipIntro', [ - isMainnet ? t('networkNameEthereum') : '', - ])} -

-

{t('transactionDetailGasTooltipExplanation')}

-

- - {t('transactionDetailGasTooltipConversion')} - -

- - } - position="top" - > - -
- - ) - } - detailTitleColor={COLORS.BLACK} - detailText={ - !isMultiLayerFeeNetwork && ( + EIP_1559_V2 ? ( + + ) : ( + + {isMultiLayerFeeNetwork + ? t('transactionDetailLayer2GasHeading') + : t('transactionDetailGasHeading')} + + + + + ) : ( + <> + {isMultiLayerFeeNetwork + ? t('transactionDetailLayer2GasHeading') + : t('transactionDetailGasHeading')} + +

+ {t('transactionDetailGasTooltipIntro', [ + isMainnet ? t('networkNameEthereum') : '', + ])} +

+

{t('transactionDetailGasTooltipExplanation')}

+

+ + {t('transactionDetailGasTooltipConversion')} + +

+ + } + position="top" + > + +
+ + ) + } + detailTitleColor={COLORS.BLACK} + detailText={ + !isMultiLayerFeeNetwork && ( +
+ {renderHeartBeatIfNotInTest()} + +
+ ) + } + detailTotal={
{renderHeartBeatIfNotInTest()} -
- ) - } - detailTotal={ -
- {renderHeartBeatIfNotInTest()} - -
- } - subText={ - !isMultiLayerFeeNetwork && - t('editGasSubTextFee', [ - - {t('editGasSubTextFeeLabel')} - , -
- {renderHeartBeatIfNotInTest()} - -
, - ]) - } - subTitle={ - <> - {txData.dappSuggestedGasFees ? ( - + } + subText={ + !isMultiLayerFeeNetwork && + t('editGasSubTextFee', [ + + {t('editGasSubTextFeeLabel')} + , +
- {t('transactionDetailDappGasMoreInfo')} - - ) : ( - '' - )} - {supportsEIP1559 && ( - - )} - - } - />, + {renderHeartBeatIfNotInTest()} + +
, + ]) + } + subTitle={ + <> + {txData.dappSuggestedGasFees ? ( + + {t('transactionDetailDappGasMoreInfo')} + + ) : ( + '' + )} + {supportsEIP1559 && ( + + )} + + } + /> + ), isMultiLayerFeeNetwork && ( { const noGasPrice = !supportsEIP1559 && getNoGasPriceFetched(state); const { useNativeCurrencyAsPrimaryCurrency } = getPreferences(state); const gasFeeIsCustom = - fullTxData.userFeeLevel === 'custom' || + fullTxData.userFeeLevel === CUSTOM_GAS_ESTIMATE || txParamsAreDappSuggested(fullTxData); const fromAddressIsLedger = isAddressLedger(state, fromAddress); const nativeCurrency = getNativeCurrency(state); diff --git a/ui/pages/confirm-transaction-base/gas-details-item/gas-details-item.js b/ui/pages/confirm-transaction-base/gas-details-item/gas-details-item.js new file mode 100644 index 000000000..6ac1e0be0 --- /dev/null +++ b/ui/pages/confirm-transaction-base/gas-details-item/gas-details-item.js @@ -0,0 +1,149 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import { COLORS } from '../../../helpers/constants/design-system'; +import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'; +import { hexWEIToDecGWEI } from '../../../helpers/utils/conversions.util'; +import { useI18nContext } from '../../../hooks/useI18nContext'; + +import Box from '../../../components/ui/box'; +import Typography from '../../../components/ui/typography/typography'; +import GasTiming from '../../../components/app/gas-timing/gas-timing.component'; +import I18nValue from '../../../components/ui/i18n-value'; +import InfoTooltip from '../../../components/ui/info-tooltip/info-tooltip'; +import LoadingHeartBeat from '../../../components/ui/loading-heartbeat'; +import TransactionDetailItem from '../../../components/app/transaction-detail-item/transaction-detail-item.component'; +import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display'; +import { useGasFeeContext } from '../../../contexts/gasFee'; + +const HeartBeat = () => + process.env.IN_TEST === 'true' ? null : ; + +const GasDetailsItem = ({ + hexMaximumTransactionFee, + hexMinimumTransactionFee, + isMainnet, + maxFeePerGas, + maxPriorityFeePerGas, + supportsEIP1559, + txData, + useNativeCurrencyAsPrimaryCurrency, +}) => { + const t = useI18nContext(); + const { estimateToUse } = useGasFeeContext(); + + return ( + + + + + + () + + + + {t('transactionDetailGasTooltipIntro', [ + isMainnet ? t('networkNameEthereum') : '', + ])} + + + {t('transactionDetailGasTooltipExplanation')} + + + + {t('transactionDetailGasTooltipConversion')} + + + + } + position="bottom" + /> + + } + detailTitleColor={COLORS.BLACK} + detailText={ +
+ + +
+ } + detailTotal={ +
+ + +
+ } + subText={t('editGasSubTextFee', [ + + + + {estimateToUse === 'high' && '⚠ '} + + + +
+ + +
+
, + ])} + subTitle={ + supportsEIP1559 && ( + + ) + } + /> + ); +}; + +GasDetailsItem.propTypes = { + hexMaximumTransactionFee: PropTypes.string, + hexMinimumTransactionFee: PropTypes.string, + isMainnet: PropTypes.bool, + maxFeePerGas: PropTypes.string, + maxPriorityFeePerGas: PropTypes.string, + supportsEIP1559: PropTypes.bool, + txData: PropTypes.object, + useNativeCurrencyAsPrimaryCurrency: PropTypes.bool, +}; + +export default GasDetailsItem; diff --git a/ui/pages/confirm-transaction-base/gas-details-item/gas-details-item.scss b/ui/pages/confirm-transaction-base/gas-details-item/gas-details-item.scss new file mode 100644 index 000000000..e65e3f362 --- /dev/null +++ b/ui/pages/confirm-transaction-base/gas-details-item/gas-details-item.scss @@ -0,0 +1,21 @@ +.gas-details-item { + &__estimate { + font-weight: 400; + font-style: italic; + font-size: 12px; + color: $Grey-500; + line-height: inherit; + } + + &__gas-fee-warning { + color: $secondary-1; + } + + &__gasfee-label { + font-weight: bold; + } + + &__currency-container { + position: relative; + } +} diff --git a/ui/pages/confirm-transaction-base/gas-details-item/gas-details-item.test.js b/ui/pages/confirm-transaction-base/gas-details-item/gas-details-item.test.js new file mode 100644 index 000000000..2d8c97fab --- /dev/null +++ b/ui/pages/confirm-transaction-base/gas-details-item/gas-details-item.test.js @@ -0,0 +1,64 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; + +import { ETH } from '../../../helpers/constants/common'; +import { GasFeeContextProvider } from '../../../contexts/gasFee'; +import { renderWithProvider } from '../../../../test/jest'; +import configureStore from '../../../store/store'; + +import GasDetailsItem from './gas-details-item'; + +jest.mock('../../../store/actions', () => ({ + disconnectGasFeeEstimatePoller: jest.fn(), + getGasFeeEstimatesAndStartPolling: jest + .fn() + .mockImplementation(() => Promise.resolve()), + addPollingTokenToAppState: jest.fn(), +})); + +const render = (props) => { + const store = configureStore({ + metamask: { + nativeCurrency: ETH, + preferences: { + useNativeCurrencyAsPrimaryCurrency: true, + }, + provider: {}, + cachedBalances: {}, + accounts: { + '0xAddress': { + address: '0xAddress', + balance: '0x176e5b6f173ebe66', + }, + }, + selectedAddress: '0xAddress', + }, + }); + + return renderWithProvider( + + + , + store, + ); +}; + +describe('GasDetailsItem', () => { + it('should render label', () => { + render(); + expect(screen.queryByText('Gas')).toBeInTheDocument(); + expect(screen.queryByText('(estimated)')).toBeInTheDocument(); + expect(screen.queryByText('Max fee:')).toBeInTheDocument(); + expect(screen.queryByText('ETH')).toBeInTheDocument(); + }); + + it('should show warning icon if estimates are high', () => { + render({ defaultEstimateToUse: 'high' }); + expect(screen.queryByText('⚠ Max fee:')).toBeInTheDocument(); + }); + + it('should not show warning icon if estimates are not high', () => { + render({ defaultEstimateToUse: 'low' }); + expect(screen.queryByText('Max fee:')).toBeInTheDocument(); + }); +}); diff --git a/ui/pages/confirm-transaction-base/gas-details-item/index.js b/ui/pages/confirm-transaction-base/gas-details-item/index.js new file mode 100644 index 000000000..f6bacdbeb --- /dev/null +++ b/ui/pages/confirm-transaction-base/gas-details-item/index.js @@ -0,0 +1 @@ +export { default } from './gas-details-item'; diff --git a/ui/pages/confirm-transaction-base/low-priority-message/index.js b/ui/pages/confirm-transaction-base/low-priority-message/index.js new file mode 100644 index 000000000..7b2838d48 --- /dev/null +++ b/ui/pages/confirm-transaction-base/low-priority-message/index.js @@ -0,0 +1 @@ +export { default } from './low-priority-message'; diff --git a/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.js b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.js new file mode 100644 index 000000000..41bb5d132 --- /dev/null +++ b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.js @@ -0,0 +1,24 @@ +import React from 'react'; + +import ActionableMessage from '../../../components/ui/actionable-message/actionable-message'; +import { useGasFeeContext } from '../../../contexts/gasFee'; +import { useI18nContext } from '../../../hooks/useI18nContext'; + +const LowPriorityMessage = () => { + const { estimateToUse } = useGasFeeContext(); + const t = useI18nContext(); + + if (estimateToUse !== 'low') return null; + return ( +
+ +
+ ); +}; + +export default LowPriorityMessage; diff --git a/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.scss b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.scss new file mode 100644 index 000000000..1a99a03af --- /dev/null +++ b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.scss @@ -0,0 +1,3 @@ +.low-priority-message { + margin-top: 20px; +} diff --git a/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.test.js b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.test.js new file mode 100644 index 000000000..9d8e233f3 --- /dev/null +++ b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.test.js @@ -0,0 +1,59 @@ +import React from 'react'; + +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import { ETH } from '../../../helpers/constants/common'; +import { GasFeeContextProvider } from '../../../contexts/gasFee'; +import configureStore from '../../../store/store'; + +import LowPriorityMessage from './low-priority-message'; + +jest.mock('../../../store/actions', () => ({ + disconnectGasFeeEstimatePoller: jest.fn(), + getGasFeeEstimatesAndStartPolling: jest + .fn() + .mockImplementation(() => Promise.resolve()), + addPollingTokenToAppState: jest.fn(), +})); + +const render = (props) => { + const store = configureStore({ + metamask: { + nativeCurrency: ETH, + preferences: { + useNativeCurrencyAsPrimaryCurrency: true, + }, + provider: {}, + cachedBalances: {}, + accounts: { + '0xAddress': { + address: '0xAddress', + balance: '0x176e5b6f173ebe66', + }, + }, + selectedAddress: '0xAddress', + }, + }); + + return renderWithProvider( + + + , + store, + ); +}; + +describe('LowPriorityMessage', () => { + it('should returning warning message for low gas estimate', () => { + render({ transaction: { userFeeLevel: 'low' } }); + expect( + document.getElementsByClassName('actionable-message--warning'), + ).toHaveLength(1); + }); + + it('should return null for gas estimate other than low', () => { + render({ transaction: { userFeeLevel: 'high' } }); + expect( + document.getElementsByClassName('actionable-message--warning'), + ).toHaveLength(0); + }); +}); diff --git a/ui/pages/create-account/connect-hardware/account-list.js b/ui/pages/create-account/connect-hardware/account-list.js index f0b8d14d9..caf7ff580 100644 --- a/ui/pages/create-account/connect-hardware/account-list.js +++ b/ui/pages/create-account/connect-hardware/account-list.js @@ -5,13 +5,11 @@ import { getAccountLink } from '@metamask/etherscan-link'; import Button from '../../../components/ui/button'; import Checkbox from '../../../components/ui/check-box'; import Dropdown from '../../../components/ui/dropdown'; -import Popover from '../../../components/ui/popover'; import { getURLHostName } from '../../../helpers/utils/util'; class AccountList extends Component { state = { - showPopover: false, pathValue: null, }; @@ -33,7 +31,7 @@ class AccountList extends Component { } renderHdPathSelector() { - const { selectedPath, hdPaths } = this.props; + const { device, selectedPath, hdPaths, onPathChange } = this.props; const { pathValue } = this.state; return ( @@ -45,10 +43,11 @@ class AccountList extends Component {
{ this.setPath(value); + onPathChange(value); }} />
@@ -61,26 +60,18 @@ class AccountList extends Component { } renderHeader() { + const { device } = this.props; + const shouldShowHDPaths = + device.toLowerCase() === 'ledger' || device.toLowerCase() === 'lattice'; return (

{this.context.t('selectAnAccount')}

+ {shouldShowHDPaths ? this.renderHdPathSelector() : null}

{this.context.t('selectAnAccount')}

-

- {this.context.t('selectAnAccountHelp')} - {this.context.t('selectAnAccountHelpDirections', [ - , - ])} -

); } @@ -228,44 +219,7 @@ class AccountList extends Component { ); } - renderSelectPathPopover() { - const { pathValue } = this.state; - const { onPathChange } = this.props; - - const footer = ( -
- - -
- ); - - return ( - - {this.renderHdPathSelector()} - - ); - } - render() { - const { showPopover } = this.state; return (
{this.renderHeader()} @@ -273,7 +227,6 @@ class AccountList extends Component { {this.renderPagination()} {this.renderButtons()} {this.renderForgetDevice()} - {showPopover ? this.renderSelectPathPopover() : null}
); } diff --git a/ui/pages/create-account/connect-hardware/index.js b/ui/pages/create-account/connect-hardware/index.js index 66082de07..6b6c3bab5 100644 --- a/ui/pages/create-account/connect-hardware/index.js +++ b/ui/pages/create-account/connect-hardware/index.js @@ -20,12 +20,32 @@ const U2F_ERROR = 'U2F'; const LEDGER_LIVE_PATH = `m/44'/60'/0'/0/0`; const MEW_PATH = `m/44'/60'/0'`; const BIP44_PATH = `m/44'/60'/0'/0`; -const HD_PATHS = [ +const LEDGER_HD_PATHS = [ { name: 'Ledger Live', value: LEDGER_LIVE_PATH }, { name: 'Legacy (MEW / MyCrypto)', value: MEW_PATH }, { name: `BIP44 Standard (e.g. MetaMask, Trezor)`, value: BIP44_PATH }, ]; +const LATTICE_STANDARD_BIP44_PATH = `m/44'/60'/0'/0/x`; +const LATTICE_LEDGER_LIVE_PATH = `m/44'/60'/x'/0/0`; +const LATTICE_MEW_PATH = `m/44'/60'/0'/x`; +const LATTICE_HD_PATHS = [ + { + name: `Standard (${LATTICE_STANDARD_BIP44_PATH})`, + value: LATTICE_STANDARD_BIP44_PATH, + }, + { + name: `Ledger Live (${LATTICE_LEDGER_LIVE_PATH})`, + value: LATTICE_LEDGER_LIVE_PATH, + }, + { name: `Ledger Legacy (${LATTICE_MEW_PATH})`, value: LATTICE_MEW_PATH }, +]; + +const HD_PATHS = { + ledger: LEDGER_HD_PATHS, + lattice: LATTICE_HD_PATHS, +}; + class ConnectHardwareForm extends Component { static contextTypes = { t: PropTypes.func, @@ -56,7 +76,7 @@ class ConnectHardwareForm extends Component { } async checkIfUnlocked() { - for (const device of ['trezor', 'ledger']) { + for (const device of ['trezor', 'ledger', 'lattice']) { const path = this.props.defaultHdPaths[device]; const unlocked = await this.props.checkHardwareStatus(device, path); if (unlocked) { diff --git a/ui/pages/create-account/connect-hardware/select-hardware.js b/ui/pages/create-account/connect-hardware/select-hardware.js index 6b711b176..3f7e6cbe8 100644 --- a/ui/pages/create-account/connect-hardware/select-hardware.js +++ b/ui/pages/create-account/connect-hardware/select-hardware.js @@ -43,6 +43,23 @@ export default class SelectHardware extends Component { ); } + renderConnectToLatticeButton() { + return ( + + ); + } + renderConnectToLedgerButton() { return (
+ } + /> + ) : null} {shouldShowWeb3ShimUsageNotification ? ( + {process.env.COLLECTIBLES_V1 ? ( + + { + console.log('Added NFT'); + }} + /> + + ) : null} { showWhatsNewPopup: getShowWhatsNewPopup(state), showRecoveryPhraseReminder: getShowRecoveryPhraseReminder(state), seedPhraseBackedUp, + newNetworkAdded: getNewNetworkAdded(state), }; }; @@ -141,6 +144,9 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setRecoveryPhraseReminderHasBeenShown()), setRecoveryPhraseReminderLastShown: (lastShown) => dispatch(setRecoveryPhraseReminderLastShown(lastShown)), + setNewNetworkAdded: (newNetwork) => { + dispatch(setNewNetworkAdded(newNetwork)); + }, }); export default compose( diff --git a/ui/pages/home/index.scss b/ui/pages/home/index.scss index d3eaefa78..804c4d8cd 100644 --- a/ui/pages/home/index.scss +++ b/ui/pages/home/index.scss @@ -133,4 +133,23 @@ color: $primary-1; } } + + &__new-network-notification { + margin-bottom: 50px; + } + + &__new-network-notification-message { + display: flex; + flex-direction: row; + + &--image { + margin-right: 10px; + } + } + + &__close { + color: $ui-black; + background: none; + margin-left: 20px; + } } diff --git a/ui/pages/onboarding-flow/create-password/create-password.js b/ui/pages/onboarding-flow/create-password/create-password.js index 66471c67f..fcdbca69a 100644 --- a/ui/pages/onboarding-flow/create-password/create-password.js +++ b/ui/pages/onboarding-flow/create-password/create-password.js @@ -131,6 +131,7 @@ export default function CreatePassword({ >
setTermsChecked(!termsChecked)} checked={termsChecked} /> @@ -190,6 +193,11 @@ export default function CreatePassword({ - diff --git a/ui/pages/onboarding-flow/import-srp/import-srp.js b/ui/pages/onboarding-flow/import-srp/import-srp.js index 7707b5f9b..797aacef4 100644 --- a/ui/pages/onboarding-flow/import-srp/import-srp.js +++ b/ui/pages/onboarding-flow/import-srp/import-srp.js @@ -78,6 +78,7 @@ export default function ImportSRP({ submitSecretRecoveryPhrase }) { />