diff --git a/.circleci/config.yml b/.circleci/config.yml index de5455c9e..f018b76bd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -309,9 +309,11 @@ jobs: path: test-artifacts destination: test-artifacts # important: generate sesify viz AFTER uploading builds as artifacts - - run: - name: build:sesify-viz - command: ./.circleci/scripts/create-sesify-viz + # Temporarily disabled until we can update to a version of `sesify` with + # this fix included: https://github.com/LavaMoat/LavaMoat/pull/121 + # - run: + # name: build:sesify-viz + # command: ./.circleci/scripts/create-sesify-viz - store_artifacts: path: build-artifacts destination: build-artifacts diff --git a/.eslintrc.js b/.eslintrc.js index 68392f022..35541a9ba 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,19 +2,19 @@ module.exports = { root: true, parser: '@babel/eslint-parser', parserOptions: { - 'sourceType': 'module', - 'ecmaVersion': 2017, - 'ecmaFeatures': { - 'experimentalObjectRestSpread': true, - 'impliedStrict': true, - 'modules': true, - 'blockBindings': true, - 'arrowFunctions': true, - 'objectLiteralShorthandMethods': true, - 'objectLiteralShorthandProperties': true, - 'templateStrings': true, - 'classes': true, - 'jsx': true, + sourceType: 'module', + ecmaVersion: 2017, + ecmaFeatures: { + experimentalObjectRestSpread: true, + impliedStrict: true, + modules: true, + blockBindings: true, + arrowFunctions: true, + objectLiteralShorthandMethods: true, + objectLiteralShorthandProperties: true, + templateStrings: true, + classes: true, + jsx: true, }, }, @@ -28,6 +28,9 @@ module.exports = { 'coverage/', 'app/scripts/chromereload.js', 'app/vendor/**', + 'test/e2e/send-eth-with-private-key-test/**', + 'nyc_output/**', + '.vscode/**', ], extends: [ @@ -38,11 +41,7 @@ module.exports = { 'plugin:react-hooks/recommended', ], - plugins: [ - '@babel', - 'react', - 'import', - ], + plugins: ['@babel', 'react', 'import', 'prettier'], globals: { document: 'readonly', @@ -50,6 +49,67 @@ module.exports = { }, rules: { + // Prettier changes and reasoning + + 'prettier/prettier': 'error', + + // Our usage of spaces before *named* function parens is unusual, and + // doesn't match the prettier spec. prettier does not offer an option + // to configure this + 'space-before-function-paren': [ + 'error', + { anonymous: 'always', named: 'never' }, + ], + // Our eslint config has the default setting for this as error. This + // include beforeBlockComment: true, but in order to match the prettier + // spec you have to enable before and after blocks, objects and arrays + // https://github.com/prettier/eslint-config-prettier#lines-around-comment + 'lines-around-comment': [ + 'error', + { + beforeBlockComment: true, + afterLineComment: false, + allowBlockStart: true, + allowBlockEnd: true, + allowObjectStart: true, + allowObjectEnd: true, + allowArrayStart: true, + allowArrayEnd: true, + }, + ], + // Prettier has some opinions on mixed-operators, and there is ongoing work + // to make the output code clear. It is better today then it was when the first + // PR to add prettier. That being said, the workaround for keeping this rule enabled + // requires breaking parts of operations into different variables -- which I believe + // to be worse. https://github.com/prettier/eslint-config-prettier#no-mixed-operators + 'no-mixed-operators': 'off', + // Prettier wraps single line functions with ternaries, etc in parens by default, but + // if the line is long enough it breaks it into a separate line and removes the parens. + // The second behavior conflicts with this rule. There is some guides on the repo about + // how you can keep it enabled: + // https://github.com/prettier/eslint-config-prettier#no-confusing-arrow + // However, in practice this conflicts with prettier adding parens around short lines, + // when autofixing in vscode and others. + 'no-confusing-arrow': 'off', + // There is no configuration in prettier for how it stylizes regexes, which conflicts + // with wrap-regex. + 'wrap-regex': 'off', + // Prettier handles all indentation automagically. it can be configured here + // https://prettier.io/docs/en/options.html#tab-width but the default matches our + // style. + indent: 'off', + // This rule conflicts with the way that prettier breaks code across multiple lines when + // it exceeds the maximum length. Prettier optimizes for readability while simultaneously + // maximizing the amount of code per line. + 'function-paren-newline': 'off', + // This rule throws an error when there is a line break in an arrow function declaration + // but prettier breaks arrow function declarations to be as readable as possible while + // still conforming to the width rules. + 'implicit-arrow-linebreak': 'off', + // This rule would result in an increase in white space in lines with generator functions, + // which impacts prettier's goal of maximizing code per line and readability. There is no + // current workaround. + 'generator-star-spacing': 'off', 'default-param-last': 'off', 'require-atomic-updates': 'off', 'import/no-unassigned-import': 'off', @@ -57,29 +117,32 @@ module.exports = { 'react/no-unused-prop-types': 'error', 'react/no-unused-state': 'error', 'react/jsx-boolean-value': 'error', - 'react/jsx-curly-brace-presence': ['error', { 'props': 'never', 'children': 'never' }], + 'react/jsx-curly-brace-presence': [ + 'error', + { props: 'never', children: 'never' }, + ], 'react/jsx-equals-spacing': 'error', 'react/no-deprecated': 'error', 'react/default-props-match-prop-types': 'error', - 'react/jsx-closing-tag-location': 'error', + 'react/jsx-closing-tag-location': [ + 'error', + { selfClosing: 'tag-aligned', nonEmpty: 'tag-aligned' }, + ], 'react/jsx-no-duplicate-props': 'error', 'react/jsx-closing-bracket-location': 'error', 'react/jsx-first-prop-new-line': ['error', 'multiline'], - 'react/jsx-max-props-per-line': ['error', { 'maximum': 1, 'when': 'multiline' }], - 'react/jsx-tag-spacing': ['error', { - 'closingSlash': 'never', - 'beforeSelfClosing': 'always', - 'afterOpening': 'never', - }], - 'react/jsx-wrap-multilines': ['error', { - 'declaration': 'parens-new-line', - 'assignment': 'parens-new-line', - 'return': 'parens-new-line', - 'arrow': 'parens-new-line', - 'condition': 'parens-new-line', - 'logical': 'parens-new-line', - 'prop': 'parens-new-line', - }], + 'react/jsx-max-props-per-line': [ + 'error', + { maximum: 1, when: 'multiline' }, + ], + 'react/jsx-tag-spacing': [ + 'error', + { + closingSlash: 'never', + beforeSelfClosing: 'always', + afterOpening: 'never', + }, + ], 'no-invalid-this': 'off', '@babel/no-invalid-this': 'error', @@ -93,67 +156,59 @@ module.exports = { 'node/no-unpublished-import': 'off', 'node/no-unpublished-require': 'off', }, - - overrides: [{ - files: [ - 'test/e2e/**/*.js', - ], - rules: { - 'mocha/no-hooks-for-single-case': 'off', + overrides: [ + { + files: ['test/e2e/**/*.js'], + rules: { + 'mocha/no-hooks-for-single-case': 'off', + }, }, - }, { - files: [ - 'app/scripts/migrations/*.js', - '*.stories.js', - ], - rules: { - 'import/no-anonymous-default-export': ['error', { 'allowObject': true }], + { + files: ['app/scripts/migrations/*.js', '*.stories.js'], + rules: { + 'import/no-anonymous-default-export': ['error', { allowObject: true }], + }, }, - }, { - files: [ - 'app/scripts/migrations/*.js', - ], - rules: { - 'node/global-require': 'off', + { + files: ['app/scripts/migrations/*.js'], + rules: { + 'node/global-require': 'off', + }, }, - }, { - files: [ - 'test/**/*-test.js', - 'test/**/*.spec.js', - ], - rules: { - // Mocha will re-assign `this` in a test context - '@babel/no-invalid-this': 'off', + { + files: ['test/**/*-test.js', 'test/**/*.spec.js'], + rules: { + // Mocha will re-assign `this` in a test context + '@babel/no-invalid-this': 'off', + }, }, - }, { - files: [ - 'development/**/*.js', - 'test/e2e/benchmark.js', - 'test/helper.js', - ], - rules: { - 'node/no-process-exit': 'off', - 'node/shebang': 'off', + { + files: ['development/**/*.js', 'test/e2e/benchmark.js', 'test/helper.js'], + rules: { + 'node/no-process-exit': 'off', + 'node/shebang': 'off', + }, }, - }, { - files: [ - '.eslintrc.js', - 'babel.config.js', - 'nyc.config.js', - 'stylelint.config.js', - 'development/**/*.js', - 'test/e2e/**/*.js', - 'test/env.js', - 'test/setup.js', - ], - parserOptions: { - sourceType: 'script', + { + files: [ + '.eslintrc.js', + 'babel.config.js', + 'nyc.config.js', + 'stylelint.config.js', + 'development/**/*.js', + 'test/e2e/**/*.js', + 'test/env.js', + 'test/setup.js', + ], + parserOptions: { + sourceType: 'script', + }, }, - }], + ], settings: { - 'react': { - 'version': 'detect', + react: { + version: 'detect', }, }, } diff --git a/.prettierignore b/.prettierignore index c2dd4a632..5428362b5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,3 +6,4 @@ coverage/ app/vendor/** .nyc_output/** .vscode/** +test/e2e/send-eth-with-private-key-test/** diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 000000000..0d65e758a --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,3 @@ +singleQuote: true +semi: false +trailingComma: all diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f60149e..4b718461b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,40 @@ ## Current Develop Branch - [#9612](https://github.com/MetaMask/metamask-extension/pull/9612): Update main-quote-summary designs/styles +## 8.1.4 Tue Nov 10 2020 +- [#9687](https://github.com/MetaMask/metamask-extension/pull/9687): Allow speeding up of underpriced transactions +- [#9694](https://github.com/MetaMask/metamask-extension/pull/9694): normalize UI component font styles +- [#9695](https://github.com/MetaMask/metamask-extension/pull/9695): normalize app component font styles +- [#9696](https://github.com/MetaMask/metamask-extension/pull/9696): normalize deprecated itcss font styles +- [#9697](https://github.com/MetaMask/metamask-extension/pull/9697): normalize page font styles +- [#9740](https://github.com/MetaMask/metamask-extension/pull/9740): Standardize network settings page +- [#9750](https://github.com/MetaMask/metamask-extension/pull/9750): Make swap arrows accessible, make swaps advanced options accessible +- [#9766](https://github.com/MetaMask/metamask-extension/pull/9766): Use 1px borders on inputs and buttons +- [#9767](https://github.com/MetaMask/metamask-extension/pull/9767): Remove border radius from transfer button +- [#9764](https://github.com/MetaMask/metamask-extension/pull/9764): Update custom RPC network dropdown icons +- [#9763](https://github.com/MetaMask/metamask-extension/pull/9763): Add confirmation for network dropdown delete action +- [#9583](https://github.com/MetaMask/metamask-extension/pull/9583): Use `chainId` for incoming transactions controller +- [#9748](https://github.com/MetaMask/metamask-extension/pull/9748): Autofocus input, improve accessibility of restore page +- [#9778](https://github.com/MetaMask/metamask-extension/pull/9778): Shorten unit input width and use ellipses for overflow +- [#9746](https://github.com/MetaMask/metamask-extension/pull/9746): Make the login screen's Restore and Import links accessible +- [#9780](https://github.com/MetaMask/metamask-extension/pull/9780): Display decimal chain ID in network form +- [#9599](https://github.com/MetaMask/metamask-extension/pull/9599): Use MetaSwap API for gas price estimation in swaps +- [#9518](https://github.com/MetaMask/metamask-extension/pull/9518): Make all UI tabs accessible via keyboard +- [#9808](https://github.com/MetaMask/metamask-extension/pull/9808): Always allow overwriting invalid custom RPC chain ID +- [#9812](https://github.com/MetaMask/metamask-extension/pull/9812): Fix send header cancel button alignment +- [#9271](https://github.com/MetaMask/metamask-extension/pull/9271): Do not check popupIsOpen on Vivaldi +- [#9306](https://github.com/MetaMask/metamask-extension/pull/9306): Fix UI crash when dapp submits negative gas price +- [#9257](https://github.com/MetaMask/metamask-extension/pull/9257): Add sort and search to AddRecipient accounts list +- [#9824](https://github.com/MetaMask/metamask-extension/pull/9824): Move `externally_connectable` from base to Chrome manifest +- [#9815](https://github.com/MetaMask/metamask-extension/pull/9815): Add support for custom network RPC URL with basic auth +- [#9822](https://github.com/MetaMask/metamask-extension/pull/9822): Make QR code button focusable +- [#9832](https://github.com/MetaMask/metamask-extension/pull/9832): Warn instead of throw on duplicate web3 +- [#9838](https://github.com/MetaMask/metamask-extension/pull/9838): @metamask/controllers@4.0.0 +- [#9856](https://github.com/MetaMask/metamask-extension/pull/9856): Prevent user from getting stuck on opt in page +- [#9845](https://github.com/MetaMask/metamask-extension/pull/9845): Show a 'send eth' button on home screen in full screen mode +- [#9871](https://github.com/MetaMask/metamask-extension/pull/9871): Show send text upon hover in main asset list +- [#9880](https://github.com/MetaMask/metamask-extension/pull/9880): Properly detect U2F errors in hardware wallet + ## 8.1.3 Mon Oct 26 2020 - [#9642](https://github.com/MetaMask/metamask-extension/pull/9642) Prevent excessive overflow from swap dropdowns - [#9658](https://github.com/MetaMask/metamask-extension/pull/9658): Fix sorting Quote Source column of quote sort list diff --git a/app/_locales/am/messages.json b/app/_locales/am/messages.json index 12af3e796..a62e23ddc 100644 --- a/app/_locales/am/messages.json +++ b/app/_locales/am/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "ሰርዝ" }, - "cancelAttempt": { - "message": "ሙከራን ሰርዝ" - }, "cancellationGasFee": { "message": "የስረዛ ነዳጅ ወጪ" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "ይህን አውታረ መረብ ለመሰረዝ እንደሚፈልጉ እርግጠኛ ነዎት?" }, - "deposit": { - "message": "ማጠራቀም" - }, "depositEther": { "message": "Ether አስቀምጥ" }, @@ -737,7 +731,7 @@ "optionalBlockExplorerUrl": { "message": "ኤክስፕሎረር URL አግድ (አማራጭ)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "ምልክት (አማራጭ)" }, "orderOneHere": { @@ -985,9 +979,6 @@ "sentEther": { "message": "የተላከ ether" }, - "sentTokens": { - "message": "የተላኩ ተለዋጭ ስሞች" - }, "separateEachWord": { "message": "እያንዳንዱን ቃል በነጠላ ክፍት ቦታ ይለያዩ" }, diff --git a/app/_locales/ar/messages.json b/app/_locales/ar/messages.json index 8d5806599..b868e8dd5 100644 --- a/app/_locales/ar/messages.json +++ b/app/_locales/ar/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "إلغاء" }, - "cancelAttempt": { - "message": "إلغاء المحاولة" - }, "cancellationGasFee": { "message": "رسوم الإلغاء بعملة جاس" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "هل أنت متأكد أنك تريد حذف هذه الشبكة؟" }, - "deposit": { - "message": "إيداع" - }, "depositEther": { "message": "إيداع عملة إيثير" }, @@ -733,7 +727,7 @@ "optionalBlockExplorerUrl": { "message": "العنوان الإلكتروني لمستكشف البلوكات (اختياري)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "الرمز (اختياري)" }, "orderOneHere": { @@ -981,9 +975,6 @@ "sentEther": { "message": "أرسل عملة إيثير" }, - "sentTokens": { - "message": "العملات الرمزية المرسلة" - }, "separateEachWord": { "message": "افصل كل كلمة بمسافة واحدة" }, diff --git a/app/_locales/bg/messages.json b/app/_locales/bg/messages.json index 70dcf4e41..cc42761e2 100644 --- a/app/_locales/bg/messages.json +++ b/app/_locales/bg/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Отказ" }, - "cancelAttempt": { - "message": "Отмяна на опита" - }, "cancellationGasFee": { "message": "Такса в газ за анулиране " }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Наистина ли искате да изтриете тази мрежа?" }, - "deposit": { - "message": "Депозит" - }, "depositEther": { "message": "Депозирайте етер" }, @@ -736,7 +730,7 @@ "optionalBlockExplorerUrl": { "message": "Блокиране на Explorer URL (по избор)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Символ (по избор)" }, "orderOneHere": { @@ -984,9 +978,6 @@ "sentEther": { "message": "изпратен етер" }, - "sentTokens": { - "message": "изпратени жетони" - }, "separateEachWord": { "message": "Отделете всяка дума с интервал" }, diff --git a/app/_locales/bn/messages.json b/app/_locales/bn/messages.json index 63ce2ed82..cfc2d49bd 100644 --- a/app/_locales/bn/messages.json +++ b/app/_locales/bn/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "বাতিল করুন" }, - "cancelAttempt": { - "message": "প্রচেষ্টা বাতিল করুন" - }, "cancellationGasFee": { "message": "বাতিল করার গ্যাস ফী" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "আপনি কি এই নেটওয়ার্কটি মোছার বিষয়ে নিশ্চিত?" }, - "deposit": { - "message": "জমা " - }, "depositEther": { "message": "ইথার জমা করুন" }, @@ -740,7 +734,7 @@ "optionalBlockExplorerUrl": { "message": "এক্সপ্লোরার URL ব্লক করুন (ঐচ্ছিক)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "প্রতীক (ঐচ্ছিক)" }, "orderOneHere": { @@ -988,9 +982,6 @@ "sentEther": { "message": "পাঠানো ইথার " }, - "sentTokens": { - "message": "টোকেনগুলি পাঠান" - }, "separateEachWord": { "message": "প্রতিটি শব্দকে একটি স্পেস দিয়ে আলাদা করুন" }, diff --git a/app/_locales/ca/messages.json b/app/_locales/ca/messages.json index 759264ec0..d1060d8bd 100644 --- a/app/_locales/ca/messages.json +++ b/app/_locales/ca/messages.json @@ -161,9 +161,6 @@ "cancel": { "message": "Cancel·la" }, - "cancelAttempt": { - "message": "Cancel·la l'intent" - }, "cancellationGasFee": { "message": "Preu de cancel·lació del gas" }, @@ -305,9 +302,6 @@ "deleteNetworkDescription": { "message": "Estàs segur que vols eliminar aquesta xarxa?" }, - "deposit": { - "message": "Depòsit" - }, "depositEther": { "message": "Diposita Ether" }, @@ -724,7 +718,7 @@ "optionalBlockExplorerUrl": { "message": "Bloqueja l'URL d'Explorer (opcional)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Símbol (opcional)" }, "orderOneHere": { @@ -966,9 +960,6 @@ "sentEther": { "message": "envia ether" }, - "sentTokens": { - "message": "fitxes enviades" - }, "separateEachWord": { "message": "Separa cada paraula amb un sol espai" }, diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index 3239f00ab..41757f932 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -118,9 +118,6 @@ "defaultNetwork": { "message": "Výchozí síť pro Etherové transakce je Main Net." }, - "deposit": { - "message": "Vklad" - }, "depositEther": { "message": "Vložit Ether" }, diff --git a/app/_locales/da/messages.json b/app/_locales/da/messages.json index 449946a69..5e19e8d01 100644 --- a/app/_locales/da/messages.json +++ b/app/_locales/da/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Afbryd" }, - "cancelAttempt": { - "message": "Annullér forsøg" - }, "cancellationGasFee": { "message": "Gebyr for brændstofannullering" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Er du sikker på, at du vil slette dette netværk?" }, - "deposit": { - "message": "Indbetal" - }, "depositEther": { "message": "Indbetal Ether" }, @@ -724,7 +718,7 @@ "optionalBlockExplorerUrl": { "message": "Blok-stifinder-URL (valgfrit)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Symbol (valgfrit)" }, "orderOneHere": { @@ -966,9 +960,6 @@ "sentEther": { "message": "sendte ether" }, - "sentTokens": { - "message": "afsendte tokens" - }, "separateEachWord": { "message": "Separer hvert ord med et enkelt mellemrum" }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 7b7da3c43..f42343322 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -158,9 +158,6 @@ "cancel": { "message": "Abbrechen" }, - "cancelAttempt": { - "message": "Versuch abbrechen" - }, "cancellationGasFee": { "message": "Stornierungs-Gasgebühr" }, @@ -299,9 +296,6 @@ "deleteNetworkDescription": { "message": "Sind Sie sicher, dass Sie dieses Netzwerk löschen möchten?" }, - "deposit": { - "message": "Einzahlen" - }, "depositEther": { "message": "Ether einzahlen" }, @@ -957,9 +951,6 @@ "sentEther": { "message": "Ether senden" }, - "sentTokens": { - "message": "gesendete Token" - }, "separateEachWord": { "message": "Trennen Sie die Wörter mit einem einzelnen Leerzeichen voneinander" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 4ba30a80e..00d493f80 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -161,9 +161,6 @@ "cancel": { "message": "Ακύρωση" }, - "cancelAttempt": { - "message": "Ακύρωση Προσπάθειας" - }, "cancellationGasFee": { "message": "Ακύρωση Χρέωσης Αερίου" }, @@ -305,9 +302,6 @@ "deleteNetworkDescription": { "message": "Θέλετε σίγουρα να διαγράψετε αυτό το δίκτυο;" }, - "deposit": { - "message": "Κατάθεση" - }, "depositEther": { "message": "Κατάθεση Ether" }, @@ -737,7 +731,7 @@ "optionalBlockExplorerUrl": { "message": "Διεύθυνση URL Εξερευνητή Μπλοκ (προαιρετικό)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Σύμβολο (προαιρετικό)" }, "orderOneHere": { @@ -985,9 +979,6 @@ "sentEther": { "message": "απεσταλμένα ether" }, - "sentTokens": { - "message": "αποστολή token" - }, "separateEachWord": { "message": "Διαχωρίστε κάθε λέξη με ένα μόνο κενό" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 427f29078..647552710 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -239,9 +239,6 @@ "cancel": { "message": "Cancel" }, - "cancelAttempt": { - "message": "Cancel Attempt" - }, "cancellationGasFee": { "message": "Cancellation Gas Fee" }, @@ -477,9 +474,6 @@ "deleteNetworkDescription": { "message": "Are you sure you want to delete this network?" }, - "deposit": { - "message": "Deposit" - }, "depositEther": { "message": "Deposit Ether" }, @@ -875,7 +869,7 @@ "message": "Invalid IPFS Gateway: The value must be a valid URL" }, "invalidNumber": { - "message": "Invalid number. Enter a decimal or hexadecimal number." + "message": "Invalid number. Enter a decimal or '0x'-prefixed hexadecimal number." }, "invalidNumberLeadingZeros": { "message": "Invalid number. Remove any leading zeros." @@ -1037,7 +1031,7 @@ "message": "Network Name" }, "networkSettingsChainIdDescription": { - "message": "The chain ID is used for signing transactions. It must match the chain ID returned by the network. Enter a decimal or hexadecimal number starting with '0x'." + "message": "The chain ID is used for signing transactions. It must match the chain ID returned by the network. You can enter a decimal or '0x'-prefixed hexadecimal number, but we will display the number in decimal." }, "networkSettingsDescription": { "message": "Add and edit custom RPC networks" @@ -1156,8 +1150,8 @@ "optionalBlockExplorerUrl": { "message": "Block Explorer URL (optional)" }, - "optionalSymbol": { - "message": "Symbol (optional)" + "optionalCurrencySymbol": { + "message": "Currency Symbol (optional)" }, "orderOneHere": { "message": "Order a Trezor or Ledger and keep your funds in cold storage" @@ -1458,9 +1452,6 @@ "sentEther": { "message": "sent ether" }, - "sentTokens": { - "message": "sent tokens" - }, "separateEachWord": { "message": "Separate each word with a single space" }, @@ -1827,6 +1818,9 @@ "swapSwapFrom": { "message": "Swap from" }, + "swapSwapSwitch": { + "message": "Switch from and to tokens" + }, "swapSwapTo": { "message": "Swap to" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index ea36e9b50..8dd8cdd6f 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -136,9 +136,6 @@ "cancel": { "message": "Cancelar" }, - "cancelAttempt": { - "message": "Intentar cancelar" - }, "cancellationGasFee": { "message": "Comisión de Gas por cancelación" }, @@ -271,9 +268,6 @@ "deleteAccount": { "message": "Eliminar Cuenta" }, - "deposit": { - "message": "Depositar" - }, "depositEther": { "message": "Depositar Ether" }, @@ -580,7 +574,7 @@ "ofTextNofM": { "message": "de" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Símbolo (opcional)" }, "orderOneHere": { @@ -783,9 +777,6 @@ "sentEther": { "message": "se mandó ether" }, - "sentTokens": { - "message": "se mandaron tokens" - }, "separateEachWord": { "message": "Separar a cada palabra con un sólo espacio" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 1efcd3d5c..f5cfccba2 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -161,9 +161,6 @@ "cancel": { "message": "Cancelar" }, - "cancelAttempt": { - "message": "Cancelar intento" - }, "cancellationGasFee": { "message": "Tasa de cancelación de gas" }, @@ -305,9 +302,6 @@ "deleteNetworkDescription": { "message": "¿Estás seguro de que deseas borrar esta red?" }, - "deposit": { - "message": "Depósito" - }, "depositEther": { "message": "Depositar Ethers" }, @@ -725,7 +719,7 @@ "optionalBlockExplorerUrl": { "message": "Bloquear la URL de Explorer (opcional)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Símbolo (opcional)" }, "orderOneHere": { @@ -973,9 +967,6 @@ "sentEther": { "message": "Ethers enviados" }, - "sentTokens": { - "message": "tokens enviados" - }, "separateEachWord": { "message": "Separa cada palabra con un solo espacio" }, diff --git a/app/_locales/et/messages.json b/app/_locales/et/messages.json index 79b42b84c..8a1c3ee52 100644 --- a/app/_locales/et/messages.json +++ b/app/_locales/et/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Tühista" }, - "cancelAttempt": { - "message": "Tühista katse" - }, "cancellationGasFee": { "message": "Tühistamise gaasitasu" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Olete kindel, et soovite selle võrgu kustutada?" }, - "deposit": { - "message": "Sissemakse" - }, "depositEther": { "message": "Eetri sissemakse" }, @@ -730,7 +724,7 @@ "optionalBlockExplorerUrl": { "message": "Blokeeri Exploreri URL (valikuline)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Sümbol (valikuline)" }, "orderOneHere": { @@ -978,9 +972,6 @@ "sentEther": { "message": "saadetud eeter" }, - "sentTokens": { - "message": "saadetud load" - }, "separateEachWord": { "message": "Eraldage iga sõna ühe tühikuga" }, diff --git a/app/_locales/fa/messages.json b/app/_locales/fa/messages.json index b6ca24ad5..f13059643 100644 --- a/app/_locales/fa/messages.json +++ b/app/_locales/fa/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "لغو" }, - "cancelAttempt": { - "message": "لغو تلاش" - }, "cancellationGasFee": { "message": "لغو فیس گاز" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "آیا مطمئن هستید که این شبکه حذف شود؟" }, - "deposit": { - "message": "سپرده" - }, "depositEther": { "message": "پرداخت ایتر" }, @@ -740,7 +734,7 @@ "optionalBlockExplorerUrl": { "message": "بلاک کردن مرورگر URL (انتخابی)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "سمبول (انتخابی)" }, "orderOneHere": { @@ -988,9 +982,6 @@ "sentEther": { "message": "ایتر ارسال شد" }, - "sentTokens": { - "message": "رمزیاب های فرستاده شده" - }, "separateEachWord": { "message": "هر کلمه را با یک فاصله واحد جدا سازید" }, diff --git a/app/_locales/fi/messages.json b/app/_locales/fi/messages.json index 0e17618db..53dd7496f 100644 --- a/app/_locales/fi/messages.json +++ b/app/_locales/fi/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Peruuta" }, - "cancelAttempt": { - "message": "Peruuta yritys" - }, "cancellationGasFee": { "message": "Peruutuksen gas-maksu" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Haluatko varmasti poistaa tämän verkon?" }, - "deposit": { - "message": "Talleta" - }, "depositEther": { "message": "Talleta Etheriä" }, @@ -737,7 +731,7 @@ "optionalBlockExplorerUrl": { "message": "Estä Explorerin URL-osoite (valinnainen)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Symboli (valinnainen)" }, "orderOneHere": { @@ -985,9 +979,6 @@ "sentEther": { "message": "lähetä etheriä" }, - "sentTokens": { - "message": "lähetetyt poletit" - }, "separateEachWord": { "message": "Erottele sanat toisistaan yhdellä välilyönnillä" }, diff --git a/app/_locales/fil/messages.json b/app/_locales/fil/messages.json index 445083185..6285949c6 100644 --- a/app/_locales/fil/messages.json +++ b/app/_locales/fil/messages.json @@ -146,9 +146,6 @@ "cancel": { "message": "Kanselahin" }, - "cancelAttempt": { - "message": "Kanselahin ang Pagtangka" - }, "cancellationGasFee": { "message": "Gas Fee sa Pagkansela" }, @@ -284,9 +281,6 @@ "deleteNetworkDescription": { "message": "Sigurado ka bang gusto mong i-delete ang network na ito?" }, - "deposit": { - "message": "Deposito" - }, "depositEther": { "message": "Magdeposito ng Ether" }, @@ -671,7 +665,7 @@ "optionalBlockExplorerUrl": { "message": "Block Explorer URL (opsyonal)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Simbolo (opsyonal)" }, "orderOneHere": { @@ -900,9 +894,6 @@ "sentEther": { "message": "nagpadala ng ether" }, - "sentTokens": { - "message": "mga ipinadalang token" - }, "separateEachWord": { "message": "Paghiwa-hiwalayin ang bawat salita gamit ang isang space" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 78cdb5e2f..750591cfc 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -155,9 +155,6 @@ "cancel": { "message": "Annuler" }, - "cancelAttempt": { - "message": "Annuler la tentative." - }, "cancellationGasFee": { "message": "Frais en gas de l'annulation" }, @@ -299,9 +296,6 @@ "deleteNetworkDescription": { "message": "Êtes-vous sûr de vouloir supprimer ce réseau ?" }, - "deposit": { - "message": "Déposer" - }, "depositEther": { "message": "Déposer de l'Ether" }, @@ -722,7 +716,7 @@ "optionalBlockExplorerUrl": { "message": "Bloquer l'URL de l'explorateur (facultatif)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Symbole (facultatif)" }, "orderOneHere": { @@ -970,9 +964,6 @@ "sentEther": { "message": "Ether envoyé" }, - "sentTokens": { - "message": "Jetons envoyés" - }, "separateEachWord": { "message": "Separez chaque mot avec un espace simple" }, diff --git a/app/_locales/he/messages.json b/app/_locales/he/messages.json index 8c10e4dad..6a0bc2cbd 100644 --- a/app/_locales/he/messages.json +++ b/app/_locales/he/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "ביטול" }, - "cancelAttempt": { - "message": "בטל ניסיון" - }, "cancellationGasFee": { "message": "עמלת דלק עבור ביטול" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "הנך בטוח/ה שברצונך למחוק רשת זו?" }, - "deposit": { - "message": "הפקדה" - }, "depositEther": { "message": "הפקדת את'ר" }, @@ -737,7 +731,7 @@ "optionalBlockExplorerUrl": { "message": "חסום כתובת URL של אקספלורר (אופציונלי)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "סמל (אופציונלי)" }, "orderOneHere": { @@ -982,9 +976,6 @@ "sentEther": { "message": "את'ר שנשלח" }, - "sentTokens": { - "message": "טוקנים שנשלחו" - }, "separateEachWord": { "message": "יש להפריד כל מילה עם רווח יחיד" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index d189f6110..da83a04b7 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "रद्द करें" }, - "cancelAttempt": { - "message": "प्रयास रद्द करें" - }, "cancellationGasFee": { "message": "रद्दीकरण गैस शुल्क" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "क्या आप वाकई इस नेटवर्क को हटाना चाहते हैं?" }, - "deposit": { - "message": "जमा " - }, "depositEther": { "message": "Ether जमा करें" }, @@ -737,7 +731,7 @@ "optionalBlockExplorerUrl": { "message": "एक्सप्लोरर यूआरएल ब्लॉक (वैकल्पिक)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "सिम्बल (वैकल्पिक)" }, "orderOneHere": { @@ -985,9 +979,6 @@ "sentEther": { "message": "भेजे गए ether" }, - "sentTokens": { - "message": "भेजे गए टोकन" - }, "separateEachWord": { "message": "प्रत्येक शब्द को एक स्पेस से अलग करें" }, diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index b0e199100..4bdac8559 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -97,9 +97,6 @@ "defaultNetwork": { "message": "ईथर लेनदेन के लिए डिफ़ॉल्ट नेटवर्क मुख्य नेट है।" }, - "deposit": { - "message": "जमा" - }, "depositEther": { "message": "जमा - Ether" }, diff --git a/app/_locales/hr/messages.json b/app/_locales/hr/messages.json index 227c154d7..1564bd012 100644 --- a/app/_locales/hr/messages.json +++ b/app/_locales/hr/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Odustani" }, - "cancelAttempt": { - "message": "Otkaži pokušaj" - }, "cancellationGasFee": { "message": "Otkazivanje naknade za gorivo" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Sigurno želite izbrisati ovu mrežu?" }, - "deposit": { - "message": "Polog" - }, "depositEther": { "message": "Položi Ether" }, @@ -733,7 +727,7 @@ "optionalBlockExplorerUrl": { "message": "Blokiraj Explorerov URL (neobavezno)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Simbol (neobavezno)" }, "orderOneHere": { @@ -981,9 +975,6 @@ "sentEther": { "message": "pošalji ether" }, - "sentTokens": { - "message": "pošalji tokene" - }, "separateEachWord": { "message": "Odvojite pojedinačne riječi jednim razmakom" }, diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index eefec6bc7..18901dcc8 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -85,9 +85,6 @@ "cancel": { "message": "Anile" }, - "cancelAttempt": { - "message": "Teste Anile" - }, "cancellationGasFee": { "message": "Anilasyon Gaz Chaj" }, @@ -169,9 +166,6 @@ "defaultNetwork": { "message": "Dfo rezo a pou tranzaksyon Ether se Mainnet." }, - "deposit": { - "message": "Depo" - }, "depositEther": { "message": "Depo Ether" }, @@ -615,9 +609,6 @@ "sentEther": { "message": "Voye ether" }, - "sentTokens": { - "message": "tokens deja voye" - }, "separateEachWord": { "message": "Separe chak mo ak yon sèl espas" }, diff --git a/app/_locales/hu/messages.json b/app/_locales/hu/messages.json index 97f63d74e..9188af756 100644 --- a/app/_locales/hu/messages.json +++ b/app/_locales/hu/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Mégse" }, - "cancelAttempt": { - "message": "Kísérlet megszakítása" - }, "cancellationGasFee": { "message": "A törlés gázára" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Biztosan törli ezt a hálózatot?" }, - "deposit": { - "message": "Befizetés" - }, "depositEther": { "message": "Ether befizetése" }, @@ -733,7 +727,7 @@ "optionalBlockExplorerUrl": { "message": "Explorer URL letiltása (nem kötelező)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Szimbólum (opcionális)" }, "orderOneHere": { @@ -981,9 +975,6 @@ "sentEther": { "message": "elküldött ether" }, - "sentTokens": { - "message": "elküldött érmék" - }, "separateEachWord": { "message": "Minden egyes szavat szóközzel válasszon el" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 1fcd8f960..9eff50642 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Batal" }, - "cancelAttempt": { - "message": "Batalkan Percobaan" - }, "cancellationGasFee": { "message": "Pembatalan Biaya Gas" }, @@ -721,7 +718,7 @@ "optionalBlockExplorerUrl": { "message": "Blokir URL Penjelajah (opsional)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Simbol (opsional)" }, "orderOneHere": { @@ -969,9 +966,6 @@ "sentEther": { "message": "kirim ether" }, - "sentTokens": { - "message": "token terkirim" - }, "separateEachWord": { "message": "Pisahkan setiap kata dengan spasi tunggal" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index f9400429c..98bd7c936 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -226,9 +226,6 @@ "cancel": { "message": "Annulla" }, - "cancelAttempt": { - "message": "Tentativo di Annullamento" - }, "cancellationGasFee": { "message": "Costo di Annullamento in Gas" }, @@ -464,9 +461,6 @@ "deleteNetworkDescription": { "message": "Sei sicuro di voler eliminare questa rete?" }, - "deposit": { - "message": "Deposita" - }, "depositEther": { "message": "Deposita Ether" }, @@ -1056,7 +1050,7 @@ "optionalBlockExplorerUrl": { "message": "URL del Block Explorer (opzionale)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Simbolo (opzionale)" }, "orderOneHere": { @@ -1358,9 +1352,6 @@ "sentEther": { "message": "ether inviati" }, - "sentTokens": { - "message": "tokens inviati" - }, "separateEachWord": { "message": "Separa ogni parola con un solo spazio" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 23e4bd9a6..737845b3b 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -169,9 +169,6 @@ "defaultNetwork": { "message": "デフォルトのEther送受信ネットワークはメインネットです。" }, - "deposit": { - "message": "振込" - }, "depositEther": { "message": "Etherを振込" }, diff --git a/app/_locales/kn/messages.json b/app/_locales/kn/messages.json index 1d92260b9..d482f073e 100644 --- a/app/_locales/kn/messages.json +++ b/app/_locales/kn/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "ರದ್ದುಮಾಡಿ" }, - "cancelAttempt": { - "message": "ಪ್ರಯತ್ನವನ್ನು ರದ್ದುಪಡಿಸಿ" - }, "cancellationGasFee": { "message": "ರದ್ದುಗೊಳಿಸುವ ಗ್ಯಾಸ್ ಶುಲ್ಕ" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "ನೀವು ಈ ನೆಟ್‌ವರ್ಕ್ ಅನ್ನು ಖಚಿತವಾಗಿ ಅಳಿಸಲು ಬಯಸುತ್ತೀರಾ?" }, - "deposit": { - "message": "ಠೇವಣಿ" - }, "depositEther": { "message": "ಎಥರ್ ಠೇವಣಿ ಮಾಡಿ" }, @@ -740,7 +734,7 @@ "optionalBlockExplorerUrl": { "message": "ಅನ್ವೇಷಕ URL ಅನ್ನು ನಿರ್ಬಂಧಿಸಿ (ಐಚ್ಛಿಕ)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "ಚಿಹ್ನೆ (ಐಚ್ಛಿಕ)" }, "orderOneHere": { @@ -988,9 +982,6 @@ "sentEther": { "message": "ಕಳುಹಿಸಲಾದ ಎಥರ್" }, - "sentTokens": { - "message": "ಕಳುಹಿಸಲಾದ ಟೋಕನ್‌ಗಳು" - }, "separateEachWord": { "message": "ಒಂದು ಸ್ಪೇಸ್ ಮೂಲಕ ಪ್ರತಿ ಪದವನ್ನು ಬೇರ್ಪಡಿಸಿ" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index decd5fecd..ec4b28b2f 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -161,9 +161,6 @@ "cancel": { "message": "취소" }, - "cancelAttempt": { - "message": "취소 시도" - }, "cancellationGasFee": { "message": "취소 가스 수수료" }, @@ -305,9 +302,6 @@ "deleteNetworkDescription": { "message": "정말로 이 네트워크를 삭제하시겠습니까?" }, - "deposit": { - "message": "입금" - }, "depositEther": { "message": "이더리움 입금하기" }, @@ -734,7 +728,7 @@ "optionalBlockExplorerUrl": { "message": "익스플로러 URL 차단 (선택 사항)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Symbol (선택)" }, "orderOneHere": { @@ -979,9 +973,6 @@ "sentEther": { "message": "전송된 이더" }, - "sentTokens": { - "message": "전송된 토큰" - }, "separateEachWord": { "message": "각 단어는 공백 한칸으로 분리합니다" }, diff --git a/app/_locales/lt/messages.json b/app/_locales/lt/messages.json index b88fc2c67..b8596bc82 100644 --- a/app/_locales/lt/messages.json +++ b/app/_locales/lt/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Atšaukti" }, - "cancelAttempt": { - "message": "Atšaukti mėginimą" - }, "cancellationGasFee": { "message": "Dujų mokesčio atšaukimas" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Ar tikrai norite panaikinti šį tinklą?" }, - "deposit": { - "message": "Indėlis" - }, "depositEther": { "message": "Įnešti eterių" }, @@ -740,7 +734,7 @@ "optionalBlockExplorerUrl": { "message": "Blokuoti naršyklės URL (pasirinktinai)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Simbolis (nebūtinas)" }, "orderOneHere": { @@ -988,9 +982,6 @@ "sentEther": { "message": "siųsti eterių" }, - "sentTokens": { - "message": "išsiųsti žetonai" - }, "separateEachWord": { "message": "Kiekvieną žodį atskirkite viengubu tarpu" }, diff --git a/app/_locales/lv/messages.json b/app/_locales/lv/messages.json index 40fb55edf..f3362994c 100644 --- a/app/_locales/lv/messages.json +++ b/app/_locales/lv/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Atcelt" }, - "cancelAttempt": { - "message": "Atcelt mēģinājumu" - }, "cancellationGasFee": { "message": "Atcelšanas maksājums par Gas" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Vai tiešām vēlaties dzēst šo tīklu?" }, - "deposit": { - "message": "Iemaksa" - }, "depositEther": { "message": "Noguldīt Ether" }, @@ -736,7 +730,7 @@ "optionalBlockExplorerUrl": { "message": "Bloķēt Explorer URL (pēc izvēles)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Simbols (neobligāti)" }, "orderOneHere": { @@ -984,9 +978,6 @@ "sentEther": { "message": "nosūtītie ether" }, - "sentTokens": { - "message": "nosūtītie marķieri" - }, "separateEachWord": { "message": "Atdaliet katru vārdu ar vienu atstarpi" }, diff --git a/app/_locales/ms/messages.json b/app/_locales/ms/messages.json index 425e9eb20..04eeb3342 100644 --- a/app/_locales/ms/messages.json +++ b/app/_locales/ms/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Batal" }, - "cancelAttempt": { - "message": "Batalkan Percubaan" - }, "cancellationGasFee": { "message": "Fi Gas Pembatalan" }, @@ -714,7 +711,7 @@ "optionalBlockExplorerUrl": { "message": "Sekat URL Explorer (pilihan)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Simbol (pilihan)" }, "orderOneHere": { @@ -962,9 +959,6 @@ "sentEther": { "message": "menghantar ether" }, - "sentTokens": { - "message": "token dihantar" - }, "separateEachWord": { "message": "Pisahkan setiap perkataan dengan ruang tunggal" }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index fa9e168ff..f6ef6b7dc 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -94,9 +94,6 @@ "defaultNetwork": { "message": "Het standaardnetwerk voor Ether-transacties is Main Net." }, - "deposit": { - "message": "Storting" - }, "depositEther": { "message": "Stort Ether" }, diff --git a/app/_locales/no/messages.json b/app/_locales/no/messages.json index ab94ce321..18ef48537 100644 --- a/app/_locales/no/messages.json +++ b/app/_locales/no/messages.json @@ -161,9 +161,6 @@ "cancel": { "message": "Avbryt" }, - "cancelAttempt": { - "message": "Avbryt forsøk" - }, "cancellationGasFee": { "message": "Kansellering gassavgift" }, @@ -305,9 +302,6 @@ "deleteNetworkDescription": { "message": "Er du sikker på at du vil slette dette nettverket?" }, - "deposit": { - "message": "Innskudd" - }, "depositEther": { "message": "Sett inn Ether " }, @@ -727,7 +721,7 @@ "optionalBlockExplorerUrl": { "message": "Blokker Explorer URL (valgfritt)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Symbol (valgfritt)" }, "orderOneHere": { @@ -966,9 +960,6 @@ "sentEther": { "message": "sendt ether" }, - "sentTokens": { - "message": "sendte tokener " - }, "separateEachWord": { "message": "Del hvert ord med et enkelt mellomrom " }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index b2635da57..6cb59f6a0 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -73,9 +73,6 @@ "defaultNetwork": { "message": "Ang default network para sa Ether transactions ay ang Main Net." }, - "deposit": { - "message": "Deposito" - }, "depositEther": { "message": "I-deposito ang Ether" }, diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index 157bfb7d0..4ad06b5df 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Anuluj" }, - "cancelAttempt": { - "message": "Anuluj próbę" - }, "cancellationGasFee": { "message": "Opłata za gaz za anulowanie" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Czy na pewno chcesz usunąć tę sieć?" }, - "deposit": { - "message": "Zdeponuj" - }, "depositEther": { "message": "Zdeponuj Eter" }, @@ -734,7 +728,7 @@ "optionalBlockExplorerUrl": { "message": "Adres URL przeglądarki łańcucha bloków (opcjonalnie)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Symbol (opcjonalnie)" }, "orderOneHere": { @@ -982,9 +976,6 @@ "sentEther": { "message": "wyślij eter" }, - "sentTokens": { - "message": "wysłane tokeny" - }, "separateEachWord": { "message": "Oddziel słowa pojedynczą spacją" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 067fd8d34..da8314cad 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -97,9 +97,6 @@ "defaultNetwork": { "message": "A rede pré definida para transações em Ether é a Main Net." }, - "deposit": { - "message": "Depósito" - }, "depositEther": { "message": "Depositar Ether" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 68eda6f73..04debdecf 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -158,9 +158,6 @@ "cancel": { "message": "Cancelar" }, - "cancelAttempt": { - "message": "Tentativa de cancelamento" - }, "cancellationGasFee": { "message": "Tarifa de Gas de cancelamento" }, @@ -302,9 +299,6 @@ "deleteNetworkDescription": { "message": "Tem certeza de que deseja excluir esta rede?" }, - "deposit": { - "message": "Depósito" - }, "depositEther": { "message": "Depositar Ether" }, @@ -728,7 +722,7 @@ "optionalBlockExplorerUrl": { "message": "URL exploradora de blocos (opcional)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Símbolo (opcional)" }, "orderOneHere": { @@ -976,9 +970,6 @@ "sentEther": { "message": "ether enviado" }, - "sentTokens": { - "message": "tokens enviados" - }, "separateEachWord": { "message": "Separe cada palavra com um único espaço" }, diff --git a/app/_locales/pt_PT/messages.json b/app/_locales/pt_PT/messages.json index ed3c0a8d5..96ed89f8e 100644 --- a/app/_locales/pt_PT/messages.json +++ b/app/_locales/pt_PT/messages.json @@ -45,9 +45,6 @@ "delete": { "message": "Eliminar" }, - "deposit": { - "message": "Depósito" - }, "details": { "message": "Detalhes" }, diff --git a/app/_locales/ro/messages.json b/app/_locales/ro/messages.json index bdcfc126f..8f0997e65 100644 --- a/app/_locales/ro/messages.json +++ b/app/_locales/ro/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Anulare" }, - "cancelAttempt": { - "message": "Anulare încercare" - }, "cancellationGasFee": { "message": "Taxă de anulare în gaz" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Sigur vreți să ștergeți această rețea?" }, - "deposit": { - "message": "Depunere" - }, "depositEther": { "message": "Depuneți Ether" }, @@ -727,7 +721,7 @@ "optionalBlockExplorerUrl": { "message": "URL explorator bloc (opțional)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Simbol (opțional)" }, "orderOneHere": { @@ -975,9 +969,6 @@ "sentEther": { "message": "trimiteți ether" }, - "sentTokens": { - "message": "tokenuri trimise" - }, "separateEachWord": { "message": "Despărțiți fiecare cuvânt cu un spațiu" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 1ef3ac602..e2927be2a 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -163,9 +163,6 @@ "cancel": { "message": "Отмена" }, - "cancelAttempt": { - "message": "Попытка отмены транзакции" - }, "cancellationGasFee": { "message": "Комиссия за газ на отмену" }, @@ -330,9 +327,6 @@ "deleteNetworkDescription": { "message": "Вы уверены, что хотите удалить эту сеть?" }, - "deposit": { - "message": "Пополнить" - }, "depositEther": { "message": "Пополнить Ether" }, @@ -769,7 +763,7 @@ "optionalBlockExplorerUrl": { "message": "URL блок-эксплорера (необязательно)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Символ (необязательно)" }, "orderOneHere": { @@ -1024,9 +1018,6 @@ "sentEther": { "message": "Отправить Ether" }, - "sentTokens": { - "message": "Отправленные токены" - }, "separateEachWord": { "message": "Разделяйте каждое слово одним пробелом" }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index 1b0ba15c9..718e32616 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -158,9 +158,6 @@ "cancel": { "message": "Zrušit" }, - "cancelAttempt": { - "message": "Zrušiť pokus" - }, "cancellationGasFee": { "message": "Storno poplatok za GAS" }, @@ -302,9 +299,6 @@ "deleteNetworkDescription": { "message": "Naozaj chcete túto sieť odstrániť?" }, - "deposit": { - "message": "Vklad" - }, "depositEther": { "message": "Vložit Ether" }, @@ -709,7 +703,7 @@ "optionalBlockExplorerUrl": { "message": "Blokovať URL Explorera (voliteľné)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Symbol (voliteľné)" }, "orderOneHere": { @@ -951,9 +945,6 @@ "sentEther": { "message": "poslaný ether" }, - "sentTokens": { - "message": "poslané tokeny" - }, "separateEachWord": { "message": "Každé slovo oddeľte jednou medzerou" }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index b49ab7fec..cf1b3ad00 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Prekliči" }, - "cancelAttempt": { - "message": "Prekliči poskus" - }, "cancellationGasFee": { "message": "Preklicani znesek gas" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Ali ste prepričani, da želite izbrisati to omrežje?" }, - "deposit": { - "message": "Vplačaj" - }, "depositEther": { "message": "Vplačilo ethra" }, @@ -725,7 +719,7 @@ "optionalBlockExplorerUrl": { "message": "Blokiraj URL Explorerja (poljubno)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Simbol (nezahtevano)" }, "orderOneHere": { @@ -970,9 +964,6 @@ "sentEther": { "message": "poslani ether" }, - "sentTokens": { - "message": "poslani žetoni" - }, "separateEachWord": { "message": "Vsako besedo ločite z enim presledkom" }, diff --git a/app/_locales/sr/messages.json b/app/_locales/sr/messages.json index d72f4007e..730f4b5e4 100644 --- a/app/_locales/sr/messages.json +++ b/app/_locales/sr/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Otkaži" }, - "cancelAttempt": { - "message": "Otkaži pokušaj" - }, "cancellationGasFee": { "message": "Otkazivanje gas naknade" }, @@ -305,9 +302,6 @@ "deleteNetworkDescription": { "message": "Da li ste sigurni da želite da izbrišete ovu mrežu?" }, - "deposit": { - "message": "Depozit" - }, "depositEther": { "message": "Dajte depozit Ether-u" }, @@ -731,7 +725,7 @@ "optionalBlockExplorerUrl": { "message": "Blokirajte URL Explorer-a (opciono)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Simbol (opciono)" }, "orderOneHere": { @@ -979,9 +973,6 @@ "sentEther": { "message": "ether je poslat" }, - "sentTokens": { - "message": "poslati tokeni" - }, "separateEachWord": { "message": "Razdvojite svaku reč jednim mestom razmaka" }, diff --git a/app/_locales/sv/messages.json b/app/_locales/sv/messages.json index 6c6b68622..bb5e8e5a5 100644 --- a/app/_locales/sv/messages.json +++ b/app/_locales/sv/messages.json @@ -161,9 +161,6 @@ "cancel": { "message": "Avbryt" }, - "cancelAttempt": { - "message": "Avbryt försök" - }, "cancellationGasFee": { "message": "Gasavgift för avbrytning" }, @@ -302,9 +299,6 @@ "deleteNetworkDescription": { "message": "Är du säker på att du vill ta bort detta nätverk?" }, - "deposit": { - "message": "Deposition" - }, "depositEther": { "message": "Sätt in Ether" }, @@ -724,7 +718,7 @@ "optionalBlockExplorerUrl": { "message": "Block Explorer URL (valfritt)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Symbol (frivillig)" }, "orderOneHere": { @@ -972,9 +966,6 @@ "sentEther": { "message": "skickat ether" }, - "sentTokens": { - "message": "skickade tokens" - }, "separateEachWord": { "message": "Lägg in ett mellanslag mellan varje ord" }, diff --git a/app/_locales/sw/messages.json b/app/_locales/sw/messages.json index 5662950c6..4244565b8 100644 --- a/app/_locales/sw/messages.json +++ b/app/_locales/sw/messages.json @@ -158,9 +158,6 @@ "cancel": { "message": "Ghairi" }, - "cancelAttempt": { - "message": "Jaribio la Kubatilisha" - }, "cancellationGasFee": { "message": "Ada ya Kubatilisha Gesi" }, @@ -302,9 +299,6 @@ "deleteNetworkDescription": { "message": "Una uhakika unataka kufuta mtandao huu?" }, - "deposit": { - "message": "Fedha zilizopo kwenye akaunti" - }, "depositEther": { "message": "Weka Ether" }, @@ -718,7 +712,7 @@ "optionalBlockExplorerUrl": { "message": "URL ya Block Explorer URL (hiari)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Ishara (hiari)" }, "orderOneHere": { @@ -966,9 +960,6 @@ "sentEther": { "message": "ether iliyotumwa" }, - "sentTokens": { - "message": "vianzio vilivyotumwa" - }, "separateEachWord": { "message": "Tenganisha kila neno kwa nafasi moja" }, diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index 507893d9a..f0ca60e9c 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -136,9 +136,6 @@ "delete": { "message": "நீக்கு" }, - "deposit": { - "message": "வைப்புத்தொகை" - }, "depositEther": { "message": "வைப்புத்தொகை எதிர் " }, diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index 28a662868..f54947489 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -142,9 +142,6 @@ "deleteNetwork": { "message": "ลบเครือข่าย?" }, - "deposit": { - "message": "ฝาก" - }, "depositEther": { "message": "การฝากอีเธอร์" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 7448ad53f..5868000c8 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -118,9 +118,6 @@ "defaultNetwork": { "message": "Ether işlemleri için varsayılan ağ Main Net." }, - "deposit": { - "message": "Yatır" - }, "depositEther": { "message": "Ether yatır" }, diff --git a/app/_locales/uk/messages.json b/app/_locales/uk/messages.json index c314c5c31..958376615 100644 --- a/app/_locales/uk/messages.json +++ b/app/_locales/uk/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "Скасувати" }, - "cancelAttempt": { - "message": "Відмінити спробу" - }, "cancellationGasFee": { "message": "Вартість пального за скасування" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "Ви впевнені, що хочете видалити цю мережу?" }, - "deposit": { - "message": "Депозит" - }, "depositEther": { "message": "Депонувати Ether" }, @@ -740,7 +734,7 @@ "optionalBlockExplorerUrl": { "message": "Блокувати Explorer URL (не обов'язково)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Символ (не обов'язково)" }, "orderOneHere": { @@ -988,9 +982,6 @@ "sentEther": { "message": "надісланий ефір" }, - "sentTokens": { - "message": "надіслані токени" - }, "separateEachWord": { "message": "Відділіть кожне слово одним пробілом" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 1e8f4b5d1..cfdcf467e 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -79,9 +79,6 @@ "defaultNetwork": { "message": "Mạng lưới mặc định dùng cho các giao dịch Ether là Main Net (tiền ETH thật)." }, - "deposit": { - "message": "Ký gửi/nạp tiền" - }, "depositEther": { "message": "Ký gửi Ether" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 454e1d7cf..493c98310 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "取消" }, - "cancelAttempt": { - "message": "取消操作" - }, "cancellationGasFee": { "message": "取消天然气费" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "您是否确认要删除该网络?" }, - "deposit": { - "message": "存入" - }, "depositEther": { "message": "存入 Ether" }, @@ -722,7 +716,7 @@ "optionalBlockExplorerUrl": { "message": "屏蔽管理器 URL(选填)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "符号(选填)" }, "orderOneHere": { @@ -970,9 +964,6 @@ "sentEther": { "message": "以太币已发送" }, - "sentTokens": { - "message": "代币已发送" - }, "separateEachWord": { "message": "用空格分隔每个单词" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index dcbd8aab0..c2cad7c58 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -164,9 +164,6 @@ "cancel": { "message": "取消" }, - "cancelAttempt": { - "message": "嘗試取消" - }, "cancellationGasFee": { "message": "需要的手續費" }, @@ -308,9 +305,6 @@ "deleteNetworkDescription": { "message": "你確定要刪除網路嗎?" }, - "deposit": { - "message": "存入" - }, "depositEther": { "message": "存入乙太幣" }, @@ -731,7 +725,7 @@ "optionalBlockExplorerUrl": { "message": "區塊鏈瀏覽器 URL(非必要)" }, - "optionalSymbol": { + "optionalCurrencySymbol": { "message": "Symbol (可選)" }, "orderOneHere": { @@ -967,9 +961,6 @@ "sentEther": { "message": "發送乙太幣" }, - "sentTokens": { - "message": "發送代幣" - }, "separateEachWord": { "message": "單詞之間請以空白間隔" }, diff --git a/app/manifest/_base.json b/app/manifest/_base.json index 499b2893b..a83ba5dff 100644 --- a/app/manifest/_base.json +++ b/app/manifest/_base.json @@ -41,10 +41,6 @@ ], "default_locale": "en", "description": "__MSG_appDescription__", - "externally_connectable": { - "matches": ["https://metamask.io/*"], - "ids": ["*"] - }, "icons": { "16": "images/icon-16.png", "19": "images/icon-19.png", @@ -68,6 +64,6 @@ "notifications" ], "short_name": "__MSG_appName__", - "version": "8.1.3", + "version": "8.1.4", "web_accessible_resources": ["inpage.js", "phishing.html"] } diff --git a/app/manifest/chrome.json b/app/manifest/chrome.json index 2763a1120..908d430c9 100644 --- a/app/manifest/chrome.json +++ b/app/manifest/chrome.json @@ -1,3 +1,7 @@ { + "externally_connectable": { + "matches": ["https://metamask.io/*"], + "ids": ["*"] + }, "minimum_chrome_version": "58" } diff --git a/app/scripts/account-import-strategies/index.js b/app/scripts/account-import-strategies/index.js index 9a7bc2f8e..49444c7ad 100644 --- a/app/scripts/account-import-strategies/index.js +++ b/app/scripts/account-import-strategies/index.js @@ -2,10 +2,10 @@ import log from 'loglevel' import Wallet from 'ethereumjs-wallet' import importers from 'ethereumjs-wallet/thirdparty' import ethUtil from 'ethereumjs-util' +import { addHexPrefix } from '../lib/util' const accountImporter = { - - importAccount (strategy, args) { + importAccount(strategy, args) { try { const importer = this.strategies[strategy] const privateKeyHex = importer(...args) @@ -21,7 +21,7 @@ const accountImporter = { throw new Error('Cannot import an empty key.') } - const prefixed = ethUtil.addHexPrefix(privateKey) + const prefixed = addHexPrefix(privateKey) const buffer = ethUtil.toBuffer(prefixed) if (!ethUtil.isValidPrivate(buffer)) { @@ -43,10 +43,9 @@ const accountImporter = { return walletToPrivateKey(wallet) }, }, - } -function walletToPrivateKey (wallet) { +function walletToPrivateKey(wallet) { const privateKeyBuffer = wallet.getPrivateKey() return ethUtil.bufferToHex(privateKeyBuffer) } diff --git a/app/scripts/background.js b/app/scripts/background.js index 32aaec4af..99242f871 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -60,9 +60,7 @@ const requestAccountTabIds = {} // state persistence const inTest = process.env.IN_TEST === 'true' -const localStore = inTest - ? new ReadOnlyNetworkStore() - : new LocalStore() +const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore() let versionedData if (inTest || process.env.METAMASK_DEBUG) { @@ -141,9 +139,9 @@ initialize().catch(log.error) /** * Initializes the MetaMask controller, and sets up all platform configuration. - * @returns {Promise} - Setup complete. + * @returns {Promise} Setup complete. */ -async function initialize () { +async function initialize() { const initState = await loadStateFromPersistence() const initLangCode = await getFirstPreferredLangCode() await setupController(initState, initLangCode) @@ -157,17 +155,17 @@ async function initialize () { /** * Loads any stored data, prioritizing the latest storage strategy. * Migrates that data schema in case it was last loaded on an older version. - * @returns {Promise} - Last data emitted from previous instance of MetaMask. + * @returns {Promise} Last data emitted from previous instance of MetaMask. */ -async function loadStateFromPersistence () { +async function loadStateFromPersistence() { // migrations const migrator = new Migrator({ migrations }) migrator.on('error', console.warn) // read from disk // first from preferred, async API: - versionedData = (await localStore.get()) || - migrator.generateInitialState(firstTimeState) + versionedData = + (await localStore.get()) || migrator.generateInitialState(firstTimeState) // check if somehow state is empty // this should never happen but new error reporting suggests that it has @@ -217,9 +215,9 @@ async function loadStateFromPersistence () { * * @param {Object} initState - The initial state to start the controller with, matches the state that is emitted from the controller. * @param {string} initLangCode - The region code for the language preferred by the current user. - * @returns {Promise} - After setup is complete. + * @returns {Promise} After setup is complete. */ -function setupController (initState, initLangCode) { +function setupController(initState, initLangCode) { // // MetaMask Controller // @@ -249,7 +247,9 @@ function setupController (initState, initLangCode) { setupEnsIpfsResolver({ getCurrentNetwork: controller.getCurrentNetwork, - getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(controller.preferencesController), + getIpfsGateway: controller.preferencesController.getIpfsGateway.bind( + controller.preferencesController, + ), provider: controller.provider, }) @@ -267,14 +267,14 @@ function setupController (initState, initLangCode) { /** * Assigns the given state to the versioned object (with metadata), and returns that. * @param {Object} state - The state object as emitted by the MetaMaskController. - * @returns {VersionedData} - The state object wrapped in an object that includes a metadata key. + * @returns {VersionedData} The state object wrapped in an object that includes a metadata key. */ - function versionifyData (state) { + function versionifyData(state) { versionedData.data = state return versionedData } - async function persistData (state) { + async function persistData(state) { if (!state) { throw new Error('MetaMask - updated state is missing') } @@ -303,12 +303,14 @@ function setupController (initState, initLangCode) { [ENVIRONMENT_TYPE_FULLSCREEN]: true, } - const metamaskBlockedPorts = [ - 'trezor-connect', - ] + const metamaskBlockedPorts = ['trezor-connect'] const isClientOpenStatus = () => { - return popupIsOpen || Boolean(Object.keys(openMetamaskTabsIDs).length) || notificationIsOpen + return ( + popupIsOpen || + Boolean(Object.keys(openMetamaskTabsIDs).length) || + notificationIsOpen + ) } /** @@ -323,7 +325,7 @@ function setupController (initState, initLangCode) { * This method identifies trusted (MetaMask) interfaces, and connects them differently from untrusted (web pages). * @param {Port} remotePort - The port provided by a new context. */ - function connectRemote (remotePort) { + function connectRemote(remotePort) { const processName = remotePort.name const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName] @@ -381,7 +383,7 @@ function setupController (initState, initLangCode) { } // communication with page or other extension - function connectExternal (remotePort) { + function connectExternal(remotePort) { const portStream = new PortStream(remotePort) controller.setupUntrustedCommunication(portStream, remotePort.sender) } @@ -404,18 +406,30 @@ function setupController (initState, initLangCode) { * Updates the Web Extension's "badge" number, on the little fox in the toolbar. * The number reflects the current number of pending transactions or message signatures needing user approval. */ - function updateBadge () { + function updateBadge() { let label = '' const unapprovedTxCount = controller.txController.getUnapprovedTxCount() const { unapprovedMsgCount } = controller.messageManager const { unapprovedPersonalMsgCount } = controller.personalMessageManager const { unapprovedDecryptMsgCount } = controller.decryptMessageManager - const { unapprovedEncryptionPublicKeyMsgCount } = controller.encryptionPublicKeyManager + const { + unapprovedEncryptionPublicKeyMsgCount, + } = controller.encryptionPublicKeyManager const { unapprovedTypedMessagesCount } = controller.typedMessageManager - const pendingPermissionRequests = Object.keys(controller.permissionsController.permissions.state.permissionsRequests).length - const waitingForUnlockCount = controller.appStateController.waitingForUnlock.length - const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount + - unapprovedTypedMessagesCount + pendingPermissionRequests + waitingForUnlockCount + const pendingPermissionRequests = Object.keys( + controller.permissionsController.permissions.state.permissionsRequests, + ).length + const waitingForUnlockCount = + controller.appStateController.waitingForUnlock.length + const count = + unapprovedTxCount + + unapprovedMsgCount + + unapprovedPersonalMsgCount + + unapprovedDecryptMsgCount + + unapprovedEncryptionPublicKeyMsgCount + + unapprovedTypedMessagesCount + + pendingPermissionRequests + + waitingForUnlockCount if (count) { label = String(count) } @@ -433,10 +447,18 @@ function setupController (initState, initLangCode) { /** * Opens the browser popup for user confirmation */ -async function triggerUi () { +async function triggerUi() { const tabs = await platform.getActiveTabs() - const currentlyActiveMetamaskTab = Boolean(tabs.find((tab) => openMetamaskTabsIDs[tab.id])) - if (!popupIsOpen && !currentlyActiveMetamaskTab) { + const currentlyActiveMetamaskTab = Boolean( + tabs.find((tab) => openMetamaskTabsIDs[tab.id]), + ) + // Vivaldi is not closing port connection on popup close, so popupIsOpen does not work correctly + // To be reviewed in the future if this behaviour is fixed - also the way we determine isVivaldi variable might change at some point + const isVivaldi = + tabs.length > 0 && + tabs[0].extData && + tabs[0].extData.indexOf('vivaldi_tab') > -1 + if ((isVivaldi || !popupIsOpen) && !currentlyActiveMetamaskTab) { await notificationManager.showPopup() } } @@ -445,23 +467,24 @@ async function triggerUi () { * Opens the browser popup for user confirmation of watchAsset * then it waits until user interact with the UI */ -async function openPopup () { +async function openPopup() { await triggerUi() - await new Promise( - (resolve) => { - const interval = setInterval(() => { - if (!notificationIsOpen) { - clearInterval(interval) - resolve() - } - }, 1000) - }, - ) + await new Promise((resolve) => { + const interval = setInterval(() => { + if (!notificationIsOpen) { + clearInterval(interval) + resolve() + } + }, 1000) + }) } // On first install, open a new tab with MetaMask extension.runtime.onInstalled.addListener(({ reason }) => { - if (reason === 'install' && !(process.env.METAMASK_DEBUG || process.env.IN_TEST)) { + if ( + reason === 'install' && + !(process.env.METAMASK_DEBUG || process.env.IN_TEST) + ) { platform.openExtensionInBrowser() } }) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index d85aca3f2..05d82c538 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -9,7 +9,10 @@ import PortStream from 'extension-port-stream' const fs = require('fs') const path = require('path') -const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js'), 'utf8') +const inpageContent = fs.readFileSync( + path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js'), + 'utf8', +) const inpageSuffix = `//# sourceURL=${extension.runtime.getURL('inpage.js')}\n` const inpageBundle = inpageContent + inpageSuffix @@ -30,7 +33,7 @@ if (shouldInjectProvider()) { * * @param {string} content - Code to be executed in the current document */ -function injectScript (content) { +function injectScript(content) { try { const container = document.head || document.documentElement const scriptTag = document.createElement('script') @@ -47,7 +50,7 @@ function injectScript (content) { * Sets up the stream communication and submits site metadata * */ -async function start () { +async function start() { await setupStreams() await domIsReady() } @@ -57,7 +60,7 @@ async function start () { * browser extension and local per-page browser context. * */ -async function setupStreams () { +async function setupStreams() { // the transport-specific streams for communication between inpage and background const pageStream = new LocalMessageDuplexStream({ name: 'contentscript', @@ -73,17 +76,11 @@ async function setupStreams () { const extensionMux = new ObjectMultiplex() extensionMux.setMaxListeners(25) - pump( - pageMux, - pageStream, - pageMux, - (err) => logStreamDisconnectWarning('MetaMask Inpage Multiplex', err), + pump(pageMux, pageStream, pageMux, (err) => + logStreamDisconnectWarning('MetaMask Inpage Multiplex', err), ) - pump( - extensionMux, - extensionStream, - extensionMux, - (err) => logStreamDisconnectWarning('MetaMask Background Multiplex', err), + pump(extensionMux, extensionStream, extensionMux, (err) => + logStreamDisconnectWarning('MetaMask Background Multiplex', err), ) // forward communication across inpage-background for these channels only @@ -95,14 +92,14 @@ async function setupStreams () { phishingStream.once('data', redirectToPhishingWarning) } -function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { +function forwardTrafficBetweenMuxers(channelName, muxA, muxB) { const channelA = muxA.createStream(channelName) const channelB = muxB.createStream(channelName) - pump( - channelA, - channelB, - channelA, - (err) => logStreamDisconnectWarning(`MetaMask muxed traffic for channel "${channelName}" failed.`, err), + pump(channelA, channelB, channelA, (err) => + logStreamDisconnectWarning( + `MetaMask muxed traffic for channel "${channelName}" failed.`, + err, + ), ) } @@ -112,7 +109,7 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { * @param {string} remoteLabel - Remote stream name * @param {Error} err - Stream connection error */ -function logStreamDisconnectWarning (remoteLabel, err) { +function logStreamDisconnectWarning(remoteLabel, err) { let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}` if (err) { warningMsg += `\n${err.stack}` @@ -123,19 +120,23 @@ function logStreamDisconnectWarning (remoteLabel, err) { /** * Determines if the provider should be injected * - * @returns {boolean} {@code true} - if the provider should be injected + * @returns {boolean} {@code true} Whether the provider should be injected */ -function shouldInjectProvider () { - return doctypeCheck() && suffixCheck() && - documentElementCheck() && !blockedDomainCheck() +function shouldInjectProvider() { + return ( + doctypeCheck() && + suffixCheck() && + documentElementCheck() && + !blockedDomainCheck() + ) } /** * Checks the doctype of the current document if it exists * - * @returns {boolean} {@code true} - if the doctype is html or if none exists + * @returns {boolean} {@code true} if the doctype is html or if none exists */ -function doctypeCheck () { +function doctypeCheck() { const { doctype } = window.document if (doctype) { return doctype.name === 'html' @@ -150,13 +151,10 @@ function doctypeCheck () { * that we should not inject the provider into. This check is indifferent of * query parameters in the location. * - * @returns {boolean} - whether or not the extension of the current document is prohibited + * @returns {boolean} whether or not the extension of the current document is prohibited */ -function suffixCheck () { - const prohibitedTypes = [ - /\.xml$/u, - /\.pdf$/u, - ] +function suffixCheck() { + const prohibitedTypes = [/\.xml$/u, /\.pdf$/u] const currentUrl = window.location.pathname for (let i = 0; i < prohibitedTypes.length; i++) { if (prohibitedTypes[i].test(currentUrl)) { @@ -169,9 +167,9 @@ function suffixCheck () { /** * Checks the documentElement of the current document * - * @returns {boolean} {@code true} - if the documentElement is an html node or if none exists + * @returns {boolean} {@code true} if the documentElement is an html node or if none exists */ -function documentElementCheck () { +function documentElementCheck() { const documentElement = document.documentElement.nodeName if (documentElement) { return documentElement.toLowerCase() === 'html' @@ -182,9 +180,9 @@ function documentElementCheck () { /** * Checks if the current domain is blocked * - * @returns {boolean} {@code true} - if the current domain is blocked + * @returns {boolean} {@code true} if the current domain is blocked */ -function blockedDomainCheck () { +function blockedDomainCheck() { const blockedDomains = [ 'uscourts.gov', 'dropbox.com', @@ -201,7 +199,10 @@ function blockedDomainCheck () { let currentRegex for (let i = 0; i < blockedDomains.length; i++) { const blockedDomain = blockedDomains[i].replace('.', '\\.') - currentRegex = new RegExp(`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`, 'u') + currentRegex = new RegExp( + `(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`, + 'u', + ) if (!currentRegex.test(currentUrl)) { return true } @@ -212,7 +213,7 @@ function blockedDomainCheck () { /** * Redirects the current page to a phishing information page */ -function redirectToPhishingWarning () { +function redirectToPhishingWarning() { console.log('MetaMask - routing to Phishing Warning component') const extensionURL = extension.runtime.getURL('phishing.html') window.location.href = `${extensionURL}#${querystring.stringify({ @@ -224,11 +225,13 @@ function redirectToPhishingWarning () { /** * Returns a promise that resolves when the DOM is loaded (does not wait for images to load) */ -async function domIsReady () { +async function domIsReady() { // already loaded if (['interactive', 'complete'].includes(document.readyState)) { return undefined } // wait for load - return new Promise((resolve) => window.addEventListener('DOMContentLoaded', resolve, { once: true })) + return new Promise((resolve) => + window.addEventListener('DOMContentLoaded', resolve, { once: true }), + ) } diff --git a/app/scripts/controllers/alert.js b/app/scripts/controllers/alert.js index dd8dc66fe..a2dd3a7db 100644 --- a/app/scripts/controllers/alert.js +++ b/app/scripts/controllers/alert.js @@ -21,14 +21,13 @@ export const ALERT_TYPES = { } const defaultState = { - alertEnabledness: Object.keys(ALERT_TYPES) - .reduce( - (alertEnabledness, alertType) => { - alertEnabledness[alertType] = true - return alertEnabledness - }, - {}, - ), + alertEnabledness: Object.keys(ALERT_TYPES).reduce( + (alertEnabledness, alertType) => { + alertEnabledness[alertType] = true + return alertEnabledness + }, + {}, + ), unconnectedAccountAlertShownOrigins: {}, } @@ -37,12 +36,11 @@ const defaultState = { * alert related state */ export default class AlertController { - /** * @constructor * @param {AlertControllerOptions} [opts] - Controller configuration parameters */ - constructor (opts = {}) { + constructor(opts = {}) { const { initState, preferencesStore } = opts const state = { ...defaultState, @@ -56,14 +54,17 @@ export default class AlertController { preferencesStore.subscribe(({ selectedAddress }) => { const currentState = this.store.getState() - if (currentState.unconnectedAccountAlertShownOrigins && this.selectedAddress !== selectedAddress) { + if ( + currentState.unconnectedAccountAlertShownOrigins && + this.selectedAddress !== selectedAddress + ) { this.selectedAddress = selectedAddress this.store.updateState({ unconnectedAccountAlertShownOrigins: {} }) } }) } - setAlertEnabledness (alertId, enabledness) { + setAlertEnabledness(alertId, enabledness) { let { alertEnabledness } = this.store.getState() alertEnabledness = { ...alertEnabledness } alertEnabledness[alertId] = enabledness @@ -74,9 +75,11 @@ export default class AlertController { * Sets the "switch to connected" alert as shown for the given origin * @param {string} origin - The origin the alert has been shown for */ - setUnconnectedAccountAlertShown (origin) { + setUnconnectedAccountAlertShown(origin) { let { unconnectedAccountAlertShownOrigins } = this.store.getState() - unconnectedAccountAlertShownOrigins = { ...unconnectedAccountAlertShownOrigins } + unconnectedAccountAlertShownOrigins = { + ...unconnectedAccountAlertShownOrigins, + } unconnectedAccountAlertShownOrigins[origin] = true this.store.updateState({ unconnectedAccountAlertShownOrigins }) } diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index edfe1c5b6..738a0ef85 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -2,12 +2,11 @@ import EventEmitter from 'events' import ObservableStore from 'obs-store' export default class AppStateController extends EventEmitter { - /** * @constructor - * @param opts + * @param {Object} opts */ - constructor (opts = {}) { + constructor(opts = {}) { const { addUnlockListener, isUnlocked, @@ -23,7 +22,8 @@ export default class AppStateController extends EventEmitter { timeoutMinutes: 0, connectedStatusPopoverHasBeenShown: true, swapsWelcomeMessageHasBeenShown: false, - defaultHomeActiveTabName: null, ...initState, + defaultHomeActiveTabName: null, + ...initState, }) this.timer = null @@ -53,7 +53,7 @@ export default class AppStateController extends EventEmitter { * @returns {Promise} A promise that resolves when the extension is * unlocked, or immediately if the extension is already unlocked. */ - getUnlockPromise (shouldShowUnlockRequest) { + getUnlockPromise(shouldShowUnlockRequest) { return new Promise((resolve) => { if (this.isUnlocked()) { resolve() @@ -72,7 +72,7 @@ export default class AppStateController extends EventEmitter { * @param {boolean} shouldShowUnlockRequest - Whether the extension notification * popup should be opened. */ - waitForUnlock (resolve, shouldShowUnlockRequest) { + waitForUnlock(resolve, shouldShowUnlockRequest) { this.waitingForUnlock.push({ resolve }) this.emit('updateBadge') if (shouldShowUnlockRequest) { @@ -83,7 +83,7 @@ export default class AppStateController extends EventEmitter { /** * Drains the waitingForUnlock queue, resolving all the related Promises. */ - handleUnlock () { + handleUnlock() { if (this.waitingForUnlock.length > 0) { while (this.waitingForUnlock.length > 0) { this.waitingForUnlock.shift().resolve() @@ -96,7 +96,7 @@ export default class AppStateController extends EventEmitter { * Sets the default home tab * @param {string} [defaultHomeActiveTabName] - the tab name */ - setDefaultHomeActiveTabName (defaultHomeActiveTabName) { + setDefaultHomeActiveTabName(defaultHomeActiveTabName) { this.store.updateState({ defaultHomeActiveTabName, }) @@ -105,7 +105,7 @@ export default class AppStateController extends EventEmitter { /** * Record that the user has seen the connected status info popover */ - setConnectedStatusPopoverHasBeenShown () { + setConnectedStatusPopoverHasBeenShown() { this.store.updateState({ connectedStatusPopoverHasBeenShown: true, }) @@ -114,7 +114,7 @@ export default class AppStateController extends EventEmitter { /** * Record that the user has seen the swap screen welcome message */ - setSwapsWelcomeMessageHasBeenShown () { + setSwapsWelcomeMessageHasBeenShown() { this.store.updateState({ swapsWelcomeMessageHasBeenShown: true, }) @@ -124,7 +124,7 @@ export default class AppStateController extends EventEmitter { * Sets the last active time to the current time * @returns {void} */ - setLastActiveTime () { + setLastActiveTime() { this._resetTimer() } @@ -134,7 +134,7 @@ export default class AppStateController extends EventEmitter { * @returns {void} * @private */ - _setInactiveTimeout (timeoutMinutes) { + _setInactiveTimeout(timeoutMinutes) { this.store.updateState({ timeoutMinutes, }) @@ -151,7 +151,7 @@ export default class AppStateController extends EventEmitter { * @returns {void} * @private */ - _resetTimer () { + _resetTimer() { const { timeoutMinutes } = this.store.getState() if (this.timer) { @@ -162,6 +162,9 @@ export default class AppStateController extends EventEmitter { return } - this.timer = setTimeout(() => this.onInactiveTimeout(), timeoutMinutes * 60 * 1000) + this.timer = setTimeout( + () => this.onInactiveTimeout(), + timeoutMinutes * 60 * 1000, + ) } } diff --git a/app/scripts/controllers/cached-balances.js b/app/scripts/controllers/cached-balances.js index 87ca28662..6e7e4eace 100644 --- a/app/scripts/controllers/cached-balances.js +++ b/app/scripts/controllers/cached-balances.js @@ -12,13 +12,12 @@ import ObservableStore from 'obs-store' * a cache of account balances in local storage */ export default class CachedBalancesController { - /** * Creates a new controller instance * - * @param {CachedBalancesOptions} [opts] Controller configuration parameters + * @param {CachedBalancesOptions} [opts] - Controller configuration parameters */ - constructor (opts = {}) { + constructor(opts = {}) { const { accountTracker, getNetwork } = opts this.accountTracker = accountTracker @@ -37,15 +36,18 @@ export default class CachedBalancesController { * @param {Object} obj - The the recently updated accounts object for the current network * @returns {Promise} */ - async updateCachedBalances ({ accounts }) { + async updateCachedBalances({ accounts }) { const network = await this.getNetwork() - const balancesToCache = await this._generateBalancesToCache(accounts, network) + const balancesToCache = await this._generateBalancesToCache( + accounts, + network, + ) this.store.updateState({ cachedBalances: balancesToCache, }) } - _generateBalancesToCache (newAccounts, currentNetwork) { + _generateBalancesToCache(newAccounts, currentNetwork) { const { cachedBalances } = this.store.getState() const currentNetworkBalancesToCache = { ...cachedBalances[currentNetwork] } @@ -68,7 +70,7 @@ export default class CachedBalancesController { * Removes cachedBalances */ - clearCachedBalances () { + clearCachedBalances() { this.store.updateState({ cachedBalances: {} }) } @@ -80,7 +82,7 @@ export default class CachedBalancesController { * @private * */ - _registerUpdates () { + _registerUpdates() { const update = this.updateCachedBalances.bind(this) this.accountTracker.store.subscribe(update) } diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index 26e8cd081..6c1c213a6 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -6,20 +6,25 @@ import { MAINNET } from './network/enums' // By default, poll every 3 minutes const DEFAULT_INTERVAL = 180 * 1000 -const SINGLE_CALL_BALANCES_ADDRESS = '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39' +const SINGLE_CALL_BALANCES_ADDRESS = + '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39' /** * A controller that polls for token exchange * rates based on a user's current token list */ export default class DetectTokensController { - /** * Creates a DetectTokensController * * @param {Object} [config] - Options to configure controller */ - constructor ({ interval = DEFAULT_INTERVAL, preferences, network, keyringMemStore } = {}) { + constructor({ + interval = DEFAULT_INTERVAL, + preferences, + network, + keyringMemStore, + } = {}) { this.preferences = preferences this.interval = interval this.network = network @@ -29,7 +34,7 @@ export default class DetectTokensController { /** * For each token in eth-contract-metadata, find check selectedAddress balance. */ - async detectNewTokens () { + async detectNewTokens() { if (!this.isActive) { return } @@ -40,7 +45,10 @@ export default class DetectTokensController { const tokensToDetect = [] this.web3.setProvider(this._network._provider) for (const contractAddress in contracts) { - if (contracts[contractAddress].erc20 && !(this.tokenAddresses.includes(contractAddress.toLowerCase()))) { + if ( + contracts[contractAddress].erc20 && + !this.tokenAddresses.includes(contractAddress.toLowerCase()) + ) { tokensToDetect.push(contractAddress) } } @@ -49,20 +57,29 @@ export default class DetectTokensController { try { result = await this._getTokenBalances(tokensToDetect) } catch (error) { - warn(`MetaMask - DetectTokensController single call balance fetch failed`, error) + warn( + `MetaMask - DetectTokensController single call balance fetch failed`, + error, + ) return } tokensToDetect.forEach((tokenAddress, index) => { const balance = result[index] if (balance && !balance.isZero()) { - this._preferences.addToken(tokenAddress, contracts[tokenAddress].symbol, contracts[tokenAddress].decimals) + this._preferences.addToken( + tokenAddress, + contracts[tokenAddress].symbol, + contracts[tokenAddress].decimals, + ) } }) } - async _getTokenBalances (tokens) { - const ethContract = this.web3.eth.contract(SINGLE_CALL_BALANCES_ABI).at(SINGLE_CALL_BALANCES_ADDRESS) + async _getTokenBalances(tokens) { + const ethContract = this.web3.eth + .contract(SINGLE_CALL_BALANCES_ABI) + .at(SINGLE_CALL_BALANCES_ADDRESS) return new Promise((resolve, reject) => { ethContract.balances([this.selectedAddress], tokens, (error, result) => { if (error) { @@ -78,7 +95,7 @@ export default class DetectTokensController { * in case of address change or user session initialization. * */ - restartTokenDetection () { + restartTokenDetection() { if (!(this.isActive && this.selectedAddress)) { return } @@ -90,7 +107,7 @@ export default class DetectTokensController { /** * @type {Number} */ - set interval (interval) { + set interval(interval) { this._handle && clearInterval(this._handle) if (!interval) { return @@ -104,7 +121,7 @@ export default class DetectTokensController { * In setter when selectedAddress is changed, detectNewTokens and restart polling * @type {Object} */ - set preferences (preferences) { + set preferences(preferences) { if (!preferences) { return } @@ -129,7 +146,7 @@ export default class DetectTokensController { /** * @type {Object} */ - set network (network) { + set network(network) { if (!network) { return } @@ -141,7 +158,7 @@ export default class DetectTokensController { * In setter when isUnlocked is updated to true, detectNewTokens and restart polling * @type {Object} */ - set keyringMemStore (keyringMemStore) { + set keyringMemStore(keyringMemStore) { if (!keyringMemStore) { return } @@ -160,7 +177,7 @@ export default class DetectTokensController { * Internal isActive state * @type {Object} */ - get isActive () { + get isActive() { return this.isOpen && this.isUnlocked } /* eslint-enable accessor-pairs */ diff --git a/app/scripts/controllers/ens/ens.js b/app/scripts/controllers/ens/ens.js index 5a1a4dc81..f29d46aaa 100644 --- a/app/scripts/controllers/ens/ens.js +++ b/app/scripts/controllers/ens/ens.js @@ -2,22 +2,22 @@ import EthJsEns from 'ethjs-ens' import ensNetworkMap from 'ethereum-ens-network-map' export default class Ens { - static getNetworkEnsSupport (network) { + static getNetworkEnsSupport(network) { return Boolean(ensNetworkMap[network]) } - constructor ({ network, provider } = {}) { + constructor({ network, provider } = {}) { this._ethJsEns = new EthJsEns({ network, provider, }) } - lookup (ensName) { + lookup(ensName) { return this._ethJsEns.lookup(ensName) } - reverse (address) { + reverse(address) { return this._ethJsEns.reverse(address) } } diff --git a/app/scripts/controllers/ens/index.js b/app/scripts/controllers/ens/index.js index 26fde22c6..766aeeb88 100644 --- a/app/scripts/controllers/ens/index.js +++ b/app/scripts/controllers/ens/index.js @@ -8,7 +8,7 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const ZERO_X_ERROR_ADDRESS = '0x' export default class EnsController { - constructor ({ ens, provider, networkStore } = {}) { + constructor({ ens, provider, networkStore } = {}) { const initState = { ensResolutionsByAddress: {}, } @@ -38,11 +38,11 @@ export default class EnsController { }) } - reverseResolveAddress (address) { + reverseResolveAddress(address) { return this._reverseResolveAddress(ethUtil.toChecksumAddress(address)) } - async _reverseResolveAddress (address) { + async _reverseResolveAddress(address) { if (!this._ens) { return undefined } @@ -68,7 +68,10 @@ export default class EnsController { return undefined } - if (registeredAddress === ZERO_ADDRESS || registeredAddress === ZERO_X_ERROR_ADDRESS) { + if ( + registeredAddress === ZERO_ADDRESS || + registeredAddress === ZERO_X_ERROR_ADDRESS + ) { return undefined } @@ -80,7 +83,7 @@ export default class EnsController { return domain } - _updateResolutionsByAddress (address, domain) { + _updateResolutionsByAddress(address, domain) { const oldState = this.store.getState() this.store.putState({ ensResolutionsByAddress: { diff --git a/app/scripts/controllers/incoming-transactions.js b/app/scripts/controllers/incoming-transactions.js index de588f38b..2f70d63cd 100644 --- a/app/scripts/controllers/incoming-transactions.js +++ b/app/scripts/controllers/incoming-transactions.js @@ -6,30 +6,49 @@ import { bnToHex } from '../lib/util' import fetchWithTimeout from '../lib/fetch-with-timeout' import { - ROPSTEN, - RINKEBY, - KOVAN, + TRANSACTION_CATEGORIES, + TRANSACTION_STATUSES, +} from '../../../shared/constants/transaction' +import { + CHAIN_ID_TO_NETWORK_ID_MAP, + CHAIN_ID_TO_TYPE_MAP, GOERLI, + GOERLI_CHAIN_ID, + KOVAN, + KOVAN_CHAIN_ID, MAINNET, - NETWORK_TYPE_TO_ID_MAP, + MAINNET_CHAIN_ID, + RINKEBY, + RINKEBY_CHAIN_ID, + ROPSTEN, + ROPSTEN_CHAIN_ID, } from './network/enums' const fetch = fetchWithTimeout({ timeout: 30000, }) -export default class IncomingTransactionsController { +/** + * This controller is responsible for retrieving incoming transactions. Etherscan is polled once every block to check + * for new incoming transactions for the current selected account on the current network + * + * Note that only the built-in Infura networks are supported (i.e. anything in `INFURA_PROVIDER_TYPES`). We will not + * attempt to retrieve incoming transactions on any custom RPC endpoints. + */ +const etherscanSupportedNetworks = [ + GOERLI_CHAIN_ID, + KOVAN_CHAIN_ID, + MAINNET_CHAIN_ID, + RINKEBY_CHAIN_ID, + ROPSTEN_CHAIN_ID, +] - constructor (opts = {}) { - const { - blockTracker, - networkController, - preferencesController, - } = opts +export default class IncomingTransactionsController { + constructor(opts = {}) { + const { blockTracker, networkController, preferencesController } = opts this.blockTracker = blockTracker this.networkController = networkController this.preferencesController = preferencesController - this.getCurrentNetwork = () => networkController.getProviderConfig().type this._onLatestBlock = async (newBlockNumberHex) => { const selectedAddress = this.preferencesController.getSelectedAddress() @@ -43,54 +62,66 @@ export default class IncomingTransactionsController { const initState = { incomingTransactions: {}, incomingTxLastFetchedBlocksByNetwork: { - [ROPSTEN]: null, - [RINKEBY]: null, - [KOVAN]: null, [GOERLI]: null, + [KOVAN]: null, [MAINNET]: null, - }, ...opts.initState, + [RINKEBY]: null, + [ROPSTEN]: null, + }, + ...opts.initState, } this.store = new ObservableStore(initState) - this.preferencesController.store.subscribe(pairwise((prevState, currState) => { - const { featureFlags: { showIncomingTransactions: prevShowIncomingTransactions } = {} } = prevState - const { featureFlags: { showIncomingTransactions: currShowIncomingTransactions } = {} } = currState + this.preferencesController.store.subscribe( + pairwise((prevState, currState) => { + const { + featureFlags: { + showIncomingTransactions: prevShowIncomingTransactions, + } = {}, + } = prevState + const { + featureFlags: { + showIncomingTransactions: currShowIncomingTransactions, + } = {}, + } = currState - if (currShowIncomingTransactions === prevShowIncomingTransactions) { - return - } + if (currShowIncomingTransactions === prevShowIncomingTransactions) { + return + } - if (prevShowIncomingTransactions && !currShowIncomingTransactions) { - this.stop() - return - } + if (prevShowIncomingTransactions && !currShowIncomingTransactions) { + this.stop() + return + } - this.start() - })) + this.start() + }), + ) - this.preferencesController.store.subscribe(pairwise(async (prevState, currState) => { - const { selectedAddress: prevSelectedAddress } = prevState - const { selectedAddress: currSelectedAddress } = currState + this.preferencesController.store.subscribe( + pairwise(async (prevState, currState) => { + const { selectedAddress: prevSelectedAddress } = prevState + const { selectedAddress: currSelectedAddress } = currState - if (currSelectedAddress === prevSelectedAddress) { - return - } + if (currSelectedAddress === prevSelectedAddress) { + return + } - await this._update({ - address: currSelectedAddress, - }) - })) + await this._update({ + address: currSelectedAddress, + }) + }), + ) - this.networkController.on('networkDidChange', async (newType) => { + this.networkController.on('networkDidChange', async () => { const address = this.preferencesController.getSelectedAddress() await this._update({ address, - networkType: newType, }) }) } - start () { + start() { const { featureFlags = {} } = this.preferencesController.store.getState() const { showIncomingTransactions } = featureFlags @@ -102,33 +133,45 @@ export default class IncomingTransactionsController { this.blockTracker.addListener('latest', this._onLatestBlock) } - stop () { + stop() { this.blockTracker.removeListener('latest', this._onLatestBlock) } - async _update ({ address, newBlockNumberDec, networkType } = {}) { + async _update({ address, newBlockNumberDec } = {}) { + const chainId = this.networkController.getCurrentChainId() + if (!etherscanSupportedNetworks.includes(chainId)) { + return + } try { - const dataForUpdate = await this._getDataForUpdate({ address, newBlockNumberDec, networkType }) - await this._updateStateWithNewTxData(dataForUpdate) + const dataForUpdate = await this._getDataForUpdate({ + address, + chainId, + newBlockNumberDec, + }) + this._updateStateWithNewTxData(dataForUpdate) } catch (err) { log.error(err) } } - async _getDataForUpdate ({ address, newBlockNumberDec, networkType } = {}) { + async _getDataForUpdate({ address, chainId, newBlockNumberDec } = {}) { const { incomingTransactions: currentIncomingTxs, incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork, } = this.store.getState() - const network = networkType || this.getCurrentNetwork() - const lastFetchBlockByCurrentNetwork = currentBlocksByNetwork[network] + const lastFetchBlockByCurrentNetwork = + currentBlocksByNetwork[CHAIN_ID_TO_TYPE_MAP[chainId]] let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec if (blockToFetchFrom === undefined) { blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16) } - const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(address, blockToFetchFrom, network) + const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll( + address, + blockToFetchFrom, + chainId, + ) return { latestIncomingTxBlockNumber, @@ -136,17 +179,17 @@ export default class IncomingTransactionsController { currentIncomingTxs, currentBlocksByNetwork, fetchedBlockNumber: blockToFetchFrom, - network, + chainId, } } - async _updateStateWithNewTxData ({ + _updateStateWithNewTxData({ latestIncomingTxBlockNumber, newTxs, currentIncomingTxs, currentBlocksByNetwork, fetchedBlockNumber, - network, + chainId, }) { const newLatestBlockHashByNetwork = latestIncomingTxBlockNumber ? parseInt(latestIncomingTxBlockNumber, 10) + 1 @@ -161,28 +204,23 @@ export default class IncomingTransactionsController { this.store.updateState({ incomingTxLastFetchedBlocksByNetwork: { ...currentBlocksByNetwork, - [network]: newLatestBlockHashByNetwork, + [CHAIN_ID_TO_TYPE_MAP[chainId]]: newLatestBlockHashByNetwork, }, incomingTransactions: newIncomingTransactions, }) } - async _fetchAll (address, fromBlock, networkType) { - const fetchedTxResponse = await this._fetchTxs(address, fromBlock, networkType) + async _fetchAll(address, fromBlock, chainId) { + const fetchedTxResponse = await this._fetchTxs(address, fromBlock, chainId) return this._processTxFetchResponse(fetchedTxResponse) } - async _fetchTxs (address, fromBlock, networkType) { - let etherscanSubdomain = 'api' - const currentNetworkID = NETWORK_TYPE_TO_ID_MAP[networkType]?.networkId + async _fetchTxs(address, fromBlock, chainId) { + const etherscanSubdomain = + chainId === MAINNET_CHAIN_ID + ? 'api' + : `api-${CHAIN_ID_TO_TYPE_MAP[chainId]}` - if (!currentNetworkID) { - return {} - } - - if (networkType !== MAINNET) { - etherscanSubdomain = `api-${networkType}` - } const apiUrl = `https://${etherscanSubdomain}.etherscan.io` let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1` @@ -195,22 +233,26 @@ export default class IncomingTransactionsController { return { ...parsedResponse, address, - currentNetworkID, + chainId, } } - _processTxFetchResponse ({ status, result = [], address, currentNetworkID }) { + _processTxFetchResponse({ status, result = [], address, chainId }) { if (status === '1' && Array.isArray(result) && result.length > 0) { const remoteTxList = {} const remoteTxs = [] result.forEach((tx) => { if (!remoteTxList[tx.hash]) { - remoteTxs.push(this._normalizeTxFromEtherscan(tx, currentNetworkID)) + remoteTxs.push(this._normalizeTxFromEtherscan(tx, chainId)) remoteTxList[tx.hash] = 1 } }) - const incomingTxs = remoteTxs.filter((tx) => tx.txParams.to && tx.txParams.to.toLowerCase() === address.toLowerCase()) + const incomingTxs = remoteTxs.filter( + (tx) => + tx.txParams.to && + tx.txParams.to.toLowerCase() === address.toLowerCase(), + ) incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1)) let latestIncomingTxBlockNumber = null @@ -218,7 +260,8 @@ export default class IncomingTransactionsController { if ( tx.blockNumber && (!latestIncomingTxBlockNumber || - parseInt(latestIncomingTxBlockNumber, 10) < parseInt(tx.blockNumber, 10)) + parseInt(latestIncomingTxBlockNumber, 10) < + parseInt(tx.blockNumber, 10)) ) { latestIncomingTxBlockNumber = tx.blockNumber } @@ -234,13 +277,16 @@ export default class IncomingTransactionsController { } } - _normalizeTxFromEtherscan (txMeta, currentNetworkID) { + _normalizeTxFromEtherscan(txMeta, chainId) { const time = parseInt(txMeta.timeStamp, 10) * 1000 - const status = txMeta.isError === '0' ? 'confirmed' : 'failed' + const status = + txMeta.isError === '0' + ? TRANSACTION_STATUSES.CONFIRMED + : TRANSACTION_STATUSES.FAILED return { blockNumber: txMeta.blockNumber, id: createId(), - metamaskNetworkId: currentNetworkID, + metamaskNetworkId: CHAIN_ID_TO_NETWORK_ID_MAP[chainId], status, time, txParams: { @@ -252,12 +298,12 @@ export default class IncomingTransactionsController { value: bnToHex(new BN(txMeta.value)), }, hash: txMeta.hash, - transactionCategory: 'incoming', + transactionCategory: TRANSACTION_CATEGORIES.INCOMING, } } } -function pairwise (fn) { +function pairwise(fn) { let first = true let cache return (value) => { diff --git a/app/scripts/controllers/network/contract-addresses.js b/app/scripts/controllers/network/contract-addresses.js index d09e9c48e..c2a8c9737 100644 --- a/app/scripts/controllers/network/contract-addresses.js +++ b/app/scripts/controllers/network/contract-addresses.js @@ -1,4 +1,8 @@ -export const SINGLE_CALL_BALANCES_ADDRESS = '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39' -export const SINGLE_CALL_BALANCES_ADDRESS_RINKEBY = '0x9f510b19f1ad66f0dcf6e45559fab0d6752c1db7' -export const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN = '0xb8e671734ce5c8d7dfbbea5574fa4cf39f7a54a4' -export const SINGLE_CALL_BALANCES_ADDRESS_KOVAN = '0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc' +export const SINGLE_CALL_BALANCES_ADDRESS = + '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39' +export const SINGLE_CALL_BALANCES_ADDRESS_RINKEBY = + '0x9f510b19f1ad66f0dcf6e45559fab0d6752c1db7' +export const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN = + '0xb8e671734ce5c8d7dfbbea5574fa4cf39f7a54a4' +export const SINGLE_CALL_BALANCES_ADDRESS_KOVAN = + '0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc' diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index 66aa69472..52048a775 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -10,7 +10,7 @@ import createInfuraMiddleware from 'eth-json-rpc-infura' import BlockTracker from 'eth-block-tracker' import * as networkEnums from './enums' -export default function createInfuraClient ({ network, projectId }) { +export default function createInfuraClient({ network, projectId }) { const infuraMiddleware = createInfuraMiddleware({ network, projectId, @@ -32,7 +32,7 @@ export default function createInfuraClient ({ network, projectId }) { return { networkMiddleware, blockTracker } } -function createNetworkAndChainIdMiddleware ({ network }) { +function createNetworkAndChainIdMiddleware({ network }) { let chainId let netId diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js index 823a34d49..b3c9cb062 100644 --- a/app/scripts/controllers/network/createJsonRpcClient.js +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -9,16 +9,12 @@ import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddlewa import BlockTracker from 'eth-block-tracker' const inTest = process.env.IN_TEST === 'true' -const blockTrackerOpts = inTest - ? { pollingInterval: 1000 } - : {} +const blockTrackerOpts = inTest ? { pollingInterval: 1000 } : {} const getTestMiddlewares = () => { - return inTest - ? [createEstimateGasDelayTestMiddleware()] - : [] + return inTest ? [createEstimateGasDelayTestMiddleware()] : [] } -export default function createJsonRpcClient ({ rpcUrl, chainId }) { +export default function createJsonRpcClient({ rpcUrl, chainId }) { const fetchMiddleware = createFetchMiddleware({ rpcUrl }) const blockProvider = providerFromMiddleware(fetchMiddleware) const blockTracker = new BlockTracker({ @@ -39,7 +35,7 @@ export default function createJsonRpcClient ({ rpcUrl, chainId }) { return { networkMiddleware, blockTracker } } -function createChainIdMiddleware (chainId) { +function createChainIdMiddleware(chainId) { return (req, res, next, end) => { if (req.method === 'eth_chainId') { res.result = chainId @@ -53,7 +49,7 @@ function createChainIdMiddleware (chainId) { * For use in tests only. * Adds a delay to `eth_estimateGas` calls. */ -function createEstimateGasDelayTestMiddleware () { +function createEstimateGasDelayTestMiddleware() { return createAsyncMiddleware(async (req, _, next) => { if (req.method === 'eth_estimateGas') { await new Promise((resolve) => setTimeout(resolve, 2000)) diff --git a/app/scripts/controllers/network/createMetamaskMiddleware.js b/app/scripts/controllers/network/createMetamaskMiddleware.js index 179164952..1d1291eea 100644 --- a/app/scripts/controllers/network/createMetamaskMiddleware.js +++ b/app/scripts/controllers/network/createMetamaskMiddleware.js @@ -1,9 +1,12 @@ import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware' import createScaffoldMiddleware from 'json-rpc-engine/src/createScaffoldMiddleware' import createWalletSubprovider from 'eth-json-rpc-middleware/wallet' -import { createPendingNonceMiddleware, createPendingTxMiddleware } from './middleware/pending' +import { + createPendingNonceMiddleware, + createPendingTxMiddleware, +} from './middleware/pending' -export default function createMetamaskMiddleware ({ +export default function createMetamaskMiddleware({ version, getAccounts, processTransaction, diff --git a/app/scripts/controllers/network/enums.js b/app/scripts/controllers/network/enums.js index 85adf3b45..6887a08dd 100644 --- a/app/scripts/controllers/network/enums.js +++ b/app/scripts/controllers/network/enums.js @@ -22,13 +22,7 @@ export const KOVAN_DISPLAY_NAME = 'Kovan' export const MAINNET_DISPLAY_NAME = 'Ethereum Mainnet' export const GOERLI_DISPLAY_NAME = 'Goerli' -export const INFURA_PROVIDER_TYPES = [ - ROPSTEN, - RINKEBY, - KOVAN, - MAINNET, - GOERLI, -] +export const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI] export const NETWORK_TYPE_TO_ID_MAP = { [ROPSTEN]: { networkId: ROPSTEN_NETWORK_ID, chainId: ROPSTEN_CHAIN_ID }, @@ -57,3 +51,17 @@ export const NETWORK_TO_NAME_MAP = { [GOERLI_CHAIN_ID]: GOERLI_DISPLAY_NAME, [MAINNET_CHAIN_ID]: MAINNET_DISPLAY_NAME, } + +export const CHAIN_ID_TO_TYPE_MAP = Object.entries( + NETWORK_TYPE_TO_ID_MAP, +).reduce((chainIdToTypeMap, [networkType, { chainId }]) => { + chainIdToTypeMap[chainId] = networkType + return chainIdToTypeMap +}, {}) + +export const CHAIN_ID_TO_NETWORK_ID_MAP = Object.values( + NETWORK_TYPE_TO_ID_MAP, +).reduce((chainIdToNetworkIdMap, { chainId, networkId }) => { + chainIdToNetworkIdMap[chainId] = networkId + return chainIdToNetworkIdMap +}, {}) diff --git a/app/scripts/controllers/network/middleware/pending.js b/app/scripts/controllers/network/middleware/pending.js index 1a67688c9..fe243ec28 100644 --- a/app/scripts/controllers/network/middleware/pending.js +++ b/app/scripts/controllers/network/middleware/pending.js @@ -1,7 +1,7 @@ import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware' import { formatTxMetaForRpcResult } from '../util' -export function createPendingNonceMiddleware ({ getPendingNonce }) { +export function createPendingNonceMiddleware({ getPendingNonce }) { return createAsyncMiddleware(async (req, res, next) => { const { method, params } = req if (method !== 'eth_getTransactionCount') { @@ -17,7 +17,7 @@ export function createPendingNonceMiddleware ({ getPendingNonce }) { }) } -export function createPendingTxMiddleware ({ getPendingTransactionByHash }) { +export function createPendingTxMiddleware({ getPendingTransactionByHash }) { return createAsyncMiddleware(async (req, res, next) => { const { method, params } = req if (method !== 'eth_getTransactionByHash') { diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index f9ffb8463..5527d8628 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -5,7 +5,10 @@ import ComposedStore from 'obs-store/lib/composed' import JsonRpcEngine from 'json-rpc-engine' import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine' import log from 'loglevel' -import { createSwappableProxy, createEventEmitterProxy } from 'swappable-obj-proxy' +import { + createSwappableProxy, + createEventEmitterProxy, +} from 'swappable-obj-proxy' import EthQuery from 'eth-query' import createMetamaskMiddleware from './createMetamaskMiddleware' import createInfuraClient from './createInfuraClient' @@ -40,8 +43,7 @@ const defaultProviderConfig = { } export default class NetworkController extends EventEmitter { - - constructor (opts = {}) { + constructor(opts = {}) { super() // create stores @@ -72,7 +74,7 @@ export default class NetworkController extends EventEmitter { * @throws {Error} if the project ID is not a valid string * @return {void} */ - setInfuraProjectId (projectId) { + setInfuraProjectId(projectId) { if (!projectId || typeof projectId !== 'string') { throw new Error('Invalid Infura project ID') } @@ -80,7 +82,7 @@ export default class NetworkController extends EventEmitter { this._infuraProjectId = projectId } - initializeProvider (providerParams) { + initializeProvider(providerParams) { this._baseProviderParams = providerParams const { type, rpcUrl, chainId } = this.getProviderConfig() this._configureProvider({ type, rpcUrl, chainId }) @@ -88,41 +90,45 @@ export default class NetworkController extends EventEmitter { } // return the proxies so the references will always be good - getProviderAndBlockTracker () { + getProviderAndBlockTracker() { const provider = this._providerProxy const blockTracker = this._blockTrackerProxy return { provider, blockTracker } } - verifyNetwork () { + verifyNetwork() { // Check network when restoring connectivity: if (this.isNetworkLoading()) { this.lookupNetwork() } } - getNetworkState () { + getNetworkState() { return this.networkStore.getState() } - setNetworkState (network) { + setNetworkState(network) { this.networkStore.putState(network) } - isNetworkLoading () { + isNetworkLoading() { return this.getNetworkState() === 'loading' } - lookupNetwork () { + lookupNetwork() { // Prevent firing when provider is not defined. if (!this._provider) { - log.warn('NetworkController - lookupNetwork aborted due to missing provider') + log.warn( + 'NetworkController - lookupNetwork aborted due to missing provider', + ) return } const chainId = this.getCurrentChainId() if (!chainId) { - log.warn('NetworkController - lookupNetwork aborted due to missing chainId') + log.warn( + 'NetworkController - lookupNetwork aborted due to missing chainId', + ) this.setNetworkState('loading') return } @@ -143,12 +149,12 @@ export default class NetworkController extends EventEmitter { }) } - getCurrentChainId () { + getCurrentChainId() { const { type, chainId: configChainId } = this.getProviderConfig() return NETWORK_TYPE_TO_ID_MAP[type]?.chainId || configChainId } - setRpcTarget (rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs) { + setRpcTarget(rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs) { this.setProviderConfig({ type: 'rpc', rpcUrl, @@ -159,26 +165,33 @@ export default class NetworkController extends EventEmitter { }) } - async setProviderType (type, rpcUrl = '', ticker = 'ETH', nickname = '') { - assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`) - assert(INFURA_PROVIDER_TYPES.includes(type), `NetworkController - Unknown rpc type "${type}"`) + async setProviderType(type, rpcUrl = '', ticker = 'ETH', nickname = '') { + assert.notEqual( + type, + 'rpc', + `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`, + ) + assert( + INFURA_PROVIDER_TYPES.includes(type), + `NetworkController - Unknown rpc type "${type}"`, + ) const { chainId } = NETWORK_TYPE_TO_ID_MAP[type] this.setProviderConfig({ type, rpcUrl, chainId, ticker, nickname }) } - resetConnection () { + resetConnection() { this.setProviderConfig(this.getProviderConfig()) } /** * Sets the provider config and switches the network. */ - setProviderConfig (config) { + setProviderConfig(config) { this.providerStore.updateState(config) this._switchNetwork(config) } - getProviderConfig () { + getProviderConfig() { return this.providerStore.getState() } @@ -186,26 +199,28 @@ export default class NetworkController extends EventEmitter { // Private // - _switchNetwork (opts) { + _switchNetwork(opts) { this.setNetworkState('loading') this._configureProvider(opts) this.emit('networkDidChange', opts.type) } - _configureProvider ({ type, rpcUrl, chainId }) { + _configureProvider({ type, rpcUrl, chainId }) { // infura type-based endpoints const isInfura = INFURA_PROVIDER_TYPES.includes(type) if (isInfura) { this._configureInfuraProvider(type, this._infuraProjectId) - // url-based rpc endpoints + // url-based rpc endpoints } else if (type === 'rpc') { this._configureStandardProvider(rpcUrl, chainId) } else { - throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`) + throw new Error( + `NetworkController - _configureProvider - unknown type "${type}"`, + ) } } - _configureInfuraProvider (type, projectId) { + _configureInfuraProvider(type, projectId) { log.info('NetworkController - configureInfuraProvider', type) const networkClient = createInfuraClient({ network: type, @@ -214,14 +229,16 @@ export default class NetworkController extends EventEmitter { this._setNetworkClient(networkClient) } - _configureStandardProvider (rpcUrl, chainId) { + _configureStandardProvider(rpcUrl, chainId) { log.info('NetworkController - configureStandardProvider', rpcUrl) const networkClient = createJsonRpcClient({ rpcUrl, chainId }) this._setNetworkClient(networkClient) } - _setNetworkClient ({ networkMiddleware, blockTracker }) { - const metamaskMiddleware = createMetamaskMiddleware(this._baseProviderParams) + _setNetworkClient({ networkMiddleware, blockTracker }) { + const metamaskMiddleware = createMetamaskMiddleware( + this._baseProviderParams, + ) const engine = new JsonRpcEngine() engine.push(metamaskMiddleware) engine.push(networkMiddleware) @@ -229,7 +246,7 @@ export default class NetworkController extends EventEmitter { this._setProviderAndBlockTracker({ provider, blockTracker }) } - _setProviderAndBlockTracker ({ provider, blockTracker }) { + _setProviderAndBlockTracker({ provider, blockTracker }) { // update or intialize proxies if (this._providerProxy) { this._providerProxy.setTarget(provider) @@ -239,7 +256,9 @@ export default class NetworkController extends EventEmitter { if (this._blockTrackerProxy) { this._blockTrackerProxy.setTarget(blockTracker) } else { - this._blockTrackerProxy = createEventEmitterProxy(blockTracker, { eventFilter: 'skipInternal' }) + this._blockTrackerProxy = createEventEmitterProxy(blockTracker, { + eventFilter: 'skipInternal', + }) } // set new provider and blockTracker this._provider = provider diff --git a/app/scripts/controllers/network/util.js b/app/scripts/controllers/network/util.js index bea71d666..958a1d4dd 100644 --- a/app/scripts/controllers/network/util.js +++ b/app/scripts/controllers/network/util.js @@ -2,21 +2,23 @@ import { NETWORK_TO_NAME_MAP } from './enums' export const getNetworkDisplayName = (key) => NETWORK_TO_NAME_MAP[key] -export function formatTxMetaForRpcResult (txMeta) { +export function formatTxMetaForRpcResult(txMeta) { return { - 'blockHash': txMeta.txReceipt ? txMeta.txReceipt.blockHash : null, - 'blockNumber': txMeta.txReceipt ? txMeta.txReceipt.blockNumber : null, - 'from': txMeta.txParams.from, - 'gas': txMeta.txParams.gas, - 'gasPrice': txMeta.txParams.gasPrice, - 'hash': txMeta.hash, - 'input': txMeta.txParams.data || '0x', - 'nonce': txMeta.txParams.nonce, - 'to': txMeta.txParams.to, - 'transactionIndex': txMeta.txReceipt ? txMeta.txReceipt.transactionIndex : null, - 'value': txMeta.txParams.value || '0x0', - 'v': txMeta.v, - 'r': txMeta.r, - 's': txMeta.s, + blockHash: txMeta.txReceipt ? txMeta.txReceipt.blockHash : null, + blockNumber: txMeta.txReceipt ? txMeta.txReceipt.blockNumber : null, + from: txMeta.txParams.from, + gas: txMeta.txParams.gas, + gasPrice: txMeta.txParams.gasPrice, + hash: txMeta.hash, + input: txMeta.txParams.data || '0x', + nonce: txMeta.txParams.nonce, + to: txMeta.txParams.to, + transactionIndex: txMeta.txReceipt + ? txMeta.txReceipt.transactionIndex + : null, + value: txMeta.txParams.value || '0x0', + v: txMeta.v, + r: txMeta.r, + s: txMeta.s, } } diff --git a/app/scripts/controllers/onboarding.js b/app/scripts/controllers/onboarding.js index 0ff92f5b1..71424a1e4 100644 --- a/app/scripts/controllers/onboarding.js +++ b/app/scripts/controllers/onboarding.js @@ -17,18 +17,17 @@ import log from 'loglevel' * state related to onboarding */ export default class OnboardingController { - /** * Creates a new controller instance * * @param {OnboardingOptions} [opts] Controller configuration parameters */ - constructor (opts = {}) { + constructor(opts = {}) { const initialTransientState = { onboardingTabs: {}, } const initState = { - seedPhraseBackedUp: true, + seedPhraseBackedUp: null, ...opts.initState, ...initialTransientState, } @@ -46,7 +45,7 @@ export default class OnboardingController { }) } - setSeedPhraseBackedUp (newSeedPhraseBackUpState) { + setSeedPhraseBackedUp(newSeedPhraseBackUpState) { this.store.updateState({ seedPhraseBackedUp: newSeedPhraseBackUpState, }) @@ -65,7 +64,9 @@ export default class OnboardingController { } const onboardingTabs = { ...this.store.getState().onboardingTabs } if (!onboardingTabs[location] || onboardingTabs[location] !== tabId) { - log.debug(`Registering onboarding tab at location '${location}' with tabId '${tabId}'`) + log.debug( + `Registering onboarding tab at location '${location}' with tabId '${tabId}'`, + ) onboardingTabs[location] = tabId this.store.updateState({ onboardingTabs }) } diff --git a/app/scripts/controllers/permissions/enums.js b/app/scripts/controllers/permissions/enums.js index a4d3afd5b..56a87fdf5 100644 --- a/app/scripts/controllers/permissions/enums.js +++ b/app/scripts/controllers/permissions/enums.js @@ -1,4 +1,3 @@ - export const WALLET_PREFIX = 'wallet_' export const HISTORY_STORE_KEY = 'permissionsHistory' @@ -23,9 +22,7 @@ export const NOTIFICATION_NAMES = { accountsChanged: 'wallet_accountsChanged', } -export const LOG_IGNORE_METHODS = [ - 'wallet_sendDomainMetadata', -] +export const LOG_IGNORE_METHODS = ['wallet_sendDomainMetadata'] export const LOG_METHOD_TYPES = { restricted: 'restricted', diff --git a/app/scripts/controllers/permissions/index.js b/app/scripts/controllers/permissions/index.js index 093983343..dac4eef94 100644 --- a/app/scripts/controllers/permissions/index.js +++ b/app/scripts/controllers/permissions/index.js @@ -24,8 +24,7 @@ import { } from './enums' export class PermissionsController { - - constructor ( + constructor( { getKeyringAccounts, getRestrictedMethods, @@ -38,7 +37,6 @@ export class PermissionsController { restoredPermissions = {}, restoredState = {}, ) { - // additional top-level store key set in _initializeMetadataStore this.store = new ObservableStore({ [LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [], @@ -75,8 +73,7 @@ export class PermissionsController { }) } - createMiddleware ({ origin, extensionId }) { - + createMiddleware({ origin, extensionId }) { if (typeof origin !== 'string' || !origin.length) { throw new Error('Must provide non-empty string origin.') } @@ -91,20 +88,26 @@ export class PermissionsController { engine.push(this.permissionsLog.createMiddleware()) - engine.push(createPermissionsMethodMiddleware({ - addDomainMetadata: this.addDomainMetadata.bind(this), - getAccounts: this.getAccounts.bind(this, origin), - getUnlockPromise: () => this._getUnlockPromise(true), - hasPermission: this.hasPermission.bind(this, origin), - notifyAccountsChanged: this.notifyAccountsChanged.bind(this, origin), - requestAccountsPermission: this._requestPermissions.bind( - this, { origin }, { eth_accounts: {} }, - ), - })) + engine.push( + createPermissionsMethodMiddleware({ + addDomainMetadata: this.addDomainMetadata.bind(this), + getAccounts: this.getAccounts.bind(this, origin), + getUnlockPromise: () => this._getUnlockPromise(true), + hasPermission: this.hasPermission.bind(this, origin), + notifyAccountsChanged: this.notifyAccountsChanged.bind(this, origin), + requestAccountsPermission: this._requestPermissions.bind( + this, + { origin }, + { eth_accounts: {} }, + ), + }), + ) - engine.push(this.permissions.providerMiddlewareFunction.bind( - this.permissions, { origin }, - )) + engine.push( + this.permissions.providerMiddlewareFunction.bind(this.permissions, { + origin, + }), + ) return asMiddleware(engine) } @@ -114,7 +117,7 @@ export class PermissionsController { * @param {string} origin - The requesting origin * @returns {Promise} The permissions request ID */ - async requestAccountsPermissionWithId (origin) { + async requestAccountsPermissionWithId(origin) { const id = nanoid() this._requestPermissions({ origin }, { eth_accounts: {} }, id) return id @@ -127,16 +130,19 @@ export class PermissionsController { * * @param {string} origin - The origin string. */ - getAccounts (origin) { + getAccounts(origin) { return new Promise((resolve, _) => { - const req = { method: 'eth_accounts' } const res = {} this.permissions.providerMiddlewareFunction( - { origin }, req, res, () => undefined, _end, + { origin }, + req, + res, + () => undefined, + _end, ) - function _end () { + function _end() { if (res.error || !Array.isArray(res.result)) { resolve([]) } else { @@ -153,7 +159,7 @@ export class PermissionsController { * @param {string} permission - The permission to check for. * @returns {boolean} Whether the origin has the permission. */ - hasPermission (origin, permission) { + hasPermission(origin, permission) { return Boolean(this.permissions.getPermission(origin, permission)) } @@ -162,7 +168,7 @@ export class PermissionsController { * * @returns {Object} identities */ - _getIdentities () { + _getIdentities() { return this.preferences.getState().identities } @@ -175,9 +181,8 @@ export class PermissionsController { * @returns {Promise} A Promise that resolves with the * approved permissions, or rejects with an error. */ - _requestPermissions (domain, permissions, id) { + _requestPermissions(domain, permissions, id) { return new Promise((resolve, reject) => { - // rpc-cap assigns an id to the request if there is none, as expected by // requestUserApproval below const req = { @@ -188,10 +193,14 @@ export class PermissionsController { const res = {} this.permissions.providerMiddlewareFunction( - domain, req, res, () => undefined, _end, + domain, + req, + res, + () => undefined, + _end, ) - function _end (_err) { + function _end(_err) { const err = _err || res.error if (err) { reject(err) @@ -211,8 +220,7 @@ export class PermissionsController { * @param {Object} approved - The request object approved by the user * @param {Array} accounts - The accounts to expose, if any */ - async approvePermissionsRequest (approved, accounts) { - + async approvePermissionsRequest(approved, accounts) { const { id } = approved.metadata const approval = this.pendingApprovals.get(id) @@ -222,28 +230,29 @@ export class PermissionsController { } try { - if (Object.keys(approved.permissions).length === 0) { - - approval.reject(ethErrors.rpc.invalidRequest({ - message: 'Must request at least one permission.', - })) - + approval.reject( + ethErrors.rpc.invalidRequest({ + message: 'Must request at least one permission.', + }), + ) } else { - // attempt to finalize the request and resolve it, // settings caveats as necessary approved.permissions = await this.finalizePermissionsRequest( - approved.permissions, accounts, + approved.permissions, + accounts, ) approval.resolve(approved.permissions) } } catch (err) { - // if finalization fails, reject the request - approval.reject(ethErrors.rpc.invalidRequest({ - message: err.message, data: err, - })) + approval.reject( + ethErrors.rpc.invalidRequest({ + message: err.message, + data: err, + }), + ) } this._removePendingApproval(id) @@ -256,7 +265,7 @@ export class PermissionsController { * * @param {string} id - The id of the request rejected by the user */ - async rejectPermissionsRequest (id) { + async rejectPermissionsRequest(id) { const approval = this.pendingApprovals.get(id) if (!approval) { @@ -277,8 +286,7 @@ export class PermissionsController { * @param {string} origin - The origin to expose the account to. * @param {string} account - The new account to expose. */ - async addPermittedAccount (origin, account) { - + async addPermittedAccount(origin, account) { const domains = this.permissions.getDomains() if (!domains[origin]) { throw new Error('Unrecognized domain') @@ -294,7 +302,8 @@ export class PermissionsController { } this.permissions.updateCaveatFor( - origin, 'eth_accounts', + origin, + 'eth_accounts', CAVEAT_NAMES.exposedAccounts, [...oldPermittedAccounts, account], ) @@ -315,8 +324,7 @@ export class PermissionsController { * @param {string} origin - The origin to remove the account from. * @param {string} account - The account to remove. */ - async removePermittedAccount (origin, account) { - + async removePermittedAccount(origin, account) { const domains = this.permissions.getDomains() if (!domains[origin]) { throw new Error('Unrecognized domain') @@ -331,15 +339,16 @@ export class PermissionsController { throw new Error('Account is not permitted for origin') } - let newPermittedAccounts = oldPermittedAccounts - .filter((acc) => acc !== account) + let newPermittedAccounts = oldPermittedAccounts.filter( + (acc) => acc !== account, + ) if (newPermittedAccounts.length === 0) { this.removePermissionsFor({ [origin]: ['eth_accounts'] }) } else { - this.permissions.updateCaveatFor( - origin, 'eth_accounts', + origin, + 'eth_accounts', CAVEAT_NAMES.exposedAccounts, newPermittedAccounts, ) @@ -358,14 +367,19 @@ export class PermissionsController { * * @param {string} account - The account to remove. */ - async removeAllAccountPermissions (account) { + async removeAllAccountPermissions(account) { this.validatePermittedAccounts([account]) const domains = this.permissions.getDomains() - const connectedOrigins = Object.keys(domains) - .filter((origin) => this._getPermittedAccounts(origin).includes(account)) + const connectedOrigins = Object.keys(domains).filter((origin) => + this._getPermittedAccounts(origin).includes(account), + ) - await Promise.all(connectedOrigins.map((origin) => this.removePermittedAccount(origin, account))) + await Promise.all( + connectedOrigins.map((origin) => + this.removePermittedAccount(origin, account), + ), + ) } /** @@ -378,15 +392,13 @@ export class PermissionsController { * @param {string[]} requestedAccounts - The accounts to expose, if any. * @returns {Object} The finalized permissions request object. */ - async finalizePermissionsRequest (requestedPermissions, requestedAccounts) { - + async finalizePermissionsRequest(requestedPermissions, requestedAccounts) { const finalizedPermissions = cloneDeep(requestedPermissions) const finalizedAccounts = cloneDeep(requestedAccounts) const { eth_accounts: ethAccounts } = finalizedPermissions if (ethAccounts) { - this.validatePermittedAccounts(finalizedAccounts) if (!ethAccounts.caveats) { @@ -394,9 +406,11 @@ export class PermissionsController { } // caveat names are unique, and we will only construct this caveat here - ethAccounts.caveats = ethAccounts.caveats.filter((c) => ( - c.name !== CAVEAT_NAMES.exposedAccounts && c.name !== CAVEAT_NAMES.primaryAccountOnly - )) + ethAccounts.caveats = ethAccounts.caveats.filter( + (c) => + c.name !== CAVEAT_NAMES.exposedAccounts && + c.name !== CAVEAT_NAMES.primaryAccountOnly, + ) ethAccounts.caveats.push({ type: CAVEAT_TYPES.limitResponseLength, @@ -420,7 +434,7 @@ export class PermissionsController { * * @param {string[]} accounts - An array of addresses. */ - validatePermittedAccounts (accounts) { + validatePermittedAccounts(accounts) { if (!Array.isArray(accounts) || accounts.length === 0) { throw new Error('Must provide non-empty array of account(s).') } @@ -441,8 +455,7 @@ export class PermissionsController { * @param {string} origin - The origin of the domain to notify. * @param {Array} newAccounts - The currently permitted accounts. */ - notifyAccountsChanged (origin, newAccounts) { - + notifyAccountsChanged(origin, newAccounts) { if (typeof origin !== 'string' || !origin) { throw new Error(`Invalid origin: '${origin}'`) } @@ -459,9 +472,7 @@ export class PermissionsController { // if the accounts changed from the perspective of the dapp, // update "last seen" time for the origin and account(s) // exception: no accounts -> no times to update - this.permissionsLog.updateAccountsHistory( - origin, newAccounts, - ) + this.permissionsLog.updateAccountsHistory(origin, newAccounts) // NOTE: // we don't check for accounts changing in the notifyAllDomains case, @@ -475,17 +486,14 @@ export class PermissionsController { * Should only be called after confirming that the permissions exist, to * avoid sending unnecessary notifications. * - * @param {Object} domains { origin: [permissions] } - The map of domain - * origins to permissions to remove. + * @param {Object} domains - The map of domain origins to permissions to remove. + * e.g. { origin: [permissions] } */ - removePermissionsFor (domains) { - + removePermissionsFor(domains) { Object.entries(domains).forEach(([origin, perms]) => { - this.permissions.removePermissionsFor( origin, perms.map((methodName) => { - if (methodName === 'eth_accounts') { this.notifyAccountsChanged(origin, []) } @@ -499,7 +507,7 @@ export class PermissionsController { /** * Removes all known domains and their related permissions. */ - clearPermissions () { + clearPermissions() { this.permissions.clearDomains() this._notifyAllDomains({ method: NOTIFICATION_NAMES.accountsChanged, @@ -517,8 +525,7 @@ export class PermissionsController { * @param {string} origin - The origin whose domain metadata to store. * @param {Object} metadata - The domain's metadata that will be stored. */ - addDomainMetadata (origin, metadata) { - + addDomainMetadata(origin, metadata) { const oldMetadataState = this.store.getState()[METADATA_STORE_KEY] const newMetadataState = { ...oldMetadataState } @@ -541,7 +548,10 @@ export class PermissionsController { lastUpdated: Date.now(), } - if (!newMetadataState[origin].extensionId && !newMetadataState[origin].host) { + if ( + !newMetadataState[origin].extensionId && + !newMetadataState[origin].host + ) { newMetadataState[origin].host = new URL(origin).host } @@ -557,8 +567,7 @@ export class PermissionsController { * * @param {Object} restoredState - The restored permissions controller state. */ - _initializeMetadataStore (restoredState) { - + _initializeMetadataStore(restoredState) { const metadataState = restoredState[METADATA_STORE_KEY] || {} const newMetadataState = this._trimDomainMetadata(metadataState) @@ -574,8 +583,7 @@ export class PermissionsController { * @param {Object} metadataState - The metadata store state object to trim. * @returns {Object} The new metadata state object. */ - _trimDomainMetadata (metadataState) { - + _trimDomainMetadata(metadataState) { const newMetadataState = { ...metadataState } const origins = Object.keys(metadataState) const permissionsDomains = this.permissions.getDomains() @@ -593,7 +601,7 @@ export class PermissionsController { * Replaces the existing domain metadata with the passed-in object. * @param {Object} newMetadataState - The new metadata to set. */ - _setDomainMetadata (newMetadataState) { + _setDomainMetadata(newMetadataState) { this.store.updateState({ [METADATA_STORE_KEY]: newMetadataState }) } @@ -603,11 +611,10 @@ export class PermissionsController { * @param {string} origin - The origin to obtain permitted accounts for * @returns {Array|null} The list of permitted accounts */ - _getPermittedAccounts (origin) { + _getPermittedAccounts(origin) { const permittedAccounts = this.permissions .getPermission(origin, 'eth_accounts') - ?.caveats - ?.find((caveat) => caveat.name === CAVEAT_NAMES.exposedAccounts) + ?.caveats?.find((caveat) => caveat.name === CAVEAT_NAMES.exposedAccounts) ?.value return permittedAccounts || null @@ -622,8 +629,7 @@ export class PermissionsController { * * @param {string} account - The newly selected account's address. */ - async _handleAccountSelected (account) { - + async _handleAccountSelected(account) { if (typeof account !== 'string') { throw new Error('Selected account should be a non-empty string.') } @@ -631,20 +637,20 @@ export class PermissionsController { const domains = this.permissions.getDomains() || {} const connectedDomains = Object.entries(domains) .filter(([_, { permissions }]) => { - const ethAccounts = permissions.find((permission) => permission.parentCapability === 'eth_accounts') - const exposedAccounts = ethAccounts - ?.caveats - .find((caveat) => caveat.name === 'exposedAccounts') - ?.value + const ethAccounts = permissions.find( + (permission) => permission.parentCapability === 'eth_accounts', + ) + const exposedAccounts = ethAccounts?.caveats.find( + (caveat) => caveat.name === 'exposedAccounts', + )?.value return exposedAccounts?.includes(account) }) .map(([domain]) => domain) await Promise.all( - connectedDomains - .map( - (origin) => this._handleConnectedAccountSelected(origin), - ), + connectedDomains.map((origin) => + this._handleConnectedAccountSelected(origin), + ), ) } @@ -656,7 +662,7 @@ export class PermissionsController { * * @param {string} origin - The origin */ - async _handleConnectedAccountSelected (origin) { + async _handleConnectedAccountSelected(origin) { const permittedAccounts = await this.getAccounts(origin) this.notifyAccountsChanged(origin, permittedAccounts) @@ -669,8 +675,7 @@ export class PermissionsController { * @param {Function} resolve - The function resolving the pending approval Promise. * @param {Function} reject - The function rejecting the pending approval Promise. */ - _addPendingApproval (id, origin, resolve, reject) { - + _addPendingApproval(id, origin, resolve, reject) { if ( this.pendingApprovalOrigins.has(origin) || this.pendingApprovals.has(id) @@ -688,7 +693,7 @@ export class PermissionsController { * Removes the pending approval with the given id. * @param {string} id - The id of the pending approval to remove. */ - _removePendingApproval (id) { + _removePendingApproval(id) { const { origin } = this.pendingApprovals.get(id) this.pendingApprovalOrigins.delete(origin) this.pendingApprovals.delete(id) @@ -698,51 +703,54 @@ export class PermissionsController { * A convenience method for retrieving a login object * or creating a new one if needed. * - * @param {string} origin = The origin string representing the domain. + * @param {string} origin - The origin string representing the domain. */ - _initializePermissions (restoredState) { - + _initializePermissions(restoredState) { // these permission requests are almost certainly stale const initState = { ...restoredState, permissionsRequests: [] } - this.permissions = new RpcCap({ + this.permissions = new RpcCap( + { + // Supports passthrough methods: + safeMethods: SAFE_METHODS, - // Supports passthrough methods: - safeMethods: SAFE_METHODS, + // optional prefix for internal methods + methodPrefix: WALLET_PREFIX, - // optional prefix for internal methods - methodPrefix: WALLET_PREFIX, + restrictedMethods: this._restrictedMethods, - restrictedMethods: this._restrictedMethods, + /** + * A promise-returning callback used to determine whether to approve + * permissions requests or not. + * + * Currently only returns a boolean, but eventually should return any + * specific parameters or amendments to the permissions. + * + * @param {string} req - The internal rpc-cap user request object. + */ + requestUserApproval: async (req) => { + const { + metadata: { id, origin }, + } = req - /** - * A promise-returning callback used to determine whether to approve - * permissions requests or not. - * - * Currently only returns a boolean, but eventually should return any - * specific parameters or amendments to the permissions. - * - * @param {string} req - The internal rpc-cap user request object. - */ - requestUserApproval: async (req) => { - const { metadata: { id, origin } } = req + if (this.pendingApprovalOrigins.has(origin)) { + throw ethErrors.rpc.resourceUnavailable( + 'Permissions request already pending; please wait.', + ) + } - if (this.pendingApprovalOrigins.has(origin)) { - throw ethErrors.rpc.resourceUnavailable( - 'Permissions request already pending; please wait.', - ) - } + this._showPermissionRequest() - this._showPermissionRequest() - - return new Promise((resolve, reject) => { - this._addPendingApproval(id, origin, resolve, reject) - }) + return new Promise((resolve, reject) => { + this._addPendingApproval(id, origin, resolve, reject) + }) + }, }, - }, initState) + initState, + ) } } -export function addInternalMethodPrefix (method) { +export function addInternalMethodPrefix(method) { return WALLET_PREFIX + method } diff --git a/app/scripts/controllers/permissions/permissionsLog.js b/app/scripts/controllers/permissions/permissionsLog.js index eda7c394a..6bb142993 100644 --- a/app/scripts/controllers/permissions/permissionsLog.js +++ b/app/scripts/controllers/permissions/permissionsLog.js @@ -14,8 +14,7 @@ import { * and permissions-related methods. */ export default class PermissionsLogController { - - constructor ({ restrictedMethods, store }) { + constructor({ restrictedMethods, store }) { this.restrictedMethods = restrictedMethods this.store = store } @@ -25,7 +24,7 @@ export default class PermissionsLogController { * * @returns {Array} The activity log. */ - getActivityLog () { + getActivityLog() { return this.store.getState()[LOG_STORE_KEY] || [] } @@ -34,7 +33,7 @@ export default class PermissionsLogController { * * @param {Array} logs - The new activity log array. */ - updateActivityLog (logs) { + updateActivityLog(logs) { this.store.updateState({ [LOG_STORE_KEY]: logs }) } @@ -43,7 +42,7 @@ export default class PermissionsLogController { * * @returns {Object} The permissions history log. */ - getHistory () { + getHistory() { return this.store.getState()[HISTORY_STORE_KEY] || {} } @@ -52,7 +51,7 @@ export default class PermissionsLogController { * * @param {Object} history - The new permissions history log object. */ - updateHistory (history) { + updateHistory(history) { this.store.updateState({ [HISTORY_STORE_KEY]: history }) } @@ -63,8 +62,7 @@ export default class PermissionsLogController { * @param {string} origin - The origin that the accounts are exposed to. * @param {Array} accounts - The accounts. */ - updateAccountsHistory (origin, accounts) { - + updateAccountsHistory(origin, accounts) { if (accounts.length === 0) { return } @@ -88,9 +86,8 @@ export default class PermissionsLogController { * * @returns {JsonRpcEngineMiddleware} The permissions log middleware. */ - createMiddleware () { + createMiddleware() { return (req, res, next, _end) => { - let activityEntry, requestedMethods const { origin, method } = req const isInternal = method.startsWith(WALLET_PREFIX) @@ -100,7 +97,6 @@ export default class PermissionsLogController { !LOG_IGNORE_METHODS.includes(method) && (isInternal || this.restrictedMethods.includes(method)) ) { - activityEntry = this.logRequest(req, isInternal) if (method === `${WALLET_PREFIX}requestPermissions`) { @@ -109,7 +105,6 @@ export default class PermissionsLogController { requestedMethods = this.getRequestedMethods(req) } } else if (method === 'eth_requestAccounts') { - // eth_requestAccounts is a special case; we need to extract the accounts // from it activityEntry = this.logRequest(req, isInternal) @@ -122,7 +117,6 @@ export default class PermissionsLogController { // call next with a return handler for capturing the response next((cb) => { - const time = Date.now() this.logResponse(activityEntry, res, time) @@ -130,7 +124,10 @@ export default class PermissionsLogController { // any permissions or accounts changes will be recorded on the response, // so we only log permissions history here this.logPermissionsHistory( - requestedMethods, origin, res.result, time, + requestedMethods, + origin, + res.result, + time, method === 'eth_requestAccounts', ) } @@ -145,13 +142,13 @@ export default class PermissionsLogController { * @param {Object} request - The request object. * @param {boolean} isInternal - Whether the request is internal. */ - logRequest (request, isInternal) { + logRequest(request, isInternal) { const activityEntry = { id: request.id, method: request.method, - methodType: ( - isInternal ? LOG_METHOD_TYPES.internal : LOG_METHOD_TYPES.restricted - ), + methodType: isInternal + ? LOG_METHOD_TYPES.internal + : LOG_METHOD_TYPES.restricted, origin: request.origin, request: cloneDeep(request), requestTime: Date.now(), @@ -171,8 +168,7 @@ export default class PermissionsLogController { * @param {Object} response - The response object. * @param {number} time - Output from Date.now() */ - logResponse (entry, response, time) { - + logResponse(entry, response, time) { if (!entry || !response) { return } @@ -188,8 +184,7 @@ export default class PermissionsLogController { * * @param {Object} entry - The activity log entry. */ - commitNewActivity (entry) { - + commitNewActivity(entry) { const logs = this.getActivityLog() // add new entry to end of log @@ -212,32 +207,31 @@ export default class PermissionsLogController { * @param {string} time - The time of the request, i.e. Date.now(). * @param {boolean} isEthRequestAccounts - Whether the permissions request was 'eth_requestAccounts'. */ - logPermissionsHistory ( - requestedMethods, origin, result, - time, isEthRequestAccounts, + logPermissionsHistory( + requestedMethods, + origin, + result, + time, + isEthRequestAccounts, ) { - let accounts, newEntries if (isEthRequestAccounts) { - accounts = result const accountToTimeMap = getAccountToTimeMap(accounts, time) newEntries = { - 'eth_accounts': { + eth_accounts: { accounts: accountToTimeMap, lastApproved: time, }, } } else { - // Records new "lastApproved" times for the granted permissions, if any. // Special handling for eth_accounts, in order to record the time the // accounts were last seen or approved by the origin. newEntries = result .map((perm) => { - if (perm.parentCapability === 'eth_accounts') { accounts = this.getAccountsFromPermission(perm) } @@ -245,13 +239,10 @@ export default class PermissionsLogController { return perm.parentCapability }) .reduce((acc, method) => { - // all approved permissions will be included in the response, // not just the newly requested ones if (requestedMethods.includes(method)) { - if (method === 'eth_accounts') { - const accountToTimeMap = getAccountToTimeMap(accounts, time) acc[method] = { @@ -280,8 +271,7 @@ export default class PermissionsLogController { * @param {string} origin - The requesting origin. * @param {Object} newEntries - The new entries to commit. */ - commitNewHistory (origin, newEntries) { - + commitNewHistory(origin, newEntries) { // a simple merge updates most permissions const history = this.getHistory() const newOriginHistory = { @@ -291,19 +281,16 @@ export default class PermissionsLogController { // eth_accounts requires special handling, because of information // we store about the accounts - const existingEthAccountsEntry = ( + const existingEthAccountsEntry = history[origin] && history[origin].eth_accounts - ) const newEthAccountsEntry = newEntries.eth_accounts if (existingEthAccountsEntry && newEthAccountsEntry) { - // we may intend to update just the accounts, not the permission // itself - const lastApproved = ( + const lastApproved = newEthAccountsEntry.lastApproved || existingEthAccountsEntry.lastApproved - ) // merge old and new eth_accounts history entries newOriginHistory.eth_accounts = { @@ -326,7 +313,7 @@ export default class PermissionsLogController { * @param {Object} request - The request object. * @returns {Array} The names of the requested permissions. */ - getRequestedMethods (request) { + getRequestedMethods(request) { if ( !request.params || !request.params[0] || @@ -345,20 +332,17 @@ export default class PermissionsLogController { * @param {Object} perm - The permissions object. * @returns {Array} The permitted accounts. */ - getAccountsFromPermission (perm) { - + getAccountsFromPermission(perm) { if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) { return [] } const accounts = new Set() for (const caveat of perm.caveats) { - if ( caveat.name === CAVEAT_NAMES.exposedAccounts && Array.isArray(caveat.value) ) { - for (const value of caveat.value) { accounts.add(value) } @@ -377,8 +361,6 @@ export default class PermissionsLogController { * @param {number} time - A time, e.g. Date.now(). * @returns {Object} A string:number map of addresses to time. */ -function getAccountToTimeMap (accounts, time) { - return accounts.reduce( - (acc, account) => ({ ...acc, [account]: time }), {}, - ) +function getAccountToTimeMap(accounts, time) { + return accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {}) } diff --git a/app/scripts/controllers/permissions/permissionsMethodMiddleware.js b/app/scripts/controllers/permissions/permissionsMethodMiddleware.js index a89d74632..eae072495 100644 --- a/app/scripts/controllers/permissions/permissionsMethodMiddleware.js +++ b/app/scripts/controllers/permissions/permissionsMethodMiddleware.js @@ -4,7 +4,7 @@ import { ethErrors } from 'eth-json-rpc-errors' /** * Create middleware for handling certain methods and preprocessing permissions requests. */ -export default function createPermissionsMethodMiddleware ({ +export default function createPermissionsMethodMiddleware({ addDomainMetadata, getAccounts, getUnlockPromise, @@ -12,26 +12,21 @@ export default function createPermissionsMethodMiddleware ({ notifyAccountsChanged, requestAccountsPermission, }) { - let isProcessingRequestAccounts = false return createAsyncMiddleware(async (req, res, next) => { - let responseHandler switch (req.method) { - // Intercepting eth_accounts requests for backwards compatibility: // The getAccounts call below wraps the rpc-cap middleware, and returns // an empty array in case of errors (such as 4100:unauthorized) case 'eth_accounts': { - res.result = await getAccounts() return } case 'eth_requestAccounts': { - if (isProcessingRequestAccounts) { res.error = ethErrors.rpc.resourceUnavailable( 'Already processing eth_requestAccounts. Please wait.', @@ -79,7 +74,6 @@ export default function createPermissionsMethodMiddleware ({ // custom method for getting metadata from the requesting domain, // sent automatically by the inpage provider when it's initialized case 'wallet_sendDomainMetadata': { - if (typeof req.domainMetadata?.name === 'string') { addDomainMetadata(req.origin, req.domainMetadata) } @@ -89,11 +83,8 @@ export default function createPermissionsMethodMiddleware ({ // register return handler to send accountsChanged notification case 'wallet_requestPermissions': { - if ('eth_accounts' in req.params?.[0]) { - responseHandler = async () => { - if (Array.isArray(res.result)) { for (const permission of res.result) { if (permission.parentCapability === 'eth_accounts') { diff --git a/app/scripts/controllers/permissions/restrictedMethods.js b/app/scripts/controllers/permissions/restrictedMethods.js index 0e1ae4b9a..b8dd39c71 100644 --- a/app/scripts/controllers/permissions/restrictedMethods.js +++ b/app/scripts/controllers/permissions/restrictedMethods.js @@ -1,6 +1,9 @@ -export default function getRestrictedMethods ({ getIdentities, getKeyringAccounts }) { +export default function getRestrictedMethods({ + getIdentities, + getKeyringAccounts, +}) { return { - 'eth_accounts': { + eth_accounts: { method: async (_, res, __, end) => { try { const accounts = await getKeyringAccounts() @@ -10,7 +13,10 @@ export default function getRestrictedMethods ({ getIdentities, getKeyringAccount throw new Error(`Missing identity for address ${firstAddress}`) } else if (!identities[secondAddress]) { throw new Error(`Missing identity for address ${secondAddress}`) - } else if (identities[firstAddress].lastSelected === identities[secondAddress].lastSelected) { + } else if ( + identities[firstAddress].lastSelected === + identities[secondAddress].lastSelected + ) { return 0 } else if (identities[firstAddress].lastSelected === undefined) { return 1 @@ -18,7 +24,10 @@ export default function getRestrictedMethods ({ getIdentities, getKeyringAccount return -1 } - return identities[secondAddress].lastSelected - identities[firstAddress].lastSelected + return ( + identities[secondAddress].lastSelected - + identities[firstAddress].lastSelected + ) }) end() } catch (err) { diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 02f4086fd..5df65dfe5 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -9,28 +9,27 @@ import { addInternalMethodPrefix } from './permissions' import { NETWORK_TYPE_TO_ID_MAP } from './network/enums' export default class PreferencesController { - /** * * @typedef {Object} PreferencesController * @param {Object} opts - Overrides the defaults for the initial state of this.store - * @property {object} store The stored object containing a users preferences, stored in local storage - * @property {array} store.frequentRpcList A list of custom rpcs to provide the user - * @property {array} store.tokens The tokens the user wants display in their token lists - * @property {object} store.accountTokens The tokens stored per account and then per network type - * @property {object} store.assetImages Contains assets objects related to assets added + * @property {Object} store The stored object containing a users preferences, stored in local storage + * @property {Array} store.frequentRpcList A list of custom rpcs to provide the user + * @property {Array} store.tokens The tokens the user wants display in their token lists + * @property {Object} store.accountTokens The tokens stored per account and then per network type + * @property {Object} store.assetImages Contains assets objects related to assets added * @property {boolean} store.useBlockie The users preference for blockie identicons within the UI * @property {boolean} store.useNonceField The users preference for nonce field within the UI - * @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the + * @property {Object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the * user wishes to see that feature. * * Feature flags can be set by the global function `setPreference(feature, enabled)`, and so should not expose any sensitive behavior. - * @property {object} store.knownMethodData Contains all data methods known by the user + * @property {Object} store.knownMethodData Contains all data methods known by the user * @property {string} store.currentLocale The preferred language locale key * @property {string} store.selectedAddress A hex string that matches the currently selected address in the app * */ - constructor (opts = {}) { + constructor(opts = {}) { const initState = { frequentRpcListDetail: [], accountTokens: {}, @@ -66,7 +65,8 @@ export default class PreferencesController { metaMetricsSendCount: 0, // ENS decentralized website resolution - ipfsGateway: 'dweb.link', ...opts.initState, + ipfsGateway: 'dweb.link', + ...opts.initState, } this.network = opts.network @@ -86,7 +86,7 @@ export default class PreferencesController { * Sets the {@code forgottenPassword} state property * @param {boolean} forgottenPassword - whether or not the user has forgotten their password */ - setPasswordForgotten (forgottenPassword) { + setPasswordForgotten(forgottenPassword) { this.store.updateState({ forgottenPassword }) } @@ -96,7 +96,7 @@ export default class PreferencesController { * @param {boolean} val - Whether or not the user prefers blockie indicators * */ - setUseBlockie (val) { + setUseBlockie(val) { this.store.updateState({ useBlockie: val }) } @@ -106,7 +106,7 @@ export default class PreferencesController { * @param {boolean} val - Whether or not the user prefers to set nonce * */ - setUseNonceField (val) { + setUseNonceField(val) { this.store.updateState({ useNonceField: val }) } @@ -116,7 +116,7 @@ export default class PreferencesController { * @param {boolean} val - Whether or not the user prefers phishing domain protection * */ - setUsePhishDetect (val) { + setUsePhishDetect(val) { this.store.updateState({ usePhishDetect: val }) } @@ -124,14 +124,19 @@ export default class PreferencesController { * Setter for the `participateInMetaMetrics` property * * @param {boolean} bool - Whether or not the user wants to participate in MetaMetrics - * @returns {string|null} - the string of the new metametrics id, or null if not set + * @returns {string|null} the string of the new metametrics id, or null if not set * */ - setParticipateInMetaMetrics (bool) { + setParticipateInMetaMetrics(bool) { this.store.updateState({ participateInMetaMetrics: bool }) let metaMetricsId = null if (bool && !this.store.getState().metaMetricsId) { - metaMetricsId = bufferToHex(sha3(String(Date.now()) + String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)))) + metaMetricsId = bufferToHex( + sha3( + String(Date.now()) + + String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)), + ), + ) this.store.updateState({ metaMetricsId }) } else if (bool === false) { this.store.updateState({ metaMetricsId }) @@ -139,11 +144,11 @@ export default class PreferencesController { return metaMetricsId } - getParticipateInMetaMetrics () { + getParticipateInMetaMetrics() { return this.store.getState().participateInMetaMetrics } - setMetaMetricsSendCount (val) { + setMetaMetricsSendCount(val) { this.store.updateState({ metaMetricsSendCount: val }) } @@ -153,19 +158,19 @@ export default class PreferencesController { * @param {string} type - Indicates the type of first time flow - create or import - the user wishes to follow * */ - setFirstTimeFlowType (type) { + setFirstTimeFlowType(type) { this.store.updateState({ firstTimeFlowType: type }) } - getSuggestedTokens () { + getSuggestedTokens() { return this.store.getState().suggestedTokens } - getAssetImages () { + getAssetImages() { return this.store.getState().assetImages } - addSuggestedERC20Asset (tokenOpts) { + addSuggestedERC20Asset(tokenOpts) { this._validateERC20AssetParams(tokenOpts) const suggested = this.getSuggestedTokens() const { rawAddress, symbol, decimals, image } = tokenOpts @@ -181,7 +186,7 @@ export default class PreferencesController { * @param {string} fourBytePrefix - Four-byte method signature * @param {string} methodData - Corresponding data method */ - addKnownMethodData (fourBytePrefix, methodData) { + addKnownMethodData(fourBytePrefix, methodData) { const { knownMethodData } = this.store.getState() knownMethodData[fourBytePrefix] = methodData this.store.updateState({ knownMethodData }) @@ -190,12 +195,12 @@ export default class PreferencesController { /** * RPC engine middleware for requesting new asset added * - * @param req - * @param res - * @param {Function} - next - * @param {Function} - end + * @param {any} req + * @param {any} res + * @param {Function} next + * @param {Function} end */ - async requestWatchAsset (req, res, next, end) { + async requestWatchAsset(req, res, next, end) { if ( req.method === 'metamask_watchAsset' || req.method === addInternalMethodPrefix('watchAsset') @@ -227,8 +232,10 @@ export default class PreferencesController { * @param {string} key - he preferred language locale key * */ - setCurrentLocale (key) { - const textDirection = (['ar', 'dv', 'fa', 'he', 'ku'].includes(key)) ? 'rtl' : 'auto' + setCurrentLocale(key) { + const textDirection = ['ar', 'dv', 'fa', 'he', 'ku'].includes(key) + ? 'rtl' + : 'auto' this.store.updateState({ currentLocale: key, textDirection, @@ -243,7 +250,7 @@ export default class PreferencesController { * @param {string[]} addresses - An array of hex addresses * */ - setAddresses (addresses) { + setAddresses(addresses) { const oldIdentities = this.store.getState().identities const oldAccountTokens = this.store.getState().accountTokens @@ -264,9 +271,9 @@ export default class PreferencesController { * Removes an address from state * * @param {string} address - A hex address - * @returns {string} - the address that was removed + * @returns {string} the address that was removed */ - removeAddress (address) { + removeAddress(address) { const { identities } = this.store.getState() const { accountTokens } = this.store.getState() if (!identities[address]) { @@ -291,7 +298,7 @@ export default class PreferencesController { * @param {string[]} addresses - An array of hex addresses * */ - addAddresses (addresses) { + addAddresses(addresses) { const { identities, accountTokens } = this.store.getState() addresses.forEach((address) => { // skip if already exists @@ -312,10 +319,9 @@ export default class PreferencesController { * Removes any unknown identities, and returns the resulting selected address. * * @param {Array} addresses - known to the vault. - * @returns {Promise} - selectedAddress the selected address. + * @returns {Promise} selectedAddress the selected address. */ - syncAddresses (addresses) { - + syncAddresses(addresses) { if (!Array.isArray(addresses) || addresses.length === 0) { throw new Error('Expected non-empty array of addresses.') } @@ -332,7 +338,6 @@ export default class PreferencesController { // Identities are no longer present. if (Object.keys(newlyLost).length > 0) { - // store lost accounts Object.keys(newlyLost).forEach((key) => { lostIdentities[key] = newlyLost[key] @@ -353,7 +358,7 @@ export default class PreferencesController { return selected } - removeSuggestedTokens () { + removeSuggestedTokens() { return new Promise((resolve) => { this.store.updateState({ suggestedTokens: {} }) resolve({}) @@ -364,10 +369,10 @@ export default class PreferencesController { * Setter for the `selectedAddress` property * * @param {string} _address - A new hex address for an account - * @returns {Promise} - Promise resolves with tokens + * @returns {Promise} Promise resolves with tokens * */ - setSelectedAddress (_address) { + setSelectedAddress(_address) { const address = normalizeAddress(_address) this._updateTokens(address) @@ -385,10 +390,10 @@ export default class PreferencesController { /** * Getter for the `selectedAddress` property * - * @returns {string} - The hex address for the currently selected account + * @returns {string} The hex address for the currently selected account * */ - getSelectedAddress () { + getSelectedAddress() { return this.store.getState().selectedAddress } @@ -409,11 +414,11 @@ export default class PreferencesController { * * @param {string} rawAddress - Hex address of the token contract. May or may not be a checksum address. * @param {string} symbol - The symbol of the token - * @param {number} decimals - The number of decimals the token uses. - * @returns {Promise} - Promises the new array of AddedToken objects. + * @param {number} decimals - The number of decimals the token uses. + * @returns {Promise} Promises the new array of AddedToken objects. * */ - async addToken (rawAddress, symbol, decimals, image) { + async addToken(rawAddress, symbol, decimals, image) { const address = normalizeAddress(rawAddress) const newEntry = { address, symbol, decimals } const { tokens } = this.store.getState() @@ -437,10 +442,10 @@ export default class PreferencesController { * Removes a specified token from the tokens array. * * @param {string} rawAddress - Hex address of the token contract to remove. - * @returns {Promise} - The new array of AddedToken objects + * @returns {Promise} The new array of AddedToken objects * */ - removeToken (rawAddress) { + removeToken(rawAddress) { const { tokens } = this.store.getState() const assetImages = this.getAssetImages() const updatedTokens = tokens.filter((token) => token.address !== rawAddress) @@ -452,10 +457,10 @@ export default class PreferencesController { /** * A getter for the `tokens` property * - * @returns {array} - The current array of AddedToken objects + * @returns {Array} The current array of AddedToken objects * */ - getTokens () { + getTokens() { return this.store.getState().tokens } @@ -465,9 +470,11 @@ export default class PreferencesController { * @param {string} label - the custom label for the account * @returns {Promise} */ - setAccountLabel (account, label) { + setAccountLabel(account, label) { if (!account) { - throw new Error(`setAccountLabel requires a valid address, got ${String(account)}`) + throw new Error( + `setAccountLabel requires a valid address, got ${String(account)}`, + ) } const address = normalizeAddress(account) const { identities } = this.store.getState() @@ -488,7 +495,7 @@ export default class PreferencesController { * @param {Object} [newRpcDetails.rpcPrefs] - Optional RPC preferences, such as the block explorer URL * */ - async updateRpc (newRpcDetails) { + async updateRpc(newRpcDetails) { const rpcList = this.getFrequentRpcListDetail() const index = rpcList.findIndex((element) => { return element.rpcUrl === newRpcDetails.rpcUrl @@ -497,7 +504,6 @@ export default class PreferencesController { const rpcDetail = rpcList[index] const updatedRpc = { ...rpcDetail, ...newRpcDetails } if (rpcDetail.chainId !== updatedRpc.chainId) { - // When the chainId is changed, associated address book entries should // also be migrated. The address book entries are keyed by the `network` state, // which for custom networks is the chainId with a fallback to the networkId @@ -506,13 +512,17 @@ export default class PreferencesController { let addressBookKey = rpcDetail.chainId if (!addressBookKey) { // We need to find the networkId to determine what these addresses were keyed by - const provider = new ethers.providers.JsonRpcProvider(rpcDetail.rpcUrl) + const provider = new ethers.providers.JsonRpcProvider( + rpcDetail.rpcUrl, + ) try { addressBookKey = await provider.send('net_version') assert(typeof addressBookKey === 'string') } catch (error) { log.debug(error) - log.warn(`Failed to get networkId from ${rpcDetail.rpcUrl}; skipping address book migration`) + log.warn( + `Failed to get networkId from ${rpcDetail.rpcUrl}; skipping address book migration`, + ) } } @@ -521,10 +531,12 @@ export default class PreferencesController { // on both networks, since we don't know which network each contact is intended for. let duplicate = false - const builtInProviderNetworkIds = Object.values(NETWORK_TYPE_TO_ID_MAP) - .map((ids) => ids.networkId) - const otherRpcEntries = rpcList - .filter((entry) => entry.rpcUrl !== newRpcDetails.rpcUrl) + const builtInProviderNetworkIds = Object.values( + NETWORK_TYPE_TO_ID_MAP, + ).map((ids) => ids.networkId) + const otherRpcEntries = rpcList.filter( + (entry) => entry.rpcUrl !== newRpcDetails.rpcUrl, + ) if ( builtInProviderNetworkIds.includes(addressBookKey) || otherRpcEntries.some((entry) => entry.chainId === addressBookKey) @@ -532,7 +544,11 @@ export default class PreferencesController { duplicate = true } - this.migrateAddressBookState(addressBookKey, updatedRpc.chainId, duplicate) + this.migrateAddressBookState( + addressBookKey, + updatedRpc.chainId, + duplicate, + ) } rpcList[index] = updatedRpc this.store.updateState({ frequentRpcListDetail: rpcList }) @@ -552,7 +568,13 @@ export default class PreferencesController { * @param {Object} [rpcPrefs] - Optional RPC preferences, such as the block explorer URL * */ - addToFrequentRpcList (rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) { + addToFrequentRpcList( + rpcUrl, + chainId, + ticker = 'ETH', + nickname = '', + rpcPrefs = {}, + ) { const rpcList = this.getFrequentRpcListDetail() const index = rpcList.findIndex((element) => { @@ -574,10 +596,10 @@ export default class PreferencesController { * Removes custom RPC url from state. * * @param {string} url - The RPC url to remove from frequentRpcList. - * @returns {Promise} - Promise resolving to updated frequentRpcList. + * @returns {Promise} Promise resolving to updated frequentRpcList. * */ - removeFromFrequentRpcList (url) { + removeFromFrequentRpcList(url) { const rpcList = this.getFrequentRpcListDetail() const index = rpcList.findIndex((element) => { return element.rpcUrl === url @@ -592,10 +614,10 @@ export default class PreferencesController { /** * Getter for the `frequentRpcListDetail` property. * - * @returns {array} - An array of rpc urls. + * @returns {array} An array of rpc urls. * */ - getFrequentRpcListDetail () { + getFrequentRpcListDetail() { return this.store.getState().frequentRpcListDetail } @@ -604,10 +626,10 @@ export default class PreferencesController { * * @param {string} feature - A key that corresponds to a UI feature. * @param {boolean} activated - Indicates whether or not the UI feature should be displayed - * @returns {Promise} - Promises a new object; the updated featureFlags object. + * @returns {Promise} Promises a new object; the updated featureFlags object. * */ - setFeatureFlag (feature, activated) { + setFeatureFlag(feature, activated) { const currentFeatureFlags = this.store.getState().featureFlags const updatedFeatureFlags = { ...currentFeatureFlags, @@ -624,9 +646,9 @@ export default class PreferencesController { * found in the settings page. * @param {string} preference - The preference to enable or disable. * @param {boolean} value - Indicates whether or not the preference should be enabled or disabled. - * @returns {Promise} - Promises a new object; the updated preferences object. + * @returns {Promise} Promises a new object; the updated preferences object. */ - setPreference (preference, value) { + setPreference(preference, value) { const currentPreferences = this.getPreferences() const updatedPreferences = { ...currentPreferences, @@ -639,9 +661,9 @@ export default class PreferencesController { /** * A getter for the `preferences` property - * @returns {Object} - A key-boolean map of user-selected preferences. + * @returns {Object} A key-boolean map of user-selected preferences. */ - getPreferences () { + getPreferences() { return this.store.getState().preferences } @@ -649,25 +671,25 @@ export default class PreferencesController { * Sets the completedOnboarding state to true, indicating that the user has completed the * onboarding process. */ - completeOnboarding () { + completeOnboarding() { this.store.updateState({ completedOnboarding: true }) return Promise.resolve(true) } /** * A getter for the `ipfsGateway` property - * @returns {string} - The current IPFS gateway domain + * @returns {string} The current IPFS gateway domain */ - getIpfsGateway () { + getIpfsGateway() { return this.store.getState().ipfsGateway } /** * A setter for the `ipfsGateway` property * @param {string} domain - The new IPFS gateway domain - * @returns {Promise} - A promise of the update IPFS gateway domain + * @returns {Promise} A promise of the update IPFS gateway domain */ - setIpfsGateway (domain) { + setIpfsGateway(domain) { this.store.updateState({ ipfsGateway: domain }) return Promise.resolve(domain) } @@ -681,7 +703,7 @@ export default class PreferencesController { * * */ - _subscribeProviderType () { + _subscribeProviderType() { this.network.providerStore.subscribe(() => { const { tokens } = this._getTokenRelatedStates() this.store.updateState({ tokens }) @@ -691,11 +713,15 @@ export default class PreferencesController { /** * Updates `accountTokens` and `tokens` of current account and network according to it. * - * @param {array} tokens - Array of tokens to be updated. + * @param {Array} tokens - Array of tokens to be updated. * */ - _updateAccountTokens (tokens, assetImages) { - const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates() + _updateAccountTokens(tokens, assetImages) { + const { + accountTokens, + providerType, + selectedAddress, + } = this._getTokenRelatedStates() accountTokens[selectedAddress][providerType] = tokens this.store.updateState({ accountTokens, tokens, assetImages }) } @@ -706,7 +732,7 @@ export default class PreferencesController { * @param {string} selectedAddress - Account address to be updated with. * */ - _updateTokens (selectedAddress) { + _updateTokens(selectedAddress) { const { tokens } = this._getTokenRelatedStates(selectedAddress) this.store.updateState({ tokens }) } @@ -714,11 +740,11 @@ export default class PreferencesController { /** * A getter for `tokens` and `accountTokens` related states. * - * @param {string} [selectedAddress] A new hex address for an account - * @returns {Object.} - States to interact with tokens in `accountTokens` + * @param {string} [selectedAddress] - A new hex address for an account + * @returns {Object.} States to interact with tokens in `accountTokens` * */ - _getTokenRelatedStates (selectedAddress) { + _getTokenRelatedStates(selectedAddress) { const { accountTokens } = this.store.getState() if (!selectedAddress) { // eslint-disable-next-line no-param-reassign @@ -738,11 +764,11 @@ export default class PreferencesController { /** * Handle the suggestion of an ERC20 asset through `watchAsset` * * - * @param {Promise} promise - Promise according to addition of ERC20 token + * @param {Object} tokenMetadata - Token metadata * */ - async _handleWatchAssetERC20 (options) { - const { address, symbol, decimals, image } = options + async _handleWatchAssetERC20(tokenMetadata) { + const { address, symbol, decimals, image } = tokenMetadata const rawAddress = address try { this._validateERC20AssetParams({ rawAddress, symbol, decimals }) @@ -752,7 +778,9 @@ export default class PreferencesController { const tokenOpts = { rawAddress, decimals, symbol, image } this.addSuggestedERC20Asset(tokenOpts) return this.openPopup().then(() => { - const tokenAddresses = this.getTokens().filter((token) => token.address === normalizeAddress(rawAddress)) + const tokenAddresses = this.getTokens().filter( + (token) => token.address === normalizeAddress(rawAddress), + ) return tokenAddresses.length > 0 }) } @@ -765,17 +793,21 @@ export default class PreferencesController { * doesn't fulfill requirements * */ - _validateERC20AssetParams (opts) { + _validateERC20AssetParams(opts) { const { rawAddress, symbol, decimals } = opts if (!rawAddress || !symbol || typeof decimals === 'undefined') { - throw new Error(`Cannot suggest token without address, symbol, and decimals`) + throw new Error( + `Cannot suggest token without address, symbol, and decimals`, + ) } if (!(symbol.length < 7)) { throw new Error(`Invalid symbol ${symbol} more than six characters`) } const numDecimals = parseInt(decimals, 10) if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) { - throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`) + throw new Error( + `Invalid decimals ${decimals} must be at least 0, and not over 36`, + ) } if (!isValidAddress(rawAddress)) { throw new Error(`Invalid address ${rawAddress}`) diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index e19185498..c7691dffe 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -2,7 +2,7 @@ import { ethers } from 'ethers' import log from 'loglevel' import BigNumber from 'bignumber.js' import ObservableStore from 'obs-store' -import { mapValues } from 'lodash' +import { mapValues, cloneDeep } from 'lodash' import abi from 'human-standard-token-abi' import { calcTokenAmount } from '../../../ui/app/helpers/utils/token-util' import { calcGasTotal } from '../../../ui/app/pages/send/send.utils' @@ -28,17 +28,14 @@ const MAX_GAS_LIMIT = 2500000 // 3 seems to be an appropriate balance of giving users the time they need when MetaMask is not left idle, and turning polling off when it is. const POLL_COUNT_LIMIT = 3 -function calculateGasEstimateWithRefund (maxGas = MAX_GAS_LIMIT, estimatedRefund = 0, estimatedGas = 0) { - const maxGasMinusRefund = new BigNumber( - maxGas, - 10, - ) - .minus(estimatedRefund, 10) +function calculateGasEstimateWithRefund( + maxGas = MAX_GAS_LIMIT, + estimatedRefund = 0, + estimatedGas = 0, +) { + const maxGasMinusRefund = new BigNumber(maxGas, 10).minus(estimatedRefund, 10) - const gasEstimateWithRefund = maxGasMinusRefund.lt( - estimatedGas, - 16, - ) + const gasEstimateWithRefund = maxGasMinusRefund.lt(estimatedGas, 16) ? maxGasMinusRefund.toString(16) : estimatedGas @@ -68,7 +65,7 @@ const initialState = { } export default class SwapsController { - constructor ({ + constructor({ getBufferedGasLimit, networkController, provider, @@ -108,18 +105,26 @@ export default class SwapsController { // that quotes will no longer be available after 1 or 2 minutes. When fetchAndSetQuotes is first called it, receives fetch that parameters are stored in // state. These stored parameters are used on subsequent calls made during polling. // Note: we stop polling after 3 requests, until new quotes are explicitly asked for. The logic that enforces that maximum is in the body of fetchAndSetQuotes - pollForNewQuotes () { + pollForNewQuotes() { this.pollingTimeout = setTimeout(() => { const { swapsState } = this.store.getState() - this.fetchAndSetQuotes(swapsState.fetchParams, swapsState.fetchParams.metaData, true) + this.fetchAndSetQuotes( + swapsState.fetchParams, + swapsState.fetchParams?.metaData, + true, + ) }, QUOTE_POLLING_INTERVAL) } - stopPollingForQuotes () { + stopPollingForQuotes() { clearTimeout(this.pollingTimeout) } - async fetchAndSetQuotes (fetchParams, fetchParamsMetaData = {}, isPolledRequest) { + async fetchAndSetQuotes( + fetchParams, + fetchParamsMetaData = {}, + isPolledRequest, + ) { if (!fetchParams) { return null } @@ -150,7 +155,10 @@ export default class SwapsController { const quotesLastFetched = Date.now() let approvalRequired = false - if (fetchParams.sourceToken !== ETH_SWAPS_TOKEN_ADDRESS && Object.values(newQuotes).length) { + if ( + fetchParams.sourceToken !== ETH_SWAPS_TOKEN_ADDRESS && + Object.values(newQuotes).length + ) { const allowance = await this._getERC20Allowance( fetchParams.sourceToken, fetchParams.fromAddress, @@ -167,7 +175,9 @@ export default class SwapsController { approvalNeeded: null, })) } else if (!isPolledRequest) { - const { gasLimit: approvalGas } = await this.timedoutGasReturn(Object.values(newQuotes)[0].approvalNeeded) + const { gasLimit: approvalGas } = await this.timedoutGasReturn( + Object.values(newQuotes)[0].approvalNeeded, + ) newQuotes = mapValues(newQuotes, (quote) => ({ ...quote, @@ -190,13 +200,12 @@ export default class SwapsController { if (Object.values(newQuotes).length === 0) { this.setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR) } else { - const topQuoteData = await this._findTopQuoteAndCalculateSavings(newQuotes) - - if (topQuoteData.topAggId) { - topAggId = topQuoteData.topAggId - newQuotes[topAggId].isBestQuote = topQuoteData.isBest - newQuotes[topAggId].savings = topQuoteData.savings - } + const [ + _topAggId, + quotesWithSavingsAndFeeData, + ] = await this._findTopQuoteAndCalculateSavings(newQuotes) + topAggId = _topAggId + newQuotes = quotesWithSavingsAndFeeData } // If a newer call has been made, don't update state with old information @@ -235,32 +244,34 @@ export default class SwapsController { return [newQuotes, topAggId] } - safeRefetchQuotes () { + safeRefetchQuotes() { const { swapsState } = this.store.getState() if (!this.pollingTimeout && swapsState.fetchParams) { this.fetchAndSetQuotes(swapsState.fetchParams) } } - setSelectedQuoteAggId (selectedAggId) { + setSelectedQuoteAggId(selectedAggId) { const { swapsState } = this.store.getState() this.store.updateState({ swapsState: { ...swapsState, selectedAggId } }) } - setSwapsTokens (tokens) { + setSwapsTokens(tokens) { const { swapsState } = this.store.getState() this.store.updateState({ swapsState: { ...swapsState, tokens } }) } - setSwapsErrorKey (errorKey) { + setSwapsErrorKey(errorKey) { const { swapsState } = this.store.getState() this.store.updateState({ swapsState: { ...swapsState, errorKey } }) } - async getAllQuotesWithGasEstimates (quotes) { + async getAllQuotesWithGasEstimates(quotes) { const quoteGasData = await Promise.all( Object.values(quotes).map(async (quote) => { - const { gasLimit, simulationFails } = await this.timedoutGasReturn(quote.trade) + const { gasLimit, simulationFails } = await this.timedoutGasReturn( + quote.trade, + ) return [gasLimit, simulationFails, quote.aggregator] }), ) @@ -268,7 +279,11 @@ export default class SwapsController { const newQuotes = {} quoteGasData.forEach(([gasLimit, simulationFails, aggId]) => { if (gasLimit && !simulationFails) { - const gasEstimateWithRefund = calculateGasEstimateWithRefund(quotes[aggId].maxGas, quotes[aggId].estimatedRefund, gasLimit) + const gasEstimateWithRefund = calculateGasEstimateWithRefund( + quotes[aggId].maxGas, + quotes[aggId].estimatedRefund, + gasLimit, + ) newQuotes[aggId] = { ...quotes[aggId], @@ -285,7 +300,7 @@ export default class SwapsController { return newQuotes } - timedoutGasReturn (tradeTxParams) { + timedoutGasReturn(tradeTxParams) { return new Promise((resolve) => { let gasTimedOut = false @@ -321,7 +336,7 @@ export default class SwapsController { }) } - async setInitialGasEstimate (initialAggId) { + async setInitialGasEstimate(initialAggId) { const { swapsState } = this.store.getState() const quoteToUpdate = { ...swapsState.quotes[initialAggId] } @@ -332,64 +347,73 @@ export default class SwapsController { } = await this.timedoutGasReturn(quoteToUpdate.trade) if (newGasEstimate && !simulationFails) { - const gasEstimateWithRefund = calculateGasEstimateWithRefund(quoteToUpdate.maxGas, quoteToUpdate.estimatedRefund, newGasEstimate) + const gasEstimateWithRefund = calculateGasEstimateWithRefund( + quoteToUpdate.maxGas, + quoteToUpdate.estimatedRefund, + newGasEstimate, + ) quoteToUpdate.gasEstimate = newGasEstimate quoteToUpdate.gasEstimateWithRefund = gasEstimateWithRefund } this.store.updateState({ - swapsState: { ...swapsState, quotes: { ...swapsState.quotes, [initialAggId]: quoteToUpdate } }, + swapsState: { + ...swapsState, + quotes: { ...swapsState.quotes, [initialAggId]: quoteToUpdate }, + }, }) } - setApproveTxId (approveTxId) { + setApproveTxId(approveTxId) { const { swapsState } = this.store.getState() this.store.updateState({ swapsState: { ...swapsState, approveTxId } }) } - setTradeTxId (tradeTxId) { + setTradeTxId(tradeTxId) { const { swapsState } = this.store.getState() this.store.updateState({ swapsState: { ...swapsState, tradeTxId } }) } - setQuotesLastFetched (quotesLastFetched) { + setQuotesLastFetched(quotesLastFetched) { const { swapsState } = this.store.getState() this.store.updateState({ swapsState: { ...swapsState, quotesLastFetched } }) } - setSwapsTxGasPrice (gasPrice) { + setSwapsTxGasPrice(gasPrice) { const { swapsState } = this.store.getState() this.store.updateState({ swapsState: { ...swapsState, customGasPrice: gasPrice }, }) } - setSwapsTxGasLimit (gasLimit) { + setSwapsTxGasLimit(gasLimit) { const { swapsState } = this.store.getState() this.store.updateState({ swapsState: { ...swapsState, customMaxGas: gasLimit }, }) } - setCustomApproveTxData (data) { + setCustomApproveTxData(data) { const { swapsState } = this.store.getState() this.store.updateState({ swapsState: { ...swapsState, customApproveTxData: data }, }) } - setBackgroundSwapRouteState (routeState) { + setBackgroundSwapRouteState(routeState) { const { swapsState } = this.store.getState() this.store.updateState({ swapsState: { ...swapsState, routeState } }) } - setSwapsLiveness (swapsFeatureIsLive) { + setSwapsLiveness(swapsFeatureIsLive) { const { swapsState } = this.store.getState() - this.store.updateState({ swapsState: { ...swapsState, swapsFeatureIsLive } }) + this.store.updateState({ + swapsState: { ...swapsState, swapsFeatureIsLive }, + }) } - resetPostFetchState () { + resetPostFetchState() { const { swapsState } = this.store.getState() this.store.updateState({ @@ -403,21 +427,25 @@ export default class SwapsController { clearTimeout(this.pollingTimeout) } - resetSwapsState () { + resetSwapsState() { const { swapsState } = this.store.getState() this.store.updateState({ - swapsState: { ...initialState.swapsState, tokens: swapsState.tokens, swapsFeatureIsLive: swapsState.swapsFeatureIsLive }, + swapsState: { + ...initialState.swapsState, + tokens: swapsState.tokens, + swapsFeatureIsLive: swapsState.swapsFeatureIsLive, + }, }) clearTimeout(this.pollingTimeout) } - async _getEthersGasPrice () { + async _getEthersGasPrice() { const ethersGasPrice = await this.ethersProvider.getGasPrice() return ethersGasPrice.toHexString() } - async _findTopQuoteAndCalculateSavings (quotes = {}) { + async _findTopQuoteAndCalculateSavings(quotes = {}) { const tokenConversionRates = this.tokenRatesStore.getState() .contractExchangeRates const { @@ -429,15 +457,14 @@ export default class SwapsController { return {} } - const usedGasPrice = customGasPrice || await this._getEthersGasPrice() + const newQuotes = cloneDeep(quotes) - let topAggId = '' - let ethTradeValueOfBestQuote = null - let ethFeeForBestQuote = null - const allEthTradeValues = [] - const allEthFees = [] + const usedGasPrice = customGasPrice || (await this._getEthersGasPrice()) - Object.values(quotes).forEach((quote) => { + let topAggId = null + let overallValueOfBestQuoteForSorting = null + + Object.values(newQuotes).forEach((quote) => { const { aggregator, approvalNeeded, @@ -449,6 +476,7 @@ export default class SwapsController { sourceAmount, sourceToken, trade, + fee: metaMaskFee, } = quote const tradeGasLimitForCalculation = gasEstimate @@ -468,8 +496,10 @@ export default class SwapsController { // It always includes any external fees charged by the quote source. In // addition, if the source asset is ETH, trade.value includes the amount // of swapped ETH. - const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16) - .plus(trade.value, 16) + const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16).plus( + trade.value, + 16, + ) const totalEthCost = conversionUtil(totalWeiCost, { fromCurrency: 'ETH', @@ -482,81 +512,122 @@ export default class SwapsController { // The total fee is aggregator/exchange fees plus gas fees. // If the swap is from ETH, subtract the sourceAmount from the total cost. // Otherwise, the total fee is simply trade.value plus gas fees. - const ethFee = sourceToken === ETH_SWAPS_TOKEN_ADDRESS - ? conversionUtil( - totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei - { - fromCurrency: 'ETH', - fromDenomination: 'WEI', - toDenomination: 'ETH', - fromNumericBase: 'BN', - numberOfDecimals: 6, - }, - ) - : totalEthCost + const ethFee = + sourceToken === ETH_SWAPS_TOKEN_ADDRESS + ? conversionUtil( + totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei + { + fromCurrency: 'ETH', + fromDenomination: 'WEI', + toDenomination: 'ETH', + fromNumericBase: 'BN', + numberOfDecimals: 6, + }, + ) + : totalEthCost + + const decimalAdjustedDestinationAmount = calcTokenAmount( + destinationAmount, + destinationTokenInfo.decimals, + ) + + const tokenPercentageOfPreFeeDestAmount = new BigNumber(100, 10) + .minus(metaMaskFee, 10) + .div(100) + const destinationAmountBeforeMetaMaskFee = decimalAdjustedDestinationAmount.div( + tokenPercentageOfPreFeeDestAmount, + ) + const metaMaskFeeInTokens = destinationAmountBeforeMetaMaskFee.minus( + decimalAdjustedDestinationAmount, + ) const tokenConversionRate = tokenConversionRates[destinationToken] - const ethValueOfTrade = - destinationToken === ETH_SWAPS_TOKEN_ADDRESS - ? calcTokenAmount(destinationAmount, 18).minus(totalEthCost, 10) - : new BigNumber(tokenConversionRate || 1, 10) - .times( - calcTokenAmount( - destinationAmount, - destinationTokenInfo.decimals, - ), - 10, - ) - .minus(tokenConversionRate ? totalEthCost : 0, 10) + const conversionRateForSorting = tokenConversionRate || 1 - // collect values for savings calculation - allEthTradeValues.push(ethValueOfTrade) - allEthFees.push(ethFee) + const ethValueOfTokens = decimalAdjustedDestinationAmount.times( + conversionRateForSorting, + 10, + ) + + const conversionRateForCalculations = + destinationToken === ETH_SWAPS_TOKEN_ADDRESS ? 1 : tokenConversionRate + + const overallValueOfQuoteForSorting = + conversionRateForCalculations === undefined + ? ethValueOfTokens + : ethValueOfTokens.minus(ethFee, 10) + + quote.ethFee = ethFee.toString(10) + + if (conversionRateForCalculations !== undefined) { + quote.ethValueOfTokens = ethValueOfTokens.toString(10) + quote.overallValueOfQuote = overallValueOfQuoteForSorting.toString(10) + quote.metaMaskFeeInEth = metaMaskFeeInTokens + .times(conversionRateForCalculations) + .toString(10) + } if ( - ethTradeValueOfBestQuote === null || - ethValueOfTrade.gt(ethTradeValueOfBestQuote) + overallValueOfBestQuoteForSorting === null || + overallValueOfQuoteForSorting.gt(overallValueOfBestQuoteForSorting) ) { topAggId = aggregator - ethTradeValueOfBestQuote = ethValueOfTrade - ethFeeForBestQuote = ethFee + overallValueOfBestQuoteForSorting = overallValueOfQuoteForSorting } }) const isBest = - quotes[topAggId].destinationToken === ETH_SWAPS_TOKEN_ADDRESS || - Boolean(tokenConversionRates[quotes[topAggId]?.destinationToken]) + newQuotes[topAggId].destinationToken === ETH_SWAPS_TOKEN_ADDRESS || + Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken]) let savings = null if (isBest) { + const bestQuote = newQuotes[topAggId] + savings = {} + + const { + ethFee: medianEthFee, + metaMaskFeeInEth: medianMetaMaskFee, + ethValueOfTokens: medianEthValueOfTokens, + } = getMedianEthValueQuote(Object.values(newQuotes)) + // Performance savings are calculated as: - // valueForBestTrade - medianValueOfAllTrades - savings.performance = ethTradeValueOfBestQuote.minus( - getMedian(allEthTradeValues), + // (ethValueOfTokens for the best trade) - (ethValueOfTokens for the media trade) + savings.performance = new BigNumber(bestQuote.ethValueOfTokens, 10).minus( + medianEthValueOfTokens, 10, ) - // Performance savings are calculated as: - // medianFeeOfAllTrades - feeForBestTrade - savings.fee = getMedian(allEthFees).minus( - ethFeeForBestQuote, - 10, - ) + // Fee savings are calculated as: + // (fee for the median trade) - (fee for the best trade) + savings.fee = new BigNumber(medianEthFee).minus(bestQuote.ethFee, 10) - // Total savings are the sum of performance and fee savings - savings.total = savings.performance.plus(savings.fee, 10).toString(10) + savings.metaMaskFee = bestQuote.metaMaskFeeInEth + + // Total savings are calculated as: + // performance savings + fee savings - metamask fee + savings.total = savings.performance + .plus(savings.fee) + .minus(savings.metaMaskFee) + .toString(10) savings.performance = savings.performance.toString(10) savings.fee = savings.fee.toString(10) + savings.medianMetaMaskFee = medianMetaMaskFee + + newQuotes[topAggId].isBestQuote = true + newQuotes[topAggId].savings = savings } - return { topAggId, isBest, savings } + return [topAggId, newQuotes] } - async _getERC20Allowance (contractAddress, walletAddress) { + async _getERC20Allowance(contractAddress, walletAddress) { const contract = new ethers.Contract( - contractAddress, abi, this.ethersProvider, + contractAddress, + abi, + this.ethersProvider, ) return await contract.allowance(walletAddress, METASWAP_ADDRESS) } @@ -569,7 +640,7 @@ export default class SwapsController { * If the browser goes offline, the interval is cleared and swaps are disabled * until the value can be fetched again. */ - _setupSwapsLivenessFetching () { + _setupSwapsLivenessFetching() { const TEN_MINUTES_MS = 10 * 60 * 1000 let intervalId = null @@ -577,7 +648,10 @@ export default class SwapsController { if (window.navigator.onLine && intervalId === null) { // Set the interval first to prevent race condition between listener and // initial call to this function. - intervalId = setInterval(this._fetchAndSetSwapsLiveness.bind(this), TEN_MINUTES_MS) + intervalId = setInterval( + this._fetchAndSetSwapsLiveness.bind(this), + TEN_MINUTES_MS, + ) this._fetchAndSetSwapsLiveness() } } @@ -608,7 +682,7 @@ export default class SwapsController { * Only updates state if the fetched/computed flag value differs from current * state. */ - async _fetchAndSetSwapsLiveness () { + async _fetchAndSetSwapsLiveness() { const { swapsState } = this.store.getState() const { swapsFeatureIsLive: oldSwapsFeatureIsLive } = swapsState let swapsFeatureIsLive = false @@ -637,7 +711,9 @@ export default class SwapsController { } if (!successfullyFetched) { - log.error('Failed to fetch swaps feature flag 3 times. Setting to false and trying again next interval.') + log.error( + 'Failed to fetch swaps feature flag 3 times. Setting to false and trying again next interval.', + ) } if (swapsFeatureIsLive !== oldSwapsFeatureIsLive) { @@ -647,36 +723,123 @@ export default class SwapsController { } /** - * Calculates the median of a sample of BigNumber values. + * Calculates the median overallValueOfQuote of a sample of quotes. * - * @param {import('bignumber.js').BigNumber[]} values - A sample of BigNumber - * values. The array will be sorted in place. - * @returns {import('bignumber.js').BigNumber} The median of the sample. + * @param {Array} quotes - A sample of quote objects with overallValueOfQuote, ethFee, metaMaskFeeInEth, and ethValueOfTokens properties + * @returns {Object} An object with the ethValueOfTokens, ethFee, and metaMaskFeeInEth of the quote with the median overallValueOfQuote */ -function getMedian (values) { - if (!Array.isArray(values) || values.length === 0) { +function getMedianEthValueQuote(_quotes) { + if (!Array.isArray(_quotes) || _quotes.length === 0) { throw new Error('Expected non-empty array param.') } - values.sort((a, b) => { - if (a.equals(b)) { + const quotes = [..._quotes] + + quotes.sort((quoteA, quoteB) => { + const overallValueOfQuoteA = new BigNumber(quoteA.overallValueOfQuote, 10) + const overallValueOfQuoteB = new BigNumber(quoteB.overallValueOfQuote, 10) + if (overallValueOfQuoteA.equals(overallValueOfQuoteB)) { return 0 } - return a.lessThan(b) ? -1 : 1 + return overallValueOfQuoteA.lessThan(overallValueOfQuoteB) ? -1 : 1 }) - if (values.length % 2 === 1) { - // return middle value - return values[(values.length - 1) / 2] + if (quotes.length % 2 === 1) { + // return middle values + const medianOverallValue = + quotes[(quotes.length - 1) / 2].overallValueOfQuote + const quotesMatchingMedianQuoteValue = quotes.filter( + (quote) => medianOverallValue === quote.overallValueOfQuote, + ) + return meansOfQuotesFeesAndValue(quotesMatchingMedianQuoteValue) } // return mean of middle two values - const upperIndex = values.length / 2 - return values[upperIndex] - .plus(values[upperIndex - 1]) - .dividedBy(2) + const upperIndex = quotes.length / 2 + const lowerIndex = upperIndex - 1 + + const overallValueAtUpperIndex = quotes[upperIndex].overallValueOfQuote + const overallValueAtLowerIndex = quotes[lowerIndex].overallValueOfQuote + + const quotesMatchingUpperIndexValue = quotes.filter( + (quote) => overallValueAtUpperIndex === quote.overallValueOfQuote, + ) + const quotesMatchingLowerIndexValue = quotes.filter( + (quote) => overallValueAtLowerIndex === quote.overallValueOfQuote, + ) + + const feesAndValueAtUpperIndex = meansOfQuotesFeesAndValue( + quotesMatchingUpperIndexValue, + ) + const feesAndValueAtLowerIndex = meansOfQuotesFeesAndValue( + quotesMatchingLowerIndexValue, + ) + + return { + ethFee: new BigNumber(feesAndValueAtUpperIndex.ethFee, 10) + .plus(feesAndValueAtLowerIndex.ethFee, 10) + .dividedBy(2) + .toString(10), + metaMaskFeeInEth: new BigNumber( + feesAndValueAtUpperIndex.metaMaskFeeInEth, + 10, + ) + .plus(feesAndValueAtLowerIndex.metaMaskFeeInEth, 10) + .dividedBy(2) + .toString(10), + ethValueOfTokens: new BigNumber( + feesAndValueAtUpperIndex.ethValueOfTokens, + 10, + ) + .plus(feesAndValueAtLowerIndex.ethValueOfTokens, 10) + .dividedBy(2) + .toString(10), + } +} + +/** + * Calculates the arithmetic mean for each of three properties - ethFee, metaMaskFeeInEth and ethValueOfTokens - across + * an array of objects containing those properties. + * + * @param {Array} quotes - A sample of quote objects with overallValueOfQuote, ethFee, metaMaskFeeInEth and + * ethValueOfTokens properties + * @returns {Object} An object with the arithmetic mean each of the ethFee, metaMaskFeeInEth and ethValueOfTokens of + * the passed quote objects + */ +function meansOfQuotesFeesAndValue(quotes) { + const feeAndValueSumsAsBigNumbers = quotes.reduce( + (feeAndValueSums, quote) => ({ + ethFee: feeAndValueSums.ethFee.plus(quote.ethFee, 10), + metaMaskFeeInEth: feeAndValueSums.metaMaskFeeInEth.plus( + quote.metaMaskFeeInEth, + 10, + ), + ethValueOfTokens: feeAndValueSums.ethValueOfTokens.plus( + quote.ethValueOfTokens, + 10, + ), + }), + { + ethFee: new BigNumber(0, 10), + metaMaskFeeInEth: new BigNumber(0, 10), + ethValueOfTokens: new BigNumber(0, 10), + }, + ) + + return { + ethFee: feeAndValueSumsAsBigNumbers.ethFee + .div(quotes.length, 10) + .toString(10), + metaMaskFeeInEth: feeAndValueSumsAsBigNumbers.metaMaskFeeInEth + .div(quotes.length, 10) + .toString(10), + ethValueOfTokens: feeAndValueSumsAsBigNumbers.ethValueOfTokens + .div(quotes.length, 10) + .toString(10), + } } export const utils = { - getMedian, + getMedianEthValueQuote, + meansOfQuotesFeesAndValue, } diff --git a/app/scripts/controllers/threebox.js b/app/scripts/controllers/threebox.js index be6c2fa0a..2459d79e2 100644 --- a/app/scripts/controllers/threebox.js +++ b/app/scripts/controllers/threebox.js @@ -18,7 +18,7 @@ import createMetamaskMiddleware from './network/createMetamaskMiddleware' const SYNC_TIMEOUT = 60 * 1000 // one minute export default class ThreeBoxController { - constructor (opts = {}) { + constructor(opts = {}) { const { preferencesController, keyringController, @@ -41,16 +41,22 @@ export default class ThreeBoxController { const accounts = await this.keyringController.getAccounts() if (isUnlocked && accounts[0]) { - const appKeyAddress = await this.keyringController.getAppKeyAddress(accounts[0], 'wallet://3box.metamask.io') + const appKeyAddress = await this.keyringController.getAppKeyAddress( + accounts[0], + 'wallet://3box.metamask.io', + ) return [appKeyAddress] } return [] }, processPersonalMessage: async (msgParams) => { const accounts = await this.keyringController.getAccounts() - return keyringController.signPersonalMessage({ ...msgParams, from: accounts[0] }, { - withAppKeyOrigin: 'wallet://3box.metamask.io', - }) + return keyringController.signPersonalMessage( + { ...msgParams, from: accounts[0] }, + { + withAppKeyOrigin: 'wallet://3box.metamask.io', + }, + ) }, }) @@ -65,14 +71,16 @@ export default class ThreeBoxController { } this.store = new ObservableStore(initState) this.registeringUpdates = false - this.lastMigration = migrations.sort((a, b) => a.version - b.version).slice(-1)[0] + this.lastMigration = migrations + .sort((a, b) => a.version - b.version) + .slice(-1)[0] if (initState.threeBoxSyncingAllowed) { this.init() } } - async init () { + async init() { const accounts = await this.keyringController.getAccounts() this.address = accounts[0] if (this.address && !(this.box && this.store.getState().threeBoxSynced)) { @@ -80,7 +88,7 @@ export default class ThreeBoxController { } } - async _update3Box () { + async _update3Box() { try { const { threeBoxSyncingAllowed, threeBoxSynced } = this.store.getState() if (threeBoxSyncingAllowed && threeBoxSynced) { @@ -99,7 +107,7 @@ export default class ThreeBoxController { } } - _createProvider (providerOpts) { + _createProvider(providerOpts) { const metamaskMiddleware = createMetamaskMiddleware(providerOpts) const engine = new JsonRpcEngine() engine.push(createOriginMiddleware({ origin: '3Box' })) @@ -108,7 +116,7 @@ export default class ThreeBoxController { return provider } - _waitForOnSyncDone () { + _waitForOnSyncDone() { return new Promise((resolve) => { this.box.onSyncDone(() => { log.debug('3Box box sync done') @@ -117,9 +125,12 @@ export default class ThreeBoxController { }) } - async new3Box () { + async new3Box() { const accounts = await this.keyringController.getAccounts() - this.address = await this.keyringController.getAppKeyAddress(accounts[0], 'wallet://3box.metamask.io') + this.address = await this.keyringController.getAppKeyAddress( + accounts[0], + 'wallet://3box.metamask.io', + ) let backupExists try { const threeBoxConfig = await Box.getConfig(this.address) @@ -170,20 +181,22 @@ export default class ThreeBoxController { } } - async getLastUpdated () { + async getLastUpdated() { const res = await this.space.private.get('metamaskBackup') const parsedRes = JSON.parse(res || '{}') return parsedRes.lastUpdated } - async migrateBackedUpState (backedUpState) { + async migrateBackedUpState(backedUpState) { const migrator = new Migrator({ migrations }) const { preferences, addressBook } = JSON.parse(backedUpState) const formattedStateBackup = { PreferencesController: preferences, AddressBookController: addressBook, } - const initialMigrationState = migrator.generateInitialState(formattedStateBackup) + const initialMigrationState = migrator.generateInitialState( + formattedStateBackup, + ) const migratedState = await migrator.migrateData(initialMigrationState) return { preferences: migratedState.data.PreferencesController, @@ -191,31 +204,30 @@ export default class ThreeBoxController { } } - async restoreFromThreeBox () { + async restoreFromThreeBox() { const backedUpState = await this.space.private.get('metamaskBackup') - const { - preferences, - addressBook, - } = await this.migrateBackedUpState(backedUpState) + const { preferences, addressBook } = await this.migrateBackedUpState( + backedUpState, + ) this.store.updateState({ threeBoxLastUpdated: backedUpState.lastUpdated }) preferences && this.preferencesController.store.updateState(preferences) addressBook && this.addressBookController.update(addressBook, true) this.setShowRestorePromptToFalse() } - turnThreeBoxSyncingOn () { + turnThreeBoxSyncingOn() { this._registerUpdates() } - turnThreeBoxSyncingOff () { + turnThreeBoxSyncingOff() { this.box.logout() } - setShowRestorePromptToFalse () { + setShowRestorePromptToFalse() { this.store.updateState({ showRestorePrompt: false }) } - setThreeBoxSyncingPermission (newThreeboxSyncingState) { + setThreeBoxSyncingPermission(newThreeboxSyncingState) { if (this.store.getState().threeBoxDisabled) { return } @@ -232,11 +244,11 @@ export default class ThreeBoxController { } } - getThreeBoxSyncingState () { + getThreeBoxSyncingState() { return this.store.getState().threeBoxSyncingAllowed } - _registerUpdates () { + _registerUpdates() { if (!this.registeringUpdates) { const updatePreferences = this._update3Box.bind(this) this.preferencesController.store.subscribe(updatePreferences) diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js index c3e872ca2..b943c540f 100644 --- a/app/scripts/controllers/token-rates.js +++ b/app/scripts/controllers/token-rates.js @@ -11,13 +11,12 @@ const DEFAULT_INTERVAL = 180 * 1000 * rates based on a user's current token list */ export default class TokenRatesController { - /** * Creates a TokenRatesController * * @param {Object} [config] - Options to configure controller */ - constructor ({ currency, preferences } = {}) { + constructor({ currency, preferences } = {}) { this.store = new ObservableStore() this.currency = currency this.preferences = preferences @@ -26,21 +25,32 @@ export default class TokenRatesController { /** * Updates exchange rates for all tokens */ - async updateExchangeRates () { + async updateExchangeRates() { const contractExchangeRates = {} - const nativeCurrency = this.currency ? this.currency.state.nativeCurrency.toLowerCase() : 'eth' + const nativeCurrency = this.currency + ? this.currency.state.nativeCurrency.toLowerCase() + : 'eth' const pairs = this._tokens.map((token) => token.address).join(',') const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}` if (this._tokens.length > 0) { try { - const response = await window.fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`) + const response = await window.fetch( + `https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`, + ) const prices = await response.json() this._tokens.forEach((token) => { - const price = prices[token.address.toLowerCase()] || prices[ethUtil.toChecksumAddress(token.address)] - contractExchangeRates[normalizeAddress(token.address)] = price ? price[nativeCurrency] : 0 + const price = + prices[token.address.toLowerCase()] || + prices[ethUtil.toChecksumAddress(token.address)] + contractExchangeRates[normalizeAddress(token.address)] = price + ? price[nativeCurrency] + : 0 }) } catch (error) { - log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error) + log.warn( + `MetaMask - TokenRatesController exchange rate fetch failed.`, + error, + ) } } this.store.putState({ contractExchangeRates }) @@ -50,7 +60,7 @@ export default class TokenRatesController { /** * @type {Object} */ - set preferences (preferences) { + set preferences(preferences) { this._preferences && this._preferences.unsubscribe() if (!preferences) { return @@ -65,13 +75,13 @@ export default class TokenRatesController { /** * @type {Array} */ - set tokens (tokens) { + set tokens(tokens) { this._tokens = tokens this.updateExchangeRates() } /* eslint-enable accessor-pairs */ - start (interval = DEFAULT_INTERVAL) { + start(interval = DEFAULT_INTERVAL) { this._handle && clearInterval(this._handle) if (!interval) { return @@ -82,7 +92,7 @@ export default class TokenRatesController { this.updateExchangeRates() } - stop () { + stop() { this._handle && clearInterval(this._handle) } } diff --git a/app/scripts/controllers/transactions/enums.js b/app/scripts/controllers/transactions/enums.js deleted file mode 100644 index dcd383dd5..000000000 --- a/app/scripts/controllers/transactions/enums.js +++ /dev/null @@ -1,14 +0,0 @@ -const TRANSACTION_TYPE_CANCEL = 'cancel' -const TRANSACTION_TYPE_RETRY = 'retry' -const TRANSACTION_TYPE_STANDARD = 'standard' - -const TRANSACTION_STATUS_APPROVED = 'approved' -const TRANSACTION_STATUS_CONFIRMED = 'confirmed' - -export { - TRANSACTION_TYPE_CANCEL, - TRANSACTION_TYPE_RETRY, - TRANSACTION_TYPE_STANDARD, - TRANSACTION_STATUS_APPROVED, - TRANSACTION_STATUS_CONFIRMED, -} diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 4ec69ff46..c905c3e1c 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -9,29 +9,24 @@ import { ethers } from 'ethers' import NonceTracker from 'nonce-tracker' import log from 'loglevel' import BigNumber from 'bignumber.js' -import { - TOKEN_METHOD_APPROVE, - TOKEN_METHOD_TRANSFER, - TOKEN_METHOD_TRANSFER_FROM, - SEND_ETHER_ACTION_KEY, - DEPLOY_CONTRACT_ACTION_KEY, - CONTRACT_INTERACTION_KEY, - SWAP, -} from '../../../../ui/app/helpers/constants/transactions' import cleanErrorStack from '../../lib/cleanErrorStack' -import { hexToBn, bnToHex, BnMultiplyByFraction } from '../../lib/util' +import { + hexToBn, + bnToHex, + BnMultiplyByFraction, + addHexPrefix, +} from '../../lib/util' import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys' import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/app/pages/swaps/swaps.util' +import { + TRANSACTION_CATEGORIES, + TRANSACTION_STATUSES, + TRANSACTION_TYPES, +} from '../../../../shared/constants/transaction' import TransactionStateManager from './tx-state-manager' import TxGasUtil from './tx-gas-utils' import PendingTransactionTracker from './pending-tx-tracker' import * as txUtils from './lib/util' -import { - TRANSACTION_TYPE_CANCEL, - TRANSACTION_TYPE_RETRY, - TRANSACTION_TYPE_STANDARD, - TRANSACTION_STATUS_APPROVED, -} from './enums' const hstInterface = new ethers.utils.Interface(abi) @@ -53,20 +48,20 @@ const MAX_MEMSTORE_TX_LIST_SIZE = 100 // Number of transactions (by unique nonce calculating nonces @class - @param {Object} - opts - @param {Object} opts.initState - initial transaction list default is an empty array - @param {Object} opts.networkStore - an observable store for network number - @param {Object} opts.blockTracker - An instance of eth-blocktracker - @param {Object} opts.provider - A network provider. - @param {Function} opts.signTransaction - function the signs an ethereumjs-tx - @param {Object} opts.getPermittedAccounts - get accounts that an origin has permissions for - @param {Function} opts.signTransaction - ethTx signer that returns a rawTx - @param {number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state - @param {Object} opts.preferencesStore + @param {Object} opts + @param {Object} opts.initState - initial transaction list default is an empty array + @param {Object} opts.networkStore - an observable store for network number + @param {Object} opts.blockTracker - An instance of eth-blocktracker + @param {Object} opts.provider - A network provider. + @param {Function} opts.signTransaction - function the signs an ethereumjs-tx + @param {Object} opts.getPermittedAccounts - get accounts that an origin has permissions for + @param {Function} opts.signTransaction - ethTx signer that returns a rawTx + @param {number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state + @param {Object} opts.preferencesStore */ export default class TransactionController extends EventEmitter { - constructor (opts) { + constructor(opts) { super() this.networkStore = opts.networkStore || new ObservableStore({}) this._getCurrentChainId = opts.getCurrentChainId @@ -95,8 +90,12 @@ export default class TransactionController extends EventEmitter { this.nonceTracker = new NonceTracker({ provider: this.provider, blockTracker: this.blockTracker, - getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), - getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), + getPendingTransactions: this.txStateManager.getPendingTransactions.bind( + this.txStateManager, + ), + getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind( + this.txStateManager, + ), }) this.pendingTxTracker = new PendingTransactionTracker({ @@ -109,7 +108,9 @@ export default class TransactionController extends EventEmitter { return [...pending, ...approved] }, approveTransaction: this.approveTransaction.bind(this), - getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), + getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind( + this.txStateManager, + ), }) this.txStateManager.store.subscribe(() => this.emit('update:badge')) @@ -132,7 +133,7 @@ export default class TransactionController extends EventEmitter { * * @returns {number} The numerical chainId. */ - getChainId () { + getChainId() { const networkState = this.networkStore.getState() const chainId = this._getCurrentChainId() const integerChainId = parseInt(chainId, 16) @@ -146,7 +147,7 @@ export default class TransactionController extends EventEmitter { Adds a tx to the txlist @emits ${txMeta.id}:unapproved */ - addTx (txMeta) { + addTx(txMeta) { this.txStateManager.addTx(txMeta) this.emit(`${txMeta.id}:unapproved`, txMeta) } @@ -155,37 +156,62 @@ export default class TransactionController extends EventEmitter { Wipes the transactions for a given account @param {string} address - hex string of the from address for txs being removed */ - wipeTransactions (address) { + wipeTransactions(address) { this.txStateManager.wipeTransactions(address) } /** - * Add a new unapproved transaction to the pipeline - * - * @returns {Promise} - the hash of the transaction after being submitted to the network - * @param {Object} txParams - txParams for the transaction - * @param {Object} opts - with the key origin to put the origin on the txMeta - */ - async newUnapprovedTransaction (txParams, opts = {}) { + * Add a new unapproved transaction to the pipeline + * + * @returns {Promise} the hash of the transaction after being submitted to the network + * @param {Object} txParams - txParams for the transaction + * @param {Object} opts - with the key origin to put the origin on the txMeta + */ + async newUnapprovedTransaction(txParams, opts = {}) { + log.debug( + `MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`, + ) - log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) - - const initialTxMeta = await this.addUnapprovedTransaction(txParams, opts.origin) + const initialTxMeta = await this.addUnapprovedTransaction( + txParams, + opts.origin, + ) // listen for tx completion (success, fail) return new Promise((resolve, reject) => { - this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => { - switch (finishedTxMeta.status) { - case 'submitted': - return resolve(finishedTxMeta.hash) - case 'rejected': - return reject(cleanErrorStack(ethErrors.provider.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.'))) - case 'failed': - return reject(cleanErrorStack(ethErrors.rpc.internal(finishedTxMeta.err.message))) - default: - return reject(cleanErrorStack(ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) - } - }) + this.txStateManager.once( + `${initialTxMeta.id}:finished`, + (finishedTxMeta) => { + switch (finishedTxMeta.status) { + case TRANSACTION_STATUSES.SUBMITTED: + return resolve(finishedTxMeta.hash) + case TRANSACTION_STATUSES.REJECTED: + return reject( + cleanErrorStack( + ethErrors.provider.userRejectedRequest( + 'MetaMask Tx Signature: User denied transaction signature.', + ), + ), + ) + case TRANSACTION_STATUSES.FAILED: + return reject( + cleanErrorStack( + ethErrors.rpc.internal(finishedTxMeta.err.message), + ), + ) + default: + return reject( + cleanErrorStack( + ethErrors.rpc.internal( + `MetaMask Tx Signature: Unknown problem: ${JSON.stringify( + finishedTxMeta.txParams, + )}`, + ), + ), + ) + } + }, + ) }) } @@ -195,8 +221,7 @@ export default class TransactionController extends EventEmitter { * * @returns {txMeta} */ - async addUnapprovedTransaction (txParams, origin) { - + async addUnapprovedTransaction(txParams, origin) { // validate const normalizedTxParams = txUtils.normalizeTxParams(txParams) @@ -210,7 +235,7 @@ export default class TransactionController extends EventEmitter { */ let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams, - type: TRANSACTION_TYPE_STANDARD, + type: TRANSACTION_TYPES.STANDARD, }) if (origin === 'metamask') { @@ -236,12 +261,15 @@ export default class TransactionController extends EventEmitter { txMeta.origin = origin - const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) + const { + transactionCategory, + getCodeResponse, + } = await this._determineTransactionCategory(txParams) txMeta.transactionCategory = transactionCategory // ensure value txMeta.txParams.value = txMeta.txParams.value - ? ethUtil.addHexPrefix(txMeta.txParams.value) + ? addHexPrefix(txMeta.txParams.value) : '0x0' this.addTx(txMeta) @@ -267,11 +295,14 @@ export default class TransactionController extends EventEmitter { /** * Adds the tx gas defaults: gas && gasPrice * @param {Object} txMeta - the txMeta object - * @returns {Promise} - resolves with txMeta + * @returns {Promise} resolves with txMeta */ - async addTxGasDefaults (txMeta, getCodeResponse) { + async addTxGasDefaults(txMeta, getCodeResponse) { const defaultGasPrice = await this._getDefaultGasPrice(txMeta) - const { gasLimit: defaultGasLimit, simulationFails } = await this._getDefaultGasLimit(txMeta, getCodeResponse) + const { + gasLimit: defaultGasLimit, + simulationFails, + } = await this._getDefaultGasLimit(txMeta, getCodeResponse) // eslint-disable-next-line no-param-reassign txMeta = this.txStateManager.getTx(txMeta.id) @@ -292,13 +323,13 @@ export default class TransactionController extends EventEmitter { * @param {Object} txMeta - The txMeta object * @returns {Promise} The default gas price */ - async _getDefaultGasPrice (txMeta) { + async _getDefaultGasPrice(txMeta) { if (txMeta.txParams.gasPrice) { return undefined } const gasPrice = await this.query.gasPrice() - return ethUtil.addHexPrefix(gasPrice.toString(16)) + return addHexPrefix(gasPrice.toString(16)) } /** @@ -307,16 +338,18 @@ export default class TransactionController extends EventEmitter { * @param {string} getCodeResponse - The transaction category code response, used for debugging purposes * @returns {Promise} Object containing the default gas limit, or the simulation failure object */ - async _getDefaultGasLimit (txMeta, getCodeResponse) { + async _getDefaultGasLimit(txMeta, getCodeResponse) { if (txMeta.txParams.gas) { return {} } else if ( txMeta.txParams.to && - txMeta.transactionCategory === SEND_ETHER_ACTION_KEY + txMeta.transactionCategory === TRANSACTION_CATEGORIES.SENT_ETHER ) { // if there's data in the params, but there's no contract code, it's not a valid transaction if (txMeta.txParams.data) { - const err = new Error('TxGasUtil - Trying to call a function on a non-contract address') + const err = new Error( + 'TxGasUtil - Trying to call a function on a non-contract address', + ) // set error key so ui can display localized error message err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY @@ -329,10 +362,17 @@ export default class TransactionController extends EventEmitter { return { gasLimit: SIMPLE_GAS_COST } } - const { blockGasLimit, estimatedGasHex, simulationFails } = await this.txGasUtil.analyzeGasUsage(txMeta) + const { + blockGasLimit, + estimatedGasHex, + simulationFails, + } = await this.txGasUtil.analyzeGasUsage(txMeta) // add additional gas buffer to our estimation for safety - const gasLimit = this.txGasUtil.addGasBuffer(ethUtil.addHexPrefix(estimatedGasHex), blockGasLimit) + const gasLimit = this.txGasUtil.addGasBuffer( + addHexPrefix(estimatedGasHex), + blockGasLimit, + ) return { gasLimit, simulationFails } } @@ -344,12 +384,14 @@ export default class TransactionController extends EventEmitter { * @param {string} [customGasPrice] - the hex value to use for the cancel transaction * @returns {txMeta} */ - async createCancelTransaction (originalTxId, customGasPrice) { + async createCancelTransaction(originalTxId, customGasPrice) { const originalTxMeta = this.txStateManager.getTx(originalTxId) const { txParams } = originalTxMeta const { gasPrice: lastGasPrice, from, nonce } = txParams - const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10)) + const newGasPrice = + customGasPrice || + bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10)) const newTxMeta = this.txStateManager.generateTxMeta({ txParams: { from, @@ -361,8 +403,8 @@ export default class TransactionController extends EventEmitter { }, lastGasPrice, loadingDefaults: false, - status: TRANSACTION_STATUS_APPROVED, - type: TRANSACTION_TYPE_CANCEL, + status: TRANSACTION_STATUSES.APPROVED, + type: TRANSACTION_TYPES.CANCEL, }) this.addTx(newTxMeta) @@ -380,12 +422,14 @@ export default class TransactionController extends EventEmitter { * @param {string} [customGasLimit] - The new custom gas limt, in hex * @returns {txMeta} */ - async createSpeedUpTransaction (originalTxId, customGasPrice, customGasLimit) { + async createSpeedUpTransaction(originalTxId, customGasPrice, customGasLimit) { const originalTxMeta = this.txStateManager.getTx(originalTxId) const { txParams } = originalTxMeta const { gasPrice: lastGasPrice } = txParams - const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10)) + const newGasPrice = + customGasPrice || + bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10)) const newTxMeta = this.txStateManager.generateTxMeta({ txParams: { @@ -394,8 +438,8 @@ export default class TransactionController extends EventEmitter { }, lastGasPrice, loadingDefaults: false, - status: TRANSACTION_STATUS_APPROVED, - type: TRANSACTION_TYPE_RETRY, + status: TRANSACTION_STATUSES.APPROVED, + type: TRANSACTION_TYPES.RETRY, }) if (customGasLimit) { @@ -411,7 +455,7 @@ export default class TransactionController extends EventEmitter { updates the txMeta in the txStateManager @param {Object} txMeta - the updated txMeta */ - async updateTransaction (txMeta) { + async updateTransaction(txMeta) { this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction') } @@ -419,7 +463,7 @@ export default class TransactionController extends EventEmitter { updates and approves the transaction @param {Object} txMeta */ - async updateAndApproveTransaction (txMeta) { + async updateAndApproveTransaction(txMeta) { this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction') await this.approveTransaction(txMeta.id) } @@ -432,7 +476,7 @@ export default class TransactionController extends EventEmitter { if any of these steps fails the tx status will be set to failed @param {number} txId - the tx's Id */ - async approveTransaction (txId) { + async approveTransaction(txId) { // TODO: Move this safety out of this function. // Since this transaction is async, // we need to keep track of what is currently being signed, @@ -456,10 +500,13 @@ export default class TransactionController extends EventEmitter { // add nonce to txParams // if txMeta has lastGasPrice then it is a retry at same nonce with higher // gas price transaction and their for the nonce should not be calculated - const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce - const customOrNonce = (customNonceValue === 0) ? customNonceValue : customNonceValue || nonce + const nonce = txMeta.lastGasPrice + ? txMeta.txParams.nonce + : nonceLock.nextNonce + const customOrNonce = + customNonceValue === 0 ? customNonceValue : customNonceValue || nonce - txMeta.txParams.nonce = ethUtil.addHexPrefix(customOrNonce.toString(16)) + txMeta.txParams.nonce = addHexPrefix(customOrNonce.toString(16)) // add nonce debugging information to txMeta txMeta.nonceDetails = nonceLock.nonceDetails if (customNonceValue) { @@ -492,9 +539,9 @@ export default class TransactionController extends EventEmitter { /** adds the chain id and signs the transaction and set the status to signed @param {number} txId - the tx's Id - @returns {string} - rawTx + @returns {string} rawTx */ - async signTransaction (txId) { + async signTransaction(txId) { const txMeta = this.txStateManager.getTx(txId) // add network/chain id const chainId = this.getChainId() @@ -510,7 +557,10 @@ export default class TransactionController extends EventEmitter { txMeta.s = ethUtil.bufferToHex(ethTx.s) txMeta.v = ethUtil.bufferToHex(ethTx.v) - this.txStateManager.updateTx(txMeta, 'transactions#signTransaction: add r, s, v values') + this.txStateManager.updateTx( + txMeta, + 'transactions#signTransaction: add r, s, v values', + ) // set state to signed this.txStateManager.setTxStatusSigned(txMeta.id) @@ -524,10 +574,10 @@ export default class TransactionController extends EventEmitter { @param {string} rawTx - the hex string of the serialized signed transaction @returns {Promise} */ - async publishTransaction (txId, rawTx) { + async publishTransaction(txId, rawTx) { const txMeta = this.txStateManager.getTx(txId) txMeta.rawTx = rawTx - if (txMeta.transactionCategory === SWAP) { + if (txMeta.transactionCategory === TRANSACTION_CATEGORIES.SWAP) { const preTxBalance = await this.query.getBalance(txMeta.txParams.from) txMeta.preTxBalance = preTxBalance.toString(16) } @@ -537,8 +587,8 @@ export default class TransactionController extends EventEmitter { txHash = await this.query.sendRawTransaction(rawTx) } catch (error) { if (error.message.toLowerCase().includes('known transaction')) { - txHash = ethUtil.sha3(ethUtil.addHexPrefix(rawTx)).toString('hex') - txHash = ethUtil.addHexPrefix(txHash) + txHash = ethUtil.sha3(addHexPrefix(rawTx)).toString('hex') + txHash = addHexPrefix(txHash) } else { throw error } @@ -554,7 +604,7 @@ export default class TransactionController extends EventEmitter { * @param {number} txId - The tx's ID * @returns {Promise} */ - async confirmTransaction (txId, txReceipt) { + async confirmTransaction(txId, txReceipt) { // get the txReceipt before marking the transaction confirmed // to ensure the receipt is gotten before the ui revives the tx const txMeta = this.txStateManager.getTx(txId) @@ -566,9 +616,10 @@ export default class TransactionController extends EventEmitter { try { // It seems that sometimes the numerical values being returned from // this.query.getTransactionReceipt are BN instances and not strings. - const gasUsed = typeof txReceipt.gasUsed === 'string' - ? txReceipt.gasUsed - : txReceipt.gasUsed.toString(16) + const gasUsed = + typeof txReceipt.gasUsed === 'string' + ? txReceipt.gasUsed + : txReceipt.gasUsed.toString(16) txMeta.txReceipt = { ...txReceipt, @@ -577,9 +628,12 @@ export default class TransactionController extends EventEmitter { this.txStateManager.setTxStatusConfirmed(txId) this._markNonceDuplicatesDropped(txId) - this.txStateManager.updateTx(txMeta, 'transactions#confirmTransaction - add txReceipt') + this.txStateManager.updateTx( + txMeta, + 'transactions#confirmTransaction - add txReceipt', + ) - if (txMeta.transactionCategory === SWAP) { + if (txMeta.transactionCategory === TRANSACTION_CATEGORIES.SWAP) { const postTxBalance = await this.query.getBalance(txMeta.txParams.from) const latestTxMeta = this.txStateManager.getTx(txId) @@ -589,11 +643,13 @@ export default class TransactionController extends EventEmitter { latestTxMeta.postTxBalance = postTxBalance.toString(16) - this.txStateManager.updateTx(latestTxMeta, 'transactions#confirmTransaction - add postTxBalance') + this.txStateManager.updateTx( + latestTxMeta, + 'transactions#confirmTransaction - add postTxBalance', + ) this._trackSwapsMetrics(latestTxMeta, approvalTxMeta) } - } catch (err) { log.error(err) } @@ -604,7 +660,7 @@ export default class TransactionController extends EventEmitter { @param {number} txId - the tx's Id @returns {Promise} */ - async cancelTransaction (txId) { + async cancelTransaction(txId) { this.txStateManager.setTxStatusRejected(txId) } @@ -613,7 +669,7 @@ export default class TransactionController extends EventEmitter { @param {number} txId - the tx's Id @param {string} txHash - the hash for the txMeta */ - setTxHash (txId, txHash) { + setTxHash(txId, txHash) { // Add the tx hash to the persisted meta-tx object const txMeta = this.txStateManager.getTx(txId) txMeta.hash = txHash @@ -624,32 +680,35 @@ export default class TransactionController extends EventEmitter { // PRIVATE METHODS // /** maps methods for convenience*/ - _mapMethods () { - - /** @returns {Object} - the state in transaction controller */ + _mapMethods() { + /** @returns {Object} the state in transaction controller */ this.getState = () => this.memStore.getState() - /** @returns {string|number} - the network number stored in networkStore */ + /** @returns {string|number} the network number stored in networkStore */ this.getNetwork = () => this.networkStore.getState() - /** @returns {string} - the user selected address */ - this.getSelectedAddress = () => this.preferencesStore.getState().selectedAddress + /** @returns {string} the user selected address */ + this.getSelectedAddress = () => + this.preferencesStore.getState().selectedAddress - /** @returns {array} - transactions whos status is unapproved */ - this.getUnapprovedTxCount = () => Object.keys(this.txStateManager.getUnapprovedTxList()).length + /** @returns {Array} transactions whos status is unapproved */ + this.getUnapprovedTxCount = () => + Object.keys(this.txStateManager.getUnapprovedTxList()).length /** - @returns {number} - number of transactions that have the status submitted + @returns {number} number of transactions that have the status submitted @param {string} account - hex prefixed account */ - this.getPendingTxCount = (account) => this.txStateManager.getPendingTransactions(account).length + this.getPendingTxCount = (account) => + this.txStateManager.getPendingTransactions(account).length /** see txStateManager */ - this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts) + this.getFilteredTxList = (opts) => + this.txStateManager.getFilteredTxList(opts) } // called once on startup - async _updatePendingTxsAfterFirstBlock () { + async _updatePendingTxsAfterFirstBlock() { // wait for first block so we know we're ready await this.blockTracker.getLatestBlock() // get status update for all pending transactions (for the current network) @@ -662,49 +721,78 @@ export default class TransactionController extends EventEmitter { transition txMetas to a failed state or try to redo those tasks. */ - _onBootCleanUp () { - this.txStateManager.getFilteredTxList({ - status: 'unapproved', - loadingDefaults: true, - }).forEach((tx) => { + _onBootCleanUp() { + this.txStateManager + .getFilteredTxList({ + status: TRANSACTION_STATUSES.UNAPPROVED, + loadingDefaults: true, + }) + .forEach((tx) => { + this.addTxGasDefaults(tx) + .then((txMeta) => { + txMeta.loadingDefaults = false + this.txStateManager.updateTx( + txMeta, + 'transactions: gas estimation for tx on boot', + ) + }) + .catch((error) => { + const txMeta = this.txStateManager.getTx(tx.id) + txMeta.loadingDefaults = false + this.txStateManager.updateTx( + txMeta, + 'failed to estimate gas during boot cleanup.', + ) + this.txStateManager.setTxStatusFailed(txMeta.id, error) + }) + }) - this.addTxGasDefaults(tx) - .then((txMeta) => { - txMeta.loadingDefaults = false - this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot') - }).catch((error) => { - const txMeta = this.txStateManager.getTx(tx.id) - txMeta.loadingDefaults = false - this.txStateManager.updateTx(txMeta, 'failed to estimate gas during boot cleanup.') - this.txStateManager.setTxStatusFailed(txMeta.id, error) - }) - }) - - this.txStateManager.getFilteredTxList({ - status: TRANSACTION_STATUS_APPROVED, - }).forEach((txMeta) => { - const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing') - this.txStateManager.setTxStatusFailed(txMeta.id, txSignError) - }) + this.txStateManager + .getFilteredTxList({ + status: TRANSACTION_STATUSES.APPROVED, + }) + .forEach((txMeta) => { + const txSignError = new Error( + 'Transaction found as "approved" during boot - possibly stuck during signing', + ) + this.txStateManager.setTxStatusFailed(txMeta.id, txSignError) + }) } /** is called in constructor applies the listeners for pendingTxTracker txStateManager and blockTracker */ - _setupListeners () { - this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update')) + _setupListeners() { + this.txStateManager.on( + 'tx:status-update', + this.emit.bind(this, 'tx:status-update'), + ) this._setupBlockTrackerListener() this.pendingTxTracker.on('tx:warning', (txMeta) => { - this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning') + this.txStateManager.updateTx( + txMeta, + 'transactions/pending-tx-tracker#event: tx:warning', + ) }) - this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager)) - this.pendingTxTracker.on('tx:confirmed', (txId, transactionReceipt) => this.confirmTransaction(txId, transactionReceipt)) - this.pendingTxTracker.on('tx:dropped', this.txStateManager.setTxStatusDropped.bind(this.txStateManager)) + this.pendingTxTracker.on( + 'tx:failed', + this.txStateManager.setTxStatusFailed.bind(this.txStateManager), + ) + this.pendingTxTracker.on('tx:confirmed', (txId, transactionReceipt) => + this.confirmTransaction(txId, transactionReceipt), + ) + this.pendingTxTracker.on( + 'tx:dropped', + this.txStateManager.setTxStatusDropped.bind(this.txStateManager), + ) this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => { if (!txMeta.firstRetryBlockNumber) { txMeta.firstRetryBlockNumber = latestBlockNumber - this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update') + this.txStateManager.updateTx( + txMeta, + 'transactions/pending-tx-tracker#event: tx:block-update', + ) } }) this.pendingTxTracker.on('tx:retry', (txMeta) => { @@ -712,7 +800,10 @@ export default class TransactionController extends EventEmitter { txMeta.retryCount = 0 } txMeta.retryCount += 1 - this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry') + this.txStateManager.updateTx( + txMeta, + 'transactions/pending-tx-tracker#event: tx:retry', + ) }) } @@ -720,7 +811,7 @@ export default class TransactionController extends EventEmitter { Returns a "type" for a transaction out of the following list: simpleSend, tokenTransfer, tokenApprove, contractDeployment, contractMethodCall */ - async _determineTransactionCategory (txParams) { + async _determineTransactionCategory(txParams) { const { data, to } = txParams let name try { @@ -730,16 +821,16 @@ export default class TransactionController extends EventEmitter { } const tokenMethodName = [ - TOKEN_METHOD_APPROVE, - TOKEN_METHOD_TRANSFER, - TOKEN_METHOD_TRANSFER_FROM, + TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE, + TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, + TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM, ].find((methodName) => methodName === name && name.toLowerCase()) let result if (txParams.data && tokenMethodName) { result = tokenMethodName } else if (txParams.data && !to) { - result = DEPLOY_CONTRACT_ACTION_KEY + result = TRANSACTION_CATEGORIES.DEPLOY_CONTRACT } let code @@ -753,7 +844,9 @@ export default class TransactionController extends EventEmitter { const codeIsEmpty = !code || code === '0x' || code === '0x0' - result = codeIsEmpty ? SEND_ETHER_ACTION_KEY : CONTRACT_INTERACTION_KEY + result = codeIsEmpty + ? TRANSACTION_CATEGORIES.SENT_ETHER + : TRANSACTION_CATEGORIES.CONTRACT_INTERACTION } return { transactionCategory: result, getCodeResponse: code } @@ -765,7 +858,7 @@ export default class TransactionController extends EventEmitter { @param {number} txId - the txId of the transaction that has been confirmed in a block */ - _markNonceDuplicatesDropped (txId) { + _markNonceDuplicatesDropped(txId) { // get the confirmed transactions nonce and from address const txMeta = this.txStateManager.getTx(txId) const { nonce, from } = txMeta.txParams @@ -779,12 +872,15 @@ export default class TransactionController extends EventEmitter { return } otherTxMeta.replacedBy = txMeta.hash - this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce') + this.txStateManager.updateTx( + txMeta, + 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce', + ) this.txStateManager.setTxStatusDropped(otherTxMeta.id) }) } - _setupBlockTrackerListener () { + _setupBlockTrackerListener() { let listenersAreActive = false const latestBlockHandler = this._onLatestBlock.bind(this) const { blockTracker, txStateManager } = this @@ -792,7 +888,7 @@ export default class TransactionController extends EventEmitter { txStateManager.on('tx:status-update', updateSubscription) updateSubscription() - function updateSubscription () { + function updateSubscription() { const pendingTxs = txStateManager.getPendingTransactions() if (!listenersAreActive && pendingTxs.length > 0) { blockTracker.on('latest', latestBlockHandler) @@ -804,7 +900,7 @@ export default class TransactionController extends EventEmitter { } } - async _onLatestBlock (blockNumber) { + async _onLatestBlock(blockNumber) { try { await this.pendingTxTracker.updatePendingTxs() } catch (err) { @@ -820,13 +916,15 @@ export default class TransactionController extends EventEmitter { /** Updates the memStore in transaction controller */ - _updateMemstore () { + _updateMemstore() { const unapprovedTxs = this.txStateManager.getUnapprovedTxList() - const currentNetworkTxList = this.txStateManager.getTxList(MAX_MEMSTORE_TX_LIST_SIZE) + const currentNetworkTxList = this.txStateManager.getTxList( + MAX_MEMSTORE_TX_LIST_SIZE, + ) this.memStore.updateState({ unapprovedTxs, currentNetworkTxList }) } - _trackSwapsMetrics (txMeta, approvalTxMeta) { + _trackSwapsMetrics(txMeta, approvalTxMeta) { if (this._getParticipateInMetrics() && txMeta.swapMetaData) { if (txMeta.txReceipt.status === '0x0') { this._trackMetaMetricsEvent({ @@ -851,19 +949,18 @@ export default class TransactionController extends EventEmitter { approvalTxMeta, ) - const quoteVsExecutionRatio = `${ - (new BigNumber(tokensReceived, 10)) - .div(txMeta.swapMetaData.token_to_amount, 10) - .times(100) - .round(2) - }%` + const quoteVsExecutionRatio = `${new BigNumber(tokensReceived, 10) + .div(txMeta.swapMetaData.token_to_amount, 10) + .times(100) + .round(2)}%` - const estimatedVsUsedGasRatio = `${ - (new BigNumber(txMeta.txReceipt.gasUsed, 16)) - .div(txMeta.swapMetaData.estimated_gas, 10) - .times(100) - .round(2) - }%` + const estimatedVsUsedGasRatio = `${new BigNumber( + txMeta.txReceipt.gasUsed, + 16, + ) + .div(txMeta.swapMetaData.estimated_gas, 10) + .times(100) + .round(2)}%` this._trackMetaMetricsEvent({ event: 'Swap Completed', diff --git a/app/scripts/controllers/transactions/lib/tx-state-history-helpers.js b/app/scripts/controllers/transactions/lib/tx-state-history-helpers.js index 20505ec06..4a757c2ff 100644 --- a/app/scripts/controllers/transactions/lib/tx-state-history-helpers.js +++ b/app/scripts/controllers/transactions/lib/tx-state-history-helpers.js @@ -3,13 +3,13 @@ import { cloneDeep } from 'lodash' /** converts non-initial history entries into diffs - @param {array} longHistory - @returns {array} + @param {Array} longHistory + @returns {Array} */ -export function migrateFromSnapshotsToDiffs (longHistory) { +export function migrateFromSnapshotsToDiffs(longHistory) { return ( longHistory - // convert non-initial history entries into diffs + // convert non-initial history entries into diffs .map((entry, index) => { if (index === 0) { return entry @@ -29,9 +29,9 @@ export function migrateFromSnapshotsToDiffs (longHistory) { @param {Object} previousState - the previous state of the object @param {Object} newState - the update object @param {string} [note] - a optional note for the state change - @returns {array} + @returns {Array} */ -export function generateHistoryEntry (previousState, newState, note) { +export function generateHistoryEntry(previousState, newState, note) { const entry = jsonDiffer.compare(previousState, newState) // Add a note to the first op, since it breaks if we append it to the entry if (entry[0]) { @@ -48,9 +48,11 @@ export function generateHistoryEntry (previousState, newState, note) { Recovers previous txMeta state obj @returns {Object} */ -export function replayHistory (_shortHistory) { +export function replayHistory(_shortHistory) { const shortHistory = cloneDeep(_shortHistory) - return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument) + return shortHistory.reduce( + (val, entry) => jsonDiffer.applyPatch(val, entry).newDocument, + ) } /** @@ -58,7 +60,7 @@ export function replayHistory (_shortHistory) { * @param {Object} txMeta - the tx metadata object * @returns {Object} a deep clone without history */ -export function snapshotFromTxMeta (txMeta) { +export function snapshotFromTxMeta(txMeta) { const shallow = { ...txMeta } delete shallow.history return cloneDeep(shallow) diff --git a/app/scripts/controllers/transactions/lib/util.js b/app/scripts/controllers/transactions/lib/util.js index da58bf6f3..758d70121 100644 --- a/app/scripts/controllers/transactions/lib/util.js +++ b/app/scripts/controllers/transactions/lib/util.js @@ -1,8 +1,11 @@ -import { addHexPrefix, isValidAddress } from 'ethereumjs-util' +import { isValidAddress } from 'ethereumjs-util' +import { addHexPrefix } from '../../../lib/util' +import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction' const normalizers = { from: (from) => addHexPrefix(from), - to: (to, lowerCase) => (lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to)), + to: (to, lowerCase) => + lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to), nonce: (nonce) => addHexPrefix(nonce), value: (value) => addHexPrefix(value), data: (data) => addHexPrefix(data), @@ -17,7 +20,7 @@ const normalizers = { * Default: true * @returns {Object} the normalized tx params */ -export function normalizeTxParams (txParams, lowerCase = true) { +export function normalizeTxParams(txParams, lowerCase = true) { // apply only keys in the normalizers const normalizedTxParams = {} for (const key in normalizers) { @@ -33,17 +36,21 @@ export function normalizeTxParams (txParams, lowerCase = true) { * @param {Object} txParams - the tx params * @throws {Error} if the tx params contains invalid fields */ -export function validateTxParams (txParams) { +export function validateTxParams(txParams) { validateFrom(txParams) validateRecipient(txParams) if ('value' in txParams) { const value = txParams.value.toString() if (value.includes('-')) { - throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`) + throw new Error( + `Invalid transaction value of ${txParams.value} not a positive number.`, + ) } if (value.includes('.')) { - throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`) + throw new Error( + `Invalid transaction value of ${txParams.value} number must be in wei`, + ) } } } @@ -53,7 +60,7 @@ export function validateTxParams (txParams) { * @param {Object} txParams * @throws {Error} if the from address isn't valid */ -export function validateFrom (txParams) { +export function validateFrom(txParams) { if (!(typeof txParams.from === 'string')) { throw new Error(`Invalid from address ${txParams.from} not a string`) } @@ -68,7 +75,7 @@ export function validateFrom (txParams) { * @returns {Object} the tx params * @throws {Error} if the recipient is invalid OR there isn't tx data */ -export function validateRecipient (txParams) { +export function validateRecipient(txParams) { if (txParams.to === '0x' || txParams.to === null) { if (txParams.data) { delete txParams.to @@ -85,11 +92,11 @@ export function validateRecipient (txParams) { * Returns a list of final states * @returns {string[]} the states that can be considered final states */ -export function getFinalStates () { +export function getFinalStates() { return [ - 'rejected', // the user has responded no! - 'confirmed', // the tx has been included in a block. - 'failed', // the tx failed for some reason, included on tx data. - 'dropped', // the tx nonce was already used + TRANSACTION_STATUSES.REJECTED, // the user has responded no! + TRANSACTION_STATUSES.CONFIRMED, // the tx has been included in a block. + TRANSACTION_STATUSES.FAILED, // the tx failed for some reason, included on tx data. + TRANSACTION_STATUSES.DROPPED, // the tx nonce was already used ] } diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index e1b33cb2f..2194ca495 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -1,6 +1,7 @@ import EventEmitter from 'safe-event-emitter' import log from 'loglevel' import EthQuery from 'ethjs-query' +import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction' /** @@ -11,15 +12,14 @@ import EthQuery from 'ethjs-query'
@param {Object} config - non optional configuration object consists of: @param {Object} config.provider - A network provider. - @param {Object} config.nonceTracker see nonce tracker - @param {function} config.getPendingTransactions a function for getting an array of transactions, - @param {function} config.publishTransaction a async function for publishing raw transactions, + @param {Object} config.nonceTracker - see nonce tracker + @param {Function} config.getPendingTransactions - a function for getting an array of transactions, + @param {Function} config.publishTransaction - a async function for publishing raw transactions, @class */ export default class PendingTransactionTracker extends EventEmitter { - /** * We wait this many blocks before emitting a 'tx:dropped' event * @@ -37,9 +37,9 @@ export default class PendingTransactionTracker extends EventEmitter { */ droppedBlocksBufferByHash = new Map() - constructor (config) { + constructor(config) { super() - this.query = config.query || (new EthQuery(config.provider)) + this.query = config.query || new EthQuery(config.provider) this.nonceTracker = config.nonceTracker this.getPendingTransactions = config.getPendingTransactions this.getCompletedTransactions = config.getCompletedTransactions @@ -51,14 +51,18 @@ export default class PendingTransactionTracker extends EventEmitter { /** checks the network for signed txs and releases the nonce global lock if it is */ - async updatePendingTxs () { + async updatePendingTxs() { // in order to keep the nonceTracker accurate we block it while updating pending transactions const nonceGlobalLock = await this.nonceTracker.getGlobalLock() try { const pendingTxs = this.getPendingTransactions() - await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta))) + await Promise.all( + pendingTxs.map((txMeta) => this._checkPendingTx(txMeta)), + ) } catch (err) { - log.error('PendingTransactionTracker - Error updating pending transactions') + log.error( + 'PendingTransactionTracker - Error updating pending transactions', + ) log.error(err) } nonceGlobalLock.releaseLock() @@ -70,7 +74,7 @@ export default class PendingTransactionTracker extends EventEmitter { * @emits tx:warning * @returns {Promise} */ - async resubmitPendingTxs (blockNumber) { + async resubmitPendingTxs(blockNumber) { const pending = this.getPendingTransactions() if (!pending.length) { return @@ -79,18 +83,20 @@ export default class PendingTransactionTracker extends EventEmitter { try { await this._resubmitTx(txMeta, blockNumber) } catch (err) { - const errorMessage = err.value?.message?.toLowerCase() || err.message.toLowerCase() - const isKnownTx = ( + const errorMessage = + err.value?.message?.toLowerCase() || err.message.toLowerCase() + const isKnownTx = // geth errorMessage.includes('replacement transaction underpriced') || errorMessage.includes('known transaction') || // parity errorMessage.includes('gas price too low to replace') || - errorMessage.includes('transaction with the same hash was already imported') || + errorMessage.includes( + 'transaction with the same hash was already imported', + ) || // other errorMessage.includes('gateway timeout') || errorMessage.includes('nonce too low') - ) // ignore resubmit warnings, return early if (isKnownTx) { return @@ -117,13 +123,16 @@ export default class PendingTransactionTracker extends EventEmitter { * @emits tx:retry * @private */ - async _resubmitTx (txMeta, latestBlockNumber) { + async _resubmitTx(txMeta, latestBlockNumber) { if (!txMeta.firstRetryBlockNumber) { this.emit('tx:block-update', txMeta, latestBlockNumber) } - const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber - const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16) + const firstRetryBlockNumber = + txMeta.firstRetryBlockNumber || latestBlockNumber + const txBlockDistance = + Number.parseInt(latestBlockNumber, 16) - + Number.parseInt(firstRetryBlockNumber, 16) const retryCount = txMeta.retryCount || 0 @@ -155,19 +164,21 @@ export default class PendingTransactionTracker extends EventEmitter { * @emits tx:warning * @private */ - async _checkPendingTx (txMeta) { + async _checkPendingTx(txMeta) { const txHash = txMeta.hash const txId = txMeta.id // Only check submitted txs - if (txMeta.status !== 'submitted') { + if (txMeta.status !== TRANSACTION_STATUSES.SUBMITTED) { return } // extra check in case there was an uncaught error during the // signature and submission process if (!txHash) { - const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') + const noTxHashErr = new Error( + 'We had an error while submitting this transaction, please try again.', + ) noTxHashErr.name = 'NoTxHashError' this.emit('tx:failed', txId, noTxHashErr) @@ -206,8 +217,11 @@ export default class PendingTransactionTracker extends EventEmitter { * @returns {Promise} * @private */ - async _checkIfTxWasDropped (txMeta) { - const { hash: txHash, txParams: { nonce, from } } = txMeta + async _checkIfTxWasDropped(txMeta) { + const { + hash: txHash, + txParams: { nonce, from }, + } = txMeta const networkNextNonce = await this.query.getTransactionCount(from) if (parseInt(nonce, 16) >= networkNextNonce.toNumber()) { @@ -235,14 +249,16 @@ export default class PendingTransactionTracker extends EventEmitter { * @returns {Promise} * @private */ - async _checkIfNonceIsTaken (txMeta) { + async _checkIfNonceIsTaken(txMeta) { const address = txMeta.txParams.from const completed = this.getCompletedTransactions(address) return completed.some( // This is called while the transaction is in-flight, so it is possible that the // list of completed transactions now includes the transaction we were looking at // and if that is the case, don't consider the transaction to have taken its own nonce - (other) => !(other.id === txMeta.id) && other.txParams.nonce === txMeta.txParams.nonce, + (other) => + !(other.id === txMeta.id) && + other.txParams.nonce === txMeta.txParams.nonce, ) } } diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index fa89ef293..71d8d27fb 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -20,8 +20,7 @@ and used to do things like calculate gas of a tx. */ export default class TxGasUtil { - - constructor (provider) { + constructor(provider) { this.query = new EthQuery(provider) } @@ -29,7 +28,7 @@ export default class TxGasUtil { @param {Object} txMeta - the txMeta object @returns {GasAnalysisResult} The result of the gas analysis */ - async analyzeGasUsage (txMeta) { + async analyzeGasUsage(txMeta) { const block = await this.query.getBlockByNumber('latest', false) // fallback to block gasLimit @@ -54,9 +53,9 @@ export default class TxGasUtil { /** Estimates the tx's gas usage @param {Object} txMeta - the txMeta object - @returns {string} - the estimated gas limit as a hex string + @returns {string} the estimated gas limit as a hex string */ - async estimateTxGas (txMeta) { + async estimateTxGas(txMeta) { const { txParams } = txMeta // estimate tx gas requirements @@ -68,9 +67,9 @@ export default class TxGasUtil { @param {string} initialGasLimitHex - the initial gas limit to add the buffer too @param {string} blockGasLimitHex - the block gas limit - @returns {string} - the buffered gas limit as a hex string + @returns {string} the buffered gas limit as a hex string */ - addGasBuffer (initialGasLimitHex, blockGasLimitHex, multiplier = 1.5) { + addGasBuffer(initialGasLimitHex, blockGasLimitHex, multiplier = 1.5) { const initialGasLimitBn = hexToBn(initialGasLimitHex) const blockGasLimitBn = hexToBn(blockGasLimitHex) const upperGasLimitBn = blockGasLimitBn.muln(0.9) @@ -88,11 +87,19 @@ export default class TxGasUtil { return bnToHex(upperGasLimitBn) } - async getBufferedGasLimit (txMeta, multiplier) { - const { blockGasLimit, estimatedGasHex, simulationFails } = await this.analyzeGasUsage(txMeta) + async getBufferedGasLimit(txMeta, multiplier) { + const { + blockGasLimit, + estimatedGasHex, + simulationFails, + } = await this.analyzeGasUsage(txMeta) // add additional gas buffer to our estimation for safety - const gasLimit = this.addGasBuffer(ethUtil.addHexPrefix(estimatedGasHex), blockGasLimit, multiplier) + const gasLimit = this.addGasBuffer( + ethUtil.addHexPrefix(estimatedGasHex), + blockGasLimit, + multiplier, + ) return { gasLimit, simulationFails } } } diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index b5c7e3353..456a1db98 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -2,57 +2,55 @@ import EventEmitter from 'safe-event-emitter' import ObservableStore from 'obs-store' import log from 'loglevel' import createId from '../../lib/random-id' -import { generateHistoryEntry, replayHistory, snapshotFromTxMeta } from './lib/tx-state-history-helpers' +import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction' +import { + generateHistoryEntry, + replayHistory, + snapshotFromTxMeta, +} from './lib/tx-state-history-helpers' import { getFinalStates, normalizeTxParams } from './lib/util' /** - TransactionStateManager is responsible for the state of a transaction and - storing the transaction - it also has some convenience methods for finding subsets of transactions - * - *STATUS METHODS -
statuses: -
- `'unapproved'` the user has not responded -
- `'rejected'` the user has responded no! -
- `'approved'` the user has approved the tx -
- `'signed'` the tx is signed -
- `'submitted'` the tx is sent to a server -
- `'confirmed'` the tx has been included in a block. -
- `'failed'` the tx failed for some reason, included on tx data. -
- `'dropped'` the tx nonce was already used - @param {Object} opts - @param {Object} [opts.initState={ transactions: [] }] initial transactions list with the key transaction {array} - @param {number} [opts.txHistoryLimit] limit for how many finished - transactions can hang around in state - @param {function} opts.getNetwork return network number - @class -*/ + * TransactionStatuses reimported from the shared transaction constants file + * @typedef {import('../../../../shared/constants/transaction').TransactionStatuses} TransactionStatuses + */ + +/** + * TransactionStateManager is responsible for the state of a transaction and + * storing the transaction. It also has some convenience methods for finding + * subsets of transactions. + * @param {Object} opts + * @param {Object} [opts.initState={ transactions: [] }] - initial transactions list with the key transaction {Array} + * @param {number} [opts.txHistoryLimit] - limit for how many finished + * transactions can hang around in state + * @param {Function} opts.getNetwork - return network number + * @class + */ export default class TransactionStateManager extends EventEmitter { - constructor ({ initState, txHistoryLimit, getNetwork }) { + constructor({ initState, txHistoryLimit, getNetwork }) { super() - this.store = new ObservableStore( - { transactions: [], ...initState }, - ) + this.store = new ObservableStore({ transactions: [], ...initState }) this.txHistoryLimit = txHistoryLimit this.getNetwork = getNetwork } /** - @param {Object} opts - the object to use when overwriting defaults - @returns {txMeta} - the default txMeta object - */ - generateTxMeta (opts) { + * @param {Object} opts - the object to use when overwriting defaults + * @returns {txMeta} the default txMeta object + */ + generateTxMeta(opts) { const netId = this.getNetwork() if (netId === 'loading') { throw new Error('MetaMask is having trouble connecting to the network') } return { id: createId(), - time: (new Date()).getTime(), - status: 'unapproved', + time: new Date().getTime(), + status: TRANSACTION_STATUSES.UNAPPROVED, metamaskNetworkId: netId, - loadingDefaults: true, ...opts, + loadingDefaults: true, + ...opts, } } @@ -61,10 +59,10 @@ export default class TransactionStateManager extends EventEmitter { * * The list is iterated backwards as new transactions are pushed onto it. * - * @param {number} [limit] a limit for the number of transactions to return + * @param {number} [limit] - a limit for the number of transactions to return * @returns {Object[]} The {@code txMeta}s, filtered to the current network */ - getTxList (limit) { + getTxList(limit) { const network = this.getNetwork() const fullTxList = this.getFullTxList() @@ -93,17 +91,20 @@ export default class TransactionStateManager extends EventEmitter { } /** - @returns {array} - of all the txMetas in store - */ - getFullTxList () { + * @returns {Array} of all the txMetas in store + */ + getFullTxList() { return this.store.getState().transactions } /** - @returns {array} - the tx list whose status is unapproved - */ - getUnapprovedTxList () { - const txList = this.getTxsByMetaData('status', 'unapproved') + * @returns {Array} the tx list with unapproved status + */ + getUnapprovedTxList() { + const txList = this.getTxsByMetaData( + 'status', + TRANSACTION_STATUSES.UNAPPROVED, + ) return txList.reduce((result, tx) => { result[tx.id] = tx return result @@ -111,12 +112,12 @@ export default class TransactionStateManager extends EventEmitter { } /** - @param [address] {string} - hex prefixed address to sort the txMetas for [optional] - @returns {array} - the tx list whose status is approved if no address is provide - returns all txMetas who's status is approved for the current network - */ - getApprovedTransactions (address) { - const opts = { status: 'approved' } + * @param {string} [address] - hex prefixed address to sort the txMetas for [optional] + * @returns {Array} the tx list with approved status if no address is provide + * returns all txMetas with approved statuses for the current network + */ + getApprovedTransactions(address) { + const opts = { status: TRANSACTION_STATUSES.APPROVED } if (address) { opts.from = address } @@ -124,12 +125,12 @@ export default class TransactionStateManager extends EventEmitter { } /** - @param [address] {string} - hex prefixed address to sort the txMetas for [optional] - @returns {array} - the tx list whose status is submitted if no address is provide - returns all txMetas who's status is submitted for the current network - */ - getPendingTransactions (address) { - const opts = { status: 'submitted' } + * @param {string} [address] - hex prefixed address to sort the txMetas for [optional] + * @returns {Array} the tx list submitted status if no address is provide + * returns all txMetas with submitted statuses for the current network + */ + getPendingTransactions(address) { + const opts = { status: TRANSACTION_STATUSES.SUBMITTED } if (address) { opts.from = address } @@ -137,12 +138,12 @@ export default class TransactionStateManager extends EventEmitter { } /** - @param [address] {string} - hex prefixed address to sort the txMetas for [optional] - @returns {array} - the tx list whose status is confirmed if no address is provide + @param {string} [address] - hex prefixed address to sort the txMetas for [optional] + @returns {Array} the tx list whose status is confirmed if no address is provide returns all txMetas who's status is confirmed for the current network */ - getConfirmedTransactions (address) { - const opts = { status: 'confirmed' } + getConfirmedTransactions(address) { + const opts = { status: TRANSACTION_STATUSES.CONFIRMED } if (address) { opts.from = address } @@ -150,15 +151,15 @@ export default class TransactionStateManager extends EventEmitter { } /** - Adds the txMeta to the list of transactions in the store. - if the list is over txHistoryLimit it will remove a transaction that - is in its final state - it will also add the key `history` to the txMeta with the snap shot of the original - object - @param {Object} txMeta - @returns {Object} - the txMeta - */ - addTx (txMeta) { + * Adds the txMeta to the list of transactions in the store. + * if the list is over txHistoryLimit it will remove a transaction that + * is in its final state. + * it will also add the key `history` to the txMeta with the snap shot of + * the original object + * @param {Object} txMeta + * @returns {Object} the txMeta + */ + addTx(txMeta) { // normalize and validate txParams if present if (txMeta.txParams) { txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams) @@ -193,8 +194,9 @@ export default class TransactionStateManager extends EventEmitter { transactions.splice(index, 1) } } - const newTxIndex = transactions - .findIndex((currentTxMeta) => currentTxMeta.time > txMeta.time) + const newTxIndex = transactions.findIndex( + (currentTxMeta) => currentTxMeta.time > txMeta.time, + ) newTxIndex === -1 ? transactions.push(txMeta) @@ -204,21 +206,21 @@ export default class TransactionStateManager extends EventEmitter { } /** - @param {number} txId - @returns {Object} - the txMeta who matches the given id if none found - for the network returns undefined - */ - getTx (txId) { + * @param {number} txId + * @returns {Object} the txMeta who matches the given id if none found + * for the network returns undefined + */ + getTx(txId) { const txMeta = this.getTxsByMetaData('id', txId)[0] return txMeta } /** - updates the txMeta in the list and adds a history entry - @param {Object} txMeta - the txMeta to update - @param {string} [note] - a note about the update for history - */ - updateTx (txMeta, note) { + * updates the txMeta in the list and adds a history entry + * @param {Object} txMeta - the txMeta to update + * @param {string} [note] - a note about the update for history + */ + updateTx(txMeta, note) { // normalize and validate txParams if present if (txMeta.txParams) { txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams) @@ -243,12 +245,12 @@ export default class TransactionStateManager extends EventEmitter { } /** - merges txParams obj onto txMeta.txParams - use extend to ensure that all fields are filled - @param {number} txId - the id of the txMeta - @param {Object} txParams - the updated txParams - */ - updateTxParams (txId, txParams) { + * merges txParams obj onto txMeta.txParams use extend to ensure + * that all fields are filled + * @param {number} txId - the id of the txMeta + * @param {Object} txParams - the updated txParams + */ + updateTxParams(txId, txParams) { const txMeta = this.getTx(txId) txMeta.txParams = { ...txMeta.txParams, ...txParams } this.updateTx(txMeta, `txStateManager#updateTxParams`) @@ -258,7 +260,7 @@ export default class TransactionStateManager extends EventEmitter { * normalize and validate txParams members * @param {Object} txParams - txParams */ - normalizeAndValidateTxParams (txParams) { + normalizeAndValidateTxParams(txParams) { if (typeof txParams.data === 'undefined') { delete txParams.data } @@ -269,22 +271,26 @@ export default class TransactionStateManager extends EventEmitter { } /** - validates txParams members by type - @param {Object} txParams - txParams to validate - */ - validateTxParams (txParams) { + * validates txParams members by type + * @param {Object} txParams - txParams to validate + */ + validateTxParams(txParams) { Object.keys(txParams).forEach((key) => { const value = txParams[key] // validate types switch (key) { case 'chainId': if (typeof value !== 'number' && typeof value !== 'string') { - throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`) + throw new Error( + `${key} in txParams is not a Number or hex string. got: (${value})`, + ) } break default: if (typeof value !== 'string') { - throw new Error(`${key} in txParams is not a string. got: (${value})`) + throw new Error( + `${key} in txParams is not a string. got: (${value})`, + ) } break } @@ -301,8 +307,8 @@ export default class TransactionStateManager extends EventEmitter { }
optionally the values of the keys can be functions for situations like where you want all but one status. - @param [initialList=this.getTxList()] - @returns {array} - array of txMeta with all + @param {Array} [initialList=this.getTxList()] + @returns {Array} array of txMeta with all options matching */ /* @@ -319,7 +325,7 @@ export default class TransactionStateManager extends EventEmitter { or for filtering for all txs from one account and that have been 'confirmed' */ - getFilteredTxList (opts, initialList) { + getFilteredTxList(opts, initialList) { let filteredTxList = initialList Object.keys(opts).forEach((key) => { filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList) @@ -328,14 +334,13 @@ export default class TransactionStateManager extends EventEmitter { } /** - - @param {string} key - the key to check - @param value - the value your looking for can also be a function that returns a bool - @param [txList=this.getTxList()] {array} - the list to search. default is the txList - from txStateManager#getTxList - @returns {array} - a list of txMetas who matches the search params - */ - getTxsByMetaData (key, value, txList = this.getTxList()) { + * @param {string} key - the key to check + * @param {any} value - the value your looking for can also be a function that returns a bool + * @param {Array} [txList=this.getTxList()] - the list to search. default is the txList + * from txStateManager#getTxList + * @returns {Array} a list of txMetas who matches the search params + */ + getTxsByMetaData(key, value, txList = this.getTxList()) { const filter = typeof value === 'function' ? value : (v) => v === value return txList.filter((txMeta) => { @@ -349,82 +354,81 @@ export default class TransactionStateManager extends EventEmitter { // get::set status /** - @param {number} txId - the txMeta Id - @returns {string} - the status of the tx. - */ - getTxStatus (txId) { + * @param {number} txId - the txMeta Id + * @returns {string} the status of the tx. + */ + getTxStatus(txId) { const txMeta = this.getTx(txId) return txMeta.status } /** - should update the status of the tx to 'rejected'. - @param {number} txId - the txMeta Id - */ - setTxStatusRejected (txId) { + * Update the status of the tx to 'rejected'. + * @param {number} txId - the txMeta Id + */ + setTxStatusRejected(txId) { this._setTxStatus(txId, 'rejected') this._removeTx(txId) } /** - should update the status of the tx to 'unapproved'. - @param {number} txId - the txMeta Id - */ - setTxStatusUnapproved (txId) { - this._setTxStatus(txId, 'unapproved') + * Update the status of the tx to 'unapproved'. + * @param {number} txId - the txMeta Id + */ + setTxStatusUnapproved(txId) { + this._setTxStatus(txId, TRANSACTION_STATUSES.UNAPPROVED) } /** - should update the status of the tx to 'approved'. - @param {number} txId - the txMeta Id - */ - setTxStatusApproved (txId) { - this._setTxStatus(txId, 'approved') + * Update the status of the tx to 'approved'. + * @param {number} txId - the txMeta Id + */ + setTxStatusApproved(txId) { + this._setTxStatus(txId, TRANSACTION_STATUSES.APPROVED) } /** - should update the status of the tx to 'signed'. - @param {number} txId - the txMeta Id - */ - setTxStatusSigned (txId) { - this._setTxStatus(txId, 'signed') + * Update the status of the tx to 'signed'. + * @param {number} txId - the txMeta Id + */ + setTxStatusSigned(txId) { + this._setTxStatus(txId, TRANSACTION_STATUSES.SIGNED) } /** - should update the status of the tx to 'submitted'. - and add a time stamp for when it was called - @param {number} txId - the txMeta Id - */ - setTxStatusSubmitted (txId) { + * Update the status of the tx to 'submitted' and add a time stamp + * for when it was called + * @param {number} txId - the txMeta Id + */ + setTxStatusSubmitted(txId) { const txMeta = this.getTx(txId) - txMeta.submittedTime = (new Date()).getTime() + txMeta.submittedTime = new Date().getTime() this.updateTx(txMeta, 'txStateManager - add submitted time stamp') - this._setTxStatus(txId, 'submitted') + this._setTxStatus(txId, TRANSACTION_STATUSES.SUBMITTED) } /** - should update the status of the tx to 'confirmed'. - @param {number} txId - the txMeta Id - */ - setTxStatusConfirmed (txId) { - this._setTxStatus(txId, 'confirmed') + * Update the status of the tx to 'confirmed'. + * @param {number} txId - the txMeta Id + */ + setTxStatusConfirmed(txId) { + this._setTxStatus(txId, TRANSACTION_STATUSES.CONFIRMED) } /** - should update the status of the tx to 'dropped'. - @param {number} txId - the txMeta Id - */ - setTxStatusDropped (txId) { - this._setTxStatus(txId, 'dropped') + * Update the status of the tx to 'dropped'. + * @param {number} txId - the txMeta Id + */ + setTxStatusDropped(txId) { + this._setTxStatus(txId, TRANSACTION_STATUSES.DROPPED) } /** - should update the status of the tx to 'failed'. - and put the error on the txMeta - @param {number} txId - the txMeta Id - @param {erroObject} err - error object - */ - setTxStatusFailed (txId, err) { + * Updates the status of the tx to 'failed' and put the error on the txMeta + * @param {number} txId - the txMeta Id + * @param {erroObject} err - error object + */ + setTxStatusFailed(txId, err) { const error = err || new Error('Internal metamask failure') const txMeta = this.getTx(txId) @@ -434,48 +438,45 @@ export default class TransactionStateManager extends EventEmitter { stack: error.stack, } this.updateTx(txMeta, 'transactions:tx-state-manager#fail - add error') - this._setTxStatus(txId, 'failed') + this._setTxStatus(txId, TRANSACTION_STATUSES.FAILED) } /** - Removes transaction from the given address for the current network - from the txList - @param {string} address - hex string of the from address on the txParams to remove - */ - wipeTransactions (address) { + * Removes transaction from the given address for the current network + * from the txList + * @param {string} address - hex string of the from address on the txParams + * to remove + */ + wipeTransactions(address) { // network only tx const txs = this.getFullTxList() const network = this.getNetwork() // Filter out the ones from the current account and network - const otherAccountTxs = txs.filter((txMeta) => !(txMeta.txParams.from === address && txMeta.metamaskNetworkId === network)) + const otherAccountTxs = txs.filter( + (txMeta) => + !( + txMeta.txParams.from === address && + txMeta.metamaskNetworkId === network + ), + ) // Update state this._saveTxList(otherAccountTxs) } + // // PRIVATE METHODS // - // STATUS METHODS - // statuses: - // - `'unapproved'` the user has not responded - // - `'rejected'` the user has responded no! - // - `'approved'` the user has approved the tx - // - `'signed'` the tx is signed - // - `'submitted'` the tx is sent to a server - // - `'confirmed'` the tx has been included in a block. - // - `'failed'` the tx failed for some reason, included on tx data. - // - `'dropped'` the tx nonce was already used - /** - @param {number} txId - the txMeta Id - @param {string} status - the status to set on the txMeta - @emits tx:status-update - passes txId and status - @emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta - @emits update:badge - */ - _setTxStatus (txId, status) { + * @param {number} txId - the txMeta Id + * @param {TransactionStatuses[keyof TransactionStatuses]} status - the status to set on the txMeta + * @emits tx:status-update - passes txId and status + * @emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta + * @emits update:badge + */ + _setTxStatus(txId, status) { const txMeta = this.getTx(txId) if (!txMeta) { @@ -487,7 +488,13 @@ export default class TransactionStateManager extends EventEmitter { this.updateTx(txMeta, `txStateManager: setting status to ${status}`) this.emit(`${txMeta.id}:${status}`, txId) this.emit(`tx:status-update`, txId, status) - if (['submitted', 'rejected', 'failed'].includes(status)) { + if ( + [ + TRANSACTION_STATUSES.SUBMITTED, + TRANSACTION_STATUSES.REJECTED, + TRANSACTION_STATUSES.FAILED, + ].includes(status) + ) { this.emit(`${txMeta.id}:finished`, txMeta) } this.emit('update:badge') @@ -497,15 +504,14 @@ export default class TransactionStateManager extends EventEmitter { } /** - Saves the new/updated txList. - @param {array} transactions - the list of transactions to save - */ - // Function is intended only for internal use - _saveTxList (transactions) { + * Saves the new/updated txList. Intended only for internal use + * @param {Array} transactions - the list of transactions to save + */ + _saveTxList(transactions) { this.store.updateState({ transactions }) } - _removeTx (txId) { + _removeTx(txId) { const transactionList = this.getFullTxList() this._saveTxList(transactionList.filter((txMeta) => txMeta.id !== txId)) } @@ -513,10 +519,11 @@ export default class TransactionStateManager extends EventEmitter { /** * Filters out the unapproved transactions */ - - clearUnapprovedTxs () { + clearUnapprovedTxs() { const transactions = this.getFullTxList() - const nonUnapprovedTxs = transactions.filter((tx) => tx.status !== 'unapproved') + const nonUnapprovedTxs = transactions.filter( + (tx) => tx.status !== TRANSACTION_STATUSES.UNAPPROVED, + ) this._saveTxList(nonUnapprovedTxs) } } diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js index 5577907fd..b5b498694 100644 --- a/app/scripts/first-time-state.js +++ b/app/scripts/first-time-state.js @@ -1,4 +1,3 @@ - /** * @typedef {Object} FirstTimeState * @property {Object} config Initial configuration parameters diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 546f0a362..8982a93f2 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -60,13 +60,13 @@ initProvider({ // TODO:deprecate:2020 // Setup web3 -if (typeof window.web3 !== 'undefined') { - throw new Error(`MetaMask detected another web3. +if (typeof window.web3 === 'undefined') { + // proxy web3, assign to window, and set up site auto reload + setupWeb3(log) +} else { + log.warn(`MetaMask detected another web3. MetaMask will not work reliably with another web3 extension. This usually happens if you have two MetaMasks installed, or MetaMask and another web3 extension. Please remove one and try again.`) } - -// proxy web3, assign to window, and set up site auto reload -setupWeb3(log) diff --git a/app/scripts/lib/ComposableObservableStore.js b/app/scripts/lib/ComposableObservableStore.js index ef8e91f22..a943b31b1 100644 --- a/app/scripts/lib/ComposableObservableStore.js +++ b/app/scripts/lib/ComposableObservableStore.js @@ -5,14 +5,13 @@ import ObservableStore from 'obs-store' * structure of child stores based on configuration */ export default class ComposableObservableStore extends ObservableStore { - /** * Create a new store * * @param {Object} [initState] - The initial store state * @param {Object} [config] - Map of internal state keys to child stores */ - constructor (initState, config) { + constructor(initState, config) { super(initState) this.updateStructure(config) } @@ -22,7 +21,7 @@ export default class ComposableObservableStore extends ObservableStore { * * @param {Object} [config] - Map of internal state keys to child stores */ - updateStructure (config) { + updateStructure(config) { this.config = config this.removeAllListeners() for (const key in config) { @@ -38,14 +37,16 @@ export default class ComposableObservableStore extends ObservableStore { * Merges all child store state into a single object rather than * returning an object keyed by child store class name * - * @returns {Object} - Object containing merged child store state + * @returns {Object} Object containing merged child store state */ - getFlatState () { + getFlatState() { let flatState = {} for (const key in this.config) { if (Object.prototype.hasOwnProperty.call(this.config, key)) { const controller = this.config[key] - const state = controller.getState ? controller.getState() : controller.state + const state = controller.getState + ? controller.getState() + : controller.state flatState = { ...flatState, ...state } } } diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index bb5563606..a22d56760 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -14,7 +14,12 @@ import log from 'loglevel' import pify from 'pify' import Web3 from 'web3' import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi' -import { MAINNET_CHAIN_ID, RINKEBY_CHAIN_ID, ROPSTEN_CHAIN_ID, KOVAN_CHAIN_ID } from '../controllers/network/enums' +import { + MAINNET_CHAIN_ID, + RINKEBY_CHAIN_ID, + ROPSTEN_CHAIN_ID, + KOVAN_CHAIN_ID, +} from '../controllers/network/enums' import { SINGLE_CALL_BALANCES_ADDRESS, @@ -42,14 +47,13 @@ import { bnToHex } from './util' * */ export default class AccountTracker { - /** * @param {Object} opts - Options for initializing the controller * @param {Object} opts.provider - An EIP-1193 provider instance that uses the current global network * @param {Object} opts.blockTracker - A block tracker, which emits events for each new block * @param {Function} opts.getCurrentChainId - A function that returns the `chainId` for the current global network */ - constructor (opts = {}) { + constructor(opts = {}) { const initState = { accounts: {}, currentBlockGasLimit: '', @@ -71,7 +75,7 @@ export default class AccountTracker { this.web3 = new Web3(this._provider) } - start () { + start() { // remove first to avoid double add this._blockTracker.removeListener('latest', this._updateForBlock) // add listener @@ -80,7 +84,7 @@ export default class AccountTracker { this._updateAccounts() } - stop () { + stop() { // remove listener this._blockTracker.removeListener('latest', this._updateForBlock) } @@ -92,11 +96,11 @@ export default class AccountTracker { * Once this AccountTracker's accounts are up to date with those referenced by the passed addresses, each * of these accounts are given an updated balance via EthQuery. * - * @param {array} address - The array of hex addresses for accounts with which this AccountTracker's accounts should be + * @param {Array} address - The array of hex addresses for accounts with which this AccountTracker's accounts should be * in sync * */ - syncWithAddresses (addresses) { + syncWithAddresses(addresses) { const { accounts } = this.store.getState() const locals = Object.keys(accounts) @@ -122,10 +126,10 @@ export default class AccountTracker { * Adds new addresses to track the balances of * given a balance as long this._currentBlockNumber is defined. * - * @param {array} addresses - An array of hex addresses of new accounts to track + * @param {Array} addresses - An array of hex addresses of new accounts to track * */ - addAccounts (addresses) { + addAccounts(addresses) { const { accounts } = this.store.getState() // add initial state for addresses addresses.forEach((address) => { @@ -143,10 +147,10 @@ export default class AccountTracker { /** * Removes accounts from being tracked * - * @param {array} an - array of hex addresses to stop tracking + * @param {Array} an - array of hex addresses to stop tracking * */ - removeAccount (addresses) { + removeAccount(addresses) { const { accounts } = this.store.getState() // remove each state object addresses.forEach((address) => { @@ -160,7 +164,7 @@ export default class AccountTracker { * Removes all addresses and associated balances */ - clearAccounts () { + clearAccounts() { this.store.updateState({ accounts: {} }) } @@ -173,7 +177,7 @@ export default class AccountTracker { * @fires 'block' The updated state, if all account updates are successful * */ - async _updateForBlock (blockNumber) { + async _updateForBlock(blockNumber) { this._currentBlockNumber = blockNumber // block gasLimit polling shouldn't be in account-tracker shouldn't be here... @@ -195,29 +199,41 @@ export default class AccountTracker { * balanceChecker is deployed on main eth (test)nets and requires a single call * for all other networks, calls this._updateAccount for each account in this.store * - * @returns {Promise} - after all account balances updated + * @returns {Promise} after all account balances updated * */ - async _updateAccounts () { + async _updateAccounts() { const { accounts } = this.store.getState() const addresses = Object.keys(accounts) const chainId = this.getCurrentChainId() switch (chainId) { case MAINNET_CHAIN_ID: - await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS) + await this._updateAccountsViaBalanceChecker( + addresses, + SINGLE_CALL_BALANCES_ADDRESS, + ) break case RINKEBY_CHAIN_ID: - await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY) + await this._updateAccountsViaBalanceChecker( + addresses, + SINGLE_CALL_BALANCES_ADDRESS_RINKEBY, + ) break case ROPSTEN_CHAIN_ID: - await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN) + await this._updateAccountsViaBalanceChecker( + addresses, + SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN, + ) break case KOVAN_CHAIN_ID: - await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_KOVAN) + await this._updateAccountsViaBalanceChecker( + addresses, + SINGLE_CALL_BALANCES_ADDRESS_KOVAN, + ) break default: @@ -230,10 +246,10 @@ export default class AccountTracker { * * @private * @param {string} address - A hex address of a the account to be updated - * @returns {Promise} - after the account balance is updated + * @returns {Promise} after the account balance is updated * */ - async _updateAccount (address) { + async _updateAccount(address) { // query balance const balance = await this._query.getBalance(address) const result = { address, balance } @@ -252,15 +268,20 @@ export default class AccountTracker { * @param {*} addresses * @param {*} deployedContractAddress */ - async _updateAccountsViaBalanceChecker (addresses, deployedContractAddress) { + async _updateAccountsViaBalanceChecker(addresses, deployedContractAddress) { const { accounts } = this.store.getState() this.web3.setProvider(this._provider) - const ethContract = this.web3.eth.contract(SINGLE_CALL_BALANCES_ABI).at(deployedContractAddress) + const ethContract = this.web3.eth + .contract(SINGLE_CALL_BALANCES_ABI) + .at(deployedContractAddress) const ethBalance = ['0x0'] ethContract.balances(addresses, ethBalance, (error, result) => { if (error) { - log.warn(`MetaMask - Account Tracker single call balance fetch failed`, error) + log.warn( + `MetaMask - Account Tracker single call balance fetch failed`, + error, + ) Promise.all(addresses.map(this._updateAccount.bind(this))) return } @@ -271,5 +292,4 @@ export default class AccountTracker { this.store.updateState({ accounts }) }) } - } diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index 8cf4c137f..674d031fa 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -2,13 +2,13 @@ * Gives the caller a url at which the user can acquire eth, depending on the network they are in * * @param {Object} opts - Options required to determine the correct url - * @param {string} opts.network The network for which to return a url - * @param {string} opts.address The address the bought ETH should be sent to. Only relevant if network === '1'. - * @returns {string|undefined} - The url at which the user can access ETH, while in the given network. If the passed + * @param {string} opts.network - The network for which to return a url + * @param {string} opts.address - The address the bought ETH should be sent to. Only relevant if network === '1'. + * @returns {string|undefined} The url at which the user can access ETH, while in the given network. If the passed * network does not match any of the specified cases, or if no network is given, returns undefined. * */ -export default function getBuyEthUrl ({ network, address, service }) { +export default function getBuyEthUrl({ network, address, service }) { // default service by network if not specified if (!service) { // eslint-disable-next-line no-param-reassign @@ -33,7 +33,7 @@ export default function getBuyEthUrl ({ network, address, service }) { } } -function getDefaultServiceForNetwork (network) { +function getDefaultServiceForNetwork(network) { switch (network) { case '1': return 'wyre' @@ -46,6 +46,8 @@ function getDefaultServiceForNetwork (network) { case '5': return 'goerli-faucet' default: - throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`) + throw new Error( + `No default cryptocurrency exchange or faucet for networkId: "${network}"`, + ) } } diff --git a/app/scripts/lib/cleanErrorStack.js b/app/scripts/lib/cleanErrorStack.js index c68c29b9e..a7bf607ad 100644 --- a/app/scripts/lib/cleanErrorStack.js +++ b/app/scripts/lib/cleanErrorStack.js @@ -1,14 +1,14 @@ /** * Returns error without stack trace for better UI display * @param {Error} err - error - * @returns {Error} - Error with clean stack trace. + * @returns {Error} Error with clean stack trace. */ -export default function cleanErrorStack (err) { +export default function cleanErrorStack(err) { let { name } = err - name = (name === undefined) ? 'Error' : String(name) + name = name === undefined ? 'Error' : String(name) let msg = err.message - msg = (msg === undefined) ? '' : String(msg) + msg = msg === undefined ? '' : String(msg) if (name === '') { err.stack = err.message diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js index d1710d274..18a128cba 100644 --- a/app/scripts/lib/createLoggerMiddleware.js +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -5,8 +5,12 @@ import log from 'loglevel' * @param {{ origin: string }} opts - The middleware options * @returns {Function} */ -export default function createLoggerMiddleware (opts) { - return function loggerMiddleware (/** @type {any} */ req, /** @type {any} */ res, /** @type {Function} */ next) { +export default function createLoggerMiddleware(opts) { + return function loggerMiddleware( + /** @type {any} */ req, + /** @type {any} */ res, + /** @type {Function} */ next, + ) { next((/** @type {Function} */ cb) => { if (res.error) { log.error('Error in RPC response:\n', res) diff --git a/app/scripts/lib/createOnboardingMiddleware.js b/app/scripts/lib/createOnboardingMiddleware.js index d7eb020f0..cef41deb6 100644 --- a/app/scripts/lib/createOnboardingMiddleware.js +++ b/app/scripts/lib/createOnboardingMiddleware.js @@ -6,8 +6,11 @@ import extension from 'extensionizer' * @param {{ location: string, registerOnboarding: Function }} opts - The middleware options * @returns {(req: any, res: any, next: Function, end: Function) => void} */ -export default function createOnboardingMiddleware ({ location, registerOnboarding }) { - return async function originMiddleware (req, res, next, end) { +export default function createOnboardingMiddleware({ + location, + registerOnboarding, +}) { + return async function originMiddleware(req, res, next, end) { try { if (req.method !== 'wallet_registerOnboarding') { next() @@ -16,7 +19,9 @@ export default function createOnboardingMiddleware ({ location, registerOnboardi if (req.tabId && req.tabId !== extension.tabs.TAB_ID_NONE) { await registerOnboarding(location, req.tabId) } else { - log.debug(`'wallet_registerOnboarding' message from ${location} ignored due to missing tabId`) + log.debug( + `'wallet_registerOnboarding' message from ${location} ignored due to missing tabId`, + ) } res.result = true end() diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js index db10c0778..031ba84d5 100644 --- a/app/scripts/lib/createOriginMiddleware.js +++ b/app/scripts/lib/createOriginMiddleware.js @@ -3,8 +3,12 @@ * @param {{ origin: string }} opts - The middleware options * @returns {Function} */ -export default function createOriginMiddleware (opts) { - return function originMiddleware (/** @type {any} */ req, /** @type {any} */ _, /** @type {Function} */ next) { +export default function createOriginMiddleware(opts) { + return function originMiddleware( + /** @type {any} */ req, + /** @type {any} */ _, + /** @type {Function} */ next, + ) { req.origin = opts.origin next() } diff --git a/app/scripts/lib/createStreamSink.js b/app/scripts/lib/createStreamSink.js index ad0a67959..648e745dd 100644 --- a/app/scripts/lib/createStreamSink.js +++ b/app/scripts/lib/createStreamSink.js @@ -2,20 +2,18 @@ import { Writable as WritableStream } from 'readable-stream' import promiseToCallback from 'promise-to-callback' class AsyncWritableStream extends WritableStream { - - constructor (asyncWriteFn, _opts) { + constructor(asyncWriteFn, _opts) { const opts = { objectMode: true, ..._opts } super(opts) this._asyncWriteFn = asyncWriteFn } // write from incoming stream to state - _write (chunk, encoding, callback) { + _write(chunk, encoding, callback) { promiseToCallback(this._asyncWriteFn(chunk, encoding))(callback) } - } -export default function createStreamSink (asyncWriteFn, _opts) { +export default function createStreamSink(asyncWriteFn, _opts) { return new AsyncWritableStream(asyncWriteFn, _opts) } diff --git a/app/scripts/lib/createTabIdMiddleware.js b/app/scripts/lib/createTabIdMiddleware.js index bb7db79d2..9848b68c9 100644 --- a/app/scripts/lib/createTabIdMiddleware.js +++ b/app/scripts/lib/createTabIdMiddleware.js @@ -3,8 +3,12 @@ * @param {{ tabId: number }} opts - The middleware options * @returns {Function} */ -export default function createTabIdMiddleware (opts) { - return function tabIdMiddleware (/** @type {any} */ req, /** @type {any} */ _, /** @type {Function} */ next) { +export default function createTabIdMiddleware(opts) { + return function tabIdMiddleware( + /** @type {any} */ req, + /** @type {any} */ _, + /** @type {Function} */ next, + ) { req.tabId = opts.tabId next() } diff --git a/app/scripts/lib/decrypt-message-manager.js b/app/scripts/lib/decrypt-message-manager.js index a31074f12..dc73b80f8 100644 --- a/app/scripts/lib/decrypt-message-manager.js +++ b/app/scripts/lib/decrypt-message-manager.js @@ -3,10 +3,11 @@ import ObservableStore from 'obs-store' import ethUtil from 'ethereumjs-util' import { ethErrors } from 'eth-json-rpc-errors' import log from 'loglevel' +import { addHexPrefix } from './util' import createId from './random-id' import { MESSAGE_TYPE } from './enums' -const hexRe = /^[0-9A-Fa-f]+$/ug +const hexRe = /^[0-9A-Fa-f]+$/gu /** * Represents, and contains data about, an 'eth_decrypt' type decryption request. These are created when a @@ -26,7 +27,6 @@ const hexRe = /^[0-9A-Fa-f]+$/ug */ export default class DecryptMessageManager extends EventEmitter { - /** * Controller in charge of managing - storing, adding, removing, updating - DecryptMessage. * @@ -34,10 +34,10 @@ export default class DecryptMessageManager extends EventEmitter { * @property {Object} memStore The observable store where DecryptMessage are saved. * @property {Object} memStore.unapprovedDecryptMsgs A collection of all DecryptMessages in the 'unapproved' state * @property {number} memStore.unapprovedDecryptMsgCount The count of all DecryptMessages in this.memStore.unapprovedDecryptMsgs - * @property {array} messages Holds all messages that have been created by this DecryptMessageManager + * @property {Array} messages Holds all messages that have been created by this DecryptMessageManager * */ - constructor () { + constructor() { super() this.memStore = new ObservableStore({ unapprovedDecryptMsgs: {}, @@ -52,7 +52,7 @@ export default class DecryptMessageManager extends EventEmitter { * @returns {number} The number of 'unapproved' DecryptMessages in this.messages * */ - get unapprovedDecryptMsgCount () { + get unapprovedDecryptMsgCount() { return Object.keys(this.getUnapprovedMsgs()).length } @@ -63,8 +63,9 @@ export default class DecryptMessageManager extends EventEmitter { * this.messages * */ - getUnapprovedMsgs () { - return this.messages.filter((msg) => msg.status === 'unapproved') + getUnapprovedMsgs() { + return this.messages + .filter((msg) => msg.status === 'unapproved') .reduce((result, msg) => { result[msg.id] = msg return result @@ -76,12 +77,12 @@ export default class DecryptMessageManager extends EventEmitter { * the new DecryptMessage to this.messages, and to save the unapproved DecryptMessages from that list to * this.memStore. * - * @param {Object} msgParams The params for the eth_decrypt call to be made after the message is approved. - * @param {Object} req (optional) The original request object possibly containing the origin + * @param {Object} msgParams - The params for the eth_decrypt call to be made after the message is approved. + * @param {Object} [req] - The original request object possibly containing the origin * @returns {Promise} The raw decrypted message contents * */ - addUnapprovedMessageAsync (msgParams, req) { + addUnapprovedMessageAsync(msgParams, req) { return new Promise((resolve, reject) => { if (!msgParams.from) { reject(new Error('MetaMask Decryption: from field is required.')) @@ -94,13 +95,23 @@ export default class DecryptMessageManager extends EventEmitter { resolve(data.rawData) return case 'rejected': - reject(ethErrors.provider.userRejectedRequest('MetaMask Decryption: User denied message decryption.')) + reject( + ethErrors.provider.userRejectedRequest( + 'MetaMask Decryption: User denied message decryption.', + ), + ) return case 'errored': reject(new Error('This message cannot be decrypted')) return default: - reject(new Error(`MetaMask Decryption: Unknown problem: ${JSON.stringify(msgParams)}`)) + reject( + new Error( + `MetaMask Decryption: Unknown problem: ${JSON.stringify( + msgParams, + )}`, + ), + ) } }) }) @@ -111,20 +122,24 @@ export default class DecryptMessageManager extends EventEmitter { * the new DecryptMessage to this.messages, and to save the unapproved DecryptMessages from that list to * this.memStore. * - * @param {Object} msgParams The params for the eth_decryptMsg call to be made after the message is approved. - * @param {Object} req (optional) The original request object possibly containing the origin + * @param {Object} msgParams - The params for the eth_decryptMsg call to be made after the message is approved. + * @param {Object} [req] - The original request object possibly containing the origin * @returns {number} The id of the newly created DecryptMessage. * */ - addUnapprovedMessage (msgParams, req) { - log.debug(`DecryptMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) + addUnapprovedMessage(msgParams, req) { + log.debug( + `DecryptMessageManager addUnapprovedMessage: ${JSON.stringify( + msgParams, + )}`, + ) // add origin from request if (req) { msgParams.origin = req.origin } msgParams.data = this.normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data - const time = (new Date()).getTime() + const time = new Date().getTime() const msgId = createId() const msgData = { id: msgId, @@ -147,7 +162,7 @@ export default class DecryptMessageManager extends EventEmitter { * @param {Message} msg The DecryptMessage to add to this.messages * */ - addMsg (msg) { + addMsg(msg) { this.messages.push(msg) this._saveMsgList() } @@ -160,7 +175,7 @@ export default class DecryptMessageManager extends EventEmitter { * if no DecryptMessage has that id. * */ - getMsg (msgId) { + getMsg(msgId) { return this.messages.find((msg) => msg.id === msgId) } @@ -173,7 +188,7 @@ export default class DecryptMessageManager extends EventEmitter { * @returns {Promise} Promises the msgParams object with metamaskId removed. * */ - approveMessage (msgParams) { + approveMessage(msgParams) { this.setMsgStatusApproved(msgParams.metamaskId) return this.prepMsgForDecryption(msgParams) } @@ -184,7 +199,7 @@ export default class DecryptMessageManager extends EventEmitter { * @param {number} msgId The id of the DecryptMessage to approve. * */ - setMsgStatusApproved (msgId) { + setMsgStatusApproved(msgId) { this._setMsgStatus(msgId, 'approved') } @@ -196,7 +211,7 @@ export default class DecryptMessageManager extends EventEmitter { * @param {buffer} rawData The raw data of the message request * */ - setMsgStatusDecrypted (msgId, rawData) { + setMsgStatusDecrypted(msgId, rawData) { const msg = this.getMsg(msgId) msg.rawData = rawData this._updateMsg(msg) @@ -210,7 +225,7 @@ export default class DecryptMessageManager extends EventEmitter { * @returns {Promise} Promises the msgParams with the metamaskId property removed * */ - prepMsgForDecryption (msgParams) { + prepMsgForDecryption(msgParams) { delete msgParams.metamaskId return Promise.resolve(msgParams) } @@ -221,7 +236,7 @@ export default class DecryptMessageManager extends EventEmitter { * @param {number} msgId The id of the DecryptMessage to reject. * */ - rejectMsg (msgId) { + rejectMsg(msgId) { this._setMsgStatus(msgId, 'rejected') } @@ -231,7 +246,7 @@ export default class DecryptMessageManager extends EventEmitter { * @param {number} msgId The id of the TypedMessage to error * */ - errorMessage (msgId, error) { + errorMessage(msgId, error) { const msg = this.getMsg(msgId) msg.error = error this._updateMsg(msg) @@ -251,15 +266,21 @@ export default class DecryptMessageManager extends EventEmitter { * with the DecryptMessage * */ - _setMsgStatus (msgId, status) { + _setMsgStatus(msgId, status) { const msg = this.getMsg(msgId) if (!msg) { - throw new Error(`DecryptMessageManager - Message not found for id: "${msgId}".`) + throw new Error( + `DecryptMessageManager - Message not found for id: "${msgId}".`, + ) } msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) - if (status === 'rejected' || status === 'decrypted' || status === 'errored') { + if ( + status === 'rejected' || + status === 'decrypted' || + status === 'errored' + ) { this.emit(`${msgId}:finished`, msg) } } @@ -269,11 +290,11 @@ export default class DecryptMessageManager extends EventEmitter { * unapprovedDecryptMsgs index to storage via this._saveMsgList * * @private - * @param {msg} DecryptMessage A DecryptMessage that will replace an existing DecryptMessage (with the same + * @param {DecryptMessage} msg - A DecryptMessage that will replace an existing DecryptMessage (with the same * id) in this.messages * */ - _updateMsg (msg) { + _updateMsg(msg) { const index = this.messages.findIndex((message) => message.id === msg.id) if (index !== -1) { this.messages[index] = msg @@ -288,10 +309,13 @@ export default class DecryptMessageManager extends EventEmitter { * @fires 'updateBadge' * */ - _saveMsgList () { + _saveMsgList() { const unapprovedDecryptMsgs = this.getUnapprovedMsgs() const unapprovedDecryptMsgCount = Object.keys(unapprovedDecryptMsgs).length - this.memStore.updateState({ unapprovedDecryptMsgs, unapprovedDecryptMsgCount }) + this.memStore.updateState({ + unapprovedDecryptMsgs, + unapprovedDecryptMsgCount, + }) this.emit('updateBadge') } @@ -302,11 +326,11 @@ export default class DecryptMessageManager extends EventEmitter { * @returns {string} A hex string conversion of the buffer data * */ - normalizeMsgData (data) { + normalizeMsgData(data) { try { const stripped = ethUtil.stripHexPrefix(data) if (stripped.match(hexRe)) { - return ethUtil.addHexPrefix(stripped) + return addHexPrefix(stripped) } } catch (e) { log.debug(`Message was not hex encoded, interpreting as utf8.`) @@ -314,5 +338,4 @@ export default class DecryptMessageManager extends EventEmitter { return ethUtil.bufferToHex(Buffer.from(data, 'utf8')) } - } diff --git a/app/scripts/lib/encryption-public-key-manager.js b/app/scripts/lib/encryption-public-key-manager.js index 2631dc7d3..5079c8be1 100644 --- a/app/scripts/lib/encryption-public-key-manager.js +++ b/app/scripts/lib/encryption-public-key-manager.js @@ -23,7 +23,6 @@ import { MESSAGE_TYPE } from './enums' */ export default class EncryptionPublicKeyManager extends EventEmitter { - /** * Controller in charge of managing - storing, adding, removing, updating - EncryptionPublicKey. * @@ -31,10 +30,10 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * @property {Object} memStore The observable store where EncryptionPublicKey are saved with persistance. * @property {Object} memStore.unapprovedEncryptionPublicKeyMsgs A collection of all EncryptionPublicKeys in the 'unapproved' state * @property {number} memStore.unapprovedEncryptionPublicKeyMsgCount The count of all EncryptionPublicKeys in this.memStore.unapprobedMsgs - * @property {array} messages Holds all messages that have been created by this EncryptionPublicKeyManager + * @property {Array} messages Holds all messages that have been created by this EncryptionPublicKeyManager * */ - constructor () { + constructor() { super() this.memStore = new ObservableStore({ unapprovedEncryptionPublicKeyMsgs: {}, @@ -49,7 +48,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * @returns {number} The number of 'unapproved' EncryptionPublicKeys in this.messages * */ - get unapprovedEncryptionPublicKeyMsgCount () { + get unapprovedEncryptionPublicKeyMsgCount() { return Object.keys(this.getUnapprovedMsgs()).length } @@ -60,8 +59,9 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * this.messages * */ - getUnapprovedMsgs () { - return this.messages.filter((msg) => msg.status === 'unapproved') + getUnapprovedMsgs() { + return this.messages + .filter((msg) => msg.status === 'unapproved') .reduce((result, msg) => { result[msg.id] = msg return result @@ -73,12 +73,12 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * the new EncryptionPublicKey to this.messages, and to save the unapproved EncryptionPublicKeys from that list to * this.memStore. * - * @param {Object} address The param for the eth_getEncryptionPublicKey call to be made after the message is approved. - * @param {Object} req (optional) The original request object possibly containing the origin + * @param {Object} address - The param for the eth_getEncryptionPublicKey call to be made after the message is approved. + * @param {Object} [req] - The original request object possibly containing the origin * @returns {Promise} The raw public key contents * */ - addUnapprovedMessageAsync (address, req) { + addUnapprovedMessageAsync(address, req) { return new Promise((resolve, reject) => { if (!address) { reject(new Error('MetaMask Message: address field is required.')) @@ -91,10 +91,20 @@ export default class EncryptionPublicKeyManager extends EventEmitter { resolve(data.rawData) return case 'rejected': - reject(ethErrors.provider.userRejectedRequest('MetaMask EncryptionPublicKey: User denied message EncryptionPublicKey.')) + reject( + ethErrors.provider.userRejectedRequest( + 'MetaMask EncryptionPublicKey: User denied message EncryptionPublicKey.', + ), + ) return default: - reject(new Error(`MetaMask EncryptionPublicKey: Unknown problem: ${JSON.stringify(address)}`)) + reject( + new Error( + `MetaMask EncryptionPublicKey: Unknown problem: ${JSON.stringify( + address, + )}`, + ), + ) } }) }) @@ -105,15 +115,15 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * the new EncryptionPublicKey to this.messages, and to save the unapproved EncryptionPublicKeys from that list to * this.memStore. * - * @param {Object} address The param for the eth_getEncryptionPublicKey call to be made after the message is approved. - * @param {Object} _req (optional) The original request object possibly containing the origin + * @param {Object} address - The param for the eth_getEncryptionPublicKey call to be made after the message is approved. + * @param {Object} [req] - The original request object possibly containing the origin * @returns {number} The id of the newly created EncryptionPublicKey. * */ - addUnapprovedMessage (address, req) { + addUnapprovedMessage(address, req) { log.debug(`EncryptionPublicKeyManager addUnapprovedMessage: address`) // create txData obj with parameters and meta data - const time = (new Date()).getTime() + const time = new Date().getTime() const msgId = createId() const msgData = { id: msgId, @@ -141,7 +151,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * @param {Message} msg The EncryptionPublicKey to add to this.messages * */ - addMsg (msg) { + addMsg(msg) { this.messages.push(msg) this._saveMsgList() } @@ -154,7 +164,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * if no EncryptionPublicKey has that id. * */ - getMsg (msgId) { + getMsg(msgId) { return this.messages.find((msg) => msg.id === msgId) } @@ -167,7 +177,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * @returns {Promise} Promises the msgParams object with metamaskId removed. * */ - approveMessage (msgParams) { + approveMessage(msgParams) { this.setMsgStatusApproved(msgParams.metamaskId) return this.prepMsgForEncryptionPublicKey(msgParams) } @@ -178,7 +188,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * @param {number} msgId The id of the EncryptionPublicKey to approve. * */ - setMsgStatusApproved (msgId) { + setMsgStatusApproved(msgId) { this._setMsgStatus(msgId, 'approved') } @@ -190,7 +200,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * @param {buffer} rawData The raw data of the message request * */ - setMsgStatusReceived (msgId, rawData) { + setMsgStatusReceived(msgId, rawData) { const msg = this.getMsg(msgId) msg.rawData = rawData this._updateMsg(msg) @@ -204,7 +214,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * @returns {Promise} Promises the msgParams with the metamaskId property removed * */ - prepMsgForEncryptionPublicKey (msgParams) { + prepMsgForEncryptionPublicKey(msgParams) { delete msgParams.metamaskId return Promise.resolve(msgParams) } @@ -215,7 +225,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * @param {number} msgId The id of the EncryptionPublicKey to reject. * */ - rejectMsg (msgId) { + rejectMsg(msgId) { this._setMsgStatus(msgId, 'rejected') } @@ -225,7 +235,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * @param {number} msgId The id of the TypedMessage to error * */ - errorMessage (msgId, error) { + errorMessage(msgId, error) { const msg = this.getMsg(msgId) msg.error = error this._updateMsg(msg) @@ -245,10 +255,12 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * with the EncryptionPublicKey * */ - _setMsgStatus (msgId, status) { + _setMsgStatus(msgId, status) { const msg = this.getMsg(msgId) if (!msg) { - throw new Error(`EncryptionPublicKeyManager - Message not found for id: "${msgId}".`) + throw new Error( + `EncryptionPublicKeyManager - Message not found for id: "${msgId}".`, + ) } msg.status = status this._updateMsg(msg) @@ -263,11 +275,11 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * unapprovedEncryptionPublicKeyMsgs index to storage via this._saveMsgList * * @private - * @param {msg} EncryptionPublicKey A EncryptionPublicKey that will replace an existing EncryptionPublicKey (with the same + * @param {EncryptionPublicKey} msg - A EncryptionPublicKey that will replace an existing EncryptionPublicKey (with the same * id) in this.messages * */ - _updateMsg (msg) { + _updateMsg(msg) { const index = this.messages.findIndex((message) => message.id === msg.id) if (index !== -1) { this.messages[index] = msg @@ -282,10 +294,15 @@ export default class EncryptionPublicKeyManager extends EventEmitter { * @fires 'updateBadge' * */ - _saveMsgList () { + _saveMsgList() { const unapprovedEncryptionPublicKeyMsgs = this.getUnapprovedMsgs() - const unapprovedEncryptionPublicKeyMsgCount = Object.keys(unapprovedEncryptionPublicKeyMsgs).length - this.memStore.updateState({ unapprovedEncryptionPublicKeyMsgs, unapprovedEncryptionPublicKeyMsgCount }) + const unapprovedEncryptionPublicKeyMsgCount = Object.keys( + unapprovedEncryptionPublicKeyMsgs, + ).length + this.memStore.updateState({ + unapprovedEncryptionPublicKeyMsgs, + unapprovedEncryptionPublicKeyMsgCount, + }) this.emit('updateBadge') } } diff --git a/app/scripts/lib/ens-ipfs/contracts/registry.js b/app/scripts/lib/ens-ipfs/contracts/registry.js index b896eaa53..4eab80287 100644 --- a/app/scripts/lib/ens-ipfs/contracts/registry.js +++ b/app/scripts/lib/ens-ipfs/contracts/registry.js @@ -1,2 +1,109 @@ -const abi = [{ 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'resolver', 'outputs': [{ 'name': '', 'type': 'address' }], 'payable': false, 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'owner', 'outputs': [{ 'name': '', 'type': 'address' }], 'payable': false, 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'label', 'type': 'bytes32' }, { 'name': 'owner', 'type': 'address' }], 'name': 'setSubnodeOwner', 'outputs': [], 'payable': false, 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'ttl', 'type': 'uint64' }], 'name': 'setTTL', 'outputs': [], 'payable': false, 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'ttl', 'outputs': [{ 'name': '', 'type': 'uint64' }], 'payable': false, 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'resolver', 'type': 'address' }], 'name': 'setResolver', 'outputs': [], 'payable': false, 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'owner', 'type': 'address' }], 'name': 'setOwner', 'outputs': [], 'payable': false, 'type': 'function' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'owner', 'type': 'address' }], 'name': 'Transfer', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': true, 'name': 'label', 'type': 'bytes32' }, { 'indexed': false, 'name': 'owner', 'type': 'address' }], 'name': 'NewOwner', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'resolver', 'type': 'address' }], 'name': 'NewResolver', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'ttl', 'type': 'uint64' }], 'name': 'NewTTL', 'type': 'event' }] +const abi = [ + { + constant: true, + inputs: [{ name: 'node', type: 'bytes32' }], + name: 'resolver', + outputs: [{ name: '', type: 'address' }], + payable: false, + type: 'function', + }, + { + constant: true, + inputs: [{ name: 'node', type: 'bytes32' }], + name: 'owner', + outputs: [{ name: '', type: 'address' }], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'label', type: 'bytes32' }, + { name: 'owner', type: 'address' }, + ], + name: 'setSubnodeOwner', + outputs: [], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'ttl', type: 'uint64' }, + ], + name: 'setTTL', + outputs: [], + payable: false, + type: 'function', + }, + { + constant: true, + inputs: [{ name: 'node', type: 'bytes32' }], + name: 'ttl', + outputs: [{ name: '', type: 'uint64' }], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'resolver', type: 'address' }, + ], + name: 'setResolver', + outputs: [], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'owner', type: 'address' }, + ], + name: 'setOwner', + outputs: [], + payable: false, + type: 'function', + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: 'node', type: 'bytes32' }, + { indexed: false, name: 'owner', type: 'address' }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: 'node', type: 'bytes32' }, + { indexed: true, name: 'label', type: 'bytes32' }, + { indexed: false, name: 'owner', type: 'address' }, + ], + name: 'NewOwner', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: 'node', type: 'bytes32' }, + { indexed: false, name: 'resolver', type: 'address' }, + ], + name: 'NewResolver', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: 'node', type: 'bytes32' }, + { indexed: false, name: 'ttl', type: 'uint64' }, + ], + name: 'NewTTL', + type: 'event', + }, +] export default abi diff --git a/app/scripts/lib/ens-ipfs/contracts/resolver.js b/app/scripts/lib/ens-ipfs/contracts/resolver.js index 92c0c0f45..a392bcfe8 100644 --- a/app/scripts/lib/ens-ipfs/contracts/resolver.js +++ b/app/scripts/lib/ens-ipfs/contracts/resolver.js @@ -1,2 +1,236 @@ -const abi = [{ 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'hash', 'type': 'bytes32' }], 'name': 'setContent', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'content', 'outputs': [{ 'name': '', 'type': 'bytes32' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'interfaceID', 'type': 'bytes4' }], 'name': 'supportsInterface', 'outputs': [{ 'name': '', 'type': 'bool' }], 'payable': false, 'stateMutability': 'pure', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'key', 'type': 'string' }, { 'name': 'value', 'type': 'string' }], 'name': 'setText', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'contentTypes', 'type': 'uint256' }], 'name': 'ABI', 'outputs': [{ 'name': 'contentType', 'type': 'uint256' }, { 'name': 'data', 'type': 'bytes' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'x', 'type': 'bytes32' }, { 'name': 'y', 'type': 'bytes32' }], 'name': 'setPubkey', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'hash', 'type': 'bytes' }], 'name': 'setContenthash', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'addr', 'outputs': [{ 'name': '', 'type': 'address' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'key', 'type': 'string' }], 'name': 'text', 'outputs': [{ 'name': '', 'type': 'string' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'contentType', 'type': 'uint256' }, { 'name': 'data', 'type': 'bytes' }], 'name': 'setABI', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'name', 'outputs': [{ 'name': '', 'type': 'string' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'name', 'type': 'string' }], 'name': 'setName', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'contenthash', 'outputs': [{ 'name': '', 'type': 'bytes' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'pubkey', 'outputs': [{ 'name': 'x', 'type': 'bytes32' }, { 'name': 'y', 'type': 'bytes32' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'addr', 'type': 'address' }], 'name': 'setAddr', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'name': 'ensAddr', 'type': 'address' }], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'a', 'type': 'address' }], 'name': 'AddrChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'name', 'type': 'string' }], 'name': 'NameChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': true, 'name': 'contentType', 'type': 'uint256' }], 'name': 'ABIChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'x', 'type': 'bytes32' }, { 'indexed': false, 'name': 'y', 'type': 'bytes32' }], 'name': 'PubkeyChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'indexedKey', 'type': 'string' }, { 'indexed': false, 'name': 'key', 'type': 'string' }], 'name': 'TextChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'hash', 'type': 'bytes' }], 'name': 'ContenthashChanged', 'type': 'event' }] +const abi = [ + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'hash', type: 'bytes32' }, + ], + name: 'setContent', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [{ name: 'node', type: 'bytes32' }], + name: 'content', + outputs: [{ name: '', type: 'bytes32' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [{ name: 'interfaceID', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ name: '', type: 'bool' }], + payable: false, + stateMutability: 'pure', + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'key', type: 'string' }, + { name: 'value', type: 'string' }, + ], + name: 'setText', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'contentTypes', type: 'uint256' }, + ], + name: 'ABI', + outputs: [ + { name: 'contentType', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'x', type: 'bytes32' }, + { name: 'y', type: 'bytes32' }, + ], + name: 'setPubkey', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'hash', type: 'bytes' }, + ], + name: 'setContenthash', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [{ name: 'node', type: 'bytes32' }], + name: 'addr', + outputs: [{ name: '', type: 'address' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'key', type: 'string' }, + ], + name: 'text', + outputs: [{ name: '', type: 'string' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'contentType', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + ], + name: 'setABI', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [{ name: 'node', type: 'bytes32' }], + name: 'name', + outputs: [{ name: '', type: 'string' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'name', type: 'string' }, + ], + name: 'setName', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [{ name: 'node', type: 'bytes32' }], + name: 'contenthash', + outputs: [{ name: '', type: 'bytes' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [{ name: 'node', type: 'bytes32' }], + name: 'pubkey', + outputs: [ + { name: 'x', type: 'bytes32' }, + { name: 'y', type: 'bytes32' }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'node', type: 'bytes32' }, + { name: 'addr', type: 'address' }, + ], + name: 'setAddr', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'ensAddr', type: 'address' }], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: 'node', type: 'bytes32' }, + { indexed: false, name: 'a', type: 'address' }, + ], + name: 'AddrChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: 'node', type: 'bytes32' }, + { indexed: false, name: 'name', type: 'string' }, + ], + name: 'NameChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: 'node', type: 'bytes32' }, + { indexed: true, name: 'contentType', type: 'uint256' }, + ], + name: 'ABIChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: 'node', type: 'bytes32' }, + { indexed: false, name: 'x', type: 'bytes32' }, + { indexed: false, name: 'y', type: 'bytes32' }, + ], + name: 'PubkeyChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: 'node', type: 'bytes32' }, + { indexed: false, name: 'indexedKey', type: 'string' }, + { indexed: false, name: 'key', type: 'string' }, + ], + name: 'TextChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: 'node', type: 'bytes32' }, + { indexed: false, name: 'hash', type: 'bytes' }, + ], + name: 'ContenthashChanged', + type: 'event', + }, +] export default abi diff --git a/app/scripts/lib/ens-ipfs/resolver.js b/app/scripts/lib/ens-ipfs/resolver.js index cc307e617..5a4b29043 100644 --- a/app/scripts/lib/ens-ipfs/resolver.js +++ b/app/scripts/lib/ens-ipfs/resolver.js @@ -5,7 +5,7 @@ import contentHash from 'content-hash' import registryAbi from './contracts/registry' import resolverAbi from './contracts/resolver' -export default async function resolveEnsToIpfsContentId ({ provider, name }) { +export default async function resolveEnsToIpfsContentId({ provider, name }) { const eth = new Eth(provider) const hash = namehash.hash(name) const contract = new EthContract(eth) @@ -13,7 +13,9 @@ export default async function resolveEnsToIpfsContentId ({ provider, name }) { const chainId = Number.parseInt(await eth.net_version(), 10) const registryAddress = getRegistryForChainId(chainId) if (!registryAddress) { - throw new Error(`EnsIpfsResolver - no known ens-ipfs registry for chainId "${chainId}"`) + throw new Error( + `EnsIpfsResolver - no known ens-ipfs registry for chainId "${chainId}"`, + ) } const Registry = contract(registryAbi).at(registryAddress) // lookup resolver @@ -33,7 +35,9 @@ export default async function resolveEnsToIpfsContentId ({ provider, name }) { const type = contentHash.getCodec(rawContentHash) if (type === 'ipfs-ns' || type === 'ipns-ns') { - decodedContentHash = contentHash.helpers.cidV0ToV1Base32(decodedContentHash) + decodedContentHash = contentHash.helpers.cidV0ToV1Base32( + decodedContentHash, + ) } return { type, hash: decodedContentHash } @@ -43,23 +47,33 @@ export default async function resolveEnsToIpfsContentId ({ provider, name }) { const contentLookupResult = await Resolver.content(hash) const content = contentLookupResult[0] if (hexValueIsEmpty(content)) { - throw new Error(`EnsIpfsResolver - no content ID found for name "${name}"`) + throw new Error( + `EnsIpfsResolver - no content ID found for name "${name}"`, + ) } return { type: 'swarm-ns', hash: content.slice(2) } } - throw new Error(`EnsIpfsResolver - the resolver for name "${name}" is not standard, it should either supports contenthash() or content()`) + throw new Error( + `EnsIpfsResolver - the resolver for name "${name}" is not standard, it should either supports contenthash() or content()`, + ) } -function hexValueIsEmpty (value) { - return [undefined, null, '0x', '0x0', '0x0000000000000000000000000000000000000000000000000000000000000000'].includes(value) +function hexValueIsEmpty(value) { + return [ + undefined, + null, + '0x', + '0x0', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ].includes(value) } /** * Returns the registry address for the given chain ID - * @param {number} chainId the chain ID + * @param {number} chainId - the chain ID * @returns {string|null} the registry address if known, null otherwise */ -function getRegistryForChainId (chainId) { +function getRegistryForChainId(chainId) { switch (chainId) { case 1: case 3: diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js index 92968d28d..ab4cec023 100644 --- a/app/scripts/lib/ens-ipfs/setup.js +++ b/app/scripts/lib/ens-ipfs/setup.js @@ -3,21 +3,27 @@ import resolveEnsToIpfsContentId from './resolver' const supportedTopLevelDomains = ['eth'] -export default function setupEnsIpfsResolver ({ provider, getCurrentNetwork, getIpfsGateway }) { - +export default function setupEnsIpfsResolver({ + provider, + getCurrentNetwork, + getIpfsGateway, +}) { // install listener const urlPatterns = supportedTopLevelDomains.map((tld) => `*://*.${tld}/*`) - extension.webRequest.onErrorOccurred.addListener(webRequestDidFail, { urls: urlPatterns, types: ['main_frame'] }) + extension.webRequest.onErrorOccurred.addListener(webRequestDidFail, { + urls: urlPatterns, + types: ['main_frame'], + }) // return api object return { // uninstall listener - remove () { + remove() { extension.webRequest.onErrorOccurred.removeListener(webRequestDidFail) }, } - async function webRequestDidFail (details) { + async function webRequestDidFail(details) { const { tabId, url } = details // ignore requests that are not associated with tabs // only attempt ENS resolution on mainnet @@ -36,14 +42,17 @@ export default function setupEnsIpfsResolver ({ provider, getCurrentNetwork, get attemptResolve({ tabId, name, pathname, search, fragment }) } - async function attemptResolve ({ tabId, name, pathname, search, fragment }) { + async function attemptResolve({ tabId, name, pathname, search, fragment }) { const ipfsGateway = getIpfsGateway() extension.tabs.update(tabId, { url: `loading.html` }) let url = `https://app.ens.domains/name/${name}` try { const { type, hash } = await resolveEnsToIpfsContentId({ provider, name }) if (type === 'ipfs-ns' || type === 'ipns-ns') { - const resolvedUrl = `https://${hash}.${type.slice(0, 4)}.${ipfsGateway}${pathname}${search || ''}${fragment || ''}` + const resolvedUrl = `https://${hash}.${type.slice( + 0, + 4, + )}.${ipfsGateway}${pathname}${search || ''}${fragment || ''}` try { // check if ipfs gateway has result const response = await window.fetch(resolvedUrl, { method: 'HEAD' }) @@ -54,11 +63,15 @@ export default function setupEnsIpfsResolver ({ provider, getCurrentNetwork, get console.warn(err) } } else if (type === 'swarm-ns') { - url = `https://swarm-gateways.net/bzz:/${hash}${pathname}${search || ''}${fragment || ''}` + url = `https://swarm-gateways.net/bzz:/${hash}${pathname}${ + search || '' + }${fragment || ''}` } else if (type === 'onion' || type === 'onion3') { url = `http://${hash}.onion${pathname}${search || ''}${fragment || ''}` } else if (type === 'zeronet') { - url = `http://127.0.0.1:43110/${hash}${pathname}${search || ''}${fragment || ''}` + url = `http://127.0.0.1:43110/${hash}${pathname}${search || ''}${ + fragment || '' + }` } } catch (err) { console.warn(err) diff --git a/app/scripts/lib/extractEthjsErrorMessage.js b/app/scripts/lib/extractEthjsErrorMessage.js index 0dde04f06..621e22195 100644 --- a/app/scripts/lib/extractEthjsErrorMessage.js +++ b/app/scripts/lib/extractEthjsErrorMessage.js @@ -6,18 +6,20 @@ const errorLabelPrefix = 'Error: ' * is returned unchanged. * * @param {string} errorMessage - The error message to parse - * @returns {string} - Returns an error message, either the same as was passed, or the ending message portion of an isEthjsRpcError + * @returns {string} Returns an error message, either the same as was passed, or the ending message portion of an isEthjsRpcError * * @example * // returns 'Transaction Failed: replacement transaction underpriced' * extractEthjsErrorMessage(`Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced`) * -*/ -export default function extractEthjsErrorMessage (errorMessage) { + */ +export default function extractEthjsErrorMessage(errorMessage) { const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug) if (isEthjsRpcError) { const payloadAndError = errorMessage.slice(ethJsRpcSlug.length) - const originalError = payloadAndError.slice(payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length) + const originalError = payloadAndError.slice( + payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length, + ) return originalError } return errorMessage diff --git a/app/scripts/lib/fetch-with-timeout.js b/app/scripts/lib/fetch-with-timeout.js index a2f6663cc..bca8d652d 100644 --- a/app/scripts/lib/fetch-with-timeout.js +++ b/app/scripts/lib/fetch-with-timeout.js @@ -1,5 +1,5 @@ const fetchWithTimeout = ({ timeout = 120000 } = {}) => { - return async function _fetch (url, opts) { + return async function _fetch(url, opts) { const abortController = new window.AbortController() const abortSignal = abortController.signal const f = window.fetch(url, { diff --git a/app/scripts/lib/freezeGlobals.js b/app/scripts/lib/freezeGlobals.js index b17a84781..08b201bee 100644 --- a/app/scripts/lib/freezeGlobals.js +++ b/app/scripts/lib/freezeGlobals.js @@ -1,13 +1,9 @@ - /** * Freezes the Promise global and prevents its reassignment. */ import deepFreeze from 'deep-freeze-strict' -if ( - process.env.IN_TEST !== 'true' && - process.env.METAMASK_ENV !== 'test' -) { +if (process.env.IN_TEST !== 'true' && process.env.METAMASK_ENV !== 'test') { freeze(global, 'Promise') } @@ -24,10 +20,10 @@ if ( * @param {any} [value] - The value to freeze, if different from the existing value on the target. * @param {boolean} [enumerable=true] - If given a value, whether the property is enumerable. */ -function freeze (target, key, value, enumerable = true) { - +function freeze(target, key, value, enumerable = true) { const opts = { - configurable: false, writable: false, + configurable: false, + writable: false, } if (value === undefined) { diff --git a/app/scripts/lib/get-first-preferred-lang-code.js b/app/scripts/lib/get-first-preferred-lang-code.js index d4bb61a8f..d37ac6d0d 100644 --- a/app/scripts/lib/get-first-preferred-lang-code.js +++ b/app/scripts/lib/get-first-preferred-lang-code.js @@ -2,16 +2,16 @@ import extension from 'extensionizer' import promisify from 'pify' import allLocales from '../../_locales/index.json' -const getPreferredLocales = extension.i18n ? promisify( - extension.i18n.getAcceptLanguages, - { errorFirst: false }, -) : async () => [] +const getPreferredLocales = extension.i18n + ? promisify(extension.i18n.getAcceptLanguages, { errorFirst: false }) + : async () => [] // mapping some browsers return hyphen instead underscore in locale codes (e.g. zh_TW -> zh-tw) const existingLocaleCodes = {} allLocales.forEach((locale) => { if (locale && locale.code) { - existingLocaleCodes[locale.code.toLowerCase().replace('_', '-')] = locale.code + existingLocaleCodes[locale.code.toLowerCase().replace('_', '-')] = + locale.code } }) @@ -19,10 +19,10 @@ allLocales.forEach((locale) => { * Returns a preferred language code, based on settings within the user's browser. If we have no translations for the * users preferred locales, 'en' is returned. * - * @returns {Promise} - Promises a locale code, either one from the user's preferred list that we have a translation for, or 'en' + * @returns {Promise} Promises a locale code, either one from the user's preferred list that we have a translation for, or 'en' * */ -export default async function getFirstPreferredLangCode () { +export default async function getFirstPreferredLangCode() { let userPreferredLocaleCodes try { @@ -40,7 +40,9 @@ export default async function getFirstPreferredLangCode () { const firstPreferredLangCode = userPreferredLocaleCodes .map((code) => code.toLowerCase().replace('_', '-')) - .find((code) => Object.prototype.hasOwnProperty.call(existingLocaleCodes, code)) + .find((code) => + Object.prototype.hasOwnProperty.call(existingLocaleCodes, code), + ) return existingLocaleCodes[firstPreferredLangCode] || 'en' } diff --git a/app/scripts/lib/getObjStructure.js b/app/scripts/lib/getObjStructure.js index ef9e3760f..b96351912 100644 --- a/app/scripts/lib/getObjStructure.js +++ b/app/scripts/lib/getObjStructure.js @@ -17,11 +17,11 @@ import { cloneDeep } from 'lodash' * type. * * @param {Object} obj - The object for which a 'structure' will be returned. Usually a plain object and not a class. - * @returns {Object} - The "mapped" version of a deep clone of the passed object, with each non-object property value + * @returns {Object} The "mapped" version of a deep clone of the passed object, with each non-object property value * replaced with the javascript type of that value. * */ -export default function getObjStructure (obj) { +export default function getObjStructure(obj) { const structure = cloneDeep(obj) return deepMap(structure, (value) => { return value === null ? 'null' : typeof value @@ -34,9 +34,9 @@ export default function getObjStructure (obj) { * * @param {Object} target - The object to modify * @param {Function} visit - The modifier to apply to each non-object property value - * @returns {Object} - The modified object + * @returns {Object} The modified object */ -function deepMap (target = {}, visit) { +function deepMap(target = {}, visit) { Object.entries(target).forEach(([key, value]) => { if (typeof value === 'object' && value !== null) { target[key] = deepMap(value, visit) diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js index e7f05781c..aa12b50f8 100644 --- a/app/scripts/lib/local-store.js +++ b/app/scripts/lib/local-store.js @@ -6,11 +6,10 @@ import { checkForError } from './util' * A wrapper around the extension's storage local API */ export default class ExtensionStore { - /** * @constructor */ - constructor () { + constructor() { this.isSupported = Boolean(extension.storage.local) if (!this.isSupported) { log.error('Storage local API not available.') @@ -21,7 +20,7 @@ export default class ExtensionStore { * Returns all of the keys currently saved * @returns {Promise<*>} */ - async get () { + async get() { if (!this.isSupported) { return undefined } @@ -39,16 +38,16 @@ export default class ExtensionStore { * @param {Object} state - The state to set * @returns {Promise} */ - async set (state) { + async set(state) { return this._set(state) } /** * Returns all of the keys currently saved * @private - * @returns {Object} - the key-value map from local storage + * @returns {Object} the key-value map from local storage */ - _get () { + _get() { const { local } = extension.storage return new Promise((resolve, reject) => { local.get(null, (/** @type {any} */ result) => { @@ -68,7 +67,7 @@ export default class ExtensionStore { * @returns {Promise} * @private */ - _set (obj) { + _set(obj) { const { local } = extension.storage return new Promise((resolve, reject) => { local.set(obj, () => { @@ -88,6 +87,6 @@ export default class ExtensionStore { * @param {Object} obj - The object to check * @returns {boolean} */ -function isEmpty (obj) { +function isEmpty(obj) { return Object.keys(obj).length === 0 } diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index 280003c40..6f6a208e2 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -24,19 +24,17 @@ import { MESSAGE_TYPE } from './enums' */ export default class MessageManager extends EventEmitter { - /** * Controller in charge of managing - storing, adding, removing, updating - Messages. * * @typedef {Object} MessageManager - * @param {Object} opts @deprecated * @property {Object} memStore The observable store where Messages are saved. * @property {Object} memStore.unapprovedMsgs A collection of all Messages in the 'unapproved' state * @property {number} memStore.unapprovedMsgCount The count of all Messages in this.memStore.unapprovedMsgs - * @property {array} messages Holds all messages that have been created by this MessageManager + * @property {Array} messages Holds all messages that have been created by this MessageManager * */ - constructor () { + constructor() { super() this.memStore = new ObservableStore({ unapprovedMsgs: {}, @@ -48,21 +46,22 @@ export default class MessageManager extends EventEmitter { /** * A getter for the number of 'unapproved' Messages in this.messages * - * @returns {number} - The number of 'unapproved' Messages in this.messages + * @returns {number} The number of 'unapproved' Messages in this.messages * */ - get unapprovedMsgCount () { + get unapprovedMsgCount() { return Object.keys(this.getUnapprovedMsgs()).length } /** * A getter for the 'unapproved' Messages in this.messages * - * @returns {Object} - An index of Message ids to Messages, for all 'unapproved' Messages in this.messages + * @returns {Object} An index of Message ids to Messages, for all 'unapproved' Messages in this.messages * */ - getUnapprovedMsgs () { - return this.messages.filter((msg) => msg.status === 'unapproved') + getUnapprovedMsgs() { + return this.messages + .filter((msg) => msg.status === 'unapproved') .reduce((result, msg) => { result[msg.id] = msg return result @@ -74,11 +73,11 @@ export default class MessageManager extends EventEmitter { * new Message to this.messages, and to save the unapproved Messages from that list to this.memStore. * * @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved. - * @param {Object} req (optional) The original request object possibly containing the origin - * @returns {promise} - after signature has been + * @param {Object} [req] - The original request object possibly containing the origin + * @returns {promise} after signature has been * */ - addUnapprovedMessageAsync (msgParams, req) { + addUnapprovedMessageAsync(msgParams, req) { return new Promise((resolve, reject) => { const msgId = this.addUnapprovedMessage(msgParams, req) // await finished @@ -87,9 +86,19 @@ export default class MessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject( + ethErrors.provider.userRejectedRequest( + 'MetaMask Message Signature: User denied message signature.', + ), + ) default: - return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + return reject( + new Error( + `MetaMask Message Signature: Unknown problem: ${JSON.stringify( + msgParams, + )}`, + ), + ) } }) }) @@ -100,18 +109,18 @@ export default class MessageManager extends EventEmitter { * new Message to this.messages, and to save the unapproved Messages from that list to this.memStore. * * @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved. - * @param {Object} req (optional) The original request object where the origin may be specified - * @returns {number} - The id of the newly created message. + * @param {Object} [req] - The original request object where the origin may be specified + * @returns {number} The id of the newly created message. * */ - addUnapprovedMessage (msgParams, req) { + addUnapprovedMessage(msgParams, req) { // add origin from request if (req) { msgParams.origin = req.origin } msgParams.data = normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data - const time = (new Date()).getTime() + const time = new Date().getTime() const msgId = createId() const msgData = { id: msgId, @@ -134,7 +143,7 @@ export default class MessageManager extends EventEmitter { * @param {Message} msg - The Message to add to this.messages * */ - addMsg (msg) { + addMsg(msg) { this.messages.push(msg) this._saveMsgList() } @@ -143,10 +152,10 @@ export default class MessageManager extends EventEmitter { * Returns a specified Message. * * @param {number} msgId - The id of the Message to get - * @returns {Message|undefined} - The Message with the id that matches the passed msgId, or undefined if no Message has that id. + * @returns {Message|undefined} The Message with the id that matches the passed msgId, or undefined if no Message has that id. * */ - getMsg (msgId) { + getMsg(msgId) { return this.messages.find((msg) => msg.id === msgId) } @@ -156,10 +165,10 @@ export default class MessageManager extends EventEmitter { * * @param {Object} msgParams - The msgParams to be used when eth_sign is called, plus data added by MetaMask. * @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask. - * @returns {Promise} - Promises the msgParams object with metamaskId removed. + * @returns {Promise} Promises the msgParams object with metamaskId removed. * */ - approveMessage (msgParams) { + approveMessage(msgParams) { this.setMsgStatusApproved(msgParams.metamaskId) return this.prepMsgForSigning(msgParams) } @@ -170,7 +179,7 @@ export default class MessageManager extends EventEmitter { * @param {number} msgId - The id of the Message to approve. * */ - setMsgStatusApproved (msgId) { + setMsgStatusApproved(msgId) { this._setMsgStatus(msgId, 'approved') } @@ -182,7 +191,7 @@ export default class MessageManager extends EventEmitter { * @param {buffer} rawSig - The raw data of the signature request * */ - setMsgStatusSigned (msgId, rawSig) { + setMsgStatusSigned(msgId, rawSig) { const msg = this.getMsg(msgId) msg.rawSig = rawSig this._updateMsg(msg) @@ -193,10 +202,10 @@ export default class MessageManager extends EventEmitter { * Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams * * @param {Object} msgParams - The msgParams to modify - * @returns {Promise} - Promises the msgParams with the metamaskId property removed + * @returns {Promise} Promises the msgParams with the metamaskId property removed * */ - prepMsgForSigning (msgParams) { + prepMsgForSigning(msgParams) { delete msgParams.metamaskId return Promise.resolve(msgParams) } @@ -207,7 +216,7 @@ export default class MessageManager extends EventEmitter { * @param {number} msgId - The id of the Message to reject. * */ - rejectMsg (msgId) { + rejectMsg(msgId) { this._setMsgStatus(msgId, 'rejected') } @@ -223,7 +232,7 @@ export default class MessageManager extends EventEmitter { * @fires If status is 'rejected' or 'signed', an event with a name equal to `${msgId}:finished` is fired along with the message * */ - _setMsgStatus (msgId, status) { + _setMsgStatus(msgId, status) { const msg = this.getMsg(msgId) if (!msg) { throw new Error(`MessageManager - Message not found for id: "${msgId}".`) @@ -244,7 +253,7 @@ export default class MessageManager extends EventEmitter { * @param {msg} Message - A Message that will replace an existing Message (with the same id) in this.messages * */ - _updateMsg (msg) { + _updateMsg(msg) { const index = this.messages.findIndex((message) => message.id === msg.id) if (index !== -1) { this.messages[index] = msg @@ -259,23 +268,22 @@ export default class MessageManager extends EventEmitter { * @fires 'updateBadge' * */ - _saveMsgList () { + _saveMsgList() { const unapprovedMsgs = this.getUnapprovedMsgs() const unapprovedMsgCount = Object.keys(unapprovedMsgs).length this.memStore.updateState({ unapprovedMsgs, unapprovedMsgCount }) this.emit('updateBadge') } - } /** * A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex. * * @param {any} data - The buffer data to convert to a hex - * @returns {string} - A hex string conversion of the buffer data + * @returns {string} A hex string conversion of the buffer data * */ -function normalizeMsgData (data) { +function normalizeMsgData(data) { if (data.slice(0, 2) === '0x') { // data is already hex return data diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index 5c2f6185f..711acb7c5 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -1,24 +1,23 @@ import EventEmitter from 'events' /** - * @typedef {object} Migration + * @typedef {Object} Migration * @property {number} version - The migration version * @property {Function} migrate - Returns a promise of the migrated data */ /** - * @typedef {object} MigratorOptions + * @typedef {Object} MigratorOptions * @property {Array} [migrations] - The list of migrations to apply * @property {number} [defaultVersion] - The version to use in the initial state */ export default class Migrator extends EventEmitter { - /** * @constructor * @param {MigratorOptions} opts */ - constructor (opts = {}) { + constructor(opts = {}) { super() const migrations = opts.migrations || [] // sort migrations by version @@ -26,11 +25,12 @@ export default class Migrator extends EventEmitter { // grab migration with highest version const lastMigration = this.migrations.slice(-1)[0] // use specified defaultVersion or highest migration version - this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0 + this.defaultVersion = + opts.defaultVersion || (lastMigration && lastMigration.version) || 0 } // run all pending migrations on meta in place - async migrateData (versionedData = this.generateInitialState()) { + async migrateData(versionedData = this.generateInitialState()) { // get all migrations that have not yet been run const pendingMigrations = this.migrations.filter(migrationIsPending) @@ -42,8 +42,13 @@ export default class Migrator extends EventEmitter { if (!migratedData.data) { throw new Error('Migrator - migration returned empty data') } - if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) { - throw new Error('Migrator - Migration did not update version number correctly') + if ( + migratedData.version !== undefined && + migratedData.meta.version !== migration.version + ) { + throw new Error( + 'Migrator - Migration did not update version number correctly', + ) } // accept the migration as good // eslint-disable-next-line no-param-reassign @@ -69,7 +74,7 @@ export default class Migrator extends EventEmitter { * @param {Migration} migration * @returns {boolean} */ - function migrationIsPending (migration) { + function migrationIsPending(migration) { return migration.version > versionedData.meta.version } } @@ -79,7 +84,7 @@ export default class Migrator extends EventEmitter { * @param {Object} [data] - The data for the initial state * @returns {{meta: {version: number}, data: any}} */ - generateInitialState (data) { + generateInitialState(data) { return { meta: { version: this.defaultVersion, @@ -87,5 +92,4 @@ export default class Migrator extends EventEmitter { data, } } - } diff --git a/app/scripts/lib/network-store.js b/app/scripts/lib/network-store.js index 3116947d7..cc2898292 100644 --- a/app/scripts/lib/network-store.js +++ b/app/scripts/lib/network-store.js @@ -8,21 +8,21 @@ const FIXTURE_SERVER_URL = `http://${FIXTURE_SERVER_HOST}:${FIXTURE_SERVER_PORT} * A read-only network-based storage wrapper */ export default class ReadOnlyNetworkStore { - constructor () { + constructor() { this._initialized = false this._initializing = this._init() this._state = undefined } /** - * Declares this store as compatible with the current browser - */ + * Declares this store as compatible with the current browser + */ isSupported = true /** * Initializes by loading state from the network */ - async _init () { + async _init() { try { const response = await window.fetch(FIXTURE_SERVER_URL) if (response.ok) { @@ -39,7 +39,7 @@ export default class ReadOnlyNetworkStore { * Returns state * @returns {Promise} */ - async get () { + async get() { if (!this._initialized) { await this._initializing } @@ -51,7 +51,7 @@ export default class ReadOnlyNetworkStore { * @param {Object} state - The state to set * @returns {Promise} */ - async set (state) { + async set(state) { if (!this._initialized) { await this._initializing } diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js index 074fba3bb..5e6f46309 100644 --- a/app/scripts/lib/nodeify.js +++ b/app/scripts/lib/nodeify.js @@ -14,7 +14,7 @@ const callbackNoop = function (err) { * @param {Object} context - The context in which the fn is to be called, most often a this reference * */ -export default function nodeify (fn, context) { +export default function nodeify(fn, context) { return function (...args) { const lastArg = args[args.length - 1] const lastArgIsCallback = typeof lastArg === 'function' diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index 631ae3290..eab217083 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -4,7 +4,6 @@ const NOTIFICATION_HEIGHT = 620 const NOTIFICATION_WIDTH = 360 export default class NotificationManager { - /** * A collection of methods for controlling the showing and hiding of the notification popup. * @@ -12,7 +11,7 @@ export default class NotificationManager { * */ - constructor () { + constructor() { this.platform = new ExtensionPlatform() } @@ -21,7 +20,7 @@ export default class NotificationManager { * notification windows are given a 'popup' type. * */ - async showPopup () { + async showPopup() { const popup = await this._getPopup() // Bring focus to chrome popup @@ -71,7 +70,7 @@ export default class NotificationManager { * @param {Function} cb - A node style callback that to which the found notification window will be passed. * */ - async _getPopup () { + async _getPopup() { const windows = await this.platform.getAllWindows() return this._getPopupIn(windows) } @@ -80,14 +79,15 @@ export default class NotificationManager { * Given an array of windows, returns the 'popup' that has been opened by MetaMask, or null if no such window exists. * * @private - * @param {array} windows - An array of objects containing data about the open MetaMask extension windows. + * @param {Array} windows - An array of objects containing data about the open MetaMask extension windows. * */ - _getPopupIn (windows) { - return windows ? windows.find((win) => { - // Returns notification popup - return (win && win.type === 'popup' && win.id === this._popupId) - }) : null + _getPopupIn(windows) { + return windows + ? windows.find((win) => { + // Returns notification popup + return win && win.type === 'popup' && win.id === this._popupId + }) + : null } - } diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 52c0f9bad..4e8b66de7 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -3,10 +3,11 @@ import ObservableStore from 'obs-store' import ethUtil from 'ethereumjs-util' import { ethErrors } from 'eth-json-rpc-errors' import log from 'loglevel' +import { addHexPrefix } from './util' import createId from './random-id' import { MESSAGE_TYPE } from './enums' -const hexRe = /^[0-9A-Fa-f]+$/ug +const hexRe = /^[0-9A-Fa-f]+$/gu /** * Represents, and contains data about, an 'personal_sign' type signature request. These are created when a @@ -28,19 +29,17 @@ const hexRe = /^[0-9A-Fa-f]+$/ug */ export default class PersonalMessageManager extends EventEmitter { - /** * Controller in charge of managing - storing, adding, removing, updating - PersonalMessage. * * @typedef {Object} PersonalMessageManager - * @param {Object} opts @deprecated * @property {Object} memStore The observable store where PersonalMessage are saved. * @property {Object} memStore.unapprovedPersonalMsgs A collection of all PersonalMessages in the 'unapproved' state * @property {number} memStore.unapprovedPersonalMsgCount The count of all PersonalMessages in this.memStore.unapprobedMsgs - * @property {array} messages Holds all messages that have been created by this PersonalMessageManager + * @property {Array} messages Holds all messages that have been created by this PersonalMessageManager * */ - constructor () { + constructor() { super() this.memStore = new ObservableStore({ unapprovedPersonalMsgs: {}, @@ -52,22 +51,23 @@ export default class PersonalMessageManager extends EventEmitter { /** * A getter for the number of 'unapproved' PersonalMessages in this.messages * - * @returns {number} - The number of 'unapproved' PersonalMessages in this.messages + * @returns {number} The number of 'unapproved' PersonalMessages in this.messages * */ - get unapprovedPersonalMsgCount () { + get unapprovedPersonalMsgCount() { return Object.keys(this.getUnapprovedMsgs()).length } /** * A getter for the 'unapproved' PersonalMessages in this.messages * - * @returns {Object} - An index of PersonalMessage ids to PersonalMessages, for all 'unapproved' PersonalMessages in + * @returns {Object} An index of PersonalMessage ids to PersonalMessages, for all 'unapproved' PersonalMessages in * this.messages * */ - getUnapprovedMsgs () { - return this.messages.filter((msg) => msg.status === 'unapproved') + getUnapprovedMsgs() { + return this.messages + .filter((msg) => msg.status === 'unapproved') .reduce((result, msg) => { result[msg.id] = msg return result @@ -80,11 +80,11 @@ export default class PersonalMessageManager extends EventEmitter { * this.memStore. * * @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved. - * @param {Object} req (optional) The original request object possibly containing the origin - * @returns {promise} - When the message has been signed or rejected + * @param {Object} [req] - The original request object possibly containing the origin + * @returns {promise} When the message has been signed or rejected * */ - addUnapprovedMessageAsync (msgParams, req) { + addUnapprovedMessageAsync(msgParams, req) { return new Promise((resolve, reject) => { if (!msgParams.from) { reject(new Error('MetaMask Message Signature: from field is required.')) @@ -97,10 +97,20 @@ export default class PersonalMessageManager extends EventEmitter { resolve(data.rawSig) return case 'rejected': - reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + reject( + ethErrors.provider.userRejectedRequest( + 'MetaMask Message Signature: User denied message signature.', + ), + ) return default: - reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + reject( + new Error( + `MetaMask Message Signature: Unknown problem: ${JSON.stringify( + msgParams, + )}`, + ), + ) } }) }) @@ -112,19 +122,23 @@ export default class PersonalMessageManager extends EventEmitter { * this.memStore. * * @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved. - * @param {Object} req (optional) The original request object possibly containing the origin - * @returns {number} - The id of the newly created PersonalMessage. + * @param {Object} [req] - The original request object possibly containing the origin + * @returns {number} The id of the newly created PersonalMessage. * */ - addUnapprovedMessage (msgParams, req) { - log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) + addUnapprovedMessage(msgParams, req) { + log.debug( + `PersonalMessageManager addUnapprovedMessage: ${JSON.stringify( + msgParams, + )}`, + ) // add origin from request if (req) { msgParams.origin = req.origin } msgParams.data = this.normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data - const time = (new Date()).getTime() + const time = new Date().getTime() const msgId = createId() const msgData = { id: msgId, @@ -147,7 +161,7 @@ export default class PersonalMessageManager extends EventEmitter { * @param {Message} msg - The PersonalMessage to add to this.messages * */ - addMsg (msg) { + addMsg(msg) { this.messages.push(msg) this._saveMsgList() } @@ -156,11 +170,11 @@ export default class PersonalMessageManager extends EventEmitter { * Returns a specified PersonalMessage. * * @param {number} msgId - The id of the PersonalMessage to get - * @returns {PersonalMessage|undefined} - The PersonalMessage with the id that matches the passed msgId, or undefined + * @returns {PersonalMessage|undefined} The PersonalMessage with the id that matches the passed msgId, or undefined * if no PersonalMessage has that id. * */ - getMsg (msgId) { + getMsg(msgId) { return this.messages.find((msg) => msg.id === msgId) } @@ -170,10 +184,10 @@ export default class PersonalMessageManager extends EventEmitter { * * @param {Object} msgParams - The msgParams to be used when eth_sign is called, plus data added by MetaMask. * @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask. - * @returns {Promise} - Promises the msgParams object with metamaskId removed. + * @returns {Promise} Promises the msgParams object with metamaskId removed. * */ - approveMessage (msgParams) { + approveMessage(msgParams) { this.setMsgStatusApproved(msgParams.metamaskId) return this.prepMsgForSigning(msgParams) } @@ -184,7 +198,7 @@ export default class PersonalMessageManager extends EventEmitter { * @param {number} msgId - The id of the PersonalMessage to approve. * */ - setMsgStatusApproved (msgId) { + setMsgStatusApproved(msgId) { this._setMsgStatus(msgId, 'approved') } @@ -196,7 +210,7 @@ export default class PersonalMessageManager extends EventEmitter { * @param {buffer} rawSig - The raw data of the signature request * */ - setMsgStatusSigned (msgId, rawSig) { + setMsgStatusSigned(msgId, rawSig) { const msg = this.getMsg(msgId) msg.rawSig = rawSig this._updateMsg(msg) @@ -207,10 +221,10 @@ export default class PersonalMessageManager extends EventEmitter { * Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams * * @param {Object} msgParams - The msgParams to modify - * @returns {Promise} - Promises the msgParams with the metamaskId property removed + * @returns {Promise} Promises the msgParams with the metamaskId property removed * */ - prepMsgForSigning (msgParams) { + prepMsgForSigning(msgParams) { delete msgParams.metamaskId return Promise.resolve(msgParams) } @@ -221,7 +235,7 @@ export default class PersonalMessageManager extends EventEmitter { * @param {number} msgId - The id of the PersonalMessage to reject. * */ - rejectMsg (msgId) { + rejectMsg(msgId) { this._setMsgStatus(msgId, 'rejected') } @@ -238,10 +252,12 @@ export default class PersonalMessageManager extends EventEmitter { * with the PersonalMessage * */ - _setMsgStatus (msgId, status) { + _setMsgStatus(msgId, status) { const msg = this.getMsg(msgId) if (!msg) { - throw new Error(`PersonalMessageManager - Message not found for id: "${msgId}".`) + throw new Error( + `PersonalMessageManager - Message not found for id: "${msgId}".`, + ) } msg.status = status this._updateMsg(msg) @@ -260,7 +276,7 @@ export default class PersonalMessageManager extends EventEmitter { * id) in this.messages * */ - _updateMsg (msg) { + _updateMsg(msg) { const index = this.messages.findIndex((message) => message.id === msg.id) if (index !== -1) { this.messages[index] = msg @@ -275,10 +291,14 @@ export default class PersonalMessageManager extends EventEmitter { * @fires 'updateBadge' * */ - _saveMsgList () { + _saveMsgList() { const unapprovedPersonalMsgs = this.getUnapprovedMsgs() - const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs).length - this.memStore.updateState({ unapprovedPersonalMsgs, unapprovedPersonalMsgCount }) + const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs) + .length + this.memStore.updateState({ + unapprovedPersonalMsgs, + unapprovedPersonalMsgCount, + }) this.emit('updateBadge') } @@ -286,14 +306,14 @@ export default class PersonalMessageManager extends EventEmitter { * A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex. * * @param {any} data - The buffer data to convert to a hex - * @returns {string} - A hex string conversion of the buffer data + * @returns {string} A hex string conversion of the buffer data * */ - normalizeMsgData (data) { + normalizeMsgData(data) { try { const stripped = ethUtil.stripHexPrefix(data) if (stripped.match(hexRe)) { - return ethUtil.addHexPrefix(stripped) + return addHexPrefix(stripped) } } catch (e) { log.debug(`Message was not hex encoded, interpreting as utf8.`) @@ -301,5 +321,4 @@ export default class PersonalMessageManager extends EventEmitter { return ethUtil.bufferToHex(Buffer.from(data, 'utf8')) } - } diff --git a/app/scripts/lib/random-id.js b/app/scripts/lib/random-id.js index 9345fcaad..a21717a2b 100644 --- a/app/scripts/lib/random-id.js +++ b/app/scripts/lib/random-id.js @@ -1,7 +1,7 @@ const MAX = Number.MAX_SAFE_INTEGER let idCounter = Math.round(Math.random() * MAX) -export default function createRandomId () { +export default function createRandomId() { idCounter %= MAX // eslint-disable-next-line no-plusplus return idCounter++ diff --git a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js index 106f38e44..fff0afb16 100644 --- a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js +++ b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js @@ -23,8 +23,8 @@ const handlerMap = handlers.reduce((map, handler) => { * @param {Function} opts.sendMetrics - A function for sending a metrics event * @returns {(req: Object, res: Object, next: Function, end: Function) => void} */ -export default function createMethodMiddleware (opts) { - return function methodMiddleware (req, res, next, end) { +export default function createMethodMiddleware(opts) { + return function methodMiddleware(req, res, next, end) { if (handlerMap.has(req.method)) { return handlerMap.get(req.method)(req, res, next, end, opts) } diff --git a/app/scripts/lib/rpc-method-middleware/handlers/index.js b/app/scripts/lib/rpc-method-middleware/handlers/index.js index 3a538680b..bc87cb309 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/index.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/index.js @@ -1,6 +1,4 @@ import logWeb3Usage from './log-web3-usage' -const handlers = [ - logWeb3Usage, -] +const handlers = [logWeb3Usage] export default handlers diff --git a/app/scripts/lib/rpc-method-middleware/handlers/log-web3-usage.js b/app/scripts/lib/rpc-method-middleware/handlers/log-web3-usage.js index 91e4e60c2..dbff526bb 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/log-web3-usage.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/log-web3-usage.js @@ -34,22 +34,20 @@ const recordedWeb3Usage = {} * @param {Function} end - The json-rpc-engine 'end' callback. * @param {LogWeb3UsageOptions} options */ -function logWeb3UsageHandler ( - req, res, _next, end, - { origin, sendMetrics }, -) { - const { action, name } = req.params[0] +function logWeb3UsageHandler(req, res, _next, end, { origin, sendMetrics }) { + const { action, path } = req.params[0] if (!recordedWeb3Usage[origin]) { recordedWeb3Usage[origin] = {} } - if (!recordedWeb3Usage[origin][name]) { - recordedWeb3Usage[origin][name] = true + if (!recordedWeb3Usage[origin][path]) { + recordedWeb3Usage[origin][path] = true sendMetrics({ - event: `Website Accessed window.web3`, + matomo: true, + event: `Website Used window.web3`, category: 'inpage_provider', - properties: { action, web3Property: name }, + properties: { action, web3Path: path }, eventContext: { referrer: { url: origin, diff --git a/app/scripts/lib/seed-phrase-verifier.js b/app/scripts/lib/seed-phrase-verifier.js index 71b9e2819..8c3e778a0 100644 --- a/app/scripts/lib/seed-phrase-verifier.js +++ b/app/scripts/lib/seed-phrase-verifier.js @@ -2,7 +2,6 @@ import KeyringController from 'eth-keyring-controller' import log from 'loglevel' const seedPhraseVerifier = { - /** * Verifies if the seed words can restore the accounts. * @@ -11,12 +10,12 @@ const seedPhraseVerifier = { * - The created accounts in the primary keyring are always the same. * - The keyring always creates the accounts in the same sequence. * - * @param {array} createdAccounts - The accounts to restore + * @param {Array} createdAccounts - The accounts to restore * @param {string} seedWords - The seed words to verify - * @returns {Promise} - Promises undefined + * @returns {Promise} Promises undefined * - */ - async verifyAccounts (createdAccounts, seedWords) { + */ + async verifyAccounts(createdAccounts, seedWords) { if (!createdAccounts || createdAccounts.length < 1) { throw new Error('No created accounts defined.') } @@ -39,8 +38,12 @@ const seedPhraseVerifier = { } for (let i = 0; i < restoredAccounts.length; i++) { - if (restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()) { - throw new Error(`Not identical accounts! Original: ${createdAccounts[i]}, Restored: ${restoredAccounts[i]}`) + if ( + restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase() + ) { + throw new Error( + `Not identical accounts! Original: ${createdAccounts[i]}, Restored: ${restoredAccounts[i]}`, + ) } } }, diff --git a/app/scripts/lib/setupFetchDebugging.js b/app/scripts/lib/setupFetchDebugging.js index 25f5431d5..8b566c31d 100644 --- a/app/scripts/lib/setupFetchDebugging.js +++ b/app/scripts/lib/setupFetchDebugging.js @@ -4,7 +4,7 @@ // https://github.com/getsentry/sentry-javascript/pull/1293 // -export default function setupFetchDebugging () { +export default function setupFetchDebugging() { if (!window.fetch) { return } @@ -12,14 +12,19 @@ export default function setupFetchDebugging () { window.fetch = wrappedFetch - async function wrappedFetch (...args) { + async function wrappedFetch(...args) { const initialStack = getCurrentStack() try { return await originalFetch.call(window, ...args) } catch (err) { if (!err.stack) { - console.warn('FetchDebugger - fetch encountered an Error without a stack', err) - console.warn('FetchDebugger - overriding stack to point of original call') + console.warn( + 'FetchDebugger - fetch encountered an Error without a stack', + err, + ) + console.warn( + 'FetchDebugger - overriding stack to point of original call', + ) err.stack = initialStack } throw err @@ -27,7 +32,7 @@ export default function setupFetchDebugging () { } } -function getCurrentStack () { +function getCurrentStack() { try { throw new Error('Fake error for generating stack trace') } catch (err) { diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index bf5490091..1deb4c94b 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -8,7 +8,8 @@ import extractEthjsErrorMessage from './extractEthjsErrorMessage' const METAMASK_DEBUG = process.env.METAMASK_DEBUG const METAMASK_ENVIRONMENT = process.env.METAMASK_ENVIRONMENT /* eslint-enable prefer-destructuring */ -const SENTRY_DSN_DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496' +const SENTRY_DSN_DEV = + 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496' // This describes the subset of Redux state attached to errors sent to Sentry // These properties have some potential to be useful for debugging, and they do @@ -66,19 +67,25 @@ export const SENTRY_STATE = { unconnectedAccount: true, } -export default function setupSentry ({ release, getState }) { +export default function setupSentry({ release, getState }) { let sentryTarget if (METAMASK_DEBUG) { return undefined } else if (METAMASK_ENVIRONMENT === 'production') { if (!process.env.SENTRY_DSN) { - throw new Error(`Missing SENTRY_DSN environment variable in production environment`) + throw new Error( + `Missing SENTRY_DSN environment variable in production environment`, + ) } - console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN`) + console.log( + `Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN`, + ) sentryTarget = process.env.SENTRY_DSN } else { - console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_DEV`) + console.log( + `Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_DEV`, + ) sentryTarget = SENTRY_DSN_DEV } @@ -86,15 +93,12 @@ export default function setupSentry ({ release, getState }) { dsn: sentryTarget, debug: METAMASK_DEBUG, environment: METAMASK_ENVIRONMENT, - integrations: [ - new Dedupe(), - new ExtraErrorData(), - ], + integrations: [new Dedupe(), new ExtraErrorData()], release, beforeSend: (report) => rewriteReport(report), }) - function rewriteReport (report) { + function rewriteReport(report) { try { // simplify certain complex error messages (e.g. Ethjs) simplifyErrorMessages(report) @@ -117,12 +121,16 @@ export default function setupSentry ({ release, getState }) { return Sentry } -function simplifyErrorMessages (report) { +function simplifyErrorMessages(report) { rewriteErrorMessages(report, (errorMessage) => { // simplify ethjs error messages let simplifiedErrorMessage = extractEthjsErrorMessage(errorMessage) // simplify 'Transaction Failed: known transaction' - if (simplifiedErrorMessage.indexOf('Transaction Failed: known transaction') === 0) { + if ( + simplifiedErrorMessage.indexOf( + 'Transaction Failed: known transaction', + ) === 0 + ) { // cut the hash from the error message simplifiedErrorMessage = 'Transaction Failed: known transaction' } @@ -130,7 +138,7 @@ function simplifyErrorMessages (report) { }) } -function rewriteErrorMessages (report, rewriteFn) { +function rewriteErrorMessages(report, rewriteFn) { // rewrite top level message if (typeof report.message === 'string') { report.message = rewriteFn(report.message) @@ -145,7 +153,7 @@ function rewriteErrorMessages (report, rewriteFn) { } } -function rewriteReportUrls (report) { +function rewriteReportUrls(report) { // update request url report.request.url = toMetamaskUrl(report.request.url) // update exception stack trace @@ -160,7 +168,7 @@ function rewriteReportUrls (report) { } } -function toMetamaskUrl (origUrl) { +function toMetamaskUrl(origUrl) { const filePath = origUrl.split(window.location.origin)[1] if (!filePath) { return origUrl diff --git a/app/scripts/lib/setupWeb3.js b/app/scripts/lib/setupWeb3.js index ad86751e1..922d29cf5 100644 --- a/app/scripts/lib/setupWeb3.js +++ b/app/scripts/lib/setupWeb3.js @@ -3,15 +3,20 @@ // TODO:deprecate:2020 // Delete this file +import web3Entitites from './web3-entities.json' import 'web3/dist/web3.min' -const shouldLogUsage = !([ +const shouldLogUsage = ![ 'docs.metamask.io', 'metamask.github.io', 'metamask.io', -].includes(window.location.hostname)) +].includes(window.location.hostname) -export default function setupWeb3 (log) { +/** + * To understand how we arrived at this implementation, please see: + * https://github.com/ethereum/web3.js/blob/0.20.7/DOCUMENTATION.md + */ +export default function setupWeb3(log) { // export web3 as a global, checking for usage let reloadInProgress = false let lastTimeUsed @@ -31,40 +36,135 @@ export default function setupWeb3 (log) { value: web3.eth, }) - const web3Proxy = new Proxy(web3, { - get: (_web3, key) => { + // Setup logging of nested property usage + if (shouldLogUsage) { + // web3 namespaces with common and uncommon dapp actions + const includedTopKeys = [ + 'eth', + 'db', + 'shh', + 'net', + 'personal', + 'bzz', + 'version', + ] + // For each top-level property, create appropriate Proxy traps for all of + // their properties + includedTopKeys.forEach((topKey) => { + const applyTrapKeys = new Map() + const getTrapKeys = new Map() + + Object.keys(web3[topKey]).forEach((key) => { + const path = `web3.${topKey}.${key}` + + if (web3Entitites[path]) { + if (web3Entitites[path] === 'function') { + applyTrapKeys.set(key, path) + } else { + getTrapKeys.set(key, path) + } + } + }) + + // Create apply traps for function properties + for (const [key, path] of applyTrapKeys) { + web3[topKey][key] = new Proxy(web3[topKey][key], { + apply: (...params) => { + window.ethereum.request({ + method: 'metamask_logInjectedWeb3Usage', + params: [ + { + action: 'apply', + path, + }, + ], + }) + + // Call function normally + return Reflect.apply(...params) + }, + }) + } + + // Create get trap for non-function properties + web3[topKey] = new Proxy(web3[topKey], { + get: (web3Prop, key, ...params) => { + const name = stringifyKey(key) + + if (getTrapKeys.has(name)) { + window.ethereum.request({ + method: 'metamask_logInjectedWeb3Usage', + params: [ + { + action: 'get', + path: getTrapKeys.get(name), + }, + ], + }) + } + + // return value normally + return Reflect.get(web3Prop, key, ...params) + }, + }) + }) + } + + const topLevelFunctions = [ + 'isConnected', + 'setProvider', + 'reset', + 'sha3', + 'toHex', + 'toAscii', + 'fromAscii', + 'toDecimal', + 'fromDecimal', + 'fromWei', + 'toWei', + 'toBigNumber', + 'isAddress', + ] + + // apply-trap top-level functions + topLevelFunctions.forEach((key) => { + // This type check is probably redundant, but we've been burned before. + if (typeof web3[key] === 'function') { + web3[key] = new Proxy(web3[key], { + apply: (...params) => { + window.ethereum.request({ + method: 'metamask_logInjectedWeb3Usage', + params: [ + { + action: 'apply', + path: `web3.${key}`, + }, + ], + }) + + // Call function normally + return Reflect.apply(...params) + }, + }) + } + }) + + const web3Proxy = new Proxy(web3, { + get: (...params) => { // get the time of use lastTimeUsed = Date.now() // show warning once on web3 access if (!hasBeenWarned) { - console.warn(`MetaMask: We will stop injecting web3 in Q4 2020.\nPlease see this article for more information: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`) + console.warn( + `MetaMask: We will stop injecting web3 in Q4 2020.\nPlease see this article for more information: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`, + ) hasBeenWarned = true } - if (shouldLogUsage) { - const name = stringifyKey(key) - window.ethereum.request({ - method: 'metamask_logInjectedWeb3Usage', - params: [{ action: 'get', name }], - }) - } - // return value normally - return _web3[key] - }, - set: (_web3, key, value) => { - const name = stringifyKey(key) - if (shouldLogUsage) { - window.ethereum.request({ - method: 'metamask_logInjectedWeb3Usage', - params: [{ action: 'set', name }], - }) - } - - // set value normally - _web3[key] = value + return Reflect.get(...params) }, }) @@ -118,7 +218,7 @@ export default function setupWeb3 (log) { } // reload the page -function triggerReset () { +function triggerReset() { global.location.reload() } @@ -128,8 +228,6 @@ function triggerReset () { * * @param {any} key - The key to stringify */ -function stringifyKey (key) { - return typeof key === 'string' - ? key - : `typeof ${typeof key}` +function stringifyKey(key) { + return typeof key === 'string' ? key : `typeof ${typeof key}` } diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index b1b4aeec3..a34245ae4 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -4,19 +4,14 @@ import pump from 'pump' /** * Sets up stream multiplexing for the given stream * @param {any} connectionStream - the stream to mux - * @returns {stream.Stream} - the multiplexed stream + * @returns {stream.Stream} the multiplexed stream */ -export function setupMultiplex (connectionStream) { +export function setupMultiplex(connectionStream) { const mux = new ObjectMultiplex() - pump( - connectionStream, - mux, - connectionStream, - (err) => { - if (err) { - console.error(err) - } - }, - ) + pump(connectionStream, mux, connectionStream, (err) => { + if (err) { + console.error(err) + } + }) return mux } diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 4769f6e4c..c4f5fd54a 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -2,7 +2,7 @@ import EventEmitter from 'events' import assert from 'assert' import ObservableStore from 'obs-store' import { ethErrors } from 'eth-json-rpc-errors' -import sigUtil from 'eth-sig-util' +import { typedSignatureHash, TYPED_MESSAGE_SCHEMA } from 'eth-sig-util' import { isValidAddress } from 'ethereumjs-util' import log from 'loglevel' import jsonschema from 'jsonschema' @@ -28,11 +28,10 @@ import { MESSAGE_TYPE } from './enums' */ export default class TypedMessageManager extends EventEmitter { - /** * Controller in charge of managing - storing, adding, removing, updating - TypedMessage. */ - constructor ({ getCurrentChainId }) { + constructor({ getCurrentChainId }) { super() this._getCurrentChainId = getCurrentChainId this.memStore = new ObservableStore({ @@ -45,22 +44,23 @@ export default class TypedMessageManager extends EventEmitter { /** * A getter for the number of 'unapproved' TypedMessages in this.messages * - * @returns {number} - The number of 'unapproved' TypedMessages in this.messages + * @returns {number} The number of 'unapproved' TypedMessages in this.messages * */ - get unapprovedTypedMessagesCount () { + get unapprovedTypedMessagesCount() { return Object.keys(this.getUnapprovedMsgs()).length } /** * A getter for the 'unapproved' TypedMessages in this.messages * - * @returns {Object} - An index of TypedMessage ids to TypedMessages, for all 'unapproved' TypedMessages in + * @returns {Object} An index of TypedMessage ids to TypedMessages, for all 'unapproved' TypedMessages in * this.messages * */ - getUnapprovedMsgs () { - return this.messages.filter((msg) => msg.status === 'unapproved') + getUnapprovedMsgs() { + return this.messages + .filter((msg) => msg.status === 'unapproved') .reduce((result, msg) => { result[msg.id] = msg return result @@ -73,11 +73,11 @@ export default class TypedMessageManager extends EventEmitter { * this.memStore. Before any of this is done, msgParams are validated * * @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved. - * @param {Object} req (optional) The original request object possibly containing the origin - * @returns {promise} - When the message has been signed or rejected + * @param {Object} [req] - The original request object possibly containing the origin + * @returns {promise} When the message has been signed or rejected * */ - addUnapprovedMessageAsync (msgParams, req, version) { + addUnapprovedMessageAsync(msgParams, req, version) { return new Promise((resolve, reject) => { const msgId = this.addUnapprovedMessage(msgParams, req, version) this.once(`${msgId}:finished`, (data) => { @@ -85,11 +85,23 @@ export default class TypedMessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject( + ethErrors.provider.userRejectedRequest( + 'MetaMask Message Signature: User denied message signature.', + ), + ) case 'errored': - return reject(new Error(`MetaMask Message Signature: ${data.error}`)) + return reject( + new Error(`MetaMask Message Signature: ${data.error}`), + ) default: - return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + return reject( + new Error( + `MetaMask Message Signature: Unknown problem: ${JSON.stringify( + msgParams, + )}`, + ), + ) } }) }) @@ -101,22 +113,23 @@ export default class TypedMessageManager extends EventEmitter { * this.memStore. Before any of this is done, msgParams are validated * * @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved. - * @param {Object} req (optional) The original request object possibly containing the origin - * @returns {number} - The id of the newly created TypedMessage. + * @param {Object} [req] - The original request object possibly containing the origin + * @returns {number} The id of the newly created TypedMessage. * */ - addUnapprovedMessage (msgParams, req, version) { - + addUnapprovedMessage(msgParams, req, version) { msgParams.version = version if (req) { msgParams.origin = req.origin } this.validateParams(msgParams) - log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) + log.debug( + `TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`, + ) // create txData obj with parameters and meta data - const time = (new Date()).getTime() + const time = new Date().getTime() const msgId = createId() const msgData = { id: msgId, @@ -138,8 +151,7 @@ export default class TypedMessageManager extends EventEmitter { * @param {Object} params - The params to validate * */ - validateParams (params) { - + validateParams(params) { assert.ok(params && typeof params === 'object', 'Params must be an object.') assert.ok('data' in params, 'Params must include a "data" field.') assert.ok('from' in params, 'Params must include a "from" field.') @@ -152,24 +164,42 @@ export default class TypedMessageManager extends EventEmitter { case 'V1': assert.ok(Array.isArray(params.data), '"params.data" must be an array.') assert.doesNotThrow(() => { - sigUtil.typedSignatureHash(params.data) + typedSignatureHash(params.data) }, 'Signing data must be valid EIP-712 typed data.') break case 'V3': case 'V4': { - assert.equal(typeof params.data, 'string', '"params.data" must be a string.') + assert.equal( + typeof params.data, + 'string', + '"params.data" must be a string.', + ) let data assert.doesNotThrow(() => { data = JSON.parse(params.data) }, '"data" must be a valid JSON string.') - const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA) - assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`) - assert.equal(validation.errors.length, 0, 'Signing data must conform to EIP-712 schema. See https://git.io/fNtcx.') + const validation = jsonschema.validate(data, TYPED_MESSAGE_SCHEMA) + assert.ok( + data.primaryType in data.types, + `Primary type of "${data.primaryType}" has no type definition.`, + ) + assert.equal( + validation.errors.length, + 0, + 'Signing data must conform to EIP-712 schema. See https://git.io/fNtcx.', + ) const { chainId } = data.domain if (chainId) { const activeChainId = parseInt(this._getCurrentChainId(), 16) - assert.ok(!Number.isNaN(activeChainId), `Cannot sign messages for chainId "${chainId}", because MetaMask is switching networks.`) - assert.equal(chainId, activeChainId, `Provided chainId "${chainId}" must match the active chainId "${activeChainId}"`) + assert.ok( + !Number.isNaN(activeChainId), + `Cannot sign messages for chainId "${chainId}", because MetaMask is switching networks.`, + ) + assert.equal( + chainId, + activeChainId, + `Provided chainId "${chainId}" must match the active chainId "${activeChainId}"`, + ) } break } @@ -185,7 +215,7 @@ export default class TypedMessageManager extends EventEmitter { * @param {Message} msg - The TypedMessage to add to this.messages * */ - addMsg (msg) { + addMsg(msg) { this.messages.push(msg) this._saveMsgList() } @@ -194,11 +224,11 @@ export default class TypedMessageManager extends EventEmitter { * Returns a specified TypedMessage. * * @param {number} msgId - The id of the TypedMessage to get - * @returns {TypedMessage|undefined} - The TypedMessage with the id that matches the passed msgId, or undefined + * @returns {TypedMessage|undefined} The TypedMessage with the id that matches the passed msgId, or undefined * if no TypedMessage has that id. * */ - getMsg (msgId) { + getMsg(msgId) { return this.messages.find((msg) => msg.id === msgId) } @@ -208,10 +238,10 @@ export default class TypedMessageManager extends EventEmitter { * * @param {Object} msgParams - The msgParams to be used when eth_sign is called, plus data added by MetaMask. * @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask. - * @returns {Promise} - Promises the msgParams object with metamaskId removed. + * @returns {Promise} Promises the msgParams object with metamaskId removed. * */ - approveMessage (msgParams) { + approveMessage(msgParams) { this.setMsgStatusApproved(msgParams.metamaskId) return this.prepMsgForSigning(msgParams) } @@ -222,7 +252,7 @@ export default class TypedMessageManager extends EventEmitter { * @param {number} msgId - The id of the TypedMessage to approve. * */ - setMsgStatusApproved (msgId) { + setMsgStatusApproved(msgId) { this._setMsgStatus(msgId, 'approved') } @@ -234,7 +264,7 @@ export default class TypedMessageManager extends EventEmitter { * @param {buffer} rawSig - The raw data of the signature request * */ - setMsgStatusSigned (msgId, rawSig) { + setMsgStatusSigned(msgId, rawSig) { const msg = this.getMsg(msgId) msg.rawSig = rawSig this._updateMsg(msg) @@ -245,10 +275,10 @@ export default class TypedMessageManager extends EventEmitter { * Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams * * @param {Object} msgParams - The msgParams to modify - * @returns {Promise} - Promises the msgParams with the metamaskId property removed + * @returns {Promise} Promises the msgParams with the metamaskId property removed * */ - prepMsgForSigning (msgParams) { + prepMsgForSigning(msgParams) { delete msgParams.metamaskId delete msgParams.version return Promise.resolve(msgParams) @@ -260,7 +290,7 @@ export default class TypedMessageManager extends EventEmitter { * @param {number} msgId - The id of the TypedMessage to reject. * */ - rejectMsg (msgId) { + rejectMsg(msgId) { this._setMsgStatus(msgId, 'rejected') } @@ -270,7 +300,7 @@ export default class TypedMessageManager extends EventEmitter { * @param {number} msgId - The id of the TypedMessage to error * */ - errorMessage (msgId, error) { + errorMessage(msgId, error) { const msg = this.getMsg(msgId) msg.error = error this._updateMsg(msg) @@ -294,10 +324,12 @@ export default class TypedMessageManager extends EventEmitter { * with the TypedMessage * */ - _setMsgStatus (msgId, status) { + _setMsgStatus(msgId, status) { const msg = this.getMsg(msgId) if (!msg) { - throw new Error(`TypedMessageManager - Message not found for id: "${msgId}".`) + throw new Error( + `TypedMessageManager - Message not found for id: "${msgId}".`, + ) } msg.status = status this._updateMsg(msg) @@ -316,7 +348,7 @@ export default class TypedMessageManager extends EventEmitter { * id) in this.messages * */ - _updateMsg (msg) { + _updateMsg(msg) { const index = this.messages.findIndex((message) => message.id === msg.id) if (index !== -1) { this.messages[index] = msg @@ -331,11 +363,14 @@ export default class TypedMessageManager extends EventEmitter { * @fires 'updateBadge' * */ - _saveMsgList () { + _saveMsgList() { const unapprovedTypedMessages = this.getUnapprovedMsgs() - const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages).length - this.memStore.updateState({ unapprovedTypedMessages, unapprovedTypedMessagesCount }) + const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages) + .length + this.memStore.updateState({ + unapprovedTypedMessages, + unapprovedTypedMessagesCount, + }) this.emit('updateBadge') } - } diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index 20ab998de..1816d04ab 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -44,12 +44,13 @@ const getEnvironmentTypeMemo = memoize((url) => { * @param {string} [url] - the URL of the window * @returns {string} the environment ENUM */ -const getEnvironmentType = (url = window.location.href) => getEnvironmentTypeMemo(url) +const getEnvironmentType = (url = window.location.href) => + getEnvironmentTypeMemo(url) /** * Returns the platform (browser) where the extension is running. * - * @returns {string} - the platform ENUM + * @returns {string} the platform ENUM * */ const getPlatform = (_) => { @@ -73,17 +74,25 @@ const getPlatform = (_) => { * Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee * * @param {Object} txParams - Contains data about a transaction - * @param {string} txParams.gas The gas for a transaction - * @param {string} txParams.gasPrice The price per gas for the transaction - * @param {string} txParams.value The value of ETH to send + * @param {string} txParams.gas - The gas for a transaction + * @param {string} txParams.gasPrice - The price per gas for the transaction + * @param {string} txParams.value - The value of ETH to send * @param {string} hexBalance - A balance of ETH represented as a hex string - * @returns {boolean} - Whether the balance is greater than or equal to the value plus the value of gas times gasPrice + * @returns {boolean} Whether the balance is greater than or equal to the value plus the value of gas times gasPrice * */ -function sufficientBalance (txParams, hexBalance) { +function sufficientBalance(txParams, hexBalance) { // validate hexBalance is a hex string - assert.equal(typeof hexBalance, 'string', 'sufficientBalance - hexBalance is not a hex string') - assert.equal(hexBalance.slice(0, 2), '0x', 'sufficientBalance - hexBalance is not a hex string') + assert.equal( + typeof hexBalance, + 'string', + 'sufficientBalance - hexBalance is not a hex string', + ) + assert.equal( + hexBalance.slice(0, 2), + '0x', + 'sufficientBalance - hexBalance is not a hex string', + ) const balance = hexToBn(hexBalance) const value = hexToBn(txParams.value) @@ -94,25 +103,14 @@ function sufficientBalance (txParams, hexBalance) { return balance.gte(maxCost) } -/** - * Converts a BN object to a hex string with a '0x' prefix - * - * @param {BN} inputBn - The BN to convert to a hex string - * @returns {string} - A '0x' prefixed hex string - * - */ -function bnToHex (inputBn) { - return ethUtil.addHexPrefix(inputBn.toString(16)) -} - /** * Converts a hex string to a BN object * * @param {string} inputHex - A number represented as a hex string - * @returns {Object} - A BN object + * @returns {Object} A BN object * */ -function hexToBn (inputHex) { +function hexToBn(inputHex) { return new BN(ethUtil.stripHexPrefix(inputHex), 16) } @@ -122,10 +120,10 @@ function hexToBn (inputHex) { * @param {BN} targetBN - The number to multiply by a fraction * @param {number|string} numerator - The numerator of the fraction multiplier * @param {number|string} denominator - The denominator of the fraction multiplier - * @returns {BN} - The product of the multiplication + * @returns {BN} The product of the multiplication * */ -function BnMultiplyByFraction (targetBN, numerator, denominator) { +function BnMultiplyByFraction(targetBN, numerator, denominator) { const numBN = new BN(numerator) const denomBN = new BN(denominator) return targetBN.mul(numBN).div(denomBN) @@ -136,7 +134,7 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) { * this is a workaround for the non-standard error object that's used * @returns {Error|undefined} */ -function checkForError () { +function checkForError() { const { lastError } = extension.runtime if (!lastError) { return undefined @@ -157,11 +155,44 @@ function checkForError () { * @returns {boolean} True if the value is a correctly formatted hex string, * false otherwise. */ -function isPrefixedFormattedHexString (value) { +function isPrefixedFormattedHexString(value) { if (typeof value !== 'string') { return false } - return (/^0x[1-9a-f]+[0-9a-f]*$/ui).test(value) + return /^0x[1-9a-f]+[0-9a-f]*$/iu.test(value) +} + +/** + * Prefixes a hex string with '0x' or '-0x' and returns it. Idempotent. + * + * @param {string} str - The string to prefix. + * @returns {string} The prefixed string. + */ +const addHexPrefix = (str) => { + if (typeof str !== 'string' || str.match(/^-?0x/u)) { + return str + } + + if (str.match(/^-?0X/u)) { + return str.replace('0X', '0x') + } + + if (str.startsWith('-')) { + return str.replace('-', '-0x') + } + + return `0x${str}` +} + +/** + * Converts a BN object to a hex string with a '0x' prefix + * + * @param {BN} inputBn - The BN to convert to a hex string + * @returns {string} - A '0x' prefixed hex string + * + */ +function bnToHex(inputBn) { + return addHexPrefix(inputBn.toString(16)) } export { @@ -169,8 +200,9 @@ export { getEnvironmentType, sufficientBalance, hexToBn, - bnToHex, BnMultiplyByFraction, checkForError, isPrefixedFormattedHexString, + addHexPrefix, + bnToHex, } diff --git a/app/scripts/lib/web3-entities.json b/app/scripts/lib/web3-entities.json new file mode 100644 index 000000000..0e9b435bc --- /dev/null +++ b/app/scripts/lib/web3-entities.json @@ -0,0 +1,101 @@ +{ + "web3.bzz.blockNetworkRead": "function", + "web3.bzz.download": "function", + "web3.bzz.get": "function", + "web3.bzz.getHive": "function", + "web3.bzz.getInfo": "function", + "web3.bzz.hive": "TRAP", + "web3.bzz.info": "TRAP", + "web3.bzz.modify": "function", + "web3.bzz.put": "function", + "web3.bzz.retrieve": "function", + "web3.bzz.store": "function", + "web3.bzz.swapEnabled": "function", + "web3.bzz.syncEnabled": "function", + "web3.bzz.upload": "function", + "web3.db.getHex": "function", + "web3.db.getString": "function", + "web3.db.putHex": "function", + "web3.db.putString": "function", + "web3.eth.accounts": "object", + "web3.eth.blockNumber": "TRAP", + "web3.eth.call": "function", + "web3.eth.coinbase": "object", + "web3.eth.compile": "object", + "web3.eth.estimateGas": "function", + "web3.eth.gasPrice": "TRAP", + "web3.eth.getAccounts": "function", + "web3.eth.getBalance": "function", + "web3.eth.getBlock": "function", + "web3.eth.getBlockNumber": "function", + "web3.eth.getBlockTransactionCount": "function", + "web3.eth.getBlockUncleCount": "function", + "web3.eth.getCode": "function", + "web3.eth.getCoinbase": "function", + "web3.eth.getCompilers": "function", + "web3.eth.getGasPrice": "function", + "web3.eth.getHashrate": "function", + "web3.eth.getMining": "function", + "web3.eth.getProtocolVersion": "function", + "web3.eth.getStorageAt": "function", + "web3.eth.getSyncing": "function", + "web3.eth.getTransaction": "function", + "web3.eth.getTransactionCount": "function", + "web3.eth.getTransactionFromBlock": "function", + "web3.eth.getTransactionReceipt": "function", + "web3.eth.getUncle": "function", + "web3.eth.getWork": "function", + "web3.eth.hashrate": "TRAP", + "web3.eth.iban": "function", + "web3.eth.mining": "TRAP", + "web3.eth.protocolVersion": "TRAP", + "web3.eth.sendIBANTransaction": "function", + "web3.eth.sendRawTransaction": "function", + "web3.eth.sendTransaction": "function", + "web3.eth.sign": "function", + "web3.eth.signTransaction": "function", + "web3.eth.submitWork": "function", + "web3.eth.syncing": "TRAP", + "web3.net.getListening": "function", + "web3.net.getPeerCount": "function", + "web3.net.listening": "TRAP", + "web3.net.peerCount": "TRAP", + "web3.personal.ecRecover": "function", + "web3.personal.getListAccounts": "function", + "web3.personal.importRawKey": "function", + "web3.personal.listAccounts": "TRAP", + "web3.personal.lockAccount": "function", + "web3.personal.newAccount": "function", + "web3.personal.sendTransaction": "function", + "web3.personal.sign": "function", + "web3.personal.unlockAccount": "function", + "web3.providers.HttpProvider": "function", + "web3.providers.IpcProvider": "function", + "web3.shh.addPrivateKey": "function", + "web3.shh.addSymKey": "function", + "web3.shh.deleteKeyPair": "function", + "web3.shh.deleteSymKey": "function", + "web3.shh.generateSymKeyFromPassword": "function", + "web3.shh.getPrivateKey": "function", + "web3.shh.getPublicKey": "function", + "web3.shh.getSymKey": "function", + "web3.shh.hasKeyPair": "function", + "web3.shh.hasSymKey": "function", + "web3.shh.info": "function", + "web3.shh.markTrustedPeer": "function", + "web3.shh.newKeyPair": "function", + "web3.shh.newSymKey": "function", + "web3.shh.post": "function", + "web3.shh.setMaxMessageSize": "function", + "web3.shh.setMinPoW": "function", + "web3.shh.version": "function", + "web3.version.api": "string", + "web3.version.ethereum": "TRAP", + "web3.version.getEthereum": "function", + "web3.version.getNetwork": "function", + "web3.version.getNode": "function", + "web3.version.getWhisper": "function", + "web3.version.network": "string", + "web3.version.node": "TRAP", + "web3.version.whisper": "TRAP" +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a72780c91..41cb53d75 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -26,6 +26,7 @@ import { } from '@metamask/controllers' import { getTrackMetaMetricsEvent } from '../../shared/modules/metametrics' import { getBackgroundMetaMetricState } from '../../ui/app/selectors' +import { TRANSACTION_STATUSES } from '../../shared/constants/transaction' import ComposableObservableStore from './lib/ComposableObservableStore' import AccountTracker from './lib/account-tracker' import createLoggerMiddleware from './lib/createLoggerMiddleware' @@ -60,12 +61,11 @@ import seedPhraseVerifier from './lib/seed-phrase-verifier' import { ENVIRONMENT_TYPE_BACKGROUND } from './lib/enums' export default class MetamaskController extends EventEmitter { - /** * @constructor * @param {Object} opts */ - constructor (opts) { + constructor(opts) { super() this.defaultMaxListeners = 20 @@ -125,7 +125,8 @@ export default class MetamaskController extends EventEmitter { } = this.preferencesController.store.getState() const chainId = this.networkController.getCurrentChainId() const provider = this.networkController.getProviderConfig() - const network = provider.type === 'rpc' ? provider.rpcUrl : provider.type + const network = + provider.type === 'rpc' ? provider.rpcUrl : provider.type return { participateInMetaMetrics, metaMetricsId, @@ -153,7 +154,10 @@ export default class MetamaskController extends EventEmitter { preferencesStore: this.preferencesController.store, }) - this.currencyRateController = new CurrencyRateController(undefined, initState.CurrencyController) + this.currencyRateController = new CurrencyRateController( + { includeUSDRate: true }, + initState.CurrencyController, + ) this.phishingController = new PhishingController() @@ -184,7 +188,9 @@ export default class MetamaskController extends EventEmitter { this.accountTracker = new AccountTracker({ provider: this.provider, blockTracker: this.blockTracker, - getCurrentChainId: this.networkController.getCurrentChainId.bind(this.networkController), + getCurrentChainId: this.networkController.getCurrentChainId.bind( + this.networkController, + ), }) // start and stop polling for balances based on activeControllerConnections @@ -202,7 +208,9 @@ export default class MetamaskController extends EventEmitter { this.cachedBalancesController = new CachedBalancesController({ accountTracker: this.accountTracker, - getNetwork: this.networkController.getNetworkState.bind(this.networkController), + getNetwork: this.networkController.getNetworkState.bind( + this.networkController, + ), initState: initState.CachedBalancesController, }) @@ -222,18 +230,28 @@ export default class MetamaskController extends EventEmitter { initState: initState.KeyringController, encryptor: opts.encryptor || undefined, }) - this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s)) + this.keyringController.memStore.subscribe((s) => + this._onKeyringControllerUpdate(s), + ) this.keyringController.on('unlock', () => this.emit('unlock')) - this.permissionsController = new PermissionsController({ - getKeyringAccounts: this.keyringController.getAccounts.bind(this.keyringController), - getRestrictedMethods, - getUnlockPromise: this.appStateController.getUnlockPromise.bind(this.appStateController), - notifyDomain: this.notifyConnections.bind(this), - notifyAllDomains: this.notifyAllConnections.bind(this), - preferences: this.preferencesController.store, - showPermissionRequest: opts.showPermissionRequest, - }, initState.PermissionsController, initState.PermissionsMetadata) + this.permissionsController = new PermissionsController( + { + getKeyringAccounts: this.keyringController.getAccounts.bind( + this.keyringController, + ), + getRestrictedMethods, + getUnlockPromise: this.appStateController.getUnlockPromise.bind( + this.appStateController, + ), + notifyDomain: this.notifyConnections.bind(this), + notifyAllDomains: this.notifyAllConnections.bind(this), + preferences: this.preferencesController.store, + showPermissionRequest: opts.showPermissionRequest, + }, + initState.PermissionsController, + initState.PermissionsMetadata, + ) this.detectTokensController = new DetectTokensController({ preferences: this.preferencesController, @@ -241,7 +259,10 @@ export default class MetamaskController extends EventEmitter { keyringMemStore: this.keyringController.memStore, }) - this.addressBookController = new AddressBookController(undefined, initState.AddressBookController) + this.addressBookController = new AddressBookController( + undefined, + initState.AddressBookController, + ) this.alertController = new AlertController({ initState: initState.AlertController, @@ -253,28 +274,41 @@ export default class MetamaskController extends EventEmitter { addressBookController: this.addressBookController, keyringController: this.keyringController, initState: initState.ThreeBoxController, - getKeyringControllerState: this.keyringController.memStore.getState.bind(this.keyringController.memStore), + getKeyringControllerState: this.keyringController.memStore.getState.bind( + this.keyringController.memStore, + ), version, }) this.txController = new TransactionController({ - initState: initState.TransactionController || initState.TransactionManager, - getPermittedAccounts: this.permissionsController.getAccounts.bind(this.permissionsController), + initState: + initState.TransactionController || initState.TransactionManager, + getPermittedAccounts: this.permissionsController.getAccounts.bind( + this.permissionsController, + ), networkStore: this.networkController.networkStore, - getCurrentChainId: this.networkController.getCurrentChainId.bind(this.networkController), + getCurrentChainId: this.networkController.getCurrentChainId.bind( + this.networkController, + ), preferencesStore: this.preferencesController.store, txHistoryLimit: 40, getNetwork: this.networkController.getNetworkState.bind(this), - signTransaction: this.keyringController.signTransaction.bind(this.keyringController), + signTransaction: this.keyringController.signTransaction.bind( + this.keyringController, + ), provider: this.provider, blockTracker: this.blockTracker, trackMetaMetricsEvent: this.trackMetaMetricsEvent, - getParticipateInMetrics: () => this.preferencesController.getParticipateInMetaMetrics(), + getParticipateInMetrics: () => + this.preferencesController.getParticipateInMetaMetrics(), }) this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx()) this.txController.on(`tx:status-update`, async (txId, status) => { - if (status === 'confirmed' || status === 'failed') { + if ( + status === TRANSACTION_STATUSES.CONFIRMED || + status === TRANSACTION_STATUSES.FAILED + ) { const txMeta = this.txController.txStateManager.getTx(txId) this.platform.showTransactionNotification(txMeta) @@ -306,14 +340,20 @@ export default class MetamaskController extends EventEmitter { this.decryptMessageManager = new DecryptMessageManager() this.encryptionPublicKeyManager = new EncryptionPublicKeyManager() this.typedMessageManager = new TypedMessageManager({ - getCurrentChainId: this.networkController.getCurrentChainId.bind(this.networkController), + getCurrentChainId: this.networkController.getCurrentChainId.bind( + this.networkController, + ), }) this.swapsController = new SwapsController({ - getBufferedGasLimit: this.txController.txGasUtil.getBufferedGasLimit.bind(this.txController.txGasUtil), + getBufferedGasLimit: this.txController.txGasUtil.getBufferedGasLimit.bind( + this.txController.txGasUtil, + ), networkController: this.networkController, provider: this.provider, - getProviderConfig: this.networkController.getProviderConfig.bind(this.networkController), + getProviderConfig: this.networkController.getProviderConfig.bind( + this.networkController, + ), tokenRatesStore: this.tokenRatesController.store, }) @@ -364,7 +404,8 @@ export default class MetamaskController extends EventEmitter { const password = process.env.CONF?.password if ( - password && !this.isUnlocked() && + password && + !this.isUnlocked() && this.onboardingController.completedOnboarding ) { this.submitPassword(password) @@ -374,7 +415,7 @@ export default class MetamaskController extends EventEmitter { /** * Constructor helper: initialize a provider. */ - initializeProvider () { + initializeProvider() { const version = this.platform.getVersion() const providerOpts = { static: { @@ -403,9 +444,15 @@ export default class MetamaskController extends EventEmitter { processDecryptMessage: this.newRequestDecryptMessage.bind(this), processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this), getPendingNonce: this.getPendingNonce.bind(this), - getPendingTransactionByHash: (hash) => this.txController.getFilteredTxList({ hash, status: 'submitted' })[0], + getPendingTransactionByHash: (hash) => + this.txController.getFilteredTxList({ + hash, + status: TRANSACTION_STATUSES.SUBMITTED, + })[0], } - const providerProxy = this.networkController.initializeProvider(providerOpts) + const providerProxy = this.networkController.initializeProvider( + providerOpts, + ) return providerProxy } @@ -413,7 +460,7 @@ export default class MetamaskController extends EventEmitter { * Constructor helper: initialize a public config store. * This store is used to make some config info available to Dapps synchronously. */ - createPublicConfigStore () { + createPublicConfigStore() { // subset of state for metamask inpage provider const publicConfigStore = new ObservableStore() const { networkController } = this @@ -423,17 +470,18 @@ export default class MetamaskController extends EventEmitter { updatePublicConfigStore(this.getState()) publicConfigStore.destroy = () => { - this.removeEventListener && this.removeEventListener('update', updatePublicConfigStore) + this.removeEventListener && + this.removeEventListener('update', updatePublicConfigStore) } - function updatePublicConfigStore (memState) { + function updatePublicConfigStore(memState) { const chainId = networkController.getCurrentChainId() if (memState.network !== 'loading') { publicConfigStore.putState(selectPublicState(chainId, memState)) } } - function selectPublicState (chainId, { isUnlocked, network }) { + function selectPublicState(chainId, { isUnlocked, network }) { return { isUnlocked, chainId, @@ -450,9 +498,9 @@ export default class MetamaskController extends EventEmitter { /** * The metamask-state of the various controllers, made available to the UI * - * @returns {Object} - status + * @returns {Object} status */ - getState () { + getState() { const { vault } = this.keyringController.store.getState() const isInitialized = Boolean(vault) @@ -467,9 +515,9 @@ export default class MetamaskController extends EventEmitter { * These functions are the interface for the UI. * The API object can be transmitted over a stream with dnode. * - * @returns {Object} - Object containing API functions. + * @returns {Object} Object containing API functions. */ - getApi () { + getApi() { const { keyringController, networkController, @@ -511,7 +559,10 @@ export default class MetamaskController extends EventEmitter { connectHardware: nodeify(this.connectHardware, this), forgetDevice: nodeify(this.forgetDevice, this), checkHardwareStatus: nodeify(this.checkHardwareStatus, this), - unlockHardwareWalletAccount: nodeify(this.unlockHardwareWalletAccount, this), + unlockHardwareWalletAccount: nodeify( + this.unlockHardwareWalletAccount, + this, + ), // mobile fetchInfoToSync: nodeify(this.fetchInfoToSync, this), @@ -521,45 +572,99 @@ export default class MetamaskController extends EventEmitter { verifyPassword: nodeify(this.verifyPassword, this), // network management - setProviderType: nodeify(networkController.setProviderType, networkController), + setProviderType: nodeify( + networkController.setProviderType, + networkController, + ), setCustomRpc: nodeify(this.setCustomRpc, this), updateAndSetCustomRpc: nodeify(this.updateAndSetCustomRpc, this), delCustomRpc: nodeify(this.delCustomRpc, this), // PreferencesController - setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController), + setSelectedAddress: nodeify( + preferencesController.setSelectedAddress, + preferencesController, + ), addToken: nodeify(preferencesController.addToken, preferencesController), - removeToken: nodeify(preferencesController.removeToken, preferencesController), - removeSuggestedTokens: nodeify(preferencesController.removeSuggestedTokens, preferencesController), - setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController), - setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController), - setPreference: nodeify(preferencesController.setPreference, preferencesController), - completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController), - addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController), + removeToken: nodeify( + preferencesController.removeToken, + preferencesController, + ), + removeSuggestedTokens: nodeify( + preferencesController.removeSuggestedTokens, + preferencesController, + ), + setAccountLabel: nodeify( + preferencesController.setAccountLabel, + preferencesController, + ), + setFeatureFlag: nodeify( + preferencesController.setFeatureFlag, + preferencesController, + ), + setPreference: nodeify( + preferencesController.setPreference, + preferencesController, + ), + completeOnboarding: nodeify( + preferencesController.completeOnboarding, + preferencesController, + ), + addKnownMethodData: nodeify( + preferencesController.addKnownMethodData, + preferencesController, + ), // AddressController - setAddressBook: nodeify(this.addressBookController.set, this.addressBookController), - removeFromAddressBook: nodeify(this.addressBookController.delete, this.addressBookController), + setAddressBook: nodeify( + this.addressBookController.set, + this.addressBookController, + ), + removeFromAddressBook: nodeify( + this.addressBookController.delete, + this.addressBookController, + ), // AppStateController - setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController), - setDefaultHomeActiveTabName: nodeify(this.appStateController.setDefaultHomeActiveTabName, this.appStateController), - setConnectedStatusPopoverHasBeenShown: nodeify(this.appStateController.setConnectedStatusPopoverHasBeenShown, this.appStateController), - setSwapsWelcomeMessageHasBeenShown: nodeify(this.appStateController.setSwapsWelcomeMessageHasBeenShown, this.appStateController), + setLastActiveTime: nodeify( + this.appStateController.setLastActiveTime, + this.appStateController, + ), + setDefaultHomeActiveTabName: nodeify( + this.appStateController.setDefaultHomeActiveTabName, + this.appStateController, + ), + setConnectedStatusPopoverHasBeenShown: nodeify( + this.appStateController.setConnectedStatusPopoverHasBeenShown, + this.appStateController, + ), + setSwapsWelcomeMessageHasBeenShown: nodeify( + this.appStateController.setSwapsWelcomeMessageHasBeenShown, + this.appStateController, + ), // EnsController - tryReverseResolveAddress: nodeify(this.ensController.reverseResolveAddress, this.ensController), + tryReverseResolveAddress: nodeify( + this.ensController.reverseResolveAddress, + this.ensController, + ), // KeyringController setLocked: nodeify(this.setLocked, this), createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this), createNewVaultAndRestore: nodeify(this.createNewVaultAndRestore, this), - exportAccount: nodeify(keyringController.exportAccount, keyringController), + exportAccount: nodeify( + keyringController.exportAccount, + keyringController, + ), // txController cancelTransaction: nodeify(txController.cancelTransaction, txController), updateTransaction: nodeify(txController.updateTransaction, txController), - updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController), + updateAndApproveTransaction: nodeify( + txController.updateAndApproveTransaction, + txController, + ), createCancelTransaction: nodeify(this.createCancelTransaction, this), createSpeedUpTransaction: nodeify(this.createSpeedUpTransaction, this), getFilteredTxList: nodeify(txController.getFilteredTxList, txController), @@ -567,7 +672,10 @@ export default class MetamaskController extends EventEmitter { estimateGas: nodeify(this.estimateGas, this), getPendingNonce: nodeify(this.getPendingNonce, this), getNextNonce: nodeify(this.getNextNonce, this), - addUnapprovedTransaction: nodeify(txController.addUnapprovedTransaction, txController), + addUnapprovedTransaction: nodeify( + txController.addUnapprovedTransaction, + txController, + ), // messageManager signMessage: nodeify(this.signMessage, this), @@ -591,47 +699,132 @@ export default class MetamaskController extends EventEmitter { cancelEncryptionPublicKey: this.cancelEncryptionPublicKey.bind(this), // onboarding controller - setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController), + setSeedPhraseBackedUp: nodeify( + onboardingController.setSeedPhraseBackedUp, + onboardingController, + ), // alert controller - setAlertEnabledness: nodeify(alertController.setAlertEnabledness, alertController), - setUnconnectedAccountAlertShown: nodeify(this.alertController.setUnconnectedAccountAlertShown, this.alertController), + setAlertEnabledness: nodeify( + alertController.setAlertEnabledness, + alertController, + ), + setUnconnectedAccountAlertShown: nodeify( + this.alertController.setUnconnectedAccountAlertShown, + this.alertController, + ), // 3Box - setThreeBoxSyncingPermission: nodeify(threeBoxController.setThreeBoxSyncingPermission, threeBoxController), - restoreFromThreeBox: nodeify(threeBoxController.restoreFromThreeBox, threeBoxController), - setShowRestorePromptToFalse: nodeify(threeBoxController.setShowRestorePromptToFalse, threeBoxController), - getThreeBoxLastUpdated: nodeify(threeBoxController.getLastUpdated, threeBoxController), - turnThreeBoxSyncingOn: nodeify(threeBoxController.turnThreeBoxSyncingOn, threeBoxController), + setThreeBoxSyncingPermission: nodeify( + threeBoxController.setThreeBoxSyncingPermission, + threeBoxController, + ), + restoreFromThreeBox: nodeify( + threeBoxController.restoreFromThreeBox, + threeBoxController, + ), + setShowRestorePromptToFalse: nodeify( + threeBoxController.setShowRestorePromptToFalse, + threeBoxController, + ), + getThreeBoxLastUpdated: nodeify( + threeBoxController.getLastUpdated, + threeBoxController, + ), + turnThreeBoxSyncingOn: nodeify( + threeBoxController.turnThreeBoxSyncingOn, + threeBoxController, + ), initializeThreeBox: nodeify(this.initializeThreeBox, this), // permissions - approvePermissionsRequest: nodeify(permissionsController.approvePermissionsRequest, permissionsController), - clearPermissions: permissionsController.clearPermissions.bind(permissionsController), - getApprovedAccounts: nodeify(permissionsController.getAccounts, permissionsController), - rejectPermissionsRequest: nodeify(permissionsController.rejectPermissionsRequest, permissionsController), - removePermissionsFor: permissionsController.removePermissionsFor.bind(permissionsController), - addPermittedAccount: nodeify(permissionsController.addPermittedAccount, permissionsController), - removePermittedAccount: nodeify(permissionsController.removePermittedAccount, permissionsController), - requestAccountsPermissionWithId: nodeify(permissionsController.requestAccountsPermissionWithId, permissionsController), + approvePermissionsRequest: nodeify( + permissionsController.approvePermissionsRequest, + permissionsController, + ), + clearPermissions: permissionsController.clearPermissions.bind( + permissionsController, + ), + getApprovedAccounts: nodeify( + permissionsController.getAccounts, + permissionsController, + ), + rejectPermissionsRequest: nodeify( + permissionsController.rejectPermissionsRequest, + permissionsController, + ), + removePermissionsFor: permissionsController.removePermissionsFor.bind( + permissionsController, + ), + addPermittedAccount: nodeify( + permissionsController.addPermittedAccount, + permissionsController, + ), + removePermittedAccount: nodeify( + permissionsController.removePermittedAccount, + permissionsController, + ), + requestAccountsPermissionWithId: nodeify( + permissionsController.requestAccountsPermissionWithId, + permissionsController, + ), // swaps - fetchAndSetQuotes: nodeify(swapsController.fetchAndSetQuotes, swapsController), - setSelectedQuoteAggId: nodeify(swapsController.setSelectedQuoteAggId, swapsController), - resetSwapsState: nodeify(swapsController.resetSwapsState, swapsController), + fetchAndSetQuotes: nodeify( + swapsController.fetchAndSetQuotes, + swapsController, + ), + setSelectedQuoteAggId: nodeify( + swapsController.setSelectedQuoteAggId, + swapsController, + ), + resetSwapsState: nodeify( + swapsController.resetSwapsState, + swapsController, + ), setSwapsTokens: nodeify(swapsController.setSwapsTokens, swapsController), setApproveTxId: nodeify(swapsController.setApproveTxId, swapsController), setTradeTxId: nodeify(swapsController.setTradeTxId, swapsController), - setSwapsTxGasPrice: nodeify(swapsController.setSwapsTxGasPrice, swapsController), - setSwapsTxGasLimit: nodeify(swapsController.setSwapsTxGasLimit, swapsController), - safeRefetchQuotes: nodeify(swapsController.safeRefetchQuotes, swapsController), - stopPollingForQuotes: nodeify(swapsController.stopPollingForQuotes, swapsController), - setBackgroundSwapRouteState: nodeify(swapsController.setBackgroundSwapRouteState, swapsController), - resetPostFetchState: nodeify(swapsController.resetPostFetchState, swapsController), - setSwapsErrorKey: nodeify(swapsController.setSwapsErrorKey, swapsController), - setInitialGasEstimate: nodeify(swapsController.setInitialGasEstimate, swapsController), - setCustomApproveTxData: nodeify(swapsController.setCustomApproveTxData, swapsController), - setSwapsLiveness: nodeify(swapsController.setSwapsLiveness, swapsController), + setSwapsTxGasPrice: nodeify( + swapsController.setSwapsTxGasPrice, + swapsController, + ), + setSwapsTxGasLimit: nodeify( + swapsController.setSwapsTxGasLimit, + swapsController, + ), + safeRefetchQuotes: nodeify( + swapsController.safeRefetchQuotes, + swapsController, + ), + stopPollingForQuotes: nodeify( + swapsController.stopPollingForQuotes, + swapsController, + ), + setBackgroundSwapRouteState: nodeify( + swapsController.setBackgroundSwapRouteState, + swapsController, + ), + resetPostFetchState: nodeify( + swapsController.resetPostFetchState, + swapsController, + ), + setSwapsErrorKey: nodeify( + swapsController.setSwapsErrorKey, + swapsController, + ), + setInitialGasEstimate: nodeify( + swapsController.setInitialGasEstimate, + swapsController, + ), + setCustomApproveTxData: nodeify( + swapsController.setCustomApproveTxData, + swapsController, + ), + setSwapsLiveness: nodeify( + swapsController.setSwapsLiveness, + swapsController, + ), } } @@ -649,11 +842,10 @@ export default class MetamaskController extends EventEmitter { * A keychain, or keyring, controls many accounts with a single backup and signing strategy. * For example, a mnemonic phrase can generate many accounts, and is a keyring. * - * @param {string} password - * - * @returns {Object} - vault + * @param {string} password + * @returns {Object} vault */ - async createNewVaultAndKeychain (password) { + async createNewVaultAndKeychain(password) { const releaseLock = await this.createVaultMutex.acquire() try { let vault @@ -674,10 +866,10 @@ export default class MetamaskController extends EventEmitter { /** * Create a new Vault and restore an existent keyring. - * @param {} password - * @param {} seed + * @param {string} password + * @param {string} seed */ - async createNewVaultAndRestore (password, seed) { + async createNewVaultAndRestore(password, seed) { const releaseLock = await this.createVaultMutex.acquire() try { let accounts, lastBalance @@ -700,13 +892,21 @@ export default class MetamaskController extends EventEmitter { this.txController.txStateManager.clearUnapprovedTxs() // create new vault - const vault = await keyringController.createNewVaultAndRestore(password, seed) + const vault = await keyringController.createNewVaultAndRestore( + password, + seed, + ) const ethQuery = new EthQuery(this.provider) accounts = await keyringController.getAccounts() - lastBalance = await this.getBalance(accounts[accounts.length - 1], ethQuery) + lastBalance = await this.getBalance( + accounts[accounts.length - 1], + ethQuery, + ) - const primaryKeyring = keyringController.getKeyringsByType('HD Key Tree')[0] + const primaryKeyring = keyringController.getKeyringsByType( + 'HD Key Tree', + )[0] if (!primaryKeyring) { throw new Error('MetamaskController - No HD Key Tree found') } @@ -715,7 +915,10 @@ export default class MetamaskController extends EventEmitter { while (lastBalance !== '0x0') { await keyringController.addNewAccount(primaryKeyring) accounts = await keyringController.getAccounts() - lastBalance = await this.getBalance(accounts[accounts.length - 1], ethQuery) + lastBalance = await this.getBalance( + accounts[accounts.length - 1], + ethQuery, + ) } // set new identities @@ -732,7 +935,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} address - The account address * @param {EthQuery} ethQuery - The EthQuery instance to use when asking the network */ - getBalance (address, ethQuery) { + getBalance(address, ethQuery) { return new Promise((resolve, reject) => { const cached = this.accountTracker.store.getState().accounts[address] @@ -758,9 +961,9 @@ export default class MetamaskController extends EventEmitter { /** * Collects all the information that we want to share * with the mobile client for syncing purposes - * @returns {Promise} - Parts of the state that we want to syncx + * @returns {Promise} Parts of the state that we want to syncx */ - async fetchInfoToSync () { + async fetchInfoToSync() { // Preferences const { accountTokens, @@ -777,14 +980,19 @@ export default class MetamaskController extends EventEmitter { const checksummedAddress = ethUtil.toChecksumAddress(address) filteredAccountTokens[checksummedAddress] = {} Object.keys(accountTokens[address]).forEach((networkType) => { - filteredAccountTokens[checksummedAddress][networkType] = networkType === 'mainnet' - ? ( - accountTokens[address][networkType].filter(({ address: tokenAddress }) => { - const checksumAddress = ethUtil.toChecksumAddress(tokenAddress) - return contractMap[checksumAddress] ? contractMap[checksumAddress].erc20 : true - }) - ) - : accountTokens[address][networkType] + filteredAccountTokens[checksummedAddress][networkType] = + networkType === 'mainnet' + ? accountTokens[address][networkType].filter( + ({ address: tokenAddress }) => { + const checksumAddress = ethUtil.toChecksumAddress( + tokenAddress, + ) + return contractMap[checksumAddress] + ? contractMap[checksumAddress].erc20 + : true + }, + ) + : accountTokens[address][networkType] }) }) @@ -799,15 +1007,24 @@ export default class MetamaskController extends EventEmitter { // Accounts const hdKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] - const simpleKeyPairKeyrings = this.keyringController.getKeyringsByType('Simple Key Pair') + const simpleKeyPairKeyrings = this.keyringController.getKeyringsByType( + 'Simple Key Pair', + ) const hdAccounts = await hdKeyring.getAccounts() const simpleKeyPairKeyringAccounts = await Promise.all( simpleKeyPairKeyrings.map((keyring) => keyring.getAccounts()), ) - const simpleKeyPairAccounts = simpleKeyPairKeyringAccounts.reduce((acc, accounts) => [...acc, ...accounts], []) + const simpleKeyPairAccounts = simpleKeyPairKeyringAccounts.reduce( + (acc, accounts) => [...acc, ...accounts], + [], + ) const accounts = { - hd: hdAccounts.filter((item, pos) => (hdAccounts.indexOf(item) === pos)).map((address) => ethUtil.toChecksumAddress(address)), - simpleKeyPair: simpleKeyPairAccounts.filter((item, pos) => (simpleKeyPairAccounts.indexOf(item) === pos)).map((address) => ethUtil.toChecksumAddress(address)), + hd: hdAccounts + .filter((item, pos) => hdAccounts.indexOf(item) === pos) + .map((address) => ethUtil.toChecksumAddress(address)), + simpleKeyPair: simpleKeyPairAccounts + .filter((item, pos) => simpleKeyPairAccounts.indexOf(item) === pos) + .map((address) => ethUtil.toChecksumAddress(address)), ledger: [], trezor: [], } @@ -818,9 +1035,7 @@ export default class MetamaskController extends EventEmitter { // delete tx for other accounts that we're not importing transactions = transactions.filter((tx) => { const checksummedTxFrom = ethUtil.toChecksumAddress(tx.txParams.from) - return ( - accounts.hd.includes(checksummedTxFrom) - ) + return accounts.hd.includes(checksummedTxFrom) }) return { @@ -837,9 +1052,9 @@ export default class MetamaskController extends EventEmitter { * is up to date with known accounts once the vault is decrypted. * * @param {string} password - The user's password - * @returns {Promise} - The keyringController update. + * @returns {Promise} The keyringController update. */ - async submitPassword (password) { + async submitPassword(password) { await this.keyringController.submitPassword(password) try { @@ -869,7 +1084,7 @@ export default class MetamaskController extends EventEmitter { * * @param {string} password The user's password */ - async verifyPassword (password) { + async verifyPassword(password) { await this.keyringController.verifyPassword(password) } @@ -884,7 +1099,7 @@ export default class MetamaskController extends EventEmitter { /** * Sets the first address in the state to the selected address */ - selectFirstIdentity () { + selectFirstIdentity() { const { identities } = this.preferencesController.store.getState() const address = Object.keys(identities)[0] this.preferencesController.setSelectedAddress(address) @@ -894,7 +1109,7 @@ export default class MetamaskController extends EventEmitter { // Hardware // - async getKeyringForDevice (deviceName, hdPath = null) { + async getKeyringForDevice(deviceName, hdPath = null) { let keyringName = null switch (deviceName) { case 'trezor': @@ -904,7 +1119,9 @@ export default class MetamaskController extends EventEmitter { keyringName = LedgerBridgeKeyring.type break default: - throw new Error('MetamaskController:getKeyringForDevice - Unknown device') + throw new Error( + 'MetamaskController:getKeyringForDevice - Unknown device', + ) } let keyring = await this.keyringController.getKeyringsByType(keyringName)[0] if (!keyring) { @@ -917,7 +1134,6 @@ export default class MetamaskController extends EventEmitter { keyring.network = this.networkController.getProviderConfig().type return keyring - } /** @@ -925,7 +1141,7 @@ export default class MetamaskController extends EventEmitter { * * @returns [] accounts */ - async connectHardware (deviceName, page, hdPath) { + async connectHardware(deviceName, page, hdPath) { const keyring = await this.getKeyringForDevice(deviceName, hdPath) let accounts = [] switch (page) { @@ -942,7 +1158,11 @@ export default class MetamaskController extends EventEmitter { // Merge with existing accounts // and make sure addresses are not repeated const oldAccounts = await this.keyringController.getAccounts() - const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map((a) => a.address.toLowerCase())))] + const accountsToTrack = [ + ...new Set( + oldAccounts.concat(accounts.map((a) => a.address.toLowerCase())), + ), + ] this.accountTracker.syncWithAddresses(accountsToTrack) return accounts } @@ -952,7 +1172,7 @@ export default class MetamaskController extends EventEmitter { * * @returns {Promise} */ - async checkHardwareStatus (deviceName, hdPath) { + async checkHardwareStatus(deviceName, hdPath) { const keyring = await this.getKeyringForDevice(deviceName, hdPath) return keyring.isUnlocked() } @@ -962,8 +1182,7 @@ export default class MetamaskController extends EventEmitter { * * @returns {Promise} */ - async forgetDevice (deviceName) { - + async forgetDevice(deviceName) { const keyring = await this.getKeyringForDevice(deviceName) keyring.forgetDevice() return true @@ -974,7 +1193,7 @@ export default class MetamaskController extends EventEmitter { * * @returns {} keyState */ - async unlockHardwareWalletAccount (index, deviceName, hdPath) { + async unlockHardwareWalletAccount(index, deviceName, hdPath) { const keyring = await this.getKeyringForDevice(deviceName, hdPath) keyring.setAccountToUnlock(index) @@ -985,7 +1204,12 @@ export default class MetamaskController extends EventEmitter { newAccounts.forEach((address) => { if (!oldAccounts.includes(address)) { // Set the account label to Trezor 1 / Ledger 1, etc - this.preferencesController.setAccountLabel(address, `${deviceName[0].toUpperCase()}${deviceName.slice(1)} ${parseInt(index, 10) + 1}`) + this.preferencesController.setAccountLabel( + address, + `${deviceName[0].toUpperCase()}${deviceName.slice(1)} ${ + parseInt(index, 10) + 1 + }`, + ) // Select the account this.preferencesController.setSelectedAddress(address) } @@ -1004,8 +1228,10 @@ export default class MetamaskController extends EventEmitter { * * @returns {} keyState */ - async addNewAccount () { - const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] + async addNewAccount() { + const primaryKeyring = this.keyringController.getKeyringsByType( + 'HD Key Tree', + )[0] if (!primaryKeyring) { throw new Error('MetamaskController - No HD Key Tree found') } @@ -1034,11 +1260,12 @@ export default class MetamaskController extends EventEmitter { * * Called when the first account is created and on unlocking the vault. * - * @returns {Promise} - Seed phrase to be confirmed by the user. + * @returns {Promise} Seed phrase to be confirmed by the user. */ - async verifySeedPhrase () { - - const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] + async verifySeedPhrase() { + const primaryKeyring = this.keyringController.getKeyringsByType( + 'HD Key Tree', + )[0] if (!primaryKeyring) { throw new Error('MetamaskController - No HD Key Tree found') } @@ -1065,9 +1292,9 @@ export default class MetamaskController extends EventEmitter { * Mostly used in development environments, when networks are restarted with * the same network ID. * - * @returns {Promise} - The current selected address. + * @returns {Promise} The current selected address. */ - async resetAccount () { + async resetAccount() { const selectedAddress = this.preferencesController.getSelectedAddress() this.txController.wipeTransactions(selectedAddress) this.networkController.resetConnection() @@ -1081,7 +1308,7 @@ export default class MetamaskController extends EventEmitter { * @param {string[]} address - A hex address * */ - async removeAccount (address) { + async removeAccount(address) { // Remove all associated permissions await this.permissionsController.removeAllAccountPermissions(address) // Remove account from the preferences controller @@ -1099,13 +1326,16 @@ export default class MetamaskController extends EventEmitter { * These are defined in app/scripts/account-import-strategies * Each strategy represents a different way of serializing an Ethereum key pair. * - * @param {string} strategy - A unique identifier for an account import strategy. - * @param {any} args - The data required by that strategy to import an account. - * @param {Function} cb - A callback function called with a state update on success. + * @param {string} strategy - A unique identifier for an account import strategy. + * @param {any} args - The data required by that strategy to import an account. + * @param {Function} cb - A callback function called with a state update on success. */ - async importAccountWithStrategy (strategy, args) { + async importAccountWithStrategy(strategy, args) { const privateKey = await accountImporter.importAccount(strategy, args) - const keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [privateKey]) + const keyring = await this.keyringController.addNewKeyring( + 'Simple Key Pair', + [privateKey], + ) const accounts = await keyring.getAccounts() // update accounts in preferences controller const allAccounts = await this.keyringController.getAccounts() @@ -1125,7 +1355,7 @@ export default class MetamaskController extends EventEmitter { * @param {Object} msgParams - The params passed to eth_sign. * @param {Object} req - (optional) the original request, containing the origin */ - async newUnapprovedTransaction (txParams, req) { + async newUnapprovedTransaction(txParams, req) { return await this.txController.newUnapprovedTransaction(txParams, req) } @@ -1138,10 +1368,13 @@ export default class MetamaskController extends EventEmitter { * information. * * @param {Object} msgParams - The params passed to eth_sign. - * @param {Function} cb = The callback function called with the signature. + * @param {Function} cb - The callback function called with the signature. */ - newUnsignedMessage (msgParams, req) { - const promise = this.messageManager.addUnapprovedMessageAsync(msgParams, req) + newUnsignedMessage(msgParams, req) { + const promise = this.messageManager.addUnapprovedMessageAsync( + msgParams, + req, + ) this.sendUpdate() this.opts.showUnconfirmedMessage() return promise @@ -1150,23 +1383,24 @@ export default class MetamaskController extends EventEmitter { /** * Signifies user intent to complete an eth_sign method. * - * @param {Object} msgParams - The params passed to eth_call. - * @returns {Promise} - Full state update. + * @param {Object} msgParams - The params passed to eth_call. + * @returns {Promise} Full state update. */ - signMessage (msgParams) { + signMessage(msgParams) { log.info('MetaMaskController - signMessage') const msgId = msgParams.metamaskId // sets the status op the message to 'approved' // and removes the metamaskId for signing - return this.messageManager.approveMessage(msgParams) + return this.messageManager + .approveMessage(msgParams) .then((cleanMsgParams) => { - // signs the message + // signs the message return this.keyringController.signMessage(cleanMsgParams) }) .then((rawSig) => { - // tells the listener that the message has been signed - // and can be returned to the dapp + // tells the listener that the message has been signed + // and can be returned to the dapp this.messageManager.setMsgStatusSigned(msgId, rawSig) return this.getState() }) @@ -1177,7 +1411,7 @@ export default class MetamaskController extends EventEmitter { * * @param {string} msgId - The id of the message to cancel. */ - cancelMessage (msgId, cb) { + cancelMessage(msgId, cb) { const { messageManager } = this messageManager.rejectMsg(msgId) if (!cb || typeof cb !== 'function') { @@ -1199,8 +1433,11 @@ export default class MetamaskController extends EventEmitter { * @param {Function} cb - The callback function called with the signature. * Passed back to the requesting Dapp. */ - async newUnsignedPersonalMessage (msgParams, req) { - const promise = this.personalMessageManager.addUnapprovedMessageAsync(msgParams, req) + async newUnsignedPersonalMessage(msgParams, req) { + const promise = this.personalMessageManager.addUnapprovedMessageAsync( + msgParams, + req, + ) this.sendUpdate() this.opts.showUnconfirmedMessage() return promise @@ -1211,21 +1448,22 @@ export default class MetamaskController extends EventEmitter { * Triggers signing, and the callback function from newUnsignedPersonalMessage. * * @param {Object} msgParams - The params of the message to sign & return to the Dapp. - * @returns {Promise} - A full state update. + * @returns {Promise} A full state update. */ - signPersonalMessage (msgParams) { + signPersonalMessage(msgParams) { log.info('MetaMaskController - signPersonalMessage') const msgId = msgParams.metamaskId // sets the status op the message to 'approved' // and removes the metamaskId for signing - return this.personalMessageManager.approveMessage(msgParams) + return this.personalMessageManager + .approveMessage(msgParams) .then((cleanMsgParams) => { - // signs the message + // signs the message return this.keyringController.signPersonalMessage(cleanMsgParams) }) .then((rawSig) => { - // tells the listener that the message has been signed - // and can be returned to the dapp + // tells the listener that the message has been signed + // and can be returned to the dapp this.personalMessageManager.setMsgStatusSigned(msgId, rawSig) return this.getState() }) @@ -1236,7 +1474,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} msgId - The ID of the message to cancel. * @param {Function} cb - The callback function called with a full state update. */ - cancelPersonalMessage (msgId, cb) { + cancelPersonalMessage(msgId, cb) { const messageManager = this.personalMessageManager messageManager.rejectMsg(msgId) if (!cb || typeof cb !== 'function') { @@ -1248,26 +1486,29 @@ export default class MetamaskController extends EventEmitter { // eth_decrypt methods /** - * Called when a dapp uses the eth_decrypt method. - * - * @param {Object} msgParams - The params of the message to sign & return to the Dapp. - * @param {Object} req - (optional) the original request, containing the origin - * Passed back to the requesting Dapp. - */ - async newRequestDecryptMessage (msgParams, req) { - const promise = this.decryptMessageManager.addUnapprovedMessageAsync(msgParams, req) + * Called when a dapp uses the eth_decrypt method. + * + * @param {Object} msgParams - The params of the message to sign & return to the Dapp. + * @param {Object} req - (optional) the original request, containing the origin + * Passed back to the requesting Dapp. + */ + async newRequestDecryptMessage(msgParams, req) { + const promise = this.decryptMessageManager.addUnapprovedMessageAsync( + msgParams, + req, + ) this.sendUpdate() this.opts.showUnconfirmedMessage() return promise } /** - * Only decrypt message and don't touch transaction state - * - * @param {Object} msgParams - The params of the message to decrypt. - * @returns {Promise} - A full state update. - */ - async decryptMessageInline (msgParams) { + * Only decrypt message and don't touch transaction state + * + * @param {Object} msgParams - The params of the message to decrypt. + * @returns {Promise} A full state update. + */ + async decryptMessageInline(msgParams) { log.info('MetaMaskController - decryptMessageInline') // decrypt the message inline const msgId = msgParams.metamaskId @@ -1287,26 +1528,30 @@ export default class MetamaskController extends EventEmitter { } /** - * Signifies a user's approval to decrypt a message in queue. - * Triggers decrypt, and the callback function from newUnsignedDecryptMessage. - * - * @param {Object} msgParams - The params of the message to decrypt & return to the Dapp. - * @returns {Promise} - A full state update. - */ - async decryptMessage (msgParams) { + * Signifies a user's approval to decrypt a message in queue. + * Triggers decrypt, and the callback function from newUnsignedDecryptMessage. + * + * @param {Object} msgParams - The params of the message to decrypt & return to the Dapp. + * @returns {Promise} A full state update. + */ + async decryptMessage(msgParams) { log.info('MetaMaskController - decryptMessage') const msgId = msgParams.metamaskId // sets the status op the message to 'approved' // and removes the metamaskId for decryption try { - const cleanMsgParams = await this.decryptMessageManager.approveMessage(msgParams) + const cleanMsgParams = await this.decryptMessageManager.approveMessage( + msgParams, + ) const stripped = ethUtil.stripHexPrefix(cleanMsgParams.data) const buff = Buffer.from(stripped, 'hex') cleanMsgParams.data = JSON.parse(buff.toString('utf8')) // decrypt the message - const rawMess = await this.keyringController.decryptMessage(cleanMsgParams) + const rawMess = await this.keyringController.decryptMessage( + cleanMsgParams, + ) // tells the listener that the message has been decrypted and can be returned to the dapp this.decryptMessageManager.setMsgStatusDecrypted(msgId, rawMess) } catch (error) { @@ -1321,7 +1566,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} msgId - The ID of the message to cancel. * @param {Function} cb - The callback function called with a full state update. */ - cancelDecryptMessage (msgId, cb) { + cancelDecryptMessage(msgId, cb) { const messageManager = this.decryptMessageManager messageManager.rejectMsg(msgId) if (!cb || typeof cb !== 'function') { @@ -1333,36 +1578,43 @@ export default class MetamaskController extends EventEmitter { // eth_getEncryptionPublicKey methods /** - * Called when a dapp uses the eth_getEncryptionPublicKey method. - * - * @param {Object} msgParams - The params of the message to sign & return to the Dapp. - * @param {Object} req - (optional) the original request, containing the origin - * Passed back to the requesting Dapp. - */ - async newRequestEncryptionPublicKey (msgParams, req) { - const promise = this.encryptionPublicKeyManager.addUnapprovedMessageAsync(msgParams, req) + * Called when a dapp uses the eth_getEncryptionPublicKey method. + * + * @param {Object} msgParams - The params of the message to sign & return to the Dapp. + * @param {Object} req - (optional) the original request, containing the origin + * Passed back to the requesting Dapp. + */ + async newRequestEncryptionPublicKey(msgParams, req) { + const promise = this.encryptionPublicKeyManager.addUnapprovedMessageAsync( + msgParams, + req, + ) this.sendUpdate() this.opts.showUnconfirmedMessage() return promise } /** - * Signifies a user's approval to receiving encryption public key in queue. - * Triggers receiving, and the callback function from newUnsignedEncryptionPublicKey. - * - * @param {Object} msgParams - The params of the message to receive & return to the Dapp. - * @returns {Promise} - A full state update. - */ - async encryptionPublicKey (msgParams) { + * Signifies a user's approval to receiving encryption public key in queue. + * Triggers receiving, and the callback function from newUnsignedEncryptionPublicKey. + * + * @param {Object} msgParams - The params of the message to receive & return to the Dapp. + * @returns {Promise} A full state update. + */ + async encryptionPublicKey(msgParams) { log.info('MetaMaskController - encryptionPublicKey') const msgId = msgParams.metamaskId // sets the status op the message to 'approved' // and removes the metamaskId for decryption try { - const params = await this.encryptionPublicKeyManager.approveMessage(msgParams) + const params = await this.encryptionPublicKeyManager.approveMessage( + msgParams, + ) // EncryptionPublicKey message - const publicKey = await this.keyringController.getEncryptionPublicKey(params.data) + const publicKey = await this.keyringController.getEncryptionPublicKey( + params.data, + ) // tells the listener that the message has been processed // and can be returned to the dapp @@ -1379,7 +1631,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} msgId - The ID of the message to cancel. * @param {Function} cb - The callback function called with a full state update. */ - cancelEncryptionPublicKey (msgId, cb) { + cancelEncryptionPublicKey(msgId, cb) { const messageManager = this.encryptionPublicKeyManager messageManager.rejectMsg(msgId) if (!cb || typeof cb !== 'function') { @@ -1396,8 +1648,12 @@ export default class MetamaskController extends EventEmitter { * @param {Object} msgParams - The params passed to eth_signTypedData. * @param {Function} cb - The callback function, called with the signature. */ - newUnsignedTypedMessage (msgParams, req, version) { - const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req, version) + newUnsignedTypedMessage(msgParams, req, version) { + const promise = this.typedMessageManager.addUnapprovedMessageAsync( + msgParams, + req, + version, + ) this.sendUpdate() this.opts.showUnconfirmedMessage() return promise @@ -1407,15 +1663,17 @@ export default class MetamaskController extends EventEmitter { * The method for a user approving a call to eth_signTypedData, per EIP 712. * Triggers the callback in newUnsignedTypedMessage. * - * @param {Object} msgParams - The params passed to eth_signTypedData. - * @returns {Object|undefined} - Full state update. + * @param {Object} msgParams - The params passed to eth_signTypedData. + * @returns {Object} Full state update. */ - async signTypedMessage (msgParams) { + async signTypedMessage(msgParams) { log.info('MetaMaskController - eth_signTypedData') const msgId = msgParams.metamaskId const { version } = msgParams try { - const cleanMsgParams = await this.typedMessageManager.approveMessage(msgParams) + const cleanMsgParams = await this.typedMessageManager.approveMessage( + msgParams, + ) // For some reason every version after V1 used stringified params. if (version !== 'V1') { @@ -1425,7 +1683,10 @@ export default class MetamaskController extends EventEmitter { } } - const signature = await this.keyringController.signTypedMessage(cleanMsgParams, { version }) + const signature = await this.keyringController.signTypedMessage( + cleanMsgParams, + { version }, + ) this.typedMessageManager.setMsgStatusSigned(msgId, signature) return this.getState() } catch (error) { @@ -1440,7 +1701,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} msgId - The ID of the message to cancel. * @param {Function} cb - The callback function called with a full state update. */ - cancelTypedMessage (msgId, cb) { + cancelTypedMessage(msgId, cb) { const messageManager = this.typedMessageManager messageManager.rejectMsg(msgId) if (!cb || typeof cb !== 'function') { @@ -1458,29 +1719,39 @@ export default class MetamaskController extends EventEmitter { * transaction. * @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel * @param {string} [customGasPrice] - the hex value to use for the cancel transaction - * @returns {Object} - MetaMask state + * @returns {Object} MetaMask state */ - async createCancelTransaction (originalTxId, customGasPrice) { - await this.txController.createCancelTransaction(originalTxId, customGasPrice) + async createCancelTransaction(originalTxId, customGasPrice) { + await this.txController.createCancelTransaction( + originalTxId, + customGasPrice, + ) const state = await this.getState() return state } - async createSpeedUpTransaction (originalTxId, customGasPrice, customGasLimit) { - await this.txController.createSpeedUpTransaction(originalTxId, customGasPrice, customGasLimit) + async createSpeedUpTransaction(originalTxId, customGasPrice, customGasLimit) { + await this.txController.createSpeedUpTransaction( + originalTxId, + customGasPrice, + customGasLimit, + ) const state = await this.getState() return state } - estimateGas (estimateGasParams) { + estimateGas(estimateGasParams) { return new Promise((resolve, reject) => { - return this.txController.txGasUtil.query.estimateGas(estimateGasParams, (err, res) => { - if (err) { - return reject(err) - } + return this.txController.txGasUtil.query.estimateGas( + estimateGasParams, + (err, res) => { + if (err) { + return reject(err) + } - return resolve(res) - }) + return resolve(res) + }, + ) }) } @@ -1492,7 +1763,7 @@ export default class MetamaskController extends EventEmitter { * Allows a user to begin the seed phrase recovery process. * @param {Function} cb - A callback function called when complete. */ - markPasswordForgotten (cb) { + markPasswordForgotten(cb) { this.preferencesController.setPasswordForgotten(true) this.sendUpdate() cb() @@ -1502,7 +1773,7 @@ export default class MetamaskController extends EventEmitter { * Allows a user to end the seed phrase recovery process. * @param {Function} cb - A callback function called when complete. */ - unMarkPasswordForgotten (cb) { + unMarkPasswordForgotten(cb) { this.preferencesController.setPasswordForgotten(false) this.sendUpdate() cb() @@ -1524,7 +1795,7 @@ export default class MetamaskController extends EventEmitter { * @param {*} connectionStream - The Duplex stream to connect to. * @param {MessageSender} sender - The sender of the messages on this stream */ - setupUntrustedCommunication (connectionStream, sender) { + setupUntrustedCommunication(connectionStream, sender) { const { usePhishDetect } = this.preferencesController.store.getState() const { hostname } = new URL(sender.url) // Check if new connection is blocked if phishing detection is on @@ -1551,7 +1822,7 @@ export default class MetamaskController extends EventEmitter { * @param {*} connectionStream - The duplex stream to connect to. * @param {MessageSender} sender - The sender of the messages on this stream */ - setupTrustedCommunication (connectionStream, sender) { + setupTrustedCommunication(connectionStream, sender) { // setup multiplexing const mux = setupMultiplex(connectionStream) // connect features @@ -1568,7 +1839,7 @@ export default class MetamaskController extends EventEmitter { * for sending the reload attempt to. * @param {string} hostname - The hostname that triggered the suspicion. */ - sendPhishingWarning (connectionStream, hostname) { + sendPhishingWarning(connectionStream, hostname) { const mux = setupMultiplex(connectionStream) const phishingStream = mux.createStream('phishing') phishingStream.write({ hostname }) @@ -1578,27 +1849,22 @@ export default class MetamaskController extends EventEmitter { * A method for providing our API over a stream using Dnode. * @param {*} outStream - The stream to provide our API over. */ - setupControllerConnection (outStream) { + setupControllerConnection(outStream) { const api = this.getApi() const dnode = Dnode(api) // report new active controller connection this.activeControllerConnections += 1 this.emit('controllerConnectionChanged', this.activeControllerConnections) // connect dnode api to remote connection - pump( - outStream, - dnode, - outStream, - (err) => { - // report new active controller connection - this.activeControllerConnections -= 1 - this.emit('controllerConnectionChanged', this.activeControllerConnections) - // report any error - if (err) { - log.error(err) - } - }, - ) + pump(outStream, dnode, outStream, (err) => { + // report new active controller connection + this.activeControllerConnections -= 1 + this.emit('controllerConnectionChanged', this.activeControllerConnections) + // report any error + if (err) { + log.error(err) + } + }) dnode.on('remote', (remote) => { // push updates to popup const sendUpdate = (update) => remote.sendUpdate(update) @@ -1614,10 +1880,8 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender} sender - The sender of the messages on this stream * @param {boolean} isInternal - True if this is a connection with an internal process */ - setupProviderConnection (outStream, sender, isInternal) { - const origin = isInternal - ? 'metamask' - : (new URL(sender.url)).origin + setupProviderConnection(outStream, sender, isInternal) { + const origin = isInternal ? 'metamask' : new URL(sender.url).origin let extensionId if (sender.id !== this.extension.runtime.id) { extensionId = sender.id @@ -1627,30 +1891,31 @@ export default class MetamaskController extends EventEmitter { tabId = sender.tab.id } - const engine = this.setupProviderEngine({ origin, location: sender.url, extensionId, tabId, isInternal }) + const engine = this.setupProviderEngine({ + origin, + location: sender.url, + extensionId, + tabId, + isInternal, + }) // setup connection const providerStream = createEngineStream({ engine }) const connectionId = this.addConnection(origin, { engine }) - pump( - outStream, - providerStream, - outStream, - (err) => { - // handle any middleware cleanup - engine._middleware.forEach((mid) => { - if (mid.destroy && typeof mid.destroy === 'function') { - mid.destroy() - } - }) - connectionId && this.removeConnection(origin, connectionId) - if (err) { - log.error(err) + pump(outStream, providerStream, outStream, (err) => { + // handle any middleware cleanup + engine._middleware.forEach((mid) => { + if (mid.destroy && typeof mid.destroy === 'function') { + mid.destroy() } - }, - ) + }) + connectionId && this.removeConnection(origin, connectionId) + if (err) { + log.error(err) + } + }) } /** @@ -1662,7 +1927,13 @@ export default class MetamaskController extends EventEmitter { * @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab * @param {boolean} [options.isInternal] - True if called for a connection to an internal process **/ - setupProviderEngine ({ origin, location, extensionId, tabId, isInternal = false }) { + setupProviderEngine({ + origin, + location, + extensionId, + tabId, + isInternal = false, + }) { // setup json rpc engine stack const engine = new RpcEngine() const { provider, blockTracker } = this @@ -1671,8 +1942,13 @@ export default class MetamaskController extends EventEmitter { const filterMiddleware = createFilterMiddleware({ provider, blockTracker }) // create subscription polyfill middleware - const subscriptionManager = createSubscriptionManager({ provider, blockTracker }) - subscriptionManager.events.on('notification', (message) => engine.emit('notification', message)) + const subscriptionManager = createSubscriptionManager({ + provider, + blockTracker, + }) + subscriptionManager.events.on('notification', (message) => + engine.emit('notification', message), + ) // append origin to each request engine.push(createOriginMiddleware({ origin })) @@ -1682,23 +1958,33 @@ export default class MetamaskController extends EventEmitter { } // logging engine.push(createLoggerMiddleware({ origin })) - engine.push(createOnboardingMiddleware({ - location, - registerOnboarding: this.onboardingController.registerOnboarding, - })) - engine.push(createMethodMiddleware({ - origin, - sendMetrics: this.trackMetaMetricsEvent, - })) + engine.push( + createOnboardingMiddleware({ + location, + registerOnboarding: this.onboardingController.registerOnboarding, + }), + ) + engine.push( + createMethodMiddleware({ + origin, + sendMetrics: this.trackMetaMetricsEvent, + }), + ) // filter and subscription polyfills engine.push(filterMiddleware) engine.push(subscriptionManager.middleware) if (!isInternal) { // permissions - engine.push(this.permissionsController.createMiddleware({ origin, extensionId })) + engine.push( + this.permissionsController.createMiddleware({ origin, extensionId }), + ) } // watch asset - engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) + engine.push( + this.preferencesController.requestWatchAsset.bind( + this.preferencesController, + ), + ) // forward to metamask primary provider engine.push(providerAsMiddleware(provider)) return engine @@ -1714,21 +2000,17 @@ export default class MetamaskController extends EventEmitter { * * @param {*} outStream - The stream to provide public config over. */ - setupPublicConfig (outStream) { + setupPublicConfig(outStream) { const configStore = this.createPublicConfigStore() const configStream = asStream(configStore) - pump( - configStream, - outStream, - (err) => { - configStore.destroy() - configStream.destroy() - if (err) { - log.error(err) - } - }, - ) + pump(configStream, outStream, (err) => { + configStore.destroy() + configStream.destroy() + if (err) { + log.error(err) + } + }) } /** @@ -1739,10 +2021,9 @@ export default class MetamaskController extends EventEmitter { * @param {string} origin - The connection's origin string. * @param {Object} options - Data associated with the connection * @param {Object} options.engine - The connection's JSON Rpc Engine - * @returns {string} - The connection's id (so that it can be deleted later) + * @returns {string} The connection's id (so that it can be deleted later) */ - addConnection (origin, { engine }) { - + addConnection(origin, { engine }) { if (origin === 'metamask') { return null } @@ -1766,8 +2047,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} origin - The connection's origin string. * @param {string} id - The connection's id, as returned from addConnection. */ - removeConnection (origin, id) { - + removeConnection(origin, id) { const connections = this.connections[origin] if (!connections) { return @@ -1788,8 +2068,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} origin - The connection's origin string. * @param {any} payload - The event payload. */ - notifyConnections (origin, payload) { - + notifyConnections(origin, payload) { const connections = this.connections[origin] if (!this.isUnlocked() || !connections) { return @@ -1807,8 +2086,7 @@ export default class MetamaskController extends EventEmitter { * * @param {any} payload - The event payload. */ - notifyAllConnections (payload) { - + notifyAllConnections(payload) { if (!this.isUnlocked()) { return } @@ -1828,9 +2106,12 @@ export default class MetamaskController extends EventEmitter { * @returns {Promise} * @private */ - async _onKeyringControllerUpdate (state) { + async _onKeyringControllerUpdate(state) { const { keyrings } = state - const addresses = keyrings.reduce((acc, { accounts }) => acc.concat(accounts), []) + const addresses = keyrings.reduce( + (acc, { accounts }) => acc.concat(accounts), + [], + ) if (!addresses.length) { return @@ -1847,14 +2128,14 @@ export default class MetamaskController extends EventEmitter { * A method for emitting the full MetaMask state to all registered listeners. * @private */ - privateSendUpdate () { + privateSendUpdate() { this.emit('update', this.getState()) } /** * @returns {boolean} Whether the extension is unlocked. */ - isUnlocked () { + isUnlocked() { return this.keyringController.memStore.getState().isUnlocked } @@ -1867,8 +2148,11 @@ export default class MetamaskController extends EventEmitter { * @param {string} address - The hex string address for the transaction * @returns {Promise} */ - async getPendingNonce (address) { - const { nonceDetails, releaseLock } = await this.txController.nonceTracker.getNonceLock(address) + async getPendingNonce(address) { + const { + nonceDetails, + releaseLock, + } = await this.txController.nonceTracker.getNonceLock(address) const pendingNonce = nonceDetails.params.highestSuggested releaseLock() @@ -1880,20 +2164,21 @@ export default class MetamaskController extends EventEmitter { * @param {string} address - The hex string address for the transaction * @returns {Promise} */ - async getNextNonce (address) { + async getNextNonce(address) { const nonceLock = await this.txController.nonceTracker.getNonceLock(address) nonceLock.releaseLock() return nonceLock.nextNonce } - async sendBackgroundMetaMetrics ({ action, name, customVariables } = {}) { - + async sendBackgroundMetaMetrics({ action, name, customVariables } = {}) { if (!action || !name) { throw new Error('Must provide action and name.') } const metamaskState = await this.getState() - const additionalProperties = getBackgroundMetaMetricState(metamaskState) + const additionalProperties = getBackgroundMetaMetricState({ + metamask: metamaskState, + }) this.trackMetaMetricsEvent({ event: name, @@ -1923,7 +2208,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} newChainId - The new chainId * @param {boolean} [duplicate] - Whether to duplicate the addresses on both chainIds (default: false) */ - async migrateAddressBookState (oldChainId, newChainId, duplicate = false) { + async migrateAddressBookState(oldChainId, newChainId, duplicate = false) { const { addressBook } = this.addressBookController.state if (!addressBook[oldChainId]) { @@ -1932,7 +2217,12 @@ export default class MetamaskController extends EventEmitter { for (const address of Object.keys(addressBook[oldChainId])) { const entry = addressBook[oldChainId][address] - this.addressBookController.set(address, entry.name, newChainId, entry.memo) + this.addressBookController.set( + address, + entry.name, + newChainId, + entry.memo, + ) if (!duplicate) { this.addressBookController.delete(oldChainId, address) } @@ -1950,7 +2240,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} currencyCode - The code of the preferred currency. * @param {Function} cb - A callback function returning currency info. */ - setCurrentCurrency (currencyCode, cb) { + setCurrentCurrency(currencyCode, cb) { const { ticker } = this.networkController.getProviderConfig() try { const currencyState = { @@ -1974,11 +2264,29 @@ export default class MetamaskController extends EventEmitter { * @param {string} chainId - The chainId of the selected network. * @param {string} ticker - The ticker symbol of the selected network. * @param {string} nickname - Optional nickname of the selected network. - * @returns {Promise} - The RPC Target URL confirmed. + * @returns {Promise} The RPC Target URL confirmed. */ - async updateAndSetCustomRpc (rpcUrl, chainId, ticker = 'ETH', nickname, rpcPrefs) { - await this.preferencesController.updateRpc({ rpcUrl, chainId, ticker, nickname, rpcPrefs }) - this.networkController.setRpcTarget(rpcUrl, chainId, ticker, nickname, rpcPrefs) + async updateAndSetCustomRpc( + rpcUrl, + chainId, + ticker = 'ETH', + nickname, + rpcPrefs, + ) { + await this.preferencesController.updateRpc({ + rpcUrl, + chainId, + ticker, + nickname, + rpcPrefs, + }) + this.networkController.setRpcTarget( + rpcUrl, + chainId, + ticker, + nickname, + rpcPrefs, + ) return rpcUrl } @@ -1988,17 +2296,43 @@ export default class MetamaskController extends EventEmitter { * @param {string} chainId - The chainId of the selected network. * @param {string} ticker - The ticker symbol of the selected network. * @param {string} nickname - Optional nickname of the selected network. - * @returns {Promise} - The RPC Target URL confirmed. + * @returns {Promise} The RPC Target URL confirmed. */ - async setCustomRpc (rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) { + async setCustomRpc( + rpcUrl, + chainId, + ticker = 'ETH', + nickname = '', + rpcPrefs = {}, + ) { const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail() - const rpcSettings = frequentRpcListDetail.find((rpc) => rpcUrl === rpc.rpcUrl) + const rpcSettings = frequentRpcListDetail.find( + (rpc) => rpcUrl === rpc.rpcUrl, + ) if (rpcSettings) { - this.networkController.setRpcTarget(rpcSettings.rpcUrl, rpcSettings.chainId, rpcSettings.ticker, rpcSettings.nickname, rpcPrefs) + this.networkController.setRpcTarget( + rpcSettings.rpcUrl, + rpcSettings.chainId, + rpcSettings.ticker, + rpcSettings.nickname, + rpcPrefs, + ) } else { - this.networkController.setRpcTarget(rpcUrl, chainId, ticker, nickname, rpcPrefs) - await this.preferencesController.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs) + this.networkController.setRpcTarget( + rpcUrl, + chainId, + ticker, + nickname, + rpcPrefs, + ) + await this.preferencesController.addToFrequentRpcList( + rpcUrl, + chainId, + ticker, + nickname, + rpcPrefs, + ) } return rpcUrl } @@ -2007,11 +2341,11 @@ export default class MetamaskController extends EventEmitter { * A method for deleting a selected custom URL. * @param {string} rpcUrl - A RPC URL to delete. */ - async delCustomRpc (rpcUrl) { + async delCustomRpc(rpcUrl) { await this.preferencesController.removeFromFrequentRpcList(rpcUrl) } - async initializeThreeBox () { + async initializeThreeBox() { await this.threeBoxController.init() } @@ -2020,7 +2354,7 @@ export default class MetamaskController extends EventEmitter { * @param {boolean} val - True for bockie, false for jazzicon. * @param {Function} cb - A callback function called when complete. */ - setUseBlockie (val, cb) { + setUseBlockie(val, cb) { try { this.preferencesController.setUseBlockie(val) cb(null) @@ -2037,7 +2371,7 @@ export default class MetamaskController extends EventEmitter { * @param {boolean} val - True for nonce field, false for not nonce field. * @param {Function} cb - A callback function called when complete. */ - setUseNonceField (val, cb) { + setUseNonceField(val, cb) { try { this.preferencesController.setUseNonceField(val) cb(null) @@ -2054,7 +2388,7 @@ export default class MetamaskController extends EventEmitter { * @param {boolean} val * @param {Function} cb */ - setUsePhishDetect (val, cb) { + setUsePhishDetect(val, cb) { try { this.preferencesController.setUsePhishDetect(val) cb(null) @@ -2071,7 +2405,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} val - the host of the gateway to set * @param {Function} cb - A callback function called when complete. */ - setIpfsGateway (val, cb) { + setIpfsGateway(val, cb) { try { this.preferencesController.setIpfsGateway(val) cb(null) @@ -2088,9 +2422,11 @@ export default class MetamaskController extends EventEmitter { * @param {boolean} bool - True for users that wish to opt-in, false for users that wish to remain out. * @param {Function} cb - A callback function called when complete. */ - setParticipateInMetaMetrics (bool, cb) { + setParticipateInMetaMetrics(bool, cb) { try { - const metaMetricsId = this.preferencesController.setParticipateInMetaMetrics(bool) + const metaMetricsId = this.preferencesController.setParticipateInMetaMetrics( + bool, + ) cb(null, metaMetricsId) return } catch (err) { @@ -2100,7 +2436,7 @@ export default class MetamaskController extends EventEmitter { } } - setMetaMetricsSendCount (val, cb) { + setMetaMetricsSendCount(val, cb) { try { this.preferencesController.setMetaMetricsSendCount(val) cb(null) @@ -2117,7 +2453,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} type - Indicates the type of first time flow the user wishes to follow * @param {Function} cb - A callback function called when complete. */ - setFirstTimeFlowType (type, cb) { + setFirstTimeFlowType(type, cb) { try { this.preferencesController.setFirstTimeFlowType(type) cb(null) @@ -2134,7 +2470,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} key - Locale identifier. * @param {Function} cb - A callback function called when complete. */ - setCurrentLocale (key, cb) { + setCurrentLocale(key, cb) { try { const direction = this.preferencesController.setCurrentLocale(key) cb(null, direction) @@ -2151,7 +2487,7 @@ export default class MetamaskController extends EventEmitter { * @param {Object} initState - The default state to initialize with. * @private */ - recordFirstTimeInfo (initState) { + recordFirstTimeInfo(initState) { if (!('firstTimeInfo' in initState)) { const version = this.platform.getVersion() initState.firstTimeInfo = { @@ -2168,33 +2504,24 @@ export default class MetamaskController extends EventEmitter { * @private * @param {boolean} open */ - set isClientOpen (open) { + set isClientOpen(open) { this._isClientOpen = open this.detectTokensController.isOpen = open } /* eslint-enable accessor-pairs */ - /** - * Creates RPC engine middleware for processing eth_signTypedData requests - * - * @param {Object} req - request object - * @param {Object} res - response object - * @param {Function} - next - * @param {Function} - end - */ - /** * Adds a domain to the PhishingController safelist * @param {string} hostname - the domain to safelist */ - safelistPhishingDomain (hostname) { + safelistPhishingDomain(hostname) { return this.phishingController.bypass(hostname) } /** * Locks MetaMask */ - setLocked () { + setLocked() { return this.keyringController.setLocked() } } diff --git a/app/scripts/migrations/002.js b/app/scripts/migrations/002.js index 24df7f4de..9de898f83 100644 --- a/app/scripts/migrations/002.js +++ b/app/scripts/migrations/002.js @@ -5,13 +5,14 @@ const version = 2 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { if (versionedData.data.config.provider.type === 'etherscan') { versionedData.data.config.provider.type = 'rpc' - versionedData.data.config.provider.rpcTarget = 'https://rpc.metamask.io/' + versionedData.data.config.provider.rpcTarget = + 'https://rpc.metamask.io/' } } catch (_) { // empty diff --git a/app/scripts/migrations/003.js b/app/scripts/migrations/003.js index 08e4be468..8859f2a0f 100644 --- a/app/scripts/migrations/003.js +++ b/app/scripts/migrations/003.js @@ -7,7 +7,7 @@ const newTestRpc = 'https://testrpc.metamask.io/' export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { diff --git a/app/scripts/migrations/004.js b/app/scripts/migrations/004.js index 48741a267..c571e8fd1 100644 --- a/app/scripts/migrations/004.js +++ b/app/scripts/migrations/004.js @@ -5,7 +5,7 @@ const version = 4 export default { version, - migrate (versionedData) { + migrate(versionedData) { const safeVersionedData = cloneDeep(versionedData) safeVersionedData.meta.version = version try { diff --git a/app/scripts/migrations/005.js b/app/scripts/migrations/005.js index baead6890..757462353 100644 --- a/app/scripts/migrations/005.js +++ b/app/scripts/migrations/005.js @@ -11,7 +11,7 @@ const version = 5 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function selectSubstateForKeyringController (state) { +function selectSubstateForKeyringController(state) { const { config } = state const newState = { ...state, diff --git a/app/scripts/migrations/006.js b/app/scripts/migrations/006.js index 8d1e72935..90ff3f2cb 100644 --- a/app/scripts/migrations/006.js +++ b/app/scripts/migrations/006.js @@ -11,7 +11,7 @@ const version = 6 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function migrateState (state) { +function migrateState(state) { const keyringSubstate = state.KeyringController // add new state diff --git a/app/scripts/migrations/007.js b/app/scripts/migrations/007.js index 41272ee3b..cea831b7e 100644 --- a/app/scripts/migrations/007.js +++ b/app/scripts/migrations/007.js @@ -11,7 +11,7 @@ const version = 7 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = { ...state, TransactionManager: { diff --git a/app/scripts/migrations/008.js b/app/scripts/migrations/008.js index 6f25bbcd5..ce8ab37ce 100644 --- a/app/scripts/migrations/008.js +++ b/app/scripts/migrations/008.js @@ -11,7 +11,7 @@ const version = 8 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = { ...state, NoticeController: { diff --git a/app/scripts/migrations/009.js b/app/scripts/migrations/009.js index f63297836..2d1370bfc 100644 --- a/app/scripts/migrations/009.js +++ b/app/scripts/migrations/009.js @@ -11,7 +11,7 @@ const version = 9 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = merge({}, state, { CurrencyController: { currentCurrency: state.currentFiat || state.fiatCurrency || 'USD', diff --git a/app/scripts/migrations/010.js b/app/scripts/migrations/010.js index 354b19871..053af2562 100644 --- a/app/scripts/migrations/010.js +++ b/app/scripts/migrations/010.js @@ -11,7 +11,7 @@ const version = 10 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = merge({}, state, { ShapeShiftController: { shapeShiftTxList: state.shapeShiftTxList || [], diff --git a/app/scripts/migrations/011.js b/app/scripts/migrations/011.js index d8f852f9f..1e1884825 100644 --- a/app/scripts/migrations/011.js +++ b/app/scripts/migrations/011.js @@ -11,7 +11,7 @@ const version = 11 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state delete newState.TOSHash delete newState.isDisclaimerConfirmed diff --git a/app/scripts/migrations/012.js b/app/scripts/migrations/012.js index d01edbc41..3a42992ee 100644 --- a/app/scripts/migrations/012.js +++ b/app/scripts/migrations/012.js @@ -11,7 +11,7 @@ const version = 12 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state newState.NoticeController.noticesList.forEach((notice) => { if (notice.read) { diff --git a/app/scripts/migrations/013.js b/app/scripts/migrations/013.js index 530fc687c..5c27496dc 100644 --- a/app/scripts/migrations/013.js +++ b/app/scripts/migrations/013.js @@ -11,7 +11,7 @@ const version = 13 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state const { config } = newState if (config && config.provider) { diff --git a/app/scripts/migrations/014.js b/app/scripts/migrations/014.js index 630aa2051..0a26fcea4 100644 --- a/app/scripts/migrations/014.js +++ b/app/scripts/migrations/014.js @@ -11,7 +11,7 @@ const version = 14 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state newState.NetworkController = {} newState.NetworkController.provider = newState.config.provider diff --git a/app/scripts/migrations/015.js b/app/scripts/migrations/015.js index 682eb8db7..f10ee9fd9 100644 --- a/app/scripts/migrations/015.js +++ b/app/scripts/migrations/015.js @@ -6,13 +6,14 @@ to a 'failed' stated */ import { cloneDeep } from 'lodash' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const version = 15 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -26,7 +27,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state const { TransactionController } = newState if (TransactionController && TransactionController.transactions) { @@ -35,7 +36,7 @@ function transformState (state) { if (!txMeta.err) { return txMeta } else if (txMeta.err.message === 'Gave up submitting tx.') { - txMeta.status = 'failed' + txMeta.status = TRANSACTION_STATUSES.FAILED } return txMeta }) diff --git a/app/scripts/migrations/016.js b/app/scripts/migrations/016.js index 974a1125d..3f3d3973c 100644 --- a/app/scripts/migrations/016.js +++ b/app/scripts/migrations/016.js @@ -6,13 +6,14 @@ to a 'failed' stated */ import { cloneDeep } from 'lodash' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const version = 16 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -26,7 +27,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state const { TransactionController } = newState if (TransactionController && TransactionController.transactions) { @@ -36,8 +37,10 @@ function transformState (state) { if (!txMeta.err) { return txMeta } - if (txMeta.err === 'transaction with the same hash was already imported.') { - txMeta.status = 'submitted' + if ( + txMeta.err === 'transaction with the same hash was already imported.' + ) { + txMeta.status = TRANSACTION_STATUSES.SUBMITTED delete txMeta.err } return txMeta diff --git a/app/scripts/migrations/017.js b/app/scripts/migrations/017.js index 497cbe33e..d6814dd8c 100644 --- a/app/scripts/migrations/017.js +++ b/app/scripts/migrations/017.js @@ -5,13 +5,14 @@ This migration sets transactions who were retried and marked as failed to submit */ import { cloneDeep } from 'lodash' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const version = 17 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,17 +26,17 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state const { TransactionController } = newState if (TransactionController && TransactionController.transactions) { const { transactions } = newState.TransactionController newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.status === 'failed') { + if (!txMeta.status === TRANSACTION_STATUSES.FAILED) { return txMeta } if (txMeta.retryCount > 0 && txMeta.retryCount < 2) { - txMeta.status = 'submitted' + txMeta.status = TRANSACTION_STATUSES.SUBMITTED delete txMeta.err } return txMeta diff --git a/app/scripts/migrations/018.js b/app/scripts/migrations/018.js index bb57ddf0c..5a616514c 100644 --- a/app/scripts/migrations/018.js +++ b/app/scripts/migrations/018.js @@ -15,7 +15,7 @@ const version = 18 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -29,7 +29,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state const { TransactionController } = newState if (TransactionController && TransactionController.transactions) { @@ -42,13 +42,11 @@ function transformState (state) { return txMeta } // has history: migrate - const newHistory = ( - migrateFromSnapshotsToDiffs(txMeta.history) + const newHistory = migrateFromSnapshotsToDiffs(txMeta.history) // remove empty diffs - .filter((entry) => { - return !Array.isArray(entry) || entry.length > 0 - }) - ) + .filter((entry) => { + return !Array.isArray(entry) || entry.length > 0 + }) txMeta.history = newHistory return txMeta }) diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js index 4c786b7c9..d8cb5f73e 100644 --- a/app/scripts/migrations/019.js +++ b/app/scripts/migrations/019.js @@ -1,4 +1,3 @@ - /* This migration sets transactions as failed @@ -7,13 +6,14 @@ whos nonce is too high */ import { cloneDeep } from 'lodash' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const version = 19 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -27,44 +27,54 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state const { TransactionController } = newState if (TransactionController && TransactionController.transactions) { - const { transactions } = newState.TransactionController - newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { - if (txMeta.status !== 'submitted') { - return txMeta - } - - const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed') - .filter((tx) => tx.txParams.from === txMeta.txParams.from) - .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) - const highestConfirmedNonce = getHighestNonce(confirmedTxs) - - const pendingTxs = txList.filter((tx) => tx.status === 'submitted') - .filter((tx) => tx.txParams.from === txMeta.txParams.from) - .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) - const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce) - - const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce) - - if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) { - txMeta.status = 'failed' - txMeta.err = { - message: 'nonce too high', - note: 'migration 019 custom error', + newState.TransactionController.transactions = transactions.map( + (txMeta, _, txList) => { + if (txMeta.status !== TRANSACTION_STATUSES.SUBMITTED) { + return txMeta } - } - return txMeta - }) + + const confirmedTxs = txList + .filter((tx) => tx.status === TRANSACTION_STATUSES.CONFIRMED) + .filter((tx) => tx.txParams.from === txMeta.txParams.from) + .filter( + (tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from, + ) + const highestConfirmedNonce = getHighestNonce(confirmedTxs) + + const pendingTxs = txList + .filter((tx) => tx.status === TRANSACTION_STATUSES.SUBMITTED) + .filter((tx) => tx.txParams.from === txMeta.txParams.from) + .filter( + (tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from, + ) + const highestContinuousNonce = getHighestContinuousFrom( + pendingTxs, + highestConfirmedNonce, + ) + + const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce) + + if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) { + txMeta.status = TRANSACTION_STATUSES.FAILED + txMeta.err = { + message: 'nonce too high', + note: 'migration 019 custom error', + } + } + return txMeta + }, + ) } return newState } -function getHighestContinuousFrom (txList, startPoint) { +function getHighestContinuousFrom(txList, startPoint) { const nonces = txList.map((txMeta) => { const { nonce } = txMeta.txParams return parseInt(nonce, 16) @@ -78,7 +88,7 @@ function getHighestContinuousFrom (txList, startPoint) { return highest } -function getHighestNonce (txList) { +function getHighestNonce(txList) { const nonces = txList.map((txMeta) => { const { nonce } = txMeta.txParams return parseInt(nonce || '0x0', 16) @@ -86,4 +96,3 @@ function getHighestNonce (txList) { const highestNonce = Math.max.apply(null, nonces) return highestNonce } - diff --git a/app/scripts/migrations/020.js b/app/scripts/migrations/020.js index eb8080124..b2a28cb7a 100644 --- a/app/scripts/migrations/020.js +++ b/app/scripts/migrations/020.js @@ -13,7 +13,7 @@ const version = 20 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -27,10 +27,9 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state - if ('metamask' in newState && - !('firstTimeInfo' in newState.metamask)) { + if ('metamask' in newState && !('firstTimeInfo' in newState.metamask)) { newState.metamask.firstTimeInfo = { version: '3.12.0', date: Date.now(), @@ -38,4 +37,3 @@ function transformState (state) { } return newState } - diff --git a/app/scripts/migrations/021.js b/app/scripts/migrations/021.js index ac3170131..4783d6ed9 100644 --- a/app/scripts/migrations/021.js +++ b/app/scripts/migrations/021.js @@ -11,7 +11,7 @@ const version = 21 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,10 +25,9 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state delete newState.BlacklistController delete newState.RecentBlocks return newState } - diff --git a/app/scripts/migrations/022.js b/app/scripts/migrations/022.js index 76f96a0ae..cb1b4335f 100644 --- a/app/scripts/migrations/022.js +++ b/app/scripts/migrations/022.js @@ -1,4 +1,3 @@ - /* This migration adds submittedTime to the txMeta if it is not their @@ -6,13 +5,14 @@ This migration adds submittedTime to the txMeta if it is not their */ import { cloneDeep } from 'lodash' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const version = 22 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -26,17 +26,20 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state const { TransactionController } = newState if (TransactionController && TransactionController.transactions) { const { transactions } = newState.TransactionController newState.TransactionController.transactions = transactions.map((txMeta) => { - if (txMeta.status !== 'submitted' || txMeta.submittedTime) { + if ( + txMeta.status !== TRANSACTION_STATUSES.SUBMITTED || + txMeta.submittedTime + ) { return txMeta } - txMeta.submittedTime = (new Date()).getTime() + txMeta.submittedTime = new Date().getTime() return txMeta }) } diff --git a/app/scripts/migrations/023.js b/app/scripts/migrations/023.js index 5f0363d53..531a73e13 100644 --- a/app/scripts/migrations/023.js +++ b/app/scripts/migrations/023.js @@ -1,4 +1,3 @@ - /* This migration removes transactions that are no longer usefull down to 40 total @@ -6,13 +5,14 @@ This migration removes transactions that are no longer usefull down to 40 total */ import { cloneDeep } from 'lodash' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const version = 23 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -26,7 +26,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state const { TransactionController } = newState @@ -41,10 +41,12 @@ function transformState (state) { let stripping = true while (reverseTxList.length > 40 && stripping) { const txIndex = reverseTxList.findIndex((txMeta) => { - return (txMeta.status === 'failed' || - txMeta.status === 'rejected' || - txMeta.status === 'confirmed' || - txMeta.status === 'dropped') + return ( + txMeta.status === TRANSACTION_STATUSES.FAILED || + txMeta.status === TRANSACTION_STATUSES.REJECTED || + txMeta.status === TRANSACTION_STATUSES.CONFIRMED || + txMeta.status === TRANSACTION_STATUSES.DROPPED + ) }) if (txIndex < 0) { stripping = false diff --git a/app/scripts/migrations/024.js b/app/scripts/migrations/024.js index 84a998d12..b4189a4e0 100644 --- a/app/scripts/migrations/024.js +++ b/app/scripts/migrations/024.js @@ -1,4 +1,3 @@ - /* This migration ensures that the from address in txParams is to lower case for @@ -7,13 +6,14 @@ all unapproved transactions */ import { cloneDeep } from 'lodash' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const version = 24 export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -23,21 +23,23 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state if (!newState.TransactionController) { return newState } const { transactions } = newState.TransactionController - newState.TransactionController.transactions = transactions.map((txMeta, _) => { - if ( - txMeta.status === 'unapproved' && - txMeta.txParams && - txMeta.txParams.from - ) { - txMeta.txParams.from = txMeta.txParams.from.toLowerCase() - } - return txMeta - }) + newState.TransactionController.transactions = transactions.map( + (txMeta, _) => { + if ( + txMeta.status === TRANSACTION_STATUSES.UNAPPROVED && + txMeta.txParams && + txMeta.txParams.from + ) { + txMeta.txParams.from = txMeta.txParams.from.toLowerCase() + } + return txMeta + }, + ) return newState } diff --git a/app/scripts/migrations/025.js b/app/scripts/migrations/025.js index 72bcdbf01..3338ec9cf 100644 --- a/app/scripts/migrations/025.js +++ b/app/scripts/migrations/025.js @@ -4,16 +4,16 @@ normalizes txParams on unconfirmed txs */ -import ethUtil from 'ethereumjs-util' - import { cloneDeep } from 'lodash' +import { addHexPrefix } from '../lib/util' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const version = 25 export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -23,35 +23,37 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state if (newState.TransactionController) { if (newState.TransactionController.transactions) { const { transactions } = newState.TransactionController - newState.TransactionController.transactions = transactions.map((txMeta) => { - if (txMeta.status !== 'unapproved') { + newState.TransactionController.transactions = transactions.map( + (txMeta) => { + if (txMeta.status !== TRANSACTION_STATUSES.UNAPPROVED) { + return txMeta + } + txMeta.txParams = normalizeTxParams(txMeta.txParams) return txMeta - } - txMeta.txParams = normalizeTxParams(txMeta.txParams) - return txMeta - }) + }, + ) } } return newState } -function normalizeTxParams (txParams) { +function normalizeTxParams(txParams) { // functions that handle normalizing of that key in txParams const whiteList = { - from: (from) => ethUtil.addHexPrefix(from).toLowerCase(), - to: () => ethUtil.addHexPrefix(txParams.to).toLowerCase(), - nonce: (nonce) => ethUtil.addHexPrefix(nonce), - value: (value) => ethUtil.addHexPrefix(value), - data: (data) => ethUtil.addHexPrefix(data), - gas: (gas) => ethUtil.addHexPrefix(gas), - gasPrice: (gasPrice) => ethUtil.addHexPrefix(gasPrice), + from: (from) => addHexPrefix(from).toLowerCase(), + to: () => addHexPrefix(txParams.to).toLowerCase(), + nonce: (nonce) => addHexPrefix(nonce), + value: (value) => addHexPrefix(value), + data: (data) => addHexPrefix(data), + gas: (gas) => addHexPrefix(gas), + gasPrice: (gasPrice) => addHexPrefix(gasPrice), } // apply only keys in the whiteList diff --git a/app/scripts/migrations/026.js b/app/scripts/migrations/026.js index e6cb9db85..c237cce1c 100644 --- a/app/scripts/migrations/026.js +++ b/app/scripts/migrations/026.js @@ -11,7 +11,7 @@ const version = 26 export default { version, - migrate (originalVersionedData) { + migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version try { @@ -25,7 +25,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { if (!state.KeyringController || !state.PreferencesController) { return state } @@ -34,14 +34,15 @@ function transformState (state) { return state } - state.PreferencesController.identities = Object.keys(state.KeyringController.walletNicknames) - .reduce((identities, address) => { - identities[address] = { - name: state.KeyringController.walletNicknames[address], - address, - } - return identities - }, {}) + state.PreferencesController.identities = Object.keys( + state.KeyringController.walletNicknames, + ).reduce((identities, address) => { + identities[address] = { + name: state.KeyringController.walletNicknames[address], + address, + } + return identities + }, {}) delete state.KeyringController.walletNicknames return state } diff --git a/app/scripts/migrations/027.js b/app/scripts/migrations/027.js index 42e024f56..09d5a00e8 100644 --- a/app/scripts/migrations/027.js +++ b/app/scripts/migrations/027.js @@ -5,13 +5,14 @@ normalizes txParams on unconfirmed txs */ import { cloneDeep } from 'lodash' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const version = 27 export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -21,13 +22,15 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state if (newState.TransactionController) { if (newState.TransactionController.transactions) { const { transactions } = newState.TransactionController - newState.TransactionController.transactions = transactions.filter((txMeta) => txMeta.status !== 'rejected') + newState.TransactionController.transactions = transactions.filter( + (txMeta) => txMeta.status !== TRANSACTION_STATUSES.REJECTED, + ) } } diff --git a/app/scripts/migrations/028.js b/app/scripts/migrations/028.js index fa816ca53..8f83bda04 100644 --- a/app/scripts/migrations/028.js +++ b/app/scripts/migrations/028.js @@ -11,7 +11,7 @@ const version = 28 export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -21,15 +21,20 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state if (newState.PreferencesController) { - if (newState.PreferencesController.tokens && newState.PreferencesController.identities) { + if ( + newState.PreferencesController.tokens && + newState.PreferencesController.identities + ) { const { identities, tokens } = newState.PreferencesController newState.PreferencesController.accountTokens = {} Object.keys(identities).forEach((identity) => { - newState.PreferencesController.accountTokens[identity] = { 'mainnet': tokens } + newState.PreferencesController.accountTokens[identity] = { + mainnet: tokens, + } }) newState.PreferencesController.tokens = [] } diff --git a/app/scripts/migrations/029.js b/app/scripts/migrations/029.js index 4773eb466..242cc995b 100644 --- a/app/scripts/migrations/029.js +++ b/app/scripts/migrations/029.js @@ -1,4 +1,5 @@ // next version number +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' import failTxsThat from './fail-tx' const version = 29 @@ -18,10 +19,14 @@ normalizes txParams on unconfirmed txs export default { version, - migrate: failTxsThat(version, 'Stuck in approved state for too long.', (txMeta) => { - const isApproved = txMeta.status === 'approved' - const createdTime = txMeta.submittedTime - const now = Date.now() - return isApproved && now - createdTime > unacceptableDelay - }), + migrate: failTxsThat( + version, + 'Stuck in approved state for too long.', + (txMeta) => { + const isApproved = txMeta.status === TRANSACTION_STATUSES.APPROVED + const createdTime = txMeta.submittedTime + const now = Date.now() + return isApproved && now - createdTime > unacceptableDelay + }, + ), } diff --git a/app/scripts/migrations/030.js b/app/scripts/migrations/030.js index 6f11e27a5..1062cbaf2 100644 --- a/app/scripts/migrations/030.js +++ b/app/scripts/migrations/030.js @@ -12,7 +12,7 @@ const version = 30 export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -22,7 +22,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state if (state.PreferencesController) { const { frequentRpcListDetail } = newState.PreferencesController @@ -37,13 +37,20 @@ function transformState (state) { } } if (state.NetworkController) { - // eslint-disable-next-line radix - if (newState.NetworkController.network && Number.isNaN(parseInt(newState.NetworkController.network))) { + if ( + newState.NetworkController.network && + // eslint-disable-next-line radix + Number.isNaN(parseInt(newState.NetworkController.network)) + ) { delete newState.NetworkController.network } - // eslint-disable-next-line radix - if (newState.NetworkController.provider && newState.NetworkController.provider.chainId && Number.isNaN(parseInt(newState.NetworkController.provider.chainId))) { + if ( + newState.NetworkController.provider && + newState.NetworkController.provider.chainId && + // eslint-disable-next-line radix + Number.isNaN(parseInt(newState.NetworkController.provider.chainId)) + ) { delete newState.NetworkController.provider.chainId } } diff --git a/app/scripts/migrations/031.js b/app/scripts/migrations/031.js index 59a8dcf15..87eea11fa 100644 --- a/app/scripts/migrations/031.js +++ b/app/scripts/migrations/031.js @@ -4,13 +4,13 @@ import { cloneDeep } from 'lodash' const version = 31 /* - * The purpose of this migration is to properly set the completedOnboarding flag based on the state - * of the KeyringController. - */ + * The purpose of this migration is to properly set the completedOnboarding flag based on the state + * of the KeyringController. + */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -20,7 +20,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const { KeyringController, PreferencesController } = state if (KeyringController && PreferencesController) { diff --git a/app/scripts/migrations/032.js b/app/scripts/migrations/032.js index fce4d65a6..facdf1072 100644 --- a/app/scripts/migrations/032.js +++ b/app/scripts/migrations/032.js @@ -7,7 +7,7 @@ const version = 32 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -16,7 +16,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const { PreferencesController } = state if (PreferencesController) { diff --git a/app/scripts/migrations/033.js b/app/scripts/migrations/033.js index e1e0ea3b4..8f88ba4e1 100644 --- a/app/scripts/migrations/033.js +++ b/app/scripts/migrations/033.js @@ -12,7 +12,7 @@ const version = 33 export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -22,7 +22,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state // transform state here if (state.NoticeController) { diff --git a/app/scripts/migrations/034.js b/app/scripts/migrations/034.js index ba61131e4..4bb2f9491 100644 --- a/app/scripts/migrations/034.js +++ b/app/scripts/migrations/034.js @@ -8,7 +8,7 @@ const version = 34 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -17,13 +17,16 @@ export default { }, } -function transformState (state) { +function transformState(state) { const { PreferencesController } = state if (PreferencesController) { const featureFlags = PreferencesController.featureFlags || {} - if (!featureFlags.privacyMode && typeof PreferencesController.migratedPrivacyMode === 'undefined') { + if ( + !featureFlags.privacyMode && + typeof PreferencesController.migratedPrivacyMode === 'undefined' + ) { // Mark the state has being migrated and enable Privacy Mode PreferencesController.migratedPrivacyMode = true featureFlags.privacyMode = true diff --git a/app/scripts/migrations/035.js b/app/scripts/migrations/035.js index 6ea634730..d98f8562a 100644 --- a/app/scripts/migrations/035.js +++ b/app/scripts/migrations/035.js @@ -12,7 +12,7 @@ const version = 35 export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version versionedData.data = transformState(versionedData.data) @@ -20,8 +20,11 @@ export default { }, } -function transformState (state) { - if (state.PreferencesController && state.PreferencesController.seedWords !== undefined) { +function transformState(state) { + if ( + state.PreferencesController && + state.PreferencesController.seedWords !== undefined + ) { delete state.PreferencesController.seedWords } return state diff --git a/app/scripts/migrations/036.js b/app/scripts/migrations/036.js index 2b64fc6ea..056f96f01 100644 --- a/app/scripts/migrations/036.js +++ b/app/scripts/migrations/036.js @@ -7,7 +7,7 @@ const version = 36 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -16,7 +16,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const { PreferencesController } = state if (PreferencesController) { diff --git a/app/scripts/migrations/037.js b/app/scripts/migrations/037.js index e650337d4..80a166e61 100644 --- a/app/scripts/migrations/037.js +++ b/app/scripts/migrations/037.js @@ -10,7 +10,7 @@ const version = 37 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -19,8 +19,7 @@ export default { }, } -function transformState (state) { - +function transformState(state) { if (state.AddressBookController) { const ab = state.AddressBookController.addressBook @@ -34,11 +33,10 @@ function transformState (state) { // fill the chainId object with the entries with the matching chainId for (const id of chainIds.values()) { - // make an empty object entry for each chainId + // make an empty object entry for each chainId newAddressBook[id] = {} for (const address in ab) { if (ab[address].chainId === id) { - ab[address].isEns = false if (util.normalizeEnsName(ab[address].name)) { ab[address].isEns = true diff --git a/app/scripts/migrations/038.js b/app/scripts/migrations/038.js index b104fe49f..9c179735b 100644 --- a/app/scripts/migrations/038.js +++ b/app/scripts/migrations/038.js @@ -7,7 +7,7 @@ const version = 38 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -16,7 +16,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const { ABTestController: ABTestControllerState = {} } = state const { abTests = {} } = ABTestControllerState diff --git a/app/scripts/migrations/039.js b/app/scripts/migrations/039.js index d190b4f9b..d8bec42e4 100644 --- a/app/scripts/migrations/039.js +++ b/app/scripts/migrations/039.js @@ -7,10 +7,13 @@ const DAI_V1_CONTRACT_ADDRESS = '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359' const DAI_V1_TOKEN_SYMBOL = 'DAI' const SAI_TOKEN_SYMBOL = 'SAI' -function isOldDai (token = {}) { - return token && typeof token === 'object' && +function isOldDai(token = {}) { + return ( + token && + typeof token === 'object' && token.symbol === DAI_V1_TOKEN_SYMBOL && ethUtil.toChecksumAddress(token.address) === DAI_V1_CONTRACT_ADDRESS + ) } /** @@ -22,7 +25,7 @@ function isOldDai (token = {}) { */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -31,7 +34,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const { PreferencesController } = state if (PreferencesController) { diff --git a/app/scripts/migrations/040.js b/app/scripts/migrations/040.js index 93b0e72be..49f91fecd 100644 --- a/app/scripts/migrations/040.js +++ b/app/scripts/migrations/040.js @@ -9,7 +9,7 @@ const version = 40 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -18,7 +18,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { delete state.ProviderApprovalController return state } diff --git a/app/scripts/migrations/041.js b/app/scripts/migrations/041.js index 9e81312c3..eb0964e4c 100644 --- a/app/scripts/migrations/041.js +++ b/app/scripts/migrations/041.js @@ -7,7 +7,7 @@ const version = 41 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -16,9 +16,10 @@ export default { }, } -function transformState (state) { +function transformState(state) { if (state.PreferencesController && state.PreferencesController.preferences) { - state.PreferencesController.preferences.autoLockTimeLimit = state.PreferencesController.preferences.autoLogoutTimeLimit + state.PreferencesController.preferences.autoLockTimeLimit = + state.PreferencesController.preferences.autoLogoutTimeLimit delete state.PreferencesController.preferences.autoLogoutTimeLimit } return state diff --git a/app/scripts/migrations/042.js b/app/scripts/migrations/042.js index ed8b9f7cc..ca52cada7 100644 --- a/app/scripts/migrations/042.js +++ b/app/scripts/migrations/042.js @@ -8,7 +8,7 @@ const version = 42 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -17,7 +17,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { if (state.AppStateController) { state.AppStateController.connectedStatusPopoverHasBeenShown = false } else { diff --git a/app/scripts/migrations/043.js b/app/scripts/migrations/043.js index ad2dddde5..afee378f8 100644 --- a/app/scripts/migrations/043.js +++ b/app/scripts/migrations/043.js @@ -7,7 +7,7 @@ const version = 43 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -16,7 +16,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { if (state?.PreferencesController?.currentAccountTab) { delete state.PreferencesController.currentAccountTab } diff --git a/app/scripts/migrations/044.js b/app/scripts/migrations/044.js index 00a23c012..a3fcf5036 100644 --- a/app/scripts/migrations/044.js +++ b/app/scripts/migrations/044.js @@ -7,7 +7,7 @@ const version = 44 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -16,8 +16,11 @@ export default { }, } -function transformState (state) { - if (typeof state?.AppStateController?.mkrMigrationReminderTimestamp !== 'undefined') { +function transformState(state) { + if ( + typeof state?.AppStateController?.mkrMigrationReminderTimestamp !== + 'undefined' + ) { delete state.AppStateController.mkrMigrationReminderTimestamp } return state diff --git a/app/scripts/migrations/045.js b/app/scripts/migrations/045.js index d8024a0f9..69965845a 100644 --- a/app/scripts/migrations/045.js +++ b/app/scripts/migrations/045.js @@ -7,7 +7,7 @@ const version = 45 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -16,12 +16,9 @@ export default { }, } -const outdatedGateways = [ - 'ipfs.io', - 'ipfs.dweb.link', -] +const outdatedGateways = ['ipfs.io', 'ipfs.dweb.link'] -function transformState (state) { +function transformState(state) { if (outdatedGateways.includes(state?.PreferencesController?.ipfsGateway)) { state.PreferencesController.ipfsGateway = 'dweb.link' } diff --git a/app/scripts/migrations/046.js b/app/scripts/migrations/046.js index 6b1ebdae0..d7e2fbcec 100644 --- a/app/scripts/migrations/046.js +++ b/app/scripts/migrations/046.js @@ -7,7 +7,7 @@ const version = 46 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -16,7 +16,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { if (typeof state?.ABTestController !== 'undefined') { delete state.ABTestController } diff --git a/app/scripts/migrations/047.js b/app/scripts/migrations/047.js index 371bf2135..2e3fffe84 100644 --- a/app/scripts/migrations/047.js +++ b/app/scripts/migrations/047.js @@ -7,7 +7,7 @@ const version = 47 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -16,7 +16,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const transactions = state?.TransactionController?.transactions if (Array.isArray(transactions)) { transactions.forEach((transaction) => { diff --git a/app/scripts/migrations/048.js b/app/scripts/migrations/048.js index 6e1585d54..4a7c1fecf 100644 --- a/app/scripts/migrations/048.js +++ b/app/scripts/migrations/048.js @@ -16,7 +16,7 @@ const version = 48 */ export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -25,21 +25,19 @@ export default { }, } -const hexRegEx = (/^0x[0-9a-f]+$/ui) -const chainIdRegEx = (/^0x[1-9a-f]+[0-9a-f]*$/ui) +const hexRegEx = /^0x[0-9a-f]+$/iu +const chainIdRegEx = /^0x[1-9a-f]+[0-9a-f]*$/iu -function transformState (state = {}) { +function transformState(state = {}) { // 1. Delete NetworkController.settings delete state.NetworkController?.settings // 2. Migrate NetworkController.provider to Rinkeby or rename rpcTarget key const provider = state.NetworkController?.provider || {} - const isCustomRpcWithInvalidChainId = ( - provider.type === 'rpc' && ( - typeof provider.chainId !== 'string' || - !chainIdRegEx.test(provider.chainId) - ) - ) + const isCustomRpcWithInvalidChainId = + provider.type === 'rpc' && + (typeof provider.chainId !== 'string' || + !chainIdRegEx.test(provider.chainId)) if (isCustomRpcWithInvalidChainId || provider.type === 'localhost') { state.NetworkController.provider = { type: 'rinkeby', @@ -84,8 +82,10 @@ function transformState (state = {}) { typeof metamaskNetworkId === 'string' && hexRegEx.test(metamaskNetworkId) ) { - transaction.metamaskNetworkId = parseInt(metamaskNetworkId, 16) - .toString(10) + transaction.metamaskNetworkId = parseInt( + metamaskNetworkId, + 16, + ).toString(10) } }) } @@ -93,7 +93,7 @@ function transformState (state = {}) { // 6. Convert address book keys from decimal to hex const addressBook = state.AddressBookController?.addressBook || {} Object.keys(addressBook).forEach((networkKey) => { - if ((/^\d+$/ui).test(networkKey)) { + if (/^\d+$/iu.test(networkKey)) { const chainId = `0x${parseInt(networkKey, 10).toString(16)}` updateChainIds(addressBook[networkKey], chainId) @@ -108,8 +108,7 @@ function transformState (state = {}) { // 7. Delete localhost key in IncomingTransactionsController delete state.IncomingTransactionsController - ?.incomingTxLastFetchedBlocksByNetwork - ?.localhost + ?.incomingTxLastFetchedBlocksByNetwork?.localhost // 8. Merge 'localhost' tokens into 'rpc' tokens const accountTokens = state.PreferencesController?.accountTokens @@ -121,7 +120,10 @@ function transformState (state = {}) { const rpcTokens = accountTokens[account].rpc || [] if (rpcTokens.length > 0) { - accountTokens[account].rpc = mergeTokenArrays(localhostTokens, rpcTokens) + accountTokens[account].rpc = mergeTokenArrays( + localhostTokens, + rpcTokens, + ) } else { accountTokens[account].rpc = localhostTokens } @@ -138,7 +140,7 @@ function transformState (state = {}) { * * @returns {void} */ -function mergeAddressBookKeys (addressBook, networkKey, chainIdKey) { +function mergeAddressBookKeys(addressBook, networkKey, chainIdKey) { const networkKeyEntries = addressBook[networkKey] || {} // For the new entries, start by copying the existing entries for the chainId const newEntries = { ...addressBook[chainIdKey] } @@ -155,11 +157,8 @@ function mergeAddressBookKeys (addressBook, networkKey, chainIdKey) { ...Object.keys(networkKeyEntries[address] || {}), ]).forEach((key) => { // Use non-empty value for the current key, if any - mergedEntry[key] = ( - newEntries[address][key] || - networkKeyEntries[address]?.[key] || - '' - ) + mergedEntry[key] = + newEntries[address][key] || networkKeyEntries[address]?.[key] || '' }) newEntries[address] = mergedEntry @@ -182,7 +181,7 @@ function mergeAddressBookKeys (addressBook, networkKey, chainIdKey) { * * @returns {void} */ -function updateChainIds (networkEntries, chainId) { +function updateChainIds(networkEntries, chainId) { Object.values(networkEntries).forEach((entry) => { if (entry && typeof entry === 'object') { entry.chainId = chainId @@ -196,7 +195,7 @@ function updateChainIds (networkEntries, chainId) { * * @returns {Array} */ -function mergeTokenArrays (localhostTokens, rpcTokens) { +function mergeTokenArrays(localhostTokens, rpcTokens) { const localhostTokensMap = tokenArrayToMap(localhostTokens) const rpcTokensMap = tokenArrayToMap(rpcTokens) @@ -213,7 +212,7 @@ function mergeTokenArrays (localhostTokens, rpcTokens) { return mergedTokens - function tokenArrayToMap (array) { + function tokenArrayToMap(array) { return array.reduce((map, token) => { if (token?.address && typeof token?.address === 'string') { map[token.address] = token diff --git a/app/scripts/migrations/fail-tx.js b/app/scripts/migrations/fail-tx.js index e6236ec4e..240d11036 100644 --- a/app/scripts/migrations/fail-tx.js +++ b/app/scripts/migrations/fail-tx.js @@ -1,6 +1,7 @@ import { cloneDeep } from 'lodash' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' -export default function failTxsThat (version, reason, condition) { +export default function failTxsThat(version, reason, condition) { return function (originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version @@ -12,11 +13,10 @@ export default function failTxsThat (version, reason, condition) { console.warn(`MetaMask Migration #${version}${err.stack}`) } return Promise.resolve(versionedData) - } } -function transformState (state, condition, reason) { +function transformState(state, condition, reason) { const newState = state const { TransactionController } = newState if (TransactionController && TransactionController.transactions) { @@ -27,7 +27,7 @@ function transformState (state, condition, reason) { return txMeta } - txMeta.status = 'failed' + txMeta.status = TRANSACTION_STATUSES.FAILED txMeta.err = { message: reason, note: `Tx automatically failed by migration because ${reason}`, @@ -38,4 +38,3 @@ function transformState (state, condition, reason) { } return newState } - diff --git a/app/scripts/migrations/template.js b/app/scripts/migrations/template.js index 6427d49f4..b582c5a00 100644 --- a/app/scripts/migrations/template.js +++ b/app/scripts/migrations/template.js @@ -12,7 +12,7 @@ const version = 0 export default { version, - async migrate (originalVersionedData) { + async migrate(originalVersionedData) { const versionedData = cloneDeep(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -22,7 +22,7 @@ export default { }, } -function transformState (state) { +function transformState(state) { const newState = state // transform state here return newState diff --git a/app/scripts/phishing-detect.js b/app/scripts/phishing-detect.js index 6311b7004..cabe08aa3 100644 --- a/app/scripts/phishing-detect.js +++ b/app/scripts/phishing-detect.js @@ -9,7 +9,7 @@ import ExtensionPlatform from './platforms/extension' document.addEventListener('DOMContentLoaded', start) -function start () { +function start() { const hash = window.location.hash.substring(1) const suspect = querystring.parse(hash) @@ -17,29 +17,36 @@ function start () { global.platform = new ExtensionPlatform() - const extensionPort = extension.runtime.connect({ name: getEnvironmentType() }) + const extensionPort = extension.runtime.connect({ + name: getEnvironmentType(), + }) const connectionStream = new PortStream(extensionPort) const mx = setupMultiplex(connectionStream) - setupControllerConnection(mx.createStream('controller'), (err, metaMaskController) => { - if (err) { - return - } + setupControllerConnection( + mx.createStream('controller'), + (err, metaMaskController) => { + if (err) { + return + } - const continueLink = document.getElementById('unsafe-continue') - continueLink.addEventListener('click', () => { - metaMaskController.safelistPhishingDomain(suspect.hostname) - window.location.href = suspect.href - }) - }) + const continueLink = document.getElementById('unsafe-continue') + continueLink.addEventListener('click', () => { + metaMaskController.safelistPhishingDomain(suspect.hostname) + window.location.href = suspect.href + }) + }, + ) } -function setupControllerConnection (connectionStream, cb) { +function setupControllerConnection(connectionStream, cb) { const eventEmitter = new EventEmitter() const metaMaskControllerDnode = dnode({ - sendUpdate (state) { + sendUpdate(state) { eventEmitter.emit('update', state) }, }) connectionStream.pipe(metaMaskControllerDnode).pipe(connectionStream) - metaMaskControllerDnode.once('remote', (backgroundConnection) => cb(null, backgroundConnection)) + metaMaskControllerDnode.once('remote', (backgroundConnection) => + cb(null, backgroundConnection), + ) } diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index c891ae97c..3644e02cb 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -2,17 +2,17 @@ import extension from 'extensionizer' import { createExplorerLink as explorerLink } from '@metamask/etherscan-link' import { getEnvironmentType, checkForError } from '../lib/util' import { ENVIRONMENT_TYPE_BACKGROUND } from '../lib/enums' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' export default class ExtensionPlatform { - // // Public // - reload () { + reload() { extension.runtime.reload() } - openTab (options) { + openTab(options) { return new Promise((resolve, reject) => { extension.tabs.create(options, (newTab) => { const error = checkForError() @@ -24,7 +24,7 @@ export default class ExtensionPlatform { }) } - openWindow (options) { + openWindow(options) { return new Promise((resolve, reject) => { extension.windows.create(options, (newWindow) => { const error = checkForError() @@ -36,7 +36,7 @@ export default class ExtensionPlatform { }) } - focusWindow (windowId) { + focusWindow(windowId) { return new Promise((resolve, reject) => { extension.windows.update(windowId, { focused: true }, () => { const error = checkForError() @@ -48,7 +48,7 @@ export default class ExtensionPlatform { }) } - updateWindowPosition (windowId, left, top) { + updateWindowPosition(windowId, left, top) { return new Promise((resolve, reject) => { extension.windows.update(windowId, { left, top }, () => { const error = checkForError() @@ -60,7 +60,7 @@ export default class ExtensionPlatform { }) } - getLastFocusedWindow () { + getLastFocusedWindow() { return new Promise((resolve, reject) => { extension.windows.getLastFocused((windowObject) => { const error = checkForError() @@ -72,17 +72,17 @@ export default class ExtensionPlatform { }) } - closeCurrentWindow () { + closeCurrentWindow() { return extension.windows.getCurrent((windowDetails) => { return extension.windows.remove(windowDetails.id) }) } - getVersion () { + getVersion() { return extension.runtime.getManifest().version } - openExtensionInBrowser (route = null, queryString = null) { + openExtensionInBrowser(route = null, queryString = null) { let extensionURL = extension.runtime.getURL('home.html') if (queryString) { @@ -98,7 +98,7 @@ export default class ExtensionPlatform { } } - getPlatformInfo (cb) { + getPlatformInfo(cb) { try { extension.runtime.getPlatformInfo((platform) => { cb(null, platform) @@ -110,20 +110,23 @@ export default class ExtensionPlatform { } } - showTransactionNotification (txMeta) { + showTransactionNotification(txMeta) { const { status, txReceipt: { status: receiptStatus } = {} } = txMeta - if (status === 'confirmed') { + if (status === TRANSACTION_STATUSES.CONFIRMED) { // There was an on-chain failure receiptStatus === '0x0' - ? this._showFailedTransaction(txMeta, 'Transaction encountered an error.') + ? this._showFailedTransaction( + txMeta, + 'Transaction encountered an error.', + ) : this._showConfirmedTransaction(txMeta) - } else if (status === 'failed') { + } else if (status === TRANSACTION_STATUSES.FAILED) { this._showFailedTransaction(txMeta) } } - getAllWindows () { + getAllWindows() { return new Promise((resolve, reject) => { extension.windows.getAll((windows) => { const error = checkForError() @@ -135,7 +138,7 @@ export default class ExtensionPlatform { }) } - getActiveTabs () { + getActiveTabs() { return new Promise((resolve, reject) => { extension.tabs.query({ active: true }, (tabs) => { const error = checkForError() @@ -147,7 +150,7 @@ export default class ExtensionPlatform { }) } - currentTab () { + currentTab() { return new Promise((resolve, reject) => { extension.tabs.getCurrent((tab) => { const err = checkForError() @@ -160,7 +163,7 @@ export default class ExtensionPlatform { }) } - switchToTab (tabId) { + switchToTab(tabId) { return new Promise((resolve, reject) => { extension.tabs.update(tabId, { highlighted: true }, (tab) => { const err = checkForError() @@ -173,7 +176,7 @@ export default class ExtensionPlatform { }) } - closeTab (tabId) { + closeTab(tabId) { return new Promise((resolve, reject) => { extension.tabs.remove(tabId, () => { const err = checkForError() @@ -186,8 +189,7 @@ export default class ExtensionPlatform { }) } - _showConfirmedTransaction (txMeta) { - + _showConfirmedTransaction(txMeta) { this._subscribeToNotificationClicked() const url = explorerLink(txMeta.hash, txMeta.metamaskNetworkId) @@ -198,33 +200,31 @@ export default class ExtensionPlatform { this._showNotification(title, message, url) } - _showFailedTransaction (txMeta, errorMessage) { - + _showFailedTransaction(txMeta, errorMessage) { const nonce = parseInt(txMeta.txParams.nonce, 16) const title = 'Failed transaction' - const message = `Transaction ${nonce} failed! ${errorMessage || txMeta.err.message}` + const message = `Transaction ${nonce} failed! ${ + errorMessage || txMeta.err.message + }` this._showNotification(title, message) } - _showNotification (title, message, url) { - extension.notifications.create( - url, - { - 'type': 'basic', - title, - 'iconUrl': extension.extension.getURL('../../images/icon-64.png'), - message, - }, - ) + _showNotification(title, message, url) { + extension.notifications.create(url, { + type: 'basic', + title, + iconUrl: extension.extension.getURL('../../images/icon-64.png'), + message, + }) } - _subscribeToNotificationClicked () { + _subscribeToNotificationClicked() { if (!extension.notifications.onClicked.hasListener(this._viewOnEtherscan)) { extension.notifications.onClicked.addListener(this._viewOnEtherscan) } } - _viewOnEtherscan (txId) { + _viewOnEtherscan(txId) { if (txId.startsWith('https://')) { extension.tabs.create({ url: txId }) } diff --git a/app/scripts/ui.js b/app/scripts/ui.js index a77c0d6a3..5b5932a50 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -1,4 +1,3 @@ - // this must run before anything else import './lib/freezeGlobals' @@ -28,8 +27,7 @@ import { getEnvironmentType } from './lib/util' start().catch(log.error) -async function start () { - +async function start() { // create platform global global.platform = new ExtensionPlatform() @@ -50,14 +48,15 @@ async function start () { const activeTab = await queryCurrentActiveTab(windowType) initializeUiWithTab(activeTab) - function displayCriticalError (container, err) { - container.innerHTML = '
The MetaMask app failed to load: please open and close MetaMask again to restart.
' + function displayCriticalError(container, err) { + container.innerHTML = + '
The MetaMask app failed to load: please open and close MetaMask again to restart.
' container.style.height = '80px' log.error(err.stack) throw err } - function initializeUiWithTab (tab) { + function initializeUiWithTab(tab) { const container = document.getElementById('app-content') initializeUi(tab, container, connectionStream, (err, store) => { if (err) { @@ -75,7 +74,7 @@ async function start () { } } -async function queryCurrentActiveTab (windowType) { +async function queryCurrentActiveTab(windowType) { return new Promise((resolve) => { // At the time of writing we only have the `activeTab` permission which means // that this query will only succeed in the popup context (i.e. after a "browserAction") @@ -99,18 +98,21 @@ async function queryCurrentActiveTab (windowType) { }) } -function initializeUi (activeTab, container, connectionStream, cb) { +function initializeUi(activeTab, container, connectionStream, cb) { connectToAccountManager(connectionStream, (err, backgroundConnection) => { if (err) { cb(err) return } - launchMetaMaskUi({ - activeTab, - container, - backgroundConnection, - }, cb) + launchMetaMaskUi( + { + activeTab, + container, + backgroundConnection, + }, + cb, + ) }) } @@ -120,7 +122,7 @@ function initializeUi (activeTab, container, connectionStream, cb) { * @param {PortDuplexStream} connectionStream - PortStream instance establishing a background connection * @param {Function} cb - Called when controller connection is established */ -function connectToAccountManager (connectionStream, cb) { +function connectToAccountManager(connectionStream, cb) { const mx = setupMultiplex(connectionStream) setupControllerConnection(mx.createStream('controller'), cb) setupWeb3Connection(mx.createStream('provider')) @@ -131,7 +133,7 @@ function connectToAccountManager (connectionStream, cb) { * * @param {PortDuplexStream} connectionStream - PortStream instance establishing a background connection */ -function setupWeb3Connection (connectionStream) { +function setupWeb3Connection(connectionStream) { const providerStream = new StreamProvider() providerStream.pipe(connectionStream).pipe(providerStream) connectionStream.on('error', console.error.bind(console)) @@ -147,10 +149,10 @@ function setupWeb3Connection (connectionStream) { * @param {PortDuplexStream} connectionStream - PortStream instance establishing a background connection * @param {Function} cb - Called when the remote account manager connection is established */ -function setupControllerConnection (connectionStream, cb) { +function setupControllerConnection(connectionStream, cb) { const eventEmitter = new EventEmitter() const backgroundDnode = Dnode({ - sendUpdate (state) { + sendUpdate(state) { eventEmitter.emit('update', state) }, }) diff --git a/babel.config.js b/babel.config.js index 26b47fdb9..9f3af9523 100644 --- a/babel.config.js +++ b/babel.config.js @@ -6,10 +6,7 @@ module.exports = function (api) { '@babel/preset-env', { targets: { - browsers: [ - 'chrome >= 58', - 'firefox >= 56.2', - ], + browsers: ['chrome >= 58', 'firefox >= 56.2'], }, }, ], diff --git a/development/announcer.js b/development/announcer.js index 798e7da07..e6927cb8b 100644 --- a/development/announcer.js +++ b/development/announcer.js @@ -2,7 +2,10 @@ const fs = require('fs') const path = require('path') const { version } = require('../app/manifest/_base.json') -const changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md'), 'utf8') +const changelog = fs.readFileSync( + path.join(__dirname, '..', 'CHANGELOG.md'), + 'utf8', +) const log = changelog.split(version)[1].split('##')[0].trim() const msg = `*MetaMask ${version}* now published! It should auto-update soon!\n${log}` diff --git a/development/build/display.js b/development/build/display.js index e5556dd09..153f47189 100644 --- a/development/build/display.js +++ b/development/build/display.js @@ -18,7 +18,7 @@ const SYMBOLS = { RightEighth: '▕', } -function setupTaskDisplay (taskEvents) { +function setupTaskDisplay(taskEvents) { const taskData = [] taskEvents.on('start', ([name]) => { console.log(`Starting '${name}'...`) @@ -32,7 +32,7 @@ function setupTaskDisplay (taskEvents) { }) } -function displayChart (data) { +function displayChart(data) { // sort tasks by start time data.sort((a, b) => a[1] - b[1]) @@ -49,30 +49,32 @@ function displayChart (data) { // build bars for bounds data.forEach((entry, index) => { const [label, start, end] = entry - const [start2, end2] = [start, end].map((value) => adjust(value, first, last, 40)) + const [start2, end2] = [start, end].map((value) => + adjust(value, first, last, 40), + ) const barString = barBuilder(start2, end2) const color = colors[index] const coloredBarString = colorize(color, barString) const duration = ((end - start) / 1e3).toFixed(1) console.log(coloredBarString, `${label} ${duration}s`) }) - } -function colorize (color, string) { - const colorizer = (typeof chalk[color] === 'function') ? chalk[color] : chalk.hex(color) +function colorize(color, string) { + const colorizer = + typeof chalk[color] === 'function' ? chalk[color] : chalk.hex(color) return colorizer(string) } // scale number within bounds -function adjust (value, first, last, size) { +function adjust(value, first, last, size) { const length = last - first - const result = (value - first) / length * size + const result = ((value - first) / length) * size return result } // draw bars -function barBuilder (start, end) { +function barBuilder(start, end) { const [spaceInt, spaceRest] = splitNumber(start) const barBodyLength = end - spaceInt let [barInt, barRest] = splitNumber(barBodyLength) @@ -92,7 +94,7 @@ function barBuilder (start, end) { } // get integer and remainder -function splitNumber (value = 0) { +function splitNumber(value = 0) { const [int, rest = '0'] = value.toString().split('.') const int2 = parseInt(int, 10) const rest2 = parseInt(rest, 10) / Math.pow(10, rest.length) @@ -100,11 +102,11 @@ function splitNumber (value = 0) { } // get partial block char for value (left-adjusted) -function getSymbolNormal (value) { +function getSymbolNormal(value) { // round to closest supported value const possibleValues = [0, 1 / 8, 1 / 4, 3 / 8, 1 / 2, 5 / 8, 3 / 4, 7 / 8, 1] const rounded = possibleValues.reduce((prev, curr) => { - return (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev) + return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev }) if (rounded === 0) { @@ -128,11 +130,11 @@ function getSymbolNormal (value) { } // get partial block char for value (right-adjusted) -function getSymbolNormalRight (value) { +function getSymbolNormalRight(value) { // round to closest supported value (not much :/) const possibleValues = [0, 1 / 2, 7 / 8, 1] const rounded = possibleValues.reduce((prev, curr) => { - return (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev) + return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev }) if (rounded === 0) { diff --git a/development/build/etc.js b/development/build/etc.js index 92af2e338..374b9a616 100644 --- a/development/build/etc.js +++ b/development/build/etc.js @@ -9,28 +9,32 @@ const { createTask, composeParallel } = require('./task') module.exports = createEtcTasks -function createEtcTasks ({ browserPlatforms, livereload }) { - - const clean = createTask('clean', async function clean () { +function createEtcTasks({ browserPlatforms, livereload }) { + const clean = createTask('clean', async function clean() { await del(['./dist/*']) - await Promise.all(browserPlatforms.map(async (platform) => { - await fs.mkdir(`./dist/${platform}`, { recursive: true }) - })) + await Promise.all( + browserPlatforms.map(async (platform) => { + await fs.mkdir(`./dist/${platform}`, { recursive: true }) + }), + ) }) - const reload = createTask('reload', function devReload () { + const reload = createTask('reload', function devReload() { livereload.listen({ port: 35729 }) }) // zip tasks for distribution - const zip = createTask('zip', composeParallel( - ...browserPlatforms.map((platform) => createZipTask(platform)), - )) + const zip = createTask( + 'zip', + composeParallel( + ...browserPlatforms.map((platform) => createZipTask(platform)), + ), + ) return { clean, reload, zip } } -function createZipTask (target) { +function createZipTask(target) { return async () => { await pump( gulp.src(`dist/${target}/**`), diff --git a/development/build/index.js b/development/build/index.js index 21efa88b1..cc75d8398 100755 --- a/development/build/index.js +++ b/development/build/index.js @@ -9,30 +9,32 @@ require('lavamoat-core/lib/ses.umd.js') lockdown() // eslint-disable-line no-undef const livereload = require('gulp-livereload') -const { createTask, composeSeries, composeParallel, detectAndRunEntryTask } = require('./task') +const { + createTask, + composeSeries, + composeParallel, + detectAndRunEntryTask, +} = require('./task') const createManifestTasks = require('./manifest') const createScriptTasks = require('./scripts') const createStyleTasks = require('./styles') const createStaticAssetTasks = require('./static') const createEtcTasks = require('./etc') -const browserPlatforms = [ - 'firefox', - 'chrome', - 'brave', - 'opera', -] +const browserPlatforms = ['firefox', 'chrome', 'brave', 'opera'] defineAllTasks() detectAndRunEntryTask() -function defineAllTasks () { - +function defineAllTasks() { const staticTasks = createStaticAssetTasks({ livereload, browserPlatforms }) const manifestTasks = createManifestTasks({ browserPlatforms }) const styleTasks = createStyleTasks({ livereload }) const scriptTasks = createScriptTasks({ livereload, browserPlatforms }) - const { clean, reload, zip } = createEtcTasks({ livereload, browserPlatforms }) + const { clean, reload, zip } = createEtcTasks({ + livereload, + browserPlatforms, + }) // build for development (livereload) createTask( @@ -70,11 +72,7 @@ function defineAllTasks () { composeSeries( clean, styleTasks.prod, - composeParallel( - scriptTasks.prod, - staticTasks.prod, - manifestTasks.prod, - ), + composeParallel(scriptTasks.prod, staticTasks.prod, manifestTasks.prod), zip, ), ) @@ -85,15 +83,10 @@ function defineAllTasks () { composeSeries( clean, styleTasks.prod, - composeParallel( - scriptTasks.test, - staticTasks.prod, - manifestTasks.test, - ), + composeParallel(scriptTasks.test, staticTasks.prod, manifestTasks.test), ), ) // special build for minimal CI testing createTask('styles', styleTasks.prod) - } diff --git a/development/build/manifest.js b/development/build/manifest.js index 1b24e6398..7750205b1 100644 --- a/development/build/manifest.js +++ b/development/build/manifest.js @@ -12,22 +12,34 @@ const scriptsToExcludeFromBackgroundDevBuild = { 'bg-libs.js': true, } -function createManifestTasks ({ browserPlatforms }) { - +function createManifestTasks({ browserPlatforms }) { // merge base manifest with per-platform manifests const prepPlatforms = async () => { - return Promise.all(browserPlatforms.map(async (platform) => { - const platformModifications = await readJson(path.join(__dirname, '..', '..', 'app', 'manifest', `${platform}.json`)) - const result = merge(cloneDeep(baseManifest), platformModifications) - const dir = path.join('.', 'dist', platform) - await fs.mkdir(dir, { recursive: true }) - await writeJson(result, path.join(dir, 'manifest.json')) - })) + return Promise.all( + browserPlatforms.map(async (platform) => { + const platformModifications = await readJson( + path.join( + __dirname, + '..', + '..', + 'app', + 'manifest', + `${platform}.json`, + ), + ) + const result = merge(cloneDeep(baseManifest), platformModifications) + const dir = path.join('.', 'dist', platform) + await fs.mkdir(dir, { recursive: true }) + await writeJson(result, path.join(dir, 'manifest.json')) + }), + ) } // dev: remove bg-libs, add chromereload, add perms const envDev = createTaskForModifyManifestForEnvironment((manifest) => { - const scripts = manifest.background.scripts.filter((scriptName) => !scriptsToExcludeFromBackgroundDevBuild[scriptName]) + const scripts = manifest.background.scripts.filter( + (scriptName) => !scriptsToExcludeFromBackgroundDevBuild[scriptName], + ) scripts.push('chromereload.js') manifest.background = { ...manifest.background, @@ -38,60 +50,68 @@ function createManifestTasks ({ browserPlatforms }) { // testDev: remove bg-libs, add perms const envTestDev = createTaskForModifyManifestForEnvironment((manifest) => { - const scripts = manifest.background.scripts.filter((scriptName) => !scriptsToExcludeFromBackgroundDevBuild[scriptName]) + const scripts = manifest.background.scripts.filter( + (scriptName) => !scriptsToExcludeFromBackgroundDevBuild[scriptName], + ) scripts.push('chromereload.js') manifest.background = { ...manifest.background, scripts, } - manifest.permissions = [...manifest.permissions, 'webRequestBlocking', 'http://localhost/*'] + manifest.permissions = [ + ...manifest.permissions, + 'webRequestBlocking', + 'http://localhost/*', + ] }) // test: add permissions const envTest = createTaskForModifyManifestForEnvironment((manifest) => { - manifest.permissions = [...manifest.permissions, 'webRequestBlocking', 'http://localhost/*'] + manifest.permissions = [ + ...manifest.permissions, + 'webRequestBlocking', + 'http://localhost/*', + ] }) // high level manifest tasks - const dev = createTask('manifest:dev', composeSeries( - prepPlatforms, - envDev, - )) + const dev = createTask('manifest:dev', composeSeries(prepPlatforms, envDev)) - const testDev = createTask('manifest:testDev', composeSeries( - prepPlatforms, - envTestDev, - )) + const testDev = createTask( + 'manifest:testDev', + composeSeries(prepPlatforms, envTestDev), + ) - const test = createTask('manifest:test', composeSeries( - prepPlatforms, - envTest, - )) + const test = createTask( + 'manifest:test', + composeSeries(prepPlatforms, envTest), + ) const prod = createTask('manifest:prod', prepPlatforms) return { prod, dev, testDev, test } // helper for modifying each platform's manifest.json in place - function createTaskForModifyManifestForEnvironment (transformFn) { + function createTaskForModifyManifestForEnvironment(transformFn) { return () => { - return Promise.all(browserPlatforms.map(async (platform) => { - const manifestPath = path.join('.', 'dist', platform, 'manifest.json') - const manifest = await readJson(manifestPath) - transformFn(manifest) - await writeJson(manifest, manifestPath) - })) + return Promise.all( + browserPlatforms.map(async (platform) => { + const manifestPath = path.join('.', 'dist', platform, 'manifest.json') + const manifest = await readJson(manifestPath) + transformFn(manifest) + await writeJson(manifest, manifestPath) + }), + ) } } - } // helper for reading and deserializing json from fs -async function readJson (file) { +async function readJson(file) { return JSON.parse(await fs.readFile(file, 'utf8')) } // helper for serializing and writing json to fs -async function writeJson (obj, file) { +async function writeJson(obj, file) { return fs.writeFile(file, JSON.stringify(obj, null, 2)) } diff --git a/development/build/scripts.js b/development/build/scripts.js index c6b9734d5..cf19060c6 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -17,89 +17,104 @@ const { makeStringTransform } = require('browserify-transform-tools') const conf = require('rc')('metamask', { INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID, + SEGMENT_HOST: process.env.SEGMENT_HOST, SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY, SEGMENT_LEGACY_WRITE_KEY: process.env.SEGMENT_LEGACY_WRITE_KEY, }) const packageJSON = require('../../package.json') -const { createTask, composeParallel, composeSeries, runInChildProcess } = require('./task') +const { + createTask, + composeParallel, + composeSeries, + runInChildProcess, +} = require('./task') module.exports = createScriptTasks -const dependencies = Object.keys((packageJSON && packageJSON.dependencies) || {}) +const dependencies = Object.keys( + (packageJSON && packageJSON.dependencies) || {}, +) const materialUIDependencies = ['@material-ui/core'] const reactDepenendencies = dependencies.filter((dep) => dep.match(/react/u)) const d3Dependencies = ['c3', 'd3'] const externalDependenciesMap = { - background: [ - '3box', - ], - ui: [ - ...materialUIDependencies, ...reactDepenendencies, ...d3Dependencies, - ], + background: ['3box'], + ui: [...materialUIDependencies, ...reactDepenendencies, ...d3Dependencies], } -function createScriptTasks ({ browserPlatforms, livereload }) { - +function createScriptTasks({ browserPlatforms, livereload }) { // internal tasks const core = { // dev tasks (live reload) - dev: createTasksForBuildJsExtension({ taskPrefix: 'scripts:core:dev', devMode: true }), - testDev: createTasksForBuildJsExtension({ taskPrefix: 'scripts:core:test-live', devMode: true, testing: true }), + dev: createTasksForBuildJsExtension({ + taskPrefix: 'scripts:core:dev', + devMode: true, + }), + testDev: createTasksForBuildJsExtension({ + taskPrefix: 'scripts:core:test-live', + devMode: true, + testing: true, + }), // built for CI tests - test: createTasksForBuildJsExtension({ taskPrefix: 'scripts:core:test', testing: true }), + test: createTasksForBuildJsExtension({ + taskPrefix: 'scripts:core:test', + testing: true, + }), // production prod: createTasksForBuildJsExtension({ taskPrefix: 'scripts:core:prod' }), } const deps = { - background: createTasksForBuildJsDeps({ filename: 'bg-libs', key: 'background' }), + background: createTasksForBuildJsDeps({ + filename: 'bg-libs', + key: 'background', + }), ui: createTasksForBuildJsDeps({ filename: 'ui-libs', key: 'ui' }), } // high level tasks - const prod = composeParallel( - deps.background, - deps.ui, - core.prod, - ) + const prod = composeParallel(deps.background, deps.ui, core.prod) const { dev, testDev } = core - const test = composeParallel( - deps.background, - deps.ui, - core.test, - ) + const test = composeParallel(deps.background, deps.ui, core.test) return { prod, dev, testDev, test } - function createTasksForBuildJsDeps ({ key, filename }) { - return createTask(`scripts:deps:${key}`, bundleTask({ - label: filename, - filename: `${filename}.js`, - buildLib: true, - dependenciesToBundle: externalDependenciesMap[key], - devMode: false, - })) + function createTasksForBuildJsDeps({ key, filename }) { + return createTask( + `scripts:deps:${key}`, + bundleTask({ + label: filename, + filename: `${filename}.js`, + buildLib: true, + dependenciesToBundle: externalDependenciesMap[key], + devMode: false, + }), + ) } - function createTasksForBuildJsExtension ({ taskPrefix, devMode, testing }) { - const standardBundles = [ - 'background', - 'ui', - 'phishing-detect', - ] + function createTasksForBuildJsExtension({ taskPrefix, devMode, testing }) { + const standardBundles = ['background', 'ui', 'phishing-detect'] const standardSubtasks = standardBundles.map((filename) => { - return createTask(`${taskPrefix}:${filename}`, - createBundleTaskForBuildJsExtensionNormal({ filename, devMode, testing })) + return createTask( + `${taskPrefix}:${filename}`, + createBundleTaskForBuildJsExtensionNormal({ + filename, + devMode, + testing, + }), + ) }) // inpage must be built before contentscript // because inpage bundle result is included inside contentscript - const contentscriptSubtask = createTask(`${taskPrefix}:contentscript`, - createTaskForBuildJsExtensionContentscript({ devMode, testing })) + const contentscriptSubtask = createTask( + `${taskPrefix}:contentscript`, + createTaskForBuildJsExtensionContentscript({ devMode, testing }), + ) // task for initiating livereload const initiateLiveReload = async () => { @@ -118,24 +133,33 @@ function createScriptTasks ({ browserPlatforms, livereload }) { } // make each bundle run in a separate process - const allSubtasks = [...standardSubtasks, contentscriptSubtask].map((subtask) => runInChildProcess(subtask)) + const allSubtasks = [ + ...standardSubtasks, + contentscriptSubtask, + ].map((subtask) => runInChildProcess(subtask)) // const allSubtasks = [...standardSubtasks, contentscriptSubtask].map(subtask => (subtask)) // make a parent task that runs each task in a child thread return composeParallel(initiateLiveReload, ...allSubtasks) } - function createBundleTaskForBuildJsExtensionNormal ({ filename, devMode, testing }) { + function createBundleTaskForBuildJsExtensionNormal({ + filename, + devMode, + testing, + }) { return bundleTask({ label: filename, filename: `${filename}.js`, filepath: `./app/scripts/${filename}.js`, - externalDependencies: devMode ? undefined : externalDependenciesMap[filename], + externalDependencies: devMode + ? undefined + : externalDependenciesMap[filename], devMode, testing, }) } - function createTaskForBuildJsExtensionContentscript ({ devMode, testing }) { + function createTaskForBuildJsExtensionContentscript({ devMode, testing }) { const inpage = 'inpage' const contentscript = 'contentscript' return composeSeries( @@ -143,7 +167,9 @@ function createScriptTasks ({ browserPlatforms, livereload }) { label: inpage, filename: `${inpage}.js`, filepath: `./app/scripts/${inpage}.js`, - externalDependencies: devMode ? undefined : externalDependenciesMap[inpage], + externalDependencies: devMode + ? undefined + : externalDependenciesMap[inpage], devMode, testing, }), @@ -151,19 +177,21 @@ function createScriptTasks ({ browserPlatforms, livereload }) { label: contentscript, filename: `${contentscript}.js`, filepath: `./app/scripts/${contentscript}.js`, - externalDependencies: devMode ? undefined : externalDependenciesMap[contentscript], + externalDependencies: devMode + ? undefined + : externalDependenciesMap[contentscript], devMode, testing, }), ) } - function bundleTask (opts) { + function bundleTask(opts) { let bundler return performBundle - async function performBundle () { + async function performBundle() { // initialize bundler if not available yet // dont create bundler until task is actually run if (!bundler) { @@ -198,26 +226,25 @@ function createScriptTasks ({ browserPlatforms, livereload }) { // Minification if (!opts.devMode) { - buildStream = buildStream - .pipe(terser({ + buildStream = buildStream.pipe( + terser({ mangle: { reserved: ['MetamaskInpageProvider'], }, sourceMap: { content: true, }, - })) + }), + ) } // Finalize Source Maps if (opts.devMode) { // Use inline source maps for development due to Chrome DevTools bug // https://bugs.chromium.org/p/chromium/issues/detail?id=931675 - buildStream = buildStream - .pipe(sourcemaps.write()) + buildStream = buildStream.pipe(sourcemaps.write()) } else { - buildStream = buildStream - .pipe(sourcemaps.write('../sourcemaps')) + buildStream = buildStream.pipe(sourcemaps.write('../sourcemaps')) } // write completed bundles @@ -230,10 +257,7 @@ function createScriptTasks ({ browserPlatforms, livereload }) { } } - function configureBundleForSesify ({ - browserifyOpts, - bundleName, - }) { + function configureBundleForSesify({ browserifyOpts, bundleName }) { // add in sesify args for better globalRef usage detection Object.assign(browserifyOpts, sesify.args) @@ -242,26 +266,36 @@ function createScriptTasks ({ browserPlatforms, livereload }) { // record dependencies used in bundle fs.mkdirSync('./sesify', { recursive: true }) - browserifyOpts.plugin.push(['deps-dump', { - filename: `./sesify/deps-${bundleName}.json`, - }]) + browserifyOpts.plugin.push([ + 'deps-dump', + { + filename: `./sesify/deps-${bundleName}.json`, + }, + ]) const sesifyConfigPath = `./sesify/${bundleName}.json` // add sesify plugin - browserifyOpts.plugin.push([sesify, { - writeAutoConfig: sesifyConfigPath, - }]) + browserifyOpts.plugin.push([ + sesify, + { + writeAutoConfig: sesifyConfigPath, + }, + ]) // remove html comments that SES is alergic to - const removeHtmlComment = makeStringTransform('remove-html-comment', { excludeExtension: ['.json'] }, (content, _, cb) => { - const result = content.split('-->').join('-- >') - cb(null, result) - }) + const removeHtmlComment = makeStringTransform( + 'remove-html-comment', + { excludeExtension: ['.json'] }, + (content, _, cb) => { + const result = content.split('-->').join('-- >') + cb(null, result) + }, + ) browserifyOpts.transform.push([removeHtmlComment, { global: true }]) } - function generateBundler (opts, performBundle) { + function generateBundler(opts, performBundle) { const browserifyOpts = assign({}, watchify.args, { plugin: [], transform: [], @@ -274,7 +308,8 @@ function createScriptTasks ({ browserPlatforms, livereload }) { // activate sesify const activateAutoConfig = Boolean(process.env.SESIFY_AUTOGEN) // const activateSesify = activateAutoConfig - const activateSesify = activateAutoConfig && ['background'].includes(bundleName) + const activateSesify = + activateAutoConfig && ['background'].includes(bundleName) if (activateSesify) { configureBundleForSesify({ browserifyOpts, bundleName }) } @@ -285,7 +320,10 @@ function createScriptTasks ({ browserPlatforms, livereload }) { if (!opts.buildLib) { if (opts.devMode && opts.filename === 'ui.js') { - browserifyOpts.entries = ['./development/require-react-devtools.js', opts.filepath] + browserifyOpts.entries = [ + './development/require-react-devtools.js', + opts.filepath, + ] } else { browserifyOpts.entries = [opts.filepath] } @@ -297,9 +335,7 @@ function createScriptTasks ({ browserPlatforms, livereload }) { // because it is incompatible with `esprima`, which is used by `envify` // See https://github.com/jquery/esprima/issues/1927 .transform('babelify', { - only: [ - './**/node_modules/libp2p', - ], + only: ['./**/node_modules/libp2p'], global: true, plugins: ['@babel/plugin-proposal-object-rest-spread'], }) @@ -313,42 +349,49 @@ function createScriptTasks ({ browserPlatforms, livereload }) { bundler = bundler.external(opts.externalDependencies) } - const environment = getEnvironment({ devMode: opts.devMode, test: opts.testing }) + const environment = getEnvironment({ + devMode: opts.devMode, + test: opts.testing, + }) if (environment === 'production' && !process.env.SENTRY_DSN) { throw new Error('Missing SENTRY_DSN environment variable') } - // 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. - const SEGMENT_PROD_WRITE_KEY = opts.testing ? undefined : process.env.SEGMENT_PROD_WRITE_KEY - const SEGMENT_DEV_WRITE_KEY = opts.testing ? undefined : conf.SEGMENT_WRITE_KEY - const SEGMENT_LEGACY_WRITE_KEY = opts.testing ? undefined : conf.SEGMENT_LEGACY_WRITE_KEY - // Inject variables into bundle - bundler.transform(envify({ - METAMASK_DEBUG: opts.devMode, - METAMASK_ENVIRONMENT: environment, - METAMETRICS_PROJECT_ID: process.env.METAMETRICS_PROJECT_ID, - NODE_ENV: opts.devMode ? 'development' : 'production', - IN_TEST: opts.testing ? 'true' : false, - PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '', - PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '', - ETH_GAS_STATION_API_KEY: process.env.ETH_GAS_STATION_API_KEY || '', - CONF: opts.devMode ? conf : ({}), - SENTRY_DSN: process.env.SENTRY_DSN, - INFURA_PROJECT_ID: ( - opts.testing + bundler.transform( + envify({ + METAMASK_DEBUG: opts.devMode, + METAMASK_ENVIRONMENT: environment, + METAMETRICS_PROJECT_ID: process.env.METAMETRICS_PROJECT_ID, + NODE_ENV: opts.devMode ? 'development' : 'production', + IN_TEST: opts.testing ? 'true' : false, + PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '', + PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '', + ETH_GAS_STATION_API_KEY: process.env.ETH_GAS_STATION_API_KEY || '', + CONF: opts.devMode ? conf : {}, + SENTRY_DSN: process.env.SENTRY_DSN, + INFURA_PROJECT_ID: opts.testing ? '00000000000000000000000000000000' - : conf.INFURA_PROJECT_ID - ), - SEGMENT_WRITE_KEY: environment === 'production' ? SEGMENT_PROD_WRITE_KEY : SEGMENT_DEV_WRITE_KEY, - SEGMENT_LEGACY_WRITE_KEY: environment === 'production' ? process.env.SEGMENT_LEGACY_WRITE_KEY : SEGMENT_LEGACY_WRITE_KEY, - }), { - global: true, - }) + : conf.INFURA_PROJECT_ID, + SEGMENT_HOST: conf.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 === 'production' + ? process.env.SEGMENT_PROD_WRITE_KEY + : conf.SEGMENT_WRITE_KEY, + SEGMENT_LEGACY_WRITE_KEY: + environment === 'production' + ? process.env.SEGMENT_PROD_LEGACY_WRITE_KEY + : conf.SEGMENT_LEGACY_WRITE_KEY, + }), + { + global: true, + }, + ) // Live reload - minimal rebundle on change if (opts.devMode) { @@ -361,14 +404,13 @@ function createScriptTasks ({ browserPlatforms, livereload }) { return bundler } - } -function beep () { +function beep() { process.stdout.write('\x07') } -function getEnvironment ({ devMode, test }) { +function getEnvironment({ devMode, test }) { // get environment slug if (devMode) { return 'development' @@ -376,7 +418,9 @@ function getEnvironment ({ devMode, test }) { return 'testing' } else if (process.env.CIRCLE_BRANCH === 'master') { return 'production' - } else if ((/^Version-v(\d+)[.](\d+)[.](\d+)/u).test(process.env.CIRCLE_BRANCH)) { + } else if ( + /^Version-v(\d+)[.](\d+)[.](\d+)/u.test(process.env.CIRCLE_BRANCH) + ) { return 'release-candidate' } else if (process.env.CIRCLE_BRANCH === 'develop') { return 'staging' diff --git a/development/build/static.js b/development/build/static.js index 3d4c74394..8a9263935 100644 --- a/development/build/static.js +++ b/development/build/static.js @@ -69,22 +69,31 @@ const copyTargetsDev = [ }, ] -function createStaticAssetTasks ({ livereload, browserPlatforms }) { - - const prod = createTask('static:prod', composeSeries(...copyTargets.map((target) => { - return async function copyStaticAssets () { - await performCopy(target) - } - }))) - const dev = createTask('static:dev', composeSeries(...copyTargetsDev.map((target) => { - return async function copyStaticAssets () { - await setupLiveCopy(target) - } - }))) +function createStaticAssetTasks({ livereload, browserPlatforms }) { + const prod = createTask( + 'static:prod', + composeSeries( + ...copyTargets.map((target) => { + return async function copyStaticAssets() { + await performCopy(target) + } + }), + ), + ) + const dev = createTask( + 'static:dev', + composeSeries( + ...copyTargetsDev.map((target) => { + return async function copyStaticAssets() { + await setupLiveCopy(target) + } + }), + ), + ) return { dev, prod } - async function setupLiveCopy (target) { + async function setupLiveCopy(target) { const pattern = target.pattern || '/**/*' watch(target.src + pattern, (event) => { livereload.changed(event.path) @@ -93,22 +102,33 @@ function createStaticAssetTasks ({ livereload, browserPlatforms }) { await performCopy(target) } - async function performCopy (target) { - await Promise.all(browserPlatforms.map(async (platform) => { - if (target.pattern) { - await copyGlob(target.src, `${target.src}${target.pattern}`, `./dist/${platform}/${target.dest}`) - } else { - await copyGlob(target.src, `${target.src}`, `./dist/${platform}/${target.dest}`) - } - })) + async function performCopy(target) { + await Promise.all( + browserPlatforms.map(async (platform) => { + if (target.pattern) { + await copyGlob( + target.src, + `${target.src}${target.pattern}`, + `./dist/${platform}/${target.dest}`, + ) + } else { + await copyGlob( + target.src, + `${target.src}`, + `./dist/${platform}/${target.dest}`, + ) + } + }), + ) } - async function copyGlob (baseDir, srcGlob, dest) { + async function copyGlob(baseDir, srcGlob, dest) { const sources = await glob(srcGlob, { onlyFiles: false }) - await Promise.all(sources.map(async (src) => { - const relativePath = path.relative(baseDir, src) - await fs.copy(src, `${dest}${relativePath}`) - })) + await Promise.all( + sources.map(async (src) => { + const relativePath = path.relative(baseDir, src) + await fs.copy(src, `${dest}${relativePath}`) + }), + ) } - } diff --git a/development/build/styles.js b/development/build/styles.js index 4be766673..966b2c818 100644 --- a/development/build/styles.js +++ b/development/build/styles.js @@ -14,35 +14,38 @@ const { createTask } = require('./task') // scss compilation and autoprefixing tasks module.exports = createStyleTasks -function createStyleTasks ({ livereload }) { +function createStyleTasks({ livereload }) { + const prod = createTask( + 'styles:prod', + createScssBuildTask({ + src: 'ui/app/css/index.scss', + dest: 'ui/app/css/output', + devMode: false, + }), + ) - const prod = createTask('styles:prod', createScssBuildTask({ - src: 'ui/app/css/index.scss', - dest: 'ui/app/css/output', - devMode: false, - })) - - const dev = createTask('styles:dev', createScssBuildTask({ - src: 'ui/app/css/index.scss', - dest: 'ui/app/css/output', - devMode: true, - pattern: 'ui/app/**/*.scss', - })) + const dev = createTask( + 'styles:dev', + createScssBuildTask({ + src: 'ui/app/css/index.scss', + dest: 'ui/app/css/output', + devMode: true, + pattern: 'ui/app/**/*.scss', + }), + ) const lint = createTask('lint-scss', function () { - return gulp - .src('ui/app/css/itcss/**/*.scss') - .pipe(gulpStylelint({ - reporters: [ - { formatter: 'string', console: true }, - ], + return gulp.src('ui/app/css/itcss/**/*.scss').pipe( + gulpStylelint({ + reporters: [{ formatter: 'string', console: true }], fix: true, - })) + }), + ) }) return { prod, dev, lint } - function createScssBuildTask ({ src, dest, devMode, pattern }) { + function createScssBuildTask({ src, dest, devMode, pattern }) { return async function () { if (devMode) { watch(pattern, async (event) => { @@ -53,26 +56,27 @@ function createStyleTasks ({ livereload }) { await buildScss() } - async function buildScss () { + async function buildScss() { await Promise.all([ buildScssPipeline(src, dest, devMode, false), buildScssPipeline(src, dest, devMode, true), ]) } } - } -async function buildScssPipeline (src, dest, devMode, rtl) { - await pump(...[ - // pre-process - gulp.src(src), - devMode && sourcemaps.init(), - sass().on('error', sass.logError), - autoprefixer(), - rtl && rtlcss(), - rtl && rename({ suffix: '-rtl' }), - devMode && sourcemaps.write(), - gulp.dest(dest), - ].filter(Boolean)) +async function buildScssPipeline(src, dest, devMode, rtl) { + await pump( + ...[ + // pre-process + gulp.src(src), + devMode && sourcemaps.init(), + sass().on('error', sass.logError), + autoprefixer(), + rtl && rtlcss(), + rtl && rename({ suffix: '-rtl' }), + devMode && sourcemaps.write(), + gulp.dest(dest), + ].filter(Boolean), + ) } diff --git a/development/build/task.js b/development/build/task.js index 6988696d7..553a06956 100644 --- a/development/build/task.js +++ b/development/build/task.js @@ -4,11 +4,20 @@ const spawn = require('cross-spawn') const tasks = {} const taskEvents = new EventEmitter() -module.exports = { detectAndRunEntryTask, tasks, taskEvents, createTask, runTask, composeSeries, composeParallel, runInChildProcess } +module.exports = { + detectAndRunEntryTask, + tasks, + taskEvents, + createTask, + runTask, + composeSeries, + composeParallel, + runInChildProcess, +} const { setupTaskDisplay } = require('./display') -function detectAndRunEntryTask () { +function detectAndRunEntryTask() { // get requested task name and execute const taskName = process.argv[2] if (!taskName) { @@ -19,7 +28,7 @@ function detectAndRunEntryTask () { runTask(taskName, { skipStats }) } -async function runTask (taskName, { skipStats } = {}) { +async function runTask(taskName, { skipStats } = {}) { if (!(taskName in tasks)) { throw new Error(`MetaMask build: Unrecognized task name "${taskName}"`) } @@ -30,16 +39,20 @@ async function runTask (taskName, { skipStats } = {}) { try { await tasks[taskName]() } catch (err) { - console.error(`MetaMask build: Encountered an error while running task "${taskName}".`) + console.error( + `MetaMask build: Encountered an error while running task "${taskName}".`, + ) console.error(err) process.exit(1) } taskEvents.emit('complete') } -function createTask (taskName, taskFn) { +function createTask(taskName, taskFn) { if (taskName in tasks) { - throw new Error(`MetaMask build: task "${taskName}" already exists. Refusing to redefine`) + throw new Error( + `MetaMask build: task "${taskName}" already exists. Refusing to redefine`, + ) } const task = instrumentForTaskStats(taskName, taskFn) task.taskName = taskName @@ -47,24 +60,34 @@ function createTask (taskName, taskFn) { return task } -function runInChildProcess (task) { +function runInChildProcess(task) { const taskName = typeof task === 'string' ? task : task.taskName if (!taskName) { - throw new Error(`MetaMask build: runInChildProcess unable to identify task name`) + throw new Error( + `MetaMask build: runInChildProcess unable to identify task name`, + ) } return instrumentForTaskStats(taskName, async () => { const childProcess = spawn('yarn', ['build', taskName, '--skip-stats']) // forward logs to main process // skip the first stdout event (announcing the process command) childProcess.stdout.once('data', () => { - childProcess.stdout.on('data', (data) => process.stdout.write(`${taskName}: ${data}`)) + childProcess.stdout.on('data', (data) => + process.stdout.write(`${taskName}: ${data}`), + ) }) - childProcess.stderr.on('data', (data) => process.stderr.write(`${taskName}: ${data}`)) + childProcess.stderr.on('data', (data) => + process.stderr.write(`${taskName}: ${data}`), + ) // await end of process await new Promise((resolve, reject) => { childProcess.once('close', (errCode) => { if (errCode !== 0) { - reject(new Error(`MetaMask build: runInChildProcess for task "${taskName}" encountered an error`)) + reject( + new Error( + `MetaMask build: runInChildProcess for task "${taskName}" encountered an error`, + ), + ) return } resolve() @@ -73,7 +96,7 @@ function runInChildProcess (task) { }) } -function instrumentForTaskStats (taskName, asyncFn) { +function instrumentForTaskStats(taskName, asyncFn) { return async () => { const start = Date.now() taskEvents.emit('start', [taskName, start]) @@ -83,7 +106,7 @@ function instrumentForTaskStats (taskName, asyncFn) { } } -function composeSeries (...subtasks) { +function composeSeries(...subtasks) { return async () => { const realTasks = subtasks for (const subtask of realTasks) { @@ -92,7 +115,7 @@ function composeSeries (...subtasks) { } } -function composeParallel (...subtasks) { +function composeParallel(...subtasks) { return async () => { const realTasks = subtasks await Promise.all(realTasks.map((subtask) => subtask())) diff --git a/development/lib/create-segment-server.js b/development/lib/create-segment-server.js new file mode 100644 index 000000000..2a15a34c9 --- /dev/null +++ b/development/lib/create-segment-server.js @@ -0,0 +1,93 @@ +const http = require('http') + +/** + * This is the default error handler to be used by this mock segment server. + * It will print the error to the console and exit the process. + * + * @param {Error} error - The server error + */ +function defaultOnError(error) { + console.log(error) + process.exit(1) +} + +/** + * @typedef {import('http').IncomingMessage} IncomingMessage + * @typedef {import('http').ServerResponse} ServerResponse + */ + +/** + * This function handles requests for the mock Segment server + * @typedef {(request: IncomingMessage, response: ServerResponse, metricEvents: Array) => void} MockSegmentRequestHandler + */ + +/** + * Creates a HTTP server that acts as a fake version of the Segment API. + * The bevahiour is rudimentary at the moment - it returns HTTP 200 in response + * to every request. The only function this serves is to spy on requests sent to + * this server, and to parse the request payloads as Segment batch events. + * + * @param {MockSegmentRequestHandler} onRequest- A callback for each request the server receives. + * @param {(error: Error) => void} [onError] - A callback for server error events + */ +function createSegmentServer(onRequest, onError = defaultOnError) { + const server = http.createServer(async (request, response) => { + const chunks = [] + + request.on('data', (chunk) => { + chunks.push(chunk) + }) + + await new Promise((resolve) => { + request.on('end', () => { + resolve() + }) + }) + + // respond to preflight request + if (request.method === 'OPTIONS') { + response.setHeader('Access-Control-Allow-Origin', '*') + response.setHeader('Access-Control-Allow-Methods', '*') + response.setHeader('Access-Control-Allow-Headers', '*') + response.statusCode = 200 + response.end() + return + } + + let metricEvents = [] + if (chunks.length) { + const body = Buffer.concat(chunks).toString() + const segmentPayload = JSON.parse(body) + metricEvents = segmentPayload.batch + } + + onRequest(request, response, metricEvents) + }) + + server.on('error', onError) + + return { + start: async (port) => { + await new Promise((resolve, reject) => { + server.listen(port, (error) => { + if (error) { + return reject(error) + } + return resolve() + }) + }) + }, + stop: async () => { + await new Promise((resolve, reject) => { + server.close((error) => { + if (error) { + return reject(error) + } + return resolve() + }) + }) + }, + } +} + +module.exports = { createSegmentServer } diff --git a/development/lib/parse-port.js b/development/lib/parse-port.js new file mode 100644 index 000000000..0a53712fb --- /dev/null +++ b/development/lib/parse-port.js @@ -0,0 +1,20 @@ +/** + * Parse a string as a port number. Non-integers or invalid ports will + * result in an error being thrown. + * + * @param {String} portString - The string to parse as a port number + * @returns {number} The parsed port number + */ +function parsePort(portString) { + const port = Number(portString) + if (!Number.isInteger(port)) { + throw new Error(`Port '${portString}' is invalid; must be an integer`) + } else if (port < 0 || port > 65535) { + throw new Error( + `Port '${portString}' is out of range; must be between 0 and 65535 inclusive`, + ) + } + return port +} + +module.exports = { parsePort } diff --git a/development/metamaskbot-build-announce.js b/development/metamaskbot-build-announce.js index 51d9617a2..fd514fa8d 100755 --- a/development/metamaskbot-build-announce.js +++ b/development/metamaskbot-build-announce.js @@ -6,12 +6,11 @@ const VERSION = require('../dist/chrome/manifest.json').version // eslint-disabl start().catch(console.error) -function capitalizeFirstLetter (string) { +function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1) } -async function start () { - +async function start() { const { GITHUB_COMMENT_TOKEN, CIRCLE_PULL_REQUEST } = process.env console.log('CIRCLE_PULL_REQUEST', CIRCLE_PULL_REQUEST) const { CIRCLE_SHA1 } = process.env @@ -32,17 +31,29 @@ async function start () { // links to extension builds const platforms = ['chrome', 'firefox', 'opera'] - const buildLinks = platforms.map((platform) => { - const url = `${BUILD_LINK_BASE}/builds/metamask-${platform}-${VERSION}.zip` - return `${platform}` - }).join(', ') + const buildLinks = platforms + .map((platform) => { + const url = `${BUILD_LINK_BASE}/builds/metamask-${platform}-${VERSION}.zip` + return `${platform}` + }) + .join(', ') // links to bundle browser builds - const bundles = ['background', 'ui', 'inpage', 'contentscript', 'ui-libs', 'bg-libs', 'phishing-detect'] - const bundleLinks = bundles.map((bundle) => { - const url = `${BUILD_LINK_BASE}/build-artifacts/source-map-explorer/${bundle}.html` - return `${bundle}` - }).join(', ') + const bundles = [ + 'background', + 'ui', + 'inpage', + 'contentscript', + 'ui-libs', + 'bg-libs', + 'phishing-detect', + ] + const bundleLinks = bundles + .map((bundle) => { + const url = `${BUILD_LINK_BASE}/build-artifacts/source-map-explorer/${bundle}.html` + return `${bundle}` + }) + .join(', ') // links to bundle browser builds const depVizUrl = `${BUILD_LINK_BASE}/build-artifacts/deps-viz/background/index.html` @@ -57,13 +68,19 @@ async function start () { `dep viz: ${depVizLink}`, `all artifacts`, ] - const hiddenContent = `
    ${contentRows.map((row) => `
  • ${row}
  • `).join('\n')}
` + const hiddenContent = `
    ${contentRows + .map((row) => `
  • ${row}
  • `) + .join('\n')}
` const exposedContent = `Builds ready [${SHORT_SHA1}]` const artifactsBody = `
${exposedContent}${hiddenContent}
` const benchmarkResults = {} for (const platform of platforms) { - const benchmarkPath = path.resolve(__dirname, '..', path.join('test-artifacts', platform, 'benchmark', 'pageload.json')) + const benchmarkPath = path.resolve( + __dirname, + '..', + path.join('test-artifacts', platform, 'benchmark', 'pageload.json'), + ) try { const data = await fs.readFile(benchmarkPath, 'utf8') const benchmark = JSON.parse(data) @@ -72,7 +89,9 @@ async function start () { if (error.code === 'ENOENT') { console.log(`No benchmark data found for ${platform}; skipping`) } else { - console.error(`Error encountered processing benchmark data for '${platform}': '${error}'`) + console.error( + `Error encountered processing benchmark data for '${platform}': '${error}'`, + ) } } } @@ -82,8 +101,14 @@ async function start () { let commentBody if (benchmarkResults[summaryPlatform]) { try { - const summaryPageLoad = Math.round(parseFloat(benchmarkResults[summaryPlatform][summaryPage].average.load)) - const summaryPageLoadMarginOfError = Math.round(parseFloat(benchmarkResults[summaryPlatform][summaryPage].marginOfError.load)) + const summaryPageLoad = Math.round( + parseFloat(benchmarkResults[summaryPlatform][summaryPage].average.load), + ) + const summaryPageLoadMarginOfError = Math.round( + parseFloat( + benchmarkResults[summaryPlatform][summaryPage].marginOfError.load, + ), + ) const benchmarkSummary = `Page Load Metrics (${summaryPageLoad} ± ${summaryPageLoadMarginOfError} ms)` const allPlatforms = new Set() @@ -117,14 +142,20 @@ async function start () { for (const metric of allMetrics) { let metricData = `${metric}` for (const measure of allMeasures) { - metricData += `${Math.round(parseFloat(benchmarkResults[platform][page][measure][metric]))}` + metricData += `${Math.round( + parseFloat(benchmarkResults[platform][page][measure][metric]), + )}` } metricRows.push(metricData) } - metricRows[0] = `${capitalizeFirstLetter(page)}${metricRows[0]}` + metricRows[0] = `${capitalizeFirstLetter(page)}${metricRows[0]}` pageRows.push(...metricRows) } - pageRows[0] = `${capitalizeFirstLetter(platform)}${pageRows[0]}` + pageRows[0] = `${capitalizeFirstLetter(platform)}${pageRows[0]}` for (const row of pageRows) { tableRows.push(`${row}`) } @@ -134,7 +165,9 @@ async function start () { for (const measure of allMeasures) { benchmarkTableHeaders.push(`${capitalizeFirstLetter(measure)} (ms)`) } - const benchmarkTableHeader = `${benchmarkTableHeaders.map((header) => `${header}`).join('')}` + const benchmarkTableHeader = `${benchmarkTableHeaders + .map((header) => `${header}`) + .join('')}` const benchmarkTableBody = `${tableRows.join('')}` const benchmarkTable = `${benchmarkTableHeader}${benchmarkTableBody}
` const benchmarkBody = `
${benchmarkSummary}${benchmarkTable}
` @@ -158,7 +191,7 @@ async function start () { body: JSON_PAYLOAD, headers: { 'User-Agent': 'metamaskbot', - 'Authorization': `token ${GITHUB_COMMENT_TOKEN}`, + Authorization: `token ${GITHUB_COMMENT_TOKEN}`, }, }) if (!response.ok) { diff --git a/development/mock-3box.js b/development/mock-3box.js index e4260c610..14c888f86 100644 --- a/development/mock-3box.js +++ b/development/mock-3box.js @@ -1,14 +1,14 @@ -function delay (time) { +function delay(time) { return new Promise((resolve) => setTimeout(resolve, time)) } -async function loadFromMock3Box (key) { +async function loadFromMock3Box(key) { const res = await window.fetch(`http://localhost:8889?key=${key}`) const text = await res.text() return text.length ? JSON.parse(text) : null } -async function saveToMock3Box (key, newDataAtKey) { +async function saveToMock3Box(key, newDataAtKey) { const res = await window.fetch('http://localhost:8889', { method: 'POST', body: JSON.stringify({ @@ -21,7 +21,7 @@ async function saveToMock3Box (key, newDataAtKey) { } class Mock3Box { - static openBox (address) { + static openBox(address) { this.address = address return Promise.resolve({ onSyncDone: (cb) => { @@ -39,11 +39,16 @@ class Mock3Box { private: { get: async (key) => { await delay(50) - const res = await loadFromMock3Box(`${this.address}-${this.spaceName}-${key}`) + const res = await loadFromMock3Box( + `${this.address}-${this.spaceName}-${key}`, + ) return res }, set: async (key, data) => { - await saveToMock3Box(`${this.address}-${this.spaceName}-${key}`, data) + await saveToMock3Box( + `${this.address}-${this.spaceName}-${key}`, + data, + ) await delay(50) return null }, @@ -54,11 +59,9 @@ class Mock3Box { }) } - static async getConfig (address) { + static async getConfig(address) { const backup = await loadFromMock3Box(`${address}-metamask-metamaskBackup`) - return backup - ? { spaces: { metamask: {} } } - : {} + return backup ? { spaces: { metamask: {} } } : {} } } diff --git a/development/mock-segment.js b/development/mock-segment.js new file mode 100644 index 000000000..f4e2acb58 --- /dev/null +++ b/development/mock-segment.js @@ -0,0 +1,68 @@ +const { createSegmentServer } = require('./lib/create-segment-server') +const { parsePort } = require('./lib/parse-port') + +const DEFAULT_PORT = 9090 +const prefix = '[mock-segment]' + +function onRequest(request, response, events) { + console.log(`${prefix}: ${request.method} ${request.url}`) + const eventDescriptions = events.map((event) => { + if (event.type === 'track') { + return event.event + } else if (event.type === 'page') { + return event.name + } + return `[Unrecognized event type: ${event.type}]` + }) + console.log(`${prefix}: Events received: ${eventDescriptions.join(', ')}`) + + response.statusCode = 200 + response.end() +} + +function onError(error) { + console.error(error) + process.exit(1) +} + +/** + * This is a mock Segment API meant to be run from the command line. It will start a server + * with the port specified, and respond with HTTP 200 to all requests. Any requests will be + * logged to the console, along with the parsed Segment events included in the request (if + * any) + * + * This can be used with the MetaMask extension by setting the `SEGMENT_HOST` environment + * variable or config entry when building MetaMask. + * + * For example, to build MetaMask for use with this mock Segment server, you could set the + * following values in `.metamaskrc` before building: + * + * SEGMENT_HOST='http://localhost:9090' + * SEGMENT_WRITE_KEY=FAKE + * SEGMENT_LEGACY_WRITE_KEY=FAKE + * + * Note that the Segment keys must also be set - otherwise the extension will not send any + * metric events. + */ +const main = async () => { + const args = process.argv.slice(2) + + let port = process.env.port || DEFAULT_PORT + + while (args.length) { + if (/^(--port|-p)$/u.test(args[0])) { + if (args[1] === undefined) { + throw new Error('Missing port argument') + } + port = parsePort(args[1]) + args.splice(0, 2) + } + } + + const server = createSegmentServer(onRequest, onError) + + await server.start(port) + console.log(`${prefix}: Listening on port ${port}`) +} + +main().catch(onError) diff --git a/development/sentry-publish.js b/development/sentry-publish.js index 80c373608..853d1391d 100644 --- a/development/sentry-publish.js +++ b/development/sentry-publish.js @@ -7,7 +7,7 @@ const VERSION = require('../dist/chrome/manifest.json').version // eslint-disabl start().catch(console.error) -async function start () { +async function start() { const authWorked = await checkIfAuthWorks() if (!authWorked) { console.log(`Sentry auth failed...`) @@ -16,48 +16,62 @@ async function start () { const versionAlreadyExists = await checkIfVersionExists() // abort if versions exists if (versionAlreadyExists) { - console.log(`Version "${VERSION}" already exists on Sentry, skipping version creation`) + console.log( + `Version "${VERSION}" already exists on Sentry, skipping version creation`, + ) } else { // create sentry release console.log(`creating Sentry release for "${VERSION}"...`) - await exec(`sentry-cli releases --org 'metamask' --project 'metamask' new ${VERSION}`) - console.log(`removing any existing files from Sentry release "${VERSION}"...`) - await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} delete --all`) + await exec( + `sentry-cli releases --org 'metamask' --project 'metamask' new ${VERSION}`, + ) + console.log( + `removing any existing files from Sentry release "${VERSION}"...`, + ) + await exec( + `sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} delete --all`, + ) } // check if version has artifacts or not - const versionHasArtifacts = versionAlreadyExists && await checkIfVersionHasArtifacts() + const versionHasArtifacts = + versionAlreadyExists && (await checkIfVersionHasArtifacts()) if (versionHasArtifacts) { - console.log(`Version "${VERSION}" already has artifacts on Sentry, skipping sourcemap upload`) + console.log( + `Version "${VERSION}" already has artifacts on Sentry, skipping sourcemap upload`, + ) return } // upload sentry source and sourcemaps await exec(`./development/sentry-upload-artifacts.sh --release ${VERSION}`) - } -async function checkIfAuthWorks () { +async function checkIfAuthWorks() { const itWorked = await doesNotFail(async () => { await exec(`sentry-cli releases --org 'metamask' --project 'metamask' list`) }) return itWorked } -async function checkIfVersionExists () { +async function checkIfVersionExists() { const versionAlreadyExists = await doesNotFail(async () => { - await exec(`sentry-cli releases --org 'metamask' --project 'metamask' info ${VERSION}`) + await exec( + `sentry-cli releases --org 'metamask' --project 'metamask' info ${VERSION}`, + ) }) return versionAlreadyExists } -async function checkIfVersionHasArtifacts () { - const artifacts = await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} list`) +async function checkIfVersionHasArtifacts() { + const artifacts = await exec( + `sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} list`, + ) // When there's no artifacts, we get a response from the shell like this ['', ''] return artifacts[0] && artifacts[0].length > 0 } -async function doesNotFail (asyncFn) { +async function doesNotFail(asyncFn) { try { await asyncFn() return true diff --git a/development/show-deps-install-scripts.js b/development/show-deps-install-scripts.js index 12301fe0d..3421ed351 100644 --- a/development/show-deps-install-scripts.js +++ b/development/show-deps-install-scripts.js @@ -14,7 +14,9 @@ readInstalled('./', { dev: true }, function (err, data) { const packageScripts = packageData.scripts || {} const scriptKeys = Reflect.ownKeys(packageScripts) - const hasInstallScript = installScripts.some((installKey) => scriptKeys.includes(installKey)) + const hasInstallScript = installScripts.some((installKey) => + scriptKeys.includes(installKey), + ) if (!hasInstallScript) { return } diff --git a/development/sourcemap-validator.js b/development/sourcemap-validator.js index cb5f6f6c8..6730b8785 100644 --- a/development/sourcemap-validator.js +++ b/development/sourcemap-validator.js @@ -18,7 +18,7 @@ start().catch((error) => { process.exit(1) }) -async function start () { +async function start() { const targetFiles = [ `background.js`, // `bg-libs`, skipped because source maps are invalid due to browserify bug: https://github.com/browserify/browserify/issues/1971 @@ -40,37 +40,53 @@ async function start () { } } -async function validateSourcemapForFile ({ buildName }) { +async function validateSourcemapForFile({ buildName }) { console.log(`build "${buildName}"`) const platform = `chrome` // load build and sourcemaps let rawBuild try { - const filePath = path.join(__dirname, `/../dist/${platform}/`, `${buildName}`) + const filePath = path.join( + __dirname, + `/../dist/${platform}/`, + `${buildName}`, + ) rawBuild = await fsAsync.readFile(filePath, 'utf8') } catch (_) { // empty } if (!rawBuild) { - throw new Error(`SourcemapValidator - failed to load source file for "${buildName}"`) + throw new Error( + `SourcemapValidator - failed to load source file for "${buildName}"`, + ) } // attempt to load in dist mode let rawSourceMap try { - const filePath = path.join(__dirname, `/../dist/sourcemaps/`, `${buildName}.map`) + const filePath = path.join( + __dirname, + `/../dist/sourcemaps/`, + `${buildName}.map`, + ) rawSourceMap = await fsAsync.readFile(filePath, 'utf8') } catch (_) { // empty } // attempt to load in dev mode try { - const filePath = path.join(__dirname, `/../dist/${platform}/`, `${buildName}.map`) + const filePath = path.join( + __dirname, + `/../dist/${platform}/`, + `${buildName}.map`, + ) rawSourceMap = await fsAsync.readFile(filePath, 'utf8') } catch (_) { // empty } if (!rawSourceMap) { - throw new Error(`SourcemapValidator - failed to load sourcemaps for "${buildName}"`) + throw new Error( + `SourcemapValidator - failed to load sourcemaps for "${buildName}"`, + ) } const consumer = await new SourceMapConsumer(rawSourceMap) @@ -96,7 +112,9 @@ async function validateSourcemapForFile ({ buildName }) { // warn if source content is missing if (!result.source) { valid = false - console.warn(`!! missing source for position: ${JSON.stringify(position)}`) + console.warn( + `!! missing source for position: ${JSON.stringify(position)}`, + ) // const buildLine = buildLines[position.line - 1] console.warn(` origin in build:`) console.warn(` ${buildLines[position.line - 2]}`) @@ -112,7 +130,9 @@ async function validateSourcemapForFile ({ buildName }) { const isMaybeValid = portion.includes(targetString) if (!isMaybeValid) { valid = false - console.error(`Sourcemap seems invalid:\n${getFencedCode(result.source, line)}`) + console.error( + `Sourcemap seems invalid:\n${getFencedCode(result.source, line)}`, + ) } }) }) @@ -123,18 +143,25 @@ async function validateSourcemapForFile ({ buildName }) { const CODE_FENCE_LENGTH = 80 const TITLE_PADDING_LENGTH = 1 -function getFencedCode (filename, code) { - const title = `${' '.repeat(TITLE_PADDING_LENGTH)}${filename}${' '.repeat(TITLE_PADDING_LENGTH)}` - const openingFenceLength = Math.max(CODE_FENCE_LENGTH - (filename.length + (TITLE_PADDING_LENGTH * 2)), 0) +function getFencedCode(filename, code) { + const title = `${' '.repeat(TITLE_PADDING_LENGTH)}${filename}${' '.repeat( + TITLE_PADDING_LENGTH, + )}` + const openingFenceLength = Math.max( + CODE_FENCE_LENGTH - (filename.length + TITLE_PADDING_LENGTH * 2), + 0, + ) const startOpeningFenceLength = Math.floor(openingFenceLength / 2) const endOpeningFenceLength = Math.ceil(openingFenceLength / 2) - const openingFence = `${'='.repeat(startOpeningFenceLength)}${title}${'='.repeat(endOpeningFenceLength)}` + const openingFence = `${'='.repeat( + startOpeningFenceLength, + )}${title}${'='.repeat(endOpeningFenceLength)}` const closingFence = '='.repeat(CODE_FENCE_LENGTH) return `${openingFence}\n${code}\n${closingFence}\n` } -function indicesOf (substring, string) { +function indicesOf(substring, string) { const a = [] let i = -1 while ((i = string.indexOf(substring, i + 1)) >= 0) { diff --git a/development/static-server.js b/development/static-server.js index a20908878..1690fc0e2 100644 --- a/development/static-server.js +++ b/development/static-server.js @@ -5,6 +5,7 @@ const chalk = require('chalk') const pify = require('pify') const createStaticServer = require('./create-static-server') +const { parsePort } = require('./lib/parse-port') const fsStat = pify(fs.stat) const DEFAULT_PORT = 9080 @@ -13,9 +14,13 @@ const onResponse = (request, response) => { if (response.statusCode >= 400) { console.log(chalk`{gray '-->'} {red ${response.statusCode}} ${request.url}`) } else if (response.statusCode >= 200 && response.statusCode < 300) { - console.log(chalk`{gray '-->'} {green ${response.statusCode}} ${request.url}`) + console.log( + chalk`{gray '-->'} {green ${response.statusCode}} ${request.url}`, + ) } else { - console.log(chalk`{gray '-->'} {green.dim ${response.statusCode}} ${request.url}`) + console.log( + chalk`{gray '-->'} {green.dim ${response.statusCode}} ${request.url}`, + ) } } const onRequest = (request, response) => { @@ -33,16 +38,6 @@ const startServer = ({ port, rootDirectory }) => { }) } -const parsePort = (portString) => { - const port = Number(portString) - if (!Number.isInteger(port)) { - throw new Error(`Port '${portString}' is invalid; must be an integer`) - } else if (port < 0 || port > 65535) { - throw new Error(`Port '${portString}' is out of range; must be between 0 and 65535 inclusive`) - } - return port -} - const parseDirectoryArgument = async (pathString) => { const resolvedPath = path.resolve(pathString) const directoryStats = await fsStat(resolvedPath) @@ -61,7 +56,7 @@ const main = async () => { } while (args.length) { - if ((/^(--port|-p)$/u).test(args[0])) { + if (/^(--port|-p)$/u.test(args[0])) { if (args[1] === undefined) { throw new Error('Missing port argument') } @@ -76,8 +71,7 @@ const main = async () => { startServer(options) } -main() - .catch((error) => { - console.error(error) - process.exit(1) - }) +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/development/verify-locale-strings.js b/development/verify-locale-strings.js index a26e421c8..46bc7f83e 100644 --- a/development/verify-locale-strings.js +++ b/development/verify-locale-strings.js @@ -46,19 +46,21 @@ for (const arg of process.argv.slice(2)) { } } -main() - .catch((error) => { - log.error(error) - process.exit(1) - }) +main().catch((error) => { + log.error(error) + process.exit(1) +}) -async function main () { +async function main() { if (specifiedLocale) { log.info(`Verifying selected locale "${specifiedLocale}":\n`) - const locale = localeIndex.find((localeMeta) => localeMeta.code === specifiedLocale) - const failed = locale.code === 'en' ? - await verifyEnglishLocale() : - await verifyLocale(locale) + const locale = localeIndex.find( + (localeMeta) => localeMeta.code === specifiedLocale, + ) + const failed = + locale.code === 'en' + ? await verifyEnglishLocale() + : await verifyLocale(locale) if (failed) { process.exit(1) } @@ -81,11 +83,11 @@ async function main () { } } -function getLocalePath (code) { +function getLocalePath(code) { return path.resolve(__dirname, '..', 'app', '_locales', code, 'messages.json') } -async function getLocale (code) { +async function getLocale(code) { try { const localeFilePath = getLocalePath(code) const fileContents = await readFile(localeFilePath, 'utf8') @@ -100,10 +102,14 @@ async function getLocale (code) { } } -async function writeLocale (code, locale) { +async function writeLocale(code, locale) { try { const localeFilePath = getLocalePath(code) - return writeFile(localeFilePath, `${JSON.stringify(locale, null, 2)}\n`, 'utf8') + return writeFile( + localeFilePath, + `${JSON.stringify(locale, null, 2)}\n`, + 'utf8', + ) } catch (e) { if (e.code === 'ENOENT') { log.error('Locale file not found') @@ -114,15 +120,22 @@ async function writeLocale (code, locale) { } } -async function verifyLocale (code) { +async function verifyLocale(code) { const englishLocale = await getLocale('en') const targetLocale = await getLocale(code) - const extraItems = compareLocalesForMissingItems({ base: targetLocale, subject: englishLocale }) - const missingItems = compareLocalesForMissingItems({ base: englishLocale, subject: targetLocale }) + const extraItems = compareLocalesForMissingItems({ + base: targetLocale, + subject: englishLocale, + }) + const missingItems = compareLocalesForMissingItems({ + base: englishLocale, + subject: targetLocale, + }) const englishEntryCount = Object.keys(englishLocale).length - const coveragePercent = 100 * (englishEntryCount - missingItems.length) / englishEntryCount + const coveragePercent = + (100 * (englishEntryCount - missingItems.length)) / englishEntryCount if (extraItems.length) { console.log(`**${code}**: ${extraItems.length} unused messages`) @@ -160,18 +173,25 @@ async function verifyLocale (code) { return false } -async function verifyEnglishLocale () { +async function verifyEnglishLocale() { const englishLocale = await getLocale('en') - const javascriptFiles = await findJavascriptFiles(path.resolve(__dirname, '..', 'ui')) + const uiJSFiles = await findJavascriptFiles( + path.resolve(__dirname, '..', 'ui'), + ) + const sharedJSFiles = await findJavascriptFiles( + path.resolve(__dirname, '..', 'shared'), + ) + + const javascriptFiles = sharedJSFiles.concat(uiJSFiles) // match "t(`...`)" because constructing message keys from template strings // prevents this script from finding the messages, and then inappropriately // deletes them - const templateStringRegex = /\bt\(`.*`\)/ug + const templateStringRegex = /\bt\(`.*`\)/gu const templateUsage = [] // match the keys from the locale file - const keyRegex = /'(\w+)'|"(\w+)"/ug + const keyRegex = /'(\w+)'|"(\w+)"/gu const usedMessages = new Set() for await (const fileContents of getFileContents(javascriptFiles)) { for (const match of matchAll.call(fileContents, keyRegex)) { @@ -189,8 +209,10 @@ async function verifyEnglishLocale () { const messageExceptions = ['appName', 'appDescription'] const englishMessages = Object.keys(englishLocale) - const unusedMessages = englishMessages - .filter((message) => !messageExceptions.includes(message) && !usedMessages.has(message)) + const unusedMessages = englishMessages.filter( + (message) => + !messageExceptions.includes(message) && !usedMessages.has(message), + ) if (unusedMessages.length) { console.log(`**en**: ${unusedMessages.length} unused messages`) @@ -223,12 +245,14 @@ async function verifyEnglishLocale () { return true // failed === true } -async function findJavascriptFiles (rootDir) { +async function findJavascriptFiles(rootDir) { const javascriptFiles = [] const contents = await readdir(rootDir, { withFileTypes: true }) for (const file of contents) { if (file.isDirectory()) { - javascriptFiles.push(...(await findJavascriptFiles(path.join(rootDir, file.name)))) + javascriptFiles.push( + ...(await findJavascriptFiles(path.join(rootDir, file.name))), + ) } else if (file.isFile() && file.name.endsWith('.js')) { javascriptFiles.push(path.join(rootDir, file.name)) } @@ -236,12 +260,12 @@ async function findJavascriptFiles (rootDir) { return javascriptFiles } -async function * getFileContents (filenames) { +async function* getFileContents(filenames) { for (const filename of filenames) { yield readFile(filename, 'utf8') } } -function compareLocalesForMissingItems ({ base, subject }) { +function compareLocalesForMissingItems({ base, subject }) { return Object.keys(base).filter((key) => !subject[key]) } diff --git a/package.json b/package.json index 457ba244a..3dd980075 100644 --- a/package.json +++ b/package.json @@ -70,10 +70,10 @@ "@formatjs/intl-relativetimeformat": "^5.2.6", "@fortawesome/fontawesome-free": "^5.13.0", "@material-ui/core": "^4.11.0", - "@metamask/controllers": "^3.1.0", + "@metamask/controllers": "^4.0.2", "@metamask/eth-ledger-bridge-keyring": "^0.2.6", "@metamask/eth-token-tracker": "^3.0.1", - "@metamask/etherscan-link": "^1.1.0", + "@metamask/etherscan-link": "^1.2.0", "@metamask/inpage-provider": "^6.1.0", "@metamask/jazzicon": "^2.0.0", "@metamask/logo": "^2.5.0", @@ -83,7 +83,7 @@ "@sentry/integrations": "^5.26.0", "@zxing/library": "^0.8.0", "abortcontroller-polyfill": "^1.4.0", - "analytics-node": "^3.4.0-beta.2", + "analytics-node": "^3.4.0-beta.3", "await-semaphore": "^0.1.1", "bignumber.js": "^4.1.0", "bn.js": "^4.11.7", @@ -108,7 +108,7 @@ "eth-method-registry": "^1.2.0", "eth-phishing-detect": "^1.1.14", "eth-query": "^2.1.2", - "eth-sig-util": "^2.3.0", + "eth-sig-util": "^3.0.0", "eth-trezor-keyring": "^0.4.0", "ethereum-ens-network-map": "^1.0.2", "ethereumjs-abi": "^0.6.4", @@ -186,7 +186,7 @@ "@babel/register": "^7.5.5", "@metamask/eslint-config": "^4.1.0", "@metamask/forwarder": "^1.1.0", - "@metamask/test-dapp": "^3.2.0", + "@metamask/test-dapp": "^4.0.1", "@sentry/cli": "^1.58.0", "@storybook/addon-actions": "^5.3.14", "@storybook/addon-backgrounds": "^5.3.14", @@ -221,6 +221,7 @@ "eslint-plugin-import": "^2.22.0", "eslint-plugin-mocha": "^8.0.0", "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-react": "~7.20.0", "eslint-plugin-react-hooks": "^4.0.4", "fancy-log": "^1.3.3", diff --git a/shared/constants/transaction.js b/shared/constants/transaction.js new file mode 100644 index 000000000..cce09fb8d --- /dev/null +++ b/shared/constants/transaction.js @@ -0,0 +1,147 @@ +/** + * Transaction Type is a MetaMask construct used internally + * @typedef {Object} TransactionTypes + * @property {'standard'} STANDARD - A standard transaction, usually the first with + * a given nonce + * @property {'cancel'} CANCEL - A transaction submitted with the same nonce as a + * previous transaction, a higher gas price and a zeroed out send amount. Useful + * for users who accidentally send to erroneous addresses or if they send too much. + * @property {'retry'} RETRY - When a transaction is failed it can be retried by + * resubmitting the same transaction with a higher gas fee. This type is also used + * to speed up pending transactions. This is accomplished by creating a new tx with + * the same nonce and higher gas fees. + */ + +/** + * @type {TransactionTypes} + */ +export const TRANSACTION_TYPES = { + STANDARD: 'standard', + CANCEL: 'cancel', + RETRY: 'retry', +} + +/** + * Transaction Status is a mix of Ethereum and MetaMask terminology, used internally + * for transaction processing. + * @typedef {Object} TransactionStatuses + * @property {'unapproved'} UNAPPROVED - A new transaction that the user has not + * approved or rejected + * @property {'approved'} APPROVED - The user has approved the transaction in the + * MetaMask UI + * @property {'rejected'} REJECTED - The user has rejected the transaction in the + * MetaMask UI + * @property {'signed'} SIGNED - The transaction has been signed + * @property {'submitted'} SUBMITTED - The transaction has been submitted to network + * @property {'failed'} FAILED - The transaction has failed for some reason + * @property {'dropped'} DROPPED - The transaction was dropped due to a tx with same + * nonce being accepted + * @property {'confirmed'} CONFIRMED - The transaction was confirmed by the network + */ + +/** + * @type {TransactionStatuses} + */ +export const TRANSACTION_STATUSES = { + UNAPPROVED: 'unapproved', + APPROVED: 'approved', + REJECTED: 'rejected', + SIGNED: 'signed', + SUBMITTED: 'submitted', + FAILED: 'failed', + DROPPED: 'dropped', + CONFIRMED: 'confirmed', +} + +/** + * @typedef {Object} TransactionCategories + * @property {'transfer'} TOKEN_METHOD_TRANSFER - A token transaction where the user + * is sending tokens that they own to another address + * @property {'transferfrom'} TOKEN_METHOD_TRANSFER_FROM - A token transaction + * transferring tokens from an account that the sender has an allowance of. + * For more information on allowances, see the approve category. + * @property {'approve'} TOKEN_METHOD_APPROVE - A token transaction requesting an + * allowance of the token to spend on behalf of the user + * @property {'incoming'} INCOMING - An incoming (deposit) transaction + * @property {'sentEther'} SENT_ETHER - A transaction sending ether to a recipient + * @property {'contractInteraction'} CONTRACT_INTERACTION - A transaction that is + * interacting with a smart contract's methods that we have not treated as a special + * case, such as approve, transfer, and transferfrom + * @property {'contractDeployment'} DEPLOY_CONTRACT - A transaction that deployed + * a smart contract + * @property {'swap'} SWAP - A transaction swapping one token for another through + * MetaMask Swaps + * @property {'swapApproval'} SWAP_APPROVAL - Similar to the approve category, a swap + * approval is a special case of ERC20 approve method that requests an allowance of + * the token to spend on behalf of the user for the MetaMask Swaps contract. The first + * swap for any token will have an accompanying swapApproval transaction. + */ + +/** + * @type {TransactionCategories} + */ +export const TRANSACTION_CATEGORIES = { + TOKEN_METHOD_TRANSFER: 'transfer', + TOKEN_METHOD_TRANSFER_FROM: 'transferfrom', + TOKEN_METHOD_APPROVE: 'approve', + INCOMING: 'incoming', + SENT_ETHER: 'sentEther', + CONTRACT_INTERACTION: 'contractInteraction', + DEPLOY_CONTRACT: 'contractDeployment', + SWAP: 'swap', + SWAP_APPROVAL: 'swapApproval', +} + +/** + * Transaction Group Status is a MetaMask construct to track the status of groups + * of transactions. + * @typedef {Object} TransactionGroupStatuses + * @property {'cancelled'} CANCELLED - A cancel type transaction in the group was + * confirmed + * @property {'pending'} PENDING - The primaryTransaction of the group has a status + * that is one of TRANSACTION_STATUSES.APPROVED, TRANSACTION_STATUSES.UNAPPROVED + * or TRANSACTION_STATUSES.SUBMITTED + */ + +/** + * @type {TransactionGroupStatuses} + */ +export const TRANSACTION_GROUP_STATUSES = { + CANCELLED: 'cancelled', + PENDING: 'pending', +} + +/** + * Transaction Group Category is a MetaMask construct to categorize the intent + * of a group of transactions for purposes of displaying in the UI + * @typedef {Object} TransactionGroupCategories + * @property {'send'} SEND - Transaction group representing ether being sent from + * the user. + * @property {'receive'} RECEIVE - Transaction group representing a deposit/incoming + * transaction. This category maps 1:1 with TRANSACTION_CATEGORIES.INCOMING. + * @property {'interaction'} INTERACTION - Transaction group representing + * an interaction with a smart contract's methods. + * @property {'approval'} APPROVAL - Transaction group representing a request for an + * allowance of a token to spend on the user's behalf. + * @property {'signature-request'} SIGNATURE_REQUEST - Transaction group representing + * a signature request This currently only shows up in the UI when its pending user + * approval in the UI. Once the user approves or rejects it will no longer show in + * activity. + * @property {'swap'} SWAP - Transaction group representing a token swap through + * MetaMask Swaps. This transaction group's primary currency changes depending + * on context. If the user is viewing an asset page for a token received from a swap, + * the primary currency will be the received token. Otherwise the token exchanged + * will be shown. + */ + +/** + * @type {TransactionGroupCategories} + */ +export const TRANSACTION_GROUP_CATEGORIES = { + SEND: 'send', + RECEIVE: 'receive', + INTERACTION: 'interaction', + APPROVAL: 'approval', + SIGNATURE_REQUEST: 'signature-request', + SWAP: 'swap', +} diff --git a/shared/modules/metametrics.js b/shared/modules/metametrics.js index e0fc186f0..523ac6933 100644 --- a/shared/modules/metametrics.js +++ b/shared/modules/metametrics.js @@ -1,26 +1,36 @@ import Analytics from 'analytics-node' -import { omit, pick } from 'lodash' +import { merge, omit, pick } from 'lodash' -// flushAt controls how many events are collected in the queue before they -// are sent to segment. I recommend a queue size of one due to an issue with -// detecting and flushing events in an extension beforeunload doesn't work in -// a notification context. Because notification windows are opened and closed -// in reaction to the very events we want to track, it is problematic to cache -// at all. -const flushAt = 1 +// flushAt controls how many events are sent to segment at once. Segment +// will hold onto a queue of events until it hits this number, then it sends +// them as a batch. This setting defaults to 20, but that is too high for +// notification workflows. We also cannot send each event as singular payloads +// because it seems to bombard segment and potentially cause event loss. +// I chose 5 here because it is sufficiently high enough to optimize our network +// requests, while also being low enough to be reasonable. +const flushAt = process.env.METAMASK_ENVIRONMENT === 'production' ? 5 : 1 +// flushInterval controls how frequently the queue is flushed to segment. +// This happens regardless of the size of the queue. The default setting is +// 10,000ms (10 seconds). This default is absurdly high for our typical user +// flow through confirmations. I have chosen 10 ms here because it works really +// well with our wrapped track function. The track function returns a promise +// that is only fulfilled when it has been sent to segment. A 10 ms delay is +// negligible to the user, but allows us to properly batch events that happen +// in rapid succession. +const flushInterval = 10 export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000' const segmentNoop = { - track (_, callback = () => undefined) { + track(_, callback = () => undefined) { // Need to call the callback so that environments without a segment id still // resolve the promise from trackMetaMetricsEvent return callback() }, - page () { + page() { // noop }, - identify () { + identify() { // noop }, } @@ -45,21 +55,38 @@ const trackableSendCounts = { 25000: true, } -export function sendCountIsTrackable (sendCount) { +export function sendCountIsTrackable(sendCount) { return Boolean(trackableSendCounts[sendCount]) } +const isDevOrTestEnvironment = Boolean( + process.env.METAMASK_DEBUG || process.env.IN_TEST, +) + +// This allows us to overwrite the metric destination for testing purposes +const host = process.env.SEGMENT_HOST ?? undefined + // We do not want to track events on development builds unless specifically // provided a SEGMENT_WRITE_KEY. This also holds true for test environments and // E2E, which is handled in the build process by never providing the SEGMENT_WRITE_KEY // when process.env.IN_TEST is truthy -export const segment = process.env.SEGMENT_WRITE_KEY - ? new Analytics(process.env.SEGMENT_WRITE_KEY, { flushAt }) - : segmentNoop +export const segment = + !process.env.SEGMENT_WRITE_KEY || (isDevOrTestEnvironment && !host) + ? segmentNoop + : new Analytics(process.env.SEGMENT_WRITE_KEY, { + host, + flushAt, + flushInterval, + }) -export const segmentLegacy = process.env.SEGMENT_LEGACY_WRITE_KEY - ? new Analytics(process.env.SEGMENT_LEGACY_WRITE_KEY, { flushAt }) - : segmentNoop +export const segmentLegacy = + !process.env.SEGMENT_LEGACY_WRITE_KEY || (isDevOrTestEnvironment && !host) + ? segmentNoop + : new Analytics(process.env.SEGMENT_LEGACY_WRITE_KEY, { + host, + flushAt, + flushInterval, + }) /** * We attach context to every meta metrics event that help to qualify our analytics. @@ -72,7 +99,6 @@ export const segmentLegacy = process.env.SEGMENT_LEGACY_WRITE_KEY * @property {string} app.name - the name of the application tracking the event * @property {string} app.version - the version of the application * @property {string} userAgent - the useragent string of the user - * @property {string} locale - the locale string for the user * @property {Object} [page] - an object representing details of the current page * @property {string} [page.path] - the path of the current page (e.g /home) * @property {string} [page.title] - the title of the current page (e.g 'home') @@ -111,6 +137,8 @@ export const segmentLegacy = process.env.SEGMENT_LEGACY_WRITE_KEY * @property {string} category - category to associate event to * @property {boolean} [isOptIn] - happened during opt in/out workflow * @property {object} [properties] - object of custom values to track, snake_case + * @property {object} [sensitiveProperties] - Object of sensitive values to track, snake_case. + * These properties will be sent in an additional event that excludes the user's metaMetricsId. * @property {number} [revenue] - amount of currency that event creates in revenue for MetaMask * @property {string} [currency] - ISO 4127 format currency for events with revenue, defaults to US dollars * @property {number} [value] - Abstract "value" that this event has for MetaMask. @@ -129,21 +157,20 @@ export const segmentLegacy = process.env.SEGMENT_LEGACY_WRITE_KEY * * @param {string} metamaskVersion - The current version of the MetaMask extension. * @param {() => MetaMetricsRequiredState} getDynamicState - A function returning required fields - * @returns {(payload: MetaMetricsEventPayload) => Promise} - function to track an event + * @returns {(payload: MetaMetricsEventPayload) => Promise} function to track an event */ -export function getTrackMetaMetricsEvent ( - metamaskVersion, - getDynamicState, -) { - const version = process.env.METAMASK_ENVIRONMENT === 'production' - ? metamaskVersion - : `${metamaskVersion}-${process.env.METAMASK_ENVIRONMENT}` +export function getTrackMetaMetricsEvent(metamaskVersion, getDynamicState) { + const version = + process.env.METAMASK_ENVIRONMENT === 'production' + ? metamaskVersion + : `${metamaskVersion}-${process.env.METAMASK_ENVIRONMENT}` - return function trackMetaMetricsEvent ({ + return function trackMetaMetricsEvent({ event, category, isOptIn, properties = {}, + sensitiveProperties, revenue, currency, value, @@ -155,6 +182,27 @@ export function getTrackMetaMetricsEvent ( if (!event || !category) { throw new Error('Must specify event and category.') } + // Uses recursion to track a duplicate event with sensitive properties included, + // but metaMetricsId excluded + if (sensitiveProperties) { + if (excludeId === true) { + throw new Error( + 'sensitiveProperties was specified in an event payload that also set the excludeMetaMetricsId flag', + ) + } + trackMetaMetricsEvent({ + event, + category, + isOptIn, + properties: merge(sensitiveProperties, properties), + revenue, + currency, + value, + excludeMetaMetricsId: true, + matomoEvent, + eventContext, + }) + } const { participateInMetaMetrics, context: providedContext, @@ -172,7 +220,11 @@ export function getTrackMetaMetricsEvent ( // to be updated to work with the new tracking plan. I think we should use // a config setting for this instead of trying to match the event name const isSendFlow = Boolean(event.match(/^send|^confirm/u)) - if (isSendFlow && metaMetricsSendCount && !sendCountIsTrackable(metaMetricsSendCount + 1)) { + if ( + isSendFlow && + metaMetricsSendCount && + !sendCountIsTrackable(metaMetricsSendCount + 1) + ) { excludeMetaMetricsId = true } @@ -186,7 +238,6 @@ export function getTrackMetaMetricsEvent ( name: 'MetaMask Extension', version, }, - locale, userAgent: window.navigator.userAgent, ...pick(providedContext, ['page', 'referrer']), ...pick(eventContext, ['page', 'referrer']), @@ -198,13 +249,16 @@ export function getTrackMetaMetricsEvent ( // These values are omitted from properties because they have special meaning // in segment. https://segment.com/docs/connections/spec/track/#properties. // to avoid accidentally using these inappropriately, you must add them as top - // level properties on the event payload. - ...omit(properties, ['revenue', 'currency', 'value']), + // level properties on the event payload. We also exclude locale to prevent consumers + // from overwriting this context level property. We track it as a property + // because not all destinations map locale from context. + ...omit(properties, ['revenue', 'locale', 'currency', 'value']), revenue, value, currency, category, network, + locale, chain_id: chainId, environment_type: environmentType, }, @@ -230,10 +284,7 @@ export function getTrackMetaMetricsEvent ( } return new Promise((resolve, reject) => { - // This is only safe to do because we are no longer batching events through segment. - // If flushAt is greater than one the callback won't be triggered until after a number - // of events have been queued equal to the flushAt value OR flushInterval passes. The - // default flushInterval is ten seconds + // This is only safe to do because we have set an extremely low (10ms) flushInterval. const callback = (err) => { if (err) { return reject(err) diff --git a/test/e2e/address-book.spec.js b/test/e2e/address-book.spec.js index aac315235..6ed55b22b 100644 --- a/test/e2e/address-book.spec.js +++ b/test/e2e/address-book.spec.js @@ -2,11 +2,7 @@ const assert = require('assert') const { By, until } = require('selenium-webdriver') const enLocaleMessages = require('../../app/_locales/en/messages.json') -const { - tinyDelayMs, - regularDelayMs, - largeDelayMs, -} = require('./helpers') +const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers') const { buildWebDriver } = require('./webdriver') const Ganache = require('./ganache') @@ -15,7 +11,8 @@ const ganacheServer = new Ganache() describe('MetaMask', function () { let driver - const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' + const testSeedPhrase = + 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' this.timeout(0) this.bail(true) @@ -24,7 +21,8 @@ describe('MetaMask', function () { await ganacheServer.start({ accounts: [ { - secretKey: '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', + secretKey: + '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', balance: 25000000000000000000, }, ], @@ -38,7 +36,9 @@ describe('MetaMask', function () { const errors = await driver.checkBrowserForConsoleErrors() if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` console.error(new Error(errorMessage)) } } @@ -55,12 +55,18 @@ describe('MetaMask', function () { describe('Going through the first time flow', function () { it('clicks the continue button on the welcome screen', async function () { await driver.findElement(By.css('.welcome-page__header')) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`, + ), + ) await driver.delay(largeDelayMs) }) it('clicks the "Create New Wallet" option', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Create a Wallet')]`), + ) await driver.delay(largeDelayMs) }) @@ -70,8 +76,12 @@ describe('MetaMask', function () { }) it('accepts a secure password', async function () { - const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password')) - const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password')) + const passwordBox = await driver.findElement( + By.css('.first-time-flow__form #create-password'), + ) + const passwordBoxConfirm = await driver.findElement( + By.css('.first-time-flow__form #confirm-password'), + ) await passwordBox.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple') @@ -84,21 +94,33 @@ describe('MetaMask', function () { let seedPhrase it('reveals the seed phrase', async function () { - const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') + const byRevealButton = By.css( + '.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button', + ) await driver.clickElement(byRevealButton) await driver.delay(regularDelayMs) - const revealedSeedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')) + const revealedSeedPhrase = await driver.findElement( + By.css('.reveal-seed-phrase__secret-words'), + ) seedPhrase = await revealedSeedPhrase.getText() assert.equal(seedPhrase.split(' ').length, 12) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.next.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) - async function clickWordAndWait (word) { - await driver.clickElement(By.css(`[data-testid="seed-phrase-sorted"] [data-testid="draggable-seed-${word}"]`)) + async function clickWordAndWait(word) { + await driver.clickElement( + By.css( + `[data-testid="seed-phrase-sorted"] [data-testid="draggable-seed-${word}"]`, + ), + ) await driver.delay(tinyDelayMs) } @@ -109,13 +131,21 @@ describe('MetaMask', function () { await clickWordAndWait(word) } - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) }) it('clicks through the success screen', async function () { - await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) + await driver.findElement( + By.xpath(`//div[contains(text(), 'Congratulations')]`), + ) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) }) @@ -125,15 +155,22 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.account-menu__icon')) await driver.delay(regularDelayMs) - const lockButton = await driver.findClickableElement(By.css('.account-menu__lock-button')) + const lockButton = await driver.findClickableElement( + By.css('.account-menu__lock-button'), + ) assert.equal(await lockButton.getText(), 'Lock') await lockButton.click() await driver.delay(regularDelayMs) }) it('imports seed phrase', async function () { - const restoreSeedLink = await driver.findClickableElement(By.css('.unlock-page__link--import')) - assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase') + const restoreSeedLink = await driver.findClickableElement( + By.css('.unlock-page__link--import'), + ) + assert.equal( + await restoreSeedLink.getText(), + 'Import using account seed phrase', + ) await restoreSeedLink.click() await driver.delay(regularDelayMs) @@ -148,12 +185,18 @@ describe('MetaMask', function () { await passwordInputs[0].sendKeys('correct horse battery staple') await passwordInputs[1].sendKeys('correct horse battery staple') - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.restore.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.restore.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) it('balance renders', async function () { - const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) + const balance = await driver.findElement( + By.css('[data-testid="wallet-balance"] .list-item__heading'), + ) await driver.wait(until.elementTextMatches(balance, /25\s*ETH/u)) await driver.delay(regularDelayMs) }) @@ -164,18 +207,26 @@ describe('MetaMask', function () { await driver.clickElement(By.css('[data-testid="eth-overview-send"]')) await driver.delay(regularDelayMs) - const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]')) + const inputAddress = await driver.findElement( + By.css('input[placeholder="Search, public address (0x), or ENS"]'), + ) await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') await driver.delay(regularDelayMs) await driver.clickElement(By.css('.dialog.send__dialog.dialog--message')) - const addressBookAddModal = await driver.findElement(By.css('span .modal')) + const addressBookAddModal = await driver.findElement( + By.css('span .modal'), + ) await driver.findElement(By.css('.add-to-address-book-modal')) - const addressBookInput = await driver.findElement(By.css('.add-to-address-book-modal__input')) + const addressBookInput = await driver.findElement( + By.css('.add-to-address-book-modal__input'), + ) await addressBookInput.sendKeys('Test Name 1') await driver.delay(tinyDelayMs) - await driver.clickElement(By.css('.add-to-address-book-modal__footer .btn-primary')) + await driver.clickElement( + By.css('.add-to-address-book-modal__footer .btn-primary'), + ) await driver.wait(until.stalenessOf(addressBookAddModal)) @@ -192,18 +243,26 @@ describe('MetaMask', function () { }) it('confirms the transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(largeDelayMs * 2) }) it('finds the transaction in the transactions list', async function () { await driver.clickElement(By.css('[data-testid="home__activity-tab"]')) await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElement( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000) }) }) @@ -213,11 +272,15 @@ describe('MetaMask', function () { await driver.clickElement(By.css('[data-testid="eth-overview-send"]')) await driver.delay(regularDelayMs) - const recipientRowTitle = await driver.findElement(By.css('.send__select-recipient-wrapper__group-item__title')) + const recipientRowTitle = await driver.findElement( + By.css('.send__select-recipient-wrapper__group-item__title'), + ) const recipientRowTitleString = await recipientRowTitle.getText() assert.equal(recipientRowTitleString, 'Test Name 1') - await driver.clickElement(By.css('.send__select-recipient-wrapper__group-item')) + await driver.clickElement( + By.css('.send__select-recipient-wrapper__group-item'), + ) await driver.delay(regularDelayMs) const inputAmount = await driver.findElement(By.css('.unit-input__input')) @@ -230,17 +293,25 @@ describe('MetaMask', function () { }) it('confirms the transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(largeDelayMs * 2) }) it('finds the transaction in the transactions list', async function () { await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 2 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElement( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txValues, /-2\s*ETH/u), 10000) }) }) diff --git a/test/e2e/benchmark.js b/test/e2e/benchmark.js index d8b79a9eb..442533c04 100644 --- a/test/e2e/benchmark.js +++ b/test/e2e/benchmark.js @@ -10,7 +10,7 @@ const { PAGES } = require('./webdriver/driver') const DEFAULT_NUM_SAMPLES = 20 const ALL_PAGES = Object.values(PAGES) -async function measurePage (pageName) { +async function measurePage(pageName) { let metrics await withFixtures({ fixtures: 'imported-account' }, async ({ driver }) => { const passwordField = await driver.findElement(By.css('#password')) @@ -24,7 +24,7 @@ async function measurePage (pageName) { return metrics } -function calculateResult (calc) { +function calculateResult(calc) { return (result) => { const calculatedResult = {} for (const key of Object.keys(result)) { @@ -44,10 +44,13 @@ const standardDeviationResult = calculateResult((array) => { return Math.sqrt(calculateAverage(squareDiffs)) }) // 95% margin of error calculated using Student's t-distribution -const calculateMarginOfError = (array) => ttest(array).confidence()[1] - calculateAverage(array) -const marginOfErrorResult = calculateResult((array) => calculateMarginOfError(array)) +const calculateMarginOfError = (array) => + ttest(array).confidence()[1] - calculateAverage(array) +const marginOfErrorResult = calculateResult((array) => + calculateMarginOfError(array), +) -async function profilePageLoad (pages, numSamples) { +async function profilePageLoad(pages, numSamples) { const results = {} for (const pageName of pages) { const runResults = [] @@ -57,15 +60,30 @@ async function profilePageLoad (pages, numSamples) { if (runResults.some((result) => result.navigation.lenth > 1)) { throw new Error(`Multiple navigations not supported`) - } else if (runResults.some((result) => result.navigation[0].type !== 'navigate')) { - throw new Error(`Navigation type ${runResults.find((result) => result.navigation[0].type !== 'navigate').navigation[0].type} not supported`) + } else if ( + runResults.some((result) => result.navigation[0].type !== 'navigate') + ) { + throw new Error( + `Navigation type ${ + runResults.find((result) => result.navigation[0].type !== 'navigate') + .navigation[0].type + } not supported`, + ) } const result = { firstPaint: runResults.map((metrics) => metrics.paint['first-paint']), - domContentLoaded: runResults.map((metrics) => metrics.navigation[0] && metrics.navigation[0].domContentLoaded), - load: runResults.map((metrics) => metrics.navigation[0] && metrics.navigation[0].load), - domInteractive: runResults.map((metrics) => metrics.navigation[0] && metrics.navigation[0].domInteractive), + domContentLoaded: runResults.map( + (metrics) => + metrics.navigation[0] && metrics.navigation[0].domContentLoaded, + ), + load: runResults.map( + (metrics) => metrics.navigation[0] && metrics.navigation[0].load, + ), + domInteractive: runResults.map( + (metrics) => + metrics.navigation[0] && metrics.navigation[0].domInteractive, + ), } results[pageName] = { @@ -79,7 +97,7 @@ async function profilePageLoad (pages, numSamples) { return results } -async function isWritable (directory) { +async function isWritable(directory) { try { await fs.access(directory, fsConstants.W_OK) return true @@ -91,7 +109,7 @@ async function isWritable (directory) { } } -async function getFirstParentDirectoryThatExists (directory) { +async function getFirstParentDirectoryThatExists(directory) { let nextDirectory = directory for (;;) { try { @@ -108,7 +126,7 @@ async function getFirstParentDirectoryThatExists (directory) { } } -async function main () { +async function main() { const args = process.argv.slice(2) let pages = ['home'] @@ -118,7 +136,7 @@ async function main () { let existingParentDirectory while (args.length) { - if ((/^(--pages|-p)$/u).test(args[0])) { + if (/^(--pages|-p)$/u.test(args[0])) { if (args[1] === undefined) { throw new Error('Missing pages argument') } @@ -129,7 +147,7 @@ async function main () { } } args.splice(0, 2) - } else if ((/^(--samples|-s)$/u).test(args[0])) { + } else if (/^(--samples|-s)$/u.test(args[0])) { if (args[1] === undefined) { throw new Error('Missing number of samples') } @@ -138,14 +156,16 @@ async function main () { throw new Error(`Invalid 'samples' argument given: '${args[1]}'`) } args.splice(0, 2) - } else if ((/^(--out|-o)$/u).test(args[0])) { + } else if (/^(--out|-o)$/u.test(args[0])) { if (args[1] === undefined) { throw new Error('Missing output filename') } outputPath = path.resolve(args[1]) outputDirectory = path.dirname(outputPath) - existingParentDirectory = await getFirstParentDirectoryThatExists(outputDirectory) - if (!await isWritable(existingParentDirectory)) { + existingParentDirectory = await getFirstParentDirectoryThatExists( + outputDirectory, + ) + if (!(await isWritable(existingParentDirectory))) { throw new Error(`Specified directory is not writable: '${args[1]}'`) } args.splice(0, 2) @@ -166,8 +186,7 @@ async function main () { } } -main() - .catch((e) => { - console.error(e) - process.exit(1) - }) +main().catch((e) => { + console.error(e) + process.exit(1) +}) diff --git a/test/e2e/ethereum-on.spec.js b/test/e2e/ethereum-on.spec.js index 784267900..2d90f23d7 100644 --- a/test/e2e/ethereum-on.spec.js +++ b/test/e2e/ethereum-on.spec.js @@ -3,10 +3,7 @@ const webdriver = require('selenium-webdriver') const { By, until } = webdriver const enLocaleMessages = require('../../app/_locales/en/messages.json') -const { - regularDelayMs, - largeDelayMs, -} = require('./helpers') +const { regularDelayMs, largeDelayMs } = require('./helpers') const { buildWebDriver } = require('./webdriver') const Ganache = require('./ganache') @@ -23,7 +20,8 @@ describe('MetaMask', function () { await ganacheServer.start({ accounts: [ { - secretKey: '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', + secretKey: + '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', balance: 25000000000000000000, }, ], @@ -37,7 +35,9 @@ describe('MetaMask', function () { const errors = await driver.checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` console.error(new Error(errorMessage)) } } @@ -54,12 +54,18 @@ describe('MetaMask', function () { describe('Going through the first time flow, but skipping the seed phrase challenge', function () { it('clicks the continue button on the welcome screen', async function () { await driver.findElement(By.css('.welcome-page__header')) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`, + ), + ) await driver.delay(largeDelayMs) }) it('clicks the "Create New Wallet" option', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Create a Wallet')]`), + ) await driver.delay(largeDelayMs) }) @@ -69,8 +75,12 @@ describe('MetaMask', function () { }) it('accepts a secure password', async function () { - const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password')) - const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password')) + const passwordBox = await driver.findElement( + By.css('.first-time-flow__form #create-password'), + ) + const passwordBoxConfirm = await driver.findElement( + By.css('.first-time-flow__form #confirm-password'), + ) await passwordBox.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple') @@ -81,15 +91,25 @@ describe('MetaMask', function () { }) it('skips the seed phrase challenge', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`, + ), + ) await driver.delay(regularDelayMs) - await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) - await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) + await driver.clickElement( + By.css('[data-testid="account-options-menu-button"]'), + ) + await driver.clickElement( + By.css('[data-testid="account-options-menu__account-details"]'), + ) }) it('gets the current accounts address', async function () { - const addressInput = await driver.findElement(By.css('.readonly-input__input')) + const addressInput = await driver.findElement( + By.css('.readonly-input__input'), + ) publicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) @@ -98,7 +118,6 @@ describe('MetaMask', function () { await driver.wait(until.stalenessOf(accountModal)) await driver.delay(regularDelayMs) }) - }) describe('provider listening for events', function () { @@ -110,7 +129,9 @@ describe('MetaMask', function () { await driver.openNewPage('http://127.0.0.1:8080/') await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Connect')]`), + ) await driver.delay(regularDelayMs) @@ -118,15 +139,22 @@ describe('MetaMask', function () { const windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles) - popup = windowHandles.find((handle) => handle !== extension && handle !== dapp) + dapp = await driver.switchToWindowWithTitle( + 'E2E Test Dapp', + windowHandles, + ) + popup = windowHandles.find( + (handle) => handle !== extension && handle !== dapp, + ) await driver.switchToWindow(popup) await driver.delay(regularDelayMs) await driver.clickElement(By.xpath(`//button[contains(text(), 'Next')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Connect')]`), + ) await driver.waitUntilXWindowHandles(2) await driver.switchToWindow(dapp) diff --git a/test/e2e/fixture-server.js b/test/e2e/fixture-server.js index b96ba8e9a..7c0ea5a86 100644 --- a/test/e2e/fixture-server.js +++ b/test/e2e/fixture-server.js @@ -9,11 +9,9 @@ const FIXTURE_SERVER_HOST = 'localhost' const FIXTURE_SERVER_PORT = 12345 class FixtureServer { - constructor () { + constructor() { this._app = new Koa() - this._stateMap = new Map([ - [DEFAULT_STATE_KEY, Object.create(null)], - ]) + this._stateMap = new Map([[DEFAULT_STATE_KEY, Object.create(null)]]) this._initialStateCache = new Map() this._app.use(async (ctx) => { @@ -25,7 +23,7 @@ class FixtureServer { }) } - async start () { + async start() { const options = { host: FIXTURE_SERVER_HOST, port: FIXTURE_SERVER_PORT, @@ -39,7 +37,7 @@ class FixtureServer { }) } - async stop () { + async stop() { if (!this._server) { return } @@ -51,7 +49,7 @@ class FixtureServer { }) } - async loadState (directory) { + async loadState(directory) { const statePath = path.resolve(__dirname, directory, 'state.json') let state @@ -66,7 +64,7 @@ class FixtureServer { this._stateMap.set(CURRENT_STATE_KEY, state) } - _isStateRequest (ctx) { + _isStateRequest(ctx) { return ctx.method === 'GET' && ctx.path === '/state.json' } } diff --git a/test/e2e/from-import-ui.spec.js b/test/e2e/from-import-ui.spec.js index 70774b04f..ec3f68d42 100644 --- a/test/e2e/from-import-ui.spec.js +++ b/test/e2e/from-import-ui.spec.js @@ -3,10 +3,7 @@ const webdriver = require('selenium-webdriver') const { By, Key, until } = webdriver const enLocaleMessages = require('../../app/_locales/en/messages.json') -const { - regularDelayMs, - largeDelayMs, -} = require('./helpers') +const { regularDelayMs, largeDelayMs } = require('./helpers') const { buildWebDriver } = require('./webdriver') const Ganache = require('./ganache') @@ -15,10 +12,13 @@ const ganacheServer = new Ganache() describe('Using MetaMask with an existing account', function () { let driver - const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' + const testSeedPhrase = + 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' const testAddress = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3' - const testPrivateKey2 = '14abe6f4aab7f9f626fe981c864d0adeb5685f289ac9270c27b8fd790b4235d6' - const testPrivateKey3 = 'F4EC2590A0C10DE95FBF4547845178910E40F5035320C516A18C117DE02B5669' + const testPrivateKey2 = + '14abe6f4aab7f9f626fe981c864d0adeb5685f289ac9270c27b8fd790b4235d6' + const testPrivateKey3 = + 'F4EC2590A0C10DE95FBF4547845178910E40F5035320C516A18C117DE02B5669' this.timeout(0) this.bail(true) @@ -27,7 +27,8 @@ describe('Using MetaMask with an existing account', function () { await ganacheServer.start({ accounts: [ { - secretKey: '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', + secretKey: + '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', balance: 25000000000000000000, }, ], @@ -41,7 +42,9 @@ describe('Using MetaMask with an existing account', function () { const errors = await driver.checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` console.error(new Error(errorMessage)) } } @@ -58,12 +61,18 @@ describe('Using MetaMask with an existing account', function () { describe('First time flow starting from an existing seed phrase', function () { it('clicks the continue button on the welcome screen', async function () { await driver.findElement(By.css('.welcome-page__header')) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`, + ), + ) await driver.delay(largeDelayMs) }) it('clicks the "Import Wallet" option', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Import wallet')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Import wallet')]`), + ) await driver.delay(largeDelayMs) }) @@ -73,36 +82,54 @@ describe('Using MetaMask with an existing account', function () { }) it('imports a seed phrase', async function () { - const [seedTextArea] = await driver.findElements(By.css('input[placeholder="Paste seed phrase from clipboard"]')) + const [seedTextArea] = await driver.findElements( + By.css('input[placeholder="Paste seed phrase from clipboard"]'), + ) await seedTextArea.sendKeys(testSeedPhrase) await driver.delay(regularDelayMs) const [password] = await driver.findElements(By.id('password')) await password.sendKeys('correct horse battery staple') - const [confirmPassword] = await driver.findElements(By.id('confirm-password')) + const [confirmPassword] = await driver.findElements( + By.id('confirm-password'), + ) confirmPassword.sendKeys('correct horse battery staple') await driver.clickElement(By.css('.first-time-flow__terms')) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Import')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Import')]`), + ) await driver.delay(regularDelayMs) }) it('clicks through the success screen', async function () { - await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) + await driver.findElement( + By.xpath(`//div[contains(text(), 'Congratulations')]`), + ) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) }) describe('Show account information', function () { it('shows the correct account address', async function () { - await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) - await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) + await driver.clickElement( + By.css('[data-testid="account-options-menu-button"]'), + ) + await driver.clickElement( + By.css('[data-testid="account-options-menu__account-details"]'), + ) await driver.findVisibleElement(By.css('.qr-code__wrapper')) await driver.delay(regularDelayMs) - const [address] = await driver.findElements(By.css('.readonly-input__input')) + const [address] = await driver.findElements( + By.css('.readonly-input__input'), + ) assert.equal(await address.getAttribute('value'), testAddress) await driver.clickElement(By.css('.account-modal__close')) @@ -110,8 +137,12 @@ describe('Using MetaMask with an existing account', function () { }) it('shows a QR code for the account', async function () { - await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) - await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) + await driver.clickElement( + By.css('[data-testid="account-options-menu-button"]'), + ) + await driver.clickElement( + By.css('[data-testid="account-options-menu__account-details"]'), + ) await driver.findVisibleElement(By.css('.qr-code__wrapper')) const detailModal = await driver.findElement(By.css('span .modal')) await driver.delay(regularDelayMs) @@ -127,7 +158,9 @@ describe('Using MetaMask with an existing account', function () { await driver.clickElement(By.css('.account-menu__icon .identicon')) await driver.delay(regularDelayMs) - const lockButton = await driver.findClickableElement(By.css('.account-menu__lock-button')) + const lockButton = await driver.findClickableElement( + By.css('.account-menu__lock-button'), + ) assert.equal(await lockButton.getText(), 'Lock') await lockButton.click() await driver.delay(regularDelayMs) @@ -146,7 +179,9 @@ describe('Using MetaMask with an existing account', function () { await driver.clickElement(By.css('.network-name')) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//span[contains(text(), 'Localhost')]`)) + await driver.clickElement( + By.xpath(`//span[contains(text(), 'Localhost')]`), + ) await driver.delay(largeDelayMs) }) @@ -154,21 +189,29 @@ describe('Using MetaMask with an existing account', function () { await driver.clickElement(By.css('.account-menu__icon')) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//div[contains(text(), 'Create Account')]`)) + await driver.clickElement( + By.xpath(`//div[contains(text(), 'Create Account')]`), + ) await driver.delay(regularDelayMs) }) it('set account name', async function () { - const [accountName] = await driver.findElements(By.css('.new-account-create-form input')) + const [accountName] = await driver.findElements( + By.css('.new-account-create-form input'), + ) await accountName.sendKeys('2nd account') await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Create')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Create')]`), + ) await driver.delay(regularDelayMs) }) it('should show the correct account name', async function () { - const accountName = await driver.findElement(By.css('.selected-account__name')) + const accountName = await driver.findElement( + By.css('.selected-account__name'), + ) assert.equal(await accountName.getText(), '2nd account') await driver.delay(regularDelayMs) }) @@ -189,7 +232,9 @@ describe('Using MetaMask with an existing account', function () { await driver.clickElement(By.css('[data-testid="eth-overview-send"]')) await driver.delay(regularDelayMs) - const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]')) + const inputAddress = await driver.findElement( + By.css('input[placeholder="Search, public address (0x), or ENS"]'), + ) await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') const inputAmount = await driver.findElement(By.css('.unit-input__input')) @@ -210,20 +255,28 @@ describe('Using MetaMask with an existing account', function () { }) it('confirms the transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) }) it('finds the transaction in the transactions list', async function () { await driver.clickElement(By.css('[data-testid="home__activity-tab"]')) await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElements( + By.css('.transaction-list-item__primary-currency'), + ) assert.equal(txValues.length, 1) - assert.ok((/-1\s*ETH/u).test(await txValues[0].getText())) + assert.ok(/-1\s*ETH/u.test(await txValues[0].getText())) }) }) @@ -232,20 +285,28 @@ describe('Using MetaMask with an existing account', function () { await driver.clickElement(By.css('.account-menu__icon')) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//div[contains(text(), 'Import Account')]`)) + await driver.clickElement( + By.xpath(`//div[contains(text(), 'Import Account')]`), + ) await driver.delay(regularDelayMs) }) it('enter private key', async function () { - const privateKeyInput = await driver.findElement(By.css('#private-key-box')) + const privateKeyInput = await driver.findElement( + By.css('#private-key-box'), + ) await privateKeyInput.sendKeys(testPrivateKey2) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Import')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Import')]`), + ) await driver.delay(regularDelayMs) }) it('should show the correct account name', async function () { - const accountName = await driver.findElement(By.css('.selected-account__name')) + const accountName = await driver.findElement( + By.css('.selected-account__name'), + ) assert.equal(await accountName.getText(), 'Account 4') await driver.delay(regularDelayMs) }) @@ -255,76 +316,104 @@ describe('Using MetaMask with an existing account', function () { // confirm 4th account is account 4, as expected const accountMenuItemSelector = '.account-menu__account:nth-child(4)' - const accountName = await driver.findElement(By.css(`${accountMenuItemSelector} .account-menu__name`)) + const accountName = await driver.findElement( + By.css(`${accountMenuItemSelector} .account-menu__name`), + ) assert.equal(await accountName.getText(), 'Account 4') // confirm label is present on the same menu item - const importedLabel = await driver.findElement(By.css(`${accountMenuItemSelector} .keyring-label`)) + const importedLabel = await driver.findElement( + By.css(`${accountMenuItemSelector} .keyring-label`), + ) assert.equal(await importedLabel.getText(), 'IMPORTED') }) }) describe('Imports and removes an account', function () { it('choose Create Account from the account menu', async function () { - await driver.clickElement(By.xpath(`//div[contains(text(), 'Import Account')]`)) + await driver.clickElement( + By.xpath(`//div[contains(text(), 'Import Account')]`), + ) await driver.delay(regularDelayMs) }) it('enter private key', async function () { - const privateKeyInput = await driver.findElement(By.css('#private-key-box')) + const privateKeyInput = await driver.findElement( + By.css('#private-key-box'), + ) await privateKeyInput.sendKeys(testPrivateKey3) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Import')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Import')]`), + ) await driver.delay(regularDelayMs) }) it('should see new account in account menu', async function () { - const accountName = await driver.findElement(By.css('.selected-account__name')) + const accountName = await driver.findElement( + By.css('.selected-account__name'), + ) assert.equal(await accountName.getText(), 'Account 5') await driver.delay(regularDelayMs) await driver.clickElement(By.css('.account-menu__icon')) await driver.delay(regularDelayMs) - const accountListItems = await driver.findElements(By.css('.account-menu__account')) + const accountListItems = await driver.findElements( + By.css('.account-menu__account'), + ) assert.equal(accountListItems.length, 5) await driver.clickPoint(By.css('.account-menu__icon'), 0, 0) }) it('should open the remove account modal', async function () { - await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) + await driver.clickElement( + By.css('[data-testid="account-options-menu-button"]'), + ) - await driver.clickElement(By.css('[data-testid="account-options-menu__remove-account"]')) + await driver.clickElement( + By.css('[data-testid="account-options-menu__remove-account"]'), + ) await driver.findElement(By.css('.confirm-remove-account__account')) }) it('should remove the account', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Remove')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Remove')]`), + ) await driver.delay(regularDelayMs) - const accountName = await driver.findElement(By.css('.selected-account__name')) + const accountName = await driver.findElement( + By.css('.selected-account__name'), + ) assert.equal(await accountName.getText(), 'Account 1') await driver.delay(regularDelayMs) await driver.clickElement(By.css('.account-menu__icon')) - const accountListItems = await driver.findElements(By.css('.account-menu__account')) + const accountListItems = await driver.findElements( + By.css('.account-menu__account'), + ) assert.equal(accountListItems.length, 4) }) }) describe('Connects to a Hardware wallet', function () { it('choose Connect Hardware Wallet from the account menu', async function () { - await driver.clickElement(By.xpath(`//div[contains(text(), 'Connect Hardware Wallet')]`)) + await driver.clickElement( + By.xpath(`//div[contains(text(), 'Connect Hardware Wallet')]`), + ) await driver.delay(regularDelayMs) }) it('should open the TREZOR Connect popup', async function () { await driver.clickElement(By.css('.hw-connect__btn:nth-of-type(2)')) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Connect')]`), + ) await driver.delay(regularDelayMs) const allWindows = await driver.getAllWindowHandles() assert.equal(allWindows.length, 2) diff --git a/test/e2e/ganache.js b/test/e2e/ganache.js index 99ea7374c..279473766 100644 --- a/test/e2e/ganache.js +++ b/test/e2e/ganache.js @@ -4,13 +4,14 @@ const ganache = require('ganache-core') const defaultOptions = { blockTime: 2, network_id: 1337, - mnemonic: 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent', + mnemonic: + 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent', port: 8545, vmErrorsOnRPCResponse: false, } class Ganache { - async start (opts) { + async start(opts) { const options = { ...defaultOptions, ...opts } const { port } = options this._server = ganache.server(options) @@ -24,7 +25,7 @@ class Ganache { } } - async quit () { + async quit() { if (!this._server) { throw new Error('Server not running yet') } diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 26047550d..9e6aa2f9f 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -10,7 +10,7 @@ const largeDelayMs = regularDelayMs * 2 const dappPort = 8080 -async function withFixtures (options, testSuite) { +async function withFixtures(options, testSuite) { const { dapp, fixtures, ganacheOptions, driverOptions, title } = options const fixtureServer = new FixtureServer() const ganacheServer = new Ganache() @@ -22,7 +22,15 @@ async function withFixtures (options, testSuite) { await fixtureServer.start() await fixtureServer.loadState(path.join(__dirname, 'fixtures', fixtures)) if (dapp) { - const dappDirectory = path.resolve(__dirname, '..', '..', 'node_modules', '@metamask', 'test-dapp', 'dist') + const dappDirectory = path.resolve( + __dirname, + '..', + '..', + 'node_modules', + '@metamask', + 'test-dapp', + 'dist', + ) dappServer = createStaticServer(dappDirectory) dappServer.listen(dappPort) await new Promise((resolve, reject) => { @@ -41,7 +49,9 @@ async function withFixtures (options, testSuite) { const errors = await driver.checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` throw new Error(errorMessage) } } diff --git a/test/e2e/incremental-security.spec.js b/test/e2e/incremental-security.spec.js index 6b76bae12..ca204baaf 100644 --- a/test/e2e/incremental-security.spec.js +++ b/test/e2e/incremental-security.spec.js @@ -3,11 +3,7 @@ const webdriver = require('selenium-webdriver') const { By, until } = webdriver const enLocaleMessages = require('../../app/_locales/en/messages.json') -const { - tinyDelayMs, - regularDelayMs, - largeDelayMs, -} = require('./helpers') +const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers') const { buildWebDriver } = require('./webdriver') const Ganache = require('./ganache') @@ -24,11 +20,13 @@ describe('MetaMask', function () { await ganacheServer.start({ accounts: [ { - secretKey: '0x250F458997A364988956409A164BA4E16F0F99F916ACDD73ADCD3A1DE30CF8D1', + secretKey: + '0x250F458997A364988956409A164BA4E16F0F99F916ACDD73ADCD3A1DE30CF8D1', balance: 0, }, { - secretKey: '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', + secretKey: + '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', balance: 25000000000000000000, }, ], @@ -42,7 +40,9 @@ describe('MetaMask', function () { const errors = await driver.checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` console.error(new Error(errorMessage)) } } @@ -59,12 +59,18 @@ describe('MetaMask', function () { describe('Going through the first time flow, but skipping the seed phrase challenge', function () { it('clicks the continue button on the welcome screen', async function () { await driver.findElement(By.css('.welcome-page__header')) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`, + ), + ) await driver.delay(largeDelayMs) }) it('clicks the "Create New Wallet" option', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Create a Wallet')]`), + ) await driver.delay(largeDelayMs) }) @@ -74,8 +80,12 @@ describe('MetaMask', function () { }) it('accepts a secure password', async function () { - const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password')) - const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password')) + const passwordBox = await driver.findElement( + By.css('.first-time-flow__form #create-password'), + ) + const passwordBoxConfirm = await driver.findElement( + By.css('.first-time-flow__form #confirm-password'), + ) await passwordBox.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple') @@ -87,15 +97,25 @@ describe('MetaMask', function () { }) it('skips the seed phrase challenge', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`, + ), + ) await driver.delay(regularDelayMs) - await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) - await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) + await driver.clickElement( + By.css('[data-testid="account-options-menu-button"]'), + ) + await driver.clickElement( + By.css('[data-testid="account-options-menu__account-details"]'), + ) }) it('gets the current accounts address', async function () { - const addressInput = await driver.findElement(By.css('.readonly-input__input')) + const addressInput = await driver.findElement( + By.css('.readonly-input__input'), + ) publicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) @@ -105,7 +125,6 @@ describe('MetaMask', function () { await driver.wait(until.stalenessOf(accountModal)) await driver.delay(regularDelayMs) }) - }) describe('send to current account from dapp with different provider', function () { @@ -135,7 +154,9 @@ describe('MetaMask', function () { }) it('should have the correct amount of eth', async function () { - const balances = await driver.findElements(By.css('.currency-display-component__text')) + const balances = await driver.findElements( + By.css('.currency-display-component__text'), + ) await driver.wait(until.elementTextMatches(balances[0], /1/u), 15000) const balance = await balances[0].getText() @@ -145,7 +166,11 @@ describe('MetaMask', function () { describe('backs up the seed phrase', function () { it('should show a backup reminder', async function () { - const backupReminder = await driver.findElements(By.xpath("//div[contains(@class, 'home-notification__text') and contains(text(), 'Backup your Secret Recovery code to keep your wallet and funds secure')]")) + const backupReminder = await driver.findElements( + By.xpath( + "//div[contains(@class, 'home-notification__text') and contains(text(), 'Backup your Secret Recovery code to keep your wallet and funds secure')]", + ), + ) assert.equal(backupReminder.length, 1) }) @@ -157,21 +182,33 @@ describe('MetaMask', function () { let seedPhrase it('reveals the seed phrase', async function () { - const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') + const byRevealButton = By.css( + '.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button', + ) await driver.clickElement(byRevealButton) await driver.delay(regularDelayMs) - const revealedSeedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')) + const revealedSeedPhrase = await driver.findElement( + By.css('.reveal-seed-phrase__secret-words'), + ) seedPhrase = await revealedSeedPhrase.getText() assert.equal(seedPhrase.split(' ').length, 12) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.next.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) - async function clickWordAndWait (word) { - await driver.clickElement(By.css(`[data-testid="seed-phrase-sorted"] [data-testid="draggable-seed-${word}"]`)) + async function clickWordAndWait(word) { + await driver.clickElement( + By.css( + `[data-testid="seed-phrase-sorted"] [data-testid="draggable-seed-${word}"]`, + ), + ) await driver.delay(tinyDelayMs) } @@ -182,17 +219,23 @@ describe('MetaMask', function () { await clickWordAndWait(word) } - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) }) it('can click through the success screen', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'All Done')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'All Done')]`), + ) await driver.delay(regularDelayMs) }) it('should have the correct amount of eth', async function () { - const balances = await driver.findElements(By.css('.currency-display-component__text')) + const balances = await driver.findElements( + By.css('.currency-display-component__text'), + ) await driver.wait(until.elementTextMatches(balances[0], /1/u), 15000) const balance = await balances[0].getText() diff --git a/test/e2e/metamask-responsive-ui.spec.js b/test/e2e/metamask-responsive-ui.spec.js index a4b7e92c9..a3cc26db4 100644 --- a/test/e2e/metamask-responsive-ui.spec.js +++ b/test/e2e/metamask-responsive-ui.spec.js @@ -3,11 +3,7 @@ const webdriver = require('selenium-webdriver') const { By, until } = webdriver const enLocaleMessages = require('../../app/_locales/en/messages.json') -const { - tinyDelayMs, - regularDelayMs, - largeDelayMs, -} = require('./helpers') +const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers') const { buildWebDriver } = require('./webdriver') const Ganache = require('./ganache') @@ -16,7 +12,8 @@ const ganacheServer = new Ganache() describe('MetaMask', function () { let driver - const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' + const testSeedPhrase = + 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' this.timeout(0) this.bail(true) @@ -32,7 +29,9 @@ describe('MetaMask', function () { const errors = await driver.checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` console.error(new Error(errorMessage)) } } @@ -49,12 +48,18 @@ describe('MetaMask', function () { describe('Going through the first time flow', function () { it('clicks the continue button on the welcome screen', async function () { await driver.findElement(By.css('.welcome-page__header')) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`, + ), + ) await driver.delay(largeDelayMs) }) it('clicks the "Create New Wallet" option', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Create a Wallet')]`), + ) await driver.delay(largeDelayMs) }) @@ -64,8 +69,12 @@ describe('MetaMask', function () { }) it('accepts a secure password', async function () { - const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password')) - const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password')) + const passwordBox = await driver.findElement( + By.css('.first-time-flow__form #create-password'), + ) + const passwordBoxConfirm = await driver.findElement( + By.css('.first-time-flow__form #confirm-password'), + ) await passwordBox.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple') @@ -79,21 +88,33 @@ describe('MetaMask', function () { let seedPhrase it('reveals the seed phrase', async function () { - const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') + const byRevealButton = By.css( + '.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button', + ) await driver.clickElement(byRevealButton) await driver.delay(regularDelayMs) - const revealedSeedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')) + const revealedSeedPhrase = await driver.findElement( + By.css('.reveal-seed-phrase__secret-words'), + ) seedPhrase = await revealedSeedPhrase.getText() assert.equal(seedPhrase.split(' ').length, 12) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.next.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) - async function clickWordAndWait (word) { - await driver.clickElement(By.css(`[data-testid="seed-phrase-sorted"] [data-testid="draggable-seed-${word}"]`)) + async function clickWordAndWait(word) { + await driver.clickElement( + By.css( + `[data-testid="seed-phrase-sorted"] [data-testid="draggable-seed-${word}"]`, + ), + ) await driver.delay(tinyDelayMs) } @@ -104,21 +125,33 @@ describe('MetaMask', function () { await clickWordAndWait(word) } - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) }) it('clicks through the success screen', async function () { - await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) + await driver.findElement( + By.xpath(`//div[contains(text(), 'Congratulations')]`), + ) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) }) describe('Show account information', function () { it('show account details dropdown menu', async function () { - await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) - const options = await driver.findElements(By.css('.account-options-menu .menu-item')) + await driver.clickElement( + By.css('[data-testid="account-options-menu-button"]'), + ) + const options = await driver.findElements( + By.css('.account-options-menu .menu-item'), + ) assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option // click outside of menu to dismiss // account menu button chosen because the menu never covers it. @@ -132,15 +165,22 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.account-menu__icon')) await driver.delay(regularDelayMs) - const lockButton = await driver.findClickableElement(By.css('.account-menu__lock-button')) + const lockButton = await driver.findClickableElement( + By.css('.account-menu__lock-button'), + ) assert.equal(await lockButton.getText(), 'Lock') await lockButton.click() await driver.delay(regularDelayMs) }) it('imports seed phrase', async function () { - const restoreSeedLink = await driver.findClickableElement(By.css('.unlock-page__link--import')) - assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase') + const restoreSeedLink = await driver.findClickableElement( + By.css('.unlock-page__link--import'), + ) + assert.equal( + await restoreSeedLink.getText(), + 'Import using account seed phrase', + ) await restoreSeedLink.click() await driver.delay(regularDelayMs) @@ -155,7 +195,11 @@ describe('MetaMask', function () { await passwordInputs[0].sendKeys('correct horse battery staple') await passwordInputs[1].sendKeys('correct horse battery staple') - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.restore.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.restore.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) @@ -163,12 +207,16 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.network-name')) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//span[contains(text(), 'Localhost')]`)) + await driver.clickElement( + By.xpath(`//span[contains(text(), 'Localhost')]`), + ) await driver.delay(largeDelayMs * 2) }) it('balance renders', async function () { - const balance = await driver.findElement(By.css('[data-testid="eth-overview__primary-currency"]')) + const balance = await driver.findElement( + By.css('[data-testid="eth-overview__primary-currency"]'), + ) await driver.wait(until.elementTextMatches(balance, /100\s*ETH/u)) await driver.delay(regularDelayMs) }) @@ -179,7 +227,9 @@ describe('MetaMask', function () { await driver.clickElement(By.css('[data-testid="eth-overview-send"]')) await driver.delay(regularDelayMs) - const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]')) + const inputAddress = await driver.findElement( + By.css('input[placeholder="Search, public address (0x), or ENS"]'), + ) await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') const inputAmount = await driver.findElement(By.css('.unit-input__input')) @@ -209,17 +259,25 @@ describe('MetaMask', function () { }) it('confirms the transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) }) it('finds the transaction in the transactions list', async function () { await driver.clickElement(By.css('[data-testid="home__activity-tab"]')) await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElement( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000) }) }) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 4ef3b056f..b98cf9f0b 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -3,11 +3,7 @@ const webdriver = require('selenium-webdriver') const { By, Key, until } = webdriver const enLocaleMessages = require('../../app/_locales/en/messages.json') -const { - tinyDelayMs, - regularDelayMs, - largeDelayMs, -} = require('./helpers') +const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers') const { buildWebDriver } = require('./webdriver') const Ganache = require('./ganache') @@ -17,7 +13,8 @@ describe('MetaMask', function () { let driver let tokenAddress - const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' + const testSeedPhrase = + 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' this.timeout(0) this.bail(true) @@ -33,7 +30,9 @@ describe('MetaMask', function () { const errors = await driver.checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` console.error(new Error(errorMessage)) } } @@ -50,12 +49,18 @@ describe('MetaMask', function () { describe('Going through the first time flow', function () { it('clicks the continue button on the welcome screen', async function () { await driver.findElement(By.css('.welcome-page__header')) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`, + ), + ) await driver.delay(largeDelayMs) }) it('clicks the "Create New Wallet" option', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Create a Wallet')]`), + ) await driver.delay(largeDelayMs) }) @@ -65,8 +70,12 @@ describe('MetaMask', function () { }) it('accepts a secure password', async function () { - const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password')) - const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password')) + const passwordBox = await driver.findElement( + By.css('.first-time-flow__form #create-password'), + ) + const passwordBoxConfirm = await driver.findElement( + By.css('.first-time-flow__form #confirm-password'), + ) await passwordBox.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple') @@ -80,22 +89,34 @@ describe('MetaMask', function () { let seedPhrase it('reveals the seed phrase', async function () { - const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') + const byRevealButton = By.css( + '.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button', + ) await driver.findElement(byRevealButton) await driver.clickElement(byRevealButton) await driver.delay(regularDelayMs) - const revealedSeedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')) + const revealedSeedPhrase = await driver.findElement( + By.css('.reveal-seed-phrase__secret-words'), + ) seedPhrase = await revealedSeedPhrase.getText() assert.equal(seedPhrase.split(' ').length, 12) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.next.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) - async function clickWordAndWait (word) { - await driver.clickElement(By.css(`[data-testid="seed-phrase-sorted"] [data-testid="draggable-seed-${word}"]`)) + async function clickWordAndWait(word) { + await driver.clickElement( + By.css( + `[data-testid="seed-phrase-sorted"] [data-testid="draggable-seed-${word}"]`, + ), + ) await driver.delay(tinyDelayMs) } @@ -106,21 +127,33 @@ describe('MetaMask', function () { await clickWordAndWait(word) } - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) }) it('clicks through the success screen', async function () { - await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) + await driver.findElement( + By.xpath(`//div[contains(text(), 'Congratulations')]`), + ) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) }) describe('Show account information', function () { it('shows the QR code for the account', async function () { - await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) - await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) + await driver.clickElement( + By.css('[data-testid="account-options-menu-button"]'), + ) + await driver.clickElement( + By.css('[data-testid="account-options-menu__account-details"]'), + ) await driver.findVisibleElement(By.css('.qr-code__wrapper')) await driver.delay(regularDelayMs) @@ -137,7 +170,9 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.account-menu__icon')) await driver.delay(regularDelayMs) - const lockButton = await driver.findClickableElement(By.css('.account-menu__lock-button')) + const lockButton = await driver.findClickableElement( + By.css('.account-menu__lock-button'), + ) assert.equal(await lockButton.getText(), 'Lock') await lockButton.click() await driver.delay(regularDelayMs) @@ -156,21 +191,29 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.account-menu__icon')) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//div[contains(text(), 'Create Account')]`)) + await driver.clickElement( + By.xpath(`//div[contains(text(), 'Create Account')]`), + ) await driver.delay(regularDelayMs) }) it('set account name', async function () { - const accountName = await driver.findElement(By.css('.new-account-create-form input')) + const accountName = await driver.findElement( + By.css('.new-account-create-form input'), + ) await accountName.sendKeys('2nd account') await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Create')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Create')]`), + ) await driver.delay(largeDelayMs) }) it('should display correct account name', async function () { - const accountName = await driver.findElement(By.css('.selected-account__name')) + const accountName = await driver.findElement( + By.css('.selected-account__name'), + ) assert.equal(await accountName.getText(), '2nd account') await driver.delay(regularDelayMs) }) @@ -181,15 +224,22 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.account-menu__icon')) await driver.delay(regularDelayMs) - const lockButton = await driver.findClickableElement(By.css('.account-menu__lock-button')) + const lockButton = await driver.findClickableElement( + By.css('.account-menu__lock-button'), + ) assert.equal(await lockButton.getText(), 'Lock') await lockButton.click() await driver.delay(regularDelayMs) }) it('imports seed phrase', async function () { - const restoreSeedLink = await driver.findClickableElement(By.css('.unlock-page__link--import')) - assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase') + const restoreSeedLink = await driver.findClickableElement( + By.css('.unlock-page__link--import'), + ) + assert.equal( + await restoreSeedLink.getText(), + 'Import using account seed phrase', + ) await restoreSeedLink.click() await driver.delay(regularDelayMs) @@ -204,12 +254,18 @@ describe('MetaMask', function () { await passwordInputs[0].sendKeys('correct horse battery staple') await passwordInputs[1].sendKeys('correct horse battery staple') - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.restore.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.restore.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) it('balance renders', async function () { - const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) + const balance = await driver.findElement( + By.css('[data-testid="wallet-balance"] .list-item__heading'), + ) await driver.wait(until.elementTextMatches(balance, /100\s*ETH/u)) await driver.delay(regularDelayMs) }) @@ -220,14 +276,22 @@ describe('MetaMask', function () { await driver.clickElement(By.css('[data-testid="eth-overview-send"]')) await driver.delay(regularDelayMs) - const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]')) + const inputAddress = await driver.findElement( + By.css('input[placeholder="Search, public address (0x), or ENS"]'), + ) await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') const inputAmount = await driver.findElement(By.css('.unit-input__input')) await inputAmount.sendKeys('1000') - const errorAmount = await driver.findElement(By.css('.send-v2__error-amount')) - assert.equal(await errorAmount.getText(), 'Insufficient funds.', 'send screen should render an insufficient fund error message') + const errorAmount = await driver.findElement( + By.css('.send-v2__error-amount'), + ) + assert.equal( + await errorAmount.getText(), + 'Insufficient funds.', + 'send screen should render an insufficient fund error message', + ) await inputAmount.sendKeys(Key.BACK_SPACE) await driver.delay(50) @@ -238,7 +302,9 @@ describe('MetaMask', function () { await driver.assertElementNotPresent(By.css('.send-v2__error-amount')) - const amountMax = await driver.findClickableElement(By.css('.send-v2__amount-max')) + const amountMax = await driver.findClickableElement( + By.css('.send-v2__amount-max'), + ) await amountMax.click() assert.equal(await inputAmount.isEnabled(), false) @@ -263,18 +329,26 @@ describe('MetaMask', function () { }) it('confirms the transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(largeDelayMs * 2) }) it('finds the transaction in the transactions list', async function () { await driver.clickElement(By.css('[data-testid="home__activity-tab"]')) await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElement( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000) }) }) @@ -284,7 +358,9 @@ describe('MetaMask', function () { await driver.clickElement(By.css('[data-testid="eth-overview-send"]')) await driver.delay(regularDelayMs) - const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]')) + const inputAddress = await driver.findElement( + By.css('input[placeholder="Search, public address (0x), or ENS"]'), + ) await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') const inputAmount = await driver.findElement(By.css('.unit-input__input')) @@ -294,7 +370,9 @@ describe('MetaMask', function () { assert.equal(inputValue, '1') // Set the gas price - await driver.clickElement(By.xpath(`//button/div/div[contains(text(), "Fast")]`)) + await driver.clickElement( + By.xpath(`//button/div/div[contains(text(), "Fast")]`), + ) await driver.delay(regularDelayMs) // Continue to next screen @@ -303,17 +381,25 @@ describe('MetaMask', function () { }) it('confirms the transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(largeDelayMs) }) it('finds the transaction in the transactions list', async function () { await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 2 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElement( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000) }) }) @@ -323,7 +409,9 @@ describe('MetaMask', function () { await driver.clickElement(By.css('[data-testid="eth-overview-send"]')) await driver.delay(regularDelayMs) - const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]')) + const inputAddress = await driver.findElement( + By.css('input[placeholder="Search, public address (0x), or ENS"]'), + ) await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') const inputAmount = await driver.findElement(By.css('.unit-input__input')) @@ -347,21 +435,31 @@ describe('MetaMask', function () { }) it('confirms the transaction', async function () { - const transactionAmounts = await driver.findElements(By.css('.currency-display-component__text')) + const transactionAmounts = await driver.findElements( + By.css('.currency-display-component__text'), + ) const transactionAmount = transactionAmounts[0] assert.equal(await transactionAmount.getText(), '1') - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(largeDelayMs) }) it('finds the transaction in the transactions list', async function () { await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 3 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElement( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000) }) }) @@ -383,12 +481,22 @@ describe('MetaMask', function () { await driver.clickElement(By.xpath(`//div[contains(text(), 'Advanced')]`)) await driver.delay(regularDelayMs) - await driver.clickElement(By.css('[data-testid="advanced-setting-show-testnet-conversion"] .settings-page__content-item-col > div > div')) + await driver.clickElement( + By.css( + '[data-testid="advanced-setting-show-testnet-conversion"] .settings-page__content-item-col > div > div', + ), + ) - const advancedGasTitle = await driver.findElement(By.xpath(`//span[contains(text(), 'Advanced gas controls')]`)) + const advancedGasTitle = await driver.findElement( + By.xpath(`//span[contains(text(), 'Advanced gas controls')]`), + ) await driver.scrollToElement(advancedGasTitle) - await driver.clickElement(By.css('[data-testid="advanced-setting-advanced-gas-inline"] .settings-page__content-item-col > div > div')) + await driver.clickElement( + By.css( + '[data-testid="advanced-setting-advanced-gas-inline"] .settings-page__content-item-col > div > div', + ), + ) windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] await driver.closeAllWindowHandlesExcept([extension]) @@ -402,7 +510,9 @@ describe('MetaMask', function () { await driver.openNewPage('http://127.0.0.1:8080/') await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Connect')]`), + ) await driver.delay(regularDelayMs) @@ -410,15 +520,22 @@ describe('MetaMask', function () { windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles) - popup = windowHandles.find((handle) => handle !== extension && handle !== dapp) + dapp = await driver.switchToWindowWithTitle( + 'E2E Test Dapp', + windowHandles, + ) + popup = windowHandles.find( + (handle) => handle !== extension && handle !== dapp, + ) await driver.switchToWindow(popup) await driver.delay(regularDelayMs) await driver.clickElement(By.xpath(`//button[contains(text(), 'Next')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Connect')]`), + ) await driver.waitUntilXWindowHandles(2) await driver.switchToWindow(dapp) @@ -426,16 +543,26 @@ describe('MetaMask', function () { }) it('initiates a send from the dapp', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Send')]`), 10000) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Send')]`), + 10000, + ) await driver.delay(2000) windowHandles = await driver.getAllWindowHandles() - await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles) + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ) await driver.delay(regularDelayMs) - await driver.assertElementNotPresent(By.xpath(`//li[contains(text(), 'Data')]`)) + await driver.assertElementNotPresent( + By.xpath(`//li[contains(text(), 'Data')]`), + ) - const [gasPriceInput, gasLimitInput] = await driver.findElements(By.css('.advanced-gas-inputs__gas-edit-row__input')) + const [gasPriceInput, gasLimitInput] = await driver.findElements( + By.css('.advanced-gas-inputs__gas-edit-row__input'), + ) await gasPriceInput.clear() await driver.delay(50) @@ -450,7 +577,10 @@ describe('MetaMask', function () { await driver.delay(1000) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`), 10000) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + 10000, + ) await driver.delay(regularDelayMs) await driver.waitUntilXWindowHandles(2) @@ -460,19 +590,31 @@ describe('MetaMask', function () { it('finds the transaction in the transactions list', async function () { await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 4 }, 10000) - const txValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) + const txValue = await driver.findClickableElement( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txValue, /-3\s*ETH/u), 10000) }) it('the transaction has the expected gas price', async function () { - const txValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) + const txValue = await driver.findClickableElement( + By.css('.transaction-list-item__primary-currency'), + ) await txValue.click() - const popoverCloseButton = await driver.findClickableElement(By.css('.popover-header__button')) - const txGasPrice = await driver.findElement(By.css('[data-testid="transaction-breakdown__gas-price"]')) + const popoverCloseButton = await driver.findClickableElement( + By.css('.popover-header__button'), + ) + const txGasPrice = await driver.findElement( + By.css('[data-testid="transaction-breakdown__gas-price"]'), + ) await driver.wait(until.elementTextMatches(txGasPrice, /^10$/u), 10000) await popoverCloseButton.click() }) @@ -490,11 +632,15 @@ describe('MetaMask', function () { await driver.switchToWindow(dapp) await driver.delay(largeDelayMs) - const send3eth = await driver.findClickableElement(By.xpath(`//button[contains(text(), 'Send')]`)) + const send3eth = await driver.findClickableElement( + By.xpath(`//button[contains(text(), 'Send')]`), + ) await send3eth.click() await driver.delay(largeDelayMs) - const contractDeployment = await driver.findClickableElement(By.xpath(`//button[contains(text(), 'Deploy Contract')]`)) + const contractDeployment = await driver.findClickableElement( + By.xpath(`//button[contains(text(), 'Deploy Contract')]`), + ) await contractDeployment.click() await driver.delay(largeDelayMs) @@ -512,45 +658,93 @@ describe('MetaMask', function () { it('navigates the transactions', async function () { await driver.clickElement(By.css('[data-testid="next-page"]')) - let navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + let navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) let navigationText = await navigationElement.getText() - assert.equal(navigationText.includes('2'), true, 'changed transaction right') + assert.equal( + navigationText.includes('2'), + true, + 'changed transaction right', + ) await driver.clickElement(By.css('[data-testid="next-page"]')) - navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) navigationText = await navigationElement.getText() - assert.equal(navigationText.includes('3'), true, 'changed transaction right') + assert.equal( + navigationText.includes('3'), + true, + 'changed transaction right', + ) await driver.clickElement(By.css('[data-testid="next-page"]')) - navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) navigationText = await navigationElement.getText() - assert.equal(navigationText.includes('4'), true, 'changed transaction right') + assert.equal( + navigationText.includes('4'), + true, + 'changed transaction right', + ) await driver.clickElement(By.css('[data-testid="first-page"]')) - navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) navigationText = await navigationElement.getText() - assert.equal(navigationText.includes('1'), true, 'navigate to first transaction') + assert.equal( + navigationText.includes('1'), + true, + 'navigate to first transaction', + ) await driver.clickElement(By.css('[data-testid="last-page"]')) - navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) navigationText = await navigationElement.getText() - assert.equal(navigationText.split('4').length, 3, 'navigate to last transaction') + assert.equal( + navigationText.split('4').length, + 3, + 'navigate to last transaction', + ) await driver.clickElement(By.css('[data-testid="previous-page"]')) - navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) navigationText = await navigationElement.getText() - assert.equal(navigationText.includes('3'), true, 'changed transaction left') + assert.equal( + navigationText.includes('3'), + true, + 'changed transaction left', + ) await driver.clickElement(By.css('[data-testid="previous-page"]')) - navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) navigationText = await navigationElement.getText() - assert.equal(navigationText.includes('2'), true, 'changed transaction left') + assert.equal( + navigationText.includes('2'), + true, + 'changed transaction left', + ) }) it('adds a transaction while confirm screen is in focus', async function () { - let navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + let navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) let navigationText = await navigationElement.getText() - assert.equal(navigationText.includes('2'), true, 'second transaction in focus') + assert.equal( + navigationText.includes('2'), + true, + 'second transaction in focus', + ) const windowHandles = await driver.getAllWindowHandles() const extension = windowHandles[0] @@ -565,17 +759,27 @@ describe('MetaMask', function () { await driver.switchToWindow(extension) await driver.delay(regularDelayMs) - navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) navigationText = await navigationElement.getText() - assert.equal(navigationText.includes('2'), true, 'correct (same) transaction in focus') + assert.equal( + navigationText.includes('2'), + true, + 'correct (same) transaction in focus', + ) }) it('rejects a transaction', async function () { await driver.delay(tinyDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Reject')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Reject')]`), + ) await driver.delay(largeDelayMs * 2) - const navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + const navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) await driver.delay(tinyDelayMs) const navigationText = await navigationElement.getText() assert.equal(navigationText.includes('4'), true, 'transaction rejected') @@ -583,10 +787,14 @@ describe('MetaMask', function () { it('confirms a transaction', async function () { await driver.delay(tinyDelayMs / 2) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) - const navigationElement = await driver.findElement(By.css('.confirm-page-container-navigation')) + const navigationElement = await driver.findElement( + By.css('.confirm-page-container-navigation'), + ) await driver.delay(tinyDelayMs / 2) const navigationText = await navigationElement.getText() await driver.delay(tinyDelayMs / 2) @@ -597,11 +805,17 @@ describe('MetaMask', function () { await driver.clickElement(By.xpath(`//a[contains(text(), 'Reject 3')]`)) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Reject All')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Reject All')]`), + ) await driver.delay(largeDelayMs * 2) await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 5 }, 10000) }) @@ -625,38 +839,53 @@ describe('MetaMask', function () { await driver.switchToWindow(extension) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//h2[contains(text(), 'Contract Deployment')]`)) + await driver.clickElement( + By.xpath(`//h2[contains(text(), 'Contract Deployment')]`), + ) await driver.delay(largeDelayMs) }) it('displays the contract creation data', async function () { - await driver.clickElement(By.xpath(`//li[contains(text(), 'Data')]`)) + await driver.clickElement(By.xpath(`//button[contains(text(), 'Data')]`)) await driver.delay(regularDelayMs) await driver.findElement(By.xpath(`//div[contains(text(), '127.0.0.1')]`)) - const confirmDataDiv = await driver.findElement(By.css('.confirm-page-container-content__data-box')) + const confirmDataDiv = await driver.findElement( + By.css('.confirm-page-container-content__data-box'), + ) const confirmDataText = await confirmDataDiv.getText() assert.ok(confirmDataText.includes('Origin:')) assert.ok(confirmDataText.includes('127.0.0.1')) assert.ok(confirmDataText.includes('Bytes:')) assert.ok(confirmDataText.includes('675')) - await driver.clickElement(By.xpath(`//li[contains(text(), 'Details')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Details')]`), + ) await driver.delay(regularDelayMs) }) it('confirms a deploy contract transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(largeDelayMs) await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 6 }, 10000) const txAction = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txAction[0], /Contract\sDeployment/u), 10000) + await driver.wait( + until.elementTextMatches(txAction[0], /Contract\sDeployment/u), + 10000, + ) await driver.delay(regularDelayMs) }) @@ -665,25 +894,38 @@ describe('MetaMask', function () { await driver.delay(regularDelayMs) let contractStatus = await driver.findElement(By.css('#contractStatus')) - await driver.wait(until.elementTextMatches(contractStatus, /Deployed/u), 15000) + await driver.wait( + until.elementTextMatches(contractStatus, /Deployed/u), + 15000, + ) await driver.clickElement(By.css('#depositButton')) await driver.delay(largeDelayMs) contractStatus = await driver.findElement(By.css('#contractStatus')) - await driver.wait(until.elementTextMatches(contractStatus, /Deposit\sinitiated/u), 10000) + await driver.wait( + until.elementTextMatches(contractStatus, /Deposit\sinitiated/u), + 10000, + ) await driver.switchToWindow(extension) await driver.delay(largeDelayMs * 2) await driver.findElements(By.css('.transaction-list-item--unconfirmed')) - const txListValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txListValue, /-4\s*ETH/u), 10000) + const txListValue = await driver.findClickableElement( + By.css('.transaction-list-item__primary-currency'), + ) + await driver.wait( + until.elementTextMatches(txListValue, /-4\s*ETH/u), + 10000, + ) await txListValue.click() await driver.delay(regularDelayMs) // Set the gas limit - await driver.clickElement(By.css('.confirm-detail-row__header-text--edit')) + await driver.clickElement( + By.css('.confirm-detail-row__header-text--edit'), + ) await driver.delay(regularDelayMs) const gasModal = await driver.findElement(By.css('span .modal')) @@ -691,7 +933,9 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.page-container__tab:nth-of-type(2)')) await driver.delay(regularDelayMs) - const [gasPriceInput, gasLimitInput] = await driver.findElements(By.css('.advanced-gas-inputs__gas-edit-row__input')) + const [gasPriceInput, gasLimitInput] = await driver.findElements( + By.css('.advanced-gas-inputs__gas-edit-row__input'), + ) const gasLimitValue = await gasLimitInput.getAttribute('value') assert(Number(gasLimitValue) < 100000, 'Gas Limit too high') @@ -711,16 +955,27 @@ describe('MetaMask', function () { await driver.wait(until.stalenessOf(gasModal)) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 7 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues[0], /-4\s*ETH/u), 10000) + const txValues = await driver.findElements( + By.css('.transaction-list-item__primary-currency'), + ) + await driver.wait( + until.elementTextMatches(txValues[0], /-4\s*ETH/u), + 10000, + ) }) it('calls and confirms a contract method where ETH is received', async function () { @@ -733,18 +988,30 @@ describe('MetaMask', function () { await driver.switchToWindow(extension) await driver.delay(largeDelayMs * 2) - await driver.clickElement(By.css('.transaction-list__pending-transactions .transaction-list-item')) + await driver.clickElement( + By.css( + '.transaction-list__pending-transactions .transaction-list-item', + ), + ) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 8 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElement( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txValues, /-0\s*ETH/u), 10000) await driver.closeAllWindowHandlesExcept([extension, dapp]) @@ -752,11 +1019,16 @@ describe('MetaMask', function () { }) it('renders the correct ETH balance', async function () { - const balance = await driver.findElement(By.css('[data-testid="eth-overview__primary-currency"]')) + const balance = await driver.findElement( + By.css('[data-testid="eth-overview__primary-currency"]'), + ) await driver.delay(regularDelayMs) - await driver.wait(until.elementTextMatches(balance, /^87.*\s*ETH.*$/u), 10000) + await driver.wait( + until.elementTextMatches(balance, /^87.*\s*ETH.*$/u), + 10000, + ) const tokenAmount = await balance.getText() - assert.ok((/^87.*\s*ETH.*$/u).test(tokenAmount)) + assert.ok(/^87.*\s*ETH.*$/u.test(tokenAmount)) await driver.delay(regularDelayMs) }) }) @@ -771,33 +1043,45 @@ describe('MetaMask', function () { await driver.switchToWindow(dapp) await driver.delay(regularDelayMs * 2) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Create Token')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Create Token')]`), + ) windowHandles = await driver.waitUntilXWindowHandles(3) const popup = windowHandles[2] await driver.switchToWindow(popup) await driver.delay(regularDelayMs) - await driver.clickElement(By.css('.confirm-detail-row__header-text--edit')) + await driver.clickElement( + By.css('.confirm-detail-row__header-text--edit'), + ) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//li[contains(text(), 'Advanced')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Advanced')]`), + ) await driver.delay(tinyDelayMs) - const [gasPriceInput, gasLimitInput] = await driver.findElements(By.css('.advanced-gas-inputs__gas-edit-row__input')) + const [gasPriceInput, gasLimitInput] = await driver.findElements( + By.css('.advanced-gas-inputs__gas-edit-row__input'), + ) assert(gasPriceInput.getAttribute('value'), 20) assert(gasLimitInput.getAttribute('value'), 4700000) await driver.clickElement(By.xpath(`//button[contains(text(), 'Save')]`)) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) await driver.switchToWindow(dapp) await driver.delay(tinyDelayMs) - const tokenContractAddress = await driver.findElement(By.css('#tokenAddress')) + const tokenContractAddress = await driver.findElement( + By.css('#tokenAddress'), + ) await driver.wait(until.elementTextMatches(tokenContractAddress, /0x/u)) tokenAddress = await tokenContractAddress.getText() @@ -810,30 +1094,40 @@ describe('MetaMask', function () { it('clicks on the Add Token button', async function () { await driver.clickElement(By.css(`[data-testid="home__asset-tab"]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Add Token')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Add Token')]`), + ) await driver.delay(regularDelayMs) }) it('picks the newly created Test token', async function () { - await driver.clickElement(By.xpath("//li[contains(text(), 'Custom Token')]")) + await driver.clickElement( + By.xpath("//button[contains(text(), 'Custom Token')]"), + ) await driver.delay(regularDelayMs) - const newTokenAddress = await driver.findElement(By.css('#custom-address')) + const newTokenAddress = await driver.findElement( + By.css('#custom-address'), + ) await newTokenAddress.sendKeys(tokenAddress) await driver.delay(regularDelayMs) await driver.clickElement(By.xpath(`//button[contains(text(), 'Next')]`)) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Add Tokens')]`), + ) await driver.delay(regularDelayMs) }) it('renders the balance for the new token', async function () { - const balance = await driver.findElement(By.css('.wallet-overview .token-overview__primary-balance')) + const balance = await driver.findElement( + By.css('.wallet-overview .token-overview__primary-balance'), + ) await driver.wait(until.elementTextMatches(balance, /^10\s*TST\s*$/u)) const tokenAmount = await balance.getText() - assert.ok((/^10\s*TST\s*$/u).test(tokenAmount)) + assert.ok(/^10\s*TST\s*$/u.test(tokenAmount)) await driver.delay(regularDelayMs) }) }) @@ -844,7 +1138,9 @@ describe('MetaMask', function () { await driver.clickElement(By.css('[data-testid="eth-overview-send"]')) await driver.delay(regularDelayMs) - const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]')) + const inputAddress = await driver.findElement( + By.css('input[placeholder="Search, public address (0x), or ENS"]'), + ) await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') const inputAmount = await driver.findElement(By.css('.unit-input__input')) @@ -873,44 +1169,72 @@ describe('MetaMask', function () { }) it('displays the token transfer data', async function () { - await driver.clickElement(By.xpath(`//li[contains(text(), 'Data')]`)) + await driver.clickElement(By.xpath(`//button[contains(text(), 'Data')]`)) await driver.delay(regularDelayMs) - const functionType = await driver.findElement(By.css('.confirm-page-container-content__function-type')) + const functionType = await driver.findElement( + By.css('.confirm-page-container-content__function-type'), + ) const functionTypeText = await functionType.getText() assert.equal(functionTypeText, 'Transfer') - const tokenAmount = await driver.findElement(By.css('.confirm-page-container-summary__title-text')) + const tokenAmount = await driver.findElement( + By.css('.confirm-page-container-summary__title-text'), + ) const tokenAmountText = await tokenAmount.getText() assert.equal(tokenAmountText, '1 TST') - const confirmDataDiv = await driver.findElement(By.css('.confirm-page-container-content__data-box')) + const confirmDataDiv = await driver.findElement( + By.css('.confirm-page-container-content__data-box'), + ) const confirmDataText = await confirmDataDiv.getText() await driver.delay(regularDelayMs) - assert(confirmDataText.match(/0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c97/u)) + assert( + confirmDataText.match( + /0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c97/u, + ), + ) - await driver.clickElement(By.xpath(`//li[contains(text(), 'Details')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Details')]`), + ) await driver.delay(regularDelayMs) }) it('submits the transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) }) it('finds the transaction in the transactions list', async function () { await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElements( + By.css('.transaction-list-item__primary-currency'), + ) assert.equal(txValues.length, 1) - await driver.wait(until.elementTextMatches(txValues[0], /-1\s*TST/u), 10000) + await driver.wait( + until.elementTextMatches(txValues[0], /-1\s*TST/u), + 10000, + ) - const txStatuses = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Send\sTST/u), 10000) + const txStatuses = await driver.findElements( + By.css('.list-item__heading'), + ) + await driver.wait( + until.elementTextMatches(txStatuses[0], /Send\sTST/u), + 10000, + ) }) }) @@ -919,29 +1243,45 @@ describe('MetaMask', function () { it('sends an already created token', async function () { const windowHandles = await driver.getAllWindowHandles() const extension = windowHandles[0] - const dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles) + const dapp = await driver.switchToWindowWithTitle( + 'E2E Test Dapp', + windowHandles, + ) await driver.delay(regularDelayMs) await driver.switchToWindow(dapp) await driver.delay(tinyDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Transfer Tokens')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Transfer Tokens')]`), + ) await driver.switchToWindow(extension) await driver.delay(largeDelayMs) - await driver.findElements(By.css('.transaction-list__pending-transactions')) - const txListValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txListValue, /-1.5\s*TST/u), 10000) + await driver.findElements( + By.css('.transaction-list__pending-transactions'), + ) + const txListValue = await driver.findClickableElement( + By.css('.transaction-list-item__primary-currency'), + ) + await driver.wait( + until.elementTextMatches(txListValue, /-1.5\s*TST/u), + 10000, + ) await txListValue.click() await driver.delay(regularDelayMs) - const transactionAmounts = await driver.findElements(By.css('.currency-display-component__text')) + const transactionAmounts = await driver.findElements( + By.css('.currency-display-component__text'), + ) const transactionAmount = transactionAmounts[0] assert(await transactionAmount.getText(), '1.5 TST') // Set the gas limit - await driver.clickElement(By.css('.confirm-detail-row__header-text--edit')) + await driver.clickElement( + By.css('.confirm-detail-row__header-text--edit'), + ) await driver.delay(regularDelayMs) gasModal = await driver.findElement(By.css('span .modal')) @@ -951,7 +1291,9 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.page-container__tab:nth-of-type(2)')) await driver.delay(regularDelayMs) - const [gasPriceInput, gasLimitInput] = await driver.findElements(By.css('.advanced-gas-inputs__gas-edit-row__input')) + const [gasPriceInput, gasLimitInput] = await driver.findElements( + By.css('.advanced-gas-inputs__gas-edit-row__input'), + ) await gasPriceInput.clear() await driver.delay(50) @@ -967,33 +1309,55 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.page-container__footer-button')) await driver.wait(until.stalenessOf(gasModal)) - const gasFeeInputs = await driver.findElements(By.css('.confirm-detail-row__primary')) + const gasFeeInputs = await driver.findElements( + By.css('.confirm-detail-row__primary'), + ) const renderedGasFee = await gasFeeInputs[0].getText() assert.equal(renderedGasFee, '0.0006') }) it('submits the transaction', async function () { - const tokenAmount = await driver.findElement(By.css('.confirm-page-container-summary__title-text')) + const tokenAmount = await driver.findElement( + By.css('.confirm-page-container-summary__title-text'), + ) const tokenAmountText = await tokenAmount.getText() assert.equal(tokenAmountText, '1.5 TST') - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) }) it('finds the transaction in the transactions list', async function () { await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 2 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElements( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txValues[0], /-1.5\s*TST/u)) - const txStatuses = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Send\sTST/u), 10000) + const txStatuses = await driver.findElements( + By.css('.list-item__heading'), + ) + await driver.wait( + until.elementTextMatches(txStatuses[0], /Send\sTST/u), + 10000, + ) - const tokenBalanceAmount = await driver.findElements(By.css('.token-overview__primary-balance')) - await driver.wait(until.elementTextMatches(tokenBalanceAmount[0], /7.5\s*TST/u), 10000) + const tokenBalanceAmount = await driver.findElements( + By.css('.token-overview__primary-balance'), + ) + await driver.wait( + until.elementTextMatches(tokenBalanceAmount[0], /7.5\s*TST/u), + 10000, + ) }) }) @@ -1002,44 +1366,71 @@ describe('MetaMask', function () { it('approves an already created token', async function () { const windowHandles = await driver.getAllWindowHandles() const extension = windowHandles[0] - const dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles) + const dapp = await driver.switchToWindowWithTitle( + 'E2E Test Dapp', + windowHandles, + ) await driver.closeAllWindowHandlesExcept([extension, dapp]) await driver.delay(regularDelayMs) await driver.switchToWindow(dapp) await driver.delay(tinyDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Approve Tokens')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Approve Tokens')]`), + ) await driver.switchToWindow(extension) await driver.delay(regularDelayMs) await driver.wait(async () => { - const pendingTxes = await driver.findElements(By.css('.transaction-list__pending-transactions .transaction-list-item')) + const pendingTxes = await driver.findElements( + By.css( + '.transaction-list__pending-transactions .transaction-list-item', + ), + ) return pendingTxes.length === 1 }, 10000) - const [txtListHeading] = await driver.findElements(By.css('.transaction-list-item .list-item__heading')) - await driver.wait(until.elementTextMatches(txtListHeading, /Approve TST spend limit/u)) + const [txtListHeading] = await driver.findElements( + By.css('.transaction-list-item .list-item__heading'), + ) + await driver.wait( + until.elementTextMatches(txtListHeading, /Approve TST spend limit/u), + ) await driver.clickElement(By.css('.transaction-list-item')) await driver.delay(regularDelayMs) }) it('displays the token approval data', async function () { - await driver.clickElement(By.css('.confirm-approve-content__view-full-tx-button')) + await driver.clickElement( + By.css('.confirm-approve-content__view-full-tx-button'), + ) await driver.delay(regularDelayMs) - const functionType = await driver.findElement(By.css('.confirm-approve-content__data .confirm-approve-content__small-text')) + const functionType = await driver.findElement( + By.css( + '.confirm-approve-content__data .confirm-approve-content__small-text', + ), + ) const functionTypeText = await functionType.getText() assert.equal(functionTypeText, 'Function: Approve') - const confirmDataDiv = await driver.findElement(By.css('.confirm-approve-content__data__data-block')) + const confirmDataDiv = await driver.findElement( + By.css('.confirm-approve-content__data__data-block'), + ) const confirmDataText = await confirmDataDiv.getText() - assert(confirmDataText.match(/0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef4/u)) + assert( + confirmDataText.match( + /0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef4/u, + ), + ) }) it('opens the gas edit modal', async function () { - await driver.clickElement(By.css('.confirm-approve-content__small-blue-text.cursor-pointer')) + await driver.clickElement( + By.css('.confirm-approve-content__small-blue-text.cursor-pointer'), + ) await driver.delay(regularDelayMs) gasModal = await driver.findElement(By.css('span .modal')) @@ -1049,7 +1440,9 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.page-container__tab:nth-of-type(2)')) await driver.delay(regularDelayMs) - const [gasPriceInput, gasLimitInput] = await driver.findElements(By.css('.advanced-gas-inputs__gas-edit-row__input')) + const [gasPriceInput, gasLimitInput] = await driver.findElements( + By.css('.advanced-gas-inputs__gas-edit-row__input'), + ) await gasPriceInput.clear() await driver.delay(50) @@ -1065,18 +1458,26 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.page-container__footer-button')) await driver.wait(until.stalenessOf(gasModal)) - const gasFeeInEth = await driver.findElement(By.css('.confirm-approve-content__transaction-details-content__secondary-fee')) + const gasFeeInEth = await driver.findElement( + By.css( + '.confirm-approve-content__transaction-details-content__secondary-fee', + ), + ) assert.equal(await gasFeeInEth.getText(), '0.0006 ETH') }) it('edits the permission', async function () { - const editButtons = await driver.findClickableElements(By.css('.confirm-approve-content__small-blue-text.cursor-pointer')) + const editButtons = await driver.findClickableElements( + By.css('.confirm-approve-content__small-blue-text.cursor-pointer'), + ) await editButtons[1].click() await driver.delay(regularDelayMs) const permissionModal = await driver.findElement(By.css('span .modal')) - const radioButtons = await driver.findClickableElements(By.css('.edit-approval-permission__edit-section__radio-button')) + const radioButtons = await driver.findClickableElements( + By.css('.edit-approval-permission__edit-section__radio-button'), + ) await radioButtons[1].click() const customInput = await driver.findElement(By.css('input')) @@ -1089,24 +1490,36 @@ describe('MetaMask', function () { await driver.wait(until.stalenessOf(permissionModal)) - const permissionInfo = await driver.findElements(By.css('.confirm-approve-content__medium-text')) + const permissionInfo = await driver.findElements( + By.css('.confirm-approve-content__medium-text'), + ) const amountDiv = permissionInfo[0] assert.equal(await amountDiv.getText(), '5 TST') }) it('submits the transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) }) it('finds the transaction in the transactions list', async function () { await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 3 }, 10000) - const txStatuses = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Approve TST spend limit/u)) + const txStatuses = await driver.findElements( + By.css('.list-item__heading'), + ) + await driver.wait( + until.elementTextMatches(txStatuses[0], /Approve TST spend limit/u), + ) }) }) @@ -1114,23 +1527,34 @@ describe('MetaMask', function () { it('transfers an already created token, without specifying gas', async function () { const windowHandles = await driver.getAllWindowHandles() const extension = windowHandles[0] - const dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles) + const dapp = await driver.switchToWindowWithTitle( + 'E2E Test Dapp', + windowHandles, + ) await driver.closeAllWindowHandlesExcept([extension, dapp]) await driver.delay(regularDelayMs) await driver.switchToWindow(dapp) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Transfer Tokens Without Gas')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Transfer Tokens Without Gas')]`), + ) await driver.switchToWindow(extension) await driver.delay(regularDelayMs) await driver.wait(async () => { - const pendingTxes = await driver.findElements(By.css('.transaction-list__pending-transactions .transaction-list-item')) + const pendingTxes = await driver.findElements( + By.css( + '.transaction-list__pending-transactions .transaction-list-item', + ), + ) return pendingTxes.length === 1 }, 10000) - const [txListValue] = await driver.findElements(By.css('.transaction-list-item__primary-currency')) + const [txListValue] = await driver.findElements( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txListValue, /-1.5\s*TST/u)) await driver.clickElement(By.css('.transaction-list-item')) await driver.delay(regularDelayMs) @@ -1138,19 +1562,29 @@ describe('MetaMask', function () { it('submits the transaction', async function () { await driver.delay(largeDelayMs * 2) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(largeDelayMs * 2) }) it('finds the transaction in the transactions list', async function () { await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 4 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElements( + By.css('.transaction-list-item__primary-currency'), + ) await driver.wait(until.elementTextMatches(txValues[0], /-1.5\s*TST/u)) - const txStatuses = await driver.findElements(By.css('.list-item__heading')) + const txStatuses = await driver.findElements( + By.css('.list-item__heading'), + ) await driver.wait(until.elementTextMatches(txStatuses[0], /Send TST/u)) }) }) @@ -1159,52 +1593,79 @@ describe('MetaMask', function () { it('approves an already created token', async function () { const windowHandles = await driver.getAllWindowHandles() const extension = windowHandles[0] - const dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles) + const dapp = await driver.switchToWindowWithTitle( + 'E2E Test Dapp', + windowHandles, + ) await driver.closeAllWindowHandlesExcept([extension, dapp]) await driver.delay(regularDelayMs) await driver.switchToWindow(dapp) await driver.delay(tinyDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Approve Tokens Without Gas')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Approve Tokens Without Gas')]`), + ) await driver.switchToWindow(extension) await driver.delay(regularDelayMs) await driver.wait(async () => { - const pendingTxes = await driver.findElements(By.css('.transaction-list__pending-transactions .transaction-list-item')) + const pendingTxes = await driver.findElements( + By.css( + '.transaction-list__pending-transactions .transaction-list-item', + ), + ) return pendingTxes.length === 1 }, 10000) - const [txtListHeading] = await driver.findElements(By.css('.transaction-list-item .list-item__heading')) - await driver.wait(until.elementTextMatches(txtListHeading, /Approve TST spend limit/u)) + const [txtListHeading] = await driver.findElements( + By.css('.transaction-list-item .list-item__heading'), + ) + await driver.wait( + until.elementTextMatches(txtListHeading, /Approve TST spend limit/u), + ) await driver.clickElement(By.css('.transaction-list-item')) await driver.delay(regularDelayMs) }) it('shows the correct recipient', async function () { - await driver.clickElement(By.css('.confirm-approve-content__view-full-tx-button')) + await driver.clickElement( + By.css('.confirm-approve-content__view-full-tx-button'), + ) await driver.delay(regularDelayMs) - const permissionInfo = await driver.findElements(By.css('.confirm-approve-content__medium-text')) + const permissionInfo = await driver.findElements( + By.css('.confirm-approve-content__medium-text'), + ) const recipientDiv = permissionInfo[1] assert.equal(await recipientDiv.getText(), '0x2f318C33...C970') }) it('submits the transaction', async function () { await driver.delay(1000) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) }) it('finds the transaction in the transactions list', async function () { await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 5 }, 10000) - const txStatuses = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Approve TST spend limit/u)) + const txStatuses = await driver.findElements( + By.css('.list-item__heading'), + ) + await driver.wait( + until.elementTextMatches(txStatuses[0], /Approve TST spend limit/u), + ) }) }) @@ -1216,7 +1677,9 @@ describe('MetaMask', function () { const confirmHideModal = await driver.findElement(By.css('span .modal')) - await driver.clickElement(By.css('[data-testid="hide-token-confirmation__hide"]')) + await driver.clickElement( + By.css('[data-testid="hide-token-confirmation__hide"]'), + ) await driver.wait(until.stalenessOf(confirmHideModal)) }) @@ -1224,7 +1687,9 @@ describe('MetaMask', function () { describe('Add existing token using search', function () { it('clicks on the Add Token button', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Add Token')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Add Token')]`), + ) await driver.delay(regularDelayMs) }) @@ -1239,12 +1704,16 @@ describe('MetaMask', function () { await driver.clickElement(By.xpath(`//button[contains(text(), 'Next')]`)) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Add Tokens')]`), + ) await driver.delay(largeDelayMs) }) it('renders the balance for the chosen token', async function () { - const balance = await driver.findElement(By.css('.token-overview__primary-balance')) + const balance = await driver.findElement( + By.css('.token-overview__primary-balance'), + ) await driver.wait(until.elementTextMatches(balance, /0\s*BAT/u)) await driver.delay(regularDelayMs) }) @@ -1258,12 +1727,16 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.network-name')) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//span[contains(text(), 'Custom RPC')]`)) + await driver.clickElement( + By.xpath(`//span[contains(text(), 'Custom RPC')]`), + ) await driver.delay(regularDelayMs) await driver.findElement(By.css('.settings-page__sub-header-text')) - const customRpcInputs = await driver.findElements(By.css('input[type="text"]')) + const customRpcInputs = await driver.findElements( + By.css('input[type="text"]'), + ) const rpcUrlInput = customRpcInputs[1] const chainIdInput = customRpcInputs[2] @@ -1274,9 +1747,7 @@ describe('MetaMask', function () { await chainIdInput.sendKeys(chainId) await driver.clickElement(By.css('.network-form__footer .btn-secondary')) - await driver.findElement( - By.xpath(`//div[contains(text(), '${rpcUrl}')]`), - ) + await driver.findElement(By.xpath(`//div[contains(text(), '${rpcUrl}')]`)) }) it(`creates second custom RPC entry`, async function () { @@ -1286,12 +1757,16 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.network-name')) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//span[contains(text(), 'Custom RPC')]`)) + await driver.clickElement( + By.xpath(`//span[contains(text(), 'Custom RPC')]`), + ) await driver.delay(regularDelayMs) await driver.findElement(By.css('.settings-page__sub-header-text')) - const customRpcInputs = await driver.findElements(By.css('input[type="text"]')) + const customRpcInputs = await driver.findElements( + By.css('input[type="text"]'), + ) const rpcUrlInput = customRpcInputs[1] const chainIdInput = customRpcInputs[2] @@ -1302,16 +1777,16 @@ describe('MetaMask', function () { await chainIdInput.sendKeys(chainId) await driver.clickElement(By.css('.network-form__footer .btn-secondary')) - await driver.findElement( - By.xpath(`//div[contains(text(), '${rpcUrl}')]`), - ) + await driver.findElement(By.xpath(`//div[contains(text(), '${rpcUrl}')]`)) }) it('selects another provider', async function () { await driver.clickElement(By.css('.network-name')) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//span[contains(text(), 'Ethereum Mainnet')]`)) + await driver.clickElement( + By.xpath(`//span[contains(text(), 'Ethereum Mainnet')]`), + ) await driver.delay(largeDelayMs * 2) }) @@ -1320,13 +1795,17 @@ describe('MetaMask', function () { await driver.delay(regularDelayMs) // only recent 3 are found and in correct order (most recent at the top) - const customRpcs = await driver.findElements(By.xpath(`//span[contains(text(), 'http://127.0.0.1:8545/')]`)) + const customRpcs = await driver.findElements( + By.xpath(`//span[contains(text(), 'http://127.0.0.1:8545/')]`), + ) assert.equal(customRpcs.length, 2) }) it('deletes a custom RPC', async function () { - const networkListItems = await driver.findClickableElements(By.css('.networks-tab__networks-list-name')) + const networkListItems = await driver.findClickableElements( + By.css('.networks-tab__networks-list-name'), + ) const lastNetworkListItem = networkListItems[networkListItems.length - 1] await lastNetworkListItem.click() await driver.delay(100) @@ -1334,14 +1813,20 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.btn-danger')) await driver.delay(regularDelayMs) - const confirmDeleteNetworkModal = await driver.findElement(By.css('span .modal')) + const confirmDeleteNetworkModal = await driver.findElement( + By.css('span .modal'), + ) - const byConfirmDeleteNetworkButton = By.css('.button.btn-danger.modal-container__footer-button') + const byConfirmDeleteNetworkButton = By.css( + '.button.btn-danger.modal-container__footer-button', + ) await driver.clickElement(byConfirmDeleteNetworkButton) await driver.wait(until.stalenessOf(confirmDeleteNetworkModal)) - const newNetworkListItems = await driver.findElements(By.css('.networks-tab__networks-list-name')) + const newNetworkListItems = await driver.findElements( + By.css('.networks-tab__networks-list-name'), + ) assert.equal(networkListItems.length - 1, newNetworkListItems.length) }) diff --git a/test/e2e/mock-3box/server.js b/test/e2e/mock-3box/server.js index 66c4be6bb..7094fddd4 100644 --- a/test/e2e/mock-3box/server.js +++ b/test/e2e/mock-3box/server.js @@ -19,7 +19,9 @@ const requestHandler = (request, response) => { response.end('ok') }) } else if (request.method === 'GET') { - const key = (new URL(request.url, 'https://example.org/')).searchParams.get('key') + const key = new URL(request.url, 'https://example.org/').searchParams.get( + 'key', + ) response.setHeader('Access-Control-Allow-Headers', '*') response.end(JSON.stringify(database[key] || '')) } else { @@ -32,6 +34,5 @@ const server = http.createServer(requestHandler) server.listen(port, (err) => { if (err) { console.log('mock 3box server error: ', err) - } }) diff --git a/test/e2e/permissions.spec.js b/test/e2e/permissions.spec.js index 87c439c0c..133bade16 100644 --- a/test/e2e/permissions.spec.js +++ b/test/e2e/permissions.spec.js @@ -3,10 +3,7 @@ const webdriver = require('selenium-webdriver') const { By, until } = webdriver const enLocaleMessages = require('../../app/_locales/en/messages.json') -const { - regularDelayMs, - largeDelayMs, -} = require('./helpers') +const { regularDelayMs, largeDelayMs } = require('./helpers') const { buildWebDriver } = require('./webdriver') const Ganache = require('./ganache') @@ -23,7 +20,8 @@ describe('MetaMask', function () { await ganacheServer.start({ accounts: [ { - secretKey: '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', + secretKey: + '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', balance: 25000000000000000000, }, ], @@ -37,7 +35,9 @@ describe('MetaMask', function () { const errors = await driver.checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` console.error(new Error(errorMessage)) } } @@ -54,12 +54,18 @@ describe('MetaMask', function () { describe('Going through the first time flow, but skipping the seed phrase challenge', function () { it('clicks the continue button on the welcome screen', async function () { await driver.findElement(By.css('.welcome-page__header')) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`, + ), + ) await driver.delay(largeDelayMs) }) it('clicks the "Create New Wallet" option', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Create a Wallet')]`), + ) await driver.delay(largeDelayMs) }) @@ -69,8 +75,12 @@ describe('MetaMask', function () { }) it('accepts a secure password', async function () { - const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password')) - const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password')) + const passwordBox = await driver.findElement( + By.css('.first-time-flow__form #create-password'), + ) + const passwordBoxConfirm = await driver.findElement( + By.css('.first-time-flow__form #confirm-password'), + ) await passwordBox.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple') @@ -82,15 +92,25 @@ describe('MetaMask', function () { }) it('skips the seed phrase challenge', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`, + ), + ) await driver.delay(regularDelayMs) - await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) - await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) + await driver.clickElement( + By.css('[data-testid="account-options-menu-button"]'), + ) + await driver.clickElement( + By.css('[data-testid="account-options-menu__account-details"]'), + ) }) it('gets the current accounts address', async function () { - const addressInput = await driver.findElement(By.css('.readonly-input__input')) + const addressInput = await driver.findElement( + By.css('.readonly-input__input'), + ) publicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) @@ -110,21 +130,30 @@ describe('MetaMask', function () { await driver.openNewPage('http://127.0.0.1:8080/') await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Connect')]`), + ) await driver.waitUntilXWindowHandles(3) const windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles) - popup = windowHandles.find((handle) => handle !== extension && handle !== dapp) + dapp = await driver.switchToWindowWithTitle( + 'E2E Test Dapp', + windowHandles, + ) + popup = windowHandles.find( + (handle) => handle !== extension && handle !== dapp, + ) await driver.switchToWindow(popup) await driver.delay(regularDelayMs) await driver.clickElement(By.xpath(`//button[contains(text(), 'Next')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Connect')]`), + ) await driver.waitUntilXWindowHandles(2) await driver.switchToWindow(extension) @@ -132,12 +161,20 @@ describe('MetaMask', function () { }) it('shows connected sites', async function () { - await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) - await driver.clickElement(By.css('[data-testid="account-options-menu__connected-sites"]')) + await driver.clickElement( + By.css('[data-testid="account-options-menu-button"]'), + ) + await driver.clickElement( + By.css('[data-testid="account-options-menu__connected-sites"]'), + ) - await driver.findElement(By.xpath(`//h2[contains(text(), 'Connected sites')]`)) + await driver.findElement( + By.xpath(`//h2[contains(text(), 'Connected sites')]`), + ) - const domains = await driver.findClickableElements(By.css('.connected-sites-list__domain-name')) + const domains = await driver.findClickableElements( + By.css('.connected-sites-list__domain-name'), + ) assert.equal(domains.length, 1) }) @@ -145,10 +182,17 @@ describe('MetaMask', function () { await driver.switchToWindow(dapp) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'eth_accounts')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'eth_accounts')]`), + ) - const getAccountsResult = await driver.findElement(By.css('#getAccountsResult')) - assert.equal((await getAccountsResult.getText()).toLowerCase(), publicAddress.toLowerCase()) + const getAccountsResult = await driver.findElement( + By.css('#getAccountsResult'), + ) + assert.equal( + (await getAccountsResult.getText()).toLowerCase(), + publicAddress.toLowerCase(), + ) }) }) }) diff --git a/test/e2e/send-edit.spec.js b/test/e2e/send-edit.spec.js index 1dba2c396..b11dde556 100644 --- a/test/e2e/send-edit.spec.js +++ b/test/e2e/send-edit.spec.js @@ -3,11 +3,7 @@ const webdriver = require('selenium-webdriver') const { By, until } = webdriver const enLocaleMessages = require('../../app/_locales/en/messages.json') -const { - tinyDelayMs, - regularDelayMs, - largeDelayMs, -} = require('./helpers') +const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers') const { buildWebDriver } = require('./webdriver') const Ganache = require('./ganache') @@ -16,7 +12,8 @@ const ganacheServer = new Ganache() describe('Using MetaMask with an existing account', function () { let driver - const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' + const testSeedPhrase = + 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' this.timeout(0) this.bail(true) @@ -25,7 +22,8 @@ describe('Using MetaMask with an existing account', function () { await ganacheServer.start({ accounts: [ { - secretKey: '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', + secretKey: + '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', balance: 25000000000000000000, }, ], @@ -39,7 +37,9 @@ describe('Using MetaMask with an existing account', function () { const errors = await driver.checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` console.error(new Error(errorMessage)) } } @@ -56,12 +56,18 @@ describe('Using MetaMask with an existing account', function () { describe('First time flow starting from an existing seed phrase', function () { it('clicks the continue button on the welcome screen', async function () { await driver.findElement(By.css('.welcome-page__header')) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`, + ), + ) await driver.delay(largeDelayMs) }) it('clicks the "Import Wallet" option', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Import wallet')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Import wallet')]`), + ) await driver.delay(largeDelayMs) }) @@ -71,24 +77,36 @@ describe('Using MetaMask with an existing account', function () { }) it('imports a seed phrase', async function () { - const [seedTextArea] = await driver.findElements(By.css('input[placeholder="Paste seed phrase from clipboard"]')) + const [seedTextArea] = await driver.findElements( + By.css('input[placeholder="Paste seed phrase from clipboard"]'), + ) await seedTextArea.sendKeys(testSeedPhrase) await driver.delay(regularDelayMs) const [password] = await driver.findElements(By.id('password')) await password.sendKeys('correct horse battery staple') - const [confirmPassword] = await driver.findElements(By.id('confirm-password')) + const [confirmPassword] = await driver.findElements( + By.id('confirm-password'), + ) confirmPassword.sendKeys('correct horse battery staple') await driver.clickElement(By.css('.first-time-flow__terms')) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Import')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Import')]`), + ) await driver.delay(regularDelayMs) }) it('clicks through the success screen', async function () { - await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) + await driver.findElement( + By.xpath(`//div[contains(text(), 'Congratulations')]`), + ) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) }) @@ -98,7 +116,9 @@ describe('Using MetaMask with an existing account', function () { await driver.clickElement(By.css('[data-testid="eth-overview-send"]')) await driver.delay(regularDelayMs) - const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]')) + const inputAddress = await driver.findElement( + By.css('input[placeholder="Search, public address (0x), or ENS"]'), + ) await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') const inputAmount = await driver.findElement(By.css('.unit-input__input')) @@ -110,7 +130,9 @@ describe('Using MetaMask with an existing account', function () { const gasModal = await driver.findElement(By.css('span .modal')) - const [gasPriceInput, gasLimitInput] = await driver.findElements(By.css('.advanced-gas-inputs__gas-edit-row__input')) + const [gasPriceInput, gasLimitInput] = await driver.findElements( + By.css('.advanced-gas-inputs__gas-edit-row__input'), + ) await gasPriceInput.clear() await driver.delay(50) @@ -135,7 +157,9 @@ describe('Using MetaMask with an existing account', function () { }) it('has correct value and fee on the confirm screen the transaction', async function () { - const transactionAmounts = await driver.findElements(By.css('.currency-display-component__text')) + const transactionAmounts = await driver.findElements( + By.css('.currency-display-component__text'), + ) const transactionAmount = transactionAmounts[0] assert.equal(await transactionAmount.getText(), '1') @@ -144,7 +168,9 @@ describe('Using MetaMask with an existing account', function () { }) it('edits the transaction', async function () { - await driver.clickElement(By.css('.confirm-page-container-header__back-button')) + await driver.clickElement( + By.css('.confirm-page-container-header__back-button'), + ) await driver.delay(regularDelayMs) @@ -159,7 +185,9 @@ describe('Using MetaMask with an existing account', function () { const gasModal = await driver.findElement(By.css('span .modal')) - const [gasPriceInput, gasLimitInput] = await driver.findElements(By.css('.advanced-gas-inputs__gas-edit-row__input')) + const [gasPriceInput, gasLimitInput] = await driver.findElements( + By.css('.advanced-gas-inputs__gas-edit-row__input'), + ) await gasPriceInput.clear() await driver.delay(50) @@ -183,7 +211,9 @@ describe('Using MetaMask with an existing account', function () { }) it('has correct updated value on the confirm screen the transaction', async function () { - const transactionAmounts = await driver.findElements(By.css('.currency-display-component__text')) + const transactionAmounts = await driver.findElements( + By.css('.currency-display-component__text'), + ) const transactionAmount = transactionAmounts[0] assert.equal(await transactionAmount.getText(), '2.2') @@ -192,20 +222,28 @@ describe('Using MetaMask with an existing account', function () { }) it('confirms the transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Confirm')]`), + ) await driver.delay(regularDelayMs) }) it('finds the transaction in the transactions list', async function () { await driver.clickElement(By.css('[data-testid="home__activity-tab"]')) await driver.wait(async () => { - const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) + const confirmedTxes = await driver.findElements( + By.css( + '.transaction-list__completed-transactions .transaction-list-item', + ), + ) return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) + const txValues = await driver.findElements( + By.css('.transaction-list-item__primary-currency'), + ) assert.equal(txValues.length, 1) - assert.ok((/-2.2\s*ETH/u).test(await txValues[0].getText())) + assert.ok(/-2.2\s*ETH/u.test(await txValues[0].getText())) }) }) }) diff --git a/test/e2e/signature-request.spec.js b/test/e2e/signature-request.spec.js index 9e83cc25e..5857f175b 100644 --- a/test/e2e/signature-request.spec.js +++ b/test/e2e/signature-request.spec.js @@ -3,10 +3,7 @@ const path = require('path') const webdriver = require('selenium-webdriver') const { By, Key, until } = webdriver -const { - regularDelayMs, - largeDelayMs, -} = require('./helpers') +const { regularDelayMs, largeDelayMs } = require('./helpers') const { buildWebDriver } = require('./webdriver') const Ganache = require('./ganache') const FixtureServer = require('./fixture-server') @@ -25,7 +22,9 @@ describe('MetaMask', function () { before(async function () { await ganacheServer.start() await fixtureServer.start() - await fixtureServer.loadState(path.join(__dirname, 'fixtures', 'imported-account')) + await fixtureServer.loadState( + path.join(__dirname, 'fixtures', 'imported-account'), + ) publicAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1' const result = await buildWebDriver() driver = result.driver @@ -36,7 +35,9 @@ describe('MetaMask', function () { const errors = await driver.checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` console.error(new Error(errorMessage)) } } @@ -69,7 +70,9 @@ describe('MetaMask', function () { await driver.openNewPage('http://127.0.0.1:8080/') await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Connect')]`), + ) await driver.delay(regularDelayMs) @@ -77,42 +80,66 @@ describe('MetaMask', function () { windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles) - popup = windowHandles.find((handle) => handle !== extension && handle !== dapp) + dapp = await driver.switchToWindowWithTitle( + 'E2E Test Dapp', + windowHandles, + ) + popup = windowHandles.find( + (handle) => handle !== extension && handle !== dapp, + ) await driver.switchToWindow(popup) await driver.delay(regularDelayMs) await driver.clickElement(By.xpath(`//button[contains(text(), 'Next')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Connect')]`), + ) await driver.waitUntilXWindowHandles(2) await driver.switchToWindow(dapp) }) it('creates a sign typed data signature request', async function () { - await driver.clickElement(By.id('signTypedData'), 10000) + await driver.clickElement(By.id('signTypedDataV4'), 10000) await driver.delay(largeDelayMs) await driver.delay(regularDelayMs) windowHandles = await driver.getAllWindowHandles() - await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles) + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ) await driver.delay(regularDelayMs) - const title = await driver.findElement(By.css('.signature-request-content__title')) - const name = await driver.findElement(By.css('.signature-request-content__info--bolded')) - const content = await driver.findElements(By.css('.signature-request-content__info')) + const title = await driver.findElement( + By.css('.signature-request-content__title'), + ) + const name = await driver.findElement( + By.css('.signature-request-content__info--bolded'), + ) + const content = await driver.findElements( + By.css('.signature-request-content__info'), + ) const origin = content[0] const address = content[1] assert.equal(await title.getText(), 'Signature Request') assert.equal(await name.getText(), 'Ether Mail') assert.equal(await origin.getText(), 'http://127.0.0.1:8080') - assert.equal(await address.getText(), `${publicAddress.slice(0, 8)}...${publicAddress.slice(publicAddress.length - 8)}`) + assert.equal( + await address.getText(), + `${publicAddress.slice(0, 8)}...${publicAddress.slice( + publicAddress.length - 8, + )}`, + ) }) it('signs the transaction', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Sign')]`), 10000) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Sign')]`), + 10000, + ) await driver.delay(regularDelayMs) extension = windowHandles[0] @@ -120,11 +147,17 @@ describe('MetaMask', function () { }) it('gets the current accounts address', async function () { - await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) - await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) + await driver.clickElement( + By.css('[data-testid="account-options-menu-button"]'), + ) + await driver.clickElement( + By.css('[data-testid="account-options-menu__account-details"]'), + ) await driver.delay(regularDelayMs) - const addressInput = await driver.findElement(By.css('.readonly-input__input')) + const addressInput = await driver.findElement( + By.css('.readonly-input__input'), + ) const newPublicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) diff --git a/test/e2e/tests/localization.spec.js b/test/e2e/tests/localization.spec.js index 127d432bd..7b17b95cc 100644 --- a/test/e2e/tests/localization.spec.js +++ b/test/e2e/tests/localization.spec.js @@ -7,7 +7,8 @@ describe('Localization', function () { const ganacheOptions = { accounts: [ { - secretKey: '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', balance: 25000000000000000000, }, ], @@ -18,7 +19,9 @@ describe('Localization', function () { const passwordField = await driver.findElement(By.css('#password')) await passwordField.sendKeys('correct horse battery staple') await passwordField.sendKeys(Key.ENTER) - const secondaryBalance = await driver.findElement(By.css('[data-testid="eth-overview__secondary-currency"]')) + const secondaryBalance = await driver.findElement( + By.css('[data-testid="eth-overview__secondary-currency"]'), + ) const secondaryBalanceText = await secondaryBalance.getText() const [fiatAmount, fiatUnit] = secondaryBalanceText.trim().split(/\s+/u) assert.ok(fiatAmount.startsWith('₱')) diff --git a/test/e2e/tests/personal-sign.spec.js b/test/e2e/tests/personal-sign.spec.js index 9cc2d4fc3..dbead3dbd 100644 --- a/test/e2e/tests/personal-sign.spec.js +++ b/test/e2e/tests/personal-sign.spec.js @@ -7,13 +7,19 @@ describe('Personal sign', function () { const ganacheOptions = { accounts: [ { - secretKey: '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', balance: 25000000000000000000, }, ], } await withFixtures( - { dapp: true, fixtures: 'personal-sign', ganacheOptions, title: this.test.title }, + { + dapp: true, + fixtures: 'personal-sign', + ganacheOptions, + title: this.test.title, + }, async ({ driver }) => { const passwordField = await driver.findElement(By.css('#password')) await passwordField.sendKeys('correct horse battery staple') @@ -25,13 +31,20 @@ describe('Personal sign', function () { await driver.waitUntilXWindowHandles(3) const windowHandles = await driver.getAllWindowHandles() - await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles) + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ) - const personalMessageRow = await driver.findElement(By.css('.request-signature__row-value')) + const personalMessageRow = await driver.findElement( + By.css('.request-signature__row-value'), + ) const personalMessage = await personalMessageRow.getText() assert.equal(personalMessage, 'Example `personal_sign` message') - await driver.clickElement(By.css('[data-testid="request-signature__sign"]')) + await driver.clickElement( + By.css('[data-testid="request-signature__sign"]'), + ) await driver.waitUntilXWindowHandles(2) }, diff --git a/test/e2e/tests/simple-send.spec.js b/test/e2e/tests/simple-send.spec.js index 3328b110c..68128dab7 100644 --- a/test/e2e/tests/simple-send.spec.js +++ b/test/e2e/tests/simple-send.spec.js @@ -6,7 +6,8 @@ describe('Simple send', function () { const ganacheOptions = { accounts: [ { - secretKey: '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', balance: 25000000000000000000, }, ], @@ -18,12 +19,22 @@ describe('Simple send', function () { await passwordField.sendKeys('correct horse battery staple') await passwordField.sendKeys(Key.ENTER) await driver.clickElement(By.css('[data-testid="eth-overview-send"]')) - const recipientAddressField = await driver.findElement(By.css('[data-testid="ens-input"]')) - await recipientAddressField.sendKeys('0x985c30949c92df7a0bd42e0f3e3d539ece98db24') - const amountField = await driver.findElement(By.css('.unit-input__input')) + const recipientAddressField = await driver.findElement( + By.css('[data-testid="ens-input"]'), + ) + await recipientAddressField.sendKeys( + '0x985c30949c92df7a0bd42e0f3e3d539ece98db24', + ) + const amountField = await driver.findElement( + By.css('.unit-input__input'), + ) await amountField.sendKeys('1') - await driver.clickElement(By.css('[data-testid="page-container-footer-next"]')) - await driver.clickElement(By.css('[data-testid="page-container-footer-next"]')) + await driver.clickElement( + By.css('[data-testid="page-container-footer-next"]'), + ) + await driver.clickElement( + By.css('[data-testid="page-container-footer-next"]'), + ) await driver.clickElement(By.css('[data-testid="home__activity-tab"]')) await driver.findElement(By.css('.transaction-list-item')) }, diff --git a/test/e2e/threebox.spec.js b/test/e2e/threebox.spec.js index 3254d8cb9..107531bff 100644 --- a/test/e2e/threebox.spec.js +++ b/test/e2e/threebox.spec.js @@ -4,11 +4,7 @@ const getPort = require('get-port') const { By, until } = webdriver const enLocaleMessages = require('../../app/_locales/en/messages.json') -const { - tinyDelayMs, - regularDelayMs, - largeDelayMs, -} = require('./helpers') +const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers') const { buildWebDriver } = require('./webdriver') const Ganache = require('./ganache') @@ -17,7 +13,8 @@ const ganacheServer = new Ganache() describe('MetaMask', function () { let driver - const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' + const testSeedPhrase = + 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' this.timeout(0) this.bail(true) @@ -26,7 +23,8 @@ describe('MetaMask', function () { await ganacheServer.start({ accounts: [ { - secretKey: '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', + secretKey: + '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', balance: 25000000000000000000, }, ], @@ -40,7 +38,9 @@ describe('MetaMask', function () { const errors = await driver.checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + const errorMessage = `Errors found in browser console:\n${errorReports.join( + '\n', + )}` console.error(new Error(errorMessage)) } } @@ -55,16 +55,21 @@ describe('MetaMask', function () { }) describe('set up data to be restored by 3box', function () { - describe('First time flow starting from an existing seed phrase', function () { it('clicks the continue button on the welcome screen', async function () { await driver.findElement(By.css('.welcome-page__header')) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`, + ), + ) await driver.delay(largeDelayMs) }) it('clicks the "Import Wallet" option', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Import wallet')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Import wallet')]`), + ) await driver.delay(largeDelayMs) }) @@ -74,29 +79,43 @@ describe('MetaMask', function () { }) it('imports a seed phrase', async function () { - const [seedTextArea] = await driver.findElements(By.css('input[placeholder="Paste seed phrase from clipboard"]')) + const [seedTextArea] = await driver.findElements( + By.css('input[placeholder="Paste seed phrase from clipboard"]'), + ) await seedTextArea.sendKeys(testSeedPhrase) await driver.delay(regularDelayMs) const [password] = await driver.findElements(By.id('password')) await password.sendKeys('correct horse battery staple') - const [confirmPassword] = await driver.findElements(By.id('confirm-password')) + const [confirmPassword] = await driver.findElements( + By.id('confirm-password'), + ) confirmPassword.sendKeys('correct horse battery staple') await driver.clickElement(By.css('.first-time-flow__terms')) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Import')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Import')]`), + ) await driver.delay(regularDelayMs) }) it('clicks through the success screen', async function () { - await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) + await driver.findElement( + By.xpath(`//div[contains(text(), 'Congratulations')]`), + ) + await driver.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`, + ), + ) await driver.delay(regularDelayMs) }) it('balance renders', async function () { - const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) + const balance = await driver.findElement( + By.css('[data-testid="wallet-balance"] .list-item__heading'), + ) await driver.wait(until.elementTextMatches(balance, /25\s*ETH/u)) await driver.delay(regularDelayMs) }) @@ -107,19 +126,26 @@ describe('MetaMask', function () { await driver.clickElement(By.css('.account-menu__icon')) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//div[contains(text(), 'Settings')]`)) + await driver.clickElement( + By.xpath(`//div[contains(text(), 'Settings')]`), + ) }) it('turns on threebox syncing', async function () { - await driver.clickElement(By.xpath(`//div[contains(text(), 'Advanced')]`)) - await driver.clickElement(By.css('[data-testid="advanced-setting-3box"] .toggle-button div')) + await driver.clickElement( + By.xpath(`//div[contains(text(), 'Advanced')]`), + ) + await driver.clickElement( + By.css('[data-testid="advanced-setting-3box"] .toggle-button div'), + ) }) - }) describe('updates settings and address book', function () { it('navigates to General settings', async function () { - await driver.clickElement(By.xpath(`//div[contains(text(), 'General')]`)) + await driver.clickElement( + By.xpath(`//div[contains(text(), 'General')]`), + ) }) it('turns on use of blockies', async function () { @@ -127,7 +153,9 @@ describe('MetaMask', function () { }) it('adds an address to the contact list', async function () { - await driver.clickElement(By.xpath(`//div[contains(text(), 'Contacts')]`)) + await driver.clickElement( + By.xpath(`//div[contains(text(), 'Contacts')]`), + ) await driver.clickElement(By.css('.address-book-add-button__button')) await driver.delay(tinyDelayMs) @@ -137,17 +165,22 @@ describe('MetaMask', function () { await driver.delay(tinyDelayMs) - await addAddressInputs[1].sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') + await addAddressInputs[1].sendKeys( + '0x2f318C334780961FB129D2a6c30D0763d9a5C970', + ) await driver.delay(largeDelayMs * 2) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Save')]`)) + await driver.clickElement( + By.xpath(`//button[contains(text(), 'Save')]`), + ) - await driver.findElement(By.xpath(`//div[contains(text(), 'Test User Name 11')]`)) + await driver.findElement( + By.xpath(`//div[contains(text(), 'Test User Name 11')]`), + ) await driver.delay(regularDelayMs) }) }) - }) describe('restoration from 3box', function () { @@ -165,12 +198,18 @@ describe('MetaMask', function () { describe('First time flow starting from an existing seed phrase', function () { it('clicks the continue button on the welcome screen', async function () { await driver2.findElement(By.css('.welcome-page__header')) - await driver2.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + await driver2.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`, + ), + ) await driver2.delay(largeDelayMs) }) it('clicks the "Import Wallet" option', async function () { - await driver2.clickElement(By.xpath(`//button[contains(text(), 'Import wallet')]`)) + await driver2.clickElement( + By.xpath(`//button[contains(text(), 'Import wallet')]`), + ) await driver2.delay(largeDelayMs) }) @@ -180,29 +219,43 @@ describe('MetaMask', function () { }) it('imports a seed phrase', async function () { - const [seedTextArea] = await driver2.findElements(By.css('input[placeholder="Paste seed phrase from clipboard"]')) + const [seedTextArea] = await driver2.findElements( + By.css('input[placeholder="Paste seed phrase from clipboard"]'), + ) await seedTextArea.sendKeys(testSeedPhrase) await driver2.delay(regularDelayMs) const [password] = await driver2.findElements(By.id('password')) await password.sendKeys('correct horse battery staple') - const [confirmPassword] = await driver2.findElements(By.id('confirm-password')) + const [confirmPassword] = await driver2.findElements( + By.id('confirm-password'), + ) confirmPassword.sendKeys('correct horse battery staple') await driver2.clickElement(By.css('.first-time-flow__terms')) - await driver2.clickElement(By.xpath(`//button[contains(text(), 'Import')]`)) + await driver2.clickElement( + By.xpath(`//button[contains(text(), 'Import')]`), + ) await driver2.delay(regularDelayMs) }) it('clicks through the success screen', async function () { - await driver2.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`)) - await driver2.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) + await driver2.findElement( + By.xpath(`//div[contains(text(), 'Congratulations')]`), + ) + await driver2.clickElement( + By.xpath( + `//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`, + ), + ) await driver2.delay(regularDelayMs) }) it('balance renders', async function () { - const balance = await driver2.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) + const balance = await driver2.findElement( + By.css('[data-testid="wallet-balance"] .list-item__heading'), + ) await driver2.wait(until.elementTextMatches(balance, /25\s*ETH/u)) await driver2.delay(regularDelayMs) }) @@ -217,21 +270,29 @@ describe('MetaMask', function () { await driver2.clickElement(By.css('.account-menu__icon')) await driver2.delay(regularDelayMs) - await driver2.clickElement(By.xpath(`//div[contains(text(), 'Settings')]`)) + await driver2.clickElement( + By.xpath(`//div[contains(text(), 'Settings')]`), + ) }) it('finds the blockies toggle turned on', async function () { await driver2.delay(regularDelayMs) - const toggleLabel = await driver2.findElement(By.css('.toggle-button__status')) + const toggleLabel = await driver2.findElement( + By.css('.toggle-button__status'), + ) const toggleLabelText = await toggleLabel.getText() assert.equal(toggleLabelText, 'ON') }) it('finds the restored address in the contact list', async function () { - await driver2.clickElement(By.xpath(`//div[contains(text(), 'Contacts')]`)) + await driver2.clickElement( + By.xpath(`//div[contains(text(), 'Contacts')]`), + ) await driver2.delay(regularDelayMs) - await driver2.findElement(By.xpath(`//div[contains(text(), 'Test User Name 11')]`)) + await driver2.findElement( + By.xpath(`//div[contains(text(), 'Test User Name 11')]`), + ) await driver2.delay(regularDelayMs) }) }) diff --git a/test/e2e/webdriver/chrome.js b/test/e2e/webdriver/chrome.js index 556073056..f68237601 100644 --- a/test/e2e/webdriver/chrome.js +++ b/test/e2e/webdriver/chrome.js @@ -5,21 +5,15 @@ const chrome = require('selenium-webdriver/chrome') * A wrapper around a {@code WebDriver} instance exposing Chrome-specific functionality */ class ChromeDriver { - static async build ({ extensionPath, responsive, port }) { - const args = [ - `load-extension=${extensionPath}`, - ] + static async build({ extensionPath, responsive, port }) { + const args = [`load-extension=${extensionPath}`] if (responsive) { args.push('--auto-open-devtools-for-tabs') } - const options = new chrome.Options() - .addArguments(args) - const builder = new Builder() - .forBrowser('chrome') - .setChromeOptions(options) + const options = new chrome.Options().addArguments(args) + const builder = new Builder().forBrowser('chrome').setChromeOptions(options) if (port) { - const service = new chrome.ServiceBuilder() - .setPort(port) + const service = new chrome.ServiceBuilder().setPort(port) builder.setChromeService(service) } const driver = builder.build() @@ -36,16 +30,16 @@ class ChromeDriver { * @constructor * @param {!ThenableWebDriver} driver - a {@code WebDriver} instance */ - constructor (driver) { + constructor(driver) { this._driver = driver } /** * Returns the extension ID for the given extension name * @param {string} extensionName - the extension name - * @returns {Promise} - the extension ID + * @returns {Promise} the extension ID */ - async getExtensionIdByName (extensionName) { + async getExtensionIdByName(extensionName) { await this._driver.get('chrome://extensions') return await this._driver.executeScript(` const extensions = document.querySelector("extensions-manager").shadowRoot diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index c4a551b36..9f094e1a6 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -3,44 +3,43 @@ const { strict: assert } = require('assert') const { until, error: webdriverError } = require('selenium-webdriver') class Driver { - /** * @param {!ThenableWebDriver} driver - A {@code WebDriver} instance * @param {string} browser - The type of browser this driver is controlling * @param {number} timeout */ - constructor (driver, browser, extensionUrl, timeout = 10000) { + constructor(driver, browser, extensionUrl, timeout = 10000) { this.driver = driver this.browser = browser this.extensionUrl = extensionUrl this.timeout = timeout } - async delay (time) { + async delay(time) { await new Promise((resolve) => setTimeout(resolve, time)) } - async wait (condition, timeout = this.timeout) { + async wait(condition, timeout = this.timeout) { await this.driver.wait(condition, timeout) } - async quit () { + async quit() { await this.driver.quit() } // Element interactions - async findElement (locator) { + async findElement(locator) { return await this.driver.wait(until.elementLocated(locator), this.timeout) } - async findVisibleElement (locator) { + async findVisibleElement(locator) { const element = await this.findElement(locator) await this.driver.wait(until.elementIsVisible(element), this.timeout) return element } - async findClickableElement (locator) { + async findClickableElement(locator) { const element = await this.findElement(locator) await Promise.all([ this.driver.wait(until.elementIsVisible(element), this.timeout), @@ -49,29 +48,30 @@ class Driver { return element } - async findElements (locator) { + async findElements(locator) { return await this.driver.wait(until.elementsLocated(locator), this.timeout) } - async findClickableElements (locator) { + async findClickableElements(locator) { const elements = await this.findElements(locator) - await Promise.all(elements - .reduce((acc, element) => { + await Promise.all( + elements.reduce((acc, element) => { acc.push( this.driver.wait(until.elementIsVisible(element), this.timeout), this.driver.wait(until.elementIsEnabled(element), this.timeout), ) return acc - }, [])) + }, []), + ) return elements } - async clickElement (locator) { + async clickElement(locator) { const element = await this.findClickableElement(locator) await element.click() } - async clickPoint (locator, x, y) { + async clickPoint(locator, x, y) { const element = await this.findElement(locator) await this.driver .actions() @@ -80,49 +80,55 @@ class Driver { .perform() } - async scrollToElement (element) { - await this.driver.executeScript('arguments[0].scrollIntoView(true)', element) + async scrollToElement(element) { + await this.driver.executeScript( + 'arguments[0].scrollIntoView(true)', + element, + ) } - async assertElementNotPresent (locator) { + async assertElementNotPresent(locator) { let dataTab try { dataTab = await this.findElement(locator) } catch (err) { - assert(err instanceof webdriverError.NoSuchElementError || err instanceof webdriverError.TimeoutError) + assert( + err instanceof webdriverError.NoSuchElementError || + err instanceof webdriverError.TimeoutError, + ) } assert.ok(!dataTab, 'Found element that should not be present') } // Navigation - async navigate (page = Driver.PAGES.HOME) { + async navigate(page = Driver.PAGES.HOME) { return await this.driver.get(`${this.extensionUrl}/${page}.html`) } // Metrics - async collectMetrics () { + async collectMetrics() { return await this.driver.executeScript(collectMetrics) } // Window management - async openNewPage (url) { + async openNewPage(url) { const newHandle = await this.driver.switchTo().newWindow() await this.driver.get(url) return newHandle } - async switchToWindow (handle) { + async switchToWindow(handle) { await this.driver.switchTo().window(handle) } - async getAllWindowHandles () { + async getAllWindowHandles() { return await this.driver.getAllWindowHandles() } - async waitUntilXWindowHandles (x, delayStep = 1000, timeout = 5000) { + async waitUntilXWindowHandles(x, delayStep = 1000, timeout = 5000) { let timeElapsed = 0 let windowHandles = [] while (timeElapsed <= timeout) { @@ -136,9 +142,9 @@ class Driver { throw new Error('waitUntilXWindowHandles timed out polling window handles') } - async switchToWindowWithTitle (title, windowHandles) { + async switchToWindowWithTitle(title, windowHandles) { // eslint-disable-next-line no-param-reassign - windowHandles = windowHandles || await this.driver.getAllWindowHandles() + windowHandles = windowHandles || (await this.driver.getAllWindowHandles()) for (const handle of windowHandles) { await this.driver.switchTo().window(handle) @@ -157,9 +163,9 @@ class Driver { * @param {Array} [windowHandles] - The full list of window handles * @returns {Promise} */ - async closeAllWindowHandlesExcept (exceptions, windowHandles) { + async closeAllWindowHandlesExcept(exceptions, windowHandles) { // eslint-disable-next-line no-param-reassign - windowHandles = windowHandles || await this.driver.getAllWindowHandles() + windowHandles = windowHandles || (await this.driver.getAllWindowHandles()) for (const handle of windowHandles) { if (!exceptions.includes(handle)) { @@ -173,34 +179,46 @@ class Driver { // Error handling - async verboseReportOnFailure (title) { + async verboseReportOnFailure(title) { const artifactDir = `./test-artifacts/${this.browser}/${title}` const filepathBase = `${artifactDir}/test-failure` await fs.mkdir(artifactDir, { recursive: true }) const screenshot = await this.driver.takeScreenshot() - await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) + await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { + encoding: 'base64', + }) const htmlSource = await this.driver.getPageSource() await fs.writeFile(`${filepathBase}-dom.html`, htmlSource) const uiState = await this.driver.executeScript( () => window.getCleanAppState && window.getCleanAppState(), ) - await fs.writeFile(`${filepathBase}-state.json`, JSON.stringify(uiState, null, 2)) + await fs.writeFile( + `${filepathBase}-state.json`, + JSON.stringify(uiState, null, 2), + ) } - async checkBrowserForConsoleErrors () { + async checkBrowserForConsoleErrors() { const ignoredLogTypes = ['WARNING'] const ignoredErrorMessages = [ // Third-party Favicon 404s show up as errors 'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)', ] const browserLogs = await this.driver.manage().logs().get('browser') - const errorEntries = browserLogs.filter((entry) => !ignoredLogTypes.includes(entry.level.toString())) + const errorEntries = browserLogs.filter( + (entry) => !ignoredLogTypes.includes(entry.level.toString()), + ) const errorObjects = errorEntries.map((entry) => entry.toJSON()) - return errorObjects.filter((entry) => !ignoredErrorMessages.some((message) => entry.message.includes(message))) + return errorObjects.filter( + (entry) => + !ignoredErrorMessages.some((message) => + entry.message.includes(message), + ), + ) } } -function collectMetrics () { +function collectMetrics() { const results = { paint: {}, navigation: [], @@ -210,15 +228,17 @@ function collectMetrics () { results.paint[paintEntry.name] = paintEntry.startTime }) - window.performance.getEntriesByType('navigation').forEach((navigationEntry) => { - results.navigation.push({ - domContentLoaded: navigationEntry.domContentLoadedEventEnd, - load: navigationEntry.loadEventEnd, - domInteractive: navigationEntry.domInteractive, - redirectCount: navigationEntry.redirectCount, - type: navigationEntry.type, + window.performance + .getEntriesByType('navigation') + .forEach((navigationEntry) => { + results.navigation.push({ + domContentLoaded: navigationEntry.domContentLoadedEventEnd, + load: navigationEntry.loadEventEnd, + domInteractive: navigationEntry.domInteractive, + redirectCount: navigationEntry.redirectCount, + type: navigationEntry.type, + }) }) - }) return results } diff --git a/test/e2e/webdriver/firefox.js b/test/e2e/webdriver/firefox.js index f5c8426ae..0ce300da9 100644 --- a/test/e2e/webdriver/firefox.js +++ b/test/e2e/webdriver/firefox.js @@ -20,22 +20,19 @@ const GeckoDriverCommand = { * A wrapper around a {@code WebDriver} instance exposing Firefox-specific functionality */ class FirefoxDriver { - /** * Builds a {@link FirefoxDriver} instance * @param {{extensionPath: string}} options - the options for the build * @returns {Promise<{driver: !ThenableWebDriver, extensionUrl: string, extensionId: string}>} */ - static async build ({ extensionPath, responsive, port }) { + static async build({ extensionPath, responsive, port }) { const templateProfile = fs.mkdtempSync(TEMP_PROFILE_PATH_PREFIX) - const options = new firefox.Options() - .setProfile(templateProfile) + const options = new firefox.Options().setProfile(templateProfile) const builder = new Builder() .forBrowser('firefox') .setFirefoxOptions(options) if (port) { - const service = new firefox.ServiceBuilder() - .setPort(port) + const service = new firefox.ServiceBuilder().setPort(port) builder.setFirefoxService(service) } const driver = builder.build() @@ -61,7 +58,7 @@ class FirefoxDriver { * @constructor * @param {!ThenableWebDriver} driver - a {@code WebDriver} instance */ - constructor (driver) { + constructor(driver) { this._driver = driver } @@ -69,8 +66,9 @@ class FirefoxDriver { * Initializes the driver * @returns {Promise} */ - async init () { - await this._driver.getExecutor() + async init() { + await this._driver + .getExecutor() .defineCommand( GeckoDriverCommand.INSTALL_ADDON, 'POST', @@ -81,9 +79,9 @@ class FirefoxDriver { /** * Installs the extension at the given path * @param {string} addonPath - the path to the unpacked extension or XPI - * @returns {Promise} - the extension ID + * @returns {Promise} the extension ID */ - async installExtension (addonPath) { + async installExtension(addonPath) { const cmd = new Command(GeckoDriverCommand.INSTALL_ADDON) .setParameter('path', path.resolve(addonPath)) .setParameter('temporary', true) @@ -93,11 +91,18 @@ class FirefoxDriver { /** * Returns the Internal UUID for the given extension - * @returns {Promise} - the Internal UUID for the given extension + * @returns {Promise} the Internal UUID for the given extension */ - async getInternalId () { + async getInternalId() { await this._driver.get('about:debugging#addons') - return await this._driver.wait(until.elementLocated(By.xpath('//dl/div[contains(., \'Internal UUID\')]/dd')), 1000).getText() + return await this._driver + .wait( + until.elementLocated( + By.xpath("//dl/div[contains(., 'Internal UUID')]/dd"), + ), + 1000, + ) + .getText() } } diff --git a/test/e2e/webdriver/index.js b/test/e2e/webdriver/index.js index 20be5d031..267287ff6 100644 --- a/test/e2e/webdriver/index.js +++ b/test/e2e/webdriver/index.js @@ -4,11 +4,15 @@ const Driver = require('./driver') const ChromeDriver = require('./chrome') const FirefoxDriver = require('./firefox') -async function buildWebDriver ({ responsive, port } = {}) { +async function buildWebDriver({ responsive, port } = {}) { const browser = process.env.SELENIUM_BROWSER const extensionPath = `dist/${browser}` - const { driver: seleniumDriver, extensionId, extensionUrl } = await buildBrowserWebDriver(browser, { extensionPath, responsive, port }) + const { + driver: seleniumDriver, + extensionId, + extensionUrl, + } = await buildBrowserWebDriver(browser, { extensionPath, responsive, port }) await setupFetchMocking(seleniumDriver) const driver = new Driver(seleniumDriver, browser, extensionUrl) await driver.navigate() @@ -21,7 +25,7 @@ async function buildWebDriver ({ responsive, port } = {}) { } } -async function buildBrowserWebDriver (browser, webDriverOptions) { +async function buildBrowserWebDriver(browser, webDriverOptions) { switch (browser) { case Browser.CHROME: { return await ChromeDriver.build(webDriverOptions) @@ -35,15 +39,17 @@ async function buildBrowserWebDriver (browser, webDriverOptions) { } } -async function setupFetchMocking (driver) { +async function setupFetchMocking(driver) { // define fetchMocking script, to be evaluated in the browser - function fetchMocking (mockResponses) { + function fetchMocking(mockResponses) { window.origFetch = window.fetch.bind(window) window.fetch = async (...args) => { const url = args[0] if (url.match(/^http(s)?:\/\/ethgasstation\.info\/json\/ethgasAPI.*/u)) { return { json: async () => clone(mockResponses.ethGasBasic) } - } else if (url.match(/http(s?):\/\/ethgasstation\.info\/json\/predictTable.*/u)) { + } else if ( + url.match(/http(s?):\/\/ethgasstation\.info\/json\/predictTable.*/u) + ) { return { json: async () => clone(mockResponses.ethGasPredictTable) } } else if (url.match(/chromeextensionmm/u)) { return { json: async () => clone(mockResponses.metametrics) } @@ -55,13 +61,17 @@ async function setupFetchMocking (driver) { return window.origFetch(...args) } if (window.chrome && window.chrome.webRequest) { - window.chrome.webRequest.onBeforeRequest.addListener(cancelInfuraRequest, { urls: ['https://*.infura.io/*'] }, ['blocking']) + window.chrome.webRequest.onBeforeRequest.addListener( + cancelInfuraRequest, + { urls: ['https://*.infura.io/*'] }, + ['blocking'], + ) } - function cancelInfuraRequest (requestDetails) { + function cancelInfuraRequest(requestDetails) { console.log(`fetchMocking - Canceling request: "${requestDetails.url}"`) return { cancel: true } } - function clone (obj) { + function clone(obj) { return JSON.parse(JSON.stringify(obj)) } } diff --git a/test/lib/createTxMeta.js b/test/lib/createTxMeta.js index 4dde18f62..c4b680d8e 100644 --- a/test/lib/createTxMeta.js +++ b/test/lib/createTxMeta.js @@ -1,8 +1,9 @@ import { snapshotFromTxMeta } from '../../app/scripts/controllers/transactions/lib/tx-state-history-helpers' +import { TRANSACTION_STATUSES } from '../../shared/constants/transaction' -export default function createTxMeta (partialMeta) { +export default function createTxMeta(partialMeta) { const txMeta = { - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, txParams: {}, ...partialMeta, } diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js index 5ea48e1e8..4b9f7faee 100644 --- a/test/lib/mock-encryptor.js +++ b/test/lib/mock-encryptor.js @@ -3,36 +3,34 @@ const mockKey = Buffer.alloc(32) let cacheVal const mockEncryptor = { - - encrypt (_, dataObj) { + encrypt(_, dataObj) { cacheVal = dataObj return Promise.resolve(mockHex) }, - decrypt () { + decrypt() { return Promise.resolve(cacheVal || {}) }, - encryptWithKey (key, dataObj) { + encryptWithKey(key, dataObj) { return this.encrypt(key, dataObj) }, - decryptWithKey (key, text) { + decryptWithKey(key, text) { return this.decrypt(key, text) }, - keyFromPassword () { + keyFromPassword() { return Promise.resolve(mockKey) }, - generateSalt () { + generateSalt() { return 'WHADDASALT!' }, - getRandomValues () { + getRandomValues() { return 'SOO RANDO!!!1' }, - } export default mockEncryptor diff --git a/test/lib/render-helpers.js b/test/lib/render-helpers.js index 09920ee7b..b8d6be450 100644 --- a/test/lib/render-helpers.js +++ b/test/lib/render-helpers.js @@ -6,8 +6,7 @@ import { MemoryRouter } from 'react-router-dom' import PropTypes from 'prop-types' import { LegacyI18nProvider } from '../../ui/app/contexts/i18n' -export function mountWithRouter (component, store = {}, pathname = '/') { - +export function mountWithRouter(component, store = {}, pathname = '/') { // Instantiate router context const router = { history: new MemoryRouter().history, @@ -43,13 +42,10 @@ export function mountWithRouter (component, store = {}, pathname = '/') { return mount(, createContext()) } -export function renderWithProvider (component, store) { - +export function renderWithProvider(component, store) { const Wrapper = () => ( - - { component } - + {component} ) diff --git a/test/lib/wait-until-called.js b/test/lib/wait-until-called.js new file mode 100644 index 000000000..7a2eb2704 --- /dev/null +++ b/test/lib/wait-until-called.js @@ -0,0 +1,26 @@ +/** + * A function that wraps a sinon stubbed function and returns a Promise + * when this stub was called. + * + * The stub that has been passed in will be setup to call the wrapped function + * directly, then trigger the returned Promise to resolve. + * + * WARNING: Any existing `callsFake` behavior will be overwritten. + * + * @param {import('sinon').stub} stub - A sinon stub of a function + * @param {unknown} [wrappedThis] - The object the stubbed function was called on, if any (i.e. the `this` value) + * @returns {Promise} A Promise that resolves when the stub has been called + */ +export default function waitUntilCalled(stub, wrappedThis = null) { + let wasCalled + const stubHasBeenCalled = new Promise((resolve) => { + wasCalled = resolve + }) + stub.callsFake((...args) => { + if (stub.wrappedMethod) { + stub.wrappedMethod.call(wrappedThis, ...args) + } + wasCalled() + }) + return stubHasBeenCalled +} diff --git a/test/stub/provider.js b/test/stub/provider.js index 4ff63297a..40fd95258 100644 --- a/test/stub/provider.js +++ b/test/stub/provider.js @@ -3,38 +3,60 @@ import scaffoldMiddleware from 'eth-json-rpc-middleware/scaffold' import providerAsMiddleware from 'eth-json-rpc-middleware/providerAsMiddleware' import GanacheCore from 'ganache-core' -export function getTestSeed () { +export function getTestSeed() { return 'people carpet cluster attract ankle motor ozone mass dove original primary mask' } -export function getTestAccounts () { +export function getTestAccounts() { return [ - { address: '0x88bb7F89eB5e5b30D3e15a57C68DBe03C6aCCB21', key: Buffer.from('254A8D551474F35CCC816388B4ED4D20B945C96B7EB857A68064CB9E9FB2C092', 'hex') }, - { address: '0x1fe9aAB565Be19629fF4e8541ca2102fb42D7724', key: Buffer.from('6BAB5A4F2A6911AF8EE2BD32C6C05F6643AC48EF6C939CDEAAAE6B1620805A9B', 'hex') }, - { address: '0xbda5c89aa6bA1b352194291AD6822C92AbC87c7B', key: Buffer.from('9B11D7F833648F26CE94D544855558D7053ECD396E4F4563968C232C012879B0', 'hex') }, + { + address: '0x88bb7F89eB5e5b30D3e15a57C68DBe03C6aCCB21', + key: Buffer.from( + '254A8D551474F35CCC816388B4ED4D20B945C96B7EB857A68064CB9E9FB2C092', + 'hex', + ), + }, + { + address: '0x1fe9aAB565Be19629fF4e8541ca2102fb42D7724', + key: Buffer.from( + '6BAB5A4F2A6911AF8EE2BD32C6C05F6643AC48EF6C939CDEAAAE6B1620805A9B', + 'hex', + ), + }, + { + address: '0xbda5c89aa6bA1b352194291AD6822C92AbC87c7B', + key: Buffer.from( + '9B11D7F833648F26CE94D544855558D7053ECD396E4F4563968C232C012879B0', + 'hex', + ), + }, ] } -export function createEngineForTestData () { +export function createEngineForTestData() { return new JsonRpcEngine() } -export function providerFromEngine (engine) { +export function providerFromEngine(engine) { const provider = { sendAsync: engine.handle.bind(engine) } return provider } -export function createTestProviderTools (opts = {}) { +export function createTestProviderTools(opts = {}) { const engine = createEngineForTestData() // handle provided hooks engine.push(scaffoldMiddleware(opts.scaffold || {})) // handle block tracker methods - engine.push(providerAsMiddleware(GanacheCore.provider({ - mnemonic: getTestSeed(), - network_id: opts.networkId, - _chainId: opts.chainId, - _chainIdRpc: opts.chainId, - }))) + engine.push( + providerAsMiddleware( + GanacheCore.provider({ + mnemonic: getTestSeed(), + network_id: opts.networkId, + _chainId: opts.chainId, + _chainIdRpc: opts.chainId, + }), + ), + ) // wrap in standard provider interface const provider = providerFromEngine(engine) return { provider, engine } diff --git a/test/unit-global/frozenPromise.js b/test/unit-global/frozenPromise.js index 594c1350a..6583e1edf 100644 --- a/test/unit-global/frozenPromise.js +++ b/test/unit-global/frozenPromise.js @@ -3,7 +3,6 @@ import '../../app/scripts/lib/freezeGlobals' import assert from 'assert' describe('Promise global is immutable', function () { - it('throws when reassinging promise (syntax 1)', function () { try { // eslint-disable-next-line no-global-assign,no-native-reassign diff --git a/test/unit/actions/set_account_label_test.js b/test/unit/actions/set_account_label_test.js index 67ad701ed..7a79bd0b4 100644 --- a/test/unit/actions/set_account_label_test.js +++ b/test/unit/actions/set_account_label_test.js @@ -26,7 +26,9 @@ describe('SET_ACCOUNT_LABEL', function () { freeze(action) const resultingState = reducers(initialState, action) - assert.equal(resultingState.metamask.identities.foo.name, action.value.label) + assert.equal( + resultingState.metamask.identities.foo.name, + action.value.label, + ) }) }) - diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index a21824688..99b27689f 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -10,8 +10,7 @@ const mockStore = configureMockStore(middlewares) describe('tx confirmation screen', function () { const txId = 1457634084250832 const initialState = { - appState: { - }, + appState: {}, metamask: { unapprovedTxs: { [txId]: { @@ -28,20 +27,22 @@ describe('tx confirmation screen', function () { describe('cancelTx', function () { it('creates COMPLETED_TX with the cancelled transaction ID', async function () { actions._setBackgroundConnection({ - approveTransaction (_, cb) { + approveTransaction(_, cb) { cb(new Error('An error!')) }, - cancelTransaction (_, cb) { + cancelTransaction(_, cb) { cb() }, - getState (cb) { + getState(cb) { cb(null, {}) }, }) await store.dispatch(actions.cancelTx({ id: txId })) const storeActions = store.getActions() - const completedTxAction = storeActions.find(({ type }) => type === actionConstants.COMPLETED_TX) + const completedTxAction = storeActions.find( + ({ type }) => type === actionConstants.COMPLETED_TX, + ) const { id } = completedTxAction.value assert.equal(id, txId) }) diff --git a/test/unit/actions/warning_test.js b/test/unit/actions/warning_test.js index 70aed3c59..a59b40f92 100644 --- a/test/unit/actions/warning_test.js +++ b/test/unit/actions/warning_test.js @@ -15,6 +15,10 @@ describe('action DISPLAY_WARNING', function () { const action = actions.displayWarning(warningText) const resultingState = reducers(initialState, action) - assert.equal(resultingState.appState.warning, warningText, 'warning text set') + assert.equal( + resultingState.appState.warning, + warningText, + 'warning text set', + ) }) }) diff --git a/test/unit/app/account-import-strategies.spec.js b/test/unit/app/account-import-strategies.spec.js index 3c6b6fbef..8918bd064 100644 --- a/test/unit/app/account-import-strategies.spec.js +++ b/test/unit/app/account-import-strategies.spec.js @@ -3,19 +3,27 @@ import ethUtil from 'ethereumjs-util' import accountImporter from '../../../app/scripts/account-import-strategies' describe('Account Import Strategies', function () { - const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' - const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}' + const privkey = + '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' + const json = + '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}' describe('private key import', function () { it('imports a private key and strips 0x prefix', async function () { - const importPrivKey = await accountImporter.importAccount('Private Key', [privkey]) + const importPrivKey = await accountImporter.importAccount('Private Key', [ + privkey, + ]) assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey)) }) it('throws an error for empty string private key', async function () { - await assert.rejects(async () => { - await accountImporter.importAccount('Private Key', ['']) - }, Error, 'no empty strings') + await assert.rejects( + async () => { + await accountImporter.importAccount('Private Key', ['']) + }, + Error, + 'no empty strings', + ) }) it('throws an error for undefined string private key', async function () { @@ -42,14 +50,23 @@ describe('Account Import Strategies', function () { try { await accountImporter.importAccount('JSON File', [json, wrongPassword]) } catch (error) { - assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase') + assert.equal( + error.message, + 'Key derivation failed - possibly wrong passphrase', + ) } }) it('imports json string and password to return a private key', async function () { const fileContentsPassword = 'password1' - const importJson = await accountImporter.importAccount('JSON File', [json, fileContentsPassword]) - assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7') + const importJson = await accountImporter.importAccount('JSON File', [ + json, + fileContentsPassword, + ]) + assert.equal( + importJson, + '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7', + ) }) }) }) diff --git a/test/unit/app/buy-eth-url.spec.js b/test/unit/app/buy-eth-url.spec.js index 7dc895e8d..749d0a40f 100644 --- a/test/unit/app/buy-eth-url.spec.js +++ b/test/unit/app/buy-eth-url.spec.js @@ -20,8 +20,10 @@ describe('buy-eth-url', function () { it('returns wyre url with address for network 1', function () { const wyreUrl = getBuyEthUrl(mainnet) - assert.equal(wyreUrl, 'https://pay.sendwyre.com/purchase?dest=ethereum:0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc&destCurrency=ETH&accountId=AC-7AG3W4XH4N2&paymentMethod=debit-card') - + assert.equal( + wyreUrl, + 'https://pay.sendwyre.com/purchase?dest=ethereum:0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc&destCurrency=ETH&accountId=AC-7AG3W4XH4N2&paymentMethod=debit-card', + ) }) it('returns metamask ropsten faucet for network 3', function () { @@ -38,5 +40,4 @@ describe('buy-eth-url', function () { const kovanUrl = getBuyEthUrl(kovan) assert.equal(kovanUrl, 'https://github.com/kovan-testnet/faucet') }) - }) diff --git a/test/unit/app/cleanErrorStack.spec.js b/test/unit/app/cleanErrorStack.spec.js index fa793b605..5aa63cb4c 100644 --- a/test/unit/app/cleanErrorStack.spec.js +++ b/test/unit/app/cleanErrorStack.spec.js @@ -2,7 +2,6 @@ import assert from 'assert' import cleanErrorStack from '../../../app/scripts/lib/cleanErrorStack' describe('Clean Error Stack', function () { - const testMessage = 'Test Message' const testError = new Error(testMessage) const undefinedErrorName = new Error(testMessage) @@ -19,7 +18,10 @@ describe('Clean Error Stack', function () { }) it('tests error with undefined name', function () { - assert.equal(cleanErrorStack(undefinedErrorName).toString(), 'Error: Test Message') + assert.equal( + cleanErrorStack(undefinedErrorName).toString(), + 'Error: Test Message', + ) }) it('tests error with blank name', function () { @@ -29,5 +31,4 @@ describe('Clean Error Stack', function () { it('tests error with blank message', function () { assert.equal(cleanErrorStack(blankMsgError), 'Error') }) - }) diff --git a/test/unit/app/controllers/cached-balances-test.js b/test/unit/app/controllers/cached-balances-test.js index f954da63f..8a7fb61ae 100644 --- a/test/unit/app/controllers/cached-balances-test.js +++ b/test/unit/app/controllers/cached-balances-test.js @@ -17,13 +17,21 @@ describe('CachedBalancesController', function () { }, }) - controller._generateBalancesToCache = sinon.stub().callsFake(() => Promise.resolve('mockNewCachedBalances')) + controller._generateBalancesToCache = sinon + .stub() + .callsFake(() => Promise.resolve('mockNewCachedBalances')) await controller.updateCachedBalances({ accounts: 'mockAccounts' }) assert.equal(controller._generateBalancesToCache.callCount, 1) - assert.deepEqual(controller._generateBalancesToCache.args[0], ['mockAccounts', 17]) - assert.equal(controller.store.getState().cachedBalances, 'mockNewCachedBalances') + assert.deepEqual(controller._generateBalancesToCache.args[0], [ + 'mockAccounts', + 17, + ]) + assert.equal( + controller.store.getState().cachedBalances, + 'mockNewCachedBalances', + ) }) }) @@ -51,11 +59,14 @@ describe('CachedBalancesController', function () { }, }) - const result = controller._generateBalancesToCache({ - a: { balance: '0x4' }, - b: { balance: null }, - c: { balance: '0x5' }, - }, 17) + const result = controller._generateBalancesToCache( + { + a: { balance: '0x4' }, + b: { balance: null }, + c: { balance: '0x5' }, + }, + 17, + ) assert.deepEqual(result, { 17: { @@ -89,11 +100,14 @@ describe('CachedBalancesController', function () { }, }) - const result = controller._generateBalancesToCache({ - a: { balance: '0x4' }, - b: { balance: null }, - c: { balance: '0x5' }, - }, 16) + const result = controller._generateBalancesToCache( + { + a: { balance: '0x4' }, + b: { balance: null }, + c: { balance: '0x5' }, + }, + 16, + ) assert.deepEqual(result, { 17: { @@ -133,5 +147,4 @@ describe('CachedBalancesController', function () { assert.equal(updateCachedBalancesSpy.callCount, 1) }) }) - }) diff --git a/test/unit/app/controllers/detect-tokens-test.js b/test/unit/app/controllers/detect-tokens-test.js index 6798703c1..8f3a5a52c 100644 --- a/test/unit/app/controllers/detect-tokens-test.js +++ b/test/unit/app/controllers/detect-tokens-test.js @@ -7,7 +7,10 @@ import BigNumber from 'bignumber.js' import DetectTokensController from '../../../../app/scripts/controllers/detect-tokens' import NetworkController from '../../../../app/scripts/controllers/network/network' import PreferencesController from '../../../../app/scripts/controllers/preferences' -import { MAINNET, ROPSTEN } from '../../../../app/scripts/controllers/network/enums' +import { + MAINNET, + ROPSTEN, +} from '../../../../app/scripts/controllers/network/enums' describe('DetectTokensController', function () { const sandbox = sinon.createSandbox() @@ -29,7 +32,6 @@ describe('DetectTokensController', function () { '0xbc86727e770de68b1060c91f6bb6945c73e10388', ]) network.initializeProvider(networkControllerProviderConfig) - }) after(function () { @@ -46,7 +48,11 @@ describe('DetectTokensController', function () { it('should be called on every polling period', async function () { const clock = sandbox.useFakeTimers() network.setProviderType(MAINNET) - const controller = new DetectTokensController({ preferences, network, keyringMemStore }) + const controller = new DetectTokensController({ + preferences, + network, + keyringMemStore, + }) controller.isOpen = true controller.isUnlocked = true @@ -65,7 +71,11 @@ describe('DetectTokensController', function () { it('should not check tokens while on test network', async function () { sandbox.useFakeTimers() network.setProviderType(ROPSTEN) - const controller = new DetectTokensController({ preferences, network, keyringMemStore }) + const controller = new DetectTokensController({ + preferences, + network, + keyringMemStore, + }) controller.isOpen = true controller.isUnlocked = true @@ -78,55 +88,83 @@ describe('DetectTokensController', function () { it('should check and add tokens while on main network', async function () { sandbox.useFakeTimers() network.setProviderType(MAINNET) - const controller = new DetectTokensController({ preferences, network, keyringMemStore }) + const controller = new DetectTokensController({ + preferences, + network, + keyringMemStore, + }) controller.isOpen = true controller.isUnlocked = true const contractAddresses = Object.keys(contracts) - const erc20ContractAddresses = contractAddresses - .filter((contractAddress) => contracts[contractAddress].erc20 === true) + const erc20ContractAddresses = contractAddresses.filter( + (contractAddress) => contracts[contractAddress].erc20 === true, + ) const existingTokenAddress = erc20ContractAddresses[0] const existingToken = contracts[existingTokenAddress] - await preferences.addToken(existingTokenAddress, existingToken.symbol, existingToken.decimals) + await preferences.addToken( + existingTokenAddress, + existingToken.symbol, + existingToken.decimals, + ) const tokenAddressToAdd = erc20ContractAddresses[1] const tokenToAdd = contracts[tokenAddressToAdd] - const contractAddresssesToDetect = contractAddresses - .filter((address) => address !== existingTokenAddress) - const indexOfTokenToAdd = contractAddresssesToDetect.indexOf(tokenAddressToAdd) + const contractAddresssesToDetect = contractAddresses.filter( + (address) => address !== existingTokenAddress, + ) + const indexOfTokenToAdd = contractAddresssesToDetect.indexOf( + tokenAddressToAdd, + ) const balances = new Array(contractAddresssesToDetect.length) balances[indexOfTokenToAdd] = new BigNumber(10) - sandbox.stub(controller, '_getTokenBalances') + sandbox + .stub(controller, '_getTokenBalances') .returns(Promise.resolve(balances)) await controller.detectNewTokens() - assert.deepEqual( - preferences.store.getState().tokens, - [ - { address: existingTokenAddress.toLowerCase(), decimals: existingToken.decimals, symbol: existingToken.symbol }, - { address: tokenAddressToAdd.toLowerCase(), decimals: tokenToAdd.decimals, symbol: tokenToAdd.symbol }, - ], - ) + assert.deepEqual(preferences.store.getState().tokens, [ + { + address: existingTokenAddress.toLowerCase(), + decimals: existingToken.decimals, + symbol: existingToken.symbol, + }, + { + address: tokenAddressToAdd.toLowerCase(), + decimals: tokenToAdd.decimals, + symbol: tokenToAdd.symbol, + }, + ]) }) it('should trigger detect new tokens when change address', async function () { sandbox.useFakeTimers() - const controller = new DetectTokensController({ preferences, network, keyringMemStore }) + const controller = new DetectTokensController({ + preferences, + network, + keyringMemStore, + }) controller.isOpen = true controller.isUnlocked = true const stub = sandbox.stub(controller, 'detectNewTokens') - await preferences.setSelectedAddress('0xbc86727e770de68b1060c91f6bb6945c73e10388') + await preferences.setSelectedAddress( + '0xbc86727e770de68b1060c91f6bb6945c73e10388', + ) sandbox.assert.called(stub) }) it('should trigger detect new tokens when submit password', async function () { sandbox.useFakeTimers() - const controller = new DetectTokensController({ preferences, network, keyringMemStore }) + const controller = new DetectTokensController({ + preferences, + network, + keyringMemStore, + }) controller.isOpen = true controller.selectedAddress = '0x0' const stub = sandbox.stub(controller, 'detectNewTokens') @@ -137,7 +175,11 @@ describe('DetectTokensController', function () { it('should not trigger detect new tokens when not unlocked', async function () { const clock = sandbox.useFakeTimers() network.setProviderType(MAINNET) - const controller = new DetectTokensController({ preferences, network, keyringMemStore }) + const controller = new DetectTokensController({ + preferences, + network, + keyringMemStore, + }) controller.isOpen = true controller.isUnlocked = false const stub = sandbox.stub(controller, '_getTokenBalances') @@ -148,9 +190,15 @@ describe('DetectTokensController', function () { it('should not trigger detect new tokens when not open', async function () { const clock = sandbox.useFakeTimers() network.setProviderType(MAINNET) - const controller = new DetectTokensController({ preferences, network, keyringMemStore }) + const controller = new DetectTokensController({ + preferences, + network, + keyringMemStore, + }) // trigger state update from preferences controller - await preferences.setSelectedAddress('0xbc86727e770de68b1060c91f6bb6945c73e10388') + await preferences.setSelectedAddress( + '0xbc86727e770de68b1060c91f6bb6945c73e10388', + ) controller.isOpen = false controller.isUnlocked = true const stub = sandbox.stub(controller, '_getTokenBalances') diff --git a/test/unit/app/controllers/ens-controller-test.js b/test/unit/app/controllers/ens-controller-test.js index cf57bdf1f..0fbaf33ac 100644 --- a/test/unit/app/controllers/ens-controller-test.js +++ b/test/unit/app/controllers/ens-controller-test.js @@ -12,7 +12,7 @@ describe('EnsController', function () { const currentNetworkId = '3' const networkStore = new ObservableStore(currentNetworkId) const ens = new EnsController({ - provider: { }, + provider: {}, networkStore, }) @@ -117,7 +117,10 @@ describe('EnsController', function () { const ens = new EnsController({ ens: { reverse: sinon.stub().withArgs(address).returns('peaksignal.eth'), - lookup: sinon.stub().withArgs('peaksignal.eth').returns(ZERO_X_ERROR_ADDRESS), + lookup: sinon + .stub() + .withArgs('peaksignal.eth') + .returns(ZERO_X_ERROR_ADDRESS), }, networkStore, }) diff --git a/test/unit/app/controllers/incoming-transactions-test.js b/test/unit/app/controllers/incoming-transactions-test.js index 727c394fa..8401d7f24 100644 --- a/test/unit/app/controllers/incoming-transactions-test.js +++ b/test/unit/app/controllers/incoming-transactions-test.js @@ -1,209 +1,926 @@ import assert from 'assert' import sinon from 'sinon' import proxyquire from 'proxyquire' +import nock from 'nock' +import { cloneDeep } from 'lodash' -import { ROPSTEN, RINKEBY, KOVAN, GOERLI, MAINNET } from '../../../../app/scripts/controllers/network/enums' +import waitUntilCalled from '../../../lib/wait-until-called' +import { + GOERLI, + KOVAN, + MAINNET, + MAINNET_CHAIN_ID, + RINKEBY, + ROPSTEN, + ROPSTEN_CHAIN_ID, + ROPSTEN_NETWORK_ID, +} from '../../../../app/scripts/controllers/network/enums' +import { + TRANSACTION_CATEGORIES, + TRANSACTION_STATUSES, +} from '../../../../shared/constants/transaction' -const IncomingTransactionsController = proxyquire('../../../../app/scripts/controllers/incoming-transactions', { - '../lib/random-id': { default: () => 54321 }, -}).default +const IncomingTransactionsController = proxyquire( + '../../../../app/scripts/controllers/incoming-transactions', + { + '../lib/random-id': { default: () => 54321 }, + }, +).default -describe('IncomingTransactionsController', function () { - const EMPTY_INIT_STATE = { +const FAKE_CHAIN_ID = '0x1338' +const MOCK_SELECTED_ADDRESS = '0x0101' +const SET_STATE_TIMEOUT = 10 + +function getEmptyInitState() { + return { incomingTransactions: {}, incomingTxLastFetchedBlocksByNetwork: { - [ROPSTEN]: null, - [RINKEBY]: null, - [KOVAN]: null, [GOERLI]: null, + [KOVAN]: null, [MAINNET]: null, + [RINKEBY]: null, + [ROPSTEN]: null, }, } +} - const NON_EMPTY_INIT_STATE = { +function getNonEmptyInitState() { + return { incomingTransactions: { '0x123456': { id: 777 }, }, incomingTxLastFetchedBlocksByNetwork: { - [ROPSTEN]: 1, - [RINKEBY]: 2, - [KOVAN]: 3, - [GOERLI]: 5, - [MAINNET]: 4, + [GOERLI]: 1, + [KOVAN]: 2, + [MAINNET]: 3, + [RINKEBY]: 5, + [ROPSTEN]: 4, }, } +} - const NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE = { - incomingTransactions: { - '0x123456': { id: 777 }, - }, - incomingTxLastFetchedBlocksByNetwork: { - [ROPSTEN]: 1, - [RINKEBY]: 2, - [KOVAN]: 3, - [GOERLI]: 5, - [MAINNET]: 4, - FAKE_NETWORK: 1111, - }, - } - - const MOCK_BLOCKTRACKER = { - addListener: sinon.spy(), - removeListener: sinon.spy(), - testProperty: 'fakeBlockTracker', - getCurrentBlock: () => '0xa', - } - - const MOCK_NETWORK_CONTROLLER = { - getProviderConfig: () => ({ type: 'FAKE_NETWORK' }), +function getMockNetworkController(chainId = FAKE_CHAIN_ID) { + return { + getCurrentChainId: () => chainId, on: sinon.spy(), } +} - const MOCK_PREFERENCES_CONTROLLER = { - getSelectedAddress: sinon.stub().returns('0x0101'), +function getMockPreferencesController({ + showIncomingTransactions = true, +} = {}) { + return { + getSelectedAddress: sinon.stub().returns(MOCK_SELECTED_ADDRESS), store: { getState: sinon.stub().returns({ featureFlags: { - showIncomingTransactions: true, + showIncomingTransactions, }, }), subscribe: sinon.spy(), }, } +} + +function getMockBlockTracker() { + return { + addListener: sinon.stub().callsArgWithAsync(1, '0xa'), + removeListener: sinon.spy(), + testProperty: 'fakeBlockTracker', + getCurrentBlock: () => '0xa', + } +} + +/** + * A transaction object in the format returned by the Etherscan API. + * + * Note that this is not an exhaustive type definiton; only the properties we use are defined + * + * @typedef {Object} EtherscanTransaction + * @property {string} blockNumber - The number of the block this transaction was found in, in decimal + * @property {string} from - The hex-prefixed address of the sender + * @property {string} gas - The gas limit, in decimal WEI + * @property {string} gasPrice - The gas price, in decimal WEI + * @property {string} hash - The hex-prefixed transaction hash + * @property {string} isError - Whether the transaction was confirmed or failed (0 for confirmed, 1 for failed) + * @property {string} nonce - The transaction nonce, in decimal + * @property {string} timeStamp - The timestamp for the transaction, in seconds + * @property {string} to - The hex-prefixed address of the recipient + * @property {string} value - The amount of ETH sent in this transaction, in decimal WEI + */ + +/** + * Returns a transaction object matching the expected format returned + * by the Etherscan API + * + * @param {string} [toAddress] - The hex-prefixed address of the recipient + * @param {number} [blockNumber] - The block number for the transaction + * @returns {EtherscanTransaction} + */ +const getFakeEtherscanTransaction = ( + toAddress = MOCK_SELECTED_ADDRESS, + blockNumber = 10, +) => { + return { + blockNumber: blockNumber.toString(), + from: '0xfake', + gas: '0', + gasPrice: '0', + hash: '0xfake', + isError: '0', + nonce: '100', + timeStamp: '16000000000000', + to: toAddress, + value: '0', + } +} + +describe('IncomingTransactionsController', function () { + afterEach(function () { + sinon.restore() + nock.cleanAll() + }) describe('constructor', function () { it('should set up correct store, listeners and properties in the constructor', function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: {}, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(), + preferencesController: getMockPreferencesController(), + initState: {}, + }, + ) sinon.spy(incomingTransactionsController, '_update') - assert.deepEqual(incomingTransactionsController.blockTracker, MOCK_BLOCKTRACKER) - assert.deepEqual(incomingTransactionsController.networkController, MOCK_NETWORK_CONTROLLER) - assert.equal(incomingTransactionsController.preferencesController, MOCK_PREFERENCES_CONTROLLER) - assert.equal(incomingTransactionsController.getCurrentNetwork(), 'FAKE_NETWORK') - - assert.deepEqual(incomingTransactionsController.store.getState(), EMPTY_INIT_STATE) + assert.deepEqual( + incomingTransactionsController.store.getState(), + getEmptyInitState(), + ) assert(incomingTransactionsController.networkController.on.calledOnce) - assert.equal(incomingTransactionsController.networkController.on.getCall(0).args[0], 'networkDidChange') - const networkControllerListenerCallback = incomingTransactionsController.networkController.on.getCall(0).args[1] + assert.equal( + incomingTransactionsController.networkController.on.getCall(0).args[0], + 'networkDidChange', + ) + const networkControllerListenerCallback = incomingTransactionsController.networkController.on.getCall( + 0, + ).args[1] assert.equal(incomingTransactionsController._update.callCount, 0) networkControllerListenerCallback('testNetworkType') assert.equal(incomingTransactionsController._update.callCount, 1) - assert.deepEqual(incomingTransactionsController._update.getCall(0).args[0], { - address: '0x0101', - networkType: 'testNetworkType', - }) + assert.deepEqual( + incomingTransactionsController._update.getCall(0).args[0], + { + address: '0x0101', + }, + ) incomingTransactionsController._update.resetHistory() }) it('should set the store to a provided initial state', function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) - assert.deepEqual(incomingTransactionsController.store.getState(), NON_EMPTY_INIT_STATE) + assert.deepEqual( + incomingTransactionsController.store.getState(), + getNonEmptyInitState(), + ) }) }) - describe('#start', function () { - it('should set up a listener for the latest block', function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: {}, - }) - sinon.spy(incomingTransactionsController, '_update') + describe('update events', function () { + it('should set up a listener for the latest block', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(), + preferencesController: getMockPreferencesController(), + initState: {}, + }, + ) incomingTransactionsController.start() assert(incomingTransactionsController.blockTracker.addListener.calledOnce) - assert.equal(incomingTransactionsController.blockTracker.addListener.getCall(0).args[0], 'latest') - const blockTrackerListenerCallback = incomingTransactionsController.blockTracker.addListener.getCall(0).args[1] - assert.equal(incomingTransactionsController._update.callCount, 0) - blockTrackerListenerCallback('0xabc') - assert.equal(incomingTransactionsController._update.callCount, 1) - assert.deepEqual(incomingTransactionsController._update.getCall(0).args[0], { - address: '0x0101', - newBlockNumberDec: 2748, - }) + assert.equal( + incomingTransactionsController.blockTracker.addListener.getCall(0) + .args[0], + 'latest', + ) + }) + + it('should update upon latest block when started and on supported network', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) + const startBlock = getNonEmptyInitState() + .incomingTxLastFetchedBlocksByNetwork[ROPSTEN] + nock('https://api-ropsten.etherscan.io') + .get( + `/api?module=account&action=txlist&address=${MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`, + ) + .reply( + 200, + JSON.stringify({ + status: '1', + result: [getFakeEtherscanTransaction()], + }), + ) + const updateStateStub = sinon.stub( + incomingTransactionsController.store, + 'updateState', + ) + const updateStateCalled = waitUntilCalled( + updateStateStub, + incomingTransactionsController.store, + ) + + incomingTransactionsController.start() + await updateStateCalled + + const actualState = incomingTransactionsController.store.getState() + const generatedTxId = actualState?.incomingTransactions?.['0xfake']?.id + + const actualStateWithoutGenerated = cloneDeep(actualState) + delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id + + assert.ok( + typeof generatedTxId === 'number' && generatedTxId > 0, + 'Generated transaction ID should be a positive number', + ) + assert.deepStrictEqual( + actualStateWithoutGenerated, + { + incomingTransactions: { + ...getNonEmptyInitState().incomingTransactions, + '0xfake': { + blockNumber: '10', + hash: '0xfake', + metamaskNetworkId: '3', + status: TRANSACTION_STATUSES.CONFIRMED, + time: 16000000000000000, + transactionCategory: TRANSACTION_CATEGORIES.INCOMING, + txParams: { + from: '0xfake', + gas: '0x0', + gasPrice: '0x0', + nonce: '0x64', + to: '0x0101', + value: '0x0', + }, + }, + }, + incomingTxLastFetchedBlocksByNetwork: { + ...getNonEmptyInitState().incomingTxLastFetchedBlocksByNetwork, + [ROPSTEN]: 11, + }, + }, + 'State should have been updated after first block was received', + ) + }) + + it('should not update upon latest block when started and not on supported network', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) + // reply with a valid request for any supported network, so that this test has every opportunity to fail + for (const network of [ + GOERLI, + KOVAN, + MAINNET, + RINKEBY, + ROPSTEN, + 'undefined', + ]) { + nock( + `https://api${ + network === MAINNET ? '' : `-${network.toLowerCase()}` + }.etherscan.io`, + ) + .get(/api.+/u) + .reply( + 200, + JSON.stringify({ + status: '1', + result: [getFakeEtherscanTransaction()], + }), + ) + } + const updateStateStub = sinon.stub( + incomingTransactionsController.store, + 'updateState', + ) + const updateStateCalled = waitUntilCalled( + updateStateStub, + incomingTransactionsController.store, + ) + const putStateStub = sinon.stub( + incomingTransactionsController.store, + 'putState', + ) + const putStateCalled = waitUntilCalled( + putStateStub, + incomingTransactionsController.store, + ) + + incomingTransactionsController.start() + + try { + await Promise.race([ + updateStateCalled, + putStateCalled, + new Promise((_, reject) => { + setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT) + }), + ]) + assert.fail('Update state should not have been called') + } catch (error) { + assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown') + } + }) + + it('should not update upon latest block when started and incoming transactions disabled', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(), + preferencesController: getMockPreferencesController({ + showIncomingTransactions: false, + }), + initState: getNonEmptyInitState(), + }, + ) + // reply with a valid request for any supported network, so that this test has every opportunity to fail + for (const network of [ + GOERLI, + KOVAN, + MAINNET, + RINKEBY, + ROPSTEN, + 'undefined', + ]) { + nock( + `https://api${ + network === MAINNET ? '' : `-${network.toLowerCase()}` + }.etherscan.io`, + ) + .get(/api.+/u) + .reply( + 200, + JSON.stringify({ + status: '1', + result: [getFakeEtherscanTransaction()], + }), + ) + } + const updateStateStub = sinon.stub( + incomingTransactionsController.store, + 'updateState', + ) + const updateStateCalled = waitUntilCalled( + updateStateStub, + incomingTransactionsController.store, + ) + const putStateStub = sinon.stub( + incomingTransactionsController.store, + 'putState', + ) + const putStateCalled = waitUntilCalled( + putStateStub, + incomingTransactionsController.store, + ) + + incomingTransactionsController.start() + + try { + await Promise.race([ + updateStateCalled, + putStateCalled, + new Promise((_, reject) => { + setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT) + }), + ]) + assert.fail('Update state should not have been called') + } catch (error) { + assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown') + } + }) + + it('should not update upon latest block when not started', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) + // reply with a valid request for any supported network, so that this test has every opportunity to fail + for (const network of [ + GOERLI, + KOVAN, + MAINNET, + RINKEBY, + ROPSTEN, + 'undefined', + ]) { + nock( + `https://api${ + network === MAINNET ? '' : `-${network.toLowerCase()}` + }.etherscan.io`, + ) + .get(/api.+/u) + .reply( + 200, + JSON.stringify({ + status: '1', + result: [getFakeEtherscanTransaction()], + }), + ) + } + const updateStateStub = sinon.stub( + incomingTransactionsController.store, + 'updateState', + ) + const updateStateCalled = waitUntilCalled( + updateStateStub, + incomingTransactionsController.store, + ) + const putStateStub = sinon.stub( + incomingTransactionsController.store, + 'putState', + ) + const putStateCalled = waitUntilCalled( + putStateStub, + incomingTransactionsController.store, + ) + + try { + await Promise.race([ + updateStateCalled, + putStateCalled, + new Promise((_, reject) => { + setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT) + }), + ]) + assert.fail('Update state should not have been called') + } catch (error) { + assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown') + } + }) + + it('should not update upon latest block when stopped', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) + // reply with a valid request for any supported network, so that this test has every opportunity to fail + for (const network of [ + GOERLI, + KOVAN, + MAINNET, + RINKEBY, + ROPSTEN, + 'undefined', + ]) { + nock( + `https://api${ + network === MAINNET ? '' : `-${network.toLowerCase()}` + }.etherscan.io`, + ) + .get(/api.+/u) + .reply( + 200, + JSON.stringify({ + status: '1', + result: [getFakeEtherscanTransaction()], + }), + ) + } + const updateStateStub = sinon.stub( + incomingTransactionsController.store, + 'updateState', + ) + const updateStateCalled = waitUntilCalled( + updateStateStub, + incomingTransactionsController.store, + ) + const putStateStub = sinon.stub( + incomingTransactionsController.store, + 'putState', + ) + const putStateCalled = waitUntilCalled( + putStateStub, + incomingTransactionsController.store, + ) + + incomingTransactionsController.stop() + + try { + await Promise.race([ + updateStateCalled, + putStateCalled, + new Promise((_, reject) => { + setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT) + }), + ]) + assert.fail('Update state should not have been called') + } catch (error) { + assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown') + } + }) + + it('should update when the selected address changes and on supported network', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) + const NEW_MOCK_SELECTED_ADDRESS = `${MOCK_SELECTED_ADDRESS}9` + const startBlock = getNonEmptyInitState() + .incomingTxLastFetchedBlocksByNetwork[ROPSTEN] + nock('https://api-ropsten.etherscan.io') + .get( + `/api?module=account&action=txlist&address=${NEW_MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`, + ) + .reply( + 200, + JSON.stringify({ + status: '1', + result: [getFakeEtherscanTransaction(NEW_MOCK_SELECTED_ADDRESS)], + }), + ) + const updateStateStub = sinon.stub( + incomingTransactionsController.store, + 'updateState', + ) + const updateStateCalled = waitUntilCalled( + updateStateStub, + incomingTransactionsController.store, + ) + + const subscription = incomingTransactionsController.preferencesController.store.subscribe.getCall( + 1, + ).args[0] + // The incoming transactions controller will always skip the first event + // We need to call subscription twice to test the event handling + // TODO: stop skipping the first event + await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS }) + await subscription({ selectedAddress: NEW_MOCK_SELECTED_ADDRESS }) + await updateStateCalled + + const actualState = incomingTransactionsController.store.getState() + const generatedTxId = actualState?.incomingTransactions?.['0xfake']?.id + + const actualStateWithoutGenerated = cloneDeep(actualState) + delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id + + assert.ok( + typeof generatedTxId === 'number' && generatedTxId > 0, + 'Generated transaction ID should be a positive number', + ) + assert.deepStrictEqual( + actualStateWithoutGenerated, + { + incomingTransactions: { + ...getNonEmptyInitState().incomingTransactions, + '0xfake': { + blockNumber: '10', + hash: '0xfake', + metamaskNetworkId: '3', + status: TRANSACTION_STATUSES.CONFIRMED, + time: 16000000000000000, + transactionCategory: TRANSACTION_CATEGORIES.INCOMING, + txParams: { + from: '0xfake', + gas: '0x0', + gasPrice: '0x0', + nonce: '0x64', + to: '0x01019', + value: '0x0', + }, + }, + }, + incomingTxLastFetchedBlocksByNetwork: { + ...getNonEmptyInitState().incomingTxLastFetchedBlocksByNetwork, + [ROPSTEN]: 11, + }, + }, + 'State should have been updated after first block was received', + ) + }) + + it('should not update when the selected address changes and not on supported network', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: { ...getMockBlockTracker() }, + networkController: getMockNetworkController(), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) + const NEW_MOCK_SELECTED_ADDRESS = `${MOCK_SELECTED_ADDRESS}9` + // reply with a valid request for any supported network, so that this test has every opportunity to fail + for (const network of [ + GOERLI, + KOVAN, + MAINNET, + RINKEBY, + ROPSTEN, + 'undefined', + ]) { + nock( + `https://api${ + network === MAINNET ? '' : `-${network.toLowerCase()}` + }.etherscan.io`, + ) + .get(/api.+/u) + .reply( + 200, + JSON.stringify({ + status: '1', + result: [getFakeEtherscanTransaction(NEW_MOCK_SELECTED_ADDRESS)], + }), + ) + } + const updateStateStub = sinon.stub( + incomingTransactionsController.store, + 'updateState', + ) + const updateStateCalled = waitUntilCalled( + updateStateStub, + incomingTransactionsController.store, + ) + const putStateStub = sinon.stub( + incomingTransactionsController.store, + 'putState', + ) + const putStateCalled = waitUntilCalled( + putStateStub, + incomingTransactionsController.store, + ) + + const subscription = incomingTransactionsController.preferencesController.store.subscribe.getCall( + 1, + ).args[0] + // The incoming transactions controller will always skip the first event + // We need to call subscription twice to test the event handling + // TODO: stop skipping the first event + await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS }) + await subscription({ selectedAddress: NEW_MOCK_SELECTED_ADDRESS }) + + try { + await Promise.race([ + updateStateCalled, + putStateCalled, + new Promise((_, reject) => { + setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT) + }), + ]) + assert.fail('Update state should not have been called') + } catch (error) { + assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown') + } + }) + + it('should update when switching to a supported network', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) + const startBlock = getNonEmptyInitState() + .incomingTxLastFetchedBlocksByNetwork[ROPSTEN] + nock('https://api-ropsten.etherscan.io') + .get( + `/api?module=account&action=txlist&address=${MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`, + ) + .reply( + 200, + JSON.stringify({ + status: '1', + result: [getFakeEtherscanTransaction()], + }), + ) + const updateStateStub = sinon.stub( + incomingTransactionsController.store, + 'updateState', + ) + const updateStateCalled = waitUntilCalled( + updateStateStub, + incomingTransactionsController.store, + ) + + const subscription = incomingTransactionsController.networkController.on.getCall( + 0, + ).args[1] + incomingTransactionsController.networkController = getMockNetworkController( + ROPSTEN_CHAIN_ID, + ) + await subscription(ROPSTEN) + await updateStateCalled + + const actualState = incomingTransactionsController.store.getState() + const generatedTxId = actualState?.incomingTransactions?.['0xfake']?.id + + const actualStateWithoutGenerated = cloneDeep(actualState) + delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id + + assert.ok( + typeof generatedTxId === 'number' && generatedTxId > 0, + 'Generated transaction ID should be a positive number', + ) + assert.deepStrictEqual( + actualStateWithoutGenerated, + { + incomingTransactions: { + ...getNonEmptyInitState().incomingTransactions, + '0xfake': { + blockNumber: '10', + hash: '0xfake', + metamaskNetworkId: '3', + status: TRANSACTION_STATUSES.CONFIRMED, + time: 16000000000000000, + transactionCategory: TRANSACTION_CATEGORIES.INCOMING, + txParams: { + from: '0xfake', + gas: '0x0', + gasPrice: '0x0', + nonce: '0x64', + to: '0x0101', + value: '0x0', + }, + }, + }, + incomingTxLastFetchedBlocksByNetwork: { + ...getNonEmptyInitState().incomingTxLastFetchedBlocksByNetwork, + [ROPSTEN]: 11, + }, + }, + 'State should have been updated after first block was received', + ) + }) + + it('should not update when switching to an unsupported network', async function () { + const networkController = getMockNetworkController(ROPSTEN_CHAIN_ID) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController, + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) + // reply with a valid request for any supported network, so that this test has every opportunity to fail + for (const network of [ + GOERLI, + KOVAN, + MAINNET, + RINKEBY, + ROPSTEN, + 'undefined', + ]) { + nock( + `https://api${ + network === MAINNET ? '' : `-${network.toLowerCase()}` + }.etherscan.io`, + ) + .get(/api.+/u) + .reply( + 200, + JSON.stringify({ + status: '1', + result: [getFakeEtherscanTransaction()], + }), + ) + } + const updateStateStub = sinon.stub( + incomingTransactionsController.store, + 'updateState', + ) + const updateStateCalled = waitUntilCalled( + updateStateStub, + incomingTransactionsController.store, + ) + const putStateStub = sinon.stub( + incomingTransactionsController.store, + 'putState', + ) + const putStateCalled = waitUntilCalled( + putStateStub, + incomingTransactionsController.store, + ) + + const subscription = incomingTransactionsController.networkController.on.getCall( + 0, + ).args[1] + + networkController.getCurrentChainId = () => FAKE_CHAIN_ID + await subscription() + + try { + await Promise.race([ + updateStateCalled, + putStateCalled, + new Promise((_, reject) => { + setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT) + }), + ]) + assert.fail('Update state should not have been called') + } catch (error) { + assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown') + } }) }) describe('_getDataForUpdate', function () { it('should call fetchAll with the correct params when passed a new block number and the current network has no stored block', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) - incomingTransactionsController._fetchAll = sinon.stub().returns({}) - - await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 }) - - assert(incomingTransactionsController._fetchAll.calledOnce) - - assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [ - 'fakeAddress', 999, 'FAKE_NETWORK', - ]) - }) - - it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE, - }) - incomingTransactionsController._fetchAll = sinon.stub().returns({}) - - await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 }) - - assert(incomingTransactionsController._fetchAll.calledOnce) - - assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [ - 'fakeAddress', 1111, 'FAKE_NETWORK', - ]) - }) - - it('should call fetchAll with the correct params when passed a new network type but no block info exists', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getEmptyInitState(), + }, + ) incomingTransactionsController._fetchAll = sinon.stub().returns({}) await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', - networkType: 'NEW_FAKE_NETWORK', + chainId: ROPSTEN_CHAIN_ID, + newBlockNumberDec: 999, }) assert(incomingTransactionsController._fetchAll.calledOnce) - assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [ - 'fakeAddress', 10, 'NEW_FAKE_NETWORK', - ]) + assert.deepEqual( + incomingTransactionsController._fetchAll.getCall(0).args, + ['fakeAddress', 999, ROPSTEN_CHAIN_ID], + ) + }) + + it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) + incomingTransactionsController._fetchAll = sinon.stub().returns({}) + + await incomingTransactionsController._getDataForUpdate({ + address: 'fakeAddress', + chainId: ROPSTEN_CHAIN_ID, + newBlockNumberDec: 999, + }) + + assert(incomingTransactionsController._fetchAll.calledOnce) + + assert.deepEqual( + incomingTransactionsController._fetchAll.getCall(0).args, + ['fakeAddress', 4, ROPSTEN_CHAIN_ID], + ) }) it('should return the expected data', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) incomingTransactionsController._fetchAll = sinon.stub().returns({ latestIncomingTxBlockNumber: 444, txs: [{ id: 555 }], @@ -211,7 +928,7 @@ describe('IncomingTransactionsController', function () { const result = await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', - networkType: 'FAKE_NETWORK', + chainId: ROPSTEN_CHAIN_ID, }) assert.deepEqual(result, { @@ -221,15 +938,14 @@ describe('IncomingTransactionsController', function () { '0x123456': { id: 777 }, }, currentBlocksByNetwork: { - [ROPSTEN]: 1, - [RINKEBY]: 2, - [KOVAN]: 3, - [GOERLI]: 5, - [MAINNET]: 4, - FAKE_NETWORK: 1111, + [GOERLI]: 1, + [KOVAN]: 2, + [MAINNET]: 3, + [RINKEBY]: 5, + [ROPSTEN]: 4, }, - fetchedBlockNumber: 1111, - network: 'FAKE_NETWORK', + fetchedBlockNumber: 4, + chainId: ROPSTEN_CHAIN_ID, }) }) }) @@ -241,15 +957,14 @@ describe('IncomingTransactionsController', function () { '0x123456': { id: 777, hash: '0x123456' }, }, currentBlocksByNetwork: { - [ROPSTEN]: 1, - [RINKEBY]: 2, - [KOVAN]: 3, - [GOERLI]: 5, - [MAINNET]: 4, - FAKE_NETWORK: 1111, + [GOERLI]: 1, + [KOVAN]: 2, + [MAINNET]: 3, + [RINKEBY]: 5, + [ROPSTEN]: 4, }, fetchedBlockNumber: 1111, - network: 'FAKE_NETWORK', + chainId: ROPSTEN_CHAIN_ID, } const MOCK_INPUT_WITH_LASTEST = { @@ -258,60 +973,76 @@ describe('IncomingTransactionsController', function () { } it('should update state with correct blockhash and transactions when passed a truthy latestIncomingTxBlockNumber', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) sinon.spy(incomingTransactionsController.store, 'updateState') - await incomingTransactionsController._updateStateWithNewTxData(MOCK_INPUT_WITH_LASTEST) + await incomingTransactionsController._updateStateWithNewTxData( + MOCK_INPUT_WITH_LASTEST, + ) assert(incomingTransactionsController.store.updateState.calledOnce) - assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], { - incomingTxLastFetchedBlocksByNetwork: { - ...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork, - 'FAKE_NETWORK': 445, + assert.deepEqual( + incomingTransactionsController.store.updateState.getCall(0).args[0], + { + incomingTxLastFetchedBlocksByNetwork: { + ...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork, + [ROPSTEN]: 445, + }, + incomingTransactions: { + '0x123456': { id: 777, hash: '0x123456' }, + '0xfff': { id: 555, hash: '0xfff' }, + }, }, - incomingTransactions: { - '0x123456': { id: 777, hash: '0x123456' }, - '0xfff': { id: 555, hash: '0xfff' }, - }, - }) + ) }) it('should update state with correct blockhash and transactions when passed a falsy latestIncomingTxBlockNumber', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) sinon.spy(incomingTransactionsController.store, 'updateState') - await incomingTransactionsController._updateStateWithNewTxData(MOCK_INPUT_WITHOUT_LASTEST) + await incomingTransactionsController._updateStateWithNewTxData( + MOCK_INPUT_WITHOUT_LASTEST, + ) assert(incomingTransactionsController.store.updateState.calledOnce) - assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], { - incomingTxLastFetchedBlocksByNetwork: { - ...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork, - 'FAKE_NETWORK': 1112, + assert.deepEqual( + incomingTransactionsController.store.updateState.getCall(0).args[0], + { + incomingTxLastFetchedBlocksByNetwork: { + ...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork, + [ROPSTEN]: 1112, + }, + incomingTransactions: { + '0x123456': { id: 777, hash: '0x123456' }, + '0xfff': { id: 555, hash: '0xfff' }, + }, }, - incomingTransactions: { - '0x123456': { id: 777, hash: '0x123456' }, - '0xfff': { id: 555, hash: '0xfff' }, - }, - }) + ) }) }) describe('_fetchTxs', function () { - const mockFetch = sinon.stub().returns(Promise.resolve({ - json: () => Promise.resolve({ someKey: 'someValue' }), - })) + const mockFetch = sinon.stub().returns( + Promise.resolve({ + json: () => Promise.resolve({ someKey: 'someValue' }), + }), + ) let tempFetch beforeEach(function () { tempFetch = window.fetch @@ -324,88 +1055,109 @@ describe('IncomingTransactionsController', function () { }) it('should call fetch with the expected url when passed an address, block number and supported network', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) - await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN) + await incomingTransactionsController._fetchTxs( + '0xfakeaddress', + '789', + ROPSTEN_CHAIN_ID, + ) assert(mockFetch.calledOnce) - assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`) + assert.equal( + mockFetch.getCall(0).args[0], + `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`, + ) }) it('should call fetch with the expected url when passed an address, block number and MAINNET', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(MAINNET_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) - await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', MAINNET) + await incomingTransactionsController._fetchTxs( + '0xfakeaddress', + '789', + MAINNET_CHAIN_ID, + ) assert(mockFetch.calledOnce) - assert.equal(mockFetch.getCall(0).args[0], `https://api.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`) + assert.equal( + mockFetch.getCall(0).args[0], + `https://api.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`, + ) }) it('should call fetch with the expected url when passed an address and supported network, but a falsy block number', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) - await incomingTransactionsController._fetchTxs('0xfakeaddress', null, ROPSTEN) + await incomingTransactionsController._fetchTxs( + '0xfakeaddress', + null, + ROPSTEN_CHAIN_ID, + ) assert(mockFetch.calledOnce) - assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`) - }) - - it('should not fetch and return an empty object when passed an unsported network', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) - - const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', null, 'UNSUPPORTED_NETWORK') - - assert(mockFetch.notCalled) - assert.deepEqual(result, {}) + assert.equal( + mockFetch.getCall(0).args[0], + `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`, + ) }) it('should return the results from the fetch call, plus the address and currentNetworkID, when passed an address, block number and supported network', async function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) - const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN) + const result = await incomingTransactionsController._fetchTxs( + '0xfakeaddress', + '789', + ROPSTEN_CHAIN_ID, + ) assert(mockFetch.calledOnce) assert.deepEqual(result, { someKey: 'someValue', address: '0xfakeaddress', - currentNetworkID: '3', + chainId: ROPSTEN_CHAIN_ID, }) }) }) describe('_processTxFetchResponse', function () { it('should return a null block number and empty tx array if status is 0', function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) const result = incomingTransactionsController._processTxFetchResponse({ status: '0', @@ -420,12 +1172,14 @@ describe('IncomingTransactionsController', function () { }) it('should return a null block number and empty tx array if the passed result array is empty', function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) const result = incomingTransactionsController._processTxFetchResponse({ status: '1', @@ -440,23 +1194,25 @@ describe('IncomingTransactionsController', function () { }) it('should return the expected block number and tx list when passed data from a successful fetch', function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) - incomingTransactionsController._normalizeTxFromEtherscan = (tx, currentNetworkID) => ({ + incomingTransactionsController._normalizeTxFromEtherscan = (tx) => ({ ...tx, - currentNetworkID, + currentNetworkID: ROPSTEN_NETWORK_ID, normalized: true, }) const result = incomingTransactionsController._processTxFetchResponse({ status: '1', address: '0xfakeaddress', - currentNetworkID: 'FAKE_NETWORK', + chainId: ROPSTEN_CHAIN_ID, result: [ { hash: '0xabc123', @@ -520,7 +1276,7 @@ describe('IncomingTransactionsController', function () { blockNumber: 5000, time: 9, normalized: true, - currentNetworkID: 'FAKE_NETWORK', + currentNetworkID: ROPSTEN_NETWORK_ID, }, { hash: '0xabc123', @@ -530,7 +1286,7 @@ describe('IncomingTransactionsController', function () { blockNumber: 5000, time: 10, normalized: true, - currentNetworkID: 'FAKE_NETWORK', + currentNetworkID: ROPSTEN_NETWORK_ID, }, { hash: '0xabc12345', @@ -540,7 +1296,7 @@ describe('IncomingTransactionsController', function () { blockNumber: 5001, time: 11, normalized: true, - currentNetworkID: 'FAKE_NETWORK', + currentNetworkID: ROPSTEN_NETWORK_ID, }, { hash: '0xabc123456', @@ -550,7 +1306,7 @@ describe('IncomingTransactionsController', function () { blockNumber: 5001, time: 12, normalized: true, - currentNetworkID: 'FAKE_NETWORK', + currentNetworkID: ROPSTEN_NETWORK_ID, }, ], }) @@ -559,31 +1315,36 @@ describe('IncomingTransactionsController', function () { describe('_normalizeTxFromEtherscan', function () { it('should return the expected data when the tx is in error', function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) - const result = incomingTransactionsController._normalizeTxFromEtherscan({ - timeStamp: '4444', - isError: '1', - blockNumber: 333, - from: '0xa', - gas: '11', - gasPrice: '12', - nonce: '13', - to: '0xe', - value: '15', - hash: '0xg', - }, 'FAKE_NETWORK') + const result = incomingTransactionsController._normalizeTxFromEtherscan( + { + timeStamp: '4444', + isError: '1', + blockNumber: 333, + from: '0xa', + gas: '11', + gasPrice: '12', + nonce: '13', + to: '0xe', + value: '15', + hash: '0xg', + }, + ROPSTEN_CHAIN_ID, + ) assert.deepEqual(result, { blockNumber: 333, id: 54321, - metamaskNetworkId: 'FAKE_NETWORK', - status: 'failed', + metamaskNetworkId: ROPSTEN_NETWORK_ID, + status: TRANSACTION_STATUSES.FAILED, time: 4444000, txParams: { from: '0xa', @@ -594,36 +1355,41 @@ describe('IncomingTransactionsController', function () { value: '0xf', }, hash: '0xg', - transactionCategory: 'incoming', + transactionCategory: TRANSACTION_CATEGORIES.INCOMING, }) }) it('should return the expected data when the tx is not in error', function () { - const incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: MOCK_BLOCKTRACKER, - networkController: MOCK_NETWORK_CONTROLLER, - preferencesController: MOCK_PREFERENCES_CONTROLLER, - initState: NON_EMPTY_INIT_STATE, - }) + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + networkController: getMockNetworkController(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ) - const result = incomingTransactionsController._normalizeTxFromEtherscan({ - timeStamp: '4444', - isError: '0', - blockNumber: 333, - from: '0xa', - gas: '11', - gasPrice: '12', - nonce: '13', - to: '0xe', - value: '15', - hash: '0xg', - }, 'FAKE_NETWORK') + const result = incomingTransactionsController._normalizeTxFromEtherscan( + { + timeStamp: '4444', + isError: '0', + blockNumber: 333, + from: '0xa', + gas: '11', + gasPrice: '12', + nonce: '13', + to: '0xe', + value: '15', + hash: '0xg', + }, + ROPSTEN_CHAIN_ID, + ) assert.deepEqual(result, { blockNumber: 333, id: 54321, - metamaskNetworkId: 'FAKE_NETWORK', - status: 'confirmed', + metamaskNetworkId: ROPSTEN_NETWORK_ID, + status: TRANSACTION_STATUSES.CONFIRMED, time: 4444000, txParams: { from: '0xa', @@ -634,7 +1400,7 @@ describe('IncomingTransactionsController', function () { value: '0xf', }, hash: '0xg', - transactionCategory: 'incoming', + transactionCategory: TRANSACTION_CATEGORIES.INCOMING, }) }) }) diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 988665544..2a7978293 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -8,6 +8,8 @@ import EthQuery from 'eth-query' import proxyquire from 'proxyquire' import firstTimeState from '../../localhostState' import createTxMeta from '../../../lib/createTxMeta' +import { addHexPrefix } from '../../../../app/scripts/lib/util' +import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction' const threeBoxSpies = { init: sinon.stub(), @@ -17,7 +19,7 @@ const threeBoxSpies = { } class ThreeBoxControllerMock { - constructor () { + constructor() { this.store = { subscribe: () => undefined, getState: () => ({}), @@ -61,19 +63,24 @@ const createLoggerMiddlewareMock = () => (req, res, next) => { next() } -const MetaMaskController = proxyquire('../../../../app/scripts/metamask-controller', { - './controllers/threebox': { default: ThreeBoxControllerMock }, - './lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock }, -}).default +const MetaMaskController = proxyquire( + '../../../../app/scripts/metamask-controller', + { + './controllers/threebox': { default: ThreeBoxControllerMock }, + './lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock }, + }, +).default const currentNetworkId = '42' const DEFAULT_LABEL = 'Account 1' const DEFAULT_LABEL_2 = 'Account 2' -const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' +const TEST_SEED = + 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' const TEST_ADDRESS_2 = '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b' const TEST_ADDRESS_3 = '0xeb9e64b93097bc15f01f13eae97015c57ab64823' -const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle' +const TEST_SEED_ALT = + 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle' const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' const CUSTOM_RPC_URL = 'http://localhost:8545' const CUSTOM_RPC_CHAIN_ID = '0x539' @@ -84,7 +91,6 @@ describe('MetaMaskController', function () { const noop = () => undefined beforeEach(function () { - nock('https://min-api.cryptocompare.com') .persist() .get(/.*/u) @@ -94,23 +100,32 @@ describe('MetaMaskController', function () { showUnapprovedTx: noop, showUnconfirmedMessage: noop, encryptor: { - encrypt (_, object) { + encrypt(_, object) { this.object = object return Promise.resolve('mock-encrypted') }, - decrypt () { + decrypt() { return Promise.resolve(this.object) }, }, initState: cloneDeep(firstTimeState), - platform: { showTransactionNotification: () => undefined, getVersion: () => 'foo' }, + platform: { + showTransactionNotification: () => undefined, + getVersion: () => 'foo', + }, extension: ExtensionizerMock, infuraProjectId: 'foo', }) // add sinon method spies - sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain') - sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore') + sandbox.spy( + metamaskController.keyringController, + 'createNewVaultAndKeychain', + ) + sandbox.spy( + metamaskController.keyringController, + 'createNewVaultAndRestore', + ) }) afterEach(function () { @@ -123,37 +138,47 @@ describe('MetaMaskController', function () { const password = 'a-fake-password' await metamaskController.createNewVaultAndRestore(password, TEST_SEED) - metamaskController.networkController._baseProviderParams.getAccounts((err, res) => { - assert.ifError(err) - assert.equal(res.length, 1) - assert.equal(res[0], '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') - }) + metamaskController.networkController._baseProviderParams.getAccounts( + (err, res) => { + assert.ifError(err) + assert.equal(res.length, 1) + assert.equal(res[0], '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') + }, + ) }) }) describe('#importAccountWithStrategy', function () { - const importPrivkey = '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' + const importPrivkey = + '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' beforeEach(async function () { const password = 'a-fake-password' await metamaskController.createNewVaultAndRestore(password, TEST_SEED) - await metamaskController.importAccountWithStrategy('Private Key', [importPrivkey]) + await metamaskController.importAccountWithStrategy('Private Key', [ + importPrivkey, + ]) }) it('adds private key to keyrings in KeyringController', async function () { - const simpleKeyrings = metamaskController.keyringController.getKeyringsByType('Simple Key Pair') + const simpleKeyrings = metamaskController.keyringController.getKeyringsByType( + 'Simple Key Pair', + ) const privKeyBuffer = simpleKeyrings[0].wallets[0]._privKey const pubKeyBuffer = simpleKeyrings[0].wallets[0]._pubKey const addressBuffer = ethUtil.pubToAddress(pubKeyBuffer) const privKey = ethUtil.bufferToHex(privKeyBuffer) const pubKey = ethUtil.bufferToHex(addressBuffer) - assert.equal(privKey, ethUtil.addHexPrefix(importPrivkey)) + assert.equal(privKey, addHexPrefix(importPrivkey)) assert.equal(pubKey, '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc') }) it('adds 1 account', async function () { const keyringAccounts = await metamaskController.keyringController.getAccounts() - assert.equal(keyringAccounts[keyringAccounts.length - 1], '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc') + assert.equal( + keyringAccounts[keyringAccounts.length - 1], + '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', + ) }) }) @@ -171,15 +196,23 @@ describe('MetaMaskController', function () { metamaskController.preferencesController.addAddresses([fakeAddress]) await metamaskController.submitPassword(password) - const identities = Object.keys(metamaskController.preferencesController.store.getState().identities) + const identities = Object.keys( + metamaskController.preferencesController.store.getState().identities, + ) const addresses = await metamaskController.keyringController.getAccounts() identities.forEach((identity) => { - assert.ok(addresses.includes(identity), `addresses should include all IDs: ${identity}`) + assert.ok( + addresses.includes(identity), + `addresses should include all IDs: ${identity}`, + ) }) addresses.forEach((address) => { - assert.ok(identities.includes(address), `identities should include all Addresses: ${address}`) + assert.ok( + identities.includes(address), + `identities should include all Addresses: ${address}`, + ) }) }) @@ -210,7 +243,10 @@ describe('MetaMaskController', function () { await metamaskController.createNewVaultAndKeychain(password) await metamaskController.createNewVaultAndKeychain(password) - assert(metamaskController.keyringController.createNewVaultAndKeychain.calledOnce) + assert( + metamaskController.keyringController.createNewVaultAndKeychain + .calledOnce, + ) selectStub.reset() }) @@ -224,10 +260,15 @@ describe('MetaMaskController', function () { return Promise.resolve('0x0') }) - await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch(() => null) + await metamaskController + .createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)) + .catch(() => null) await metamaskController.createNewVaultAndRestore(password, TEST_SEED) - assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice) + assert( + metamaskController.keyringController.createNewVaultAndRestore + .calledTwice, + ) }) it('should clear previous identities after vault restoration', async function () { @@ -240,12 +281,12 @@ describe('MetaMaskController', function () { await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) let endTime = Date.now() - const firstVaultIdentities = cloneDeep(metamaskController.getState().identities) + const firstVaultIdentities = cloneDeep( + metamaskController.getState().identities, + ) assert.ok( - ( - firstVaultIdentities[TEST_ADDRESS].lastSelected >= startTime && - firstVaultIdentities[TEST_ADDRESS].lastSelected <= endTime - ), + firstVaultIdentities[TEST_ADDRESS].lastSelected >= startTime && + firstVaultIdentities[TEST_ADDRESS].lastSelected <= endTime, `'${firstVaultIdentities[TEST_ADDRESS].lastSelected}' expected to be between '${startTime}' and '${endTime}'`, ) delete firstVaultIdentities[TEST_ADDRESS].lastSelected @@ -253,24 +294,32 @@ describe('MetaMaskController', function () { [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, }) - await metamaskController.preferencesController.setAccountLabel(TEST_ADDRESS, 'Account Foo') + await metamaskController.preferencesController.setAccountLabel( + TEST_ADDRESS, + 'Account Foo', + ) - const labelledFirstVaultIdentities = cloneDeep(metamaskController.getState().identities) + const labelledFirstVaultIdentities = cloneDeep( + metamaskController.getState().identities, + ) delete labelledFirstVaultIdentities[TEST_ADDRESS].lastSelected assert.deepEqual(labelledFirstVaultIdentities, { [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' }, }) startTime = Date.now() - await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) + await metamaskController.createNewVaultAndRestore( + 'foobar1337', + TEST_SEED_ALT, + ) endTime = Date.now() - const secondVaultIdentities = cloneDeep(metamaskController.getState().identities) + const secondVaultIdentities = cloneDeep( + metamaskController.getState().identities, + ) assert.ok( - ( - secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected >= startTime && - secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected <= endTime - ), + secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected >= startTime && + secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected <= endTime, `'${secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected}' expected to be between '${startTime}' and '${endTime}'`, ) delete secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected @@ -295,7 +344,10 @@ describe('MetaMaskController', function () { await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) const identities = cloneDeep(metamaskController.getState().identities) - assert.ok(identities[TEST_ADDRESS].lastSelected >= startTime && identities[TEST_ADDRESS].lastSelected <= Date.now()) + assert.ok( + identities[TEST_ADDRESS].lastSelected >= startTime && + identities[TEST_ADDRESS].lastSelected <= Date.now(), + ) delete identities[TEST_ADDRESS].lastSelected assert.deepEqual(identities, { [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, @@ -350,16 +402,20 @@ describe('MetaMaskController', function () { }) describe('preferencesController', function () { - it('defaults useBlockie to false', function () { - assert.equal(metamaskController.preferencesController.store.getState().useBlockie, false) + assert.equal( + metamaskController.preferencesController.store.getState().useBlockie, + false, + ) }) it('setUseBlockie to true', function () { metamaskController.setUseBlockie(true, noop) - assert.equal(metamaskController.preferencesController.store.getState().useBlockie, true) + assert.equal( + metamaskController.preferencesController.store.getState().useBlockie, + true, + ) }) - }) describe('#selectFirstIdentity', function () { @@ -370,11 +426,11 @@ describe('MetaMaskController', function () { identities = { '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { address, - 'name': 'Account 1', + name: 'Account 1', }, '0xc42edfcc21ed14dda456aa0756c153f7985d8813': { - 'address': '0xc42edfcc21ed14dda456aa0756c153f7985d8813', - 'name': 'Account 2', + address: '0xc42edfcc21ed14dda456aa0756c153f7985d8813', + name: 'Account 2', }, } metamaskController.preferencesController.store.updateState({ identities }) @@ -393,12 +449,18 @@ describe('MetaMaskController', function () { }) describe('connectHardware', function () { - it('should throw if it receives an unknown device name', async function () { try { - await metamaskController.connectHardware('Some random device name', 0, `m/44/0'/0'`) + await metamaskController.connectHardware( + 'Some random device name', + 0, + `m/44/0'/0'`, + ) } catch (e) { - assert.equal(e, 'Error: MetamaskController:getKeyringForDevice - Unknown device') + assert.equal( + e, + 'Error: MetamaskController:getKeyringForDevice - Unknown device', + ) } }) @@ -408,7 +470,10 @@ describe('MetaMaskController', function () { const keyrings = await metamaskController.keyringController.getKeyringsByType( 'Trezor Hardware', ) - assert.equal(metamaskController.keyringController.addNewKeyring.getCall(0).args, 'Trezor Hardware') + assert.equal( + metamaskController.keyringController.addNewKeyring.getCall(0).args, + 'Trezor Hardware', + ) assert.equal(keyrings.length, 1) }) @@ -418,18 +483,26 @@ describe('MetaMaskController', function () { const keyrings = await metamaskController.keyringController.getKeyringsByType( 'Ledger Hardware', ) - assert.equal(metamaskController.keyringController.addNewKeyring.getCall(0).args, 'Ledger Hardware') + assert.equal( + metamaskController.keyringController.addNewKeyring.getCall(0).args, + 'Ledger Hardware', + ) assert.equal(keyrings.length, 1) }) - }) describe('checkHardwareStatus', function () { it('should throw if it receives an unknown device name', async function () { try { - await metamaskController.checkHardwareStatus('Some random device name', `m/44/0'/0'`) + await metamaskController.checkHardwareStatus( + 'Some random device name', + `m/44/0'/0'`, + ) } catch (e) { - assert.equal(e, 'Error: MetamaskController:getKeyringForDevice - Unknown device') + assert.equal( + e, + 'Error: MetamaskController:getKeyringForDevice - Unknown device', + ) } }) @@ -445,7 +518,10 @@ describe('MetaMaskController', function () { try { await metamaskController.forgetDevice('Some random device name') } catch (e) { - assert.equal(e, 'Error: MetamaskController:getKeyringForDevice - Unknown device') + assert.equal( + e, + 'Error: MetamaskController:getKeyringForDevice - Unknown device', + ) } }) @@ -472,10 +548,16 @@ describe('MetaMaskController', function () { windowOpenStub = sinon.stub(window, 'open') windowOpenStub.returns(noop) - addNewAccountStub = sinon.stub(metamaskController.keyringController, 'addNewAccount') + addNewAccountStub = sinon.stub( + metamaskController.keyringController, + 'addNewAccount', + ) addNewAccountStub.returns({}) - getAccountsStub = sinon.stub(metamaskController.keyringController, 'getAccounts') + getAccountsStub = sinon.stub( + metamaskController.keyringController, + 'getAccounts', + ) // Need to return different address to mock the behavior of // adding a new account from the keyring getAccountsStub.onCall(0).returns(Promise.resolve(['0x1'])) @@ -485,8 +567,14 @@ describe('MetaMaskController', function () { sinon.spy(metamaskController.preferencesController, 'setAddresses') sinon.spy(metamaskController.preferencesController, 'setSelectedAddress') sinon.spy(metamaskController.preferencesController, 'setAccountLabel') - await metamaskController.connectHardware('trezor', 0, `m/44/0'/0'`).catch(() => null) - await metamaskController.unlockHardwareWalletAccount(accountToUnlock, 'trezor', `m/44/0'/0'`) + await metamaskController + .connectHardware('trezor', 0, `m/44/0'/0'`) + .catch(() => null) + await metamaskController.unlockHardwareWalletAccount( + accountToUnlock, + 'trezor', + `m/44/0'/0'`, + ) }) afterEach(function () { @@ -518,13 +606,16 @@ describe('MetaMaskController', function () { }) it('should call preferencesController.setSelectedAddress', async function () { - assert(metamaskController.preferencesController.setSelectedAddress.calledOnce) + assert( + metamaskController.preferencesController.setSelectedAddress.calledOnce, + ) }) it('should call preferencesController.setAccountLabel', async function () { - assert(metamaskController.preferencesController.setAccountLabel.calledOnce) + assert( + metamaskController.preferencesController.setAccountLabel.calledOnce, + ) }) - }) describe('#setCustomRpc', function () { @@ -551,7 +642,8 @@ describe('MetaMaskController', function () { let defaultMetaMaskCurrency beforeEach(function () { - defaultMetaMaskCurrency = metamaskController.currencyRateController.state.currentCurrency + defaultMetaMaskCurrency = + metamaskController.currencyRateController.state.currentCurrency }) it('defaults to usd', function () { @@ -560,7 +652,10 @@ describe('MetaMaskController', function () { it('sets currency to JPY', function () { metamaskController.setCurrentCurrency('JPY', noop) - assert.equal(metamaskController.currencyRateController.state.currentCurrency, 'JPY') + assert.equal( + metamaskController.currencyRateController.state.currentCurrency, + 'JPY', + ) }) }) @@ -599,21 +694,49 @@ describe('MetaMaskController', function () { describe('#resetAccount', function () { it('wipes transactions from only the correct network id and with the selected address', async function () { - const selectedAddressStub = sinon.stub(metamaskController.preferencesController, 'getSelectedAddress') - const getNetworkstub = sinon.stub(metamaskController.txController.txStateManager, 'getNetwork') + const selectedAddressStub = sinon.stub( + metamaskController.preferencesController, + 'getSelectedAddress', + ) + const getNetworkstub = sinon.stub( + metamaskController.txController.txStateManager, + 'getNetwork', + ) selectedAddressStub.returns('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') getNetworkstub.returns(42) metamaskController.txController.txStateManager._saveTxList([ - createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' } }), - createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' } }), - createTxMeta({ id: 2, status: 'rejected', metamaskNetworkId: '32' }), - createTxMeta({ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: { from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4' } }), + createTxMeta({ + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' }, + }), + createTxMeta({ + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' }, + }), + createTxMeta({ + id: 2, + status: TRANSACTION_STATUSES.REJECTED, + metamaskNetworkId: '32', + }), + createTxMeta({ + id: 3, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + txParams: { from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4' }, + }), ]) await metamaskController.resetAccount() - assert.equal(metamaskController.txController.txStateManager.getTx(1), undefined) + assert.equal( + metamaskController.txController.txStateManager.getTx(1), + undefined, + ) }) }) @@ -625,10 +748,12 @@ describe('MetaMaskController', function () { sinon.stub(metamaskController.preferencesController, 'removeAddress') sinon.stub(metamaskController.accountTracker, 'removeAccount') sinon.stub(metamaskController.keyringController, 'removeAccount') - sinon.stub(metamaskController.permissionsController, 'removeAllAccountPermissions') + sinon.stub( + metamaskController.permissionsController, + 'removeAllAccountPermissions', + ) ret = await metamaskController.removeAccount(addressToRemove) - }) afterEach(function () { @@ -639,16 +764,32 @@ describe('MetaMaskController', function () { }) it('should call preferencesController.removeAddress', async function () { - assert(metamaskController.preferencesController.removeAddress.calledWith(addressToRemove)) + assert( + metamaskController.preferencesController.removeAddress.calledWith( + addressToRemove, + ), + ) }) it('should call accountTracker.removeAccount', async function () { - assert(metamaskController.accountTracker.removeAccount.calledWith([addressToRemove])) + assert( + metamaskController.accountTracker.removeAccount.calledWith([ + addressToRemove, + ]), + ) }) it('should call keyringController.removeAccount', async function () { - assert(metamaskController.keyringController.removeAccount.calledWith(addressToRemove)) + assert( + metamaskController.keyringController.removeAccount.calledWith( + addressToRemove, + ), + ) }) it('should call permissionsController.removeAllAccountPermissions', async function () { - assert(metamaskController.permissionsController.removeAllAccountPermissions.calledWith(addressToRemove)) + assert( + metamaskController.permissionsController.removeAllAccountPermissions.calledWith( + addressToRemove, + ), + ) }) it('should return address', async function () { assert.equal(ret, '0x1') @@ -656,22 +797,21 @@ describe('MetaMaskController', function () { }) describe('#setCurrentLocale', function () { - it('checks the default currentLocale', function () { - const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale + const preferenceCurrentLocale = metamaskController.preferencesController.store.getState() + .currentLocale assert.equal(preferenceCurrentLocale, undefined) }) it('sets current locale in preferences controller', function () { metamaskController.setCurrentLocale('ja', noop) - const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale + const preferenceCurrentLocale = metamaskController.preferencesController.store.getState() + .currentLocale assert.equal(preferenceCurrentLocale, 'ja') }) - }) describe('#newUnsignedMessage', function () { - let msgParams, metamaskMsgs, messages, msgId const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' @@ -683,10 +823,13 @@ describe('MetaMaskController', function () { return Promise.resolve('0x0') }) - await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) + await metamaskController.createNewVaultAndRestore( + 'foobar1337', + TEST_SEED_ALT, + ) msgParams = { - 'from': address, + from: address, data, } @@ -709,7 +852,7 @@ describe('MetaMaskController', function () { }) it('sets the status to unapproved', function () { - assert.equal(metamaskMsgs[msgId].status, 'unapproved') + assert.equal(metamaskMsgs[msgId].status, TRANSACTION_STATUSES.UNAPPROVED) }) it('sets the type to eth_sign', function () { @@ -719,7 +862,7 @@ describe('MetaMaskController', function () { it('rejects the message', function () { const msgIdInt = parseInt(msgId, 10) metamaskController.cancelMessage(msgIdInt, noop) - assert.equal(messages[0].status, 'rejected') + assert.equal(messages[0].status, TRANSACTION_STATUSES.REJECTED) }) it('errors when signing a message', async function () { @@ -743,10 +886,13 @@ describe('MetaMaskController', function () { return Promise.resolve('0x0') }) - await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) + await metamaskController.createNewVaultAndRestore( + 'foobar1337', + TEST_SEED_ALT, + ) msgParams = { - 'from': address, + from: address, data, } @@ -767,7 +913,10 @@ describe('MetaMaskController', function () { }) assert.fail('should have thrown') } catch (error) { - assert.equal(error.message, 'MetaMask Message Signature: from field is required.') + assert.equal( + error.message, + 'MetaMask Message Signature: from field is required.', + ) } }) @@ -780,7 +929,10 @@ describe('MetaMaskController', function () { }) it('sets the status to unapproved', function () { - assert.equal(metamaskPersonalMsgs[msgId].status, 'unapproved') + assert.equal( + metamaskPersonalMsgs[msgId].status, + TRANSACTION_STATUSES.UNAPPROVED, + ) }) it('sets the type to personal_sign', function () { @@ -790,18 +942,25 @@ describe('MetaMaskController', function () { it('rejects the message', function () { const msgIdInt = parseInt(msgId, 10) metamaskController.cancelPersonalMessage(msgIdInt, noop) - assert.equal(personalMessages[0].status, 'rejected') + assert.equal(personalMessages[0].status, TRANSACTION_STATUSES.REJECTED) }) it('errors when signing a message', async function () { - await metamaskController.signPersonalMessage(personalMessages[0].msgParams) - assert.equal(metamaskPersonalMsgs[msgId].status, 'signed') - assert.equal(metamaskPersonalMsgs[msgId].rawSig, '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b') + await metamaskController.signPersonalMessage( + personalMessages[0].msgParams, + ) + assert.equal( + metamaskPersonalMsgs[msgId].status, + TRANSACTION_STATUSES.SIGNED, + ) + assert.equal( + metamaskPersonalMsgs[msgId].rawSig, + '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b', + ) }) }) describe('#setupUntrustedCommunication', function () { - const mockTxParams = { from: TEST_ADDRESS } beforeEach(function () { @@ -824,12 +983,18 @@ describe('MetaMaskController', function () { cb() return } - assert.equal(chunk.data.hostname, (new URL(phishingMessageSender.url)).hostname) + assert.equal( + chunk.data.hostname, + new URL(phishingMessageSender.url).hostname, + ) resolve() cb() }) - metamaskController.setupUntrustedCommunication(streamTest, phishingMessageSender) + metamaskController.setupUntrustedCommunication( + streamTest, + phishingMessageSender, + ) await promise streamTest.end() }) @@ -855,22 +1020,23 @@ describe('MetaMaskController', function () { params: [{ ...mockTxParams }], method: 'eth_sendTransaction', } - streamTest.write({ - name: 'provider', - data: message, - }, null, () => { - setTimeout(() => { - assert.deepStrictEqual( - loggerMiddlewareMock.requests[0], - { + streamTest.write( + { + name: 'provider', + data: message, + }, + null, + () => { + setTimeout(() => { + assert.deepStrictEqual(loggerMiddlewareMock.requests[0], { ...message, origin: 'http://mycrypto.com', tabId: 456, - }, - ) - done() - }) - }) + }) + done() + }) + }, + ) }) it('should add only origin to request if tabId not provided', function (done) { @@ -893,21 +1059,22 @@ describe('MetaMaskController', function () { params: [{ ...mockTxParams }], method: 'eth_sendTransaction', } - streamTest.write({ - name: 'provider', - data: message, - }, null, () => { - setTimeout(() => { - assert.deepStrictEqual( - loggerMiddlewareMock.requests[0], - { + streamTest.write( + { + name: 'provider', + data: message, + }, + null, + () => { + setTimeout(() => { + assert.deepStrictEqual(loggerMiddlewareMock.requests[0], { ...message, origin: 'http://mycrypto.com', - }, - ) - done() - }) - }) + }) + done() + }) + }, + ) }) }) @@ -947,7 +1114,6 @@ describe('MetaMaskController', function () { }) describe('#_onKeyringControllerUpdate', function () { - it('should do nothing if there are no keyrings in state', async function () { const syncAddresses = sinon.fake() const syncWithAddresses = sinon.fake() @@ -978,9 +1144,11 @@ describe('MetaMaskController', function () { const oldState = metamaskController.getState() await metamaskController._onKeyringControllerUpdate({ - keyrings: [{ - accounts: ['0x1', '0x2'], - }], + keyrings: [ + { + accounts: ['0x1', '0x2'], + }, + ], }) assert.deepEqual(syncAddresses.args, [[['0x1', '0x2']]]) @@ -1001,9 +1169,11 @@ describe('MetaMaskController', function () { const oldState = metamaskController.getState() await metamaskController._onKeyringControllerUpdate({ isUnlocked: true, - keyrings: [{ - accounts: ['0x1', '0x2'], - }], + keyrings: [ + { + accounts: ['0x1', '0x2'], + }, + ], }) assert.deepEqual(syncAddresses.args, [[['0x1', '0x2']]]) @@ -1011,10 +1181,9 @@ describe('MetaMaskController', function () { assert.deepEqual(metamaskController.getState(), oldState) }) }) - }) -function deferredPromise () { +function deferredPromise() { let resolve const promise = new Promise((_resolve) => { resolve = _resolve diff --git a/test/unit/app/controllers/network/network-controller-test.js b/test/unit/app/controllers/network/network-controller-test.js index 7971939b0..2fb123fec 100644 --- a/test/unit/app/controllers/network/network-controller-test.js +++ b/test/unit/app/controllers/network/network-controller-test.js @@ -19,7 +19,8 @@ describe('NetworkController', function () { describe('#provider', function () { it('provider should be updatable without reassignment', function () { networkController.initializeProvider(networkControllerProviderConfig) - const providerProxy = networkController.getProviderAndBlockTracker().provider + const providerProxy = networkController.getProviderAndBlockTracker() + .provider assert.equal(providerProxy.test, undefined) providerProxy.setTarget({ test: true }) assert.equal(providerProxy.test, true) @@ -56,7 +57,8 @@ describe('NetworkController', function () { networkController.setProviderType('mainnet') assert.equal( - spy.callCount, 1, + spy.callCount, + 1, 'should have called setNetworkState 2 times', ) assert.ok( @@ -73,40 +75,52 @@ describe('NetworkController', function () { { input: '3', expected: 'Ropsten', - }, { + }, + { input: '4', expected: 'Rinkeby', - }, { + }, + { input: '42', expected: 'Kovan', - }, { + }, + { input: '0x3', expected: 'Ropsten', - }, { + }, + { input: '0x4', expected: 'Rinkeby', - }, { + }, + { input: '0x2a', expected: 'Kovan', - }, { + }, + { input: 'ropsten', expected: 'Ropsten', - }, { + }, + { input: 'rinkeby', expected: 'Rinkeby', - }, { + }, + { input: 'kovan', expected: 'Kovan', - }, { + }, + { input: 'mainnet', expected: 'Ethereum Mainnet', - }, { + }, + { input: 'goerli', expected: 'Goerli', }, ] - tests.forEach(({ input, expected }) => assert.equal(getNetworkDisplayName(input), expected)) + tests.forEach(({ input, expected }) => + assert.equal(getNetworkDisplayName(input), expected), + ) }) }) }) diff --git a/test/unit/app/controllers/network/pending-middleware-test.js b/test/unit/app/controllers/network/pending-middleware-test.js index a339d9b68..36b917d16 100644 --- a/test/unit/app/controllers/network/pending-middleware-test.js +++ b/test/unit/app/controllers/network/pending-middleware-test.js @@ -1,12 +1,17 @@ import assert from 'assert' -import { createPendingNonceMiddleware, createPendingTxMiddleware } from '../../../../../app/scripts/controllers/network/middleware/pending' +import { + createPendingNonceMiddleware, + createPendingTxMiddleware, +} from '../../../../../app/scripts/controllers/network/middleware/pending' import { txMetaStub } from './stubs' describe('PendingNonceMiddleware', function () { describe('#createPendingNonceMiddleware', function () { const getPendingNonce = async () => '0x2' const address = '0xF231D46dD78806E1DD93442cf33C7671f8538748' - const pendingNonceMiddleware = createPendingNonceMiddleware({ getPendingNonce }) + const pendingNonceMiddleware = createPendingNonceMiddleware({ + getPendingNonce, + }) it('should call next if not a eth_getTransactionCount request', function (done) { const req = { method: 'eth_getBlockByNumber' } @@ -19,37 +24,49 @@ describe('PendingNonceMiddleware', function () { pendingNonceMiddleware(req, res, () => done()) }) it('should fill the result with a the "pending" nonce', function (done) { - const req = { method: 'eth_getTransactionCount', params: [address, 'pending'] } + const req = { + method: 'eth_getTransactionCount', + params: [address, 'pending'], + } const res = {} - pendingNonceMiddleware(req, res, () => { - done(new Error('should not have called next')) - }, () => { - assert(res.result === '0x2') - done() - }) + pendingNonceMiddleware( + req, + res, + () => { + done(new Error('should not have called next')) + }, + () => { + assert(res.result === '0x2') + done() + }, + ) }) }) describe('#createPendingTxMiddleware', function () { let returnUndefined = true - const getPendingTransactionByHash = () => (returnUndefined ? undefined : txMetaStub) + const getPendingTransactionByHash = () => + returnUndefined ? undefined : txMetaStub const address = '0xF231D46dD78806E1DD93442cf33C7671f8538748' - const pendingTxMiddleware = createPendingTxMiddleware({ getPendingTransactionByHash }) + const pendingTxMiddleware = createPendingTxMiddleware({ + getPendingTransactionByHash, + }) const spec = { - 'blockHash': null, - 'blockNumber': null, - 'from': '0xf231d46dd78806e1dd93442cf33c7671f8538748', - 'gas': '0x5208', - 'gasPrice': '0x1e8480', - 'hash': '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09', - 'input': '0x', - 'nonce': '0x4', - 'to': '0xf231d46dd78806e1dd93442cf33c7671f8538748', - 'transactionIndex': null, - 'value': '0x0', - 'v': '0x2c', - 'r': '0x5f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57', - 's': '0x0259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a', + blockHash: null, + blockNumber: null, + from: '0xf231d46dd78806e1dd93442cf33c7671f8538748', + gas: '0x5208', + gasPrice: '0x1e8480', + hash: + '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09', + input: '0x', + nonce: '0x4', + to: '0xf231d46dd78806e1dd93442cf33c7671f8538748', + transactionIndex: null, + value: '0x0', + v: '0x2c', + r: '0x5f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57', + s: '0x0259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a', } it('should call next if not a eth_getTransactionByHash request', function (done) { const req = { method: 'eth_getBlockByNumber' } @@ -65,14 +82,26 @@ describe('PendingNonceMiddleware', function () { it('should fill the result with a the "pending" tx the result should match the rpc spec', function (done) { returnUndefined = false - const req = { method: 'eth_getTransactionByHash', params: [address, 'pending'] } + const req = { + method: 'eth_getTransactionByHash', + params: [address, 'pending'], + } const res = {} - pendingTxMiddleware(req, res, () => { - done(new Error('should not have called next')) - }, () => { - assert.deepStrictEqual(res.result, spec, new Error('result does not match the spec object')) - done() - }) + pendingTxMiddleware( + req, + res, + () => { + done(new Error('should not have called next')) + }, + () => { + assert.deepStrictEqual( + res.result, + spec, + new Error('result does not match the spec object'), + ) + done() + }, + ) }) }) }) diff --git a/test/unit/app/controllers/network/stubs.js b/test/unit/app/controllers/network/stubs.js index c18d24b5d..0388e8d08 100644 --- a/test/unit/app/controllers/network/stubs.js +++ b/test/unit/app/controllers/network/stubs.js @@ -1,199 +1,210 @@ +import { + TRANSACTION_CATEGORIES, + TRANSACTION_STATUSES, + TRANSACTION_TYPES, +} from '../../../../../shared/constants/transaction' + export const txMetaStub = { - 'firstRetryBlockNumber': '0x51a402', - 'hash': '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09', - 'history': [ + firstRetryBlockNumber: '0x51a402', + hash: '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09', + history: [ { - 'id': 405984854664302, - 'loadingDefaults': true, - 'metamaskNetworkId': '4', - 'status': 'unapproved', - 'time': 1572395156620, - 'transactionCategory': 'sentEther', - 'txParams': { - 'from': '0xf231d46dd78806e1dd93442cf33c7671f8538748', - 'gas': '0x5208', - 'gasPrice': '0x1e8480', - 'to': '0xf231d46dd78806e1dd93442cf33c7671f8538748', - 'value': '0x0', + id: 405984854664302, + loadingDefaults: true, + metamaskNetworkId: '4', + status: TRANSACTION_STATUSES.UNAPPROVED, + time: 1572395156620, + transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER, + txParams: { + from: '0xf231d46dd78806e1dd93442cf33c7671f8538748', + gas: '0x5208', + gasPrice: '0x1e8480', + to: '0xf231d46dd78806e1dd93442cf33c7671f8538748', + value: '0x0', }, - 'type': 'standard', + type: TRANSACTION_TYPES.STANDARD, }, [ { - 'op': 'replace', - 'path': '/loadingDefaults', - 'timestamp': 1572395156645, - 'value': false, + op: 'replace', + path: '/loadingDefaults', + timestamp: 1572395156645, + value: false, }, ], [ { - 'note': '#newUnapprovedTransaction - adding the origin', - 'op': 'add', - 'path': '/origin', - 'timestamp': 1572395156645, - 'value': 'MetaMask', + note: '#newUnapprovedTransaction - adding the origin', + op: 'add', + path: '/origin', + timestamp: 1572395156645, + value: 'MetaMask', }, ], [], [ { - 'note': 'txStateManager: setting status to approved', - 'op': 'replace', - 'path': '/status', - 'timestamp': 1572395158240, - 'value': 'approved', + note: 'txStateManager: setting status to approved', + op: 'replace', + path: '/status', + timestamp: 1572395158240, + value: TRANSACTION_STATUSES.APPROVED, }, ], [ { - 'note': 'transactions#approveTransaction', - 'op': 'add', - 'path': '/txParams/nonce', - 'timestamp': 1572395158261, - 'value': '0x4', + note: 'transactions#approveTransaction', + op: 'add', + path: '/txParams/nonce', + timestamp: 1572395158261, + value: '0x4', }, { - 'op': 'add', - 'path': '/nonceDetails', - 'value': { - 'local': { - 'details': { - 'highest': 4, - 'startPoint': 4, + op: 'add', + path: '/nonceDetails', + value: { + local: { + details: { + highest: 4, + startPoint: 4, }, - 'name': 'local', - 'nonce': 4, + name: 'local', + nonce: 4, }, - 'network': { - 'details': { - 'baseCount': 4, - 'blockNumber': '0x51a401', + network: { + details: { + baseCount: 4, + blockNumber: '0x51a401', }, - 'name': 'network', - 'nonce': 4, + name: 'network', + nonce: 4, }, - 'params': { - 'highestLocallyConfirmed': 0, - 'highestSuggested': 4, - 'nextNetworkNonce': 4, + params: { + highestLocallyConfirmed: 0, + highestSuggested: 4, + nextNetworkNonce: 4, }, }, }, ], [ { - 'note': 'transactions#signTransaction: add r, s, v values', - 'op': 'add', - 'path': '/r', - 'timestamp': 1572395158280, - 'value': '0x5f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57', + note: 'transactions#signTransaction: add r, s, v values', + op: 'add', + path: '/r', + timestamp: 1572395158280, + value: + '0x5f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57', }, { - 'op': 'add', - 'path': '/s', - 'value': '0x0259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a', + op: 'add', + path: '/s', + value: + '0x0259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a', }, { - 'op': 'add', - 'path': '/v', - 'value': '0x2c', + op: 'add', + path: '/v', + value: '0x2c', }, ], [ { - 'note': 'transactions#publishTransaction', - 'op': 'replace', - 'path': '/status', - 'timestamp': 1572395158281, - 'value': 'signed', + note: 'transactions#publishTransaction', + op: 'replace', + path: '/status', + timestamp: 1572395158281, + value: TRANSACTION_STATUSES.SIGNED, }, { - 'op': 'add', - 'path': '/rawTx', - 'value': '0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a', + op: 'add', + path: '/rawTx', + value: + '0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a', }, ], [], [ { - 'note': 'transactions#setTxHash', - 'op': 'add', - 'path': '/hash', - 'timestamp': 1572395158570, - 'value': '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09', + note: 'transactions#setTxHash', + op: 'add', + path: '/hash', + timestamp: 1572395158570, + value: + '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09', }, ], [ { - 'note': 'txStateManager - add submitted time stamp', - 'op': 'add', - 'path': '/submittedTime', - 'timestamp': 1572395158571, - 'value': 1572395158570, + note: 'txStateManager - add submitted time stamp', + op: 'add', + path: '/submittedTime', + timestamp: 1572395158571, + value: 1572395158570, }, ], [ { - 'note': 'txStateManager: setting status to submitted', - 'op': 'replace', - 'path': '/status', - 'timestamp': 1572395158576, - 'value': 'submitted', + note: 'txStateManager: setting status to submitted', + op: 'replace', + path: '/status', + timestamp: 1572395158576, + value: TRANSACTION_STATUSES.SUBMITTED, }, ], [ { - 'note': 'transactions/pending-tx-tracker#event: tx:block-update', - 'op': 'add', - 'path': '/firstRetryBlockNumber', - 'timestamp': 1572395168972, - 'value': '0x51a402', + note: 'transactions/pending-tx-tracker#event: tx:block-update', + op: 'add', + path: '/firstRetryBlockNumber', + timestamp: 1572395168972, + value: '0x51a402', }, ], ], - 'id': 405984854664302, - 'loadingDefaults': false, - 'metamaskNetworkId': '4', - 'nonceDetails': { - 'local': { - 'details': { - 'highest': 4, - 'startPoint': 4, + id: 405984854664302, + loadingDefaults: false, + metamaskNetworkId: '4', + nonceDetails: { + local: { + details: { + highest: 4, + startPoint: 4, }, - 'name': 'local', - 'nonce': 4, + name: 'local', + nonce: 4, }, - 'network': { - 'details': { - 'baseCount': 4, - 'blockNumber': '0x51a401', + network: { + details: { + baseCount: 4, + blockNumber: '0x51a401', }, - 'name': 'network', - 'nonce': 4, + name: 'network', + nonce: 4, }, - 'params': { - 'highestLocallyConfirmed': 0, - 'highestSuggested': 4, - 'nextNetworkNonce': 4, + params: { + highestLocallyConfirmed: 0, + highestSuggested: 4, + nextNetworkNonce: 4, }, }, - 'origin': 'MetaMask', - 'r': '0x5f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57', - 'rawTx': '0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a', - 's': '0x0259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a', - 'status': 'submitted', - 'submittedTime': 1572395158570, - 'time': 1572395156620, - 'transactionCategory': 'sentEther', - 'txParams': { - 'from': '0xf231d46dd78806e1dd93442cf33c7671f8538748', - 'gas': '0x5208', - 'gasPrice': '0x1e8480', - 'nonce': '0x4', - 'to': '0xf231d46dd78806e1dd93442cf33c7671f8538748', - 'value': '0x0', + origin: 'MetaMask', + r: '0x5f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57', + rawTx: + '0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a', + s: '0x0259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a', + status: TRANSACTION_STATUSES.SUBMITTED, + submittedTime: 1572395158570, + time: 1572395156620, + transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER, + txParams: { + from: '0xf231d46dd78806e1dd93442cf33c7671f8538748', + gas: '0x5208', + gasPrice: '0x1e8480', + nonce: '0x4', + to: '0xf231d46dd78806e1dd93442cf33c7671f8538748', + value: '0x0', }, - 'type': 'standard', - 'v': '0x2c', + type: TRANSACTION_TYPES.STANDARD, + v: '0x2c', } diff --git a/test/unit/app/controllers/permissions/helpers.js b/test/unit/app/controllers/permissions/helpers.js index e920b851d..0524be1e9 100644 --- a/test/unit/app/controllers/permissions/helpers.js +++ b/test/unit/app/controllers/permissions/helpers.js @@ -12,10 +12,8 @@ import { noop } from './mocks' * @param {string} origin - The origin to grant permissions to. * @param {Object} permissions - The permissions to grant. */ -export function grantPermissions (permController, origin, permissions) { - permController.permissions.grantNewPermissions( - origin, permissions, {}, noop, - ) +export function grantPermissions(permController, origin, permissions) { + permController.permissions.grantNewPermissions(origin, permissions, {}, noop) } /** @@ -25,8 +23,7 @@ export function grantPermissions (permController, origin, permissions) { * @param {PermissionsController} permController - The permissions controller. * @return {Function} A convenient wrapper for the requestUserApproval function. */ -export function getRequestUserApprovalHelper (permController) { - +export function getRequestUserApprovalHelper(permController) { /** * Returns a request object that can be passed to requestUserApproval. * @@ -35,7 +32,9 @@ export function getRequestUserApprovalHelper (permController) { * @returns {Object} The corresponding request object. */ return (id, origin = 'defaultOrigin') => { - return permController.permissions.requestUserApproval({ metadata: { id, origin } }) + return permController.permissions.requestUserApproval({ + metadata: { id, origin }, + }) } } @@ -46,11 +45,11 @@ export function getRequestUserApprovalHelper (permController) { * * This function must be called on the permissions controller for each request. * - * @param {PermissionsController} - A permissions controller. + * @param {PermissionsController} permController - A permissions controller. * @returns {Promise} A Promise that resolves once a pending approval * has been set. */ -export function getUserApprovalPromise (permController) { +export function getUserApprovalPromise(permController) { const originalFunction = permController.permissions.requestUserApproval return new Promise((resolveHelperPromise) => { permController.permissions.requestUserApproval = (req) => { @@ -72,65 +71,47 @@ export function getUserApprovalPromise (permController) { * @param {'restricted'|'internal'} methodType - The method log controller method type of the request. * @param {boolean} success - Whether the request succeeded or not. */ -export function validateActivityEntry ( - entry, req, res, methodType, success, -) { - assert.doesNotThrow( - () => { - _validateActivityEntry( - entry, req, res, methodType, success, - ) - }, - 'should have expected activity entry', - ) +export function validateActivityEntry(entry, req, res, methodType, success) { + assert.doesNotThrow(() => { + _validateActivityEntry(entry, req, res, methodType, success) + }, 'should have expected activity entry') } -function _validateActivityEntry ( - entry, req, res, methodType, success, -) { - +function _validateActivityEntry(entry, req, res, methodType, success) { assert.ok(entry, 'entry should exist') assert.equal(entry.id, req.id) assert.equal(entry.method, req.method) assert.equal(entry.origin, req.origin) assert.equal(entry.methodType, methodType) - assert.deepEqual( - entry.request, req, - 'entry.request should equal the request', - ) + assert.deepEqual(entry.request, req, 'entry.request should equal the request') if (res) { - assert.ok( - ( - Number.isInteger(entry.requestTime) && - Number.isInteger(entry.responseTime) - ), + Number.isInteger(entry.requestTime) && + Number.isInteger(entry.responseTime), 'request and response times should be numbers', ) assert.ok( - (entry.requestTime <= entry.responseTime), + entry.requestTime <= entry.responseTime, 'request time should be less than response time', ) assert.equal(entry.success, success) assert.deepEqual( - entry.response, res, + entry.response, + res, 'entry.response should equal the response', ) } else { - assert.ok( Number.isInteger(entry.requestTime) && entry.requestTime > 0, 'entry should have non-zero request time', ) assert.ok( - ( - entry.success === null && + entry.success === null && entry.responseTime === null && - entry.response === null - ), + entry.response === null, 'entry response values should be null', ) } diff --git a/test/unit/app/controllers/permissions/mocks.js b/test/unit/app/controllers/permissions/mocks.js index 578b39911..9040f6b12 100644 --- a/test/unit/app/controllers/permissions/mocks.js +++ b/test/unit/app/controllers/permissions/mocks.js @@ -1,8 +1,7 @@ import { ethErrors, ERROR_CODES } from 'eth-json-rpc-errors' import deepFreeze from 'deep-freeze-strict' -import _getRestrictedMethods - from '../../../../../app/scripts/controllers/permissions/restrictedMethods' +import _getRestrictedMethods from '../../../../../app/scripts/controllers/permissions/restrictedMethods' import { CAVEAT_NAMES, @@ -36,24 +35,20 @@ const keyringAccounts = deepFreeze([ const getKeyringAccounts = async () => [...keyringAccounts] const getIdentities = () => { - return keyringAccounts.reduce( - (identities, address, index) => { - identities[address] = { address, name: `Account ${index}` } - return identities - }, - {}, - ) + return keyringAccounts.reduce((identities, address, index) => { + identities[address] = { address, name: `Account ${index}` } + return identities + }, {}) } // perm controller initialization helper const getRestrictedMethods = (permController) => { return { - // the actual, production restricted methods ..._getRestrictedMethods(permController), // our own dummy method for testing - 'test_method': { + test_method: { description: `This method is only for testing.`, method: (req, res, __, end) => { if (req.params[0]) { @@ -74,7 +69,7 @@ const getUnlockPromise = () => Promise.resolve() * * @returns {Object} A PermissionsController constructor options object. */ -export function getPermControllerOpts () { +export function getPermControllerOpts() { return { showPermissionRequest: noop, getKeyringAccounts, @@ -103,18 +98,17 @@ export function getPermControllerOpts () { * @param {string} extensionId - The extension id for the middleware. * @returns {Function} A Promise-wrapped middleware function with convenient default args. */ -export function getPermissionsMiddleware (permController, origin, extensionId) { +export function getPermissionsMiddleware(permController, origin, extensionId) { const middleware = permController.createMiddleware({ origin, extensionId }) return (req, res = {}, next = noop, end) => { return new Promise((resolve, reject) => { - // eslint-disable-next-line no-param-reassign end = end || _end middleware(req, res, next, end) // emulates json-rpc-engine error handling - function _end (err) { + function _end(err) { if (err || res.error) { reject(err || res.error) } else { @@ -131,7 +125,10 @@ export function getPermissionsMiddleware (permController, origin, extensionId) { * @returns {Function} A function passed to the permissions controller at initialization, * for recording notifications. */ -export const getNotifyDomain = (notifications = {}) => (origin, notification) => { +export const getNotifyDomain = (notifications = {}) => ( + origin, + notification, +) => { notifications[origin].push(notification) } @@ -183,7 +180,6 @@ const ACCOUNTS = { * Helpers for getting mock caveats. */ const CAVEATS = { - /** * Gets a correctly formatted eth_accounts exposedAccounts caveat. * @@ -191,15 +187,18 @@ const CAVEATS = { * @returns {Object} An eth_accounts exposedAccounts caveats */ eth_accounts: (accounts) => { - return [{ - type: CAVEAT_TYPES.limitResponseLength, - value: 1, - name: CAVEAT_NAMES.primaryAccountOnly, - }, { - type: CAVEAT_TYPES.filterResponse, - value: accounts, - name: CAVEAT_NAMES.exposedAccounts, - }] + return [ + { + type: CAVEAT_TYPES.limitResponseLength, + value: 1, + name: CAVEAT_NAMES.primaryAccountOnly, + }, + { + type: CAVEAT_TYPES.filterResponse, + value: accounts, + name: CAVEAT_NAMES.exposedAccounts, + }, + ] }, } @@ -208,7 +207,6 @@ const CAVEATS = { * by permissions controller functions if we used TypeScript. */ const PERMS = { - /** * The argument to approvePermissionsRequest * @param {string} id - The rpc-cap permissions request id. @@ -225,7 +223,6 @@ const PERMS = { * Requested permissions objects, as passed to wallet_requestPermissions. */ requests: { - /** * @returns {Object} A permissions request object with eth_accounts */ @@ -252,7 +249,6 @@ const PERMS = { * Finalized permission requests, as returned by finalizePermissionsRequest */ finalizedRequests: { - /** * @param {Array} accounts - The accounts for the eth_accounts permission caveat * @returns {Object} A finalized permissions request object with eth_accounts and its caveat @@ -281,7 +277,6 @@ const PERMS = { * - wallet_getPermissions */ granted: { - /** * @param {Array} accounts - The accounts for the eth_accounts permission caveat * @returns {Object} A granted permissions object with eth_accounts and its caveat @@ -309,7 +304,6 @@ const PERMS = { * caveats, errors, permissions requests etc. */ export const getters = deepFreeze({ - CAVEATS, PERMS, @@ -318,9 +312,7 @@ export const getters = deepFreeze({ * Getters for errors by the method or workflow that throws them. */ ERRORS: { - validatePermittedAccounts: { - invalidParam: () => { return { name: 'Error', @@ -467,7 +459,6 @@ export const getters = deepFreeze({ * Getters for notifications produced by the permissions controller. */ NOTIFICATIONS: { - /** * Gets a removed accounts notification. * @@ -498,7 +489,6 @@ export const getters = deepFreeze({ * Getters for mock RPC request objects. */ RPC_REQUESTS: { - /** * Gets an arbitrary RPC request object. * @@ -638,16 +628,12 @@ export const constants = deepFreeze({ PERM_NAMES: { ...PERM_NAMES }, - RESTRICTED_METHODS: [ - 'eth_accounts', - 'test_method', - ], + RESTRICTED_METHODS: ['eth_accounts', 'test_method'], /** * Mock permissions history objects. */ EXPECTED_HISTORIES: { - case1: [ { [DOMAINS.a.origin]: { diff --git a/test/unit/app/controllers/permissions/permissions-controller-test.js b/test/unit/app/controllers/permissions/permissions-controller-test.js index f51ed0800..5e593f114 100644 --- a/test/unit/app/controllers/permissions/permissions-controller-test.js +++ b/test/unit/app/controllers/permissions/permissions-controller-test.js @@ -14,10 +14,7 @@ import { addInternalMethodPrefix, } from '../../../../../app/scripts/controllers/permissions' -import { - getRequestUserApprovalHelper, - grantPermissions, -} from './helpers' +import { getRequestUserApprovalHelper, grantPermissions } from './helpers' import { noop, @@ -28,11 +25,7 @@ import { getPermControllerOpts, } from './mocks' -const { - ERRORS, - NOTIFICATIONS, - PERMS, -} = getters +const { ERRORS, NOTIFICATIONS, PERMS } = getters const { ALL_ACCOUNTS, @@ -60,34 +53,35 @@ const initPermController = (notifications = initNotifications()) => { } describe('permissions controller', function () { - describe('getAccounts', function () { - let permController beforeEach(function () { permController = initPermController() grantPermissions( - permController, DOMAINS.a.origin, + permController, + DOMAINS.a.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ) grantPermissions( - permController, DOMAINS.b.origin, + permController, + DOMAINS.b.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), ) }) it('gets permitted accounts for permitted origins', async function () { - const aAccounts = await permController.getAccounts(DOMAINS.a.origin) const bAccounts = await permController.getAccounts(DOMAINS.b.origin) assert.deepEqual( - aAccounts, [ACCOUNTS.a.primary], + aAccounts, + [ACCOUNTS.a.primary], 'first origin should have correct accounts', ) assert.deepEqual( - bAccounts, [ACCOUNTS.b.primary], + bAccounts, + [ACCOUNTS.b.primary], 'second origin should have correct accounts', ) }) @@ -104,16 +98,16 @@ describe('permissions controller', function () { }) describe('hasPermission', function () { - it('returns correct values', async function () { - const permController = initPermController() grantPermissions( - permController, DOMAINS.a.origin, + permController, + DOMAINS.a.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ) grantPermissions( - permController, DOMAINS.b.origin, + permController, + DOMAINS.b.origin, PERMS.finalizedRequests.test_method(), ) @@ -147,22 +141,23 @@ describe('permissions controller', function () { }) describe('clearPermissions', function () { - it('notifies all appropriate domains and removes permissions', async function () { - const notifications = initNotifications() const permController = initPermController(notifications) grantPermissions( - permController, DOMAINS.a.origin, + permController, + DOMAINS.a.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ) grantPermissions( - permController, DOMAINS.b.origin, + permController, + DOMAINS.b.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), ) grantPermissions( - permController, DOMAINS.c.origin, + permController, + DOMAINS.c.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.c.permitted), ) @@ -171,15 +166,18 @@ describe('permissions controller', function () { let cAccounts = await permController.getAccounts(DOMAINS.c.origin) assert.deepEqual( - aAccounts, [ACCOUNTS.a.primary], + aAccounts, + [ACCOUNTS.a.primary], 'first origin should have correct accounts', ) assert.deepEqual( - bAccounts, [ACCOUNTS.b.primary], + bAccounts, + [ACCOUNTS.b.primary], 'second origin should have correct accounts', ) assert.deepEqual( - cAccounts, [ACCOUNTS.c.primary], + cAccounts, + [ACCOUNTS.c.primary], 'third origin should have correct accounts', ) @@ -210,40 +208,43 @@ describe('permissions controller', function () { }) assert.deepEqual( - Object.keys(permController.permissions.getDomains()), [], + Object.keys(permController.permissions.getDomains()), + [], 'all domains should be deleted', ) }) }) describe('removePermissionsFor', function () { - let permController, notifications beforeEach(function () { notifications = initNotifications() permController = initPermController(notifications) grantPermissions( - permController, DOMAINS.a.origin, + permController, + DOMAINS.a.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ) grantPermissions( - permController, DOMAINS.b.origin, + permController, + DOMAINS.b.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), ) }) it('removes permissions for multiple domains', async function () { - let aAccounts = await permController.getAccounts(DOMAINS.a.origin) let bAccounts = await permController.getAccounts(DOMAINS.b.origin) assert.deepEqual( - aAccounts, [ACCOUNTS.a.primary], + aAccounts, + [ACCOUNTS.a.primary], 'first origin should have correct accounts', ) assert.deepEqual( - bAccounts, [ACCOUNTS.b.primary], + bAccounts, + [ACCOUNTS.b.primary], 'second origin should have correct accounts', ) @@ -259,34 +260,38 @@ describe('permissions controller', function () { assert.deepEqual(bAccounts, [], 'second origin should have no accounts') assert.deepEqual( - notifications[DOMAINS.a.origin], [NOTIFICATIONS.removedAccounts()], + notifications[DOMAINS.a.origin], + [NOTIFICATIONS.removedAccounts()], 'first origin should have correct notification', ) assert.deepEqual( - notifications[DOMAINS.b.origin], [NOTIFICATIONS.removedAccounts()], + notifications[DOMAINS.b.origin], + [NOTIFICATIONS.removedAccounts()], 'second origin should have correct notification', ) assert.deepEqual( - Object.keys(permController.permissions.getDomains()), [], + Object.keys(permController.permissions.getDomains()), + [], 'all domains should be deleted', ) }) it('only removes targeted permissions from single domain', async function () { - grantPermissions( - permController, DOMAINS.b.origin, PERMS.finalizedRequests.test_method(), + permController, + DOMAINS.b.origin, + PERMS.finalizedRequests.test_method(), ) - let bPermissions = permController.permissions.getPermissionsForDomain(DOMAINS.b.origin) + let bPermissions = permController.permissions.getPermissionsForDomain( + DOMAINS.b.origin, + ) assert.ok( - ( - bPermissions.length === 2 && + bPermissions.length === 2 && find(bPermissions, { parentCapability: PERM_NAMES.eth_accounts }) && - find(bPermissions, { parentCapability: PERM_NAMES.test_method }) - ), + find(bPermissions, { parentCapability: PERM_NAMES.test_method }), 'origin should have correct permissions', ) @@ -294,19 +299,18 @@ describe('permissions controller', function () { [DOMAINS.b.origin]: [PERM_NAMES.test_method], }) - bPermissions = permController.permissions.getPermissionsForDomain(DOMAINS.b.origin) + bPermissions = permController.permissions.getPermissionsForDomain( + DOMAINS.b.origin, + ) assert.ok( - ( - bPermissions.length === 1 && - find(bPermissions, { parentCapability: PERM_NAMES.eth_accounts }) - ), + bPermissions.length === 1 && + find(bPermissions, { parentCapability: PERM_NAMES.eth_accounts }), 'only targeted permission should have been removed', ) }) it('removes permissions for a single domain, without affecting another', async function () { - permController.removePermissionsFor({ [DOMAINS.b.origin]: [PERM_NAMES.eth_accounts], }) @@ -315,35 +319,39 @@ describe('permissions controller', function () { const bAccounts = await permController.getAccounts(DOMAINS.b.origin) assert.deepEqual( - aAccounts, [ACCOUNTS.a.primary], + aAccounts, + [ACCOUNTS.a.primary], 'first origin should have correct accounts', ) assert.deepEqual(bAccounts, [], 'second origin should have no accounts') assert.deepEqual( - notifications[DOMAINS.a.origin], [], + notifications[DOMAINS.a.origin], + [], 'first origin should have no notifications', ) assert.deepEqual( - notifications[DOMAINS.b.origin], [NOTIFICATIONS.removedAccounts()], + notifications[DOMAINS.b.origin], + [NOTIFICATIONS.removedAccounts()], 'second origin should have correct notification', ) assert.deepEqual( - Object.keys(permController.permissions.getDomains()), [DOMAINS.a.origin], + Object.keys(permController.permissions.getDomains()), + [DOMAINS.a.origin], 'only first origin should remain', ) }) it('send notification but does not affect permissions for unknown domain', async function () { - // it knows nothing of this origin permController.removePermissionsFor({ [DOMAINS.c.origin]: [PERM_NAMES.eth_accounts], }) assert.deepEqual( - notifications[DOMAINS.c.origin], [NOTIFICATIONS.removedAccounts()], + notifications[DOMAINS.c.origin], + [NOTIFICATIONS.removedAccounts()], 'unknown origin should have notification', ) @@ -351,11 +359,13 @@ describe('permissions controller', function () { const bAccounts = await permController.getAccounts(DOMAINS.b.origin) assert.deepEqual( - aAccounts, [ACCOUNTS.a.primary], + aAccounts, + [ACCOUNTS.a.primary], 'first origin should have correct accounts', ) assert.deepEqual( - bAccounts, [ACCOUNTS.b.primary], + bAccounts, + [ACCOUNTS.b.primary], 'second origin should have correct accounts', ) @@ -368,23 +378,23 @@ describe('permissions controller', function () { }) describe('validatePermittedAccounts', function () { - let permController beforeEach(function () { permController = initPermController() grantPermissions( - permController, DOMAINS.a.origin, + permController, + DOMAINS.a.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ) grantPermissions( - permController, DOMAINS.b.origin, + permController, + DOMAINS.b.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), ) }) it('throws error on non-array accounts', async function () { - await assert.throws( () => permController.validatePermittedAccounts(undefined), ERRORS.validatePermittedAccounts.invalidParam(), @@ -411,7 +421,6 @@ describe('permissions controller', function () { }) it('throws error on empty array of accounts', async function () { - await assert.throws( () => permController.validatePermittedAccounts([]), ERRORS.validatePermittedAccounts.invalidParam(), @@ -420,7 +429,6 @@ describe('permissions controller', function () { }) it('throws error if any account value is not in keyring', async function () { - const keyringAccounts = await permController.getKeyringAccounts() await assert.throws( @@ -430,14 +438,16 @@ describe('permissions controller', function () { ) await assert.throws( - () => permController.validatePermittedAccounts(keyringAccounts.concat(DUMMY_ACCOUNT)), + () => + permController.validatePermittedAccounts( + keyringAccounts.concat(DUMMY_ACCOUNT), + ), ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT), 'should throw on non-keyring account with other accounts', ) }) it('succeeds if all accounts are in keyring', async function () { - const keyringAccounts = await permController.getKeyringAccounts() await assert.doesNotThrow( @@ -464,11 +474,13 @@ describe('permissions controller', function () { notifications = initNotifications() permController = initPermController(notifications) grantPermissions( - permController, DOMAINS.a.origin, + permController, + DOMAINS.a.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ) grantPermissions( - permController, DOMAINS.b.origin, + permController, + DOMAINS.b.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), ) }) @@ -483,7 +495,8 @@ describe('permissions controller', function () { it('should throw if given account is not in keyring', async function () { await assert.rejects( - () => permController.addPermittedAccount(DOMAINS.a.origin, DUMMY_ACCOUNT), + () => + permController.addPermittedAccount(DOMAINS.a.origin, DUMMY_ACCOUNT), ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT), 'should throw on non-keyring account', ) @@ -499,7 +512,8 @@ describe('permissions controller', function () { it('should throw if origin lacks any permissions', async function () { await assert.rejects( - () => permController.addPermittedAccount(DOMAINS.c.origin, EXTRA_ACCOUNT), + () => + permController.addPermittedAccount(DOMAINS.c.origin, EXTRA_ACCOUNT), ERRORS.addPermittedAccount.invalidOrigin(), 'should throw on origin without permissions', ) @@ -507,12 +521,14 @@ describe('permissions controller', function () { it('should throw if origin lacks eth_accounts permission', async function () { grantPermissions( - permController, DOMAINS.c.origin, + permController, + DOMAINS.c.origin, PERMS.finalizedRequests.test_method(), ) await assert.rejects( - () => permController.addPermittedAccount(DOMAINS.c.origin, EXTRA_ACCOUNT), + () => + permController.addPermittedAccount(DOMAINS.c.origin, EXTRA_ACCOUNT), ERRORS.addPermittedAccount.noEthAccountsPermission(), 'should throw on origin without eth_accounts permission', ) @@ -520,7 +536,11 @@ describe('permissions controller', function () { it('should throw if account is already permitted', async function () { await assert.rejects( - () => permController.addPermittedAccount(DOMAINS.a.origin, ACCOUNTS.a.permitted[0]), + () => + permController.addPermittedAccount( + DOMAINS.a.origin, + ACCOUNTS.a.permitted[0], + ), ERRORS.addPermittedAccount.alreadyPermitted(), 'should throw if account is already permitted', ) @@ -529,10 +549,13 @@ describe('permissions controller', function () { it('should successfully add permitted account', async function () { await permController.addPermittedAccount(DOMAINS.a.origin, EXTRA_ACCOUNT) - const accounts = await permController._getPermittedAccounts(DOMAINS.a.origin) + const accounts = await permController._getPermittedAccounts( + DOMAINS.a.origin, + ) assert.deepEqual( - accounts, [...ACCOUNTS.a.permitted, EXTRA_ACCOUNT], + accounts, + [...ACCOUNTS.a.permitted, EXTRA_ACCOUNT], 'origin should have correct accounts', ) @@ -551,11 +574,13 @@ describe('permissions controller', function () { notifications = initNotifications() permController = initPermController(notifications) grantPermissions( - permController, DOMAINS.a.origin, + permController, + DOMAINS.a.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ) grantPermissions( - permController, DOMAINS.b.origin, + permController, + DOMAINS.b.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), ) }) @@ -570,7 +595,11 @@ describe('permissions controller', function () { it('should throw if given account is not in keyring', async function () { await assert.rejects( - () => permController.removePermittedAccount(DOMAINS.a.origin, DUMMY_ACCOUNT), + () => + permController.removePermittedAccount( + DOMAINS.a.origin, + DUMMY_ACCOUNT, + ), ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT), 'should throw on non-keyring account', ) @@ -586,7 +615,11 @@ describe('permissions controller', function () { it('should throw if origin lacks any permissions', async function () { await assert.rejects( - () => permController.removePermittedAccount(DOMAINS.c.origin, EXTRA_ACCOUNT), + () => + permController.removePermittedAccount( + DOMAINS.c.origin, + EXTRA_ACCOUNT, + ), ERRORS.removePermittedAccount.invalidOrigin(), 'should throw on origin without permissions', ) @@ -594,12 +627,17 @@ describe('permissions controller', function () { it('should throw if origin lacks eth_accounts permission', async function () { grantPermissions( - permController, DOMAINS.c.origin, + permController, + DOMAINS.c.origin, PERMS.finalizedRequests.test_method(), ) await assert.rejects( - () => permController.removePermittedAccount(DOMAINS.c.origin, EXTRA_ACCOUNT), + () => + permController.removePermittedAccount( + DOMAINS.c.origin, + EXTRA_ACCOUNT, + ), ERRORS.removePermittedAccount.noEthAccountsPermission(), 'should throw on origin without eth_accounts permission', ) @@ -607,19 +645,29 @@ describe('permissions controller', function () { it('should throw if account is not permitted', async function () { await assert.rejects( - () => permController.removePermittedAccount(DOMAINS.b.origin, ACCOUNTS.c.permitted[0]), + () => + permController.removePermittedAccount( + DOMAINS.b.origin, + ACCOUNTS.c.permitted[0], + ), ERRORS.removePermittedAccount.notPermitted(), 'should throw if account is not permitted', ) }) it('should successfully remove permitted account', async function () { - await permController.removePermittedAccount(DOMAINS.a.origin, ACCOUNTS.a.permitted[1]) + await permController.removePermittedAccount( + DOMAINS.a.origin, + ACCOUNTS.a.permitted[1], + ) - const accounts = await permController._getPermittedAccounts(DOMAINS.a.origin) + const accounts = await permController._getPermittedAccounts( + DOMAINS.a.origin, + ) assert.deepEqual( - accounts, ACCOUNTS.a.permitted.filter((acc) => acc !== ACCOUNTS.a.permitted[1]), + accounts, + ACCOUNTS.a.permitted.filter((acc) => acc !== ACCOUNTS.a.permitted[1]), 'origin should have correct accounts', ) @@ -631,20 +679,25 @@ describe('permissions controller', function () { }) it('should remove eth_accounts permission if removing only permitted account', async function () { - await permController.removePermittedAccount(DOMAINS.b.origin, ACCOUNTS.b.permitted[0]) + await permController.removePermittedAccount( + DOMAINS.b.origin, + ACCOUNTS.b.permitted[0], + ) const accounts = await permController.getAccounts(DOMAINS.b.origin) - assert.deepEqual( - accounts, [], - 'origin should have no accounts', - ) + assert.deepEqual(accounts, [], 'origin should have no accounts') const permission = await permController.permissions.getPermission( - DOMAINS.b.origin, PERM_NAMES.eth_accounts, + DOMAINS.b.origin, + PERM_NAMES.eth_accounts, ) - assert.equal(permission, undefined, 'origin should not have eth_accounts permission') + assert.equal( + permission, + undefined, + 'origin should not have eth_accounts permission', + ) assert.deepEqual( notifications[DOMAINS.b.origin][0], @@ -661,15 +714,18 @@ describe('permissions controller', function () { notifications = initNotifications() permController = initPermController(notifications) grantPermissions( - permController, DOMAINS.a.origin, + permController, + DOMAINS.a.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ) grantPermissions( - permController, DOMAINS.b.origin, + permController, + DOMAINS.b.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), ) grantPermissions( - permController, DOMAINS.c.origin, + permController, + DOMAINS.c.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), ) }) @@ -693,10 +749,13 @@ describe('permissions controller', function () { it('should remove permitted account from single origin', async function () { await permController.removeAllAccountPermissions(ACCOUNTS.a.permitted[1]) - const accounts = await permController._getPermittedAccounts(DOMAINS.a.origin) + const accounts = await permController._getPermittedAccounts( + DOMAINS.a.origin, + ) assert.deepEqual( - accounts, ACCOUNTS.a.permitted.filter((acc) => acc !== ACCOUNTS.a.permitted[1]), + accounts, + ACCOUNTS.a.permitted.filter((acc) => acc !== ACCOUNTS.a.permitted[1]), 'origin should have correct accounts', ) @@ -711,16 +770,10 @@ describe('permissions controller', function () { await permController.removeAllAccountPermissions(ACCOUNTS.b.permitted[0]) const bAccounts = await permController.getAccounts(DOMAINS.b.origin) - assert.deepEqual( - bAccounts, [], - 'first origin should no accounts', - ) + assert.deepEqual(bAccounts, [], 'first origin should no accounts') const cAccounts = await permController.getAccounts(DOMAINS.c.origin) - assert.deepEqual( - cAccounts, [], - 'second origin no accounts', - ) + assert.deepEqual(cAccounts, [], 'second origin no accounts') assert.deepEqual( notifications[DOMAINS.b.origin][0], @@ -740,16 +793,18 @@ describe('permissions controller', function () { const accounts = await permController.getAccounts(DOMAINS.b.origin) - assert.deepEqual( - accounts, [], - 'origin should have no accounts', - ) + assert.deepEqual(accounts, [], 'origin should have no accounts') const permission = await permController.permissions.getPermission( - DOMAINS.b.origin, PERM_NAMES.eth_accounts, + DOMAINS.b.origin, + PERM_NAMES.eth_accounts, ) - assert.equal(permission, undefined, 'origin should not have eth_accounts permission') + assert.equal( + permission, + undefined, + 'origin should not have eth_accounts permission', + ) assert.deepEqual( notifications[DOMAINS.b.origin][0], @@ -760,7 +815,6 @@ describe('permissions controller', function () { }) describe('finalizePermissionsRequest', function () { - let permController beforeEach(function () { @@ -768,10 +822,10 @@ describe('permissions controller', function () { }) it('throws on non-keyring accounts', async function () { - await assert.rejects( permController.finalizePermissionsRequest( - PERMS.requests.eth_accounts(), [DUMMY_ACCOUNT], + PERMS.requests.eth_accounts(), + [DUMMY_ACCOUNT], ), ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT), 'should throw on non-keyring account', @@ -779,54 +833,52 @@ describe('permissions controller', function () { }) it('adds caveat to eth_accounts permission', async function () { - const perm = await permController.finalizePermissionsRequest( PERMS.requests.eth_accounts(), ACCOUNTS.a.permitted, ) - assert.deepEqual(perm, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted)) + assert.deepEqual( + perm, + PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), + ) }) it('replaces caveat of eth_accounts permission', async function () { - const perm = await permController.finalizePermissionsRequest( PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ACCOUNTS.b.permitted, ) assert.deepEqual( - perm, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), + perm, + PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), 'permission should have correct caveat', ) }) it('handles non-eth_accounts permission', async function () { - const perm = await permController.finalizePermissionsRequest( PERMS.finalizedRequests.test_method(), ACCOUNTS.b.permitted, ) assert.deepEqual( - perm, PERMS.finalizedRequests.test_method(), + perm, + PERMS.finalizedRequests.test_method(), 'permission should have correct caveat', ) }) }) describe('preferences state update', function () { - let permController, notifications, preferences, identities beforeEach(function () { - identities = ALL_ACCOUNTS.reduce( - (identitiesAcc, account) => { - identitiesAcc[account] = {} - return identitiesAcc - }, - {}, - ) + identities = ALL_ACCOUNTS.reduce((identitiesAcc, account) => { + identitiesAcc[account] = {} + return identitiesAcc + }, {}) preferences = { getState: sinon.stub(), subscribe: sinon.stub(), @@ -843,17 +895,21 @@ describe('permissions controller', function () { preferences, }) grantPermissions( - permController, DOMAINS.b.origin, - PERMS.finalizedRequests.eth_accounts([...ACCOUNTS.a.permitted, EXTRA_ACCOUNT]), + permController, + DOMAINS.b.origin, + PERMS.finalizedRequests.eth_accounts([ + ...ACCOUNTS.a.permitted, + EXTRA_ACCOUNT, + ]), ) grantPermissions( - permController, DOMAINS.c.origin, + permController, + DOMAINS.c.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ) }) it('should throw if given invalid account', async function () { - assert(preferences.subscribe.calledOnce) assert(preferences.subscribe.firstCall.args.length === 1) const onPreferencesUpdate = preferences.subscribe.firstCall.args[0] @@ -873,11 +929,13 @@ describe('permissions controller', function () { await onPreferencesUpdate({ selectedAddress: DUMMY_ACCOUNT }) assert.deepEqual( - notifications[DOMAINS.b.origin], [], + notifications[DOMAINS.b.origin], + [], 'should not have emitted notification', ) assert.deepEqual( - notifications[DOMAINS.c.origin], [], + notifications[DOMAINS.c.origin], + [], 'should not have emitted notification', ) }) @@ -916,7 +974,8 @@ describe('permissions controller', function () { 'should have emitted notification', ) assert.deepEqual( - notifications[DOMAINS.c.origin], [], + notifications[DOMAINS.c.origin], + [], 'should not have emitted notification', ) }) @@ -943,7 +1002,6 @@ describe('permissions controller', function () { }) describe('approvePermissionsRequest', function () { - let permController, requestUserApproval beforeEach(function () { @@ -952,9 +1010,9 @@ describe('permissions controller', function () { }) it('does nothing if called on non-existing request', async function () { - assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'pending approvals should be empty on init', ) @@ -973,13 +1031,13 @@ describe('permissions controller', function () { ) assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'pending approvals should still be empty after request', ) }) it('rejects request with bad accounts param', async function () { - const request = PERMS.approvedRequest( REQUEST_IDS.a, PERMS.requests.eth_accounts(), @@ -995,13 +1053,13 @@ describe('permissions controller', function () { await rejectionPromise assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'pending approvals should be empty after rejection', ) }) it('rejects request with no permissions', async function () { - const request = PERMS.approvedRequest(REQUEST_IDS.a, {}) const requestRejection = assert.rejects( @@ -1010,92 +1068,113 @@ describe('permissions controller', function () { 'should reject if no permissions in request', ) - await permController.approvePermissionsRequest(request, ACCOUNTS.a.permitted) + await permController.approvePermissionsRequest( + request, + ACCOUNTS.a.permitted, + ) await requestRejection assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'pending approvals should be empty after rejection', ) }) it('approves valid request', async function () { - - const request = PERMS.approvedRequest(REQUEST_IDS.a, PERMS.requests.eth_accounts()) + const request = PERMS.approvedRequest( + REQUEST_IDS.a, + PERMS.requests.eth_accounts(), + ) let perms - const requestApproval = assert.doesNotReject( - async () => { - perms = await requestUserApproval(REQUEST_IDS.a) - }, - 'should not reject single valid request', - ) + const requestApproval = assert.doesNotReject(async () => { + perms = await requestUserApproval(REQUEST_IDS.a) + }, 'should not reject single valid request') - await permController.approvePermissionsRequest(request, ACCOUNTS.a.permitted) + await permController.approvePermissionsRequest( + request, + ACCOUNTS.a.permitted, + ) await requestApproval assert.deepEqual( - perms, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), + perms, + PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), 'should produce expected approved permissions', ) assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'pending approvals should be empty after approval', ) }) it('approves valid requests regardless of order', async function () { - - const request1 = PERMS.approvedRequest(REQUEST_IDS.a, PERMS.requests.eth_accounts()) - const request2 = PERMS.approvedRequest(REQUEST_IDS.b, PERMS.requests.eth_accounts()) - const request3 = PERMS.approvedRequest(REQUEST_IDS.c, PERMS.requests.eth_accounts()) + const request1 = PERMS.approvedRequest( + REQUEST_IDS.a, + PERMS.requests.eth_accounts(), + ) + const request2 = PERMS.approvedRequest( + REQUEST_IDS.b, + PERMS.requests.eth_accounts(), + ) + const request3 = PERMS.approvedRequest( + REQUEST_IDS.c, + PERMS.requests.eth_accounts(), + ) let perms1, perms2 - const approval1 = assert.doesNotReject( - async () => { - perms1 = await requestUserApproval(REQUEST_IDS.a, DOMAINS.a.origin) - }, - 'should not reject request', - ) + const approval1 = assert.doesNotReject(async () => { + perms1 = await requestUserApproval(REQUEST_IDS.a, DOMAINS.a.origin) + }, 'should not reject request') - const approval2 = assert.doesNotReject( - async () => { - perms2 = await requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin) - }, - 'should not reject request', - ) + const approval2 = assert.doesNotReject(async () => { + perms2 = await requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin) + }, 'should not reject request') // approve out of order - await permController.approvePermissionsRequest(request2, ACCOUNTS.b.permitted) + await permController.approvePermissionsRequest( + request2, + ACCOUNTS.b.permitted, + ) // add a non-existing request to the mix - await permController.approvePermissionsRequest(request3, ACCOUNTS.c.permitted) - await permController.approvePermissionsRequest(request1, ACCOUNTS.a.permitted) + await permController.approvePermissionsRequest( + request3, + ACCOUNTS.c.permitted, + ) + await permController.approvePermissionsRequest( + request1, + ACCOUNTS.a.permitted, + ) await approval1 await approval2 assert.deepEqual( - perms1, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), + perms1, + PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), 'first request should produce expected approved permissions', ) assert.deepEqual( - perms2, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), + perms2, + PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), 'second request should produce expected approved permissions', ) assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'pending approvals should be empty after approvals', ) }) }) describe('rejectPermissionsRequest', function () { - let permController, requestUserApproval beforeEach(async function () { @@ -1104,9 +1183,9 @@ describe('permissions controller', function () { }) it('does nothing if called on non-existing request', async function () { - assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'pending approvals should be empty on init', ) @@ -1116,13 +1195,13 @@ describe('permissions controller', function () { ) assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'pending approvals should still be empty after request', ) }) it('rejects single existing request', async function () { - const requestRejection = assert.rejects( requestUserApproval(REQUEST_IDS.a), ERRORS.rejectPermissionsRequest.rejection(), @@ -1133,13 +1212,13 @@ describe('permissions controller', function () { await requestRejection assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'pending approvals should be empty after rejection', ) }) it('rejects requests regardless of order', async function () { - const requestRejection1 = assert.rejects( requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin), ERRORS.rejectPermissionsRequest.rejection(), @@ -1162,7 +1241,8 @@ describe('permissions controller', function () { await requestRejection2 assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'pending approvals should be empty after approval', ) }) @@ -1170,7 +1250,6 @@ describe('permissions controller', function () { // see permissions-middleware-test for testing the middleware itself describe('createMiddleware', function () { - let permController, clock beforeEach(function () { @@ -1183,7 +1262,6 @@ describe('permissions controller', function () { }) it('should throw on bad origin', function () { - assert.throws( () => permController.createMiddleware({ origin: {} }), ERRORS.createMiddleware.badOrigin(), @@ -1204,62 +1282,52 @@ describe('permissions controller', function () { }) it('should create a middleware', function () { - let middleware - assert.doesNotThrow( - () => { - middleware = permController.createMiddleware({ origin: DOMAINS.a.origin }) - }, - 'should not throw', - ) + assert.doesNotThrow(() => { + middleware = permController.createMiddleware({ + origin: DOMAINS.a.origin, + }) + }, 'should not throw') + + assert.equal(typeof middleware, 'function', 'should return function') assert.equal( - typeof middleware, 'function', - 'should return function', - ) - - assert.equal( - middleware.name, 'engineAsMiddleware', + middleware.name, + 'engineAsMiddleware', 'function name should be "engineAsMiddleware"', ) }) it('should create a middleware with extensionId', function () { - const extensionId = 'fooExtension' let middleware - assert.doesNotThrow( - () => { - middleware = permController.createMiddleware({ - origin: DOMAINS.a.origin, - extensionId, - }) - }, - 'should not throw', - ) + assert.doesNotThrow(() => { + middleware = permController.createMiddleware({ + origin: DOMAINS.a.origin, + extensionId, + }) + }, 'should not throw') + + assert.equal(typeof middleware, 'function', 'should return function') assert.equal( - typeof middleware, 'function', - 'should return function', - ) - - assert.equal( - middleware.name, 'engineAsMiddleware', + middleware.name, + 'engineAsMiddleware', 'function name should be "engineAsMiddleware"', ) const metadataStore = permController.store.getState()[METADATA_STORE_KEY] assert.deepEqual( - metadataStore[DOMAINS.a.origin], { extensionId, lastUpdated: 1 }, + metadataStore[DOMAINS.a.origin], + { extensionId, lastUpdated: 1 }, 'metadata should be stored', ) }) }) describe('notifyAccountsChanged', function () { - let notifications, permController beforeEach(function () { @@ -1269,7 +1337,6 @@ describe('permissions controller', function () { }) it('notifyAccountsChanged records history and sends notification', async function () { - permController.notifyAccountsChanged( DOMAINS.a.origin, ACCOUNTS.a.permitted, @@ -1288,42 +1355,28 @@ describe('permissions controller', function () { }) it('notifyAccountsChanged throws on invalid origin', async function () { - assert.throws( - () => permController.notifyAccountsChanged( - 4, - ACCOUNTS.a.permitted, - ), + () => permController.notifyAccountsChanged(4, ACCOUNTS.a.permitted), ERRORS.notifyAccountsChanged.invalidOrigin(4), 'should throw expected error for non-string origin', ) assert.throws( - () => permController.notifyAccountsChanged( - '', - ACCOUNTS.a.permitted, - ), + () => permController.notifyAccountsChanged('', ACCOUNTS.a.permitted), ERRORS.notifyAccountsChanged.invalidOrigin(''), 'should throw expected error for empty string origin', ) }) it('notifyAccountsChanged throws on invalid accounts', async function () { - assert.throws( - () => permController.notifyAccountsChanged( - DOMAINS.a.origin, - 4, - ), + () => permController.notifyAccountsChanged(DOMAINS.a.origin, 4), ERRORS.notifyAccountsChanged.invalidAccounts(), 'should throw expected error for truthy non-array accounts', ) assert.throws( - () => permController.notifyAccountsChanged( - DOMAINS.a.origin, - null, - ), + () => permController.notifyAccountsChanged(DOMAINS.a.origin, null), ERRORS.notifyAccountsChanged.invalidAccounts(), 'should throw expected error for falsy non-array accounts', ) @@ -1331,10 +1384,9 @@ describe('permissions controller', function () { }) describe('addDomainMetadata', function () { - let permController, clock - function getMockMetadata (size) { + function getMockMetadata(size) { const dummyData = {} for (let i = 0; i < size; i++) { const key = i.toString() @@ -1354,7 +1406,6 @@ describe('permissions controller', function () { }) it('calls setter function with expected new state when adding domain', function () { - permController.store.getState = sinon.fake.returns({ [METADATA_STORE_KEY]: { [DOMAINS.a.origin]: { @@ -1370,12 +1421,12 @@ describe('permissions controller', function () { 'should have called store.getState', ) assert.equal( - permController._setDomainMetadata.getCalls().length, 1, + permController._setDomainMetadata.getCalls().length, + 1, 'should have called _setDomainMetadata once', ) - assert.deepEqual( - permController._setDomainMetadata.lastCall.args, - [{ + assert.deepEqual(permController._setDomainMetadata.lastCall.args, [ + { [DOMAINS.a.origin]: { foo: 'bar', }, @@ -1384,12 +1435,11 @@ describe('permissions controller', function () { host: DOMAINS.b.host, lastUpdated: 1, }, - }], - ) + }, + ]) }) it('calls setter function with expected new states when updating existing domain', function () { - permController.store.getState = sinon.fake.returns({ [METADATA_STORE_KEY]: { [DOMAINS.a.origin]: { @@ -1408,12 +1458,12 @@ describe('permissions controller', function () { 'should have called store.getState', ) assert.equal( - permController._setDomainMetadata.getCalls().length, 1, + permController._setDomainMetadata.getCalls().length, + 1, 'should have called _setDomainMetadata once', ) - assert.deepEqual( - permController._setDomainMetadata.lastCall.args, - [{ + assert.deepEqual(permController._setDomainMetadata.lastCall.args, [ + { [DOMAINS.a.origin]: { foo: 'bar', }, @@ -1423,12 +1473,11 @@ describe('permissions controller', function () { host: DOMAINS.b.host, lastUpdated: 1, }, - }], - ) + }, + ]) }) it('pops metadata on add when too many origins are pending', function () { - sinon.spy(permController._pendingSiteMetadata, 'delete') const mockMetadata = getMockMetadata(METADATA_CACHE_MAX_SIZE) @@ -1462,26 +1511,26 @@ describe('permissions controller', function () { delete expectedMetadata[expectedDeletedOrigin] assert.ok( - permController._pendingSiteMetadata.delete.calledOnceWithExactly(expectedDeletedOrigin), + permController._pendingSiteMetadata.delete.calledOnceWithExactly( + expectedDeletedOrigin, + ), 'should have called _pendingSiteMetadata.delete once', ) assert.equal( - permController._setDomainMetadata.getCalls().length, 1, + permController._setDomainMetadata.getCalls().length, + 1, 'should have called _setDomainMetadata once', ) - assert.deepEqual( - permController._setDomainMetadata.lastCall.args, - [expectedMetadata], - ) + assert.deepEqual(permController._setDomainMetadata.lastCall.args, [ + expectedMetadata, + ]) }) }) describe('_trimDomainMetadata', function () { - const permController = initPermController() it('trims domain metadata for domains without permissions', function () { - const metadataArg = { [DOMAINS.a.origin]: {}, [DOMAINS.b.origin]: {}, @@ -1494,7 +1543,8 @@ describe('permissions controller', function () { const metadataResult = permController._trimDomainMetadata(metadataArg) assert.equal( - permController.permissions.getDomains.getCalls().length, 1, + permController.permissions.getDomains.getCalls().length, + 1, 'should have called permissions.getDomains once', ) assert.deepEqual( @@ -1508,7 +1558,6 @@ describe('permissions controller', function () { }) describe('miscellanea and edge cases', function () { - let permController beforeEach(function () { @@ -1516,18 +1565,21 @@ describe('permissions controller', function () { }) it('requestAccountsPermissionWithId calls _requestAccountsPermission with an explicit request ID', async function () { - const _requestPermissions = sinon.stub(permController, '_requestPermissions').resolves() + const _requestPermissions = sinon + .stub(permController, '_requestPermissions') + .resolves() await permController.requestAccountsPermissionWithId('example.com') - assert.ok(_requestPermissions.calledOnceWithExactly( - sinon.match.object.and(sinon.match.has('origin')), - { eth_accounts: {} }, - sinon.match.string.and(sinon.match.truthy), - )) + assert.ok( + _requestPermissions.calledOnceWithExactly( + sinon.match.object.and(sinon.match.has('origin')), + { eth_accounts: {} }, + sinon.match.string.and(sinon.match.truthy), + ), + ) _requestPermissions.restore() }) it('_addPendingApproval: should throw if adding origin twice', function () { - const id = nanoid() const origin = DOMAINS.a @@ -1542,12 +1594,14 @@ describe('permissions controller', function () { ) assert.equal( - permController.pendingApprovals.size, 1, + permController.pendingApprovals.size, + 1, 'pending approvals should have single entry', ) assert.equal( - permController.pendingApprovalOrigins.size, 1, + permController.pendingApprovalOrigins.size, + 1, 'pending approval origins should have single item', ) diff --git a/test/unit/app/controllers/permissions/permissions-log-controller-test.js b/test/unit/app/controllers/permissions/permissions-log-controller-test.js index 6f9fc104b..ac498a0e0 100644 --- a/test/unit/app/controllers/permissions/permissions-log-controller-test.js +++ b/test/unit/app/controllers/permissions/permissions-log-controller-test.js @@ -3,28 +3,18 @@ import ObservableStore from 'obs-store' import nanoid from 'nanoid' import { useFakeTimers } from 'sinon' -import PermissionsLogController - from '../../../../../app/scripts/controllers/permissions/permissionsLog' +import PermissionsLogController from '../../../../../app/scripts/controllers/permissions/permissionsLog' import { LOG_LIMIT, LOG_METHOD_TYPES, } from '../../../../../app/scripts/controllers/permissions/enums' -import { - validateActivityEntry, -} from './helpers' +import { validateActivityEntry } from './helpers' -import { - constants, - getters, - noop, -} from './mocks' +import { constants, getters, noop } from './mocks' -const { - PERMS, - RPC_REQUESTS, -} = getters +const { PERMS, RPC_REQUESTS } = getters const { ACCOUNTS, @@ -72,9 +62,7 @@ const getSavedMockNext = (arr) => (handler) => { } describe('permissions log', function () { - describe('activity log', function () { - let permLog, logMiddleware beforeEach(function () { @@ -83,7 +71,6 @@ describe('permissions log', function () { }) it('records activity for restricted methods', function () { - let log, req, res // test_method, success @@ -99,8 +86,11 @@ describe('permissions log', function () { assert.equal(log.length, 1, 'log should have single entry') validateActivityEntry( - entry1, { ...req }, { ...res }, - LOG_METHOD_TYPES.restricted, true, + entry1, + { ...req }, + { ...res }, + LOG_METHOD_TYPES.restricted, + true, ) // eth_accounts, failure @@ -116,8 +106,11 @@ describe('permissions log', function () { assert.equal(log.length, 2, 'log should have 2 entries') validateActivityEntry( - entry2, { ...req }, { ...res }, - LOG_METHOD_TYPES.restricted, false, + entry2, + { ...req }, + { ...res }, + LOG_METHOD_TYPES.restricted, + false, ) // eth_requestAccounts, success @@ -133,8 +126,11 @@ describe('permissions log', function () { assert.equal(log.length, 3, 'log should have 3 entries') validateActivityEntry( - entry3, { ...req }, { ...res }, - LOG_METHOD_TYPES.restricted, true, + entry3, + { ...req }, + { ...res }, + LOG_METHOD_TYPES.restricted, + true, ) // test_method, no response @@ -150,8 +146,11 @@ describe('permissions log', function () { assert.equal(log.length, 4, 'log should have 4 entries') validateActivityEntry( - entry4, { ...req }, null, - LOG_METHOD_TYPES.restricted, false, + entry4, + { ...req }, + null, + LOG_METHOD_TYPES.restricted, + false, ) // validate final state @@ -163,7 +162,6 @@ describe('permissions log', function () { }) it('handles responses added out of order', function () { - let log const handlerArray = [] @@ -194,11 +192,12 @@ describe('permissions log', function () { const entry2 = log[1] const entry3 = log[2] assert.ok( - ( - entry1.id === id1 && entry1.response === null && - entry2.id === id2 && entry2.response === null && - entry3.id === id3 && entry3.response === null - ), + entry1.id === id1 && + entry1.response === null && + entry2.id === id2 && + entry2.response === null && + entry3.id === id3 && + entry3.response === null, 'all entries should be in correct order and without responses', ) @@ -215,23 +214,31 @@ describe('permissions log', function () { log = permLog.getActivityLog() validateActivityEntry( - log[0], { ...req, id: id1 }, { ...res1 }, - LOG_METHOD_TYPES.restricted, true, + log[0], + { ...req, id: id1 }, + { ...res1 }, + LOG_METHOD_TYPES.restricted, + true, ) validateActivityEntry( - log[1], { ...req, id: id2 }, { ...res2 }, - LOG_METHOD_TYPES.restricted, true, + log[1], + { ...req, id: id2 }, + { ...res2 }, + LOG_METHOD_TYPES.restricted, + true, ) validateActivityEntry( - log[2], { ...req, id: id3 }, { ...res3 }, - LOG_METHOD_TYPES.restricted, true, + log[2], + { ...req, id: id3 }, + { ...res3 }, + LOG_METHOD_TYPES.restricted, + true, ) }) it('handles a lack of response', function () { - let req = RPC_REQUESTS.test_method(DOMAINS.a.origin) req.id = REQUEST_IDS.a let res = { foo: 'bar' } @@ -244,8 +251,11 @@ describe('permissions log', function () { assert.equal(log.length, 1, 'log should have single entry') validateActivityEntry( - entry1, { ...req }, null, - LOG_METHOD_TYPES.restricted, true, + entry1, + { ...req }, + null, + LOG_METHOD_TYPES.restricted, + true, ) // next request should be handled as normal @@ -259,8 +269,11 @@ describe('permissions log', function () { const entry2 = log[1] assert.equal(log.length, 2, 'log should have 2 entries') validateActivityEntry( - entry2, { ...req }, { ...res }, - LOG_METHOD_TYPES.restricted, true, + entry2, + { ...req }, + { ...res }, + LOG_METHOD_TYPES.restricted, + true, ) // validate final state @@ -269,12 +282,14 @@ describe('permissions log', function () { }) it('ignores expected methods', function () { - let log = permLog.getActivityLog() assert.equal(log.length, 0, 'log should be empty') const res = { foo: 'bar' } - const req1 = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, 'foobar') + const req1 = RPC_REQUESTS.wallet_sendDomainMetadata( + DOMAINS.c.origin, + 'foobar', + ) const req2 = RPC_REQUESTS.custom(DOMAINS.b.origin, 'eth_getBlockNumber') const req3 = RPC_REQUESTS.custom(DOMAINS.b.origin, 'net_version') @@ -287,7 +302,6 @@ describe('permissions log', function () { }) it('enforces log limit', function () { - const req = RPC_REQUESTS.test_method(DOMAINS.a.origin) const res = { foo: 'bar' } @@ -301,12 +315,17 @@ describe('permissions log', function () { // check last entry valid let log = permLog.getActivityLog() assert.equal( - log.length, LOG_LIMIT, 'log should have LOG_LIMIT num entries', + log.length, + LOG_LIMIT, + 'log should have LOG_LIMIT num entries', ) validateActivityEntry( - log[LOG_LIMIT - 1], { ...req, id: lastId }, res, - LOG_METHOD_TYPES.restricted, true, + log[LOG_LIMIT - 1], + { ...req, id: lastId }, + res, + LOG_METHOD_TYPES.restricted, + true, ) // store the id of the current second entry @@ -319,24 +338,31 @@ describe('permissions log', function () { // check log length log = permLog.getActivityLog() assert.equal( - log.length, LOG_LIMIT, 'log should have LOG_LIMIT num entries', + log.length, + LOG_LIMIT, + 'log should have LOG_LIMIT num entries', ) // check first and last entries validateActivityEntry( - log[0], { ...req, id: nextFirstId }, res, - LOG_METHOD_TYPES.restricted, true, + log[0], + { ...req, id: nextFirstId }, + res, + LOG_METHOD_TYPES.restricted, + true, ) validateActivityEntry( - log[LOG_LIMIT - 1], { ...req, id: lastId }, res, - LOG_METHOD_TYPES.restricted, true, + log[LOG_LIMIT - 1], + { ...req, id: lastId }, + res, + LOG_METHOD_TYPES.restricted, + true, ) }) }) describe('permissions history', function () { - let permLog, logMiddleware beforeEach(function () { @@ -350,11 +376,11 @@ describe('permissions log', function () { }) it('only updates history on responses', function () { - let permHistory const req = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.test_method, + DOMAINS.a.origin, + PERM_NAMES.test_method, ) const res = { result: [PERMS.granted.test_method()] } @@ -369,7 +395,8 @@ describe('permissions log', function () { permHistory = permLog.getHistory() assert.equal( - Object.keys(permHistory).length, 1, + Object.keys(permHistory).length, + 1, 'history should have single origin', ) assert.ok( @@ -379,9 +406,9 @@ describe('permissions log', function () { }) it('ignores malformed permissions requests', function () { - const req = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.test_method, + DOMAINS.a.origin, + PERM_NAMES.test_method, ) delete req.params const res = { result: [PERMS.granted.test_method()] } @@ -389,15 +416,19 @@ describe('permissions log', function () { // no params => no response logMiddleware({ ...req }, { ...res }) - assert.deepEqual(permLog.getHistory(), {}, 'history should not have been updated') + assert.deepEqual( + permLog.getHistory(), + {}, + 'history should not have been updated', + ) }) it('records and updates account history as expected', async function () { - let permHistory const req = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.eth_accounts, + DOMAINS.a.origin, + PERM_NAMES.eth_accounts, ) const res = { result: [PERMS.granted.eth_accounts(ACCOUNTS.a.permitted)], @@ -433,9 +464,9 @@ describe('permissions log', function () { }) it('handles eth_accounts response without caveats', async function () { - const req = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.eth_accounts, + DOMAINS.a.origin, + PERM_NAMES.eth_accounts, ) const res = { result: [PERMS.granted.eth_accounts(ACCOUNTS.a.permitted)], @@ -447,15 +478,16 @@ describe('permissions log', function () { // validate history assert.deepEqual( - permLog.getHistory(), EXPECTED_HISTORIES.case2[0], + permLog.getHistory(), + EXPECTED_HISTORIES.case2[0], 'should have expected history', ) }) it('handles extra caveats for eth_accounts', async function () { - const req = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.eth_accounts, + DOMAINS.a.origin, + PERM_NAMES.eth_accounts, ) const res = { result: [PERMS.granted.eth_accounts(ACCOUNTS.a.permitted)], @@ -476,9 +508,9 @@ describe('permissions log', function () { // wallet_requestPermissions returns all permissions approved for the // requesting origin, including old ones it('handles unrequested permissions on the response', async function () { - const req = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.eth_accounts, + DOMAINS.a.origin, + PERM_NAMES.eth_accounts, ) const res = { result: [ @@ -499,14 +531,12 @@ describe('permissions log', function () { }) it('does not update history if no new permissions are approved', async function () { - let req = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.test_method, + DOMAINS.a.origin, + PERM_NAMES.test_method, ) let res = { - result: [ - PERMS.granted.test_method(), - ], + result: [PERMS.granted.test_method()], } logMiddleware({ ...req }, { ...res }) @@ -524,12 +554,11 @@ describe('permissions log', function () { clock.tick(1) req = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.eth_accounts, + DOMAINS.a.origin, + PERM_NAMES.eth_accounts, ) res = { - result: [ - PERMS.granted.test_method(), - ], + result: [PERMS.granted.test_method()], } logMiddleware({ ...req }, { ...res }) @@ -544,7 +573,6 @@ describe('permissions log', function () { }) it('records and updates history for multiple origins, regardless of response order', async function () { - let permHistory // make first round of requests @@ -555,7 +583,8 @@ describe('permissions log', function () { // first origin round1.push({ req: RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.test_method, + DOMAINS.a.origin, + PERM_NAMES.test_method, ), res: { result: [PERMS.granted.test_method()], @@ -565,7 +594,8 @@ describe('permissions log', function () { // second origin round1.push({ req: RPC_REQUESTS.requestPermission( - DOMAINS.b.origin, PERM_NAMES.eth_accounts, + DOMAINS.b.origin, + PERM_NAMES.eth_accounts, ), res: { result: [PERMS.granted.eth_accounts(ACCOUNTS.b.permitted)], @@ -599,7 +629,8 @@ describe('permissions log', function () { permHistory = permLog.getHistory() assert.deepEqual( - permHistory, EXPECTED_HISTORIES.case3[0], + permHistory, + EXPECTED_HISTORIES.case3[0], 'should have expected history', ) @@ -613,7 +644,8 @@ describe('permissions log', function () { // first origin round2.push({ req: RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.test_method, + DOMAINS.a.origin, + PERM_NAMES.test_method, ), res: { result: [PERMS.granted.test_method()], @@ -628,9 +660,7 @@ describe('permissions log', function () { [PERM_NAMES.eth_accounts]: {}, }), res: { - result: [ - PERMS.granted.eth_accounts(ACCOUNTS.b.permitted), - ], + result: [PERMS.granted.eth_accounts(ACCOUNTS.b.permitted)], }, }) @@ -643,7 +673,8 @@ describe('permissions log', function () { permHistory = permLog.getHistory() assert.deepEqual( - permHistory, EXPECTED_HISTORIES.case3[1], + permHistory, + EXPECTED_HISTORIES.case3[1], 'should have expected history', ) }) diff --git a/test/unit/app/controllers/permissions/permissions-middleware-test.js b/test/unit/app/controllers/permissions/permissions-middleware-test.js index 8b56eb764..2888d3da5 100644 --- a/test/unit/app/controllers/permissions/permissions-middleware-test.js +++ b/test/unit/app/controllers/permissions/permissions-middleware-test.js @@ -1,18 +1,11 @@ import { strict as assert } from 'assert' import sinon from 'sinon' -import { - METADATA_STORE_KEY, -} from '../../../../../app/scripts/controllers/permissions/enums' +import { METADATA_STORE_KEY } from '../../../../../app/scripts/controllers/permissions/enums' -import { - PermissionsController, -} from '../../../../../app/scripts/controllers/permissions' +import { PermissionsController } from '../../../../../app/scripts/controllers/permissions' -import { - getUserApprovalPromise, - grantPermissions, -} from './helpers' +import { getUserApprovalPromise, grantPermissions } from './helpers' import { constants, @@ -21,24 +14,23 @@ import { getPermissionsMiddleware, } from './mocks' -const { - CAVEATS, - ERRORS, - PERMS, - RPC_REQUESTS, -} = getters +const { CAVEATS, ERRORS, PERMS, RPC_REQUESTS } = getters -const { - ACCOUNTS, - DOMAINS, - PERM_NAMES, -} = constants +const { ACCOUNTS, DOMAINS, PERM_NAMES } = constants const validatePermission = (perm, name, origin, caveats) => { - assert.equal(name, perm.parentCapability, 'should have expected permission name') + assert.equal( + name, + perm.parentCapability, + 'should have expected permission name', + ) assert.equal(origin, perm.invoker, 'should have expected permission origin') if (caveats) { - assert.deepEqual(caveats, perm.caveats, 'should have expected permission caveats') + assert.deepEqual( + caveats, + perm.caveats, + 'should have expected permission caveats', + ) } else { assert.ok(!perm.caveats, 'should not have any caveats') } @@ -51,9 +43,7 @@ const initPermController = () => { } describe('permissions middleware', function () { - describe('wallet_requestPermissions', function () { - let permController beforeEach(function () { @@ -62,11 +52,14 @@ describe('permissions middleware', function () { }) it('grants permissions on user approval', async function () { - - const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin) + const aMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.a.origin, + ) const req = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.eth_accounts, + DOMAINS.a.origin, + PERM_NAMES.eth_accounts, ) const res = {} @@ -80,14 +73,21 @@ describe('permissions middleware', function () { await userApprovalPromise assert.equal( - permController.pendingApprovals.size, 1, + permController.pendingApprovals.size, + 1, 'perm controller should have single pending approval', ) const id = permController.pendingApprovals.keys().next().value - const approvedReq = PERMS.approvedRequest(id, PERMS.requests.eth_accounts()) + const approvedReq = PERMS.approvedRequest( + id, + PERMS.requests.eth_accounts(), + ) - await permController.approvePermissionsRequest(approvedReq, ACCOUNTS.a.permitted) + await permController.approvePermissionsRequest( + approvedReq, + ACCOUNTS.a.permitted, + ) await pendingApproval assert.ok( @@ -96,7 +96,8 @@ describe('permissions middleware', function () { ) assert.equal( - res.result.length, 1, + res.result.length, + 1, 'origin should have single approved permission', ) @@ -109,26 +110,31 @@ describe('permissions middleware', function () { const aAccounts = await permController.getAccounts(DOMAINS.a.origin) assert.deepEqual( - aAccounts, [ACCOUNTS.a.primary], + aAccounts, + [ACCOUNTS.a.primary], 'origin should have correct accounts', ) assert.ok( permController.notifyAccountsChanged.calledOnceWith( - DOMAINS.a.origin, aAccounts, + DOMAINS.a.origin, + aAccounts, ), 'expected notification call should have been made', ) }) it('handles serial approved requests that overwrite existing permissions', async function () { - - const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin) + const aMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.a.origin, + ) // create first request const req1 = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.eth_accounts, + DOMAINS.a.origin, + PERM_NAMES.eth_accounts, ) const res1 = {} @@ -145,9 +151,15 @@ describe('permissions middleware', function () { await userApprovalPromise const id1 = permController.pendingApprovals.keys().next().value - const approvedReq1 = PERMS.approvedRequest(id1, PERMS.requests.eth_accounts()) + const approvedReq1 = PERMS.approvedRequest( + id1, + PERMS.requests.eth_accounts(), + ) - await permController.approvePermissionsRequest(approvedReq1, ACCOUNTS.a.permitted) + await permController.approvePermissionsRequest( + approvedReq1, + ACCOUNTS.a.permitted, + ) await pendingApproval1 assert.ok( @@ -156,7 +168,8 @@ describe('permissions middleware', function () { ) assert.equal( - res1.result.length, 1, + res1.result.length, + 1, 'origin should have single approved permission', ) @@ -169,13 +182,15 @@ describe('permissions middleware', function () { const accounts1 = await permController.getAccounts(DOMAINS.a.origin) assert.deepEqual( - accounts1, [ACCOUNTS.a.primary], + accounts1, + [ACCOUNTS.a.primary], 'origin should have correct accounts', ) assert.ok( permController.notifyAccountsChanged.calledOnceWith( - DOMAINS.a.origin, accounts1, + DOMAINS.a.origin, + accounts1, ), 'expected notification call should have been made', ) @@ -187,9 +202,9 @@ describe('permissions middleware', function () { ...PERMS.requests.test_method(), } - const req2 = RPC_REQUESTS.requestPermissions( - DOMAINS.a.origin, { ...requestedPerms2 }, - ) + const req2 = RPC_REQUESTS.requestPermissions(DOMAINS.a.origin, { + ...requestedPerms2, + }) const res2 = {} // send, approve, and validate second request @@ -207,7 +222,10 @@ describe('permissions middleware', function () { const id2 = permController.pendingApprovals.keys().next().value const approvedReq2 = PERMS.approvedRequest(id2, { ...requestedPerms2 }) - await permController.approvePermissionsRequest(approvedReq2, ACCOUNTS.b.permitted) + await permController.approvePermissionsRequest( + approvedReq2, + ACCOUNTS.b.permitted, + ) await pendingApproval2 assert.ok( @@ -216,7 +234,8 @@ describe('permissions middleware', function () { ) assert.equal( - res2.result.length, 2, + res2.result.length, + 2, 'origin should have single approved permission', ) @@ -235,29 +254,35 @@ describe('permissions middleware', function () { const accounts2 = await permController.getAccounts(DOMAINS.a.origin) assert.deepEqual( - accounts2, [ACCOUNTS.b.primary], + accounts2, + [ACCOUNTS.b.primary], 'origin should have correct accounts', ) assert.equal( - permController.notifyAccountsChanged.callCount, 2, + permController.notifyAccountsChanged.callCount, + 2, 'should have called notification method 2 times in total', ) assert.ok( permController.notifyAccountsChanged.lastCall.calledWith( - DOMAINS.a.origin, accounts2, + DOMAINS.a.origin, + accounts2, ), 'expected notification call should have been made', ) }) it('rejects permissions on user rejection', async function () { - - const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin) + const aMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.a.origin, + ) const req = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.eth_accounts, + DOMAINS.a.origin, + PERM_NAMES.eth_accounts, ) const res = {} @@ -274,7 +299,8 @@ describe('permissions middleware', function () { await userApprovalPromise assert.equal( - permController.pendingApprovals.size, 1, + permController.pendingApprovals.size, + 1, 'perm controller should have single pending approval', ) @@ -284,16 +310,15 @@ describe('permissions middleware', function () { await requestRejection assert.ok( - ( - !res.result && res.error && - res.error.message === expectedError.message - ), + !res.result && res.error && res.error.message === expectedError.message, 'response should have expected error and no result', ) const aAccounts = await permController.getAccounts(DOMAINS.a.origin) assert.deepEqual( - aAccounts, [], 'origin should have have correct accounts', + aAccounts, + [], + 'origin should have have correct accounts', ) assert.ok( @@ -303,15 +328,15 @@ describe('permissions middleware', function () { }) it('rejects requests with unknown permissions', async function () { - - const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin) - - const req = RPC_REQUESTS.requestPermissions( - DOMAINS.a.origin, { - ...PERMS.requests.does_not_exist(), - ...PERMS.requests.test_method(), - }, + const aMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.a.origin, ) + + const req = RPC_REQUESTS.requestPermissions(DOMAINS.a.origin, { + ...PERMS.requests.does_not_exist(), + ...PERMS.requests.test_method(), + }) const res = {} const expectedError = ERRORS.rejectPermissionsRequest.methodNotFound( @@ -325,15 +350,13 @@ describe('permissions middleware', function () { ) assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'perm controller should have no pending approvals', ) assert.ok( - ( - !res.result && res.error && - res.error.message === expectedError.message - ), + !res.result && res.error && res.error.message === expectedError.message, 'response should have expected error and no result', ) @@ -344,18 +367,24 @@ describe('permissions middleware', function () { }) it('accepts only a single pending permissions request per origin', async function () { - const expectedError = ERRORS.pendingApprovals.requestAlreadyPending() // two middlewares for two origins - const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin) - const bMiddleware = getPermissionsMiddleware(permController, DOMAINS.b.origin) + const aMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.a.origin, + ) + const bMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.b.origin, + ) // create and start processing first request for first origin const reqA1 = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.test_method, + DOMAINS.a.origin, + PERM_NAMES.test_method, ) const resA1 = {} @@ -371,7 +400,8 @@ describe('permissions middleware', function () { // create and start processing first request for second origin const reqB1 = RPC_REQUESTS.requestPermission( - DOMAINS.b.origin, PERM_NAMES.test_method, + DOMAINS.b.origin, + PERM_NAMES.test_method, ) const resB1 = {} @@ -385,7 +415,8 @@ describe('permissions middleware', function () { await userApprovalPromise assert.equal( - permController.pendingApprovals.size, 2, + permController.pendingApprovals.size, + 2, 'perm controller should have expected number of pending approvals', ) @@ -393,7 +424,8 @@ describe('permissions middleware', function () { // which should throw const reqA2 = RPC_REQUESTS.requestPermission( - DOMAINS.a.origin, PERM_NAMES.test_method, + DOMAINS.a.origin, + PERM_NAMES.test_method, ) const resA2 = {} @@ -409,17 +441,17 @@ describe('permissions middleware', function () { await requestApprovalFail assert.ok( - ( - !resA2.result && resA2.error && - resA2.error.message === expectedError.message - ), + !resA2.result && + resA2.error && + resA2.error.message === expectedError.message, 'response should have expected error and no result', ) // first requests for both origins should remain assert.equal( - permController.pendingApprovals.size, 2, + permController.pendingApprovals.size, + 2, 'perm controller should have expected number of pending approvals', ) @@ -438,7 +470,8 @@ describe('permissions middleware', function () { 'first response should have result and no error', ) assert.equal( - resA1.result.length, 1, + resA1.result.length, + 1, 'first origin should have single approved permission', ) @@ -447,19 +480,20 @@ describe('permissions middleware', function () { 'second response should have result and no error', ) assert.equal( - resB1.result.length, 1, + resB1.result.length, + 1, 'second origin should have single approved permission', ) assert.equal( - permController.pendingApprovals.size, 0, + permController.pendingApprovals.size, + 0, 'perm controller should have expected number of pending approvals', ) }) }) describe('restricted methods', function () { - let permController beforeEach(function () { @@ -467,8 +501,10 @@ describe('permissions middleware', function () { }) it('prevents restricted method access for unpermitted domain', async function () { - - const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin) + const aMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.a.origin, + ) const req = RPC_REQUESTS.test_method(DOMAINS.a.origin) const res = {} @@ -482,27 +518,27 @@ describe('permissions middleware', function () { ) assert.ok( - ( - !res.result && res.error && - res.error.code === expectedError.code - ), + !res.result && res.error && res.error.code === expectedError.code, 'response should have expected error and no result', ) }) it('allows restricted method access for permitted domain', async function () { + const bMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.b.origin, + ) - const bMiddleware = getPermissionsMiddleware(permController, DOMAINS.b.origin) - - grantPermissions(permController, DOMAINS.b.origin, PERMS.finalizedRequests.test_method()) + grantPermissions( + permController, + DOMAINS.b.origin, + PERMS.finalizedRequests.test_method(), + ) const req = RPC_REQUESTS.test_method(DOMAINS.b.origin, true) const res = {} - await assert.doesNotReject( - bMiddleware(req, res), - 'should not reject', - ) + await assert.doesNotReject(bMiddleware(req, res), 'should not reject') assert.ok( res.result && res.result === 1, @@ -512,7 +548,6 @@ describe('permissions middleware', function () { }) describe('eth_accounts', function () { - let permController beforeEach(function () { @@ -520,57 +555,53 @@ describe('permissions middleware', function () { }) it('returns empty array for non-permitted domain', async function () { - - const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin) + const aMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.a.origin, + ) const req = RPC_REQUESTS.eth_accounts(DOMAINS.a.origin) const res = {} - await assert.doesNotReject( - aMiddleware(req, res), - 'should not reject', - ) + await assert.doesNotReject(aMiddleware(req, res), 'should not reject') assert.ok( res.result && !res.error, 'response should have result and no error', ) - assert.deepEqual( - res.result, [], - 'response should have correct result', - ) + assert.deepEqual(res.result, [], 'response should have correct result') }) it('returns correct accounts for permitted domain', async function () { - - const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin) + const aMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.a.origin, + ) grantPermissions( - permController, DOMAINS.a.origin, + permController, + DOMAINS.a.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), ) const req = RPC_REQUESTS.eth_accounts(DOMAINS.a.origin) const res = {} - await assert.doesNotReject( - aMiddleware(req, res), - 'should not reject', - ) + await assert.doesNotReject(aMiddleware(req, res), 'should not reject') assert.ok( res.result && !res.error, 'response should have result and no error', ) assert.deepEqual( - res.result, [ACCOUNTS.a.primary], + res.result, + [ACCOUNTS.a.primary], 'response should have correct result', ) }) }) describe('eth_requestAccounts', function () { - let permController beforeEach(function () { @@ -578,10 +609,12 @@ describe('permissions middleware', function () { }) it('requests accounts for unpermitted origin, and approves on user approval', async function () { - const userApprovalPromise = getUserApprovalPromise(permController) - const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin) + const aMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.a.origin, + ) const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.a.origin) const res = {} @@ -594,22 +627,32 @@ describe('permissions middleware', function () { await userApprovalPromise assert.equal( - permController.pendingApprovals.size, 1, + permController.pendingApprovals.size, + 1, 'perm controller should have single pending approval', ) const id = permController.pendingApprovals.keys().next().value - const approvedReq = PERMS.approvedRequest(id, PERMS.requests.eth_accounts()) + const approvedReq = PERMS.approvedRequest( + id, + PERMS.requests.eth_accounts(), + ) - await permController.approvePermissionsRequest(approvedReq, ACCOUNTS.a.permitted) + await permController.approvePermissionsRequest( + approvedReq, + ACCOUNTS.a.permitted, + ) // wait for permission to be granted await pendingApproval - const perms = permController.permissions.getPermissionsForDomain(DOMAINS.a.origin) + const perms = permController.permissions.getPermissionsForDomain( + DOMAINS.a.origin, + ) assert.equal( - perms.length, 1, + perms.length, + 1, 'domain should have correct number of permissions', ) @@ -627,22 +670,27 @@ describe('permissions middleware', function () { ) assert.deepEqual( - res.result, [ACCOUNTS.a.primary], + res.result, + [ACCOUNTS.a.primary], 'result should have correct accounts', ) // we should also be able to get the accounts independently const aAccounts = await permController.getAccounts(DOMAINS.a.origin) assert.deepEqual( - aAccounts, [ACCOUNTS.a.primary], 'origin should have have correct accounts', + aAccounts, + [ACCOUNTS.a.primary], + 'origin should have have correct accounts', ) }) it('requests accounts for unpermitted origin, and rejects on user rejection', async function () { - const userApprovalPromise = getUserApprovalPromise(permController) - const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin) + const aMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.a.origin, + ) const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.a.origin) const res = {} @@ -658,7 +706,8 @@ describe('permissions middleware', function () { await userApprovalPromise assert.equal( - permController.pendingApprovals.size, 1, + permController.pendingApprovals.size, + 1, 'perm controller should have single pending approval', ) @@ -668,48 +717,47 @@ describe('permissions middleware', function () { await requestRejection assert.ok( - ( - !res.result && res.error && - res.error.message === expectedError.message - ), + !res.result && res.error && res.error.message === expectedError.message, 'response should have expected error and no result', ) const aAccounts = await permController.getAccounts(DOMAINS.a.origin) assert.deepEqual( - aAccounts, [], 'origin should have have correct accounts', + aAccounts, + [], + 'origin should have have correct accounts', ) }) it('directly returns accounts for permitted domain', async function () { - - const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin) + const cMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.c.origin, + ) grantPermissions( - permController, DOMAINS.c.origin, + permController, + DOMAINS.c.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.c.permitted), ) const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.c.origin) const res = {} - await assert.doesNotReject( - cMiddleware(req, res), - 'should not reject', - ) + await assert.doesNotReject(cMiddleware(req, res), 'should not reject') assert.ok( res.result && !res.error, 'response should have result and no error', ) assert.deepEqual( - res.result, [ACCOUNTS.c.primary], + res.result, + [ACCOUNTS.c.primary], 'response should have correct result', ) }) it('rejects new requests when request already pending', async function () { - let unlock const unlockPromise = new Promise((resolve) => { unlock = resolve @@ -717,10 +765,14 @@ describe('permissions middleware', function () { permController.getUnlockPromise = () => unlockPromise - const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin) + const cMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.c.origin, + ) grantPermissions( - permController, DOMAINS.c.origin, + permController, + DOMAINS.c.origin, PERMS.finalizedRequests.eth_accounts(ACCOUNTS.c.permitted), ) @@ -749,14 +801,14 @@ describe('permissions middleware', function () { 'response should have result and no error', ) assert.deepEqual( - res.result, [ACCOUNTS.c.primary], + res.result, + [ACCOUNTS.c.primary], 'response should have correct result', ) }) }) describe('wallet_sendDomainMetadata', function () { - let permController, clock beforeEach(function () { @@ -769,18 +821,17 @@ describe('permissions middleware', function () { }) it('records domain metadata', async function () { - const name = 'BAZ' - const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin) + const cMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.c.origin, + ) const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, name) const res = {} - await assert.doesNotReject( - cMiddleware(req, res), - 'should not reject', - ) + await assert.doesNotReject(cMiddleware(req, res), 'should not reject') assert.ok(res.result, 'result should be true') @@ -800,20 +851,20 @@ describe('permissions middleware', function () { }) it('records domain metadata and preserves extensionId', async function () { - const extensionId = 'fooExtension' const name = 'BAZ' - const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin, extensionId) + const cMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.c.origin, + extensionId, + ) const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, name) const res = {} - await assert.doesNotReject( - cMiddleware(req, res), - 'should not reject', - ) + await assert.doesNotReject(cMiddleware(req, res), 'should not reject') assert.ok(res.result, 'result should be true') @@ -827,48 +878,48 @@ describe('permissions middleware', function () { }) it('should not record domain metadata if no name', async function () { - const name = null - const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin) + const cMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.c.origin, + ) const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, name) const res = {} - await assert.doesNotReject( - cMiddleware(req, res), - 'should not reject', - ) + await assert.doesNotReject(cMiddleware(req, res), 'should not reject') assert.ok(res.result, 'result should be true') const metadataStore = permController.store.getState()[METADATA_STORE_KEY] assert.deepEqual( - metadataStore, {}, + metadataStore, + {}, 'metadata should not have been added to store', ) }) it('should not record domain metadata if no metadata', async function () { - - const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin) + const cMiddleware = getPermissionsMiddleware( + permController, + DOMAINS.c.origin, + ) const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin) delete req.domainMetadata const res = {} - await assert.doesNotReject( - cMiddleware(req, res), - 'should not reject', - ) + await assert.doesNotReject(cMiddleware(req, res), 'should not reject') assert.ok(res.result, 'result should be true') const metadataStore = permController.store.getState()[METADATA_STORE_KEY] assert.deepEqual( - metadataStore, {}, + metadataStore, + {}, 'metadata should not have been added to store', ) }) diff --git a/test/unit/app/controllers/permissions/restricted-methods-test.js b/test/unit/app/controllers/permissions/restricted-methods-test.js index 54ba3814f..2b7361110 100644 --- a/test/unit/app/controllers/permissions/restricted-methods-test.js +++ b/test/unit/app/controllers/permissions/restricted-methods-test.js @@ -1,8 +1,7 @@ import { strict as assert } from 'assert' import pify from 'pify' -import getRestrictedMethods - from '../../../../../app/scripts/controllers/permissions/restrictedMethods' +import getRestrictedMethods from '../../../../../app/scripts/controllers/permissions/restrictedMethods' describe('restricted methods', function () { describe('eth_accounts', function () { @@ -23,7 +22,8 @@ describe('restricted methods', function () { ) assert.deepEqual( - res, { error: fooError }, + res, + { error: fooError }, 'response should have expected error and no result', ) }) @@ -40,7 +40,11 @@ describe('restricted methods', function () { const res = {} await assert.rejects(ethAccountsMethod(null, res, null)) assert.ok(res.error instanceof Error, 'result should have error') - assert.deepEqual(Object.keys(res), ['error'], 'result should only contain error') + assert.deepEqual( + Object.keys(res), + ['error'], + 'result should only contain error', + ) }) it('should handle missing identity for second account when sorting', async function () { @@ -55,20 +59,21 @@ describe('restricted methods', function () { const res = {} await assert.rejects(ethAccountsMethod(null, res, null)) assert.ok(res.error instanceof Error, 'result should have error') - assert.deepEqual(Object.keys(res), ['error'], 'result should only contain error') + assert.deepEqual( + Object.keys(res), + ['error'], + 'result should only contain error', + ) }) it('should return accounts in keyring order when none are selected', async function () { const keyringAccounts = ['0x7e57e2', '0x7e57e3', '0x7e57e4', '0x7e57e5'] const restrictedMethods = getRestrictedMethods({ getIdentities: () => { - return keyringAccounts.reduce( - (identities, address) => { - identities[address] = {} - return identities - }, - {}, - ) + return keyringAccounts.reduce((identities, address) => { + identities[address] = {} + return identities + }, {}) }, getKeyringAccounts: async () => [...keyringAccounts], }) @@ -76,20 +81,21 @@ describe('restricted methods', function () { const res = {} await ethAccountsMethod(null, res, null) - assert.deepEqual(res, { result: keyringAccounts }, 'should return accounts in correct order') + assert.deepEqual( + res, + { result: keyringAccounts }, + 'should return accounts in correct order', + ) }) it('should return accounts in keyring order when all have same last selected time', async function () { const keyringAccounts = ['0x7e57e2', '0x7e57e3', '0x7e57e4', '0x7e57e5'] const restrictedMethods = getRestrictedMethods({ getIdentities: () => { - return keyringAccounts.reduce( - (identities, address) => { - identities[address] = { lastSelected: 1000 } - return identities - }, - {}, - ) + return keyringAccounts.reduce((identities, address) => { + identities[address] = { lastSelected: 1000 } + return identities + }, {}) }, getKeyringAccounts: async () => [...keyringAccounts], }) @@ -97,7 +103,11 @@ describe('restricted methods', function () { const res = {} await ethAccountsMethod(null, res, null) - assert.deepEqual(res, { result: keyringAccounts }, 'should return accounts in correct order') + assert.deepEqual( + res, + { result: keyringAccounts }, + 'should return accounts in correct order', + ) }) it('should return accounts sorted by last selected (descending)', async function () { @@ -105,13 +115,10 @@ describe('restricted methods', function () { const expectedResult = keyringAccounts.slice().reverse() const restrictedMethods = getRestrictedMethods({ getIdentities: () => { - return keyringAccounts.reduce( - (identities, address, index) => { - identities[address] = { lastSelected: index * 1000 } - return identities - }, - {}, - ) + return keyringAccounts.reduce((identities, address, index) => { + identities[address] = { lastSelected: index * 1000 } + return identities + }, {}) }, getKeyringAccounts: async () => [...keyringAccounts], }) @@ -119,12 +126,28 @@ describe('restricted methods', function () { const res = {} await ethAccountsMethod(null, res, null) - assert.deepEqual(res, { result: expectedResult }, 'should return accounts in correct order') + assert.deepEqual( + res, + { result: expectedResult }, + 'should return accounts in correct order', + ) }) it('should return accounts sorted by last selected (descending) with unselected accounts last, in keyring order', async function () { - const keyringAccounts = ['0x7e57e2', '0x7e57e3', '0x7e57e4', '0x7e57e5', '0x7e57e6'] - const expectedResult = ['0x7e57e4', '0x7e57e2', '0x7e57e3', '0x7e57e5', '0x7e57e6'] + const keyringAccounts = [ + '0x7e57e2', + '0x7e57e3', + '0x7e57e4', + '0x7e57e5', + '0x7e57e6', + ] + const expectedResult = [ + '0x7e57e4', + '0x7e57e2', + '0x7e57e3', + '0x7e57e5', + '0x7e57e6', + ] const restrictedMethods = getRestrictedMethods({ getIdentities: () => { return { @@ -141,7 +164,11 @@ describe('restricted methods', function () { const res = {} await ethAccountsMethod(null, res, null) - assert.deepEqual(res, { result: expectedResult }, 'should return accounts in correct order') + assert.deepEqual( + res, + { result: expectedResult }, + 'should return accounts in correct order', + ) }) }) }) diff --git a/test/unit/app/controllers/preferences-controller-test.js b/test/unit/app/controllers/preferences-controller-test.js index a1ffe5b7b..e041bb582 100644 --- a/test/unit/app/controllers/preferences-controller-test.js +++ b/test/unit/app/controllers/preferences-controller-test.js @@ -11,7 +11,10 @@ describe('preferences controller', function () { beforeEach(function () { network = { providerStore: new ObservableStore({ type: 'mainnet' }) } - preferencesController = new PreferencesController({ migrateAddressBookState, network }) + preferencesController = new PreferencesController({ + migrateAddressBookState, + network, + }) }) afterEach(function () { @@ -20,10 +23,7 @@ describe('preferences controller', function () { describe('setAddresses', function () { it('should keep a map of addresses to names and addresses in the store', function () { - preferencesController.setAddresses([ - '0xda22le', - '0x7e57e2', - ]) + preferencesController.setAddresses(['0xda22le', '0x7e57e2']) const { identities } = preferencesController.store.getState() assert.deepEqual(identities, { @@ -39,10 +39,7 @@ describe('preferences controller', function () { }) it('should create account tokens for each account in the store', function () { - preferencesController.setAddresses([ - '0xda22le', - '0x7e57e2', - ]) + preferencesController.setAddresses(['0xda22le', '0x7e57e2']) const { accountTokens } = preferencesController.store.getState() @@ -53,14 +50,8 @@ describe('preferences controller', function () { }) it('should replace its list of addresses', function () { - preferencesController.setAddresses([ - '0xda22le', - '0x7e57e2', - ]) - preferencesController.setAddresses([ - '0xda22le77', - '0x7e57e277', - ]) + preferencesController.setAddresses(['0xda22le', '0x7e57e2']) + preferencesController.setAddresses(['0xda22le77', '0x7e57e277']) const { identities } = preferencesController.store.getState() assert.deepEqual(identities, { @@ -78,32 +69,29 @@ describe('preferences controller', function () { describe('removeAddress', function () { it('should remove an address from state', function () { - preferencesController.setAddresses([ - '0xda22le', - '0x7e57e2', - ]) + preferencesController.setAddresses(['0xda22le', '0x7e57e2']) preferencesController.removeAddress('0xda22le') - assert.equal(preferencesController.store.getState().identities['0xda22le'], undefined) + assert.equal( + preferencesController.store.getState().identities['0xda22le'], + undefined, + ) }) it('should remove an address from state and respective tokens', function () { - preferencesController.setAddresses([ - '0xda22le', - '0x7e57e2', - ]) + preferencesController.setAddresses(['0xda22le', '0x7e57e2']) preferencesController.removeAddress('0xda22le') - assert.equal(preferencesController.store.getState().accountTokens['0xda22le'], undefined) + assert.equal( + preferencesController.store.getState().accountTokens['0xda22le'], + undefined, + ) }) it('should switch accounts if the selected address is removed', function () { - preferencesController.setAddresses([ - '0xda22le', - '0x7e57e2', - ]) + preferencesController.setAddresses(['0xda22le', '0x7e57e2']) preferencesController.setSelectedAddress('0x7e57e2') preferencesController.removeAddress('0x7e57e2') @@ -114,21 +102,24 @@ describe('preferences controller', function () { describe('setAccountLabel', function () { it('should update a label for the given account', function () { - preferencesController.setAddresses([ - '0xda22le', - '0x7e57e2', - ]) + preferencesController.setAddresses(['0xda22le', '0x7e57e2']) - assert.deepEqual(preferencesController.store.getState().identities['0xda22le'], { - name: 'Account 1', - address: '0xda22le', - }) + assert.deepEqual( + preferencesController.store.getState().identities['0xda22le'], + { + name: 'Account 1', + address: '0xda22le', + }, + ) preferencesController.setAccountLabel('0xda22le', 'Dazzle') - assert.deepEqual(preferencesController.store.getState().identities['0xda22le'], { - name: 'Dazzle', - address: '0xda22le', - }) + assert.deepEqual( + preferencesController.store.getState().identities['0xda22le'], + { + name: 'Dazzle', + address: '0xda22le', + }, + ) }) }) @@ -187,18 +178,23 @@ describe('preferences controller', function () { const symbol = 'ABBR' const decimals = 5 - preferencesController.setAddresses([ - '0x7e57e2', - '0xda22le', - ]) + preferencesController.setAddresses(['0x7e57e2', '0xda22le']) await preferencesController.setSelectedAddress('0x7e57e2') await preferencesController.addToken(address, symbol, decimals) - assert.equal(preferencesController.getTokens().length, 1, 'one token added for 1st address') + assert.equal( + preferencesController.getTokens().length, + 1, + 'one token added for 1st address', + ) await preferencesController.setSelectedAddress('0xda22le') await preferencesController.addToken(address, symbol, decimals) - assert.equal(preferencesController.getTokens().length, 1, 'one token added for 2nd address') + assert.equal( + preferencesController.getTokens().length, + 1, + 'one token added for 2nd address', + ) }) it('should add token per account', async function () { @@ -208,20 +204,25 @@ describe('preferences controller', function () { const symbolSecond = 'ABBB' const decimals = 5 - preferencesController.setAddresses([ - '0x7e57e2', - '0xda22le', - ]) + preferencesController.setAddresses(['0x7e57e2', '0xda22le']) await preferencesController.setSelectedAddress('0x7e57e2') await preferencesController.addToken(addressFirst, symbolFirst, decimals) const tokensFirstAddress = preferencesController.getTokens() await preferencesController.setSelectedAddress('0xda22le') - await preferencesController.addToken(addressSecond, symbolSecond, decimals) + await preferencesController.addToken( + addressSecond, + symbolSecond, + decimals, + ) const tokensSeconAddress = preferencesController.getTokens() - assert.notEqual(tokensFirstAddress, tokensSeconAddress, 'add different tokens for two account and tokens are equal') + assert.notEqual( + tokensFirstAddress, + tokensSeconAddress, + 'add different tokens for two account and tokens are equal', + ) }) it('should add token per network', async function () { @@ -236,10 +237,18 @@ describe('preferences controller', function () { const tokensFirstAddress = preferencesController.getTokens() network.providerStore.updateState({ type: 'rinkeby' }) - await preferencesController.addToken(addressSecond, symbolSecond, decimals) + await preferencesController.addToken( + addressSecond, + symbolSecond, + decimals, + ) const tokensSeconAddress = preferencesController.getTokens() - assert.notEqual(tokensFirstAddress, tokensSeconAddress, 'add different tokens for two networks and tokens are equal') + assert.notEqual( + tokensFirstAddress, + tokensSeconAddress, + 'add different tokens for two networks and tokens are equal', + ) }) }) @@ -269,10 +278,7 @@ describe('preferences controller', function () { }) it('should remove a token from its state on corresponding address', async function () { - preferencesController.setAddresses([ - '0x7e57e2', - '0x7e57e3', - ]) + preferencesController.setAddresses(['0x7e57e2', '0x7e57e3']) await preferencesController.setSelectedAddress('0x7e57e2') await preferencesController.addToken('0xa', 'A', 4) await preferencesController.addToken('0xb', 'B', 5) @@ -291,7 +297,11 @@ describe('preferences controller', function () { await preferencesController.setSelectedAddress('0x7e57e3') const tokensSecond = preferencesController.getTokens() - assert.deepEqual(tokensSecond, initialTokensSecond, 'token deleted for account') + assert.deepEqual( + tokensSecond, + initialTokensSecond, + 'token deleted for account', + ) }) it('should remove a token from its state on corresponding network', async function () { @@ -313,16 +323,17 @@ describe('preferences controller', function () { network.providerStore.updateState({ type: 'rinkeby' }) const tokensSecond = preferencesController.getTokens() - assert.deepEqual(tokensSecond, initialTokensSecond, 'token deleted for network') + assert.deepEqual( + tokensSecond, + initialTokensSecond, + 'token deleted for network', + ) }) }) describe('on setSelectedAddress', function () { it('should update tokens from its state on corresponding address', async function () { - preferencesController.setAddresses([ - '0x7e57e2', - '0x7e57e3', - ]) + preferencesController.setAddresses(['0x7e57e2', '0x7e57e3']) await preferencesController.setSelectedAddress('0x7e57e2') await preferencesController.addToken('0xa', 'A', 4) await preferencesController.addToken('0xb', 'B', 5) @@ -335,15 +346,27 @@ describe('preferences controller', function () { await preferencesController.setSelectedAddress('0x7e57e3') const initialTokensSecond = preferencesController.getTokens() - assert.notDeepEqual(initialTokensFirst, initialTokensSecond, 'tokens not equal for different accounts and tokens') + assert.notDeepEqual( + initialTokensFirst, + initialTokensSecond, + 'tokens not equal for different accounts and tokens', + ) await preferencesController.setSelectedAddress('0x7e57e2') const tokensFirst = preferencesController.getTokens() await preferencesController.setSelectedAddress('0x7e57e3') const tokensSecond = preferencesController.getTokens() - assert.deepEqual(tokensFirst, initialTokensFirst, 'tokens equal for same account') - assert.deepEqual(tokensSecond, initialTokensSecond, 'tokens equal for same account') + assert.deepEqual( + tokensFirst, + initialTokensFirst, + 'tokens equal for same account', + ) + assert.deepEqual( + tokensSecond, + initialTokensSecond, + 'tokens equal for same account', + ) }) }) @@ -358,14 +381,26 @@ describe('preferences controller', function () { await preferencesController.addToken('0xb', 'D', 5) const initialTokensSecond = preferencesController.getTokens() - assert.notDeepEqual(initialTokensFirst, initialTokensSecond, 'tokens not equal for different networks and tokens') + assert.notDeepEqual( + initialTokensFirst, + initialTokensSecond, + 'tokens not equal for different networks and tokens', + ) network.providerStore.updateState({ type: 'mainnet' }) const tokensFirst = preferencesController.getTokens() network.providerStore.updateState({ type: 'rinkeby' }) const tokensSecond = preferencesController.getTokens() - assert.deepEqual(tokensFirst, initialTokensFirst, 'tokens equal for same network') - assert.deepEqual(tokensSecond, initialTokensSecond, 'tokens equal for same network') + assert.deepEqual( + tokensFirst, + initialTokensFirst, + 'tokens equal for same network', + ) + assert.deepEqual( + tokensSecond, + initialTokensSecond, + 'tokens equal for same network', + ) }) }) @@ -377,7 +412,10 @@ describe('preferences controller', function () { req = { params: {} } res = {} asy = { next: sandbox.spy(), end: sandbox.spy() } - stubHandleWatchAssetERC20 = sandbox.stub(preferencesController, '_handleWatchAssetERC20') + stubHandleWatchAssetERC20 = sandbox.stub( + preferencesController, + '_handleWatchAssetERC20', + ) }) after(function () { sandbox.restore() @@ -436,16 +474,26 @@ describe('preferences controller', function () { const image = 'someimage' req.params.options = { address, symbol, decimals, image } - sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true) + sandbox + .stub(preferencesController, '_validateERC20AssetParams') + .returns(true) preferencesController.openPopup = async () => undefined await preferencesController._handleWatchAssetERC20(req.params.options) const suggested = preferencesController.getSuggestedTokens() - assert.equal(Object.keys(suggested).length, 1, `one token added ${Object.keys(suggested)}`) + assert.equal( + Object.keys(suggested).length, + 1, + `one token added ${Object.keys(suggested)}`, + ) assert.equal(suggested[address].address, address, 'set address correctly') assert.equal(suggested[address].symbol, symbol, 'set symbol correctly') - assert.equal(suggested[address].decimals, decimals, 'set decimals correctly') + assert.equal( + suggested[address].decimals, + decimals, + 'set decimals correctly', + ) assert.equal(suggested[address].image, image, 'set image correctly') }) @@ -456,7 +504,9 @@ describe('preferences controller', function () { const image = 'someimage' req.params.options = { address, symbol, decimals, image } - sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true) + sandbox + .stub(preferencesController, '_validateERC20AssetParams') + .returns(true) preferencesController.openPopup = async () => { await preferencesController.addToken(address, symbol, decimals, image) } @@ -475,14 +525,64 @@ describe('preferences controller', function () { it('should validate ERC20 asset correctly', async function () { const validate = preferencesController._validateERC20AssetParams - assert.doesNotThrow(() => validate({ rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC', decimals: 0 })) - assert.throws(() => validate({ symbol: 'ABC', decimals: 0 }), 'missing address should fail') - assert.throws(() => validate({ rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', decimals: 0 }), 'missing symbol should fail') - assert.throws(() => validate({ rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC' }), 'missing decimals should fail') - assert.throws(() => validate({ rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 0 }), 'invalid symbol should fail') - assert.throws(() => validate({ rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC', decimals: -1 }), 'decimals < 0 should fail') - assert.throws(() => validate({ rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC', decimals: 38 }), 'decimals > 36 should fail') - assert.throws(() => validate({ rawAddress: '0x123', symbol: 'ABC', decimals: 0 }), 'invalid address should fail') + assert.doesNotThrow(() => + validate({ + rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', + symbol: 'ABC', + decimals: 0, + }), + ) + assert.throws( + () => validate({ symbol: 'ABC', decimals: 0 }), + 'missing address should fail', + ) + assert.throws( + () => + validate({ + rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', + decimals: 0, + }), + 'missing symbol should fail', + ) + assert.throws( + () => + validate({ + rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', + symbol: 'ABC', + }), + 'missing decimals should fail', + ) + assert.throws( + () => + validate({ + rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', + symbol: 'ABCDEFGHI', + decimals: 0, + }), + 'invalid symbol should fail', + ) + assert.throws( + () => + validate({ + rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', + symbol: 'ABC', + decimals: -1, + }), + 'decimals < 0 should fail', + ) + assert.throws( + () => + validate({ + rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', + symbol: 'ABC', + decimals: 38, + }), + 'decimals > 36 should fail', + ) + assert.throws( + () => validate({ rawAddress: '0x123', symbol: 'ABC', decimals: 0 }), + 'invalid address should fail', + ) }) }) @@ -493,27 +593,46 @@ describe('preferences controller', function () { }) it('should set the forgottenPassword property in state', function () { - assert.equal(preferencesController.store.getState().forgottenPassword, false) + assert.equal( + preferencesController.store.getState().forgottenPassword, + false, + ) preferencesController.setPasswordForgotten(true) - assert.equal(preferencesController.store.getState().forgottenPassword, true) + assert.equal( + preferencesController.store.getState().forgottenPassword, + true, + ) }) }) describe('#updateRpc', function () { it('should update the rpcDetails properly', async function () { - preferencesController.store.updateState({ frequentRpcListDetail: [{}, { rpcUrl: 'test', chainId: '0x1' }, {}] }) + preferencesController.store.updateState({ + frequentRpcListDetail: [{}, { rpcUrl: 'test', chainId: '0x1' }, {}], + }) await preferencesController.updateRpc({ rpcUrl: 'test', chainId: '0x1' }) - await preferencesController.updateRpc({ rpcUrl: 'test/1', chainId: '0x1' }) - await preferencesController.updateRpc({ rpcUrl: 'test/2', chainId: '0x1' }) - await preferencesController.updateRpc({ rpcUrl: 'test/3', chainId: '0x1' }) + await preferencesController.updateRpc({ + rpcUrl: 'test/1', + chainId: '0x1', + }) + await preferencesController.updateRpc({ + rpcUrl: 'test/2', + chainId: '0x1', + }) + await preferencesController.updateRpc({ + rpcUrl: 'test/3', + chainId: '0x1', + }) const list = preferencesController.getFrequentRpcListDetail() assert.deepEqual(list[1], { rpcUrl: 'test', chainId: '0x1' }) }) it('should migrate address book entries if chainId changes', async function () { - preferencesController.store.updateState({ frequentRpcListDetail: [{}, { rpcUrl: 'test', chainId: '1' }, {}] }) + preferencesController.store.updateState({ + frequentRpcListDetail: [{}, { rpcUrl: 'test', chainId: '1' }, {}], + }) await preferencesController.updateRpc({ rpcUrl: 'test', chainId: '0x1' }) assert(migrateAddressBookState.calledWith('1', '0x1')) }) @@ -524,43 +643,58 @@ describe('preferences controller', function () { preferencesController.addToFrequentRpcList('rpc_url', '0x1') assert.deepEqual( preferencesController.store.getState().frequentRpcListDetail, - [{ - rpcUrl: 'rpc_url', - chainId: '0x1', - ticker: 'ETH', - nickname: '', - rpcPrefs: {}, - }], + [ + { + rpcUrl: 'rpc_url', + chainId: '0x1', + ticker: 'ETH', + nickname: '', + rpcPrefs: {}, + }, + ], ) preferencesController.addToFrequentRpcList('rpc_url', '0x1') assert.deepEqual( preferencesController.store.getState().frequentRpcListDetail, - [{ - rpcUrl: 'rpc_url', - chainId: '0x1', - ticker: 'ETH', - nickname: '', - rpcPrefs: {}, - }], + [ + { + rpcUrl: 'rpc_url', + chainId: '0x1', + ticker: 'ETH', + nickname: '', + rpcPrefs: {}, + }, + ], ) }) it('should throw if chainId is invalid', function () { - assert.throws( - () => { - preferencesController.addToFrequentRpcList('rpc_url', '1') - }, - 'should throw on invalid chainId', - ) + assert.throws(() => { + preferencesController.addToFrequentRpcList('rpc_url', '1') + }, 'should throw on invalid chainId') }) it('should remove custom RPC url from state', function () { preferencesController.addToFrequentRpcList('rpc_url', '0x1') - assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: '0x1', ticker: 'ETH', nickname: '', rpcPrefs: {} }]) + assert.deepEqual( + preferencesController.store.getState().frequentRpcListDetail, + [ + { + rpcUrl: 'rpc_url', + chainId: '0x1', + ticker: 'ETH', + nickname: '', + rpcPrefs: {}, + }, + ], + ) preferencesController.removeFromFrequentRpcList('other_rpc_url') preferencesController.removeFromFrequentRpcList('http://localhost:8545') preferencesController.removeFromFrequentRpcList('rpc_url') - assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, []) + assert.deepEqual( + preferencesController.store.getState().frequentRpcListDetail, + [], + ) }) }) diff --git a/test/unit/app/controllers/swaps-test.js b/test/unit/app/controllers/swaps-test.js index 9707b34e6..775c53d9f 100644 --- a/test/unit/app/controllers/swaps-test.js +++ b/test/unit/app/controllers/swaps-test.js @@ -2,11 +2,18 @@ import assert from 'assert' import sinon from 'sinon' import { ethers } from 'ethers' +import { mapValues } from 'lodash' import BigNumber from 'bignumber.js' import ObservableStore from 'obs-store' -import { ROPSTEN_NETWORK_ID, MAINNET_NETWORK_ID } from '../../../../app/scripts/controllers/network/enums' +import { + ROPSTEN_NETWORK_ID, + MAINNET_NETWORK_ID, +} from '../../../../app/scripts/controllers/network/enums' +import { ETH_SWAPS_TOKEN_ADDRESS } from '../../../../ui/app/helpers/constants/swaps' import { createTestProviderTools } from '../../../stub/provider' -import SwapsController, { utils } from '../../../../app/scripts/controllers/swaps' +import SwapsController, { + utils, +} from '../../../../app/scripts/controllers/swaps' const MOCK_FETCH_PARAMS = { slippage: 3, @@ -20,16 +27,21 @@ const MOCK_FETCH_PARAMS = { const TEST_AGG_ID_1 = 'TEST_AGG_1' const TEST_AGG_ID_2 = 'TEST_AGG_2' +const TEST_AGG_ID_3 = 'TEST_AGG_3' +const TEST_AGG_ID_4 = 'TEST_AGG_4' +const TEST_AGG_ID_5 = 'TEST_AGG_5' +const TEST_AGG_ID_6 = 'TEST_AGG_6' const TEST_AGG_ID_BEST = 'TEST_AGG_BEST' const TEST_AGG_ID_APPROVAL = 'TEST_AGG_APPROVAL' const MOCK_APPROVAL_NEEDED = { - 'data': '0x095ea7b300000000000000000000000095e6f48254609a6ee006f7d493c8e5fb97094cef0000000000000000000000000000000000000000004a817c7ffffffdabf41c00', - 'to': '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - 'amount': '0', - 'from': '0x2369267687A84ac7B494daE2f1542C40E37f4455', - 'gas': '12', - 'gasPrice': '34', + data: + '0x095ea7b300000000000000000000000095e6f48254609a6ee006f7d493c8e5fb97094cef0000000000000000000000000000000000000000004a817c7ffffffdabf41c00', + to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + amount: '0', + from: '0x2369267687A84ac7B494daE2f1542C40E37f4455', + gas: '12', + gasPrice: '34', } const MOCK_QUOTES_APPROVAL_REQUIRED = { @@ -55,6 +67,7 @@ const MOCK_QUOTES_APPROVAL_REQUIRED = { aggType: 'AGG', slippage: 3, approvalNeeded: MOCK_APPROVAL_NEEDED, + fee: 1, }, } @@ -66,7 +79,10 @@ const MOCK_FETCH_METADATA = { } const MOCK_TOKEN_RATES_STORE = new ObservableStore({ - contractExchangeRates: { '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': 2 }, + contractExchangeRates: { + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': 2, + '0x1111111111111111111111111111111111111111': 0.1, + }, }) const MOCK_GET_PROVIDER_CONFIG = () => ({ type: 'FAKE_NETWORK' }) @@ -76,7 +92,7 @@ const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({ simulationFails: undefined, }) -function getMockNetworkController () { +function getMockNetworkController() { return { store: { getState: () => { @@ -134,8 +150,11 @@ describe('SwapsController', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0x', } - provider = createTestProviderTools({ scaffold: providerResultStub, networkId: 1, chainId: 1 }) - .provider + provider = createTestProviderTools({ + scaffold: providerResultStub, + networkId: 1, + chainId: 1, + }).provider }) afterEach(function () { @@ -341,6 +360,13 @@ describe('SwapsController', function () { }) describe('_findTopQuoteAndCalculateSavings', function () { + beforeEach(function () { + const { swapsState } = swapsController.store.getState() + swapsController.store.updateState({ + swapsState: { ...swapsState, customGasPrice: '0x174876e800' }, + }) + }) + it('returns empty object if passed undefined or empty object', async function () { assert.deepStrictEqual( await swapsController._findTopQuoteAndCalculateSavings(), @@ -351,6 +377,261 @@ describe('SwapsController', function () { {}, ) }) + + it('returns the top aggId and quotes with savings and fee values if passed necessary data and an even number of quotes', async function () { + const [ + topAggId, + resultQuotes, + ] = await swapsController._findTopQuoteAndCalculateSavings( + getTopQuoteAndSavingsMockQuotes(), + ) + assert.equal(topAggId, TEST_AGG_ID_1) + assert.deepStrictEqual( + resultQuotes, + getTopQuoteAndSavingsBaseExpectedResults(), + ) + }) + + it('returns the top aggId and quotes with savings and fee values if passed necessary data and an odd number of quotes', async function () { + const testInput = getTopQuoteAndSavingsMockQuotes() + delete testInput[TEST_AGG_ID_6] + const expectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults() + delete expectedResultQuotes[TEST_AGG_ID_6] + expectedResultQuotes[TEST_AGG_ID_1].savings = { + total: '0.0292', + performance: '0.0297', + fee: '0.02', + metaMaskFee: '0.0205', + medianMetaMaskFee: '0.0202', + } + + const [ + topAggId, + resultQuotes, + ] = await swapsController._findTopQuoteAndCalculateSavings(testInput) + assert.equal(topAggId, TEST_AGG_ID_1) + assert.deepStrictEqual(resultQuotes, expectedResultQuotes) + }) + + it('returns the top aggId, without best quote flagged, and quotes with fee values if passed necessary data but no custom convert rate exists', async function () { + const testInput = mapValues( + getTopQuoteAndSavingsMockQuotes(), + (quote) => ({ + ...quote, + destinationToken: '0xnoConversionRateExists', + }), + ) + const expectedResultQuotes = { + [TEST_AGG_ID_1]: { + ...testInput[TEST_AGG_ID_1], + ethFee: '0.01', + }, + [TEST_AGG_ID_2]: { + ...testInput[TEST_AGG_ID_2], + ethFee: '0.02', + }, + [TEST_AGG_ID_3]: { + ...testInput[TEST_AGG_ID_3], + ethFee: '0.03', + }, + [TEST_AGG_ID_4]: { + ...testInput[TEST_AGG_ID_4], + ethFee: '0.04', + }, + [TEST_AGG_ID_5]: { + ...testInput[TEST_AGG_ID_5], + ethFee: '0.05', + }, + [TEST_AGG_ID_6]: { + ...testInput[TEST_AGG_ID_6], + ethFee: '0.06', + }, + } + + const [ + topAggId, + resultQuotes, + ] = await swapsController._findTopQuoteAndCalculateSavings(testInput) + assert.equal(topAggId, TEST_AGG_ID_1) + assert.deepStrictEqual(resultQuotes, expectedResultQuotes) + }) + + it('returns the top aggId and quotes with savings and fee values if passed necessary data and the source token is ETH', async function () { + const testInput = mapValues( + getTopQuoteAndSavingsMockQuotes(), + (quote) => ({ + ...quote, + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + }), + ) + const baseExpectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults() + const expectedResultQuotes = { + [TEST_AGG_ID_1]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_1], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '2.0195', + }, + [TEST_AGG_ID_2]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_2], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '1.9996', + }, + [TEST_AGG_ID_3]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_3], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '1.9698', + }, + [TEST_AGG_ID_4]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_4], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '1.94', + }, + [TEST_AGG_ID_5]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_5], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '1.9102', + }, + [TEST_AGG_ID_6]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_6], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '1.8705', + }, + } + + const [ + topAggId, + resultQuotes, + ] = await swapsController._findTopQuoteAndCalculateSavings(testInput) + assert.equal(topAggId, TEST_AGG_ID_1) + assert.deepStrictEqual(resultQuotes, expectedResultQuotes) + }) + + it('returns the top aggId and quotes with savings and fee values if passed necessary data and the source token is ETH and an ETH fee is included in the trade value of what would be the best quote', async function () { + const testInput = mapValues( + getTopQuoteAndSavingsMockQuotes(), + (quote) => ({ + ...quote, + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + }), + ) + // 0.04 ETH fee included in trade value + testInput[TEST_AGG_ID_1].trade.value = '0x8b553ece48ec0000' + const baseExpectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults() + const expectedResultQuotes = { + [TEST_AGG_ID_1]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_1], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8b553ece48ec0000' }, + overallValueOfQuote: '1.9795', + ethFee: '0.05', + }, + [TEST_AGG_ID_2]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_2], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '1.9996', + isBestQuote: true, + savings: { + total: '0.0243', + performance: '0.0297', + fee: '0.015', + metaMaskFee: '0.0204', + medianMetaMaskFee: '0.0201', + }, + }, + [TEST_AGG_ID_3]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_3], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '1.9698', + }, + [TEST_AGG_ID_4]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_4], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '1.94', + }, + [TEST_AGG_ID_5]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_5], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '1.9102', + }, + [TEST_AGG_ID_6]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_6], + sourceToken: ETH_SWAPS_TOKEN_ADDRESS, + destinationToken: '0x1111111111111111111111111111111111111111', + trade: { value: '0x8ac7230489e80000' }, + overallValueOfQuote: '1.8705', + }, + } + delete expectedResultQuotes[TEST_AGG_ID_1].isBestQuote + delete expectedResultQuotes[TEST_AGG_ID_1].savings + + const [ + topAggId, + resultQuotes, + ] = await swapsController._findTopQuoteAndCalculateSavings(testInput) + assert.equal(topAggId, TEST_AGG_ID_2) + assert.deepStrictEqual(resultQuotes, expectedResultQuotes) + }) + + it('returns the top aggId and quotes with savings and fee values if passed necessary data and the source token is not ETH and an ETH fee is included in the trade value of what would be the best quote', async function () { + const testInput = getTopQuoteAndSavingsMockQuotes() + // 0.04 ETH fee included in trade value + testInput[TEST_AGG_ID_1].trade.value = '0x8e1bc9bf040000' + const baseExpectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults() + const expectedResultQuotes = { + ...baseExpectedResultQuotes, + [TEST_AGG_ID_1]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_1], + trade: { value: '0x8e1bc9bf040000' }, + overallValueOfQuote: '1.9795', + ethFee: '0.05', + }, + [TEST_AGG_ID_2]: { + ...baseExpectedResultQuotes[TEST_AGG_ID_2], + isBestQuote: true, + savings: { + total: '0.0243', + performance: '0.0297', + fee: '0.015', + metaMaskFee: '0.0204', + medianMetaMaskFee: '0.0201', + }, + }, + } + delete expectedResultQuotes[TEST_AGG_ID_1].isBestQuote + delete expectedResultQuotes[TEST_AGG_ID_1].savings + + const [ + topAggId, + resultQuotes, + ] = await swapsController._findTopQuoteAndCalculateSavings(testInput) + assert.equal(topAggId, [TEST_AGG_ID_2]) + assert.deepStrictEqual(resultQuotes, expectedResultQuotes) + }) }) describe('fetchAndSetQuotes', function () { @@ -385,9 +666,15 @@ describe('SwapsController', function () { gasEstimateWithRefund: 'b8cae', savings: { fee: '0', + metaMaskFee: '0.5050505050505050505', performance: '6', - total: '6', + total: '5.4949494949494949495', + medianMetaMaskFee: '0.44444444444444444444', }, + ethFee: '33554432', + overallValueOfQuote: '-33554382', + metaMaskFeeInEth: '0.5050505050505050505', + ethValueOfTokens: '50', }) assert.strictEqual( @@ -506,7 +793,7 @@ describe('SwapsController', function () { MOCK_FETCH_METADATA, ) - assert.strictEqual(newQuotes[topAggId].isBestQuote, false) + assert.strictEqual(newQuotes[topAggId].isBestQuote, undefined) }) }) @@ -580,7 +867,6 @@ describe('SwapsController', function () { }) describe('_setupSwapsLivenessFetching ', function () { - let clock const EXPECTED_TIME = 600000 @@ -603,15 +889,11 @@ describe('SwapsController', function () { clock = sandbox.useFakeTimers() sandbox.spy(clock, 'setInterval') - sandbox.stub( - SwapsController.prototype, - '_fetchAndSetSwapsLiveness', - ).resolves(undefined) + sandbox + .stub(SwapsController.prototype, '_fetchAndSetSwapsLiveness') + .resolves(undefined) - sandbox.spy( - SwapsController.prototype, - '_setupSwapsLivenessFetching', - ) + sandbox.spy(SwapsController.prototype, '_setupSwapsLivenessFetching') sandbox.spy(window, 'addEventListener') }) @@ -627,12 +909,8 @@ describe('SwapsController', function () { swapsController._setupSwapsLivenessFetching.calledOnce, 'should have called _setupSwapsLivenessFetching once', ) - assert.ok( - window.addEventListener.calledWith('online'), - ) - assert.ok( - window.addEventListener.calledWith('offline'), - ) + assert.ok(window.addEventListener.calledWith('online')) + assert.ok(window.addEventListener.calledWith('offline')) assert.ok( clock.setInterval.calledOnceWithExactly( sinon.match.func, @@ -659,7 +937,8 @@ describe('SwapsController', function () { 'should not have set an interval', ) assert.strictEqual( - getLivenessState(), false, + getLivenessState(), + false, 'swaps feature should be disabled', ) @@ -734,24 +1013,21 @@ describe('SwapsController', function () { 'should have called updateState once', ) assert.strictEqual( - getLivenessState(), false, + getLivenessState(), + false, 'swaps feature should be disabled', ) }) }) describe('_fetchAndSetSwapsLiveness', function () { - const getLivenessState = () => { return swapsController.store.getState().swapsState.swapsFeatureIsLive } beforeEach(function () { fetchSwapsFeatureLivenessStub.reset() - sandbox.stub( - SwapsController.prototype, - '_setupSwapsLivenessFetching', - ) + sandbox.stub(SwapsController.prototype, '_setupSwapsLivenessFetching') swapsController = getSwapsController() }) @@ -763,7 +1039,9 @@ describe('SwapsController', function () { fetchSwapsFeatureLivenessStub.resolves(true) assert.strictEqual( - getLivenessState(), false, 'liveness should be false on boot', + getLivenessState(), + false, + 'liveness should be false on boot', ) await swapsController._fetchAndSetSwapsLiveness() @@ -773,7 +1051,9 @@ describe('SwapsController', function () { 'should have called fetch function once', ) assert.strictEqual( - getLivenessState(), true, 'liveness should be true after call', + getLivenessState(), + true, + 'liveness should be true after call', ) }) @@ -782,7 +1062,9 @@ describe('SwapsController', function () { sandbox.spy(swapsController.store, 'updateState') assert.strictEqual( - getLivenessState(), false, 'liveness should be false on boot', + getLivenessState(), + false, + 'liveness should be false on boot', ) await swapsController._fetchAndSetSwapsLiveness() @@ -796,7 +1078,9 @@ describe('SwapsController', function () { 'should not have called store.updateState', ) assert.strictEqual( - getLivenessState(), false, 'liveness should remain false after call', + getLivenessState(), + false, + 'liveness should remain false after call', ) }) @@ -806,7 +1090,9 @@ describe('SwapsController', function () { sandbox.spy(swapsController.store, 'updateState') assert.strictEqual( - getLivenessState(), false, 'liveness should be false on boot', + getLivenessState(), + false, + 'liveness should be false on boot', ) swapsController._fetchAndSetSwapsLiveness() @@ -821,7 +1107,9 @@ describe('SwapsController', function () { 'should not have called store.updateState', ) assert.strictEqual( - getLivenessState(), false, 'liveness should remain false after call', + getLivenessState(), + false, + 'liveness should remain false after call', ) }) @@ -832,60 +1120,236 @@ describe('SwapsController', function () { fetchSwapsFeatureLivenessStub.onCall(2).resolves(true) assert.strictEqual( - getLivenessState(), false, 'liveness should be false on boot', + getLivenessState(), + false, + 'liveness should be false on boot', ) swapsController._fetchAndSetSwapsLiveness() await clock.runAllAsync() assert.strictEqual( - fetchSwapsFeatureLivenessStub.callCount, 3, + fetchSwapsFeatureLivenessStub.callCount, + 3, 'should have called fetch function three times', ) assert.strictEqual( - getLivenessState(), true, 'liveness should be true after call', + getLivenessState(), + true, + 'liveness should be true after call', ) }) }) }) describe('utils', function () { - describe('getMedian', function () { - const { getMedian } = utils + describe('getMedianEthValueQuote', function () { + const { getMedianEthValueQuote } = utils it('calculates median correctly with uneven sample', function () { - const values = [3, 2, 6].map((value) => new BigNumber(value)) - const median = getMedian(values) + const expectedResult = { + ethFee: '10', + metaMaskFeeInEth: '5', + ethValueOfTokens: '0.3', + } + const values = [ + { + overallValueOfQuote: '3', + ethFee: '10', + metaMaskFeeInEth: '5', + ethValueOfTokens: '0.3', + }, + { + overallValueOfQuote: '2', + ethFee: '20', + metaMaskFeeInEth: '3', + ethValueOfTokens: '0.2', + }, + { + overallValueOfQuote: '6', + ethFee: '40', + metaMaskFeeInEth: '6', + ethValueOfTokens: '0.6', + }, + ] - assert.strictEqual( - median.toNumber(), 3, - 'should have returned correct median', + const median = getMedianEthValueQuote(values) + + assert.deepEqual( + median, + expectedResult, + 'should have returned correct median quote object', ) }) it('calculates median correctly with even sample', function () { - const values = [3, 2, 2, 6].map((value) => new BigNumber(value)) - const median = getMedian(values) + const expectedResult = { + ethFee: '20', + metaMaskFeeInEth: '6.5', + ethValueOfTokens: '0.25', + } + const values = [ + { + overallValueOfQuote: '3', + ethFee: '10', + metaMaskFeeInEth: '5', + ethValueOfTokens: '0.3', + }, + { + overallValueOfQuote: '1', + ethFee: '20', + metaMaskFeeInEth: '3', + ethValueOfTokens: '0.2', + }, + { + overallValueOfQuote: '2', + ethFee: '30', + metaMaskFeeInEth: '8', + ethValueOfTokens: '0.2', + }, + { + overallValueOfQuote: '6', + ethFee: '40', + metaMaskFeeInEth: '6', + ethValueOfTokens: '0.6', + }, + ] + const median = getMedianEthValueQuote(values) - assert.strictEqual( - median.toNumber(), 2.5, - 'should have returned correct median', + assert.deepEqual( + median, + expectedResult, + 'should have returned correct median quote object', + ) + }) + + it('calculates median correctly with an uneven sample where multiple quotes have the median overall value', function () { + const expectedResult = { + ethFee: '2', + metaMaskFeeInEth: '0.5', + ethValueOfTokens: '5', + } + + const values = [ + { + overallValueOfQuote: '1', + ethValueOfTokens: '2', + ethFee: '1', + metaMaskFeeInEth: '0.2', + }, + { + overallValueOfQuote: '3', + ethValueOfTokens: '4', + ethFee: '1', + metaMaskFeeInEth: '0.4', + }, + { + overallValueOfQuote: '3', + ethValueOfTokens: '5', + ethFee: '2', + metaMaskFeeInEth: '0.5', + }, + { + overallValueOfQuote: '3', + ethValueOfTokens: '6', + ethFee: '3', + metaMaskFeeInEth: '0.6', + }, + { + overallValueOfQuote: '4', + ethValueOfTokens: '6', + ethFee: '2', + metaMaskFeeInEth: '0.6', + }, + { + overallValueOfQuote: '4', + ethValueOfTokens: '7', + ethFee: '3', + metaMaskFeeInEth: '0.7', + }, + { + overallValueOfQuote: '6', + ethValueOfTokens: '8', + ethFee: '2', + metaMaskFeeInEth: '0.8', + }, + ] + const median = getMedianEthValueQuote(values) + + assert.deepEqual( + median, + expectedResult, + 'should have returned correct median quote object', + ) + }) + + it('calculates median correctly with an even sample where multiple quotes have the same overall value as either of the two middle values', function () { + const expectedResult = { + ethFee: '2', + metaMaskFeeInEth: '0.55', + ethValueOfTokens: '5.5', + } + + const values = [ + { + overallValueOfQuote: '1', + ethValueOfTokens: '2', + ethFee: '1', + metaMaskFeeInEth: '0.2', + }, + { + overallValueOfQuote: '3', + ethValueOfTokens: '4', + ethFee: '1', + metaMaskFeeInEth: '0.4', + }, + { + overallValueOfQuote: '3', + ethValueOfTokens: '5', + ethFee: '2', + metaMaskFeeInEth: '0.5', + }, + { + overallValueOfQuote: '4', + ethValueOfTokens: '6', + ethFee: '2', + metaMaskFeeInEth: '0.6', + }, + { + overallValueOfQuote: '4', + ethValueOfTokens: '7', + ethFee: '3', + metaMaskFeeInEth: '0.7', + }, + { + overallValueOfQuote: '6', + ethValueOfTokens: '8', + ethFee: '2', + metaMaskFeeInEth: '0.8', + }, + ] + const median = getMedianEthValueQuote(values) + + assert.deepEqual( + median, + expectedResult, + 'should have returned correct median quote object', ) }) it('throws on empty or non-array sample', function () { assert.throws( - () => getMedian([]), + () => getMedianEthValueQuote([]), 'should throw on empty array', ) assert.throws( - () => getMedian(), + () => getMedianEthValueQuote(), 'should throw on non-array param', ) assert.throws( - () => getMedian({}), + () => getMedianEthValueQuote({}), 'should throw on non-array param', ) }) @@ -893,105 +1357,261 @@ describe('SwapsController', function () { }) }) -function getMockQuotes () { +function getMockQuotes() { return { [TEST_AGG_ID_1]: { - 'trade': { - 'from': '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', - 'value': '0x0', - 'gas': '0x61a80', // 4e5 - 'to': '0x881D40237659C251811CEC9c364ef91dC08D300C', + trade: { + from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', + value: '0x0', + gas: '0x61a80', // 4e5 + to: '0x881D40237659C251811CEC9c364ef91dC08D300C', }, - 'sourceAmount': '10000000000000000000', // 10e18 - 'destinationAmount': '20000000000000000000', // 20e18 - 'error': null, - 'sourceToken': '0x6b175474e89094c44da98b954eedeac495271d0f', - 'destinationToken': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - 'approvalNeeded': null, - 'maxGas': 600000, - 'averageGas': 120000, - 'estimatedRefund': 80000, - 'fetchTime': 607, - 'aggregator': TEST_AGG_ID_1, - 'aggType': 'AGG', - 'slippage': 2, - 'sourceTokenInfo': { - 'address': '0x6b175474e89094c44da98b954eedeac495271d0f', - 'symbol': 'DAI', - 'decimals': 18, - 'iconUrl': 'https://foo.bar/logo.png', + sourceAmount: '10000000000000000000', // 10e18 + destinationAmount: '20000000000000000000', // 20e18 + error: null, + sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f', + destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + approvalNeeded: null, + maxGas: 600000, + averageGas: 120000, + estimatedRefund: 80000, + fetchTime: 607, + aggregator: TEST_AGG_ID_1, + aggType: 'AGG', + slippage: 2, + sourceTokenInfo: { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + symbol: 'DAI', + decimals: 18, + iconUrl: 'https://foo.bar/logo.png', }, - 'destinationTokenInfo': { - 'address': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - 'symbol': 'USDC', - 'decimals': 18, + destinationTokenInfo: { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + symbol: 'USDC', + decimals: 18, }, + fee: 1, }, [TEST_AGG_ID_BEST]: { - 'trade': { - 'from': '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', - 'value': '0x0', - 'gas': '0x61a80', - 'to': '0x881D40237659C251811CEC9c364ef91dC08D300C', + trade: { + from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', + value: '0x0', + gas: '0x61a80', + to: '0x881D40237659C251811CEC9c364ef91dC08D300C', }, - 'sourceAmount': '10000000000000000000', - 'destinationAmount': '25000000000000000000', // 25e18 - 'error': null, - 'sourceToken': '0x6b175474e89094c44da98b954eedeac495271d0f', - 'destinationToken': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - 'approvalNeeded': null, - 'maxGas': 1100000, - 'averageGas': 411000, - 'estimatedRefund': 343090, - 'fetchTime': 1003, - 'aggregator': TEST_AGG_ID_BEST, - 'aggType': 'AGG', - 'slippage': 2, - 'sourceTokenInfo': { - 'address': '0x6b175474e89094c44da98b954eedeac495271d0f', - 'symbol': 'DAI', - 'decimals': 18, - 'iconUrl': 'https://foo.bar/logo.png', + sourceAmount: '10000000000000000000', + destinationAmount: '25000000000000000000', // 25e18 + error: null, + sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f', + destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + approvalNeeded: null, + maxGas: 1100000, + averageGas: 411000, + estimatedRefund: 343090, + fetchTime: 1003, + aggregator: TEST_AGG_ID_BEST, + aggType: 'AGG', + slippage: 2, + sourceTokenInfo: { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + symbol: 'DAI', + decimals: 18, + iconUrl: 'https://foo.bar/logo.png', }, - 'destinationTokenInfo': { - 'address': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - 'symbol': 'USDC', - 'decimals': 18, + destinationTokenInfo: { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + symbol: 'USDC', + decimals: 18, }, + fee: 1, }, [TEST_AGG_ID_2]: { - 'trade': { - 'from': '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', - 'value': '0x0', - 'gas': '0x61a80', - 'to': '0x881D40237659C251811CEC9c364ef91dC08D300C', + trade: { + from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', + value: '0x0', + gas: '0x61a80', + to: '0x881D40237659C251811CEC9c364ef91dC08D300C', }, - 'sourceAmount': '10000000000000000000', - 'destinationAmount': '22000000000000000000', // 22e18 - 'error': null, - 'sourceToken': '0x6b175474e89094c44da98b954eedeac495271d0f', - 'destinationToken': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - 'approvalNeeded': null, - 'maxGas': 368000, - 'averageGas': 197000, - 'estimatedRefund': 18205, - 'fetchTime': 1354, - 'aggregator': TEST_AGG_ID_2, - 'aggType': 'AGG', - 'slippage': 2, - 'sourceTokenInfo': { - 'address': '0x6b175474e89094c44da98b954eedeac495271d0f', - 'symbol': 'DAI', - 'decimals': 18, - 'iconUrl': 'https://foo.bar/logo.png', + sourceAmount: '10000000000000000000', + destinationAmount: '22000000000000000000', // 22e18 + error: null, + sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f', + destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + approvalNeeded: null, + maxGas: 368000, + averageGas: 197000, + estimatedRefund: 18205, + fetchTime: 1354, + aggregator: TEST_AGG_ID_2, + aggType: 'AGG', + slippage: 2, + sourceTokenInfo: { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + symbol: 'DAI', + decimals: 18, + iconUrl: 'https://foo.bar/logo.png', }, - 'destinationTokenInfo': { - 'address': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - 'symbol': 'USDC', - 'decimals': 18, + destinationTokenInfo: { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + symbol: 'USDC', + decimals: 18, }, + fee: 1, + }, + } +} + +function getTopQuoteAndSavingsMockQuotes() { + // These destination amounts are calculated using the following "pre-fee" amounts + // TEST_AGG_ID_1: 20.5 + // TEST_AGG_ID_2: 20.4 + // TEST_AGG_ID_3: 20.2 + // TEST_AGG_ID_4: 20 + // TEST_AGG_ID_5: 19.8 + // TEST_AGG_ID_6: 19.5 + + return { + [TEST_AGG_ID_1]: { + aggregator: TEST_AGG_ID_1, + approvalNeeded: null, + gasEstimate: '0x186a0', + destinationAmount: '20295000000000000000', + destinationToken: '0x1111111111111111111111111111111111111111', + destinationTokenInfo: { decimals: 18 }, + sourceAmount: '10000000000000000000', + sourceToken: '0xsomeERC20TokenAddress', + trade: { + value: '0x0', + }, + fee: 1, + }, + [TEST_AGG_ID_2]: { + aggregator: TEST_AGG_ID_2, + approvalNeeded: null, + gasEstimate: '0x30d40', + destinationAmount: '20196000000000000000', + destinationToken: '0x1111111111111111111111111111111111111111', + destinationTokenInfo: { decimals: 18 }, + sourceAmount: '10000000000000000000', + sourceToken: '0xsomeERC20TokenAddress', + trade: { + value: '0x0', + }, + fee: 1, + }, + [TEST_AGG_ID_3]: { + aggregator: TEST_AGG_ID_3, + approvalNeeded: null, + gasEstimate: '0x493e0', + destinationAmount: '19998000000000000000', + destinationToken: '0x1111111111111111111111111111111111111111', + destinationTokenInfo: { decimals: 18 }, + sourceAmount: '10000000000000000000', + sourceToken: '0xsomeERC20TokenAddress', + trade: { + value: '0x0', + }, + fee: 1, + }, + [TEST_AGG_ID_4]: { + aggregator: TEST_AGG_ID_4, + approvalNeeded: null, + gasEstimate: '0x61a80', + destinationAmount: '19800000000000000000', + destinationToken: '0x1111111111111111111111111111111111111111', + destinationTokenInfo: { decimals: 18 }, + sourceAmount: '10000000000000000000', + sourceToken: '0xsomeERC20TokenAddress', + trade: { + value: '0x0', + }, + fee: 1, + }, + [TEST_AGG_ID_5]: { + aggregator: TEST_AGG_ID_5, + approvalNeeded: null, + gasEstimate: '0x7a120', + destinationAmount: '19602000000000000000', + destinationToken: '0x1111111111111111111111111111111111111111', + destinationTokenInfo: { decimals: 18 }, + sourceAmount: '10000000000000000000', + sourceToken: '0xsomeERC20TokenAddress', + trade: { + value: '0x0', + }, + fee: 1, + }, + [TEST_AGG_ID_6]: { + aggregator: TEST_AGG_ID_6, + approvalNeeded: null, + gasEstimate: '0x927c0', + destinationAmount: '19305000000000000000', + destinationToken: '0x1111111111111111111111111111111111111111', + destinationTokenInfo: { decimals: 18 }, + sourceAmount: '10000000000000000000', + sourceToken: '0xsomeERC20TokenAddress', + trade: { + value: '0x0', + }, + fee: 1, + }, + } +} + +function getTopQuoteAndSavingsBaseExpectedResults() { + const baseTestInput = getTopQuoteAndSavingsMockQuotes() + return { + [TEST_AGG_ID_1]: { + ...baseTestInput[TEST_AGG_ID_1], + isBestQuote: true, + ethFee: '0.01', + overallValueOfQuote: '2.0195', + metaMaskFeeInEth: '0.0205', + ethValueOfTokens: '2.0295', + savings: { + total: '0.0441', + performance: '0.0396', + fee: '0.025', + metaMaskFee: '0.0205', + medianMetaMaskFee: '0.0201', + }, + }, + [TEST_AGG_ID_2]: { + ...baseTestInput[TEST_AGG_ID_2], + ethFee: '0.02', + overallValueOfQuote: '1.9996', + metaMaskFeeInEth: '0.0204', + ethValueOfTokens: '2.0196', + }, + [TEST_AGG_ID_3]: { + ...baseTestInput[TEST_AGG_ID_3], + ethFee: '0.03', + overallValueOfQuote: '1.9698', + metaMaskFeeInEth: '0.0202', + ethValueOfTokens: '1.9998', + }, + [TEST_AGG_ID_4]: { + ...baseTestInput[TEST_AGG_ID_4], + ethFee: '0.04', + overallValueOfQuote: '1.94', + metaMaskFeeInEth: '0.02', + ethValueOfTokens: '1.98', + }, + [TEST_AGG_ID_5]: { + ...baseTestInput[TEST_AGG_ID_5], + ethFee: '0.05', + overallValueOfQuote: '1.9102', + metaMaskFeeInEth: '0.0198', + ethValueOfTokens: '1.9602', + }, + [TEST_AGG_ID_6]: { + ...baseTestInput[TEST_AGG_ID_6], + ethFee: '0.06', + overallValueOfQuote: '1.8705', + metaMaskFeeInEth: '0.0195', + ethValueOfTokens: '1.9305', }, } } diff --git a/test/unit/app/controllers/transactions/pending-tx-tracker-test.js b/test/unit/app/controllers/transactions/pending-tx-tracker-test.js index bbdc72551..6252da35a 100644 --- a/test/unit/app/controllers/transactions/pending-tx-tracker-test.js +++ b/test/unit/app/controllers/transactions/pending-tx-tracker-test.js @@ -2,6 +2,7 @@ import { strict as assert } from 'assert' import sinon from 'sinon' import BN from 'bn.js' import PendingTransactionTracker from '../../../../../app/scripts/controllers/transactions/pending-tx-tracker' +import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction' describe('PendingTransactionTracker', function () { describe('#resubmitPendingTxs', function () { @@ -28,17 +29,23 @@ describe('PendingTransactionTracker', function () { pendingTxTracker.on('tx:warning', warningListener) await pendingTxTracker.resubmitPendingTxs('0x1') - assert.ok(getPendingTransactions.calledOnceWithExactly(), 'should call getPendingTransaction') + assert.ok( + getPendingTransactions.calledOnceWithExactly(), + 'should call getPendingTransaction', + ) assert.ok(resubmitTx.notCalled, 'should NOT call _resubmitTx') assert.ok(warningListener.notCalled, "should NOT emit 'tx:warning'") }) it('should resubmit each pending transaction', async function () { - const getPendingTransactions = sinon.stub().returns([{ - id: 1, - }, { - id: 2, - }]) + const getPendingTransactions = sinon.stub().returns([ + { + id: 1, + }, + { + id: 2, + }, + ]) const pendingTxTracker = new PendingTransactionTracker({ query: { getTransactionReceipt: sinon.stub(), @@ -60,15 +67,20 @@ describe('PendingTransactionTracker', function () { pendingTxTracker.on('tx:warning', warningListener) await pendingTxTracker.resubmitPendingTxs('0x1') - assert.ok(getPendingTransactions.calledOnceWithExactly(), 'should call getPendingTransaction') + assert.ok( + getPendingTransactions.calledOnceWithExactly(), + 'should call getPendingTransaction', + ) assert.ok(resubmitTx.calledTwice, 'should call _resubmitTx') assert.ok(warningListener.notCalled, "should NOT emit 'tx:warning'") }) it("should NOT emit 'tx:warning' for known failed resubmission", async function () { - const getPendingTransactions = sinon.stub().returns([{ - id: 1, - }]) + const getPendingTransactions = sinon.stub().returns([ + { + id: 1, + }, + ]) const pendingTxTracker = new PendingTransactionTracker({ query: { getTransactionReceipt: sinon.stub(), @@ -84,21 +96,28 @@ describe('PendingTransactionTracker', function () { publishTransaction: sinon.spy(), confirmTransaction: sinon.spy(), }) - const resubmitTx = sinon.stub(pendingTxTracker, '_resubmitTx').rejects({ message: 'known transaction' }) + const resubmitTx = sinon + .stub(pendingTxTracker, '_resubmitTx') + .rejects({ message: 'known transaction' }) const warningListener = sinon.spy() pendingTxTracker.on('tx:warning', warningListener) await pendingTxTracker.resubmitPendingTxs('0x1') - assert.ok(getPendingTransactions.calledOnceWithExactly(), 'should call getPendingTransaction') + assert.ok( + getPendingTransactions.calledOnceWithExactly(), + 'should call getPendingTransaction', + ) assert.ok(resubmitTx.calledOnce, 'should call _resubmitTx') assert.ok(warningListener.notCalled, "should NOT emit 'tx:warning'") }) it("should emit 'tx:warning' for unknown failed resubmission", async function () { - const getPendingTransactions = sinon.stub().returns([{ - id: 1, - }]) + const getPendingTransactions = sinon.stub().returns([ + { + id: 1, + }, + ]) const pendingTxTracker = new PendingTransactionTracker({ query: { getTransactionReceipt: sinon.stub(), @@ -114,13 +133,18 @@ describe('PendingTransactionTracker', function () { publishTransaction: sinon.spy(), confirmTransaction: sinon.spy(), }) - const resubmitTx = sinon.stub(pendingTxTracker, '_resubmitTx').rejects({ message: 'who dis' }) + const resubmitTx = sinon + .stub(pendingTxTracker, '_resubmitTx') + .rejects({ message: 'who dis' }) const warningListener = sinon.spy() pendingTxTracker.on('tx:warning', warningListener) await pendingTxTracker.resubmitPendingTxs('0x1') - assert.ok(getPendingTransactions.calledOnceWithExactly(), 'should call getPendingTransaction') + assert.ok( + getPendingTransactions.calledOnceWithExactly(), + 'should call getPendingTransaction', + ) assert.ok(resubmitTx.calledOnce, 'should call _resubmitTx') assert.ok(warningListener.calledOnce, "should emit 'tx:warning'") }) @@ -130,15 +154,17 @@ describe('PendingTransactionTracker', function () { it('should call _checkPendingTx for each pending transaction', async function () { const txMeta = { id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'signed', + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.SIGNED, txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', nonce: '0x1', value: '0xfffff', }, history: [{}], - rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', + rawTx: + '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', } const txList = [1, 2, 3].map((id) => ({ ...txMeta, id })) const pendingTxTracker = new PendingTransactionTracker({ @@ -158,13 +184,27 @@ describe('PendingTransactionTracker', function () { confirmTransaction: () => undefined, }) - const checkPendingTxStub = sinon.stub(pendingTxTracker, '_checkPendingTx').resolves() + const checkPendingTxStub = sinon + .stub(pendingTxTracker, '_checkPendingTx') + .resolves() await pendingTxTracker.updatePendingTxs() assert.ok(checkPendingTxStub.calledThrice) - assert.ok(checkPendingTxStub.firstCall.calledWithExactly(sinon.match.has('id', 1))) - assert.ok(checkPendingTxStub.secondCall.calledWithExactly(sinon.match.has('id', 2))) - assert.ok(checkPendingTxStub.thirdCall.calledWithExactly(sinon.match.has('id', 3))) + assert.ok( + checkPendingTxStub.firstCall.calledWithExactly( + sinon.match.has('id', 1), + ), + ) + assert.ok( + checkPendingTxStub.secondCall.calledWithExactly( + sinon.match.has('id', 2), + ), + ) + assert.ok( + checkPendingTxStub.thirdCall.calledWithExactly( + sinon.match.has('id', 3), + ), + ) }) }) @@ -172,15 +212,17 @@ describe('PendingTransactionTracker', function () { it('should publish a new transaction', async function () { const txMeta = { id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'signed', + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.SIGNED, txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', nonce: '0x1', value: '0xfffff', }, history: [{}], - rawTx: '0xf86c808504a817c80086a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', + rawTx: + '0xf86c808504a817c80086a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', } const approveTransaction = sinon.spy() const publishTransaction = sinon.spy() @@ -202,22 +244,30 @@ describe('PendingTransactionTracker', function () { await pendingTxTracker._resubmitTx(txMeta) - assert.ok(publishTransaction.calledOnceWithExactly(txMeta.rawTx), 'should call publish transaction with the rawTx') - assert.ok(approveTransaction.notCalled, 'should NOT try to approve transaction') + assert.ok( + publishTransaction.calledOnceWithExactly(txMeta.rawTx), + 'should call publish transaction with the rawTx', + ) + assert.ok( + approveTransaction.notCalled, + 'should NOT try to approve transaction', + ) }) it('should publish the given transaction if more than 2**retryCount blocks have passed', async function () { const txMeta = { id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'signed', + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.SIGNED, txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', nonce: '0x1', value: '0xfffff', }, history: [{}], - rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a996e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', + rawTx: + '0xf86c808504a817c800827b0d940c62bb85faa3311a996e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', retryCount: 4, firstRetryBlockNumber: '0x1', } @@ -241,22 +291,30 @@ describe('PendingTransactionTracker', function () { await pendingTxTracker._resubmitTx(txMeta, '0x11' /* 16 */) - assert.ok(publishTransaction.calledOnceWithExactly(txMeta.rawTx), 'should try to publish transaction') - assert.ok(approveTransaction.notCalled, 'should NOT try to approve transaction') + assert.ok( + publishTransaction.calledOnceWithExactly(txMeta.rawTx), + 'should try to publish transaction', + ) + assert.ok( + approveTransaction.notCalled, + 'should NOT try to approve transaction', + ) }) it('should NOT publish the given transaction if fewer than 2**retryCount blocks have passed', async function () { const txMeta = { id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'signed', + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.SIGNED, txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', nonce: '0x1', value: '0xfffff', }, history: [{}], - rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a996e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', + rawTx: + '0xf86c808504a817c800827b0d940c62bb85faa3311a996e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', retryCount: 4, firstRetryBlockNumber: '0x1', } @@ -280,8 +338,14 @@ describe('PendingTransactionTracker', function () { await pendingTxTracker._resubmitTx(txMeta, '0x5') - assert.ok(publishTransaction.notCalled, 'should NOT try to publish transaction') - assert.ok(approveTransaction.notCalled, 'should NOT try to approve transaction') + assert.ok( + publishTransaction.notCalled, + 'should NOT try to publish transaction', + ) + assert.ok( + approveTransaction.notCalled, + 'should NOT try to approve transaction', + ) }) it('should call approveTransaction if the tx is not yet signed', async function () { @@ -305,8 +369,14 @@ describe('PendingTransactionTracker', function () { await pendingTxTracker._resubmitTx({ id: 40 }) - assert.ok(approveTransaction.calledOnceWithExactly(40), 'should call approveTransaction with the tx ID') - assert.ok(publishTransaction.notCalled, 'should NOT try to publish transaction') + assert.ok( + approveTransaction.calledOnceWithExactly(40), + 'should call approveTransaction with the tx ID', + ) + assert.ok( + publishTransaction.notCalled, + 'should NOT try to publish transaction', + ) }) }) @@ -331,17 +401,20 @@ describe('PendingTransactionTracker', function () { pendingTxTracker.DROPPED_BUFFER_COUNT = 0 - assert.ok(await pendingTxTracker._checkIfTxWasDropped({ - id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'submitted', - txParams: { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - nonce: '0x1', - value: '0xfffff', - }, - rawTx: '0xf86c808504a817c800827b0d940c62bba0ea0d00cc9789d0d7ff1f471d', - })) + assert.ok( + await pendingTxTracker._checkIfTxWasDropped({ + id: 1, + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.SUBMITTED, + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x1', + value: '0xfffff', + }, + rawTx: '0xf86c808504a817c800827b0d940c62bba0ea0d00cc9789d0d7ff1f471d', + }), + ) }) it('should return false when the given nonce is the network nonce', async function () { @@ -364,14 +437,16 @@ describe('PendingTransactionTracker', function () { const dropped = await pendingTxTracker._checkIfTxWasDropped({ id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'submitted', + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.SUBMITTED, txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', nonce: '0x1', value: '0xfffff', }, - rawTx: '0xf86c808504a89e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', + rawTx: + '0xf86c808504a89e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', }) assert.ok(!dropped, 'should be false') @@ -380,27 +455,34 @@ describe('PendingTransactionTracker', function () { describe('#_checkIfNonceIsTaken', function () { it('should return false if the given nonce is not taken', async function () { - const confirmedTxList = [{ - id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'confirmed', - txParams: { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - nonce: '0x1', - value: '0xfffff', + const confirmedTxList = [ + { + id: 1, + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x1', + value: '0xfffff', + }, + rawTx: + '0xf86c808504a817c800827b0d940c62bb85fa3320e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', }, - rawTx: '0xf86c808504a817c800827b0d940c62bb85fa3320e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', - }, { - id: 2, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'confirmed', - txParams: { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - nonce: '0x2', - value: '0xfffff', + { + id: 2, + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x2', + value: '0xfffff', + }, + rawTx: + '0xf86c808507a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', }, - rawTx: '0xf86c808507a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', - }] + ] const getCompletedTransactions = sinon.stub().returns(confirmedTxList) const pendingTxTracker = new PendingTransactionTracker({ query: sinon.spy(), @@ -423,32 +505,43 @@ describe('PendingTransactionTracker', function () { }, }) - assert.ok(getCompletedTransactions.calledOnceWithExactly('0x1678a085c290ebd122dc42cba69373b5953b831d')) + assert.ok( + getCompletedTransactions.calledOnceWithExactly( + '0x1678a085c290ebd122dc42cba69373b5953b831d', + ), + ) assert.ok(!taken) }) it('should return true if the nonce is taken', async function () { - const confirmedTxList = [{ - id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'confirmed', - txParams: { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - nonce: '0x1', - value: '0xfffff', + const confirmedTxList = [ + { + id: 1, + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x1', + value: '0xfffff', + }, + rawTx: + '0xf86c808504a817c80082ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', }, - rawTx: '0xf86c808504a817c80082ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', - }, { - id: 2, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'confirmed', - txParams: { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - nonce: '0x2', - value: '0xfffff', + { + id: 2, + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x2', + value: '0xfffff', + }, + rawTx: + '0xf86c808504a817c800827b0d940c62bb760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', }, - rawTx: '0xf86c808504a817c800827b0d940c62bb760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', - }] + ] const getCompletedTransactions = sinon.stub().returns(confirmedTxList) const pendingTxTracker = new PendingTransactionTracker({ query: sinon.spy(), @@ -471,7 +564,11 @@ describe('PendingTransactionTracker', function () { }, }) - assert.ok(getCompletedTransactions.calledOnceWithExactly('0x1678a085c290ebd122dc42cba69373b5953b831d')) + assert.ok( + getCompletedTransactions.calledOnceWithExactly( + '0x1678a085c290ebd122dc42cba69373b5953b831d', + ), + ) assert.ok(taken) }) }) @@ -480,8 +577,9 @@ describe('PendingTransactionTracker', function () { it("should emit 'tx:warning' if getTransactionReceipt rejects", async function () { const txMeta = { id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'submitted', + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.SUBMITTED, txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', nonce: '0x1', @@ -550,12 +648,12 @@ describe('PendingTransactionTracker', function () { pendingTxTracker.once('tx:failed', listeners.failed) pendingTxTracker.once('tx:warning', listeners.warning) await pendingTxTracker._checkPendingTx({ - 'status': 'confirmed', - 'history': [{}], - 'txParams': { 'nonce': '0x1' }, - 'id': '456', - 'value': '0x01', - 'hash': '0xbad', + status: TRANSACTION_STATUSES.CONFIRMED, + history: [{}], + txParams: { nonce: '0x1' }, + id: '456', + value: '0x01', + hash: '0xbad', }) assert.ok(listeners.failed.notCalled, "should not emit 'tx:failed'") @@ -591,32 +689,42 @@ describe('PendingTransactionTracker', function () { await pendingTxTracker._checkPendingTx({ id: '2', history: [{}], - status: 'submitted', + status: TRANSACTION_STATUSES.SUBMITTED, txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d' }, }) - assert.ok(listeners.failed.calledOnceWithExactly('2', sinon.match.instanceOf(Error)), "should pass txId to 'tx:failed' listener") + assert.ok( + listeners.failed.calledOnceWithExactly( + '2', + sinon.match.instanceOf(Error), + ), + "should pass txId to 'tx:failed' listener", + ) assert.ok(listeners.confirmed.notCalled, "should not emit 'tx:confirmed'") assert.ok(listeners.dropped.notCalled, "should not emit 'tx:dropped'") assert.ok(listeners.warning.notCalled, "should not emit 'tx:warning'") }) it("should emit 'tx:dropped' if another tx with the same nonce succeeds", async function () { - const txs = [{ - 'status': 'confirmed', - 'history': [{}], - 'txParams': { 'nonce': '0x1' }, - 'id': '456', - 'value': '0x01', - 'hash': '0xbad', - }, { - 'status': 'submitted', - 'history': [{}], - 'txParams': { 'nonce': '0x1' }, - 'id': '123', - 'value': '0x02', - 'hash': '0x2a919d2512ec963f524bfd9730fb66b6d5a2e399d1dd957abb5e2b544a12644b', - }] + const txs = [ + { + status: TRANSACTION_STATUSES.CONFIRMED, + history: [{}], + txParams: { nonce: '0x1' }, + id: '456', + value: '0x01', + hash: '0xbad', + }, + { + status: TRANSACTION_STATUSES.SUBMITTED, + history: [{}], + txParams: { nonce: '0x1' }, + id: '123', + value: '0x02', + hash: + '0x2a919d2512ec963f524bfd9730fb66b6d5a2e399d1dd957abb5e2b544a12644b', + }, + ] const pendingTxTracker = new PendingTransactionTracker({ query: { getTransactionReceipt: sinon.stub().resolves(null), @@ -654,8 +762,9 @@ describe('PendingTransactionTracker', function () { const nonceBN = new BN(2) const txMeta = { id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'submitted', + hash: + '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: TRANSACTION_STATUSES.SUBMITTED, txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', nonce: '0x1', diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index 68c96f108..f3426d5f9 100644 --- a/test/unit/app/controllers/transactions/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -5,17 +5,16 @@ import EthTx from 'ethereumjs-tx' import ObservableStore from 'obs-store' import sinon from 'sinon' import TransactionController from '../../../../../app/scripts/controllers/transactions' -import { TRANSACTION_TYPE_RETRY } from '../../../../../app/scripts/controllers/transactions/enums' import { - TOKEN_METHOD_APPROVE, - TOKEN_METHOD_TRANSFER, - SEND_ETHER_ACTION_KEY, - DEPLOY_CONTRACT_ACTION_KEY, - CONTRACT_INTERACTION_KEY, -} from '../../../../../ui/app/helpers/constants/transactions' - -import { createTestProviderTools, getTestAccounts } from '../../../../stub/provider' + createTestProviderTools, + getTestAccounts, +} from '../../../../stub/provider' +import { + TRANSACTION_CATEGORIES, + TRANSACTION_STATUSES, + TRANSACTION_TYPES, +} from '../../../../../shared/constants/transaction' const noop = () => true const currentNetworkId = '42' @@ -31,46 +30,79 @@ describe('Transaction Controller', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0x', } - provider = createTestProviderTools({ scaffold: providerResultStub }).provider + provider = createTestProviderTools({ scaffold: providerResultStub }) + .provider fromAccount = getTestAccounts()[0] const blockTrackerStub = new EventEmitter() blockTrackerStub.getCurrentBlock = noop blockTrackerStub.getLatestBlock = noop txController = new TransactionController({ provider, - getGasPrice () { + getGasPrice() { return '0xee6b2800' }, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: blockTrackerStub, - signTransaction: (ethTx) => new Promise((resolve) => { - ethTx.sign(fromAccount.key) - resolve() - }), + signTransaction: (ethTx) => + new Promise((resolve) => { + ethTx.sign(fromAccount.key) + resolve() + }), getPermittedAccounts: () => undefined, getCurrentChainId: () => currentChainId, getParticipateInMetrics: () => false, }) - txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) + txController.nonceTracker.getNonceLock = () => + Promise.resolve({ nextNonce: 0, releaseLock: noop }) }) describe('#getState', function () { it('should return a state object with the right keys and data types', function () { const exposedState = txController.getState() - assert.ok('unapprovedTxs' in exposedState, 'state should have the key unapprovedTxs') - assert.ok('currentNetworkTxList' in exposedState, 'state should have the key currentNetworkTxList') - assert.ok(typeof exposedState?.unapprovedTxs === 'object', 'should be an object') - assert.ok(Array.isArray(exposedState.currentNetworkTxList), 'should be an array') + assert.ok( + 'unapprovedTxs' in exposedState, + 'state should have the key unapprovedTxs', + ) + assert.ok( + 'currentNetworkTxList' in exposedState, + 'state should have the key currentNetworkTxList', + ) + assert.ok( + typeof exposedState?.unapprovedTxs === 'object', + 'should be an object', + ) + assert.ok( + Array.isArray(exposedState.currentNetworkTxList), + 'should be an array', + ) }) }) describe('#getUnapprovedTxCount', function () { it('should return the number of unapproved txs', function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, - { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, - { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, + { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, + { + id: 2, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, + { + id: 3, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, ]) const unapprovedTxCount = txController.getUnapprovedTxCount() assert.equal(unapprovedTxCount, 3, 'should be 3') @@ -80,9 +112,27 @@ describe('Transaction Controller', function () { describe('#getPendingTxCount', function () { it('should return the number of pending txs', function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, - { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, + { + id: 1, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, + { + id: 2, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, + { + id: 3, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, ]) const pendingTxCount = txController.getPendingTxCount() assert.equal(pendingTxCount, 3, 'should be 3') @@ -93,21 +143,78 @@ describe('Transaction Controller', function () { it('should return the number of confirmed txs', function () { const address = '0xc684832530fcbddae4b4230a47e991ddcec2831d' const txParams = { - 'from': address, - 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', + from: address, + to: '0xc684832530fcbddae4b4230a47e991ddcec2831d', } txController.txStateManager._saveTxList([ - { id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [{}] }, - { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [{}] }, - { id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [{}] }, - { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams, history: [{}] }, - { id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams, history: [{}] }, - { id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams, history: [{}] }, - { id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams, history: [{}] }, - { id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [{}] }, - { id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams, history: [{}] }, + { + id: 0, + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams, + history: [{}], + }, + { + id: 1, + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams, + history: [{}], + }, + { + id: 2, + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams, + history: [{}], + }, + { + id: 3, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams, + history: [{}], + }, + { + id: 4, + status: TRANSACTION_STATUSES.REJECTED, + metamaskNetworkId: currentNetworkId, + txParams, + history: [{}], + }, + { + id: 5, + status: TRANSACTION_STATUSES.APPROVED, + metamaskNetworkId: currentNetworkId, + txParams, + history: [{}], + }, + { + id: 6, + status: TRANSACTION_STATUSES.SIGNED, + metamaskNetworkId: currentNetworkId, + txParams, + history: [{}], + }, + { + id: 7, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + txParams, + history: [{}], + }, + { + id: 8, + status: TRANSACTION_STATUSES.FAILED, + metamaskNetworkId: currentNetworkId, + txParams, + history: [{}], + }, ]) - assert.equal(txController.nonceTracker.getConfirmedTransactions(address).length, 3) + assert.equal( + txController.nonceTracker.getConfirmedTransactions(address).length, + 3, + ) }) }) @@ -115,21 +222,23 @@ describe('Transaction Controller', function () { let stub, txMeta, txParams beforeEach(function () { txParams = { - 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d', - 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', + from: '0xc684832530fcbddae4b4230a47e991ddcec2831d', + to: '0xc684832530fcbddae4b4230a47e991ddcec2831d', } txMeta = { - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, id: 1, metamaskNetworkId: currentNetworkId, txParams, history: [{}], } txController.txStateManager._saveTxList([txMeta]) - stub = sinon.stub(txController, 'addUnapprovedTransaction').callsFake(() => { - txController.emit('newUnapprovedTx', txMeta) - return Promise.resolve(txController.txStateManager.addTx(txMeta)) - }) + stub = sinon + .stub(txController, 'addUnapprovedTransaction') + .callsFake(() => { + txController.emit('newUnapprovedTx', txMeta) + return Promise.resolve(txController.txStateManager.addTx(txMeta)) + }) }) afterEach(function () { @@ -158,7 +267,9 @@ describe('Transaction Controller', function () { await assert.rejects( () => txController.newUnapprovedTransaction(txParams), - { message: 'MetaMask Tx Signature: User denied transaction signature.' }, + { + message: 'MetaMask Tx Signature: User denied transaction signature.', + }, ) }) }) @@ -168,8 +279,12 @@ describe('Transaction Controller', function () { let getSelectedAddress, getPermittedAccounts beforeEach(function () { - getSelectedAddress = sinon.stub(txController, 'getSelectedAddress').returns(selectedAddress) - getPermittedAccounts = sinon.stub(txController, 'getPermittedAccounts').returns([selectedAddress]) + getSelectedAddress = sinon + .stub(txController, 'getSelectedAddress') + .returns(selectedAddress) + getPermittedAccounts = sinon + .stub(txController, 'getPermittedAccounts') + .returns([selectedAddress]) }) afterEach(function () { @@ -178,13 +293,22 @@ describe('Transaction Controller', function () { }) it('should add an unapproved transaction and return a valid txMeta', async function () { - const txMeta = await txController.addUnapprovedTransaction({ from: selectedAddress }) + const txMeta = await txController.addUnapprovedTransaction({ + from: selectedAddress, + }) assert.ok('id' in txMeta, 'should have a id') assert.ok('time' in txMeta, 'should have a time stamp') - assert.ok('metamaskNetworkId' in txMeta, 'should have a metamaskNetworkId') + assert.ok( + 'metamaskNetworkId' in txMeta, + 'should have a metamaskNetworkId', + ) assert.ok('txParams' in txMeta, 'should have a txParams') assert.ok('history' in txMeta, 'should have a history') - assert.equal(txMeta.txParams.value, '0x0', 'should have added 0x0 as the value') + assert.equal( + txMeta.txParams.value, + '0x0', + 'should have added 0x0 as the value', + ) const memTxMeta = txController.txStateManager.getTx(txMeta.id) assert.deepEqual(txMeta, memTxMeta) @@ -196,18 +320,27 @@ describe('Transaction Controller', function () { assert.ok(txMetaFromEmit, 'txMeta is falsy') done() }) - txController.addUnapprovedTransaction({ from: selectedAddress }) + txController + .addUnapprovedTransaction({ from: selectedAddress }) .catch(done) }) it("should fail if the from address isn't the selected address", async function () { - await assert.rejects(() => txController.addUnapprovedTransaction({ from: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' })) + await assert.rejects(() => + txController.addUnapprovedTransaction({ + from: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2', + }), + ) }) it('should fail if netId is loading', async function () { txController.networkStore = new ObservableStore('loading') await assert.rejects( - () => txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }), + () => + txController.addUnapprovedTransaction({ + from: selectedAddress, + to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2', + }), { message: 'MetaMask is having trouble connecting to the network' }, ) }) @@ -216,7 +349,13 @@ describe('Transaction Controller', function () { describe('#addTxGasDefaults', function () { it('should add the tx defaults if their are none', async function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, + { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, ]) const txMeta = { id: 1, @@ -231,8 +370,14 @@ describe('Transaction Controller', function () { providerResultStub.eth_estimateGas = '5209' const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta) - assert.ok(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price') - assert.ok(txMetaWithDefaults.txParams.gas, 'should have added the gas field') + assert.ok( + txMetaWithDefaults.txParams.gasPrice, + 'should have added the gas price', + ) + assert.ok( + txMetaWithDefaults.txParams.gas, + 'should have added the gas field', + ) }) }) @@ -240,7 +385,7 @@ describe('Transaction Controller', function () { it('should emit updates', function (done) { const txMeta = { id: '1', - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, metamaskNetworkId: currentNetworkId, txParams: {}, } @@ -248,15 +393,21 @@ describe('Transaction Controller', function () { const eventNames = ['update:badge', '1:unapproved'] const listeners = [] eventNames.forEach((eventName) => { - listeners.push(new Promise((resolve) => { - txController.once(eventName, (arg) => { - resolve(arg) - }) - })) + listeners.push( + new Promise((resolve) => { + txController.once(eventName, (arg) => { + resolve(arg) + }) + }), + ) }) Promise.all(listeners) .then((returnValues) => { - assert.deepEqual(returnValues.pop(), txMeta, 'last event 1:unapproved should return txMeta') + assert.deepEqual( + returnValues.pop(), + txMeta, + 'last event 1:unapproved should return txMeta', + ) done() }) .catch(done) @@ -269,7 +420,7 @@ describe('Transaction Controller', function () { const originalValue = '0x01' const txMeta = { id: '1', - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, metamaskNetworkId: currentNetworkId, txParams: { nonce: originalValue, @@ -284,12 +435,16 @@ describe('Transaction Controller', function () { providerResultStub.eth_gasPrice = wrongValue providerResultStub.eth_estimateGas = '0x5209' - const signStub = sinon.stub(txController, 'signTransaction').callsFake(() => Promise.resolve()) + const signStub = sinon + .stub(txController, 'signTransaction') + .callsFake(() => Promise.resolve()) - const pubStub = sinon.stub(txController, 'publishTransaction').callsFake(() => { - txController.setTxHash('1', originalValue) - txController.txStateManager.setTxStatusSubmitted('1') - }) + const pubStub = sinon + .stub(txController, 'publishTransaction') + .callsFake(() => { + txController.setTxHash('1', originalValue) + txController.txStateManager.setTxStatusSubmitted('1') + }) await txController.approveTransaction(txMeta.id) const result = txController.txStateManager.getTx(txMeta.id) @@ -298,7 +453,11 @@ describe('Transaction Controller', function () { assert.equal(params.gas, originalValue, 'gas unmodified') assert.equal(params.gasPrice, originalValue, 'gas price unmodified') assert.equal(result.hash, originalValue) - assert.equal(result.status, 'submitted', 'should have reached the submitted status.') + assert.equal( + result.status, + TRANSACTION_STATUSES.SUBMITTED, + 'should have reached the submitted status.', + ) signStub.restore() pubStub.restore() }) @@ -306,7 +465,15 @@ describe('Transaction Controller', function () { describe('#sign replay-protected tx', function () { it('prepares a tx with the chainId set', async function () { - txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.addTx( + { + id: '1', + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + }, + noop, + ) const rawTx = await txController.signTransaction('1') const ethTx = new EthTx(ethUtil.toBuffer(rawTx)) assert.equal(ethTx.getChainId(), 42) @@ -317,7 +484,7 @@ describe('Transaction Controller', function () { it('should update and approve transactions', async function () { const txMeta = { id: 1, - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, txParams: { from: fromAccount.address, to: '0x1678a085c290ebd122dc42cba69373b5953b831d', @@ -330,7 +497,7 @@ describe('Transaction Controller', function () { txController.txStateManager.addTx(txMeta) const approvalPromise = txController.updateAndApproveTransaction(txMeta) const tx = txController.txStateManager.getTx(1) - assert.equal(tx.status, 'approved') + assert.equal(tx.status, TRANSACTION_STATUSES.APPROVED) await approvalPromise }) }) @@ -345,18 +512,64 @@ describe('Transaction Controller', function () { describe('#cancelTransaction', function () { it('should emit a status change to rejected', function (done) { txController.txStateManager._saveTxList([ - { id: 0, status: 'unapproved', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 1, status: 'rejected', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 2, status: 'approved', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 3, status: 'signed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 4, status: 'submitted', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 5, status: 'confirmed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 6, status: 'failed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, + { + id: 0, + status: TRANSACTION_STATUSES.UNAPPROVED, + txParams: {}, + metamaskNetworkId: currentNetworkId, + history: [{}], + }, + { + id: 1, + status: TRANSACTION_STATUSES.REJECTED, + txParams: {}, + metamaskNetworkId: currentNetworkId, + history: [{}], + }, + { + id: 2, + status: TRANSACTION_STATUSES.APPROVED, + txParams: {}, + metamaskNetworkId: currentNetworkId, + history: [{}], + }, + { + id: 3, + status: TRANSACTION_STATUSES.SIGNED, + txParams: {}, + metamaskNetworkId: currentNetworkId, + history: [{}], + }, + { + id: 4, + status: TRANSACTION_STATUSES.SUBMITTED, + txParams: {}, + metamaskNetworkId: currentNetworkId, + history: [{}], + }, + { + id: 5, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: {}, + metamaskNetworkId: currentNetworkId, + history: [{}], + }, + { + id: 6, + status: TRANSACTION_STATUSES.FAILED, + txParams: {}, + metamaskNetworkId: currentNetworkId, + history: [{}], + }, ]) txController.once('tx:status-update', (txId, status) => { try { - assert.equal(status, 'rejected', 'status should e rejected') + assert.equal( + status, + TRANSACTION_STATUSES.REJECTED, + 'status should be rejected', + ) assert.equal(txId, 0, 'id should e 0') done() } catch (e) { @@ -386,7 +599,13 @@ describe('Transaction Controller', function () { gasPrice: '0xa', } txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [{}] }, + { + id: 1, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + txParams, + history: [{}], + }, ]) expectedTxParams = { ...txParams, gasPrice: '0xb' } @@ -405,10 +624,13 @@ describe('Transaction Controller', function () { assert.deepEqual(addTxArgs.txParams, expectedTxParams) const { lastGasPrice, type } = addTxArgs - assert.deepEqual({ lastGasPrice, type }, { - lastGasPrice: '0xa', - type: TRANSACTION_TYPE_RETRY, - }) + assert.deepEqual( + { lastGasPrice, type }, + { + lastGasPrice: '0xa', + type: TRANSACTION_TYPES.RETRY, + }, + ) }) it('should call this.approveTransaction with the id of the returned tx', async function () { @@ -425,20 +647,24 @@ describe('Transaction Controller', function () { assert.deepEqual(result.txParams, expectedTxParams) const { lastGasPrice, type } = result - assert.deepEqual({ lastGasPrice, type }, { - lastGasPrice: '0xa', - type: TRANSACTION_TYPE_RETRY, - }) + assert.deepEqual( + { lastGasPrice, type }, + { + lastGasPrice: '0xa', + type: TRANSACTION_TYPES.RETRY, + }, + ) }) }) describe('#publishTransaction', function () { let hash, txMeta beforeEach(function () { - hash = '0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8' + hash = + '0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8' txMeta = { id: 1, - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, txParams: {}, metamaskNetworkId: currentNetworkId, } @@ -446,42 +672,96 @@ describe('Transaction Controller', function () { }) it('should publish a tx, updates the rawTx when provided a one', async function () { - const rawTx = '0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c' + const rawTx = + '0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c' txController.txStateManager.addTx(txMeta) await txController.publishTransaction(txMeta.id, rawTx) const publishedTx = txController.txStateManager.getTx(1) assert.equal(publishedTx.hash, hash) - assert.equal(publishedTx.status, 'submitted') + assert.equal(publishedTx.status, TRANSACTION_STATUSES.SUBMITTED) }) it('should ignore the error "Transaction Failed: known transaction" and be as usual', async function () { providerResultStub.eth_sendRawTransaction = async (_, __, ___, end) => { end('Transaction Failed: known transaction') } - const rawTx = '0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a' + const rawTx = + '0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a' txController.txStateManager.addTx(txMeta) await txController.publishTransaction(txMeta.id, rawTx) const publishedTx = txController.txStateManager.getTx(1) - assert.equal(publishedTx.hash, '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09') - assert.equal(publishedTx.status, 'submitted') + assert.equal( + publishedTx.hash, + '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09', + ) + assert.equal(publishedTx.status, TRANSACTION_STATUSES.SUBMITTED) }) }) describe('#_markNonceDuplicatesDropped', function () { it('should mark all nonce duplicates as dropped without marking the confirmed transaction as dropped', function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 4, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 6, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { + id: 1, + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + history: [{}], + txParams: { nonce: '0x01' }, + }, + { + id: 2, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + history: [{}], + txParams: { nonce: '0x01' }, + }, + { + id: 3, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + history: [{}], + txParams: { nonce: '0x01' }, + }, + { + id: 4, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + history: [{}], + txParams: { nonce: '0x01' }, + }, + { + id: 5, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + history: [{}], + txParams: { nonce: '0x01' }, + }, + { + id: 6, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + history: [{}], + txParams: { nonce: '0x01' }, + }, + { + id: 7, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + history: [{}], + txParams: { nonce: '0x01' }, + }, ]) txController._markNonceDuplicatesDropped(1) const confirmedTx = txController.txStateManager.getTx(1) - const droppedTxs = txController.txStateManager.getFilteredTxList({ nonce: '0x01', status: 'dropped' }) - assert.equal(confirmedTx.status, 'confirmed', 'the confirmedTx should remain confirmed') + const droppedTxs = txController.txStateManager.getFilteredTxList({ + nonce: '0x01', + status: TRANSACTION_STATUSES.DROPPED, + }) + assert.equal( + confirmedTx.status, + TRANSACTION_STATUSES.CONFIRMED, + 'the confirmedTx should remain confirmed', + ) assert.equal(droppedTxs.length, 6, 'their should be 6 dropped txs') }) }) @@ -492,23 +772,34 @@ describe('Transaction Controller', function () { to: '0xabc', data: '', }) - assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: null }) + assert.deepEqual(result, { + transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER, + getCodeResponse: null, + }) }) it('should return a token transfer transactionCategory when data is for the respective method call', async function () { const result = await txController._determineTransactionCategory({ to: '0xabc', - data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', + data: + '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', + }) + assert.deepEqual(result, { + transactionCategory: TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, + getCodeResponse: undefined, }) - assert.deepEqual(result, { transactionCategory: TOKEN_METHOD_TRANSFER, getCodeResponse: undefined }) }) it('should return a token approve transactionCategory when data is for the respective method call', async function () { const result = await txController._determineTransactionCategory({ to: '0xabc', - data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', + data: + '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', + }) + assert.deepEqual(result, { + transactionCategory: TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE, + getCodeResponse: undefined, }) - assert.deepEqual(result, { transactionCategory: TOKEN_METHOD_APPROVE, getCodeResponse: undefined }) }) it('should return a contract deployment transactionCategory when to is falsy and there is data', async function () { @@ -516,7 +807,10 @@ describe('Transaction Controller', function () { to: '', data: '0xabd', }) - assert.deepEqual(result, { transactionCategory: DEPLOY_CONTRACT_ACTION_KEY, getCodeResponse: undefined }) + assert.deepEqual(result, { + transactionCategory: TRANSACTION_CATEGORIES.DEPLOY_CONTRACT, + getCodeResponse: undefined, + }) }) it('should return a simple send transactionCategory with a 0x getCodeResponse when there is data and but the to address is not a contract address', async function () { @@ -524,7 +818,10 @@ describe('Transaction Controller', function () { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '0xabd', }) - assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: '0x' }) + assert.deepEqual(result, { + transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER, + getCodeResponse: '0x', + }) }) it('should return a simple send transactionCategory with a null getCodeResponse when to is truthy and there is data and but getCode returns an error', async function () { @@ -532,7 +829,10 @@ describe('Transaction Controller', function () { to: '0xabc', data: '0xabd', }) - assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: null }) + assert.deepEqual(result, { + transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER, + getCodeResponse: null, + }) }) it('should return a contract interaction transactionCategory with the correct getCodeResponse when to is truthy and there is data and it is not a token transaction', async function () { @@ -542,30 +842,36 @@ describe('Transaction Controller', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0xa', } - const _provider = createTestProviderTools({ scaffold: _providerResultStub }).provider + const _provider = createTestProviderTools({ + scaffold: _providerResultStub, + }).provider const _fromAccount = getTestAccounts()[0] const _blockTrackerStub = new EventEmitter() _blockTrackerStub.getCurrentBlock = noop _blockTrackerStub.getLatestBlock = noop const _txController = new TransactionController({ provider: _provider, - getGasPrice () { + getGasPrice() { return '0xee6b2800' }, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: _blockTrackerStub, - signTransaction: (ethTx) => new Promise((resolve) => { - ethTx.sign(_fromAccount.key) - resolve() - }), + signTransaction: (ethTx) => + new Promise((resolve) => { + ethTx.sign(_fromAccount.key) + resolve() + }), getParticipateInMetrics: () => false, }) const result = await _txController._determineTransactionCategory({ to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: 'abd', }) - assert.deepEqual(result, { transactionCategory: CONTRACT_INTERACTION_KEY, getCodeResponse: '0x0a' }) + assert.deepEqual(result, { + transactionCategory: TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, + getCodeResponse: '0x0a', + }) }) it('should return a contract interaction transactionCategory with the correct getCodeResponse when to is a contract address and data is falsy', async function () { @@ -575,49 +881,107 @@ describe('Transaction Controller', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0xa', } - const _provider = createTestProviderTools({ scaffold: _providerResultStub }).provider + const _provider = createTestProviderTools({ + scaffold: _providerResultStub, + }).provider const _fromAccount = getTestAccounts()[0] const _blockTrackerStub = new EventEmitter() _blockTrackerStub.getCurrentBlock = noop _blockTrackerStub.getLatestBlock = noop const _txController = new TransactionController({ provider: _provider, - getGasPrice () { + getGasPrice() { return '0xee6b2800' }, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: _blockTrackerStub, - signTransaction: (ethTx) => new Promise((resolve) => { - ethTx.sign(_fromAccount.key) - resolve() - }), + signTransaction: (ethTx) => + new Promise((resolve) => { + ethTx.sign(_fromAccount.key) + resolve() + }), getParticipateInMetrics: () => false, }) const result = await _txController._determineTransactionCategory({ to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '', }) - assert.deepEqual(result, { transactionCategory: CONTRACT_INTERACTION_KEY, getCodeResponse: '0x0a' }) + assert.deepEqual(result, { + transactionCategory: TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, + getCodeResponse: '0x0a', + }) }) }) describe('#getPendingTransactions', function () { it('should show only submitted and approved transactions as pending transaction', function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 2, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, - { id: 3, status: 'approved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, - { id: 4, status: 'signed', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, - { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, - { id: 6, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, - { id: 7, status: 'failed', metamaskNetworkId: currentNetworkId, txParams: {}, history: [{}] }, + { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + }, + { + id: 2, + status: TRANSACTION_STATUSES.REJECTED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, + { + id: 3, + status: TRANSACTION_STATUSES.APPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, + { + id: 4, + status: TRANSACTION_STATUSES.SIGNED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, + { + id: 5, + status: TRANSACTION_STATUSES.SUBMITTED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, + { + id: 6, + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, + { + id: 7, + status: TRANSACTION_STATUSES.FAILED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + history: [{}], + }, ]) - assert.equal(txController.pendingTxTracker.getPendingTransactions().length, 2) - const states = txController.pendingTxTracker.getPendingTransactions().map((tx) => tx.status) - assert.ok(states.includes('approved'), 'includes approved') - assert.ok(states.includes('submitted'), 'includes submitted') + assert.equal( + txController.pendingTxTracker.getPendingTransactions().length, + 2, + ) + const states = txController.pendingTxTracker + .getPendingTransactions() + .map((tx) => tx.status) + assert.ok( + states.includes(TRANSACTION_STATUSES.APPROVED), + 'includes approved', + ) + assert.ok( + states.includes(TRANSACTION_STATUSES.SUBMITTED), + 'includes submitted', + ) }) }) }) diff --git a/test/unit/app/controllers/transactions/tx-gas-util-test.js b/test/unit/app/controllers/transactions/tx-gas-util-test.js index 21ba62746..c08b70520 100644 --- a/test/unit/app/controllers/transactions/tx-gas-util-test.js +++ b/test/unit/app/controllers/transactions/tx-gas-util-test.js @@ -7,11 +7,16 @@ describe('txUtils', function () { let txUtils before(function () { - txUtils = new TxUtils(new Proxy({}, { - get: () => { - return () => undefined - }, - })) + txUtils = new TxUtils( + new Proxy( + {}, + { + get: () => { + return () => undefined + }, + }, + ), + ) }) describe('chain Id', function () { @@ -53,7 +58,10 @@ describe('txUtils', function () { // const inputBn = hexToBn(inputHex) const outputBn = hexToBn(output) const expectedBn = hexToBn(inputHex) - assert.ok(outputBn.eq(expectedBn), 'returns the original estimatedGas value') + assert.ok( + outputBn.eq(expectedBn), + 'returns the original estimatedGas value', + ) }) it('buffers up to recommend gas limit recommended ceiling', function () { @@ -67,7 +75,11 @@ describe('txUtils', function () { // const inputBn = hexToBn(inputHex) // const outputBn = hexToBn(output) const expectedHex = bnToHex(ceilGasLimitBn) - assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value') + assert.equal( + output, + expectedHex, + 'returns the gas limit recommended ceiling value', + ) }) }) }) diff --git a/test/unit/app/controllers/transactions/tx-state-history-helpers-test.js b/test/unit/app/controllers/transactions/tx-state-history-helpers-test.js index 66833a4cd..c11cb17e0 100644 --- a/test/unit/app/controllers/transactions/tx-state-history-helpers-test.js +++ b/test/unit/app/controllers/transactions/tx-state-history-helpers-test.js @@ -37,14 +37,26 @@ describe('Transaction state history helper', function () { const newHistory = migrateFromSnapshotsToDiffs(tx.history) newHistory.forEach((newEntry, index) => { if (index === 0) { - assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj') + assert.equal( + Array.isArray(newEntry), + false, + 'initial history item IS NOT a json patch obj', + ) } else { - assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj') + assert.equal( + Array.isArray(newEntry), + true, + 'non-initial history entry IS a json patch obj', + ) } const oldEntry = tx.history[index] const historySubset = newHistory.slice(0, index + 1) const reconstructedValue = replayHistory(historySubset) - assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs') + assert.deepEqual( + oldEntry, + reconstructedValue, + 'was able to reconstruct old entry from diffs', + ) }) }) }) @@ -53,29 +65,40 @@ describe('Transaction state history helper', function () { describe('#replayHistory', function () { it('replaying history does not mutate the original object', function () { const initialState = { test: true, message: 'hello', value: 1 } - const diff1 = [{ - 'op': 'replace', - 'path': '/message', - 'value': 'haay', - }] - const diff2 = [{ - 'op': 'replace', - 'path': '/value', - 'value': 2, - }] + const diff1 = [ + { + op: 'replace', + path: '/message', + value: 'haay', + }, + ] + const diff2 = [ + { + op: 'replace', + path: '/value', + value: 2, + }, + ] const history = [initialState, diff1, diff2] const beforeStateSnapshot = JSON.stringify(initialState) const latestState = replayHistory(history) const afterStateSnapshot = JSON.stringify(initialState) - assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state') - assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run') + assert.notEqual( + initialState, + latestState, + 'initial state is not the same obj as the latest state', + ) + assert.equal( + beforeStateSnapshot, + afterStateSnapshot, + 'initial state is not modified during run', + ) }) }) describe('#generateHistoryEntry', function () { - - function generateHistoryEntryTest (note) { + function generateHistoryEntryTest(note) { const prevState = { someValue: 'value 1', foo: { @@ -102,17 +125,29 @@ describe('Transaction state history helper', function () { assert.ok(Array.isArray(result)) assert.equal(result.length, 3) - const expectedEntry1 = { op: 'add', path: '/foo/newPropFirstLevel', value: 'new property - first level' } + const expectedEntry1 = { + op: 'add', + path: '/foo/newPropFirstLevel', + value: 'new property - first level', + } assert.equal(result[0].op, expectedEntry1.op) assert.equal(result[0].path, expectedEntry1.path) assert.equal(result[0].value, expectedEntry1.value) assert.equal(result[0].note, note) assert.ok(result[0].timestamp >= before && result[0].timestamp <= after) - const expectedEntry2 = { op: 'replace', path: '/someValue', value: 'value 2' } + const expectedEntry2 = { + op: 'replace', + path: '/someValue', + value: 'value 2', + } assert.deepEqual(result[1], expectedEntry2) - const expectedEntry3 = { op: 'add', path: '/newPropRoot', value: 'new property - root' } + const expectedEntry3 = { + op: 'add', + path: '/newPropRoot', + value: 'new property - root', + } assert.deepEqual(result[2], expectedEntry3) } diff --git a/test/unit/app/controllers/transactions/tx-state-manager-test.js b/test/unit/app/controllers/transactions/tx-state-manager-test.js index 915e36362..0b32e7e6f 100644 --- a/test/unit/app/controllers/transactions/tx-state-manager-test.js +++ b/test/unit/app/controllers/transactions/tx-state-manager-test.js @@ -2,6 +2,7 @@ import { strict as assert } from 'assert' import sinon from 'sinon' import TxStateManager from '../../../../../app/scripts/controllers/transactions/tx-state-manager' import { snapshotFromTxMeta } from '../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helpers' +import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction' const noop = () => true @@ -22,17 +23,27 @@ describe('TransactionStateManager', function () { describe('#setTxStatusSigned', function () { it('sets the tx status to signed', function () { - const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + } txStateManager.addTx(tx, noop) txStateManager.setTxStatusSigned(1) const result = txStateManager.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 1) - assert.equal(result[0].status, 'signed') + assert.equal(result[0].status, TRANSACTION_STATUSES.SIGNED) }) it('should emit a signed event to signal the execution of callback', function () { - const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + } const clock = sinon.useFakeTimers() const onSigned = sinon.spy() @@ -48,7 +59,12 @@ describe('TransactionStateManager', function () { describe('#setTxStatusRejected', function () { it('sets the tx status to rejected and removes it from history', function () { - const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + } txStateManager.addTx(tx) txStateManager.setTxStatusRejected(1) const result = txStateManager.getTxList() @@ -57,7 +73,12 @@ describe('TransactionStateManager', function () { }) it('should emit a rejected event to signal the execution of callback', function () { - const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + } const clock = sinon.useFakeTimers() const onSigned = sinon.spy() @@ -96,7 +117,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x0', }, - status: 'submitted', + status: TRANSACTION_STATUSES.SUBMITTED, } const confirmedTx = { @@ -108,23 +129,17 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x3', }, - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, } const txm = new TxStateManager({ initState: { - transactions: [ - submittedTx, - confirmedTx, - ], + transactions: [submittedTx, confirmedTx], }, getNetwork: () => currentNetworkId, }) - assert.deepEqual(txm.getTxList(), [ - submittedTx, - confirmedTx, - ]) + assert.deepEqual(txm.getTxList(), [submittedTx, confirmedTx]) }) it('should return a list of transactions, limited by N unique nonces when there are NO duplicates', function () { @@ -137,7 +152,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x0', }, - status: 'submitted', + status: TRANSACTION_STATUSES.SUBMITTED, } const unapprovedTx1 = { @@ -149,7 +164,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x1', }, - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, } const approvedTx2 = { @@ -161,7 +176,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x2', }, - status: 'approved', + status: TRANSACTION_STATUSES.APPROVED, } const confirmedTx3 = { @@ -173,7 +188,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x3', }, - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, } const txm = new TxStateManager({ @@ -188,10 +203,7 @@ describe('TransactionStateManager', function () { getNetwork: () => currentNetworkId, }) - assert.deepEqual(txm.getTxList(2), [ - approvedTx2, - confirmedTx3, - ]) + assert.deepEqual(txm.getTxList(2), [approvedTx2, confirmedTx3]) }) it('should return a list of transactions, limited by N unique nonces when there ARE duplicates', function () { @@ -205,7 +217,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x0', }, - status: 'submitted', + status: TRANSACTION_STATUSES.SUBMITTED, }, { id: 0, @@ -216,7 +228,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x0', }, - status: 'submitted', + status: TRANSACTION_STATUSES.SUBMITTED, }, ] @@ -229,7 +241,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x1', }, - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, } const approvedTx2s = [ @@ -242,7 +254,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x2', }, - status: 'approved', + status: TRANSACTION_STATUSES.APPROVED, }, { id: 2, @@ -253,7 +265,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x2', }, - status: 'approved', + status: TRANSACTION_STATUSES.APPROVED, }, ] @@ -267,7 +279,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x3', }, - status: 'failed', + status: TRANSACTION_STATUSES.FAILED, }, { id: 3, @@ -278,7 +290,7 @@ describe('TransactionStateManager', function () { to: '0xRecipient', nonce: '0x3', }, - status: 'failed', + status: TRANSACTION_STATUSES.FAILED, }, ] @@ -294,16 +306,18 @@ describe('TransactionStateManager', function () { getNetwork: () => currentNetworkId, }) - assert.deepEqual(txm.getTxList(2), [ - ...approvedTx2s, - ...failedTx3s, - ]) + assert.deepEqual(txm.getTxList(2), [...approvedTx2s, ...failedTx3s]) }) }) describe('#addTx', function () { it('adds a tx returned in getTxList', function () { - const tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { + id: 1, + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + } txStateManager.addTx(tx, noop) const result = txStateManager.getTxList() assert.ok(Array.isArray(result)) @@ -327,14 +341,17 @@ describe('TransactionStateManager', function () { for (const value of invalidValues) { const tx = { id: 1, - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, metamaskNetworkId: currentNetworkId, txParams: { ...validTxParams, [key]: value, }, } - assert.throws(txStateManager.addTx.bind(txStateManager, tx), 'addTx should throw error') + assert.throws( + txStateManager.addTx.bind(txStateManager, tx), + 'addTx should throw error', + ) const result = txStateManager.getTxList() assert.ok(Array.isArray(result), 'txList should be an array') assert.equal(result.length, 0, 'txList should be empty') @@ -343,8 +360,18 @@ describe('TransactionStateManager', function () { }) it('does not override txs from other networks', function () { - const tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - const tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} } + const tx = { + id: 1, + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + } + const tx2 = { + id: 2, + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: otherNetworkId, + txParams: {}, + } txStateManager.addTx(tx, noop) txStateManager.addTx(tx2, noop) const result = txStateManager.getFullTxList() @@ -356,7 +383,13 @@ describe('TransactionStateManager', function () { it('cuts off early txs beyond a limit', function () { const limit = txStateManager.txHistoryLimit for (let i = 0; i < limit + 1; i++) { - const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { + id: i, + time: new Date(), + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + } txStateManager.addTx(tx, noop) } const result = txStateManager.getTxList() @@ -367,7 +400,13 @@ describe('TransactionStateManager', function () { it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function () { const limit = txStateManager.txHistoryLimit for (let i = 0; i < limit + 1; i++) { - const tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { + id: i, + time: new Date(), + status: TRANSACTION_STATUSES.REJECTED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + } txStateManager.addTx(tx, noop) } const result = txStateManager.getTxList() @@ -376,25 +415,57 @@ describe('TransactionStateManager', function () { }) it('cuts off early txs beyond a limit but does not cut unapproved txs', function () { - const unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + const unconfirmedTx = { + id: 0, + time: new Date(), + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + } txStateManager.addTx(unconfirmedTx, noop) const limit = txStateManager.txHistoryLimit for (let i = 1; i < limit + 1; i++) { - const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { + id: i, + time: new Date(), + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + } txStateManager.addTx(tx, noop) } const result = txStateManager.getTxList() assert.equal(result.length, limit, `limit of ${limit} txs enforced`) assert.equal(result[0].id, 0, 'first tx should still be there') - assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved') + assert.equal( + result[0].status, + TRANSACTION_STATUSES.UNAPPROVED, + 'first tx should be unapproved', + ) assert.equal(result[1].id, 2, 'early txs truncated') }) }) describe('#updateTx', function () { it('replaces the tx with the same id', function () { - txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txStateManager.addTx( + { + id: '1', + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + }, + noop, + ) + txStateManager.addTx( + { + id: '2', + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + }, + noop, + ) const txMeta = txStateManager.getTx('1') txMeta.hash = 'foo' txStateManager.updateTx(txMeta) @@ -414,7 +485,12 @@ describe('TransactionStateManager', function () { } const invalidValues = [1, true, {}, Symbol('1')] - txStateManager.addTx({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: validTxParams }) + txStateManager.addTx({ + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: validTxParams, + }) Object.keys(validTxParams).forEach((key) => { for (const value of invalidValues) { @@ -426,7 +502,10 @@ describe('TransactionStateManager', function () { [key]: value, }, } - assert.throws(txStateManager.updateTx.bind(txStateManager, newTx), 'updateTx should throw an error') + assert.throws( + txStateManager.updateTx.bind(txStateManager, newTx), + 'updateTx should throw an error', + ) const result = txStateManager.getTx(1) assert.deepEqual(result, originalTx, 'tx should not be updated') } @@ -439,7 +518,7 @@ describe('TransactionStateManager', function () { const txMeta = { id: '1', - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, metamaskNetworkId: currentNetworkId, txParams: { gasPrice: originalGasPrice, @@ -450,8 +529,16 @@ describe('TransactionStateManager', function () { const updatedTx = txStateManager.getTx('1') // verify tx was initialized correctly assert.equal(updatedTx.history.length, 1, 'one history item (initial)') - assert.equal(Array.isArray(updatedTx.history[0]), false, 'first history item is initial state') - assert.deepEqual(updatedTx.history[0], snapshotFromTxMeta(updatedTx), 'first history item is initial state') + assert.equal( + Array.isArray(updatedTx.history[0]), + false, + 'first history item is initial state', + ) + assert.deepEqual( + updatedTx.history[0], + snapshotFromTxMeta(updatedTx), + 'first history item is initial state', + ) // modify value and updateTx updatedTx.txParams.gasPrice = desiredGasPrice const before = new Date().getTime() @@ -459,22 +546,53 @@ describe('TransactionStateManager', function () { const after = new Date().getTime() // check updated value const result = txStateManager.getTx('1') - assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated') + assert.equal( + result.txParams.gasPrice, + desiredGasPrice, + 'gas price updated', + ) // validate history was updated - assert.equal(result.history.length, 2, 'two history items (initial + diff)') - assert.equal(result.history[1].length, 1, 'two history state items (initial + diff)') + assert.equal( + result.history.length, + 2, + 'two history items (initial + diff)', + ) + assert.equal( + result.history[1].length, + 1, + 'two history state items (initial + diff)', + ) - const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice } - assert.deepEqual(result.history[1][0].op, expectedEntry.op, 'two history items (initial + diff) operation') - assert.deepEqual(result.history[1][0].path, expectedEntry.path, 'two history items (initial + diff) path') - assert.deepEqual(result.history[1][0].value, expectedEntry.value, 'two history items (initial + diff) value') - assert.ok(result.history[1][0].timestamp >= before && result.history[1][0].timestamp <= after) + const expectedEntry = { + op: 'replace', + path: '/txParams/gasPrice', + value: desiredGasPrice, + } + assert.deepEqual( + result.history[1][0].op, + expectedEntry.op, + 'two history items (initial + diff) operation', + ) + assert.deepEqual( + result.history[1][0].path, + expectedEntry.path, + 'two history items (initial + diff) path', + ) + assert.deepEqual( + result.history[1][0].value, + expectedEntry.value, + 'two history items (initial + diff) value', + ) + assert.ok( + result.history[1][0].timestamp >= before && + result.history[1][0].timestamp <= after, + ) }) it('does NOT add empty history items', function () { const txMeta = { id: '1', - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, metamaskNetworkId: currentNetworkId, txParams: { gasPrice: '0x01', @@ -491,76 +609,211 @@ describe('TransactionStateManager', function () { describe('#getUnapprovedTxList', function () { it('returns unapproved txs in a hash', function () { - txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txStateManager.addTx( + { + id: '1', + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + }, + noop, + ) + txStateManager.addTx( + { + id: '2', + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + }, + noop, + ) const result = txStateManager.getUnapprovedTxList() assert.equal(typeof result, 'object') - assert.equal(result['1'].status, 'unapproved') + assert.equal(result['1'].status, TRANSACTION_STATUSES.UNAPPROVED) assert.equal(result['2'], undefined) }) }) describe('#getTx', function () { it('returns a tx with the requested id', function () { - txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - assert.equal(txStateManager.getTx('1').status, 'unapproved') - assert.equal(txStateManager.getTx('2').status, 'confirmed') + txStateManager.addTx( + { + id: '1', + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + }, + noop, + ) + txStateManager.addTx( + { + id: '2', + status: TRANSACTION_STATUSES.CONFIRMED, + metamaskNetworkId: currentNetworkId, + txParams: {}, + }, + noop, + ) + assert.equal( + txStateManager.getTx('1').status, + TRANSACTION_STATUSES.UNAPPROVED, + ) + assert.equal( + txStateManager.getTx('2').status, + TRANSACTION_STATUSES.CONFIRMED, + ) }) }) describe('#getFilteredTxList', function () { it('returns a tx with the requested data', function () { const txMetas = [ - { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { + id: 0, + status: TRANSACTION_STATUSES.UNAPPROVED, + txParams: { from: '0xaa', to: '0xbb' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + txParams: { from: '0xaa', to: '0xbb' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 2, + status: TRANSACTION_STATUSES.UNAPPROVED, + txParams: { from: '0xaa', to: '0xbb' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 3, + status: TRANSACTION_STATUSES.UNAPPROVED, + txParams: { from: '0xbb', to: '0xaa' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 4, + status: TRANSACTION_STATUSES.UNAPPROVED, + txParams: { from: '0xbb', to: '0xaa' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 5, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: '0xaa', to: '0xbb' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 6, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: '0xaa', to: '0xbb' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 7, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: '0xbb', to: '0xaa' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 8, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: '0xbb', to: '0xaa' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 9, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: '0xbb', to: '0xaa' }, + metamaskNetworkId: currentNetworkId, + }, ] txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop)) let filterParams - filterParams = { status: 'unapproved', from: '0xaa' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'unapproved', to: '0xaa' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'confirmed', from: '0xbb' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'confirmed' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { status: TRANSACTION_STATUSES.UNAPPROVED, from: '0xaa' } + assert.equal( + txStateManager.getFilteredTxList(filterParams).length, + 3, + `getFilteredTxList - ${JSON.stringify(filterParams)}`, + ) + filterParams = { status: TRANSACTION_STATUSES.UNAPPROVED, to: '0xaa' } + assert.equal( + txStateManager.getFilteredTxList(filterParams).length, + 2, + `getFilteredTxList - ${JSON.stringify(filterParams)}`, + ) + filterParams = { status: TRANSACTION_STATUSES.CONFIRMED, from: '0xbb' } + assert.equal( + txStateManager.getFilteredTxList(filterParams).length, + 3, + `getFilteredTxList - ${JSON.stringify(filterParams)}`, + ) + filterParams = { status: TRANSACTION_STATUSES.CONFIRMED } + assert.equal( + txStateManager.getFilteredTxList(filterParams).length, + 5, + `getFilteredTxList - ${JSON.stringify(filterParams)}`, + ) filterParams = { from: '0xaa' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal( + txStateManager.getFilteredTxList(filterParams).length, + 5, + `getFilteredTxList - ${JSON.stringify(filterParams)}`, + ) filterParams = { to: '0xaa' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: (status) => status !== 'confirmed' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal( + txStateManager.getFilteredTxList(filterParams).length, + 5, + `getFilteredTxList - ${JSON.stringify(filterParams)}`, + ) + filterParams = { + status: (status) => status !== TRANSACTION_STATUSES.CONFIRMED, + } + assert.equal( + txStateManager.getFilteredTxList(filterParams).length, + 5, + `getFilteredTxList - ${JSON.stringify(filterParams)}`, + ) }) }) describe('#wipeTransactions', function () { - const specificAddress = '0xaa' const otherAddress = '0xbb' it('should remove only the transactions from a specific address', function () { - const txMetas = [ - { id: 0, status: 'unapproved', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: currentNetworkId }, - { id: 1, status: 'confirmed', txParams: { from: otherAddress, to: specificAddress }, metamaskNetworkId: currentNetworkId }, - { id: 2, status: 'confirmed', txParams: { from: otherAddress, to: specificAddress }, metamaskNetworkId: currentNetworkId }, + { + id: 0, + status: TRANSACTION_STATUSES.UNAPPROVED, + txParams: { from: specificAddress, to: otherAddress }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 1, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: otherAddress, to: specificAddress }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 2, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: otherAddress, to: specificAddress }, + metamaskNetworkId: currentNetworkId, + }, ] txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop)) txStateManager.wipeTransactions(specificAddress) - const transactionsFromCurrentAddress = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from === specificAddress) - const transactionsFromOtherAddresses = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from !== specificAddress) + const transactionsFromCurrentAddress = txStateManager + .getTxList() + .filter((txMeta) => txMeta.txParams.from === specificAddress) + const transactionsFromOtherAddresses = txStateManager + .getTxList() + .filter((txMeta) => txMeta.txParams.from !== specificAddress) assert.equal(transactionsFromCurrentAddress.length, 0) assert.equal(transactionsFromOtherAddresses.length, 2) @@ -568,17 +821,36 @@ describe('TransactionStateManager', function () { it('should not remove the transactions from other networks', function () { const txMetas = [ - { id: 0, status: 'unapproved', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: currentNetworkId }, - { id: 1, status: 'confirmed', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: otherNetworkId }, - { id: 2, status: 'confirmed', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: otherNetworkId }, + { + id: 0, + status: TRANSACTION_STATUSES.UNAPPROVED, + txParams: { from: specificAddress, to: otherAddress }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 1, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: specificAddress, to: otherAddress }, + metamaskNetworkId: otherNetworkId, + }, + { + id: 2, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: specificAddress, to: otherAddress }, + metamaskNetworkId: otherNetworkId, + }, ] txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop)) txStateManager.wipeTransactions(specificAddress) - const txsFromCurrentNetworkAndAddress = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from === specificAddress) - const txFromOtherNetworks = txStateManager.getFullTxList().filter((txMeta) => txMeta.metamaskNetworkId === otherNetworkId) + const txsFromCurrentNetworkAndAddress = txStateManager + .getTxList() + .filter((txMeta) => txMeta.txParams.from === specificAddress) + const txFromOtherNetworks = txStateManager + .getFullTxList() + .filter((txMeta) => txMeta.metamaskNetworkId === otherNetworkId) assert.equal(txsFromCurrentNetworkAndAddress.length, 0) assert.equal(txFromOtherNetworks.length, 2) @@ -589,30 +861,59 @@ describe('TransactionStateManager', function () { it('should remove the transaction from the storage', function () { txStateManager._saveTxList([{ id: 1 }]) txStateManager._removeTx(1) - assert.ok(!txStateManager.getFullTxList().length, 'txList should be empty') + assert.ok( + !txStateManager.getFullTxList().length, + 'txList should be empty', + ) }) it('should only remove the transaction with ID 1 from the storage', function () { txStateManager._saveTxList([{ id: 1 }, { id: 2 }]) txStateManager._removeTx(1) - assert.equal(txStateManager.getFullTxList()[0].id, 2, 'txList should have a id of 2') + assert.equal( + txStateManager.getFullTxList()[0].id, + 2, + 'txList should have a id of 2', + ) }) }) describe('#clearUnapprovedTxs', function () { it('removes unapproved transactions', function () { const txMetas = [ - { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 2, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: otherNetworkId }, - { id: 3, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: otherNetworkId }, + { + id: 0, + status: TRANSACTION_STATUSES.UNAPPROVED, + txParams: { from: '0xaa', to: '0xbb' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + txParams: { from: '0xaa', to: '0xbb' }, + metamaskNetworkId: currentNetworkId, + }, + { + id: 2, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: '0xaa', to: '0xbb' }, + metamaskNetworkId: otherNetworkId, + }, + { + id: 3, + status: TRANSACTION_STATUSES.CONFIRMED, + txParams: { from: '0xaa', to: '0xbb' }, + metamaskNetworkId: otherNetworkId, + }, ] txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop)) txStateManager.clearUnapprovedTxs() - const unapprovedTxList = txStateManager.getFullTxList().filter((tx) => tx.status === 'unapproved') + const unapprovedTxList = txStateManager + .getFullTxList() + .filter((tx) => tx.status === TRANSACTION_STATUSES.UNAPPROVED) assert.equal(unapprovedTxList.length, 0) }) diff --git a/test/unit/app/controllers/transactions/tx-utils-test.js b/test/unit/app/controllers/transactions/tx-utils-test.js index 6923d53d5..02c5171c7 100644 --- a/test/unit/app/controllers/transactions/tx-utils-test.js +++ b/test/unit/app/controllers/transactions/tx-utils-test.js @@ -38,13 +38,28 @@ describe('txUtils', function () { assert.ok(!normalizedTxParams.chainId, 'there should be no chainId') assert.ok(!normalizedTxParams.to, 'there should be no to address if null') - assert.equal(normalizedTxParams.from.slice(0, 2), '0x', 'from should be hex-prefixed') - assert.equal(normalizedTxParams.data.slice(0, 2), '0x', 'data should be hex-prefixed') - assert.ok(!('random' in normalizedTxParams), 'there should be no random key in normalizedTxParams') + assert.equal( + normalizedTxParams.from.slice(0, 2), + '0x', + 'from should be hex-prefixed', + ) + assert.equal( + normalizedTxParams.data.slice(0, 2), + '0x', + 'data should be hex-prefixed', + ) + assert.ok( + !('random' in normalizedTxParams), + 'there should be no random key in normalizedTxParams', + ) txParams.to = 'a7df1beDBF813f57096dF77FCd515f0B3900e402' normalizedTxParams = txUtils.normalizeTxParams(txParams) - assert.equal(normalizedTxParams.to.slice(0, 2), '0x', 'to should be hex-prefixed') + assert.equal( + normalizedTxParams.to.slice(0, 2), + '0x', + 'to should be hex-prefixed', + ) }) }) @@ -55,8 +70,17 @@ describe('txUtils', function () { to: '0x', data: 'bytecode', } - const sanitizedTxParams = txUtils.validateRecipient(zeroRecipientDataTxParams) - assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x') + const sanitizedTxParams = txUtils.validateRecipient( + zeroRecipientDataTxParams, + ) + assert.deepEqual( + sanitizedTxParams, + { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + data: 'bytecode', + }, + 'no recipient with 0x', + ) }) it('should error when recipient is 0x', function () { @@ -64,38 +88,57 @@ describe('txUtils', function () { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x', } - assert.throws(() => { - txUtils.validateRecipient(zeroRecipientTxParams) - }, Error, 'Invalid recipient address') + assert.throws( + () => { + txUtils.validateRecipient(zeroRecipientTxParams) + }, + Error, + 'Invalid recipient address', + ) }) }) describe('#validateFrom', function () { it('should error when from is not a hex string', function () { - // where from is undefined const txParams = {} - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws( + () => { + txUtils.validateFrom(txParams) + }, + Error, + `Invalid from address ${txParams.from} not a string`, + ) // where from is array txParams.from = [] - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws( + () => { + txUtils.validateFrom(txParams) + }, + Error, + `Invalid from address ${txParams.from} not a string`, + ) // where from is a object txParams.from = {} - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws( + () => { + txUtils.validateFrom(txParams) + }, + Error, + `Invalid from address ${txParams.from} not a string`, + ) // where from is a invalid address txParams.from = 'im going to fail' - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address`) + assert.throws( + () => { + txUtils.validateFrom(txParams) + }, + Error, + `Invalid from address`, + ) // should run txParams.from = '0x1678a085c290ebd122dc42cba69373b5953b831d' diff --git a/test/unit/app/fetch-with-timeout.test.js b/test/unit/app/fetch-with-timeout.test.js index af25a329c..89ea2fec9 100644 --- a/test/unit/app/fetch-with-timeout.test.js +++ b/test/unit/app/fetch-with-timeout.test.js @@ -5,9 +5,7 @@ import fetchWithTimeout from '../../../app/scripts/lib/fetch-with-timeout' describe('fetchWithTimeout', function () { it('fetches a url', async function () { - nock('https://api.infura.io') - .get('/money') - .reply(200, '{"hodl": false}') + nock('https://api.infura.io').get('/money').reply(200, '{"hodl": false}') const fetch = fetchWithTimeout() const response = await (await fetch('https://api.infura.io/money')).json() diff --git a/test/unit/app/message-manager-test.js b/test/unit/app/message-manager-test.js index 66eef7b10..cd1769cda 100644 --- a/test/unit/app/message-manager-test.js +++ b/test/unit/app/message-manager-test.js @@ -1,5 +1,6 @@ import assert from 'assert' import MessageManager from '../../../app/scripts/lib/message-manager' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' describe('Message Manager', function () { let messageManager @@ -18,7 +19,11 @@ describe('Message Manager', function () { describe('#addMsg', function () { it('adds a Msg returned in getMsgList', function () { - const Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + const Msg = { + id: 1, + status: TRANSACTION_STATUSES.APPROVED, + metamaskNetworkId: 'unit test', + } messageManager.addMsg(Msg) const result = messageManager.messages assert.ok(Array.isArray(result)) @@ -29,33 +34,54 @@ describe('Message Manager', function () { describe('#setMsgStatusApproved', function () { it('sets the Msg status to approved', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + const Msg = { + id: 1, + status: 'unapproved', + metamaskNetworkId: 'unit test', + } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) - assert.equal(result[0].status, 'approved') + assert.equal(result[0].status, TRANSACTION_STATUSES.APPROVED) }) }) describe('#rejectMsg', function () { it('sets the Msg status to rejected', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + const Msg = { + id: 1, + status: 'unapproved', + metamaskNetworkId: 'unit test', + } messageManager.addMsg(Msg) messageManager.rejectMsg(1) const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) - assert.equal(result[0].status, 'rejected') + assert.equal(result[0].status, TRANSACTION_STATUSES.REJECTED) }) }) describe('#_updateMsg', function () { it('replaces the Msg with the same id', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ + id: '1', + status: 'unapproved', + metamaskNetworkId: 'unit test', + }) + messageManager.addMsg({ + id: '2', + status: TRANSACTION_STATUSES.APPROVED, + metamaskNetworkId: 'unit test', + }) + messageManager._updateMsg({ + id: '1', + status: 'blah', + hash: 'foo', + metamaskNetworkId: 'unit test', + }) const result = messageManager.getMsg('1') assert.equal(result.hash, 'foo') }) @@ -63,8 +89,16 @@ describe('Message Manager', function () { describe('#getUnapprovedMsgs', function () { it('returns unapproved Msgs in a hash', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ + id: '1', + status: 'unapproved', + metamaskNetworkId: 'unit test', + }) + messageManager.addMsg({ + id: '2', + status: TRANSACTION_STATUSES.APPROVED, + metamaskNetworkId: 'unit test', + }) const result = messageManager.getUnapprovedMsgs() assert.equal(typeof result, 'object') assert.equal(result['1'].status, 'unapproved') @@ -74,10 +108,21 @@ describe('Message Manager', function () { describe('#getMsg', function () { it('returns a Msg with the requested id', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ + id: '1', + status: 'unapproved', + metamaskNetworkId: 'unit test', + }) + messageManager.addMsg({ + id: '2', + status: TRANSACTION_STATUSES.APPROVED, + metamaskNetworkId: 'unit test', + }) assert.equal(messageManager.getMsg('1').status, 'unapproved') - assert.equal(messageManager.getMsg('2').status, 'approved') + assert.equal( + messageManager.getMsg('2').status, + TRANSACTION_STATUSES.APPROVED, + ) }) }) }) diff --git a/test/unit/app/nodeify-test.js b/test/unit/app/nodeify-test.js index f6e9a3207..050553e61 100644 --- a/test/unit/app/nodeify-test.js +++ b/test/unit/app/nodeify-test.js @@ -4,7 +4,7 @@ import nodeify from '../../../app/scripts/lib/nodeify' describe('nodeify', function () { const obj = { foo: 'bar', - promiseFunc (a) { + promiseFunc(a) { const solution = this.foo + a return Promise.resolve(solution) }, @@ -29,7 +29,11 @@ describe('nodeify', function () { nodified('baz') done() } catch (err) { - done(new Error('should not have thrown if the last argument is not a function')) + done( + new Error( + 'should not have thrown if the last argument is not a function', + ), + ) } }) diff --git a/test/unit/app/personal-message-manager-test.js b/test/unit/app/personal-message-manager-test.js index d95ca5e14..fd1aa9164 100644 --- a/test/unit/app/personal-message-manager-test.js +++ b/test/unit/app/personal-message-manager-test.js @@ -1,5 +1,6 @@ import assert from 'assert' import PersonalMessageManager from '../../../app/scripts/lib/personal-message-manager' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' describe('Personal Message Manager', function () { let messageManager @@ -18,7 +19,11 @@ describe('Personal Message Manager', function () { describe('#addMsg', function () { it('adds a Msg returned in getMsgList', function () { - const Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + const Msg = { + id: 1, + status: TRANSACTION_STATUSES.APPROVED, + metamaskNetworkId: 'unit test', + } messageManager.addMsg(Msg) const result = messageManager.messages assert.ok(Array.isArray(result)) @@ -29,33 +34,54 @@ describe('Personal Message Manager', function () { describe('#setMsgStatusApproved', function () { it('sets the Msg status to approved', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + const Msg = { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: 'unit test', + } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) - assert.equal(result[0].status, 'approved') + assert.equal(result[0].status, TRANSACTION_STATUSES.APPROVED) }) }) describe('#rejectMsg', function () { it('sets the Msg status to rejected', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + const Msg = { + id: 1, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: 'unit test', + } messageManager.addMsg(Msg) messageManager.rejectMsg(1) const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) - assert.equal(result[0].status, 'rejected') + assert.equal(result[0].status, TRANSACTION_STATUSES.REJECTED) }) }) describe('#_updateMsg', function () { it('replaces the Msg with the same id', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ + id: '1', + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: 'unit test', + }) + messageManager.addMsg({ + id: '2', + status: TRANSACTION_STATUSES.APPROVED, + metamaskNetworkId: 'unit test', + }) + messageManager._updateMsg({ + id: '1', + status: 'blah', + hash: 'foo', + metamaskNetworkId: 'unit test', + }) const result = messageManager.getMsg('1') assert.equal(result.hash, 'foo') }) @@ -63,21 +89,43 @@ describe('Personal Message Manager', function () { describe('#getUnapprovedMsgs', function () { it('returns unapproved Msgs in a hash', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ + id: '1', + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: 'unit test', + }) + messageManager.addMsg({ + id: '2', + status: TRANSACTION_STATUSES.APPROVED, + metamaskNetworkId: 'unit test', + }) const result = messageManager.getUnapprovedMsgs() assert.equal(typeof result, 'object') - assert.equal(result['1'].status, 'unapproved') + assert.equal(result['1'].status, TRANSACTION_STATUSES.UNAPPROVED) assert.equal(result['2'], undefined) }) }) describe('#getMsg', function () { it('returns a Msg with the requested id', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - assert.equal(messageManager.getMsg('1').status, 'unapproved') - assert.equal(messageManager.getMsg('2').status, 'approved') + messageManager.addMsg({ + id: '1', + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: 'unit test', + }) + messageManager.addMsg({ + id: '2', + status: TRANSACTION_STATUSES.APPROVED, + metamaskNetworkId: 'unit test', + }) + assert.equal( + messageManager.getMsg('1').status, + TRANSACTION_STATUSES.UNAPPROVED, + ) + assert.equal( + messageManager.getMsg('2').status, + TRANSACTION_STATUSES.APPROVED, + ) }) }) diff --git a/test/unit/app/seed-phrase-verifier-test.js b/test/unit/app/seed-phrase-verifier-test.js index 7da97794a..9f569c556 100644 --- a/test/unit/app/seed-phrase-verifier-test.js +++ b/test/unit/app/seed-phrase-verifier-test.js @@ -6,9 +6,7 @@ import seedPhraseVerifier from '../../../app/scripts/lib/seed-phrase-verifier' import mockEncryptor from '../../lib/mock-encryptor' describe('SeedPhraseVerifier', function () { - describe('verifyAccounts', function () { - const password = 'passw0rd1' const hdKeyTree = 'HD Key Tree' @@ -28,7 +26,6 @@ describe('SeedPhraseVerifier', function () { }) it('should be able to verify created account with seed words', async function () { - const createdAccounts = await primaryKeyring.getAccounts() assert.equal(createdAccounts.length, 1) @@ -40,7 +37,6 @@ describe('SeedPhraseVerifier', function () { }) it('should be able to verify created account (upper case) with seed words', async function () { - const createdAccounts = await primaryKeyring.getAccounts() assert.equal(createdAccounts.length, 1) @@ -54,7 +50,6 @@ describe('SeedPhraseVerifier', function () { }) it('should be able to verify created account (lower case) with seed words', async function () { - const createdAccounts = await primaryKeyring.getAccounts() assert.equal(createdAccounts.length, 1) const lowerCaseAccounts = [createdAccounts[0].toLowerCase()] @@ -67,28 +62,31 @@ describe('SeedPhraseVerifier', function () { }) it('should return error with good but different seed words', async function () { - const createdAccounts = await primaryKeyring.getAccounts() assert.equal(createdAccounts.length, 1) await primaryKeyring.serialize() - const seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' + const seedWords = + 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' try { await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords) assert.fail('Should reject') } catch (err) { - assert.ok(err.message.indexOf('Not identical accounts!') >= 0, 'Wrong error message') + assert.ok( + err.message.indexOf('Not identical accounts!') >= 0, + 'Wrong error message', + ) } }) it('should return error with undefined existing accounts', async function () { - const createdAccounts = await primaryKeyring.getAccounts() assert.equal(createdAccounts.length, 1) await primaryKeyring.serialize() - const seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' + const seedWords = + 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' try { await seedPhraseVerifier.verifyAccounts(undefined, seedWords) @@ -99,12 +97,12 @@ describe('SeedPhraseVerifier', function () { }) it('should return error with empty accounts array', async function () { - const createdAccounts = await primaryKeyring.getAccounts() assert.equal(createdAccounts.length, 1) await primaryKeyring.serialize() - const seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' + const seedWords = + 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' try { await seedPhraseVerifier.verifyAccounts([], seedWords) @@ -115,7 +113,6 @@ describe('SeedPhraseVerifier', function () { }) it('should be able to verify more than one created account with seed words', async function () { - await keyringController.addNewAccount(primaryKeyring) await keyringController.addNewAccount(primaryKeyring) diff --git a/test/unit/app/typed-message-manager.spec.js b/test/unit/app/typed-message-manager.spec.js index 7598ea185..b396b0c22 100644 --- a/test/unit/app/typed-message-manager.spec.js +++ b/test/unit/app/typed-message-manager.spec.js @@ -1,9 +1,16 @@ import assert from 'assert' import sinon from 'sinon' import TypedMessageManager from '../../../app/scripts/lib/typed-message-manager' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' describe('Typed Message Manager', function () { - let typedMessageManager, msgParamsV1, msgParamsV3, typedMsgs, messages, msgId, numberMsgId + let typedMessageManager, + msgParamsV1, + msgParamsV3, + typedMsgs, + messages, + msgId, + numberMsgId const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' @@ -16,47 +23,51 @@ describe('Typed Message Manager', function () { from: address, data: [ { type: 'string', name: 'unit test', value: 'hello there' }, - { type: 'uint32', name: 'A number, but not really a number', value: '$$$' }, + { + type: 'uint32', + name: 'A number, but not really a number', + value: '$$$', + }, ], } msgParamsV3 = { from: address, data: JSON.stringify({ - 'types': { - 'EIP712Domain': [ - { 'name': 'name', 'type': 'string' }, - { 'name': 'version', 'type': 'string' }, - { 'name': 'chainId', 'type': 'uint256' }, - { 'name': 'verifyingContract', 'type': 'address' }, + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, ], - 'Person': [ - { 'name': 'name', 'type': 'string' }, - { 'name': 'wallet', 'type': 'address' }, + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, ], - 'Mail': [ - { 'name': 'from', 'type': 'Person' }, - { 'name': 'to', 'type': 'Person' }, - { 'name': 'contents', 'type': 'string' }, + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, ], }, - 'primaryType': 'Mail', - 'domain': { - 'name': 'Ether Mainl', - 'version': '1', - 'chainId': 1, - 'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + primaryType: 'Mail', + domain: { + name: 'Ether Mainl', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', }, - 'message': { - 'from': { - 'name': 'Cow', - 'wallet': '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', }, - 'to': { - 'name': 'Bob', - 'wallet': '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', }, - 'contents': 'Hello, Bob!', + contents: 'Hello, Bob!', }, }), } @@ -79,7 +90,7 @@ describe('Typed Message Manager', function () { }) it('adds to unapproved messages and sets status to unapproved', function () { - assert.equal(typedMsgs[msgId].status, 'unapproved') + assert.equal(typedMsgs[msgId].status, TRANSACTION_STATUSES.UNAPPROVED) }) it('validates params', function () { @@ -96,18 +107,17 @@ describe('Typed Message Manager', function () { it('approves messages', async function () { const messageMetaMaskId = messages[0].msgParams typedMessageManager.approveMessage(messageMetaMaskId) - assert.equal(messages[0].status, 'approved') + assert.equal(messages[0].status, TRANSACTION_STATUSES.APPROVED) }) it('sets msg status to signed and adds a raw sig to message details', function () { typedMessageManager.setMsgStatusSigned(numberMsgId, 'raw sig') - assert.equal(messages[0].status, 'signed') + assert.equal(messages[0].status, TRANSACTION_STATUSES.SIGNED) assert.equal(messages[0].rawSig, 'raw sig') }) it('rejects message', function () { typedMessageManager.rejectMsg(numberMsgId) - assert.equal(messages[0].status, 'rejected') + assert.equal(messages[0].status, TRANSACTION_STATUSES.REJECTED) }) - }) diff --git a/test/unit/app/util-test.js b/test/unit/app/util-test.js index 5d25d9b08..f2372576f 100644 --- a/test/unit/app/util-test.js +++ b/test/unit/app/util-test.js @@ -15,42 +15,58 @@ import { describe('app utils', function () { describe('getEnvironmentType', function () { it('should return popup type', function () { - const environmentType = getEnvironmentType('http://extension-id/popup.html') + const environmentType = getEnvironmentType( + 'http://extension-id/popup.html', + ) assert.equal(environmentType, ENVIRONMENT_TYPE_POPUP) }) it('should return notification type', function () { - const environmentType = getEnvironmentType('http://extension-id/notification.html') + const environmentType = getEnvironmentType( + 'http://extension-id/notification.html', + ) assert.equal(environmentType, ENVIRONMENT_TYPE_NOTIFICATION) }) it('should return fullscreen type for home.html', function () { - const environmentType = getEnvironmentType('http://extension-id/home.html') + const environmentType = getEnvironmentType( + 'http://extension-id/home.html', + ) assert.equal(environmentType, ENVIRONMENT_TYPE_FULLSCREEN) }) it('should return fullscreen type for phishing.html', function () { - const environmentType = getEnvironmentType('http://extension-id/phishing.html') + const environmentType = getEnvironmentType( + 'http://extension-id/phishing.html', + ) assert.equal(environmentType, ENVIRONMENT_TYPE_FULLSCREEN) }) it('should return background type', function () { - const environmentType = getEnvironmentType('http://extension-id/_generated_background_page.html') + const environmentType = getEnvironmentType( + 'http://extension-id/_generated_background_page.html', + ) assert.equal(environmentType, ENVIRONMENT_TYPE_BACKGROUND) }) it('should return the correct type for a URL with a hash fragment', function () { - const environmentType = getEnvironmentType('http://extension-id/popup.html#hash') + const environmentType = getEnvironmentType( + 'http://extension-id/popup.html#hash', + ) assert.equal(environmentType, ENVIRONMENT_TYPE_POPUP) }) it('should return the correct type for a URL with query parameters', function () { - const environmentType = getEnvironmentType('http://extension-id/popup.html?param=foo') + const environmentType = getEnvironmentType( + 'http://extension-id/popup.html?param=foo', + ) assert.equal(environmentType, ENVIRONMENT_TYPE_POPUP) }) it('should return the correct type for a URL with query parameters and a hash fragment', function () { - const environmentType = getEnvironmentType('http://extension-id/popup.html?param=foo#hash') + const environmentType = getEnvironmentType( + 'http://extension-id/popup.html?param=foo#hash', + ) assert.equal(environmentType, ENVIRONMENT_TYPE_POPUP) }) }) @@ -58,9 +74,9 @@ describe('app utils', function () { describe('SufficientBalance', function () { it('returns true if max tx cost is equal to balance.', function () { const tx = { - 'value': '0x1', - 'gas': '0x2', - 'gasPrice': '0x3', + value: '0x1', + gas: '0x2', + gasPrice: '0x3', } const balance = '0x8' @@ -70,9 +86,9 @@ describe('app utils', function () { it('returns true if max tx cost is less than balance.', function () { const tx = { - 'value': '0x1', - 'gas': '0x2', - 'gasPrice': '0x3', + value: '0x1', + gas: '0x2', + gasPrice: '0x3', } const balance = '0x9' @@ -82,9 +98,9 @@ describe('app utils', function () { it('returns false if max tx cost is more than balance.', function () { const tx = { - 'value': '0x1', - 'gas': '0x2', - 'gasPrice': '0x3', + value: '0x1', + gas: '0x2', + gasPrice: '0x3', } const balance = '0x6' @@ -96,71 +112,80 @@ describe('app utils', function () { describe('isPrefixedFormattedHexString', function () { it('should return true for valid hex strings', function () { assert.equal( - isPrefixedFormattedHexString('0x1'), true, + isPrefixedFormattedHexString('0x1'), + true, 'should return true', ) assert.equal( - isPrefixedFormattedHexString('0xa'), true, + isPrefixedFormattedHexString('0xa'), + true, 'should return true', ) assert.equal( - isPrefixedFormattedHexString('0xabcd1123fae909aad87452'), true, + isPrefixedFormattedHexString('0xabcd1123fae909aad87452'), + true, 'should return true', ) }) it('should return false for invalid hex strings', function () { assert.equal( - isPrefixedFormattedHexString('0x'), false, + isPrefixedFormattedHexString('0x'), + false, 'should return false', ) assert.equal( - isPrefixedFormattedHexString('0x0'), false, + isPrefixedFormattedHexString('0x0'), + false, 'should return false', ) assert.equal( - isPrefixedFormattedHexString('0x01'), false, + isPrefixedFormattedHexString('0x01'), + false, 'should return false', ) assert.equal( - isPrefixedFormattedHexString(' 0x1'), false, + isPrefixedFormattedHexString(' 0x1'), + false, 'should return false', ) assert.equal( - isPrefixedFormattedHexString('0x1 '), false, + isPrefixedFormattedHexString('0x1 '), + false, 'should return false', ) assert.equal( - isPrefixedFormattedHexString('0x1afz'), false, + isPrefixedFormattedHexString('0x1afz'), + false, 'should return false', ) assert.equal( - isPrefixedFormattedHexString('z'), false, + isPrefixedFormattedHexString('z'), + false, 'should return false', ) assert.equal( - isPrefixedFormattedHexString(2), false, + isPrefixedFormattedHexString(2), + false, 'should return false', ) assert.equal( - isPrefixedFormattedHexString(['0x1']), false, + isPrefixedFormattedHexString(['0x1']), + false, 'should return false', ) - assert.equal( - isPrefixedFormattedHexString(), false, - 'should return false', - ) + assert.equal(isPrefixedFormattedHexString(), false, 'should return false') }) }) }) diff --git a/test/unit/localhostState.js b/test/unit/localhostState.js index 13bd7af3c..8b0c68507 100644 --- a/test/unit/localhostState.js +++ b/test/unit/localhostState.js @@ -1,4 +1,3 @@ - /** * @typedef {Object} FirstTimeState * @property {Object} config Initial configuration parameters diff --git a/test/unit/migrations/021-test.js b/test/unit/migrations/021-test.js index bc18d6cfe..028e0e99d 100644 --- a/test/unit/migrations/021-test.js +++ b/test/unit/migrations/021-test.js @@ -4,12 +4,14 @@ import migration21 from '../../../app/scripts/migrations/021' describe('wallet2 is migrated successfully with out the BlacklistController', function () { it('should delete BlacklistController key', function (done) { - migration21.migrate(wallet2) + migration21 + .migrate(wallet2) .then((migratedData) => { assert.equal(migratedData.meta.version, 21) assert(!migratedData.data.BlacklistController) assert(!migratedData.data.RecentBlocks) done() - }).catch(done) + }) + .catch(done) }) }) diff --git a/test/unit/migrations/022-test.js b/test/unit/migrations/022-test.js index 8f33d62e2..347edf99a 100644 --- a/test/unit/migrations/022-test.js +++ b/test/unit/migrations/022-test.js @@ -1,15 +1,16 @@ import assert from 'assert' import migration22 from '../../../app/scripts/migrations/022' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' -const properTime = (new Date()).getTime() +const properTime = new Date().getTime() const storage = { - 'meta': {}, - 'data': { - 'TransactionController': { - 'transactions': [ - { 'status': 'submitted' }, - { 'status': 'submitted', 'submittedTime': properTime }, - { 'status': 'confirmed' }, + meta: {}, + data: { + TransactionController: { + transactions: [ + { status: TRANSACTION_STATUSES.SUBMITTED }, + { status: TRANSACTION_STATUSES.SUBMITTED, submittedTime: properTime }, + { status: TRANSACTION_STATUSES.CONFIRMED }, ], }, }, @@ -17,9 +18,14 @@ const storage = { describe('storage is migrated successfully where transactions that are submitted have submittedTimes', function () { it('should add submittedTime key on the txMeta if appropriate', function (done) { - migration22.migrate(storage) + migration22 + .migrate(storage) .then((migratedData) => { - const [txMeta1, txMeta2, txMeta3] = migratedData.data.TransactionController.transactions + const [ + txMeta1, + txMeta2, + txMeta3, + ] = migratedData.data.TransactionController.transactions assert.equal(migratedData.meta.version, 22) // should have written a submitted time assert(txMeta1.submittedTime) @@ -28,6 +34,7 @@ describe('storage is migrated successfully where transactions that are submitted // should not have written a submitted time assert(!txMeta3.submittedTime) done() - }).catch(done) + }) + .catch(done) }) }) diff --git a/test/unit/migrations/023-test.js b/test/unit/migrations/023-test.js index 49f40b033..a0cc09f30 100644 --- a/test/unit/migrations/023-test.js +++ b/test/unit/migrations/023-test.js @@ -1,12 +1,12 @@ import assert from 'assert' import migration23 from '../../../app/scripts/migrations/023' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const storage = { - 'meta': {}, - 'data': { - 'TransactionController': { - 'transactions': [ - ], + meta: {}, + data: { + TransactionController: { + transactions: [], }, }, } @@ -15,22 +15,13 @@ const transactions = [] const transactions40 = [] const transactions20 = [] -const txStates = [ - 'unapproved', - 'approved', - 'signed', - 'submitted', - 'confirmed', - 'rejected', - 'failed', - 'dropped', -] +const txStates = Object.values(TRANSACTION_STATUSES) const deletableTxStates = [ - 'confirmed', - 'rejected', - 'failed', - 'dropped', + TRANSACTION_STATUSES.CONFIRMED, + TRANSACTION_STATUSES.REJECTED, + TRANSACTION_STATUSES.FAILED, + TRANSACTION_STATUSES.DROPPED, ] let nonDeletableCount = 0 @@ -60,42 +51,65 @@ storage.data.TransactionController.transactions = transactions describe('storage is migrated successfully and the proper transactions are remove from state', function () { it('should remove transactions that are unneeded', function (done) { - migration23.migrate(storage) + migration23 + .migrate(storage) .then((migratedData) => { let leftoverNonDeletableTxCount = 0 - const migratedTransactions = migratedData.data.TransactionController.transactions + const migratedTransactions = + migratedData.data.TransactionController.transactions migratedTransactions.forEach((tx) => { if (!deletableTxStates.find((s) => s === tx.status)) { leftoverNonDeletableTxCount += 1 } }) - assert.equal(leftoverNonDeletableTxCount, nonDeletableCount, "migration shouldn't delete transactions we want to keep") - assert((migratedTransactions.length >= 40), `should be equal or greater to 40 if they are non deletable states got ${migratedTransactions.length} transactions`) + assert.equal( + leftoverNonDeletableTxCount, + nonDeletableCount, + "migration shouldn't delete transactions we want to keep", + ) + assert( + migratedTransactions.length >= 40, + `should be equal or greater to 40 if they are non deletable states got ${migratedTransactions.length} transactions`, + ) done() - }).catch(done) + }) + .catch(done) }) it('should not remove any transactions because 40 is the expected limit', function (done) { storage.meta.version = 22 storage.data.TransactionController.transactions = transactions40 - migration23.migrate(storage) + migration23 + .migrate(storage) .then((migratedData) => { - const migratedTransactions = migratedData.data.TransactionController.transactions + const migratedTransactions = + migratedData.data.TransactionController.transactions - assert.equal(migratedTransactions.length, 40, "migration shouldn't delete when at limit") + assert.equal( + migratedTransactions.length, + 40, + "migration shouldn't delete when at limit", + ) done() - }).catch(done) + }) + .catch(done) }) it('should not remove any transactions because 20 txs is under the expected limit', function (done) { storage.meta.version = 22 storage.data.TransactionController.transactions = transactions20 - migration23.migrate(storage) + migration23 + .migrate(storage) .then((migratedData) => { - const migratedTransactions = migratedData.data.TransactionController.transactions - assert.equal(migratedTransactions.length, 20, "migration shouldn't delete when under limit") + const migratedTransactions = + migratedData.data.TransactionController.transactions + assert.equal( + migratedTransactions.length, + 20, + "migration shouldn't delete when under limit", + ) done() - }).catch(done) + }) + .catch(done) }) - }) diff --git a/test/unit/migrations/024-test.js b/test/unit/migrations/024-test.js index 446f85f07..b753c4d06 100644 --- a/test/unit/migrations/024-test.js +++ b/test/unit/migrations/024-test.js @@ -1,17 +1,17 @@ import assert from 'assert' import migration24 from '../../../app/scripts/migrations/024' import data from '../../../app/scripts/first-time-state' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const firstTimeState = { meta: {}, data, } const storage = { - 'meta': {}, - 'data': { - 'TransactionController': { - 'transactions': [ - ], + meta: {}, + data: { + TransactionController: { + transactions: [], }, }, } @@ -19,33 +19,50 @@ const storage = { const transactions = [] while (transactions.length <= 10) { - transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'unapproved' }) - transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'confirmed' }) + transactions.push({ + txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, + status: TRANSACTION_STATUSES.UNAPPROVED, + }) + transactions.push({ + txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, + status: TRANSACTION_STATUSES.CONFIRMED, + }) } storage.data.TransactionController.transactions = transactions describe('storage is migrated successfully and the txParams.from are lowercase', function () { it('should lowercase the from for unapproved txs', function (done) { - migration24.migrate(storage) + migration24 + .migrate(storage) .then((migratedData) => { - const migratedTransactions = migratedData.data.TransactionController.transactions + const migratedTransactions = + migratedData.data.TransactionController.transactions migratedTransactions.forEach((tx) => { - if (tx.status === 'unapproved') { - assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675') + if (tx.status === TRANSACTION_STATUSES.UNAPPROVED) { + assert.equal( + tx.txParams.from, + '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675', + ) } else { - assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675') + assert.equal( + tx.txParams.from, + '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675', + ) } }) done() - }).catch(done) + }) + .catch(done) }) it('should migrate first time state', function (done) { - migration24.migrate(firstTimeState) + migration24 + .migrate(firstTimeState) .then((migratedData) => { assert.equal(migratedData.meta.version, 24) done() - }).catch(done) + }) + .catch(done) }) }) diff --git a/test/unit/migrations/025-test.js b/test/unit/migrations/025-test.js index 2cd3381ca..811cbaef7 100644 --- a/test/unit/migrations/025-test.js +++ b/test/unit/migrations/025-test.js @@ -1,6 +1,7 @@ import assert from 'assert' import migration25 from '../../../app/scripts/migrations/025' import data from '../../../app/scripts/first-time-state' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const firstTimeState = { meta: {}, @@ -8,11 +9,10 @@ const firstTimeState = { } const storage = { - 'meta': {}, - 'data': { - 'TransactionController': { - 'transactions': [ - ], + meta: {}, + data: { + TransactionController: { + transactions: [], }, }, } @@ -20,34 +20,49 @@ const storage = { const transactions = [] while (transactions.length <= 10) { - transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675', random: 'stuff', chainId: 2 }, status: 'unapproved' }) - transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'confirmed' }) + transactions.push({ + txParams: { + from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675', + random: 'stuff', + chainId: 2, + }, + status: TRANSACTION_STATUSES.UNAPPROVED, + }) + transactions.push({ + txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, + status: TRANSACTION_STATUSES.CONFIRMED, + }) } storage.data.TransactionController.transactions = transactions describe('storage is migrated successfully and the txParams.from are lowercase', function () { it('should lowercase the from for unapproved txs', function (done) { - migration25.migrate(storage) + migration25 + .migrate(storage) .then((migratedData) => { - const migratedTransactions = migratedData.data.TransactionController.transactions + const migratedTransactions = + migratedData.data.TransactionController.transactions migratedTransactions.forEach((tx) => { - if (tx.status === 'unapproved') { + if (tx.status === TRANSACTION_STATUSES.UNAPPROVED) { assert(!tx.txParams.random) } - if (tx.status === 'unapproved') { + if (tx.status === TRANSACTION_STATUSES.UNAPPROVED) { assert(!tx.txParams.chainId) } }) done() - }).catch(done) + }) + .catch(done) }) it('should migrate first time state', function (done) { - migration25.migrate(firstTimeState) + migration25 + .migrate(firstTimeState) .then((migratedData) => { assert.equal(migratedData.meta.version, 25) done() - }).catch(done) + }) + .catch(done) }) }) diff --git a/test/unit/migrations/026-test.js b/test/unit/migrations/026-test.js index 3227e3394..9168ea493 100644 --- a/test/unit/migrations/026-test.js +++ b/test/unit/migrations/026-test.js @@ -3,11 +3,11 @@ import firstTimeState from '../../../app/scripts/first-time-state' import migration26 from '../../../app/scripts/migrations/026' const oldStorage = { - 'meta': { 'version': 25 }, - 'data': { - 'PreferencesController': {}, - 'KeyringController': { - 'walletNicknames': { + meta: { version: 25 }, + data: { + PreferencesController: {}, + KeyringController: { + walletNicknames: { '0x1e77e2': 'Test Account 1', '0x7e57e2': 'Test Account 2', }, @@ -17,27 +17,33 @@ const oldStorage = { describe('migration #26', function () { it('should move the identities from KeyringController', function (done) { - migration26.migrate(oldStorage) + migration26 + .migrate(oldStorage) .then((newStorage) => { const { identities } = newStorage.data.PreferencesController assert.deepEqual(identities, { '0x1e77e2': { name: 'Test Account 1', address: '0x1e77e2' }, '0x7e57e2': { name: 'Test Account 2', address: '0x7e57e2' }, }) - assert.strictEqual(newStorage.data.KeyringController.walletNicknames, undefined) + assert.strictEqual( + newStorage.data.KeyringController.walletNicknames, + undefined, + ) done() }) .catch(done) }) it('should successfully migrate first time state', function (done) { - migration26.migrate({ - meta: {}, - data: firstTimeState, - }) + migration26 + .migrate({ + meta: {}, + data: firstTimeState, + }) .then((migratedData) => { assert.equal(migratedData.meta.version, migration26.version) done() - }).catch(done) + }) + .catch(done) }) }) diff --git a/test/unit/migrations/027-test.js b/test/unit/migrations/027-test.js index 959f30374..18a19107a 100644 --- a/test/unit/migrations/027-test.js +++ b/test/unit/migrations/027-test.js @@ -1,13 +1,13 @@ import assert from 'assert' import firstTimeState from '../../../app/scripts/first-time-state' import migration27 from '../../../app/scripts/migrations/027' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' const oldStorage = { - 'meta': {}, - 'data': { - 'TransactionController': { - 'transactions': [ - ], + meta: {}, + data: { + TransactionController: { + transactions: [], }, }, } @@ -15,21 +15,27 @@ const oldStorage = { const transactions = [] while (transactions.length < 9) { - transactions.push({ status: 'rejected' }) - transactions.push({ status: 'unapproved' }) - transactions.push({ status: 'approved' }) + transactions.push({ status: TRANSACTION_STATUSES.REJECTED }) + transactions.push({ status: TRANSACTION_STATUSES.UNAPPROVED }) + transactions.push({ status: TRANSACTION_STATUSES.APPROVED }) } oldStorage.data.TransactionController.transactions = transactions describe('migration #27', function () { it('should remove rejected transactions', function (done) { - migration27.migrate(oldStorage) + migration27 + .migrate(oldStorage) .then((newStorage) => { - const newTransactions = newStorage.data.TransactionController.transactions - assert.equal(newTransactions.length, 6, 'transactions is expected to have the length of 6') + const newTransactions = + newStorage.data.TransactionController.transactions + assert.equal( + newTransactions.length, + 6, + 'transactions is expected to have the length of 6', + ) newTransactions.forEach((txMeta) => { - if (txMeta.status === 'rejected') { + if (txMeta.status === TRANSACTION_STATUSES.REJECTED) { done(new Error('transaction was found with a status of rejected')) } }) @@ -39,13 +45,15 @@ describe('migration #27', function () { }) it('should successfully migrate first time state', function (done) { - migration27.migrate({ - meta: {}, - data: firstTimeState, - }) + migration27 + .migrate({ + meta: {}, + data: firstTimeState, + }) .then((migratedData) => { assert.equal(migratedData.meta.version, migration27.version) done() - }).catch(done) + }) + .catch(done) }) }) diff --git a/test/unit/migrations/028-test.js b/test/unit/migrations/028-test.js index b352f0a29..caa8c86bf 100644 --- a/test/unit/migrations/028-test.js +++ b/test/unit/migrations/028-test.js @@ -3,11 +3,14 @@ import firstTimeState from '../../../app/scripts/first-time-state' import migration28 from '../../../app/scripts/migrations/028' const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'tokens': [{ address: '0xa', symbol: 'A', decimals: 4 }, { address: '0xb', symbol: 'B', decimals: 4 }], - 'identities': { + meta: {}, + data: { + PreferencesController: { + tokens: [ + { address: '0xa', symbol: 'A', decimals: 4 }, + { address: '0xb', symbol: 'B', decimals: 4 }, + ], + identities: { '0x6d14': {}, '0x3695': {}, }, @@ -17,31 +20,62 @@ const oldStorage = { describe('migration #28', function () { it('should add corresponding tokens to accountTokens', function (done) { - migration28.migrate(oldStorage) + migration28 + .migrate(oldStorage) .then((newStorage) => { const newTokens = newStorage.data.PreferencesController.tokens - const newAccountTokens = newStorage.data.PreferencesController.accountTokens + const newAccountTokens = + newStorage.data.PreferencesController.accountTokens - const testTokens = [{ address: '0xa', symbol: 'A', decimals: 4 }, { address: '0xb', symbol: 'B', decimals: 4 }] - assert.equal(newTokens.length, 0, 'tokens is expected to have the length of 0') - assert.equal(newAccountTokens['0x6d14'].mainnet.length, 2, 'tokens for address is expected to have the length of 2') - assert.equal(newAccountTokens['0x3695'].mainnet.length, 2, 'tokens for address is expected to have the length of 2') - assert.equal(Object.keys(newAccountTokens).length, 2, 'account tokens should be created for all identities') - assert.deepEqual(newAccountTokens['0x6d14'].mainnet, testTokens, 'tokens for address should be the same than before') - assert.deepEqual(newAccountTokens['0x3695'].mainnet, testTokens, 'tokens for address should be the same than before') + const testTokens = [ + { address: '0xa', symbol: 'A', decimals: 4 }, + { address: '0xb', symbol: 'B', decimals: 4 }, + ] + assert.equal( + newTokens.length, + 0, + 'tokens is expected to have the length of 0', + ) + assert.equal( + newAccountTokens['0x6d14'].mainnet.length, + 2, + 'tokens for address is expected to have the length of 2', + ) + assert.equal( + newAccountTokens['0x3695'].mainnet.length, + 2, + 'tokens for address is expected to have the length of 2', + ) + assert.equal( + Object.keys(newAccountTokens).length, + 2, + 'account tokens should be created for all identities', + ) + assert.deepEqual( + newAccountTokens['0x6d14'].mainnet, + testTokens, + 'tokens for address should be the same than before', + ) + assert.deepEqual( + newAccountTokens['0x3695'].mainnet, + testTokens, + 'tokens for address should be the same than before', + ) done() }) .catch(done) }) it('should successfully migrate first time state', function (done) { - migration28.migrate({ - meta: {}, - data: firstTimeState, - }) + migration28 + .migrate({ + meta: {}, + data: firstTimeState, + }) .then((migratedData) => { assert.equal(migratedData.meta.version, migration28.version) done() - }).catch(done) + }) + .catch(done) }) }) diff --git a/test/unit/migrations/029-test.js b/test/unit/migrations/029-test.js index ccde6b4cb..4e37a48f4 100644 --- a/test/unit/migrations/029-test.js +++ b/test/unit/migrations/029-test.js @@ -1,17 +1,30 @@ import assert from 'assert' import migration29 from '../../../app/scripts/migrations/029' +import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction' -const properTime = (new Date()).getTime() +const properTime = new Date().getTime() const storage = { - 'meta': {}, - 'data': { - 'TransactionController': { - 'transactions': [ - { 'status': 'approved', id: 1, submittedTime: 0 }, - { 'status': 'approved', id: 2, submittedTime: properTime }, - { 'status': 'confirmed', id: 3, submittedTime: properTime }, - { 'status': 'submitted', id: 4, submittedTime: properTime }, - { 'status': 'submitted', id: 5, submittedTime: 0 }, + meta: {}, + data: { + TransactionController: { + transactions: [ + { status: TRANSACTION_STATUSES.APPROVED, id: 1, submittedTime: 0 }, + { + status: TRANSACTION_STATUSES.APPROVED, + id: 2, + submittedTime: properTime, + }, + { + status: TRANSACTION_STATUSES.CONFIRMED, + id: 3, + submittedTime: properTime, + }, + { + status: TRANSACTION_STATUSES.SUBMITTED, + id: 4, + submittedTime: properTime, + }, + { status: TRANSACTION_STATUSES.SUBMITTED, id: 5, submittedTime: 0 }, ], }, }, @@ -19,23 +32,36 @@ const storage = { describe('storage is migrated successfully where transactions that are submitted have submittedTimes', function () { it('should auto fail transactions more than 12 hours old', function (done) { - migration29.migrate(storage) + migration29 + .migrate(storage) .then((migratedData) => { const txs = migratedData.data.TransactionController.transactions const [txMeta1] = txs assert.equal(migratedData.meta.version, 29) - assert.equal(txMeta1.status, 'failed', 'old tx is auto failed') - assert(txMeta1.err.message.includes('too long'), 'error message assigned') + assert.equal( + txMeta1.status, + TRANSACTION_STATUSES.FAILED, + 'old tx is auto failed', + ) + assert( + txMeta1.err.message.includes('too long'), + 'error message assigned', + ) txs.forEach((tx) => { if (tx.id === 1) { return } - assert.notEqual(tx.status, 'failed', 'other tx is not auto failed') + assert.notEqual( + tx.status, + TRANSACTION_STATUSES.FAILED, + 'other tx is not auto failed', + ) }) done() - }).catch(done) + }) + .catch(done) }) }) diff --git a/test/unit/migrations/030-test.js b/test/unit/migrations/030-test.js index 6d0271f0d..24acbfba2 100644 --- a/test/unit/migrations/030-test.js +++ b/test/unit/migrations/030-test.js @@ -16,8 +16,18 @@ const storage = { }, PreferencesController: { frequentRpcListDetail: [ - { chainId: 'fail', nickname: '', rpcUrl: 'http://127.0.0.1:8545', ticker: '' }, - { chainId: '1', nickname: '', rpcUrl: 'https://api.myetherwallet.com/eth', ticker: 'ETH' }, + { + chainId: 'fail', + nickname: '', + rpcUrl: 'http://127.0.0.1:8545', + ticker: '', + }, + { + chainId: '1', + nickname: '', + rpcUrl: 'https://api.myetherwallet.com/eth', + ticker: 'ETH', + }, ], }, }, @@ -25,14 +35,27 @@ const storage = { describe('storage is migrated successfully', function () { it('should work', function (done) { - migrationTemplate.migrate(storage) + migrationTemplate + .migrate(storage) .then((migratedData) => { assert.equal(migratedData.meta.version, 30) - assert.equal(migratedData.data.PreferencesController.frequentRpcListDetail[0].chainId, undefined) - assert.equal(migratedData.data.PreferencesController.frequentRpcListDetail[1].chainId, '1') - assert.equal(migratedData.data.NetworkController.provider.chainId, undefined) + assert.equal( + migratedData.data.PreferencesController.frequentRpcListDetail[0] + .chainId, + undefined, + ) + assert.equal( + migratedData.data.PreferencesController.frequentRpcListDetail[1] + .chainId, + '1', + ) + assert.equal( + migratedData.data.NetworkController.provider.chainId, + undefined, + ) assert.equal(migratedData.data.NetworkController.network, undefined) done() - }).catch(done) + }) + .catch(done) }) }) diff --git a/test/unit/migrations/031-test.js b/test/unit/migrations/031-test.js index 5c9f7d20b..2110fc373 100644 --- a/test/unit/migrations/031-test.js +++ b/test/unit/migrations/031-test.js @@ -4,28 +4,35 @@ import migration31 from '../../../app/scripts/migrations/031' describe('migration #31', function () { it('should set completedOnboarding to true if vault exists', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'tokens': [{ address: '0xa', symbol: 'A', decimals: 4 }, { address: '0xb', symbol: 'B', decimals: 4 }], - 'identities': { + meta: {}, + data: { + PreferencesController: { + tokens: [ + { address: '0xa', symbol: 'A', decimals: 4 }, + { address: '0xb', symbol: 'B', decimals: 4 }, + ], + identities: { '0x6d14': {}, '0x3695': {}, }, }, - 'KeyringController': { - 'vault': { - 'data': 'test0', - 'iv': 'test1', - 'salt': 'test2', + KeyringController: { + vault: { + data: 'test0', + iv: 'test1', + salt: 'test2', }, }, }, } - migration31.migrate(oldStorage) + migration31 + .migrate(oldStorage) .then((newStorage) => { - assert.equal(newStorage.data.PreferencesController.completedOnboarding, true) + assert.equal( + newStorage.data.PreferencesController.completedOnboarding, + true, + ) done() }) .catch(done) @@ -33,22 +40,29 @@ describe('migration #31', function () { it('should set completedOnboarding to false if vault does not exist', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'tokens': [{ address: '0xa', symbol: 'A', decimals: 4 }, { address: '0xb', symbol: 'B', decimals: 4 }], - 'identities': { + meta: {}, + data: { + PreferencesController: { + tokens: [ + { address: '0xa', symbol: 'A', decimals: 4 }, + { address: '0xb', symbol: 'B', decimals: 4 }, + ], + identities: { '0x6d14': {}, '0x3695': {}, }, }, - 'KeyringController': {}, + KeyringController: {}, }, } - migration31.migrate(oldStorage) + migration31 + .migrate(oldStorage) .then((newStorage) => { - assert.equal(newStorage.data.PreferencesController.completedOnboarding, false) + assert.equal( + newStorage.data.PreferencesController.completedOnboarding, + false, + ) done() }) .catch(done) diff --git a/test/unit/migrations/033-test.js b/test/unit/migrations/033-test.js index 4456a7e4a..b09846f0f 100644 --- a/test/unit/migrations/033-test.js +++ b/test/unit/migrations/033-test.js @@ -3,10 +3,10 @@ import migration33 from '../../../app/scripts/migrations/033' describe('Migration to delete notice controller', function () { const oldStorage = { - 'meta': {}, - 'data': { - 'NoticeController': { - 'noticesList': [ + meta: {}, + data: { + NoticeController: { + noticesList: [ { id: 0, read: false, @@ -32,9 +32,8 @@ describe('Migration to delete notice controller', function () { } it('removes notice controller from state', function () { - migration33.migrate(oldStorage) - .then((newStorage) => { - assert.equal(newStorage.data.NoticeController, undefined) - }) + migration33.migrate(oldStorage).then((newStorage) => { + assert.equal(newStorage.data.NoticeController, undefined) + }) }) }) diff --git a/test/unit/migrations/034-test.js b/test/unit/migrations/034-test.js index 64af34b2e..74de274cf 100644 --- a/test/unit/migrations/034-test.js +++ b/test/unit/migrations/034-test.js @@ -4,16 +4,17 @@ import migration34 from '../../../app/scripts/migrations/034' describe('migration #34', function () { it('should update the version metadata', function (done) { const oldStorage = { - 'meta': { - 'version': 33, + meta: { + version: 33, }, - 'data': {}, + data: {}, } - migration34.migrate(oldStorage) + migration34 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.meta, { - 'version': 34, + version: 34, }) done() }) @@ -22,22 +23,23 @@ describe('migration #34', function () { it('should set migratedPrivacyMode & privacyMode if featureFlags.privacyMode was false', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'featureFlags': { - 'privacyMode': false, + meta: {}, + data: { + PreferencesController: { + featureFlags: { + privacyMode: false, }, }, }, } - migration34.migrate(oldStorage) + migration34 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data.PreferencesController, { - 'migratedPrivacyMode': true, - 'featureFlags': { - 'privacyMode': true, + migratedPrivacyMode: true, + featureFlags: { + privacyMode: true, }, }) done() @@ -47,18 +49,19 @@ describe('migration #34', function () { it('should NOT change any state if migratedPrivacyMode is already set to true', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'migratedPrivacyMode': true, - 'featureFlags': { - 'privacyMode': true, + meta: {}, + data: { + PreferencesController: { + migratedPrivacyMode: true, + featureFlags: { + privacyMode: true, }, }, }, } - migration34.migrate(oldStorage) + migration34 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -68,18 +71,19 @@ describe('migration #34', function () { it('should NOT change any state if migratedPrivacyMode is already set to false', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'migratedPrivacyMode': false, - 'featureFlags': { - 'privacyMode': true, + meta: {}, + data: { + PreferencesController: { + migratedPrivacyMode: false, + featureFlags: { + privacyMode: true, }, }, }, } - migration34.migrate(oldStorage) + migration34 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -89,11 +93,12 @@ describe('migration #34', function () { it('should NOT change any state if PreferencesController is missing', function (done) { const oldStorage = { - 'meta': {}, - 'data': {}, + meta: {}, + data: {}, } - migration34.migrate(oldStorage) + migration34 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -103,17 +108,18 @@ describe('migration #34', function () { it('should NOT change any state if featureFlags.privacyMode is already true', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'featureFlags': { - 'privacyMode': true, + meta: {}, + data: { + PreferencesController: { + featureFlags: { + privacyMode: true, }, }, }, } - migration34.migrate(oldStorage) + migration34 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() diff --git a/test/unit/migrations/035-test.js b/test/unit/migrations/035-test.js index 02f5c5314..263b2e2df 100644 --- a/test/unit/migrations/035-test.js +++ b/test/unit/migrations/035-test.js @@ -10,10 +10,11 @@ describe('migration #35', function () { data: {}, } - migration35.migrate(oldStorage) + migration35 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.meta, { - 'version': 35, + version: 35, }) done() }) @@ -30,7 +31,8 @@ describe('migration #35', function () { }, } - migration35.migrate(oldStorage) + migration35 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data.PreferencesController, {}) done() @@ -48,7 +50,8 @@ describe('migration #35', function () { }, } - migration35.migrate(oldStorage) + migration35 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data.PreferencesController, {}) done() @@ -85,7 +88,8 @@ describe('migration #35', function () { }, } - migration35.migrate(oldStorage) + migration35 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() diff --git a/test/unit/migrations/036-test.js b/test/unit/migrations/036-test.js index e1666e612..380de627b 100644 --- a/test/unit/migrations/036-test.js +++ b/test/unit/migrations/036-test.js @@ -4,16 +4,17 @@ import migration36 from '../../../app/scripts/migrations/036' describe('migration #36', function () { it('should update the version metadata', function (done) { const oldStorage = { - 'meta': { - 'version': 35, + meta: { + version: 35, }, - 'data': {}, + data: {}, } - migration36.migrate(oldStorage) + migration36 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.meta, { - 'version': 36, + version: 36, }) done() }) @@ -22,21 +23,21 @@ describe('migration #36', function () { it('should remove privacyMode if featureFlags.privacyMode was false', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'featureFlags': { - 'privacyMode': false, + meta: {}, + data: { + PreferencesController: { + featureFlags: { + privacyMode: false, }, }, }, } - migration36.migrate(oldStorage) + migration36 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data.PreferencesController, { - 'featureFlags': { - }, + featureFlags: {}, }) done() }) @@ -45,21 +46,21 @@ describe('migration #36', function () { it('should remove privacyMode if featureFlags.privacyMode was true', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'featureFlags': { - 'privacyMode': true, + meta: {}, + data: { + PreferencesController: { + featureFlags: { + privacyMode: true, }, }, }, } - migration36.migrate(oldStorage) + migration36 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data.PreferencesController, { - 'featureFlags': { - }, + featureFlags: {}, }) done() }) @@ -68,17 +69,17 @@ describe('migration #36', function () { it('should NOT change any state if privacyMode does not exist', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'migratedPrivacyMode': true, - 'featureFlags': { - }, + meta: {}, + data: { + PreferencesController: { + migratedPrivacyMode: true, + featureFlags: {}, }, }, } - migration36.migrate(oldStorage) + migration36 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -88,11 +89,12 @@ describe('migration #36', function () { it('should NOT change any state if PreferencesController is missing', function (done) { const oldStorage = { - 'meta': {}, - 'data': {}, + meta: {}, + data: {}, } - migration36.migrate(oldStorage) + migration36 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -102,14 +104,14 @@ describe('migration #36', function () { it('should NOT change any state if featureFlags is missing', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - }, + meta: {}, + data: { + PreferencesController: {}, }, } - migration36.migrate(oldStorage) + migration36 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() diff --git a/test/unit/migrations/037-test.js b/test/unit/migrations/037-test.js index f67f8eb3f..861ab4cd1 100644 --- a/test/unit/migrations/037-test.js +++ b/test/unit/migrations/037-test.js @@ -4,16 +4,17 @@ import migration37 from '../../../app/scripts/migrations/037' describe('migration #37', function () { it('should update the version metadata', function (done) { const oldStorage = { - 'meta': { - 'version': 36, + meta: { + version: 36, }, - 'data': {}, + data: {}, } - migration37.migrate(oldStorage) + migration37 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.meta, { - 'version': 37, + version: 37, }) done() }) @@ -22,10 +23,10 @@ describe('migration #37', function () { it('should transform old state to new format', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'AddressBookController': { - 'addressBook': { + meta: {}, + data: { + AddressBookController: { + addressBook: { '0x1De7e54679bfF0c23856FbF547b2394e723FCA91': { address: '0x1De7e54679bfF0c23856FbF547b2394e723FCA91', chainId: '4', @@ -50,10 +51,11 @@ describe('migration #37', function () { }, } - migration37.migrate(oldStorage) + migration37 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data.AddressBookController.addressBook, { - '4': { + 4: { '0x1De7e54679bfF0c23856FbF547b2394e723FCA91': { address: '0x1De7e54679bfF0c23856FbF547b2394e723FCA91', chainId: '4', @@ -69,7 +71,7 @@ describe('migration #37', function () { name: 'account 2', }, }, - '2': { + 2: { '0x1De7e54679bfF0c23856FbF547b2394e723FCA93': { address: '0x1De7e54679bfF0c23856FbF547b2394e723FCA93', chainId: '2', @@ -86,10 +88,10 @@ describe('migration #37', function () { it('ens validation test', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'AddressBookController': { - 'addressBook': { + meta: {}, + data: { + AddressBookController: { + addressBook: { '0x1De7e54679bfF0c23856FbF547b2394e723FCA91': { address: '0x1De7e54679bfF0c23856FbF547b2394e723FCA91', chainId: '4', @@ -101,10 +103,11 @@ describe('migration #37', function () { }, } - migration37.migrate(oldStorage) + migration37 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data.AddressBookController.addressBook, { - '4': { + 4: { '0x1De7e54679bfF0c23856FbF547b2394e723FCA91': { address: '0x1De7e54679bfF0c23856FbF547b2394e723FCA91', chainId: '4', diff --git a/test/unit/migrations/038-test.js b/test/unit/migrations/038-test.js index 0060f9f51..2c727a352 100644 --- a/test/unit/migrations/038-test.js +++ b/test/unit/migrations/038-test.js @@ -4,16 +4,17 @@ import migration38 from '../../../app/scripts/migrations/038' describe('migration #38', function () { it('should update the version metadata', function (done) { const oldStorage = { - 'meta': { - 'version': 37, + meta: { + version: 37, }, - 'data': {}, + data: {}, } - migration38.migrate(oldStorage) + migration38 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.meta, { - 'version': 38, + version: 38, }) done() }) @@ -22,13 +23,17 @@ describe('migration #38', function () { it('should add a fullScreenVsPopup property set to either "control" or "fullScreen"', function (done) { const oldStorage = { - 'meta': {}, - 'data': {}, + meta: {}, + data: {}, } - migration38.migrate(oldStorage) + migration38 + .migrate(oldStorage) .then((newStorage) => { - assert.equal(newStorage.data.ABTestController.abTests.fullScreenVsPopup, 'control') + assert.equal( + newStorage.data.ABTestController.abTests.fullScreenVsPopup, + 'control', + ) done() }) .catch(done) @@ -36,21 +41,22 @@ describe('migration #38', function () { it('should leave the fullScreenVsPopup property unchanged if it exists', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'ABTestController': { + meta: {}, + data: { + ABTestController: { abTests: { - 'fullScreenVsPopup': 'fullScreen', + fullScreenVsPopup: 'fullScreen', }, }, }, } - migration38.migrate(oldStorage) + migration38 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data.ABTestController, { abTests: { - 'fullScreenVsPopup': 'fullScreen', + fullScreenVsPopup: 'fullScreen', }, }) done() diff --git a/test/unit/migrations/039-test.js b/test/unit/migrations/039-test.js index 400a7a3ae..70b00da5f 100644 --- a/test/unit/migrations/039-test.js +++ b/test/unit/migrations/039-test.js @@ -4,16 +4,17 @@ import migration39 from '../../../app/scripts/migrations/039' describe('migration #39', function () { it('should update the version metadata', function (done) { const oldStorage = { - 'meta': { - 'version': 38, + meta: { + version: 38, }, - 'data': {}, + data: {}, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.meta, { - 'version': 39, + version: 39, }) done() }) @@ -22,42 +23,51 @@ describe('migration #39', function () { it('should update old DAI token symbol to SAI in tokens', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'tokens': [{ - 'address': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - 'decimals': 18, - 'symbol': 'DAI', - }, { - 'address': '0x0d8775f648430679a709e98d2b0cb6250d2887ef', - 'symbol': 'BAT', - 'decimals': 18, - }, { - 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', - 'symbol': 'META', - 'decimals': 18, - }], + meta: {}, + data: { + PreferencesController: { + tokens: [ + { + address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + decimals: 18, + symbol: 'DAI', + }, + { + address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', + symbol: 'BAT', + decimals: 18, + }, + { + address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + symbol: 'META', + decimals: 18, + }, + ], }, }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data.PreferencesController, { - 'tokens': [{ - 'address': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - 'decimals': 18, - 'symbol': 'SAI', - }, { - 'address': '0x0d8775f648430679a709e98d2b0cb6250d2887ef', - 'symbol': 'BAT', - 'decimals': 18, - }, { - 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', - 'symbol': 'META', - 'decimals': 18, - }], + tokens: [ + { + address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + decimals: 18, + symbol: 'SAI', + }, + { + address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', + symbol: 'BAT', + decimals: 18, + }, + { + address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + symbol: 'META', + decimals: 18, + }, + ], }) done() }) @@ -66,40 +76,40 @@ describe('migration #39', function () { it('should update old DAI token symbol to SAI in accountTokens', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'accountTokens': { + meta: {}, + data: { + PreferencesController: { + accountTokens: { '0x7250739de134d33ec7ab1ee592711e15098c9d2d': { - 'mainnet': [ + mainnet: [ { - 'address': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - 'decimals': 18, - 'symbol': 'DAI', + address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + decimals: 18, + symbol: 'DAI', }, ], }, '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5': { - 'mainnet': [], - 'rinkeby': [], + mainnet: [], + rinkeby: [], }, '0x8e5d75d60224ea0c33d1041e75de68b1c3cb6dd5': {}, '0xb3958fb96c8201486ae20be1d5c9f58083df343a': { - 'mainnet': [ + mainnet: [ { - 'address': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - 'decimals': 18, - 'symbol': 'DAI', + address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + decimals: 18, + symbol: 'DAI', }, { - 'address': '0x0d8775f648430679a709e98d2b0cb6250d2887ef', - 'decimals': 18, - 'symbol': 'BAT', + address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', + decimals: 18, + symbol: 'BAT', }, { - 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', - 'decimals': 18, - 'symbol': 'META', + address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + decimals: 18, + symbol: 'META', }, ], }, @@ -108,40 +118,41 @@ describe('migration #39', function () { }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data.PreferencesController, { - 'accountTokens': { + accountTokens: { '0x7250739de134d33ec7ab1ee592711e15098c9d2d': { - 'mainnet': [ + mainnet: [ { - 'address': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - 'decimals': 18, - 'symbol': 'SAI', + address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + decimals: 18, + symbol: 'SAI', }, ], }, '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5': { - 'mainnet': [], - 'rinkeby': [], + mainnet: [], + rinkeby: [], }, '0x8e5d75d60224ea0c33d1041e75de68b1c3cb6dd5': {}, '0xb3958fb96c8201486ae20be1d5c9f58083df343a': { - 'mainnet': [ + mainnet: [ { - 'address': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - 'decimals': 18, - 'symbol': 'SAI', + address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + decimals: 18, + symbol: 'SAI', }, { - 'address': '0x0d8775f648430679a709e98d2b0cb6250d2887ef', - 'decimals': 18, - 'symbol': 'BAT', + address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', + decimals: 18, + symbol: 'BAT', }, { - 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', - 'decimals': 18, - 'symbol': 'META', + address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + decimals: 18, + symbol: 'META', }, ], }, @@ -154,15 +165,16 @@ describe('migration #39', function () { it('should NOT change any state if accountTokens is not an object', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'accountTokens': [], + meta: {}, + data: { + PreferencesController: { + accountTokens: [], }, }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -172,33 +184,29 @@ describe('migration #39', function () { it('should NOT change any state if accountTokens is an object with invalid values', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'accountTokens': { + meta: {}, + data: { + PreferencesController: { + accountTokens: { '0x7250739de134d33ec7ab1ee592711e15098c9d2d': [ { - 'address': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - 'decimals': 18, - 'symbol': 'DAI', + address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + decimals: 18, + symbol: 'DAI', }, ], '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359': null, '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5': { - 'mainnet': [ - null, - undefined, - [], - 42, - ], - 'rinkeby': null, + mainnet: [null, undefined, [], 42], + rinkeby: null, }, }, }, }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -208,40 +216,40 @@ describe('migration #39', function () { it('should NOT change any state if accountTokens includes the new DAI token', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'accountTokens': { + meta: {}, + data: { + PreferencesController: { + accountTokens: { '0x7250739de134d33ec7ab1ee592711e15098c9d2d': { - 'mainnet': [ + mainnet: [ { - 'address': '0x6B175474E89094C44Da98b954EedeAC495271d0F', - 'decimals': 18, - 'symbol': 'DAI', + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + decimals: 18, + symbol: 'DAI', }, ], }, '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5': { - 'mainnet': [], - 'rinkeby': [], + mainnet: [], + rinkeby: [], }, '0x8e5d75d60224ea0c33d1041e75de68b1c3cb6dd5': {}, '0xb3958fb96c8201486ae20be1d5c9f58083df343a': { - 'mainnet': [ + mainnet: [ { - 'address': '0x6B175474E89094C44Da98b954EedeAC495271d0F', - 'decimals': 18, - 'symbol': 'DAI', + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + decimals: 18, + symbol: 'DAI', }, { - 'address': '0x0d8775f648430679a709e98d2b0cb6250d2887ef', - 'decimals': 18, - 'symbol': 'BAT', + address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', + decimals: 18, + symbol: 'BAT', }, { - 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', - 'decimals': 18, - 'symbol': 'META', + address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + decimals: 18, + symbol: 'META', }, ], }, @@ -250,7 +258,8 @@ describe('migration #39', function () { }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -260,23 +269,27 @@ describe('migration #39', function () { it('should NOT change any state if tokens includes the new DAI token', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'tokens': [{ - 'address': '0x6B175474E89094C44Da98b954EedeAC495271d0F', - 'symbol': 'DAI', - 'decimals': 18, - }, { - 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', - 'symbol': 'META', - 'decimals': 18, - }], + meta: {}, + data: { + PreferencesController: { + tokens: [ + { + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + symbol: 'DAI', + decimals: 18, + }, + { + address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + symbol: 'META', + decimals: 18, + }, + ], }, }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -286,23 +299,27 @@ describe('migration #39', function () { it('should NOT change any state if tokens does not include DAI', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'tokens': [{ - 'address': '0x0d8775f648430679a709e98d2b0cb6250d2887ef', - 'symbol': 'BAT', - 'decimals': 18, - }, { - 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', - 'symbol': 'META', - 'decimals': 18, - }], + meta: {}, + data: { + PreferencesController: { + tokens: [ + { + address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', + symbol: 'BAT', + decimals: 18, + }, + { + address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + symbol: 'META', + decimals: 18, + }, + ], }, }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -312,20 +329,16 @@ describe('migration #39', function () { it('should NOT change any state if a tokens property has invalid entries', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'tokens': [ - null, - [], - undefined, - 42, - ], + meta: {}, + data: { + PreferencesController: { + tokens: [null, [], undefined, 42], }, }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -335,15 +348,16 @@ describe('migration #39', function () { it('should NOT change any state if a tokens property is not an array', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'tokens': {}, + meta: {}, + data: { + PreferencesController: { + tokens: {}, }, }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -353,15 +367,16 @@ describe('migration #39', function () { it('should NOT change any state if a tokens property is null', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - 'tokens': null, + meta: {}, + data: { + PreferencesController: { + tokens: null, }, }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -371,14 +386,14 @@ describe('migration #39', function () { it('should NOT change any state if a tokens property is missing', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - }, + meta: {}, + data: { + PreferencesController: {}, }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -388,14 +403,14 @@ describe('migration #39', function () { it('should NOT change any state if a accountTokens property is missing', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'PreferencesController': { - }, + meta: {}, + data: { + PreferencesController: {}, }, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() @@ -405,11 +420,12 @@ describe('migration #39', function () { it('should NOT change any state if PreferencesController is missing', function (done) { const oldStorage = { - 'meta': {}, - 'data': {}, + meta: {}, + data: {}, } - migration39.migrate(oldStorage) + migration39 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, oldStorage.data) done() diff --git a/test/unit/migrations/040-test.js b/test/unit/migrations/040-test.js index 215deb5b7..fd4f48193 100644 --- a/test/unit/migrations/040-test.js +++ b/test/unit/migrations/040-test.js @@ -2,19 +2,19 @@ import assert from 'assert' import migration40 from '../../../app/scripts/migrations/040' describe('migration #40', function () { - it('should update the version metadata', function (done) { const oldStorage = { - 'meta': { - 'version': 39, + meta: { + version: 39, }, - 'data': {}, + data: {}, } - migration40.migrate(oldStorage) + migration40 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.meta, { - 'version': 40, + version: 40, }) done() }) @@ -23,14 +23,15 @@ describe('migration #40', function () { it('should delete ProviderApprovalController storage key', function (done) { const oldStorage = { - 'meta': {}, - 'data': { - 'ProviderApprovalController': {}, - 'foo': 'bar', + meta: {}, + data: { + ProviderApprovalController: {}, + foo: 'bar', }, } - migration40.migrate(oldStorage) + migration40 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, { foo: 'bar' }) done() @@ -40,11 +41,12 @@ describe('migration #40', function () { it('should do nothing if no ProviderApprovalController storage key', function (done) { const oldStorage = { - 'meta': {}, - 'data': { foo: 'bar' }, + meta: {}, + data: { foo: 'bar' }, } - migration40.migrate(oldStorage) + migration40 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, { foo: 'bar' }) done() diff --git a/test/unit/migrations/041-test.js b/test/unit/migrations/041-test.js index 98ef4316d..7b95dfa3f 100644 --- a/test/unit/migrations/041-test.js +++ b/test/unit/migrations/041-test.js @@ -2,19 +2,19 @@ import assert from 'assert' import migration41 from '../../../app/scripts/migrations/041' describe('migration #41', function () { - it('should update the version metadata', function (done) { const oldStorage = { - 'meta': { - 'version': 40, + meta: { + version: 40, }, - 'data': {}, + data: {}, } - migration41.migrate(oldStorage) + migration41 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.meta, { - 'version': 41, + version: 41, }) done() }) @@ -36,7 +36,8 @@ describe('migration #41', function () { }, } - migration41.migrate(oldStorage) + migration41 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, { PreferencesController: { @@ -61,7 +62,8 @@ describe('migration #41', function () { }, } - migration41.migrate(oldStorage) + migration41 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, { foo: 'bar', @@ -82,7 +84,8 @@ describe('migration #41', function () { }, } - migration41.migrate(oldStorage) + migration41 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, { PreferencesController: { diff --git a/test/unit/migrations/042-test.js b/test/unit/migrations/042-test.js index 14a61d95e..0c2cbf01a 100644 --- a/test/unit/migrations/042-test.js +++ b/test/unit/migrations/042-test.js @@ -2,19 +2,19 @@ import assert from 'assert' import migration42 from '../../../app/scripts/migrations/042' describe('migration #42', function () { - it('should update the version metadata', function (done) { const oldStorage = { - 'meta': { - 'version': 41, + meta: { + version: 41, }, - 'data': {}, + data: {}, } - migration42.migrate(oldStorage) + migration42 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.meta, { - 'version': 42, + version: 42, }) done() }) @@ -33,7 +33,8 @@ describe('migration #42', function () { }, } - migration42.migrate(oldStorage) + migration42 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, { AppStateController: { @@ -55,7 +56,8 @@ describe('migration #42', function () { }, } - migration42.migrate(oldStorage) + migration42 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, { foo: 'bar', diff --git a/test/unit/migrations/043-test.js b/test/unit/migrations/043-test.js index e8b2039e3..5a25e676c 100644 --- a/test/unit/migrations/043-test.js +++ b/test/unit/migrations/043-test.js @@ -2,18 +2,17 @@ import { strict as assert } from 'assert' import migration43 from '../../../app/scripts/migrations/043' describe('migration #43', function () { - it('should update the version metadata', async function () { const oldStorage = { - 'meta': { - 'version': 42, + meta: { + version: 42, }, - 'data': {}, + data: {}, } const newStorage = await migration43.migrate(oldStorage) assert.deepEqual(newStorage.meta, { - 'version': 43, + version: 43, }) }) diff --git a/test/unit/migrations/044-test.js b/test/unit/migrations/044-test.js index 08a609854..0fe8a9648 100644 --- a/test/unit/migrations/044-test.js +++ b/test/unit/migrations/044-test.js @@ -4,15 +4,15 @@ import migration44 from '../../../app/scripts/migrations/044' describe('migration #44', function () { it('should update the version metadata', async function () { const oldStorage = { - 'meta': { - 'version': 43, + meta: { + version: 43, }, - 'data': {}, + data: {}, } const newStorage = await migration44.migrate(oldStorage) assert.deepEqual(newStorage.meta, { - 'version': 44, + version: 44, }) }) diff --git a/test/unit/migrations/045-test.js b/test/unit/migrations/045-test.js index 650e06f38..6547203da 100644 --- a/test/unit/migrations/045-test.js +++ b/test/unit/migrations/045-test.js @@ -4,16 +4,17 @@ import migration45 from '../../../app/scripts/migrations/045' describe('migration #45', function () { it('should update the version metadata', function (done) { const oldStorage = { - 'meta': { - 'version': 44, + meta: { + version: 44, }, - 'data': {}, + data: {}, } - migration45.migrate(oldStorage) + migration45 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.meta, { - 'version': 45, + version: 45, }) done() }) @@ -32,7 +33,8 @@ describe('migration #45', function () { }, } - migration45.migrate(oldStorage) + migration45 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, { PreferencesController: { @@ -58,7 +60,8 @@ describe('migration #45', function () { }, } - migration45.migrate(oldStorage) + migration45 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, { PreferencesController: { @@ -80,7 +83,8 @@ describe('migration #45', function () { }, } - migration45.migrate(oldStorage) + migration45 + .migrate(oldStorage) .then((newStorage) => { assert.deepEqual(newStorage.data, { foo: 'bar', diff --git a/test/unit/migrations/046-test.js b/test/unit/migrations/046-test.js index ddb406c14..0c327eb8a 100644 --- a/test/unit/migrations/046-test.js +++ b/test/unit/migrations/046-test.js @@ -4,15 +4,15 @@ import migration46 from '../../../app/scripts/migrations/046' describe('migration #46', function () { it('should update the version metadata', async function () { const oldStorage = { - 'meta': { - 'version': 45, + meta: { + version: 45, }, - 'data': {}, + data: {}, } const newStorage = await migration46.migrate(oldStorage) assert.deepEqual(newStorage.meta, { - 'version': 46, + version: 46, }) }) diff --git a/test/unit/migrations/047-test.js b/test/unit/migrations/047-test.js index 4ee0e320a..bdaf66175 100644 --- a/test/unit/migrations/047-test.js +++ b/test/unit/migrations/047-test.js @@ -4,15 +4,15 @@ import migration47 from '../../../app/scripts/migrations/047' describe('migration #47', function () { it('should update the version metadata', async function () { const oldStorage = { - 'meta': { - 'version': 46, + meta: { + version: 46, }, - 'data': {}, + data: {}, } const newStorage = await migration47.migrate(oldStorage) assert.deepEqual(newStorage.meta, { - 'version': 47, + version: 47, }) }) diff --git a/test/unit/migrations/048-test.js b/test/unit/migrations/048-test.js index ec7f16181..bf96a0715 100644 --- a/test/unit/migrations/048-test.js +++ b/test/unit/migrations/048-test.js @@ -10,24 +10,26 @@ const localhostNetwork = { } const expectedPreferencesState = { PreferencesController: { - frequentRpcListDetail: [{ - ...localhostNetwork, - }], + frequentRpcListDetail: [ + { + ...localhostNetwork, + }, + ], }, } describe('migration #48', function () { it('should update the version metadata', async function () { const oldStorage = { - 'meta': { - 'version': 47, + meta: { + version: 47, }, - 'data': {}, + data: {}, } const newStorage = await migration48.migrate(oldStorage) assert.deepEqual(newStorage.meta, { - 'version': 48, + version: 48, }) }) @@ -252,9 +254,7 @@ describe('migration #48', function () { meta: {}, data: { PreferencesController: { - frequentRpcListDetail: [ - ...existingList, - ], + frequentRpcListDetail: [...existingList], }, foo: 'bar', }, @@ -263,10 +263,7 @@ describe('migration #48', function () { const newStorage = await migration48.migrate(oldStorage) assert.deepEqual(newStorage.data, { PreferencesController: { - frequentRpcListDetail: [ - { ...localhostNetwork }, - ...existingList, - ], + frequentRpcListDetail: [{ ...localhostNetwork }, ...existingList], }, foo: 'bar', }) @@ -355,20 +352,20 @@ describe('migration #48', function () { data: { AddressBookController: { addressBook: { - '1': { - 'address1': { + 1: { + address1: { chainId: '1', foo: 'bar', }, }, - '100': { - 'address1': { + 100: { + address1: { chainId: '100', foo: 'bar', }, }, '0x2': { - 'address2': { + address2: { chainId: '0x2', foo: 'bar', }, @@ -388,19 +385,19 @@ describe('migration #48', function () { AddressBookController: { addressBook: { '0x1': { - 'address1': { + address1: { chainId: '0x1', foo: 'bar', }, }, '0x64': { - 'address1': { + address1: { chainId: '0x64', foo: 'bar', }, }, '0x2': { - 'address2': { + address2: { chainId: '0x2', foo: 'bar', }, @@ -420,27 +417,27 @@ describe('migration #48', function () { data: { AddressBookController: { addressBook: { - '2': { - 'address1': { + 2: { + address1: { chainId: '2', key2: 'kaplar', key3: 'value3', key4: null, foo: 'bar', }, - 'address2': { + address2: { chainId: '2', foo: 'bar', }, }, '0x2': { - 'address1': { + address1: { chainId: '0x2', key1: 'value1', key2: 'value2', foo: 'bar', }, - 'address3': { + address3: { chainId: '0x2', foo: 'bar', }, @@ -460,7 +457,7 @@ describe('migration #48', function () { AddressBookController: { addressBook: { '0x2': { - 'address1': { + address1: { chainId: '0x2', key1: 'value1', key2: 'value2', @@ -468,11 +465,11 @@ describe('migration #48', function () { key4: '', foo: 'bar', }, - 'address2': { + address2: { chainId: '0x2', foo: 'bar', }, - 'address3': { + address3: { chainId: '0x2', foo: 'bar', }, @@ -493,7 +490,7 @@ describe('migration #48', function () { AddressBookController: { addressBook: { '0x1': { foo: { bar: 'baz' } }, - 'kaplar': { foo: { bar: 'baz' } }, + kaplar: { foo: { bar: 'baz' } }, }, bar: { baz: 'buzz', @@ -509,7 +506,7 @@ describe('migration #48', function () { AddressBookController: { addressBook: { '0x1': { foo: { bar: 'baz' } }, - 'kaplar': { foo: { bar: 'baz' } }, + kaplar: { foo: { bar: 'baz' } }, }, bar: { baz: 'buzz', @@ -626,9 +623,11 @@ describe('migration #48', function () { }, bar: 'baz', // from other migration - frequentRpcListDetail: [{ - ...localhostNetwork, - }], + frequentRpcListDetail: [ + { + ...localhostNetwork, + }, + ], }, foo: 'bar', }) diff --git a/test/unit/migrations/migrations-test.js b/test/unit/migrations/migrations-test.js index 29d941edb..a61376145 100644 --- a/test/unit/migrations/migrations-test.js +++ b/test/unit/migrations/migrations-test.js @@ -23,83 +23,230 @@ describe('wallet1 is migrated successfully', function () { it('should convert providers', function () { wallet1.data.config.provider = { type: 'etherscan', rpcTarget: null } - return migration2.migrate(wallet1) + return migration2 + .migrate(wallet1) .then((secondResult) => { const secondData = secondResult.data - assert.equal(secondData.config.provider.type, 'rpc', 'provider should be rpc') - assert.equal(secondData.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'main provider should be our rpc') + assert.equal( + secondData.config.provider.type, + 'rpc', + 'provider should be rpc', + ) + assert.equal( + secondData.config.provider.rpcTarget, + 'https://rpc.metamask.io/', + 'main provider should be our rpc', + ) secondResult.data.config.provider.rpcTarget = oldTestRpc return migration3.migrate(secondResult) - }).then((thirdResult) => { - assert.equal(thirdResult.data.config.provider.rpcTarget, newTestRpc, 'config.provider.rpcTarget should be set to the proper testrpc url.') + }) + .then((thirdResult) => { + assert.equal( + thirdResult.data.config.provider.rpcTarget, + newTestRpc, + 'config.provider.rpcTarget should be set to the proper testrpc url.', + ) return migration4.migrate(thirdResult) - }).then((fourthResult) => { + }) + .then((fourthResult) => { const fourthData = fourthResult.data - assert.equal(fourthData.config.provider.rpcTarget, null, 'old rpcTarget should not exist.') - assert.equal(fourthData.config.provider.type, 'testnet', 'config.provider should be set to testnet.') + assert.equal( + fourthData.config.provider.rpcTarget, + null, + 'old rpcTarget should not exist.', + ) + assert.equal( + fourthData.config.provider.type, + 'testnet', + 'config.provider should be set to testnet.', + ) return migration5.migrate(vault4) - }).then((fifthResult) => { + }) + .then((fifthResult) => { const fifthData = fifthResult.data assert.equal(fifthData.vault, null, 'old vault should not exist') - assert.equal(fifthData.walletNicknames, null, 'old walletNicknames should not exist') - assert.equal(fifthData.config.selectedAccount, null, 'old config.selectedAccount should not exist') - assert.equal(fifthData.KeyringController.vault, vault4.data.vault, 'KeyringController.vault should exist') - assert.equal(fifthData.KeyringController.selectedAccount, vault4.data.config.selectedAccount, 'KeyringController.selectedAccount should have moved') - assert.equal(fifthData.KeyringController.walletNicknames['0x0beb674745816b125fbc07285d39fd373e64895c'], vault4.data.walletNicknames['0x0beb674745816b125fbc07285d39fd373e64895c'], 'KeyringController.walletNicknames should have moved') + assert.equal( + fifthData.walletNicknames, + null, + 'old walletNicknames should not exist', + ) + assert.equal( + fifthData.config.selectedAccount, + null, + 'old config.selectedAccount should not exist', + ) + assert.equal( + fifthData.KeyringController.vault, + vault4.data.vault, + 'KeyringController.vault should exist', + ) + assert.equal( + fifthData.KeyringController.selectedAccount, + vault4.data.config.selectedAccount, + 'KeyringController.selectedAccount should have moved', + ) + assert.equal( + fifthData.KeyringController.walletNicknames[ + '0x0beb674745816b125fbc07285d39fd373e64895c' + ], + vault4.data.walletNicknames[ + '0x0beb674745816b125fbc07285d39fd373e64895c' + ], + 'KeyringController.walletNicknames should have moved', + ) vault5 = fifthResult return migration6.migrate(fifthResult) - }).then((sixthResult) => { - assert.equal(sixthResult.data.KeyringController.selectedAccount, null, 'old selectedAccount should not exist') - assert.equal(sixthResult.data.PreferencesController.selectedAddress, vault5.data.KeyringController.selectedAccount, 'selectedAccount should have moved') + }) + .then((sixthResult) => { + assert.equal( + sixthResult.data.KeyringController.selectedAccount, + null, + 'old selectedAccount should not exist', + ) + assert.equal( + sixthResult.data.PreferencesController.selectedAddress, + vault5.data.KeyringController.selectedAccount, + 'selectedAccount should have moved', + ) vault6 = sixthResult return migration7.migrate(sixthResult) - }).then((seventhResult) => { - assert.equal(seventhResult.data.transactions, null, 'old transactions should not exist') - assert.equal(seventhResult.data.gasMultiplier, null, 'old gasMultiplier should not exist') - assert.equal(seventhResult.data.TransactionManager.transactions[0].id, vault6.data.transactions[0].id, 'transactions should have moved') - assert.equal(seventhResult.data.TransactionManager.gasMultiplier, vault6.data.gasMultiplier, 'gasMultiplier should have moved') + }) + .then((seventhResult) => { + assert.equal( + seventhResult.data.transactions, + null, + 'old transactions should not exist', + ) + assert.equal( + seventhResult.data.gasMultiplier, + null, + 'old gasMultiplier should not exist', + ) + assert.equal( + seventhResult.data.TransactionManager.transactions[0].id, + vault6.data.transactions[0].id, + 'transactions should have moved', + ) + assert.equal( + seventhResult.data.TransactionManager.gasMultiplier, + vault6.data.gasMultiplier, + 'gasMultiplier should have moved', + ) vault7 = seventhResult return migration8.migrate(seventhResult) - }).then((eighthResult) => { - assert.equal(eighthResult.data.noticesList, null, 'old noticesList should not exist') - assert.equal(eighthResult.data.NoticeController.noticesList[0].title, vault7.data.noticesList[0].title, 'noticesList should have moved') + }) + .then((eighthResult) => { + assert.equal( + eighthResult.data.noticesList, + null, + 'old noticesList should not exist', + ) + assert.equal( + eighthResult.data.NoticeController.noticesList[0].title, + vault7.data.noticesList[0].title, + 'noticesList should have moved', + ) vault8 = eighthResult return migration9.migrate(eighthResult) - }).then((ninthResult) => { - assert.equal(ninthResult.data.currentFiat, null, 'old currentFiat should not exist') - assert.equal(ninthResult.data.fiatCurrency, null, 'old fiatCurrency should not exist') - assert.equal(ninthResult.data.conversionRate, null, 'old conversionRate should not exist') - assert.equal(ninthResult.data.conversionDate, null, 'old conversionDate should not exist') + }) + .then((ninthResult) => { + assert.equal( + ninthResult.data.currentFiat, + null, + 'old currentFiat should not exist', + ) + assert.equal( + ninthResult.data.fiatCurrency, + null, + 'old fiatCurrency should not exist', + ) + assert.equal( + ninthResult.data.conversionRate, + null, + 'old conversionRate should not exist', + ) + assert.equal( + ninthResult.data.conversionDate, + null, + 'old conversionDate should not exist', + ) - assert.equal(ninthResult.data.CurrencyController.currentCurrency, vault8.data.fiatCurrency, 'currentFiat should have moved') - assert.equal(ninthResult.data.CurrencyController.conversionRate, vault8.data.conversionRate, 'conversionRate should have moved') - assert.equal(ninthResult.data.CurrencyController.conversionDate, vault8.data.conversionDate, 'conversionDate should have moved') + assert.equal( + ninthResult.data.CurrencyController.currentCurrency, + vault8.data.fiatCurrency, + 'currentFiat should have moved', + ) + assert.equal( + ninthResult.data.CurrencyController.conversionRate, + vault8.data.conversionRate, + 'conversionRate should have moved', + ) + assert.equal( + ninthResult.data.CurrencyController.conversionDate, + vault8.data.conversionDate, + 'conversionDate should have moved', + ) vault9 = ninthResult return migration10.migrate(ninthResult) - }).then((tenthResult) => { - assert.equal(tenthResult.data.shapeShiftTxList, null, 'old shapeShiftTxList should not exist') - assert.equal(tenthResult.data.ShapeShiftController.shapeShiftTxList[0].transaction, vault9.data.shapeShiftTxList[0].transaction) + }) + .then((tenthResult) => { + assert.equal( + tenthResult.data.shapeShiftTxList, + null, + 'old shapeShiftTxList should not exist', + ) + assert.equal( + tenthResult.data.ShapeShiftController.shapeShiftTxList[0].transaction, + vault9.data.shapeShiftTxList[0].transaction, + ) return migration11.migrate(tenthResult) - }).then((eleventhResult) => { - assert.equal(eleventhResult.data.isDisclaimerConfirmed, null, 'isDisclaimerConfirmed should not exist') - assert.equal(eleventhResult.data.TOSHash, null, 'TOSHash should not exist') + }) + .then((eleventhResult) => { + assert.equal( + eleventhResult.data.isDisclaimerConfirmed, + null, + 'isDisclaimerConfirmed should not exist', + ) + assert.equal( + eleventhResult.data.TOSHash, + null, + 'TOSHash should not exist', + ) return migration12.migrate(eleventhResult) - }).then((twelfthResult) => { - assert.equal(twelfthResult.data.NoticeController.noticesList[0].body, '', 'notices that have been read should have an empty body.') - assert.equal(twelfthResult.data.NoticeController.noticesList[1].body, 'nonempty', 'notices that have not been read should not have an empty body.') + }) + .then((twelfthResult) => { + assert.equal( + twelfthResult.data.NoticeController.noticesList[0].body, + '', + 'notices that have been read should have an empty body.', + ) + assert.equal( + twelfthResult.data.NoticeController.noticesList[1].body, + 'nonempty', + 'notices that have not been read should not have an empty body.', + ) - assert.equal(twelfthResult.data.config.provider.type, 'testnet', 'network is originally testnet.') + assert.equal( + twelfthResult.data.config.provider.type, + 'testnet', + 'network is originally testnet.', + ) return migration13.migrate(twelfthResult) - }).then((thirteenthResult) => { - assert.equal(thirteenthResult.data.config.provider.type, 'ropsten', 'network has been changed to ropsten.') + }) + .then((thirteenthResult) => { + assert.equal( + thirteenthResult.data.config.provider.type, + 'ropsten', + 'network has been changed to ropsten.', + ) }) }) }) diff --git a/test/unit/migrations/migrator-test.js b/test/unit/migrations/migrator-test.js index 87468d917..7c5001596 100644 --- a/test/unit/migrations/migrator-test.js +++ b/test/unit/migrations/migrator-test.js @@ -41,7 +41,6 @@ const firstTimeState = { describe('migrations', function () { describe('liveMigrations require list', function () { - let migrationNumbers before(function () { @@ -49,7 +48,7 @@ describe('migrations', function () { migrationNumbers = fileNames .reduce((acc, filename) => { const name = filename.split('.')[0] - if ((/^\d+$/u).test(name)) { + if (/^\d+$/u.test(name)) { acc.push(name) } return acc @@ -60,7 +59,10 @@ describe('migrations', function () { it('should include all migrations', function () { migrationNumbers.forEach((num) => { const migration = liveMigrations.find((m) => m.version === num) - assert(migration, `migration not included in 'migrations/index.js': ${num}`) + assert( + migration, + `migration not included in 'migrations/index.js': ${num}`, + ) }) }) @@ -69,7 +71,7 @@ describe('migrations', function () { const testNumbers = fileNames .reduce((acc, filename) => { const name = filename.split('-test.')[0] - if ((/^\d+$/u).test(name)) { + if (/^\d+$/u.test(name)) { acc.push(name) } return acc @@ -78,7 +80,10 @@ describe('migrations', function () { migrationNumbers.forEach((num) => { if (num >= 33) { - assert.ok(testNumbers.includes(num), `no test found for migration: ${num}`) + assert.ok( + testNumbers.includes(num), + `no test found for migration: ${num}`, + ) } }) }) @@ -100,12 +105,14 @@ describe('migrations', function () { it('should emit an error', async function () { const migrator = new Migrator({ - migrations: [{ - version: 1, - async migrate () { - throw new Error('test') + migrations: [ + { + version: 1, + async migrate() { + throw new Error('test') + }, }, - }], + ], }) await assert.rejects(migrator.migrateData({ meta: { version: 0 } })) }) diff --git a/test/unit/migrations/template-test.js b/test/unit/migrations/template-test.js index e08ddf287..ca74b30f0 100644 --- a/test/unit/migrations/template-test.js +++ b/test/unit/migrations/template-test.js @@ -8,10 +8,12 @@ const storage = { describe('storage is migrated successfully', function () { it('should work', function (done) { - migrationTemplate.migrate(storage) + migrationTemplate + .migrate(storage) .then((migratedData) => { assert.equal(migratedData.meta.version, 0) done() - }).catch(done) + }) + .catch(done) }) }) diff --git a/test/unit/responsive/components/dropdown-test.js b/test/unit/responsive/components/dropdown-test.js index eff359a1f..fc550a6fd 100644 --- a/test/unit/responsive/components/dropdown-test.js +++ b/test/unit/responsive/components/dropdown-test.js @@ -7,7 +7,6 @@ import { renderWithProvider } from '../../../lib/render-helpers' import { Dropdown } from '../../../../ui/app/components/app/dropdowns/components/dropdown' describe('Dropdown components', function () { - const mockState = { metamask: {}, } @@ -33,7 +32,8 @@ describe('Dropdown components', function () {
  • Item 1
  • Item 2
  • -
    , store, + , + store, ) const item1 = getByText(/Item 1/u) @@ -41,5 +41,4 @@ describe('Dropdown components', function () { assert.ok(onClickSpy.calledOnce) }) - }) diff --git a/test/unit/ui/app/actions.spec.js b/test/unit/ui/app/actions.spec.js index ff8d74155..928bf2012 100644 --- a/test/unit/ui/app/actions.spec.js +++ b/test/unit/ui/app/actions.spec.js @@ -11,6 +11,7 @@ import enLocale from '../../../../app/_locales/en/messages.json' import * as actions from '../../../../ui/app/store/actions' import MetaMaskController from '../../../../app/scripts/metamask-controller' import firstTimeState from '../../localhostState' +import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction' const { provider } = createTestProviderTools({ scaffold: {} }) const middleware = [thunk] @@ -25,19 +26,19 @@ const extensionMock = { } describe('Actions', function () { - const noop = () => undefined const currentNetworkId = '42' let background, metamaskController - const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' + const TEST_SEED = + 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' const password = 'a-fake-password' - const importPrivkey = '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' + const importPrivkey = + '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' beforeEach(async function () { - metamaskController = new MetaMaskController({ extension: extensionMock, platform: { getVersion: () => 'foo' }, @@ -46,11 +47,11 @@ describe('Actions', function () { showUnapprovedTx: noop, showUnconfirmedMessage: noop, encryptor: { - encrypt (_, object) { + encrypt(_, object) { this.object = object return Promise.resolve('mock-encrypted') }, - decrypt () { + decrypt() { return Promise.resolve(this.object) }, }, @@ -65,7 +66,9 @@ describe('Actions', function () { await metamaskController.createNewVaultAndRestore(password, TEST_SEED) - await metamaskController.importAccountWithStrategy('Private Key', [importPrivkey]) + await metamaskController.importAccountWithStrategy('Private Key', [ + importPrivkey, + ]) background = metamaskController.getApi() @@ -75,7 +78,6 @@ describe('Actions', function () { }) describe('#tryUnlockMetamask', function () { - let submitPasswordSpy, verifySeedPhraseSpy afterEach(function () { @@ -84,7 +86,6 @@ describe('Actions', function () { }) it('calls submitPassword and verifySeedPhrase', async function () { - const store = mockStore() submitPasswordSpy = sinon.spy(background, 'submitPassword') @@ -96,7 +97,6 @@ describe('Actions', function () { }) it('errors on submitPassword will fail', async function () { - const store = mockStore() const expectedActions = [ @@ -135,8 +135,12 @@ describe('Actions', function () { assert.fail('Should have thrown error') } catch (_) { const actions1 = store.getActions() - const warning = actions1.filter((action) => action.type === 'DISPLAY_WARNING') - const unlockFailed = actions1.filter((action) => action.type === 'UNLOCK_FAILED') + const warning = actions1.filter( + (action) => action.type === 'DISPLAY_WARNING', + ) + const unlockFailed = actions1.filter( + (action) => action.type === 'UNLOCK_FAILED', + ) assert.deepEqual(warning, displayWarningError) assert.deepEqual(unlockFailed, unlockFailedError) } @@ -144,7 +148,6 @@ describe('Actions', function () { }) describe('#createNewVaultAndRestore', function () { - let createNewVaultAndRestoreSpy afterEach(function () { @@ -152,10 +155,12 @@ describe('Actions', function () { }) it('restores new vault', async function () { - const store = mockStore() - createNewVaultAndRestoreSpy = sinon.spy(background, 'createNewVaultAndRestore') + createNewVaultAndRestoreSpy = sinon.spy( + background, + 'createNewVaultAndRestore', + ) try { await store.dispatch(actions.createNewVaultAndRestore()) @@ -174,7 +179,10 @@ describe('Actions', function () { { type: 'HIDE_LOADING_INDICATION' }, ] - createNewVaultAndRestoreSpy = sinon.stub(background, 'createNewVaultAndRestore') + createNewVaultAndRestoreSpy = sinon.stub( + background, + 'createNewVaultAndRestore', + ) createNewVaultAndRestoreSpy.callsFake((_, __, callback) => { callback(new Error('error')) @@ -225,7 +233,6 @@ describe('Actions', function () { } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } - }) }) @@ -250,11 +257,11 @@ describe('Actions', function () { removeAccountStub = sinon.stub(background, 'removeAccount') removeAccountStub.callsFake((_, callback) => callback()) - await store.dispatch(actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc')) + await store.dispatch( + actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc'), + ) assert(removeAccountStub.calledOnce) - const actionTypes = store - .getActions() - .map((action) => action.type) + const actionTypes = store.getActions().map((action) => action.type) assert.deepEqual(actionTypes, expectedActions) }) @@ -273,20 +280,18 @@ describe('Actions', function () { }) try { - await store.dispatch(actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc')) + await store.dispatch( + actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc'), + ) assert.fail('Should have thrown error') } catch (_) { - const actionTypes = store - .getActions() - .map((action) => action.type) + const actionTypes = store.getActions().map((action) => action.type) assert.deepEqual(actionTypes, expectedActions) } - }) }) describe('#resetAccount', function () { - let resetAccountSpy afterEach(function () { @@ -294,7 +299,6 @@ describe('Actions', function () { }) it('resets account', async function () { - const store = mockStore() const expectedActions = [ @@ -334,7 +338,6 @@ describe('Actions', function () { }) describe('#importNewAccount', function () { - let importAccountWithStrategySpy afterEach(function () { @@ -344,11 +347,16 @@ describe('Actions', function () { it('calls importAccountWithStrategies in background', async function () { const store = mockStore() - importAccountWithStrategySpy = sinon.spy(background, 'importAccountWithStrategy') + importAccountWithStrategySpy = sinon.spy( + background, + 'importAccountWithStrategy', + ) - await store.dispatch(actions.importNewAccount('Private Key', [ - 'c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3', - ])) + await store.dispatch( + actions.importNewAccount('Private Key', [ + 'c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3', + ]), + ) assert(importAccountWithStrategySpy.calledOnce) }) @@ -356,12 +364,18 @@ describe('Actions', function () { const store = mockStore() const expectedActions = [ - { type: 'SHOW_LOADING_INDICATION', value: 'This may take a while, please be patient.' }, + { + type: 'SHOW_LOADING_INDICATION', + value: 'This may take a while, please be patient.', + }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] - importAccountWithStrategySpy = sinon.stub(background, 'importAccountWithStrategy') + importAccountWithStrategySpy = sinon.stub( + background, + 'importAccountWithStrategy', + ) importAccountWithStrategySpy.callsFake((_, __, callback) => { callback(new Error('error')) }) @@ -376,7 +390,6 @@ describe('Actions', function () { }) describe('#addNewAccount', function () { - it('Adds a new account', async function () { const store = mockStore({ metamask: { identities: {} } }) @@ -385,11 +398,9 @@ describe('Actions', function () { await store.dispatch(actions.addNewAccount()) assert(addNewAccountSpy.calledOnce) }) - }) describe('#checkHardwareStatus', function () { - let checkHardwareStatusSpy afterEach(function () { @@ -397,16 +408,20 @@ describe('Actions', function () { }) it('calls checkHardwareStatus in background', async function () { - checkHardwareStatusSpy = sinon.stub(background, 'checkHardwareStatus') + checkHardwareStatusSpy = sinon + .stub(background, 'checkHardwareStatus') .callsArgWith(2, null) const store = mockStore() - await store.dispatch(actions.checkHardwareStatus('ledger', `m/44'/60'/0'/0`)) + await store.dispatch( + actions.checkHardwareStatus('ledger', `m/44'/60'/0'/0`), + ) assert.equal(checkHardwareStatusSpy.calledOnce, true) }) it('shows loading indicator and displays error', async function () { - checkHardwareStatusSpy = sinon.stub(background, 'checkHardwareStatus') + checkHardwareStatusSpy = sinon + .stub(background, 'checkHardwareStatus') .callsArgWith(2, new Error('error')) const store = mockStore() @@ -425,7 +440,6 @@ describe('Actions', function () { }) describe('#forgetDevice', function () { - let forgetDeviceSpy afterEach(function () { @@ -433,17 +447,18 @@ describe('Actions', function () { }) it('calls forgetDevice in background', async function () { - forgetDeviceSpy = sinon.stub(background, 'forgetDevice') + forgetDeviceSpy = sinon + .stub(background, 'forgetDevice') .callsArgWith(1, null) const store = mockStore() await store.dispatch(actions.forgetDevice('ledger')) assert.equal(forgetDeviceSpy.calledOnce, true) - }) it('shows loading indicator and displays error', async function () { - forgetDeviceSpy = sinon.stub(background, 'forgetDevice') + forgetDeviceSpy = sinon + .stub(background, 'forgetDevice') .callsArgWith(1, new Error('error')) const store = mockStore() @@ -462,7 +477,6 @@ describe('Actions', function () { }) describe('#connectHardware', function () { - let connectHardwareSpy afterEach(function () { @@ -470,22 +484,28 @@ describe('Actions', function () { }) it('calls connectHardware in background', async function () { - connectHardwareSpy = sinon.stub(background, 'connectHardware') + connectHardwareSpy = sinon + .stub(background, 'connectHardware') .callsArgWith(3, null) const store = mockStore() - await store.dispatch(actions.connectHardware('ledger', 0, `m/44'/60'/0'/0`)) + await store.dispatch( + actions.connectHardware('ledger', 0, `m/44'/60'/0'/0`), + ) assert.equal(connectHardwareSpy.calledOnce, true) - }) it('shows loading indicator and displays error', async function () { - connectHardwareSpy = sinon.stub(background, 'connectHardware') + connectHardwareSpy = sinon + .stub(background, 'connectHardware') .callsArgWith(3, new Error('error')) const store = mockStore() const expectedActions = [ - { type: 'SHOW_LOADING_INDICATION', value: 'Looking for your Ledger...' }, + { + type: 'SHOW_LOADING_INDICATION', + value: 'Looking for your Ledger...', + }, { type: 'DISPLAY_WARNING', value: 'error' }, ] @@ -499,7 +519,6 @@ describe('Actions', function () { }) describe('#unlockHardwareWalletAccount', function () { - let unlockHardwareWalletAccountSpy afterEach(function () { @@ -507,17 +526,20 @@ describe('Actions', function () { }) it('calls unlockHardwareWalletAccount in background', async function () { - unlockHardwareWalletAccountSpy = sinon.stub(background, 'unlockHardwareWalletAccount') + unlockHardwareWalletAccountSpy = sinon + .stub(background, 'unlockHardwareWalletAccount') .callsArgWith(3, null) const store = mockStore() - await store.dispatch(actions.unlockHardwareWalletAccount('ledger', 0, `m/44'/60'/0'/0`)) + await store.dispatch( + actions.unlockHardwareWalletAccount('ledger', 0, `m/44'/60'/0'/0`), + ) assert.equal(unlockHardwareWalletAccountSpy.calledOnce, true) - }) it('shows loading indicator and displays error', async function () { - unlockHardwareWalletAccountSpy = sinon.stub(background, 'unlockHardwareWalletAccount') + unlockHardwareWalletAccountSpy = sinon + .stub(background, 'unlockHardwareWalletAccount') .callsArgWith(3, new Error('error')) const store = mockStore() @@ -536,7 +558,6 @@ describe('Actions', function () { }) describe('#setCurrentCurrency', function () { - let setCurrentCurrencySpy afterEach(function () { @@ -544,7 +565,8 @@ describe('Actions', function () { }) it('calls setCurrentCurrency', async function () { - setCurrentCurrencySpy = sinon.stub(background, 'setCurrentCurrency') + setCurrentCurrencySpy = sinon + .stub(background, 'setCurrentCurrency') .callsArgWith(1, null, {}) const store = mockStore() @@ -553,7 +575,8 @@ describe('Actions', function () { }) it('throws if setCurrentCurrency throws', async function () { - setCurrentCurrencySpy = sinon.stub(background, 'setCurrentCurrency') + setCurrentCurrencySpy = sinon + .stub(background, 'setCurrentCurrency') .callsArgWith(1, new Error('error')) const store = mockStore() const expectedActions = [ @@ -568,12 +591,12 @@ describe('Actions', function () { }) describe('#signMsg', function () { - let signMessageSpy, metamaskMsgs, msgId, messages const msgParams = { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', - data: '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0', + data: + '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0', } beforeEach(function () { @@ -594,7 +617,6 @@ describe('Actions', function () { signMessageSpy = sinon.spy(background, 'signMessage') await store.dispatch(actions.signMsg(msgParams)) assert(signMessageSpy.calledOnce) - }) it('errors when signMessage in background throws', async function () { @@ -617,16 +639,15 @@ describe('Actions', function () { assert.deepEqual(store.getActions(), expectedActions) } }) - }) describe('#signPersonalMsg', function () { - let signPersonalMessageSpy, metamaskMsgs, msgId, personalMessages const msgParams = { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', - data: '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0', + data: + '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0', } beforeEach(function () { @@ -648,7 +669,6 @@ describe('Actions', function () { await store.dispatch(actions.signPersonalMsg(msgParams)) assert(signPersonalMessageSpy.calledOnce) - }) it('throws if signPersonalMessage throws', async function () { @@ -671,7 +691,6 @@ describe('Actions', function () { assert.deepEqual(store.getActions(), expectedActions) } }) - }) describe('#signTypedMsg', function () { @@ -680,39 +699,39 @@ describe('Actions', function () { const msgParamsV3 = { from: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc', data: JSON.stringify({ - 'types': { - 'EIP712Domain': [ - { 'name': 'name', 'type': 'string' }, - { 'name': 'version', 'type': 'string' }, - { 'name': 'chainId', 'type': 'uint256' }, - { 'name': 'verifyingContract', 'type': 'address' }, + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, ], - 'Person': [ - { 'name': 'name', 'type': 'string' }, - { 'name': 'wallet', 'type': 'address' }, + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, ], - 'Mail': [ - { 'name': 'from', 'type': 'Person' }, - { 'name': 'to', 'type': 'Person' }, - { 'name': 'contents', 'type': 'string' }, + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, ], }, - 'primaryType': 'Mail', - 'domain': { - 'name': 'Ether Mainl', - 'version': '1', - 'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + primaryType: 'Mail', + domain: { + name: 'Ether Mainl', + version: '1', + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', }, - 'message': { - 'from': { - 'name': 'Cow', - 'wallet': '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', }, - 'to': { - 'name': 'Bob', - 'wallet': '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', }, - 'contents': 'Hello, Bob!', + contents: 'Hello, Bob!', }, }), } @@ -730,7 +749,8 @@ describe('Actions', function () { }) it('calls signTypedMsg in background with no error', async function () { - signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') + signTypedMsgSpy = sinon + .stub(background, 'signTypedMessage') .callsArgWith(1, null, defaultState) const store = mockStore() @@ -739,7 +759,8 @@ describe('Actions', function () { }) it('returns expected actions with error', async function () { - signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') + signTypedMsgSpy = sinon + .stub(background, 'signTypedMessage') .callsArgWith(1, new Error('error')) const store = mockStore() const expectedActions = [ @@ -755,11 +776,9 @@ describe('Actions', function () { assert.deepEqual(store.getActions(), expectedActions) } }) - }) describe('#signTx', function () { - let sendTransactionSpy beforeEach(function () { @@ -798,7 +817,10 @@ describe('Actions', function () { const expectedActions = [ { type: 'GAS_LOADING_STARTED' }, - { type: 'UPDATE_SEND_ERRORS', value: { gasLoadingError: 'gasLoadingError' } }, + { + type: 'UPDATE_SEND_ERRORS', + value: { gasLoadingError: 'gasLoadingError' }, + }, { type: 'GAS_LOADING_FINISHED' }, ] @@ -858,18 +880,22 @@ describe('Actions', function () { }) describe('#updateTransaction', function () { - let updateTransactionSpy const txParams = { - 'from': '0x1', - 'gas': '0x5208', - 'gasPrice': '0x3b9aca00', - 'to': '0x2', - 'value': '0x0', + from: '0x1', + gas: '0x5208', + gasPrice: '0x3b9aca00', + to: '0x2', + value: '0x0', } - const txData = { id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams } + const txData = { + id: '1', + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: currentNetworkId, + txParams, + } beforeEach(async function () { await metamaskController.txController.txStateManager.addTx(txData) @@ -888,7 +914,11 @@ describe('Actions', function () { const resultantActions = store.getActions() assert.ok(updateTransactionSpy.calledOnce) - assert.deepEqual(resultantActions[1], { type: 'UPDATE_TRANSACTION_PARAMS', id: txData.id, value: txParams }) + assert.deepEqual(resultantActions[1], { + type: 'UPDATE_TRANSACTION_PARAMS', + id: txData.id, + value: txParams, + }) }) it('rejects with error message', async function () { @@ -944,7 +974,6 @@ describe('Actions', function () { } catch (error) { assert.deepEqual(store.getActions(), expectedActions) } - }) }) @@ -956,16 +985,22 @@ describe('Actions', function () { }) it('calls setSelectedAddress in background', async function () { - setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress') + setSelectedAddressSpy = sinon + .stub(background, 'setSelectedAddress') .callsArgWith(1, null) const store = mockStore() - await store.dispatch(actions.setSelectedAddress('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) + await store.dispatch( + actions.setSelectedAddress( + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + ), + ) assert(setSelectedAddressSpy.calledOnce) }) it('errors when setSelectedAddress throws', async function () { - setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress') + setSelectedAddressSpy = sinon + .stub(background, 'setSelectedAddress') .callsArgWith(1, new Error('error')) const store = mockStore() const expectedActions = [ @@ -976,7 +1011,6 @@ describe('Actions', function () { await store.dispatch(actions.setSelectedAddress()) assert.deepEqual(store.getActions(), expectedActions) - }) }) @@ -988,18 +1022,26 @@ describe('Actions', function () { }) it('#showAccountDetail', async function () { - setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress') + setSelectedAddressSpy = sinon + .stub(background, 'setSelectedAddress') .callsArgWith(1, null) - const store = mockStore({ activeTab: {}, metamask: { alertEnabledness: {}, selectedAddress: '0x123' } }) + const store = mockStore({ + activeTab: {}, + metamask: { alertEnabledness: {}, selectedAddress: '0x123' }, + }) await store.dispatch(actions.showAccountDetail()) assert(setSelectedAddressSpy.calledOnce) }) it('displays warning if setSelectedAddress throws', async function () { - setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress') + setSelectedAddressSpy = sinon + .stub(background, 'setSelectedAddress') .callsArgWith(1, new Error('error')) - const store = mockStore({ activeTab: {}, metamask: { alertEnabledness: {}, selectedAddress: '0x123' } }) + const store = mockStore({ + activeTab: {}, + metamask: { alertEnabledness: {}, selectedAddress: '0x123' }, + }) const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, @@ -1019,8 +1061,7 @@ describe('Actions', function () { }) it('calls addToken in background', async function () { - addTokenSpy = sinon.stub(background, 'addToken') - .callsArgWith(4, null) + addTokenSpy = sinon.stub(background, 'addToken').callsArgWith(4, null) const store = mockStore() await store.dispatch(actions.addToken()) @@ -1028,7 +1069,8 @@ describe('Actions', function () { }) it('errors when addToken in background throws', async function () { - addTokenSpy = sinon.stub(background, 'addToken') + addTokenSpy = sinon + .stub(background, 'addToken') .callsArgWith(4, new Error('error')) const store = mockStore() const expectedActions = [ @@ -1047,7 +1089,6 @@ describe('Actions', function () { }) describe('#removeToken', function () { - let removeTokenSpy afterEach(function () { @@ -1055,7 +1096,8 @@ describe('Actions', function () { }) it('calls removeToken in background', async function () { - removeTokenSpy = sinon.stub(background, 'removeToken') + removeTokenSpy = sinon + .stub(background, 'removeToken') .callsArgWith(1, null) const store = mockStore() await store.dispatch(actions.removeToken()) @@ -1063,7 +1105,8 @@ describe('Actions', function () { }) it('errors when removeToken in background fails', async function () { - removeTokenSpy = sinon.stub(background, 'removeToken') + removeTokenSpy = sinon + .stub(background, 'removeToken') .callsArgWith(1, new Error('error')) const store = mockStore() const expectedActions = [ @@ -1094,14 +1137,16 @@ describe('Actions', function () { }) it('calls setProviderType', async function () { - setProviderTypeSpy = sinon.stub(background, 'setProviderType') + setProviderTypeSpy = sinon + .stub(background, 'setProviderType') .callsArgWith(1, null) await store.dispatch(actions.setProviderType()) assert(setProviderTypeSpy.calledOnce) }) it('displays warning when setProviderType throws', async function () { - setProviderTypeSpy = sinon.stub(background, 'setProviderType') + setProviderTypeSpy = sinon + .stub(background, 'setProviderType') .callsArgWith(1, new Error('error')) const expectedActions = [ { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' }, @@ -1121,7 +1166,8 @@ describe('Actions', function () { }) it('calls setRpcTarget', async function () { - setRpcTargetSpy = sinon.stub(background, 'setCustomRpc') + setRpcTargetSpy = sinon + .stub(background, 'setCustomRpc') .callsArgWith(4, null) const store = mockStore() await store.dispatch(actions.setRpcTarget('http://localhost:8545')) @@ -1129,7 +1175,8 @@ describe('Actions', function () { }) it('displays warning when setRpcTarget throws', async function () { - setRpcTargetSpy = sinon.stub(background, 'setCustomRpc') + setRpcTargetSpy = sinon + .stub(background, 'setCustomRpc') .callsArgWith(4, new Error('error')) const store = mockStore() const expectedActions = [ @@ -1143,7 +1190,8 @@ describe('Actions', function () { describe('#addToAddressBook', function () { it('calls setAddressBook', async function () { - const addToAddressBookSpy = sinon.stub(background, 'setAddressBook') + const addToAddressBookSpy = sinon + .stub(background, 'setAddressBook') .callsArgWith(4, null, true) const store = mockStore() await store.dispatch(actions.addToAddressBook('test')) @@ -1165,13 +1213,22 @@ describe('Actions', function () { const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, - { type: 'SHOW_PRIVATE_KEY', value: '7ec73b91bb20f209a7ff2d32f542c3420b4fccf14abcc7840d2eff0ebcb18505' }, + { + type: 'SHOW_PRIVATE_KEY', + value: + '7ec73b91bb20f209a7ff2d32f542c3420b4fccf14abcc7840d2eff0ebcb18505', + }, ] verifyPasswordSpy = sinon.spy(background, 'verifyPassword') exportAccountSpy = sinon.spy(background, 'exportAccount') - await store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) + await store.dispatch( + actions.exportAccount( + password, + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + ), + ) assert(verifyPasswordSpy.calledOnce) assert(exportAccountSpy.calledOnce) assert.deepEqual(store.getActions(), expectedActions) @@ -1191,7 +1248,12 @@ describe('Actions', function () { }) try { - await store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) + await store.dispatch( + actions.exportAccount( + password, + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + ), + ) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) @@ -1203,7 +1265,10 @@ describe('Actions', function () { const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, - { type: 'DISPLAY_WARNING', value: 'Had a problem exporting the account.' }, + { + type: 'DISPLAY_WARNING', + value: 'Had a problem exporting the account.', + }, ] exportAccountSpy = sinon.stub(background, 'exportAccount') @@ -1212,7 +1277,12 @@ describe('Actions', function () { }) try { - await store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) + await store.dispatch( + actions.exportAccount( + password, + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + ), + ) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) @@ -1222,10 +1292,16 @@ describe('Actions', function () { describe('#setAccountLabel', function () { it('calls setAccountLabel', async function () { - const setAccountLabelSpy = sinon.stub(background, 'setAccountLabel') + const setAccountLabelSpy = sinon + .stub(background, 'setAccountLabel') .callsArgWith(2, null) const store = mockStore() - await store.dispatch(actions.setAccountLabel('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', 'test')) + await store.dispatch( + actions.setAccountLabel( + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + 'test', + ), + ) assert(setAccountLabelSpy.calledOnce) }) }) @@ -1238,7 +1314,8 @@ describe('Actions', function () { }) it('calls setFeatureFlag in the background', async function () { - setFeatureFlagSpy = sinon.stub(background, 'setFeatureFlag') + setFeatureFlagSpy = sinon + .stub(background, 'setFeatureFlag') .callsArgWith(2, null) const store = mockStore() @@ -1247,7 +1324,8 @@ describe('Actions', function () { }) it('errors when setFeatureFlag in background throws', async function () { - setFeatureFlagSpy = sinon.stub(background, 'setFeatureFlag') + setFeatureFlagSpy = sinon + .stub(background, 'setFeatureFlag') .callsArgWith(2, new Error('error')) const store = mockStore() const expectedActions = [ @@ -1316,10 +1394,9 @@ describe('Actions', function () { let setCurrentLocaleSpy beforeEach(function () { - sinon.stub(window, 'fetch') - .resolves({ - json: async () => enLocale, - }) + sinon.stub(window, 'fetch').resolves({ + json: async () => enLocale, + }) }) afterEach(function () { @@ -1333,7 +1410,10 @@ describe('Actions', function () { const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, - { type: 'SET_CURRENT_LOCALE', value: { locale: 'en', messages: enLocale } }, + { + type: 'SET_CURRENT_LOCALE', + value: { locale: 'en', messages: enLocale }, + }, { type: 'HIDE_LOADING_INDICATION' }, ] @@ -1360,19 +1440,23 @@ describe('Actions', function () { } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } - }) }) describe('#markPasswordForgotten', function () { it('calls markPasswordForgotten', async function () { const store = mockStore() - const markPasswordForgottenSpy = sinon.stub(background, 'markPasswordForgotten').callsArg(0) + const markPasswordForgottenSpy = sinon + .stub(background, 'markPasswordForgotten') + .callsArg(0) await store.dispatch(actions.markPasswordForgotten()) const resultantActions = store.getActions() - assert.deepEqual(resultantActions[1], { type: 'FORGOT_PASSWORD', value: true }) + assert.deepEqual(resultantActions[1], { + type: 'FORGOT_PASSWORD', + value: true, + }) assert.ok(markPasswordForgottenSpy.calledOnce) markPasswordForgottenSpy.restore() }) @@ -1381,12 +1465,17 @@ describe('Actions', function () { describe('#unMarkPasswordForgotten', function () { it('calls unMarkPasswordForgotten', async function () { const store = mockStore() - const unMarkPasswordForgottenSpy = sinon.stub(background, 'unMarkPasswordForgotten').callsArg(0) + const unMarkPasswordForgottenSpy = sinon + .stub(background, 'unMarkPasswordForgotten') + .callsArg(0) await store.dispatch(actions.unMarkPasswordForgotten()) const resultantActions = store.getActions() - assert.deepEqual(resultantActions[0], { type: 'FORGOT_PASSWORD', value: false }) + assert.deepEqual(resultantActions[0], { + type: 'FORGOT_PASSWORD', + value: false, + }) assert.ok(unMarkPasswordForgottenSpy.calledOnce) unMarkPasswordForgottenSpy.restore() }) diff --git a/test/unit/ui/app/reducers/app.spec.js b/test/unit/ui/app/reducers/app.spec.js index 7ba811cc3..3cf350b7f 100644 --- a/test/unit/ui/app/reducers/app.spec.js +++ b/test/unit/ui/app/reducers/app.spec.js @@ -5,7 +5,6 @@ import * as actionConstants from '../../../../../ui/app/store/actionConstants' const actions = actionConstants describe('App State', function () { - const metamaskState = { selectedAddress: '0xAddress', identities: { @@ -42,9 +41,9 @@ describe('App State', function () { it('opens sidebar', function () { const value = { - 'transitionName': 'sidebar-right', - 'type': 'wallet-view', - 'isOpen': true, + transitionName: 'sidebar-right', + type: 'wallet-view', + isOpen: true, } const state = reduceApp(metamaskState, { type: actions.SIDEBAR_OPEN, @@ -151,7 +150,6 @@ describe('App State', function () { assert.equal(state.accountDetail.accountExport, 'none') assert.equal(state.accountDetail.privateKey, '') assert.equal(state.warning, null) - }) it('shows account detail', function () { @@ -163,7 +161,6 @@ describe('App State', function () { assert.equal(state.accountDetail.subview, 'transactions') // default assert.equal(state.accountDetail.accountExport, 'none') // default assert.equal(state.accountDetail.privateKey, '') // default - }) it('clears account details', function () { @@ -214,7 +211,6 @@ describe('App State', function () { assert.equal(state.txId, 2) assert.equal(state.warning, null) assert.equal(state.isLoading, false) - }) it('completes tx continues to show pending txs current view context', function () { diff --git a/test/unit/ui/app/reducers/metamask.spec.js b/test/unit/ui/app/reducers/metamask.spec.js index f662d8173..c36ad1bb9 100644 --- a/test/unit/ui/app/reducers/metamask.spec.js +++ b/test/unit/ui/app/reducers/metamask.spec.js @@ -3,7 +3,6 @@ import reduceMetamask from '../../../../../ui/app/ducks/metamask/metamask' import * as actionConstants from '../../../../../ui/app/store/actionConstants' describe('MetaMask Reducers', function () { - it('init state', function () { const initState = reduceMetamask(undefined, {}) assert(initState) @@ -22,29 +21,37 @@ describe('MetaMask Reducers', function () { }) it('sets rpc target', function () { - const state = reduceMetamask({}, { - type: actionConstants.SET_RPC_TARGET, - value: 'https://custom.rpc', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.SET_RPC_TARGET, + value: 'https://custom.rpc', + }, + ) assert.equal(state.provider.rpcUrl, 'https://custom.rpc') }) it('sets provider type', function () { - const state = reduceMetamask({}, { - type: actionConstants.SET_PROVIDER_TYPE, - value: 'provider type', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.SET_PROVIDER_TYPE, + value: 'provider type', + }, + ) assert.equal(state.provider.type, 'provider type') }) it('shows account detail', function () { - - const state = reduceMetamask({}, { - type: actionConstants.SHOW_ACCOUNT_DETAIL, - value: 'test address', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.SHOW_ACCOUNT_DETAIL, + value: 'test address', + }, + ) assert.equal(state.isUnlocked, true) assert.equal(state.isInitialized, true) @@ -52,15 +59,20 @@ describe('MetaMask Reducers', function () { }) it('sets account label', function () { - const state = reduceMetamask({}, { - type: actionConstants.SET_ACCOUNT_LABEL, - value: { - account: 'test account', - label: 'test label', + const state = reduceMetamask( + {}, + { + type: actionConstants.SET_ACCOUNT_LABEL, + value: { + account: 'test account', + label: 'test label', + }, }, - }) + ) - assert.deepEqual(state.identities, { 'test account': { name: 'test label' } }) + assert.deepEqual(state.identities, { + 'test account': { name: 'test label' }, + }) }) it('sets current fiat', function () { @@ -70,10 +82,13 @@ describe('MetaMask Reducers', function () { conversionDate: new Date(2018, 9), } - const state = reduceMetamask({}, { - type: actionConstants.SET_CURRENT_FIAT, - value, - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.SET_CURRENT_FIAT, + value, + }, + ) assert.equal(state.currentCurrency, value.currentCurrency) assert.equal(state.conversionRate, value.conversionRate) @@ -82,100 +97,129 @@ describe('MetaMask Reducers', function () { it('updates tokens', function () { const newTokens = { - 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', - 'decimals': 18, - 'symbol': 'META', + address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + decimals: 18, + symbol: 'META', } - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_TOKENS, - newTokens, - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_TOKENS, + newTokens, + }, + ) assert.deepEqual(state.tokens, newTokens) }) it('updates send gas limit', function () { - - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_GAS_LIMIT, - value: '0xGasLimit', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_GAS_LIMIT, + value: '0xGasLimit', + }, + ) assert.equal(state.send.gasLimit, '0xGasLimit') }) it('updates send gas price', function () { - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_GAS_PRICE, - value: '0xGasPrice', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_GAS_PRICE, + value: '0xGasPrice', + }, + ) assert.equal(state.send.gasPrice, '0xGasPrice') }) it('toggles account menu ', function () { - const state = reduceMetamask({}, { - type: actionConstants.TOGGLE_ACCOUNT_MENU, - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.TOGGLE_ACCOUNT_MENU, + }, + ) assert.equal(state.isAccountMenuOpen, true) }) it('updates gas total', function () { - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_GAS_TOTAL, - value: '0xGasTotal', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_GAS_TOTAL, + value: '0xGasTotal', + }, + ) assert.equal(state.send.gasTotal, '0xGasTotal') }) it('updates send token balance', function () { - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_SEND_TOKEN_BALANCE, - value: '0xTokenBalance', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_SEND_TOKEN_BALANCE, + value: '0xTokenBalance', + }, + ) assert.equal(state.send.tokenBalance, '0xTokenBalance') }) it('updates data', function () { - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_SEND_HEX_DATA, - value: '0xData', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_SEND_HEX_DATA, + value: '0xData', + }, + ) assert.equal(state.send.data, '0xData') }) it('updates send to', function () { - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_SEND_TO, - value: { - to: '0xAddress', - nickname: 'nickname', + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_SEND_TO, + value: { + to: '0xAddress', + nickname: 'nickname', + }, }, - }) + ) assert.equal(state.send.to, '0xAddress') assert.equal(state.send.toNickname, 'nickname') }) it('update send amount', function () { - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_SEND_AMOUNT, - value: '0xAmount', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_SEND_AMOUNT, + value: '0xAmount', + }, + ) assert.equal(state.send.amount, '0xAmount') }) it('updates max mode', function () { - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_MAX_MODE, - value: true, - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_MAX_MODE, + value: true, + }, + ) assert.equal(state.send.maxModeOn, true) }) @@ -198,18 +242,20 @@ describe('MetaMask Reducers', function () { ensResolutionError: '', } - const sendState = reduceMetamask({}, { - type: actionConstants.UPDATE_SEND, - value, - }) + const sendState = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_SEND, + value, + }, + ) assert.deepEqual(sendState.send, value) }) it('clears send', function () { const initStateSend = { - send: - { + send: { gasLimit: null, gasPrice: null, gasTotal: null, @@ -269,62 +315,77 @@ describe('MetaMask Reducers', function () { }) it('sets blockies', function () { - const state = reduceMetamask({}, { - type: actionConstants.SET_USE_BLOCKIE, - value: true, - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.SET_USE_BLOCKIE, + value: true, + }, + ) assert.equal(state.useBlockie, true) }) it('updates an arbitrary feature flag', function () { - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_FEATURE_FLAGS, - value: { - foo: true, + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_FEATURE_FLAGS, + value: { + foo: true, + }, }, - }) + ) assert.equal(state.featureFlags.foo, true) }) it('close welcome screen', function () { - const state = reduceMetamask({}, { - type: actionConstants.CLOSE_WELCOME_SCREEN, - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.CLOSE_WELCOME_SCREEN, + }, + ) assert.equal(state.welcomeScreenSeen, true) }) it('sets current locale', function () { - const state = reduceMetamask({}, { - type: actionConstants.SET_CURRENT_LOCALE, - value: { locale: 'ge' }, - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.SET_CURRENT_LOCALE, + value: { locale: 'ge' }, + }, + ) assert.equal(state.currentLocale, 'ge') }) it('sets pending tokens ', function () { const payload = { - 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', - 'decimals': 18, - 'symbol': 'META', + address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + decimals: 18, + symbol: 'META', } - const pendingTokensState = reduceMetamask({}, { - type: actionConstants.SET_PENDING_TOKENS, - payload, - }) + const pendingTokensState = reduceMetamask( + {}, + { + type: actionConstants.SET_PENDING_TOKENS, + payload, + }, + ) assert.deepEqual(pendingTokensState.pendingTokens, payload) }) it('clears pending tokens', function () { const payload = { - 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', - 'decimals': 18, - 'symbol': 'META', + address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + decimals: 18, + symbol: 'META', } const pendingTokensState = { @@ -339,20 +400,26 @@ describe('MetaMask Reducers', function () { }) it('update ensResolution', function () { - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_SEND_ENS_RESOLUTION, - payload: '0x1337', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_SEND_ENS_RESOLUTION, + payload: '0x1337', + }, + ) assert.deepEqual(state.send.ensResolution, '0x1337') assert.deepEqual(state.send.ensResolutionError, '') }) it('update ensResolutionError', function () { - const state = reduceMetamask({}, { - type: actionConstants.UPDATE_SEND_ENS_RESOLUTION_ERROR, - payload: 'ens name not found', - }) + const state = reduceMetamask( + {}, + { + type: actionConstants.UPDATE_SEND_ENS_RESOLUTION_ERROR, + payload: 'ens name not found', + }, + ) assert.deepEqual(state.send.ensResolutionError, 'ens name not found') assert.deepEqual(state.send.ensResolution, null) diff --git a/test/unit/ui/etherscan-prefix-for-network.spec.js b/test/unit/ui/etherscan-prefix-for-network.spec.js index 4b0867678..1b3ce7b34 100644 --- a/test/unit/ui/etherscan-prefix-for-network.spec.js +++ b/test/unit/ui/etherscan-prefix-for-network.spec.js @@ -2,7 +2,6 @@ import assert from 'assert' import { getEtherscanNetworkPrefix } from '../../../ui/lib/etherscan-prefix-for-network' describe('Etherscan Network Prefix', function () { - it('returns empty string as default value', function () { assert.equal(getEtherscanNetworkPrefix(), '') }) @@ -26,5 +25,4 @@ describe('Etherscan Network Prefix', function () { it('returs goerli as prefix for networkId of 5', function () { assert.equal(getEtherscanNetworkPrefix('5'), 'goerli.') }) - }) diff --git a/ui/app/components/app/account-list-item/account-list-item.js b/ui/app/components/app/account-list-item/account-list-item.js index 7e001f7d6..362c740d0 100644 --- a/ui/app/components/app/account-list-item/account-list-item.js +++ b/ui/app/components/app/account-list-item/account-list-item.js @@ -4,7 +4,7 @@ import { checksumAddress } from '../../../helpers/utils/util' import Identicon from '../../ui/identicon' import AccountMismatchWarning from '../../ui/account-mismatch-warning/account-mismatch-warning.component' -export default function AccountListItem ({ +export default function AccountListItem({ account, className, displayAddress = false, @@ -16,9 +16,8 @@ export default function AccountListItem ({ return (
    handleClick && handleClick({ name, address, balance })} + onClick={() => handleClick?.({ name, address, balance })} > -
    -
    { name || address }
    +
    {name || address}
    - {icon &&
    { icon }
    } + {icon &&
    {icon}
    }
    {displayAddress && name && (
    - { checksumAddress(address) } + {checksumAddress(address)}
    )}
    diff --git a/ui/app/components/app/account-list-item/index.scss b/ui/app/components/app/account-list-item/index.scss index dcc08d9b5..3faebe29f 100644 --- a/ui/app/components/app/account-list-item/index.scss +++ b/ui/app/components/app/account-list-item/index.scss @@ -7,7 +7,8 @@ } &__account-name { - font-size: 16px; + @include Paragraph; + margin-left: 8px; } diff --git a/ui/app/components/app/account-list-item/tests/account-list-item-component.test.js b/ui/app/components/app/account-list-item/tests/account-list-item-component.test.js index d42bd7bc9..4c65e3b70 100644 --- a/ui/app/components/app/account-list-item/tests/account-list-item-component.test.js +++ b/ui/app/components/app/account-list-item/tests/account-list-item-component.test.js @@ -11,21 +11,28 @@ describe('AccountListItem Component', function () { describe('render', function () { before(function () { - checksumAddressStub = sinon.stub(utils, 'checksumAddress').returns('mockCheckSumAddress') + checksumAddressStub = sinon + .stub(utils, 'checksumAddress') + .returns('mockCheckSumAddress') propsMethodSpies = { handleClick: sinon.spy(), } }) beforeEach(function () { - wrapper = shallow(( + wrapper = shallow( } - /> - ), { context: { t: (str) => `${str}_t` } }) + />, + { context: { t: (str) => `${str}_t` } }, + ) }) afterEach(function () { @@ -48,65 +55,96 @@ describe('AccountListItem Component', function () { assert.equal(propsMethodSpies.handleClick.callCount, 0) onClick() assert.equal(propsMethodSpies.handleClick.callCount, 1) - assert.deepEqual( - propsMethodSpies.handleClick.getCall(0).args, - [{ address: 'mockAddress', name: 'mockName', balance: 'mockBalance' }], - ) + assert.deepEqual(propsMethodSpies.handleClick.getCall(0).args, [ + { address: 'mockAddress', name: 'mockName', balance: 'mockBalance' }, + ]) }) it('should have a top row div', function () { - assert.equal(wrapper.find('.mockClassName > .account-list-item__top-row').length, 1) - assert(wrapper.find('.mockClassName > .account-list-item__top-row').is('div')) + assert.equal( + wrapper.find('.mockClassName > .account-list-item__top-row').length, + 1, + ) + assert( + wrapper.find('.mockClassName > .account-list-item__top-row').is('div'), + ) }) it('should have an identicon, name and icon in the top row', function () { - const topRow = wrapper.find('.mockClassName > .account-list-item__top-row') + const topRow = wrapper.find( + '.mockClassName > .account-list-item__top-row', + ) assert.equal(topRow.find(Identicon).length, 1) assert.equal(topRow.find('.account-list-item__account-name').length, 1) assert.equal(topRow.find('.account-list-item__icon').length, 1) }) it('should show the account name if it exists', function () { - const topRow = wrapper.find('.mockClassName > .account-list-item__top-row') - assert.equal(topRow.find('.account-list-item__account-name').text(), 'mockName') + const topRow = wrapper.find( + '.mockClassName > .account-list-item__top-row', + ) + assert.equal( + topRow.find('.account-list-item__account-name').text(), + 'mockName', + ) }) it('should show the account address if there is no name', function () { wrapper.setProps({ account: { address: 'addressButNoName' } }) - const topRow = wrapper.find('.mockClassName > .account-list-item__top-row') - assert.equal(topRow.find('.account-list-item__account-name').text(), 'addressButNoName') + const topRow = wrapper.find( + '.mockClassName > .account-list-item__top-row', + ) + assert.equal( + topRow.find('.account-list-item__account-name').text(), + 'addressButNoName', + ) }) it('should render the passed icon', function () { - const topRow = wrapper.find('.mockClassName > .account-list-item__top-row') + const topRow = wrapper.find( + '.mockClassName > .account-list-item__top-row', + ) assert(topRow.find('.account-list-item__icon').childAt(0).is('i')) - assert(topRow.find('.account-list-item__icon').childAt(0).hasClass('mockIcon')) + assert( + topRow.find('.account-list-item__icon').childAt(0).hasClass('mockIcon'), + ) }) it('should not render an icon if none is passed', function () { wrapper.setProps({ icon: null }) - const topRow = wrapper.find('.mockClassName > .account-list-item__top-row') + const topRow = wrapper.find( + '.mockClassName > .account-list-item__top-row', + ) assert.equal(topRow.find('.account-list-item__icon').length, 0) }) it('should render the account address as a checksumAddress if displayAddress is true and name is provided', function () { wrapper.setProps({ displayAddress: true }) - assert.equal(wrapper.find('.account-list-item__account-address').length, 1) - assert.equal(wrapper.find('.account-list-item__account-address').text(), 'mockCheckSumAddress') - assert.deepEqual( - checksumAddressStub.getCall(0).args, - ['mockAddress'], + assert.equal( + wrapper.find('.account-list-item__account-address').length, + 1, ) + assert.equal( + wrapper.find('.account-list-item__account-address').text(), + 'mockCheckSumAddress', + ) + assert.deepEqual(checksumAddressStub.getCall(0).args, ['mockAddress']) }) it('should not render the account address as a checksumAddress if displayAddress is false', function () { wrapper.setProps({ displayAddress: false }) - assert.equal(wrapper.find('.account-list-item__account-address').length, 0) + assert.equal( + wrapper.find('.account-list-item__account-address').length, + 0, + ) }) it('should not render the account address as a checksumAddress if name is not provided', function () { wrapper.setProps({ account: { address: 'someAddressButNoName' } }) - assert.equal(wrapper.find('.account-list-item__account-address').length, 0) + assert.equal( + wrapper.find('.account-list-item__account-address').length, + 0, + ) }) }) }) diff --git a/ui/app/components/app/account-menu/account-menu.component.js b/ui/app/components/app/account-menu/account-menu.component.js index b4f5a2b32..b6739f08f 100644 --- a/ui/app/components/app/account-menu/account-menu.component.js +++ b/ui/app/components/app/account-menu/account-menu.component.js @@ -21,31 +21,25 @@ import { import TextField from '../../ui/text-field' import SearchIcon from '../../ui/search-icon' -export function AccountMenuItem (props) { - const { - icon, - children, - text, - subText, - className, - onClick, - } = props +export function AccountMenuItem(props) { + const { icon, children, text, subText, className, onClick } = props const itemClassName = classnames('account-menu__item', className, { 'account-menu__item--clickable': Boolean(onClick), }) - return children - ?
    {children}
    - : ( -
    - {icon ?
    {icon}
    : null} - {text ?
    {text}
    : null} - {subText ?
    {subText}
    : null} -
    - ) + return children ? ( +
    + {children} +
    + ) : ( +
    + {icon ?
    {icon}
    : null} + {text ?
    {text}
    : null} + {subText ? ( +
    {subText}
    + ) : null} +
    + ) } AccountMenuItem.propTypes = { @@ -97,7 +91,7 @@ export default class AccountMenu extends Component { ], }) - componentDidUpdate (prevProps, prevState) { + componentDidUpdate(prevProps, prevState) { const { isAccountMenuOpen: prevIsAccountMenuOpen } = prevProps const { searchQuery: prevSearchQuery } = prevState const { isAccountMenuOpen } = this.props @@ -115,7 +109,7 @@ export default class AccountMenu extends Component { } } - renderAccountsSearch () { + renderAccountsSearch() { const inputAdornment = ( {this.context.t('noAccountsFound')}

    + return ( +

    + {this.context.t('noAccountsFound')} +

    + ) } return filteredIdentities.map((identity) => { @@ -172,7 +170,10 @@ export default class AccountMenu extends Component { const simpleAddress = identity.address.substring(2).toLowerCase() const keyring = keyrings.find((kr) => { - return kr.accounts.includes(simpleAddress) || kr.accounts.includes(identity.address) + return ( + kr.accounts.includes(simpleAddress) || + kr.accounts.includes(identity.address) + ) }) const addressDomains = addressConnectedDomainMap[identity.address] || {} const iconAndNameForOpenDomain = addressDomains[originOfCurrentTab] @@ -193,37 +194,33 @@ export default class AccountMenu extends Component { key={identity.address} >
    - { isSelected &&
    } + {isSelected &&
    }
    - +
    -
    - { identity.name || '' } -
    +
    {identity.name || ''}
    - { this.renderKeyringType(keyring) } - { iconAndNameForOpenDomain - ? ( -
    - -
    - ) - : null - } + {this.renderKeyringType(keyring)} + {iconAndNameForOpenDomain ? ( +
    + +
    + ) : null}
    ) }) } - renderKeyringType (keyring) { + renderKeyringType(keyring) { const { t } = this.context // Sometimes keyrings aren't loaded yet @@ -246,18 +243,14 @@ export default class AccountMenu extends Component { return null } - return ( -
    - { label } -
    - ) + return
    {label}
    } - resetSearchQuery () { + resetSearchQuery() { this.setSearchQuery('') } - setSearchQuery (searchQuery) { + setSearchQuery(searchQuery) { this.setState({ searchQuery }) } @@ -284,25 +277,27 @@ export default class AccountMenu extends Component { this.setShouldShowScrollButton() } - renderScrollButton () { + renderScrollButton() { const { shouldShowScrollButton } = this.state - return shouldShowScrollButton && ( -
    - scroll down -
    + return ( + shouldShowScrollButton && ( +
    + scroll down +
    + ) ) } - render () { + render() { const { t, metricsEvent } = this.context const { shouldShowAccountsSearch, @@ -317,12 +312,10 @@ export default class AccountMenu extends Component { } return ( -
    +
    - { t('myAccounts') } + {t('myAccounts')}
    @@ -343,9 +336,9 @@ export default class AccountMenu extends Component { this.accountsRef = ref }} > - { this.renderAccounts() } + {this.renderAccounts()}
    - { this.renderScrollButton() } + {this.renderScrollButton()}
    - )} + } text={t('createAccount')} /> - )} + } text={t('importAccount')} /> - )} + } text={t('connectHardwareWallet')} />
    @@ -418,9 +411,7 @@ export default class AccountMenu extends Component { toggleAccountMenu() history.push(ABOUT_US_ROUTE) }} - icon={ - - } + icon={} text={t('infoHelp')} /> - )} + } text={t('settings')} />
    diff --git a/ui/app/components/app/account-menu/account-menu.container.js b/ui/app/components/app/account-menu/account-menu.container.js index c08537264..2ce69b256 100644 --- a/ui/app/components/app/account-menu/account-menu.container.js +++ b/ui/app/components/app/account-menu/account-menu.container.js @@ -22,8 +22,10 @@ import AccountMenu from './account-menu.component' */ const SHOW_SEARCH_ACCOUNTS_MIN_COUNT = 5 -function mapStateToProps (state) { - const { metamask: { isAccountMenuOpen } } = state +function mapStateToProps(state) { + const { + metamask: { isAccountMenuOpen }, + } = state const accounts = getMetaMaskAccountsOrdered(state) const origin = getOriginOfCurrentTab(state) const selectedAddress = getSelectedAddress(state) @@ -39,7 +41,7 @@ function mapStateToProps (state) { } } -function mapDispatchToProps (dispatch) { +function mapDispatchToProps(dispatch) { return { toggleAccountMenu: () => dispatch(toggleAccountMenu()), showAccountDetail: (address) => { diff --git a/ui/app/components/app/account-menu/index.scss b/ui/app/components/app/account-menu/index.scss index e7763766f..ee3e82bc0 100644 --- a/ui/app/components/app/account-menu/index.scss +++ b/ui/app/components/app/account-menu/index.scss @@ -56,12 +56,12 @@ } &__text { - font-size: 16px; - line-height: 21px; + @include Paragraph; } &__subtext { - font-size: 12px; + @include H7; + padding: 5px 0 0 30px; } } @@ -98,13 +98,13 @@ } &__lock-button { + @include H7; + border: 1px solid $dusty-gray; background-color: transparent; color: $white; border-radius: 4px; - font-size: 12px; - line-height: 23px; - padding: 0 24px; + padding: 3.5px 24px; } &__item-icon { @@ -138,9 +138,9 @@ } .keyring-label { + @include H9; + z-index: 1; - font-size: 8px; - line-height: 8px; border-radius: 10px; padding: 4px; text-align: center; @@ -151,11 +151,14 @@ color: $black; font-weight: normal; letter-spacing: 0.5px; + display: flex; + align-items: center; } } &__no-accounts { - font-size: 0.8em; + @include H6; + padding: 16px 14px; } @@ -215,8 +218,9 @@ } &__name { + @include H4; + color: $white; - font-size: 18px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; @@ -224,13 +228,14 @@ } &__balance { + @include H6; + color: $dusty-gray; - font-size: 14px; } &__action { - font-size: 16px; - line-height: 18px; + @include Paragraph; + cursor: pointer; } diff --git a/ui/app/components/app/account-menu/tests/account-menu.test.js b/ui/app/components/app/account-menu/tests/account-menu.test.js index 6c4fd3a72..70dba281f 100644 --- a/ui/app/components/app/account-menu/tests/account-menu.test.js +++ b/ui/app/components/app/account-menu/tests/account-menu.test.js @@ -7,7 +7,6 @@ import { mountWithRouter } from '../../../../../../test/lib/render-helpers' import AccountMenu from '..' describe('Account Menu', function () { - let wrapper const mockStore = { @@ -41,15 +40,11 @@ describe('Account Menu', function () { keyrings: [ { type: 'HD Key Tree', - accounts: [ - '0xAdress', - ], + accounts: ['0xAdress'], }, { type: 'Simple Key Pair', - accounts: [ - '0xImportedAddress', - ], + accounts: ['0xImportedAddress'], }, ], prevIsAccountMenuOpen: false, @@ -60,14 +55,14 @@ describe('Account Menu', function () { history: { push: sinon.spy(), }, - } before(function () { wrapper = mountWithRouter( - , store, + , + store, ) }) @@ -83,12 +78,16 @@ describe('Account Menu', function () { }) it('renders user preference currency display balance from account balance', function () { - const accountBalance = wrapper.find('.currency-display-component.account-menu__balance') + const accountBalance = wrapper.find( + '.currency-display-component.account-menu__balance', + ) assert.equal(accountBalance.length, 2) }) it('simulate click', function () { - const click = wrapper.find('.account-menu__account.account-menu__item--clickable') + const click = wrapper.find( + '.account-menu__account.account-menu__item--clickable', + ) click.first().simulate('click') assert(props.showAccountDetail.calledOnce) @@ -147,7 +146,6 @@ describe('Account Menu', function () { }) describe('Connect Hardware Wallet', function () { - let connectHardwareWallet it('renders import account item', function () { @@ -158,12 +156,14 @@ describe('Account Menu', function () { it('calls toggle menu and push /new-account/connect route to history', function () { connectHardwareWallet.simulate('click') assert(props.toggleAccountMenu.calledOnce) - assert.equal(props.history.push.getCall(0).args[0], '/new-account/connect') + assert.equal( + props.history.push.getCall(0).args[0], + '/new-account/connect', + ) }) }) describe('Info & Help', function () { - let infoHelp it('renders import account item', function () { @@ -179,7 +179,6 @@ describe('Account Menu', function () { }) describe('Settings', function () { - let settings it('renders import account item', function () { diff --git a/ui/app/components/app/add-token-button/add-token-button.component.js b/ui/app/components/app/add-token-button/add-token-button.component.js index 1ddf2b1ff..6073faef0 100644 --- a/ui/app/components/app/add-token-button/add-token-button.component.js +++ b/ui/app/components/app/add-token-button/add-token-button.component.js @@ -5,7 +5,7 @@ import { useI18nContext } from '../../../hooks/useI18nContext' import { ADD_TOKEN_ROUTE } from '../../../helpers/constants/routes' import Button from '../../ui/button' -export default function AddTokenButton () { +export default function AddTokenButton() { const addTokenEvent = useMetricEvent({ eventOpts: { category: 'Navigation', diff --git a/ui/app/components/app/alerts/alerts.js b/ui/app/components/app/alerts/alerts.js index 9aa63418f..f5034073d 100644 --- a/ui/app/components/app/alerts/alerts.js +++ b/ui/app/components/app/alerts/alerts.js @@ -8,18 +8,18 @@ import InvalidCustomNetworkAlert from './invalid-custom-network-alert' import UnconnectedAccountAlert from './unconnected-account-alert' const Alerts = ({ history }) => { - const _invalidCustomNetworkAlertIsOpen = useSelector(invalidCustomNetworkAlertIsOpen) - const _unconnectedAccountAlertIsOpen = useSelector(unconnectedAccountAlertIsOpen) + const _invalidCustomNetworkAlertIsOpen = useSelector( + invalidCustomNetworkAlertIsOpen, + ) + const _unconnectedAccountAlertIsOpen = useSelector( + unconnectedAccountAlertIsOpen, + ) if (_invalidCustomNetworkAlertIsOpen) { - return ( - - ) + return } if (_unconnectedAccountAlertIsOpen) { - return ( - - ) + return } return null diff --git a/ui/app/components/app/alerts/invalid-custom-network-alert/invalid-custom-network-alert.js b/ui/app/components/app/alerts/invalid-custom-network-alert/invalid-custom-network-alert.js index 5c23d58b3..40cad2a0f 100644 --- a/ui/app/components/app/alerts/invalid-custom-network-alert/invalid-custom-network-alert.js +++ b/ui/app/components/app/alerts/invalid-custom-network-alert/invalid-custom-network-alert.js @@ -13,10 +13,7 @@ import Button from '../../../ui/button' import { useI18nContext } from '../../../../hooks/useI18nContext' import { NETWORKS_ROUTE } from '../../../../helpers/constants/routes' -const { - ERROR, - LOADING, -} = ALERT_STATE +const { ERROR, LOADING } = ALERT_STATE const InvalidCustomNetworkAlert = ({ history }) => { const t = useI18nContext() @@ -28,15 +25,11 @@ const InvalidCustomNetworkAlert = ({ history }) => { const footer = ( <> - { - alertState === ERROR - ? ( -
    - { t('failureMessage') } -
    - ) - : null - } + {alertState === ERROR ? ( +
    + {t('failureMessage')} +
    + ) : null}
    @@ -72,19 +65,17 @@ const InvalidCustomNetworkAlert = ({ history }) => {

    {t('invalidCustomNetworkAlertContent1', [networkName])}

    {t('invalidCustomNetworkAlertContent2')}

    - { - t('invalidCustomNetworkAlertContent3', [( - global.platform.openTab({ url: 'https://chainid.network' }) - } - > - chainId.network - - )]) - } + {t('invalidCustomNetworkAlertContent3', [ + + global.platform.openTab({ url: 'https://chainid.network' }) + } + > + chainId.network + , + ])}

    ) diff --git a/ui/app/components/app/alerts/unconnected-account-alert/tests/unconnected-account-alert.test.js b/ui/app/components/app/alerts/unconnected-account-alert/tests/unconnected-account-alert.test.js index ff85ed553..fff6504bb 100644 --- a/ui/app/components/app/alerts/unconnected-account-alert/tests/unconnected-account-alert.test.js +++ b/ui/app/components/app/alerts/unconnected-account-alert/tests/unconnected-account-alert.test.js @@ -13,7 +13,6 @@ import * as actions from '../../../../../store/actions' import UnconnectedAccountAlert from '..' describe('Unconnected Account Alert', function () { - const network = '123' const selectedAddress = '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b' @@ -41,7 +40,7 @@ describe('Unconnected Account Alert', function () { } const cachedBalances = { - '123': { + 123: { '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': '0x0', '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b': '0x0', }, @@ -87,9 +86,7 @@ describe('Unconnected Account Alert', function () { { name: 'exposedAccounts', type: 'filterResponse', - value: [ - '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', - ], + value: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'], }, ], invoker: 'https://test.dapp', @@ -112,7 +109,6 @@ describe('Unconnected Account Alert', function () { }) it('checks that checkbox is checked', function () { - const store = configureMockStore()(mockState) const { getByRole } = renderWithProvider(, store) @@ -140,7 +136,10 @@ describe('Unconnected Account Alert', function () { const store = configureMockStore([thunk])(mockState) - const { getByText, getByRole } = renderWithProvider(, store) + const { getByText, getByRole } = renderWithProvider( + , + store, + ) const dismissButton = getByText(/dismiss/u) const dontShowCheckbox = getByRole('checkbox') @@ -149,10 +148,14 @@ describe('Unconnected Account Alert', function () { fireEvent.click(dismissButton) setImmediate(() => { - assert.equal(store.getActions()[0].type, 'unconnectedAccount/disableAlertRequested') - assert.equal(store.getActions()[1].type, 'unconnectedAccount/disableAlertSucceeded') + assert.equal( + store.getActions()[0].type, + 'unconnectedAccount/disableAlertRequested', + ) + assert.equal( + store.getActions()[1].type, + 'unconnectedAccount/disableAlertSucceeded', + ) }) - }) - }) diff --git a/ui/app/components/app/alerts/unconnected-account-alert/unconnected-account-alert.js b/ui/app/components/app/alerts/unconnected-account-alert/unconnected-account-alert.js index 3ed29ba04..ce6e236aa 100644 --- a/ui/app/components/app/alerts/unconnected-account-alert/unconnected-account-alert.js +++ b/ui/app/components/app/alerts/unconnected-account-alert/unconnected-account-alert.js @@ -23,10 +23,7 @@ import Tooltip from '../../../ui/tooltip' import ConnectedAccountsList from '../../connected-accounts-list' import { useI18nContext } from '../../../../hooks/useI18nContext' -const { - ERROR, - LOADING, -} = ALERT_STATE +const { ERROR, LOADING } = ALERT_STATE const UnconnectedAccountAlert = () => { const t = useI18nContext() @@ -46,15 +43,11 @@ const UnconnectedAccountAlert = () => { const footer = ( <> - { - alertState === ERROR - ? ( -
    - { t('failureMessage') } -
    - ) - : null - } + {alertState === ERROR ? ( +
    + {t('failureMessage')} +
    + ) : null}
    { className="unconnected-account-alert__checkbox-label" htmlFor="unconnectedAccount_dontShowThisAgain" > - { t('dontShowThisAgain') } + {t('dontShowThisAgain')} { rounded className="unconnected-account-alert__dismiss-button" > - { t('dismiss') } + {t('dismiss')}
    @@ -92,7 +85,9 @@ const UnconnectedAccountAlert = () => { return ( { - if (!disabled) { - !isAccountMenuOpen && this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Home', - name: 'Opened Main Menu', - }, - }) - toggleAccountMenu() - } - }} - > - -
    + return ( + isUnlocked && ( +
    { + if (!disabled) { + !isAccountMenuOpen && + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Home', + name: 'Opened Main Menu', + }, + }) + toggleAccountMenu() + } + }} + > + +
    + ) ) } - render () { + render() { const { history, network, @@ -93,7 +102,9 @@ export default class AppHeader extends PureComponent { return (
    - { - !hideNetworkIndicator && ( -
    - this.handleNetworkIndicatorClick(event)} - disabled={disabled || disableNetworkIndicator} - /> -
    - ) - } - { this.renderAccountMenu() } + {!hideNetworkIndicator && ( +
    + this.handleNetworkIndicatorClick(event)} + disabled={disabled || disableNetworkIndicator} + /> +
    + )} + {this.renderAccountMenu()}
    diff --git a/ui/app/components/app/app-header/tests/app-header.test.js b/ui/app/components/app/app-header/tests/app-header.test.js index 76ece07a7..0bb9ed9df 100644 --- a/ui/app/components/app/app-header/tests/app-header.test.js +++ b/ui/app/components/app/app-header/tests/app-header.test.js @@ -26,14 +26,12 @@ describe('App Header', function () { } beforeEach(function () { - wrapper = shallow( - , { - context: { - t: (str) => str, - metricsEvent: () => undefined, - }, + wrapper = shallow(, { + context: { + t: (str) => str, + metricsEvent: () => undefined, }, - ) + }) }) afterEach(function () { @@ -81,7 +79,6 @@ describe('App Header', function () { }) describe('Account Menu', function () { - it('toggles account menu', function () { const accountMenu = wrapper.find('.account-menu__icon') accountMenu.simulate('click') @@ -95,5 +92,4 @@ describe('App Header', function () { assert(props.toggleAccountMenu.notCalled) }) }) - }) diff --git a/ui/app/components/app/asset-list-item/asset-list-item.js b/ui/app/components/app/asset-list-item/asset-list-item.js index 7119cbaa7..274f0576b 100644 --- a/ui/app/components/app/asset-list-item/asset-list-item.js +++ b/ui/app/components/app/asset-list-item/asset-list-item.js @@ -36,27 +36,23 @@ const AssetListItem = ({ name: 'Clicked Send: Token', }, }) - const titleIcon = warning - ? ( - - - - ) - : null + const titleIcon = warning ? ( + + + + ) : null - const midContent = warning - ? ( - <> - -
    {warning}
    - - ) - : null + const midContent = warning ? ( + <> + +
    {warning}
    + + ) : null const sendTokenButton = useMemo(() => { if (tokenAddress === null || tokenAddress === undefined) { @@ -69,11 +65,13 @@ const AssetListItem = ({ onClick={(e) => { e.stopPropagation() sendTokenEvent() - dispatch(updateSendToken({ - address: tokenAddress, - decimals: tokenDecimals, - symbol: tokenSymbol, - })) + dispatch( + updateSendToken({ + address: tokenAddress, + decimals: tokenDecimals, + symbol: tokenSymbol, + }), + ) history.push(SEND_ROUTE) }} > @@ -94,30 +92,36 @@ const AssetListItem = ({ -

    {primary}

    -

    {tokenSymbol}

    - - )} + title={ + + } titleIcon={titleIcon} subtitle={

    {secondary}

    } onClick={onClick} - icon={( + icon={ - )} + } midContent={midContent} - rightContent={( + rightContent={ <> {sendTokenButton} - )} + } /> ) } diff --git a/ui/app/components/app/asset-list-item/asset-list-item.scss b/ui/app/components/app/asset-list-item/asset-list-item.scss index d8d96b153..6ee0853a5 100644 --- a/ui/app/components/app/asset-list-item/asset-list-item.scss +++ b/ui/app/components/app/asset-list-item/asset-list-item.scss @@ -1,9 +1,24 @@ .asset-list-item { - &__token-value { - padding: 0 5px 0 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; + & .list-item__heading { + max-width: 100%; + } + + &__token-button { + padding-inline-start: 0; + min-width: 0; + min-height: 0; + text-align: start; + + & h2 { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 100%; + } + + & span { + padding-right: 5px; + } } &__chevron-right { @@ -15,8 +30,9 @@ } .list-item__subheading { + @include H6; + margin-top: 6px; - font-size: 14px; } &__warning { @@ -25,10 +41,13 @@ } & &__send-token-button { + @include H6; + display: none; text-transform: uppercase; width: fit-content; - font-size: 14px; + padding-top: 0; + padding-bottom: 0; } @media (min-width: 576px) { @@ -40,7 +59,8 @@ display: flex; } - & &__send-token-button { + &:hover &__send-token-button, + &:focus-within &__send-token-button { display: inline-block; } diff --git a/ui/app/components/app/asset-list/asset-list.js b/ui/app/components/app/asset-list/asset-list.js index 12d54e9dc..a218e2f15 100644 --- a/ui/app/components/app/asset-list/asset-list.js +++ b/ui/app/components/app/asset-list/asset-list.js @@ -9,12 +9,20 @@ import AssetListItem from '../asset-list-item' import { PRIMARY, SECONDARY } from '../../../helpers/constants/common' import { useMetricEvent } from '../../../hooks/useMetricEvent' import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency' -import { getCurrentAccountWithSendEtherInfo, getNativeCurrency, getShouldShowFiat } from '../../../selectors' +import { + getCurrentAccountWithSendEtherInfo, + getNativeCurrency, + getShouldShowFiat, + getSelectedAddress, +} from '../../../selectors' import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay' const AssetList = ({ onClickAsset }) => { const history = useHistory() - const selectedAccountBalance = useSelector((state) => getCurrentAccountWithSendEtherInfo(state).balance) + const selectedAddress = useSelector((state) => getSelectedAddress(state)) + const selectedAccountBalance = useSelector( + (state) => getCurrentAccountWithSendEtherInfo(state).balance, + ) const nativeCurrency = useSelector(getNativeCurrency) const showFiat = useSelector(getShouldShowFiat) const selectTokenEvent = useMetricEvent({ @@ -41,14 +49,20 @@ const AssetList = ({ onClickAsset }) => { numberOfDecimals: secondaryNumberOfDecimals, } = useUserPreferencedCurrency(SECONDARY, { ethNumberOfDecimals: 4 }) - const [primaryCurrencyDisplay] = useCurrencyDisplay( + const [, primaryCurrencyProperties] = useCurrencyDisplay( selectedAccountBalance, - { numberOfDecimals: primaryNumberOfDecimals, currency: primaryCurrency }, + { + numberOfDecimals: primaryNumberOfDecimals, + currency: primaryCurrency, + }, ) const [secondaryCurrencyDisplay] = useCurrencyDisplay( selectedAccountBalance, - { numberOfDecimals: secondaryNumberOfDecimals, currency: secondaryCurrency }, + { + numberOfDecimals: secondaryNumberOfDecimals, + currency: secondaryCurrency, + }, ) return ( @@ -56,7 +70,9 @@ const AssetList = ({ onClickAsset }) => { onClickAsset(nativeCurrency)} data-testid="wallet-balance" - primary={primaryCurrencyDisplay} + primary={primaryCurrencyProperties.value} + tokenAddress={selectedAddress} + tokenSymbol={primaryCurrencyProperties.suffix} secondary={showFiat ? secondaryCurrencyDisplay : undefined} /> { return (
    -
    - { label } -
    +
    {label}
    - { - headerText && ( -
    onHeaderClick && onHeaderClick()} - > - { headerText } -
    - ) - } - { - primaryText - ? ( -
    - { primaryText } -
    - ) : ( - - ) - } - { - secondaryText - ? ( -
    - { secondaryText } -
    - ) : ( - - ) - } + {headerText && ( +
    onHeaderClick?.()} + > + {headerText} +
    + )} + {primaryText ? ( +
    + {primaryText} +
    + ) : ( + + )} + {secondaryText ? ( +
    {secondaryText}
    + ) : ( + + )}
    ) diff --git a/ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss b/ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss index cfd888817..5b78bfd39 100644 --- a/ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss +++ b/ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss @@ -6,7 +6,8 @@ align-items: center; &__label { - font-size: 0.75rem; + @include H7; + font-weight: 500; color: $scorpion; text-transform: uppercase; @@ -19,7 +20,8 @@ } &__primary { - font-size: 1.5rem; + @include H3; + justify-content: flex-end; } @@ -29,7 +31,8 @@ } &__header-text { - font-size: 0.75rem; + @include H7; + text-transform: uppercase; margin-bottom: 6px; color: $scorpion; @@ -40,7 +43,7 @@ } &--total { - font-size: 0.625rem; + @include H8; } } @@ -50,8 +53,9 @@ .custom-nonce-input { input { + @include Paragraph; + width: 90px; - font-size: 1rem; } } } diff --git a/ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js b/ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js index d431033c3..add78407c 100644 --- a/ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js +++ b/ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js @@ -33,27 +33,59 @@ describe('Confirm Detail Row Component', function () { }) it('should render the label as a child of the confirm-detail-row__label', function () { - assert.equal(wrapper.find('.confirm-detail-row > .confirm-detail-row__label').childAt(0).text(), 'mockLabel') + assert.equal( + wrapper + .find('.confirm-detail-row > .confirm-detail-row__label') + .childAt(0) + .text(), + 'mockLabel', + ) }) it('should render the headerText as a child of the confirm-detail-row__header-text', function () { - assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__header-text').childAt(0).text(), 'mockHeaderText') + assert.equal( + wrapper + .find( + '.confirm-detail-row__details > .confirm-detail-row__header-text', + ) + .childAt(0) + .text(), + 'mockHeaderText', + ) }) it('should render the primaryText as a child of the confirm-detail-row__primary', function () { - assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__primary').childAt(0).text(), 'mockFiatText') + assert.equal( + wrapper + .find('.confirm-detail-row__details > .confirm-detail-row__primary') + .childAt(0) + .text(), + 'mockFiatText', + ) }) it('should render the ethText as a child of the confirm-detail-row__secondary', function () { - assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__secondary').childAt(0).text(), 'mockEthText') + assert.equal( + wrapper + .find('.confirm-detail-row__details > .confirm-detail-row__secondary') + .childAt(0) + .text(), + 'mockEthText', + ) }) it('should set the fiatTextColor on confirm-detail-row__primary', function () { - assert.equal(wrapper.find('.confirm-detail-row__primary').props().style.color, 'mockColor') + assert.equal( + wrapper.find('.confirm-detail-row__primary').props().style.color, + 'mockColor', + ) }) it('should assure the confirm-detail-row__header-text classname is correct', function () { - assert.equal(wrapper.find('.confirm-detail-row__header-text').props().className, 'confirm-detail-row__header-text mockHeaderClass') + assert.equal( + wrapper.find('.confirm-detail-row__header-text').props().className, + 'confirm-detail-row__header-text mockHeaderClass', + ) }) it('should call onHeaderClick when headerText div gets clicked', function () { diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index bf226df7e..9a6c3f5c5 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -34,32 +34,31 @@ export default class ConfirmPageContainerContent extends Component { rejectNText: PropTypes.string, } - renderContent () { + renderContent() { const { detailsComponent, dataComponent } = this.props if (detailsComponent && dataComponent) { return this.renderTabs() } return detailsComponent || dataComponent - } - renderTabs () { + renderTabs() { const { detailsComponent, dataComponent } = this.props return ( - { detailsComponent } + {detailsComponent} - { dataComponent } + {dataComponent} ) } - render () { + render() { const { action, errorKey, @@ -88,40 +87,30 @@ export default class ConfirmPageContainerContent extends Component { return (
    - { - warning && ( - - ) - } - { - summaryComponent || ( - - ) - } - { this.renderContent() } - { - (errorKey || errorMessage) && ( -
    - -
    - ) - } + {warning && } + {summaryComponent || ( + + )} + {this.renderContent()} + {(errorKey || errorMessage) && ( +
    + +
    + )} - {unapprovedTxCount > 1 && ( - - {rejectNText} - - )} + {unapprovedTxCount > 1 && {rejectNText}} -
    ) } diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js index 85cbc0076..dfa7b63fb 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js @@ -20,39 +20,31 @@ const ConfirmPageContainerSummary = (props) => { return (
    -
    - { action } -
    - { - nonce && ( -
    - { `#${nonce}` } -
    - ) - } +
    {action}
    + {nonce && ( +
    + {`#${nonce}`} +
    + )}
    - { - identiconAddress && ( - - ) - } + {identiconAddress && ( + + )}
    - { titleComponent || title } + {titleComponent || title}
    - { - hideSubtitle || ( -
    - { subtitleComponent || subtitle } -
    - ) - } + {hideSubtitle || ( +
    + {subtitleComponent || subtitle} +
    + )}
    ) } diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss index 4f1e1547a..5ce8f708a 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss @@ -10,9 +10,10 @@ } &__action { + @include H7; + text-transform: uppercase; color: $oslo-gray; - font-size: 0.75rem; padding: 3px 8px; border: 1px solid $oslo-gray; border-radius: 4px; @@ -35,7 +36,8 @@ } &__title-text { - font-size: 2.25rem; + @include H1; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js index 8865fb976..d235ad3b4 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js @@ -9,7 +9,7 @@ const ConfirmPageContainerWarning = (props) => { src="/images/alert.svg" />
    - { props.warning } + {props.warning}
    ) diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss index 8451165cf..f439be370 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss @@ -12,7 +12,8 @@ } &__warning { - font-size: 0.75rem; + @include H7; + color: #5f5922; } } diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss index cb263ec9c..c45f5d11f 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss @@ -23,18 +23,20 @@ } &__data-box { + @include H7; + background-color: #f9fafa; padding: 12px; - font-size: 0.75rem; margin-bottom: 16px; word-wrap: break-word; max-height: 200px; overflow-y: auto; &-label { + @include H7; + text-transform: uppercase; padding: 8px 0 12px; - font-size: 12px; } } @@ -61,7 +63,8 @@ } &__function-type { - font-size: 0.875rem; + @include H6; + font-weight: 500; text-transform: capitalize; color: $black; @@ -69,10 +72,17 @@ } &__tab { - font-size: 0.75rem; + @include H7; + color: #8c8e94; text-transform: uppercase; margin: 0 8px; + + & button { + font-size: unset; + color: #8c8e94; + text-transform: uppercase; + } } .page-container__footer { diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js index f2e2c0f2b..92d94f937 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js @@ -11,7 +11,7 @@ import { shortenAddress } from '../../../../helpers/utils/util' import AccountMismatchWarning from '../../../ui/account-mismatch-warning/account-mismatch-warning.component' import { useI18nContext } from '../../../../hooks/useI18nContext' -export default function ConfirmPageContainerHeader ({ +export default function ConfirmPageContainerHeader({ onEdit, showEdit, accountAddress, @@ -20,7 +20,8 @@ export default function ConfirmPageContainerHeader ({ }) { const t = useI18nContext() const windowType = getEnvironmentType() - const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION && + const isFullScreen = + windowType !== ENVIRONMENT_TYPE_NOTIFICATION && windowType !== ENVIRONMENT_TYPE_POPUP if (!showEdit && isFullScreen) { @@ -29,43 +30,35 @@ export default function ConfirmPageContainerHeader ({ return (
    - { showAccountInHeader - ? ( -
    -
    - -
    -
    - { shortenAddress(accountAddress) } -
    - + {showAccountInHeader ? ( +
    +
    +
    - ) - : ( -
    + {shortenAddress(accountAddress)} +
    + +
    + ) : ( +
    + + onEdit()} > - - onEdit()} - > - { t('edit') } - -
    - ) - } - { !isFullScreen && } + {t('edit')} + +
    + )} + {!isFullScreen && }
    - { children } + {children}
    ) } diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss index fb24feb58..79d79e55b 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss @@ -23,10 +23,10 @@ } &__back-button { + @include Paragraph; + color: #2f9ae0; - font-size: 1rem; cursor: pointer; - font-weight: 400; padding-left: 5px; } @@ -38,7 +38,8 @@ } &__address { + @include H6; + margin-left: 6px; - font-size: 14px; } } diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js index 0adc54fdb..f8f4e3ae8 100755 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js @@ -2,7 +2,18 @@ import React from 'react' import PropTypes from 'prop-types' const ConfirmPageContainerNavigation = (props) => { - const { onNextTx, totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = props + const { + onNextTx, + totalTx, + positionOfCurrentTx, + nextTxId, + prevTxId, + showNavigation, + firstTx, + lastTx, + ofText, + requestsWaitingText, + } = props return (
    { data-testid="next-page" onClick={() => onNextTx(nextTxId)} > - +
    onNextTx(lastTx)} > - +
    diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.scss index fe3106aa8..9db235003 100755 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.scss +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.scss @@ -36,13 +36,15 @@ } &__navtext { - font-size: 9px; + @include H9; + font-weight: bold; } &__longtext { + @include H9; + color: $oslo-gray; - font-size: 8px; } &__imageflip { diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js index baf75bbd3..45c32b3b6 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js @@ -2,7 +2,11 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SenderToRecipient from '../../ui/sender-to-recipient' import { PageContainerFooter } from '../../ui/page-container' -import { ConfirmPageContainerHeader, ConfirmPageContainerContent, ConfirmPageContainerNavigation } from '.' +import { + ConfirmPageContainerHeader, + ConfirmPageContainerContent, + ConfirmPageContainerNavigation, +} from '.' export default class ConfirmPageContainer extends Component { static contextTypes = { @@ -58,7 +62,7 @@ export default class ConfirmPageContainer extends Component { disabled: PropTypes.bool, } - render () { + render() { const { showEdit, onEdit, @@ -102,7 +106,8 @@ export default class ConfirmPageContainer extends Component { hideSenderToRecipient, showAccountInHeader, } = this.props - const renderAssetImage = contentComponent || (!contentComponent && !identiconAddress) + const renderAssetImage = + contentComponent || (!contentComponent && !identiconAddress) return (
    @@ -124,68 +129,61 @@ export default class ConfirmPageContainer extends Component { showAccountInHeader={showAccountInHeader} accountAddress={fromAddress} > - { hideSenderToRecipient - ? null - : ( - - ) - } - - { - contentComponent || ( - - ) - } - { - contentComponent && ( - - {unapprovedTxCount > 1 && ( - - {this.context.t('rejectTxsN', [unapprovedTxCount])} - - )} - - ) - } + )} + + {contentComponent || ( + + )} + {contentComponent && ( + + {unapprovedTxCount > 1 && ( + + {this.context.t('rejectTxsN', [unapprovedTxCount])} + + )} + + )}
    ) } diff --git a/ui/app/components/app/connected-accounts-list/connected-accounts-list-item/connected-accounts-list-item.component.js b/ui/app/components/app/connected-accounts-list/connected-accounts-list-item/connected-accounts-list-item.component.js index cfff63c1c..8a1fc0852 100644 --- a/ui/app/components/app/connected-accounts-list/connected-accounts-list-item/connected-accounts-list-item.component.js +++ b/ui/app/components/app/connected-accounts-list/connected-accounts-list-item/connected-accounts-list-item.component.js @@ -23,15 +23,8 @@ export default class ConnectedAccountsListItem extends PureComponent { action: null, } - render () { - const { - address, - className, - name, - status, - action, - options, - } = this.props + render() { + const { address, className, name, status, action, options } = this.props return (
    @@ -45,16 +38,12 @@ export default class ConnectedAccountsListItem extends PureComponent {

    {name}

    - { - status - ? ( -

    -    - {status} -

    - ) - : null - } + {status ? ( +

    +    + {status} +

    + ) : null} {action}
    diff --git a/ui/app/components/app/connected-accounts-list/connected-accounts-list-options/connected-accounts-list-options.component.js b/ui/app/components/app/connected-accounts-list/connected-accounts-list-options/connected-accounts-list-options.component.js index 0fcc2d818..c647bca40 100644 --- a/ui/app/components/app/connected-accounts-list/connected-accounts-list-options/connected-accounts-list-options.component.js +++ b/ui/app/components/app/connected-accounts-list/connected-accounts-list-options/connected-accounts-list-options.component.js @@ -2,25 +2,34 @@ import PropTypes from 'prop-types' import React, { useState } from 'react' import { Menu } from '../../../ui/menu' -const ConnectedAccountsListOptions = ({ children, onShowOptions, onHideOptions, show }) => { +const ConnectedAccountsListOptions = ({ + children, + onShowOptions, + onHideOptions, + show, +}) => { const [optionsButtonElement, setOptionsButtonElement] = useState(null) return ( <> - ) } diff --git a/ui/app/components/app/connected-status-indicator/connected-status-indicator.container.js b/ui/app/components/app/connected-status-indicator/connected-status-indicator.container.js index 17034f32d..253027959 100644 --- a/ui/app/components/app/connected-status-indicator/connected-status-indicator.container.js +++ b/ui/app/components/app/connected-status-indicator/connected-status-indicator.container.js @@ -18,7 +18,9 @@ const mapStateToProps = (state) => { const originOfCurrentTab = getOriginOfCurrentTab(state) const selectedAddressDomainMap = addressConnectedDomainMap[selectedAddress] - const currentTabIsConnectedToSelectedAddress = Boolean(selectedAddressDomainMap && selectedAddressDomainMap[originOfCurrentTab]) + const currentTabIsConnectedToSelectedAddress = Boolean( + selectedAddressDomainMap && selectedAddressDomainMap[originOfCurrentTab], + ) let status if (currentTabIsConnectedToSelectedAddress) { diff --git a/ui/app/components/app/connected-status-indicator/index.scss b/ui/app/components/app/connected-status-indicator/index.scss index 6d9711c0b..17b5b7d1d 100644 --- a/ui/app/components/app/connected-status-indicator/index.scss +++ b/ui/app/components/app/connected-status-indicator/index.scss @@ -51,7 +51,8 @@ } &__text { - font-size: 10px; + @include H8; + color: $Grey-500; margin-left: 6px; white-space: nowrap; diff --git a/ui/app/components/app/contact-list/contact-list.component.js b/ui/app/components/app/contact-list/contact-list.component.js index 0e151d23d..3b62741e6 100644 --- a/ui/app/components/app/contact-list/contact-list.component.js +++ b/ui/app/components/app/contact-list/contact-list.component.js @@ -21,7 +21,7 @@ export default class ContactList extends PureComponent { isShowingAllRecent: false, } - renderRecents () { + renderRecents() { const { t } = this.context const { isShowingAllRecent } = this.state const nonContacts = this.props.searchForRecents() @@ -36,22 +36,20 @@ export default class ContactList extends PureComponent { onSelect={this.props.selectRecipient} selectedAddress={this.props.selectedAddress} /> - { - showLoadMore && ( - - ) - } + {showLoadMore && ( + + )} ) } - renderAddressBook () { + renderAddressBook() { const contacts = this.props.searchForContacts() const contactGroups = contacts.reduce((acc, contact) => { @@ -62,8 +60,7 @@ export default class ContactList extends PureComponent { return acc }, {}) - return Object - .entries(contactGroups) + return Object.entries(contactGroups) .sort(([letter1], [letter2]) => { if (letter1 > letter2) { return 1 @@ -83,7 +80,7 @@ export default class ContactList extends PureComponent { )) } - renderMyAccounts () { + renderMyAccounts() { const myAccounts = this.props.searchForMyAccounts() return ( @@ -95,7 +92,7 @@ export default class ContactList extends PureComponent { ) } - render () { + render() { const { children, searchForRecents, @@ -105,10 +102,10 @@ export default class ContactList extends PureComponent { return (
    - { children || null } - { searchForRecents && this.renderRecents() } - { searchForContacts && this.renderAddressBook() } - { searchForMyAccounts && this.renderMyAccounts() } + {children || null} + {searchForRecents && this.renderRecents()} + {searchForContacts && this.renderAddressBook()} + {searchForMyAccounts && this.renderMyAccounts()}
    ) } diff --git a/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js b/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js index 9614b16a9..a50b2eea3 100644 --- a/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js +++ b/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js @@ -4,11 +4,16 @@ import classnames from 'classnames' import Identicon from '../../../ui/identicon' import { ellipsify } from '../../../../pages/send/send.utils' -function addressesEqual (address1, address2) { +function addressesEqual(address1, address2) { return String(address1).toLowerCase() === String(address2).toLowerCase() } -export default function RecipientGroup ({ label, items, onSelect, selectedAddress }) { +export default function RecipientGroup({ + label, + items, + onSelect, + selectedAddress, +}) { if (!items || !items.length) { return null } @@ -20,42 +25,46 @@ export default function RecipientGroup ({ label, items, onSelect, selectedAddres {label} )} - { - items.map(({ address, name }) => ( -
    onSelect(address, name)} - className={classnames({ - 'send__select-recipient-wrapper__group-item': !addressesEqual(address, selectedAddress), - 'send__select-recipient-wrapper__group-item--selected': addressesEqual(address, selectedAddress), - })} - > - -
    -
    - {name || ellipsify(address)} -
    - { - name && ( -
    - {ellipsify(address)} -
    - ) - } + {items.map(({ address, name }) => ( +
    onSelect(address, name)} + className={classnames({ + 'send__select-recipient-wrapper__group-item': !addressesEqual( + address, + selectedAddress, + ), + 'send__select-recipient-wrapper__group-item--selected': addressesEqual( + address, + selectedAddress, + ), + })} + > + +
    +
    + {name || ellipsify(address)}
    + {name && ( +
    + {ellipsify(address)} +
    + )}
    - )) - } +
    + ))}
    ) } RecipientGroup.propTypes = { label: PropTypes.string, - items: PropTypes.arrayOf(PropTypes.shape({ - address: PropTypes.string.isRequired, - name: PropTypes.string, - })), + items: PropTypes.arrayOf( + PropTypes.shape({ + address: PropTypes.string.isRequired, + name: PropTypes.string, + }), + ), onSelect: PropTypes.func.isRequired, selectedAddress: PropTypes.string, } diff --git a/ui/app/components/app/dropdowns/components/dropdown.js b/ui/app/components/app/dropdowns/components/dropdown.js index b0b9cac99..52a4af4ef 100644 --- a/ui/app/components/app/dropdowns/components/dropdown.js +++ b/ui/app/components/app/dropdowns/components/dropdown.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import MenuDroppo from '../../menu-droppo' export class Dropdown extends Component { - render () { + render() { const { containerClassName, isOpen, @@ -18,7 +18,8 @@ export class Dropdown extends Component { borderRadius: '4px', padding: '8px 16px', background: 'rgba(0, 0, 0, 0.8)', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', ...innerStyle, + boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', + ...innerStyle, } return ( @@ -41,7 +42,7 @@ export class Dropdown extends Component { li.dropdown-menu-item { color: rgb(185, 185, 185); } `} - { children } + {children} ) } @@ -62,7 +63,7 @@ Dropdown.propTypes = { } export class DropdownMenuItem extends Component { - render () { + render() { const { onClick, closeMenu, children, style } = this.props return ( @@ -72,7 +73,7 @@ export class DropdownMenuItem extends Component { onClick() closeMenu() }} - style={({ + style={{ listStyle: 'none', padding: '8px 0px', fontSize: '18px', @@ -81,8 +82,9 @@ export class DropdownMenuItem extends Component { display: 'flex', justifyContent: 'flex-start', alignItems: 'center', - color: 'white', ...style, - })} + color: 'white', + ...style, + }} > {children} diff --git a/ui/app/components/app/dropdowns/components/network-dropdown-icon.js b/ui/app/components/app/dropdowns/components/network-dropdown-icon.js index d15212001..2201651b8 100644 --- a/ui/app/components/app/dropdowns/components/network-dropdown-icon.js +++ b/ui/app/components/app/dropdowns/components/network-dropdown-icon.js @@ -1,40 +1,32 @@ import PropTypes from 'prop-types' import React from 'react' -function NetworkDropdownIcon (props) { - const { - backgroundColor, - isSelected, - innerBorder, - diameter, - loading, - } = props +function NetworkDropdownIcon(props) { + const { backgroundColor, isSelected, innerBorder, diameter, loading } = props - return loading - ? ( - + + + ) : ( +
    +
    - - - ) - : ( -
    -
    -
    - ) + /> +
    + ) } NetworkDropdownIcon.defaultProps = { diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js index 0bd9aed86..e55f77afc 100644 --- a/ui/app/components/app/dropdowns/network-dropdown.js +++ b/ui/app/components/app/dropdowns/network-dropdown.js @@ -4,11 +4,17 @@ import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' import { compose } from 'redux' import * as actions from '../../../store/actions' +import { openAlert as displayInvalidCustomNetworkAlert } from '../../../ducks/alerts/invalid-custom-network' import { - openAlert as displayInvalidCustomNetworkAlert, -} from '../../../ducks/alerts/invalid-custom-network' -import { NETWORKS_ROUTE } from '../../../helpers/constants/routes' -import { isPrefixedFormattedHexString } from '../../../../../app/scripts/lib/util' + NETWORKS_ROUTE, + NETWORKS_FORM_ROUTE, +} from '../../../helpers/constants/routes' +import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../app/scripts/lib/enums' +import { + getEnvironmentType, + isPrefixedFormattedHexString, +} from '../../../../../app/scripts/lib/util' + import { Dropdown, DropdownMenuItem } from './components/dropdown' import NetworkDropdownIcon from './components/network-dropdown-icon' @@ -21,7 +27,7 @@ const notToggleElementClassnames = [ 'network-component', ] -function mapStateToProps (state) { +function mapStateToProps(state) { return { provider: state.metamask.provider, frequentRpcListDetail: state.metamask.frequentRpcListDetail || [], @@ -29,7 +35,7 @@ function mapStateToProps (state) { } } -function mapDispatchToProps (dispatch) { +function mapDispatchToProps(dispatch) { return { setProviderType: (type) => { dispatch(actions.setProviderType(type)) @@ -37,16 +43,25 @@ function mapDispatchToProps (dispatch) { setRpcTarget: (target, chainId, ticker, nickname) => { dispatch(actions.setRpcTarget(target, chainId, ticker, nickname)) }, - delRpcTarget: (target) => { - dispatch(actions.delRpcTarget(target)) - }, hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), setNetworksTabAddMode: (isInAddMode) => { dispatch(actions.setNetworksTabAddMode(isInAddMode)) }, + setSelectedSettingsRpcUrl: (url) => { + dispatch(actions.setSelectedSettingsRpcUrl(url)) + }, displayInvalidCustomNetworkAlert: (networkName) => { dispatch(displayInvalidCustomNetworkAlert(networkName)) }, + showConfirmDeleteNetworkModal: ({ target, onConfirm }) => { + return dispatch( + actions.showModal({ + name: 'CONFIRM_DELETE_NETWORK', + target, + onConfirm, + }), + ) + }, } } @@ -67,15 +82,19 @@ class NetworkDropdown extends Component { setRpcTarget: PropTypes.func.isRequired, hideNetworkDropdown: PropTypes.func.isRequired, setNetworksTabAddMode: PropTypes.func.isRequired, + setSelectedSettingsRpcUrl: PropTypes.func.isRequired, frequentRpcListDetail: PropTypes.array.isRequired, networkDropdownOpen: PropTypes.bool.isRequired, history: PropTypes.object.isRequired, - delRpcTarget: PropTypes.func.isRequired, displayInvalidCustomNetworkAlert: PropTypes.func.isRequired, + showConfirmDeleteNetworkModal: PropTypes.func.isRequired, } - handleClick (newProviderType) { - const { provider: { type: providerType }, setProviderType } = this.props + handleClick(newProviderType) { + const { + provider: { type: providerType }, + setProviderType, + } = this.props const { metricsEvent } = this.context metricsEvent({ @@ -92,14 +111,13 @@ class NetworkDropdown extends Component { setProviderType(newProviderType) } - renderCustomRpcList (rpcListDetail, provider) { + renderCustomRpcList(rpcListDetail, provider) { const reversedRpcListDetail = rpcListDetail.slice().reverse() return reversedRpcListDetail.map((entry) => { const { rpcUrl, chainId, ticker = 'ETH', nickname = '' } = entry - const currentRpcTarget = ( + const isCurrentRpcTarget = provider.type === 'rpc' && rpcUrl === provider.rpcUrl - ) return ( - { - currentRpcTarget - ? - :
    - } - + {isCurrentRpcTarget ? ( + + ) : ( +
    + )} + {nickname || rpcUrl} - { - currentRpcTarget - ? null - : ( - { - e.stopPropagation() - this.props.delRpcTarget(rpcUrl) - }} - /> - ) - } + {isCurrentRpcTarget ? null : ( + { + e.stopPropagation() + this.props.showConfirmDeleteNetworkModal({ + target: rpcUrl, + onConfirm: () => undefined, + }) + }} + /> + )}
    ) }) } - getNetworkName () { + getNetworkName() { const { provider } = this.props const providerName = provider.type @@ -175,8 +193,12 @@ class NetworkDropdown extends Component { return name } - render () { - const { provider: { type: providerType, rpcUrl: activeNetwork }, setNetworksTabAddMode } = this.props + render() { + const { + provider: { type: providerType, rpcUrl: activeNetwork }, + setNetworksTabAddMode, + setSelectedSettingsRpcUrl, + } = this.props const rpcListDetail = this.props.frequentRpcListDetail const isOpen = this.props.networkDropdownOpen const dropdownMenuItemStyle = { @@ -191,7 +213,9 @@ class NetworkDropdown extends Component { onClickOutside={(event) => { const { classList } = event.target const isInClassList = (className) => classList.contains(className) - const notToggleElementIndex = notToggleElementClassnames.findIndex(isInClassList) + const notToggleElementIndex = notToggleElementClassnames.findIndex( + isInClassList, + ) if (notToggleElementIndex === -1) { this.props.hideNetworkDropdown() @@ -224,18 +248,19 @@ class NetworkDropdown extends Component { onClick={() => this.handleClick('mainnet')} style={{ ...dropdownMenuItemStyle, borderColor: '#038789' }} > - { - providerType === 'mainnet' - ? - :
    - } - + {providerType === 'mainnet' ? ( + + ) : ( +
    + )} + {this.context.t('mainnet')} @@ -247,18 +272,19 @@ class NetworkDropdown extends Component { onClick={() => this.handleClick('ropsten')} style={dropdownMenuItemStyle} > - { - providerType === 'ropsten' - ? - :
    - } - + {providerType === 'ropsten' ? ( + + ) : ( +
    + )} + {this.context.t('ropsten')} @@ -270,18 +296,19 @@ class NetworkDropdown extends Component { onClick={() => this.handleClick('kovan')} style={dropdownMenuItemStyle} > - { - providerType === 'kovan' - ? - :
    - } - + {providerType === 'kovan' ? ( + + ) : ( +
    + )} + {this.context.t('kovan')} @@ -293,18 +320,19 @@ class NetworkDropdown extends Component { onClick={() => this.handleClick('rinkeby')} style={dropdownMenuItemStyle} > - { - providerType === 'rinkeby' - ? - :
    - } - + {providerType === 'rinkeby' ? ( + + ) : ( +
    + )} + {this.context.t('rinkeby')} @@ -316,18 +344,19 @@ class NetworkDropdown extends Component { onClick={() => this.handleClick('goerli')} style={dropdownMenuItemStyle} > - { - providerType === 'goerli' - ? - :
    - } - + {providerType === 'goerli' ? ( + + ) : ( +
    + )} + {this.context.t('goerli')} @@ -337,23 +366,29 @@ class NetworkDropdown extends Component { this.props.hideNetworkDropdown()} onClick={() => { + this.props.history.push( + getEnvironmentType() === ENVIRONMENT_TYPE_FULLSCREEN + ? NETWORKS_ROUTE + : NETWORKS_FORM_ROUTE, + ) + setSelectedSettingsRpcUrl('') setNetworksTabAddMode(true) - this.props.history.push(NETWORKS_ROUTE) }} style={dropdownMenuItemStyle} > - { - activeNetwork === 'custom' - ? - :
    - } - + {activeNetwork === 'custom' ? ( + + ) : ( +
    + )} + {this.context.t('customRPC')} diff --git a/ui/app/components/app/dropdowns/simple-dropdown.js b/ui/app/components/app/dropdowns/simple-dropdown.js index a8de50851..a1a97a8b6 100644 --- a/ui/app/components/app/dropdowns/simple-dropdown.js +++ b/ui/app/components/app/dropdowns/simple-dropdown.js @@ -14,7 +14,7 @@ class SimpleDropdown extends Component { isOpen: false, } - getDisplayValue () { + getDisplayValue() { const { selectedOption, options } = this.props const matchesOption = (option) => option.value === selectedOption const matchingOption = options.find(matchesOption) @@ -23,17 +23,17 @@ class SimpleDropdown extends Component { : selectedOption } - handleClose () { + handleClose() { this.setState({ isOpen: false }) } - toggleOpen () { + toggleOpen() { this.setState((prevState) => ({ isOpen: !prevState.isOpen, })) } - renderOptions () { + renderOptions() { const { options, onSelect, selectedOption } = this.props return ( @@ -49,7 +49,8 @@ class SimpleDropdown extends Component { {options.map((option) => (
    { @@ -69,13 +70,15 @@ class SimpleDropdown extends Component { ) } - render () { + render() { const { placeholder } = this.props const { isOpen } = this.state return (
    this.toggleOpen()}> -
    {this.getDisplayValue() || placeholder || 'Select'}
    +
    + {this.getDisplayValue() || placeholder || 'Select'} +
    {isOpen && this.renderOptions()}
    diff --git a/ui/app/components/app/dropdowns/tests/dropdown.test.js b/ui/app/components/app/dropdowns/tests/dropdown.test.js index 20d736800..e362104ba 100644 --- a/ui/app/components/app/dropdowns/tests/dropdown.test.js +++ b/ui/app/components/app/dropdowns/tests/dropdown.test.js @@ -15,8 +15,7 @@ describe('Dropdown', function () { onClick={onClickSpy} style={{ test: 'style' }} closeMenu={closeMenuSpy} - > - , + />, ) }) @@ -33,5 +32,4 @@ describe('Dropdown', function () { assert.equal(onClickSpy.callCount, 1) assert.equal(closeMenuSpy.callCount, 1) }) - }) diff --git a/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js index 370ed334f..f0370b148 100644 --- a/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js +++ b/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js @@ -5,14 +5,14 @@ import NetworkDropdownIcon from '../components/network-dropdown-icon' describe('Network Dropdown Icon', function () { it('adds style props based on props', function () { - const wrapper = shallow(( + const wrapper = shallow( - )) + />, + ) const styleProp = wrapper.find('.menu-icon-circle').children().prop('style') assert.equal(styleProp.background, 'red') assert.equal(styleProp.border, 'none') diff --git a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js index 750e74bd4..09bec2615 100644 --- a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js +++ b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js @@ -27,9 +27,7 @@ describe('Network Dropdown', function () { const store = createMockStore(mockState) beforeEach(function () { - wrapper = mountWithRouter( - , - ) + wrapper = mountWithRouter() }) it('checks for network droppo class', function () { @@ -39,7 +37,6 @@ describe('Network Dropdown', function () { it('renders only one child when networkDropdown is false in state', function () { assert.equal(wrapper.children().length, 1) }) - }) describe('NetworkDropdown in appState is true', function () { @@ -47,7 +44,7 @@ describe('Network Dropdown', function () { metamask: { network: '1', provider: { - 'type': 'test', + type: 'test', }, frequentRpcListDetail: [ { chainId: '0x1a', rpcUrl: 'http://localhost:7545' }, @@ -55,15 +52,13 @@ describe('Network Dropdown', function () { ], }, appState: { - 'networkDropdownOpen': true, + networkDropdownOpen: true, }, } const store = createMockStore(mockState) beforeEach(function () { - wrapper = mountWithRouter( - , - ) + wrapper = mountWithRouter() }) it('renders 8 DropDownMenuItems ', function () { @@ -71,31 +66,52 @@ describe('Network Dropdown', function () { }) it('checks background color for first NetworkDropdownIcon', function () { - assert.equal(wrapper.find(NetworkDropdownIcon).at(0).prop('backgroundColor'), '#29B6AF') // Ethereum Mainnet Teal + assert.equal( + wrapper.find(NetworkDropdownIcon).at(0).prop('backgroundColor'), + '#29B6AF', + ) // Ethereum Mainnet Teal }) it('checks background color for second NetworkDropdownIcon', function () { - assert.equal(wrapper.find(NetworkDropdownIcon).at(1).prop('backgroundColor'), '#ff4a8d') // Ropsten Red + assert.equal( + wrapper.find(NetworkDropdownIcon).at(1).prop('backgroundColor'), + '#ff4a8d', + ) // Ropsten Red }) it('checks background color for third NetworkDropdownIcon', function () { - assert.equal(wrapper.find(NetworkDropdownIcon).at(2).prop('backgroundColor'), '#7057ff') // Kovan Purple + assert.equal( + wrapper.find(NetworkDropdownIcon).at(2).prop('backgroundColor'), + '#7057ff', + ) // Kovan Purple }) it('checks background color for fourth NetworkDropdownIcon', function () { - assert.equal(wrapper.find(NetworkDropdownIcon).at(3).prop('backgroundColor'), '#f6c343') // Rinkeby Yellow + assert.equal( + wrapper.find(NetworkDropdownIcon).at(3).prop('backgroundColor'), + '#f6c343', + ) // Rinkeby Yellow }) it('checks background color for fifth NetworkDropdownIcon', function () { - assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('backgroundColor'), '#3099f2') // Goerli Blue + assert.equal( + wrapper.find(NetworkDropdownIcon).at(4).prop('backgroundColor'), + '#3099f2', + ) // Goerli Blue }) it('checks background color for sixth NetworkDropdownIcon', function () { - assert.equal(wrapper.find(NetworkDropdownIcon).at(5).prop('innerBorder'), '1px solid #9b9b9b') + assert.equal( + wrapper.find(NetworkDropdownIcon).at(5).prop('backgroundColor'), + '#d6d9dc', + ) // "Custom network grey" }) it('checks dropdown for frequestRPCList from state', function () { - assert.equal(wrapper.find(DropdownMenuItem).at(6).text(), '✓http://localhost:7545') + assert.equal( + wrapper.find(DropdownMenuItem).at(6).text(), + '✓http://localhost:7545', + ) }) }) }) diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js index 5886f75d9..a7c3270d0 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js @@ -26,7 +26,7 @@ export default class AdvancedGasInputs extends Component { minimumGasLimit: Number(MIN_GAS_LIMIT_DEC), } - constructor (props) { + constructor(props) { super(props) this.state = { gasPrice: this.props.customGasPrice, @@ -36,8 +36,11 @@ export default class AdvancedGasInputs extends Component { this.changeGasLimit = debounce(this.changeGasLimit, 500) } - componentDidUpdate (prevProps) { - const { customGasPrice: prevCustomGasPrice, customGasLimit: prevCustomGasLimit } = prevProps + componentDidUpdate(prevProps) { + const { + customGasPrice: prevCustomGasPrice, + customGasLimit: prevCustomGasLimit, + } = prevProps const { customGasPrice, customGasLimit } = this.props const { gasPrice, gasLimit } = this.state @@ -67,7 +70,12 @@ export default class AdvancedGasInputs extends Component { this.props.updateCustomGasPrice(Number(e.target.value)) } - gasPriceError ({ insufficientBalance, customPriceIsSafe, isSpeedUp, gasPrice }) { + gasPriceError({ + insufficientBalance, + customPriceIsSafe, + isSpeedUp, + gasPrice, + }) { const { t } = this.context if (insufficientBalance) { @@ -90,7 +98,7 @@ export default class AdvancedGasInputs extends Component { return {} } - gasLimitError ({ insufficientBalance, gasLimit, minimumGasLimit }) { + gasLimitError({ insufficientBalance, gasLimit, minimumGasLimit }) { const { t } = this.context if (insufficientBalance) { @@ -108,11 +116,19 @@ export default class AdvancedGasInputs extends Component { return {} } - renderGasInput ({ value, onChange, errorComponent, errorType, label, customMessageComponent, tooltipTitle }) { + renderGasInput({ + value, + onChange, + errorComponent, + errorType, + label, + customMessageComponent, + tooltipTitle, + }) { return (
    - { label } + {label} @@ -120,8 +136,10 @@ export default class AdvancedGasInputs extends Component {
    onChange({ target: { value: Math.max(value - 1, 0) } })} + onClick={() => + onChange({ target: { value: Math.max(value - 1, 0) } }) + } >
    - { errorComponent || customMessageComponent } + {errorComponent || customMessageComponent}
    ) } - render () { + render() { const { insufficientBalance, customPriceIsSafe, @@ -161,18 +186,22 @@ export default class AdvancedGasInputs extends Component { customGasLimitMessage, minimumGasLimit, } = this.props - const { - gasPrice, - gasLimit, - } = this.state + const { gasPrice, gasLimit } = this.state const { errorText: gasPriceErrorText, errorType: gasPriceErrorType, - } = this.gasPriceError({ insufficientBalance, customPriceIsSafe, isSpeedUp, gasPrice }) + } = this.gasPriceError({ + insufficientBalance, + customPriceIsSafe, + isSpeedUp, + gasPrice, + }) const gasPriceErrorComponent = gasPriceErrorType ? ( -
    - { gasPriceErrorText } +
    + {gasPriceErrorText}
    ) : null @@ -181,30 +210,30 @@ export default class AdvancedGasInputs extends Component { errorType: gasLimitErrorType, } = this.gasLimitError({ insufficientBalance, gasLimit, minimumGasLimit }) const gasLimitErrorComponent = gasLimitErrorType ? ( -
    - { gasLimitErrorText } +
    + {gasLimitErrorText}
    ) : null - const gasLimitCustomMessageComponent = customGasLimitMessage - ? ( -
    - { customGasLimitMessage } -
    - ) - : null + const gasLimitCustomMessageComponent = customGasLimitMessage ? ( +
    + {customGasLimitMessage} +
    + ) : null return (
    - { this.renderGasInput({ + {this.renderGasInput({ label: this.context.t('gasPrice'), tooltipTitle: this.context.t('gasPriceInfoTooltipContent'), value: this.state.gasPrice, onChange: this.onChangeGasPrice, errorComponent: gasPriceErrorComponent, errorType: gasPriceErrorType, - }) } - { this.renderGasInput({ + })} + {this.renderGasInput({ label: this.context.t('gasLimit'), tooltipTitle: this.context.t('gasLimitInfoTooltipContent'), value: this.state.gasLimit, @@ -212,7 +241,7 @@ export default class AdvancedGasInputs extends Component { errorComponent: gasLimitErrorComponent, customMessageComponent: gasLimitCustomMessageComponent, errorType: gasLimitErrorType, - }) } + })}
    ) } diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js index 2b17a7847..949f23fea 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js @@ -6,23 +6,29 @@ import { } from '../../../../helpers/utils/conversions.util' import AdvancedGasInputs from './advanced-gas-inputs.component' -function convertGasPriceForInputs (gasPriceInHexWEI) { +function convertGasPriceForInputs(gasPriceInHexWEI) { return Number(hexWEIToDecGWEI(gasPriceInHexWEI)) } -function convertGasLimitForInputs (gasLimitInHexWEI) { +function convertGasLimitForInputs(gasLimitInHexWEI) { return parseInt(gasLimitInHexWEI, 16) || 0 } const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { customGasPrice, customGasLimit, updateCustomGasPrice, updateCustomGasLimit } = ownProps + const { + customGasPrice, + customGasLimit, + updateCustomGasPrice, + updateCustomGasLimit, + } = ownProps return { ...ownProps, ...stateProps, ...dispatchProps, customGasPrice: convertGasPriceForInputs(customGasPrice), customGasLimit: convertGasLimitForInputs(customGasLimit), - updateCustomGasPrice: (price) => updateCustomGasPrice(decGWEIToHexWEI(price)), + updateCustomGasPrice: (price) => + updateCustomGasPrice(decGWEIToHexWEI(price)), updateCustomGasLimit: (limit) => updateCustomGasLimit(decimalToHex(limit)), } } diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/index.scss b/ui/app/components/app/gas-customization/advanced-gas-inputs/index.scss index 5a9dad81f..1a747dcb4 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/index.scss +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/index.scss @@ -11,14 +11,15 @@ width: 47.5%; &__label { + @include H7; + color: #313b5e; - font-size: 12px; display: flex; justify-content: space-between; align-items: center; @media screen and (max-width: 576px) { - font-size: 10px; + @include H8; } .fa-info-circle { @@ -32,12 +33,14 @@ } &__error-text { - font-size: 12px; + @include H7; + color: red; } &__warning-text { - font-size: 12px; + @include H7; + color: orange; } @@ -47,11 +50,12 @@ &__input { /*rtl:ignore*/ + @include Paragraph; + direction: ltr; border: 1px solid $dusty-gray; border-radius: 4px; color: $mid-gray; - font-size: 16px; height: 24px; width: 100%; padding-left: 8px; @@ -68,6 +72,8 @@ } &__input-arrows { + @include H6; + position: absolute; top: 7px; @@ -80,7 +86,6 @@ display: flex; flex-direction: column; color: #9b9b9b; - font-size: 0.8em; border-bottom-right-radius: 4px; cursor: pointer; @@ -102,7 +107,7 @@ } i { - font-size: 10px; + font-size: $font-size-h8; } } diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js index b47a224a6..161237c90 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js @@ -23,15 +23,11 @@ describe('Advanced Gas Inputs', function () { beforeEach(function () { clock = sinon.useFakeTimers() - wrapper = mount( - , { - context: { - t: (str) => str, - }, + wrapper = mount(, { + context: { + t: (str) => str, }, - ) + }) }) afterEach(function () { @@ -78,7 +74,9 @@ describe('Advanced Gas Inputs', function () { it('errors when insufficientBalance under gas price and gas limit', function () { wrapper.setProps({ insufficientBalance: true }) - const renderError = wrapper.find('.advanced-gas-inputs__gas-edit-row__error-text') + const renderError = wrapper.find( + '.advanced-gas-inputs__gas-edit-row__error-text', + ) assert.equal(renderError.length, 2) assert.equal(renderError.at(0).text(), 'insufficientBalance') @@ -88,7 +86,9 @@ describe('Advanced Gas Inputs', function () { it('errors zero gas price / speed up', function () { wrapper.setProps({ isSpeedUp: true }) - const renderError = wrapper.find('.advanced-gas-inputs__gas-edit-row__error-text') + const renderError = wrapper.find( + '.advanced-gas-inputs__gas-edit-row__error-text', + ) assert.equal(renderError.length, 2) assert.equal(renderError.at(0).text(), 'zeroGasPriceOnSpeedUpError') @@ -98,7 +98,9 @@ describe('Advanced Gas Inputs', function () { it('warns when custom gas price is too low', function () { wrapper.setProps({ customPriceIsSafe: false }) - const renderWarning = wrapper.find('.advanced-gas-inputs__gas-edit-row__warning-text') + const renderWarning = wrapper.find( + '.advanced-gas-inputs__gas-edit-row__warning-text', + ) assert.equal(renderWarning.length, 1) assert.equal(renderWarning.text(), 'gasPriceExtremelyLow') diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js index 176fd9b12..c79122aa2 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js @@ -1,8 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import { - decGWEIToHexWEI, -} from '../../../../../helpers/utils/conversions.util' +import { decGWEIToHexWEI } from '../../../../../helpers/utils/conversions.util' import Loading from '../../../../ui/loading-screen' import GasPriceChart from '../../gas-price-chart' import AdvancedGasInputs from '../../advanced-gas-inputs' @@ -27,21 +25,23 @@ export default class AdvancedTabContent extends Component { isSpeedUp: PropTypes.bool, isEthereumNetwork: PropTypes.bool, customGasLimitMessage: PropTypes.string, - minimumGasLimit: PropTypes.number.isRequired, + minimumGasLimit: PropTypes.number, } - renderDataSummary (transactionFee, timeRemaining) { + renderDataSummary(transactionFee, timeRemaining) { return (
    - { this.context.t('newTransactionFee') } - ~{ this.context.t('transactionTime') } + {this.context.t('newTransactionFee')} + ~{this.context.t('transactionTime')}
    {transactionFee}
    -
    {timeRemaining}
    +
    + {timeRemaining} +
    ) @@ -52,7 +52,7 @@ export default class AdvancedTabContent extends Component { updateCustomGasPrice(decGWEIToHexWEI(price)) } - render () { + render() { const { t } = this.context const { updateCustomGasPrice, @@ -73,7 +73,7 @@ export default class AdvancedTabContent extends Component { return (
    - { this.renderDataSummary(transactionFee, timeRemaining) } + {this.renderDataSummary(transactionFee, timeRemaining)}
    - { isEthereumNetwork - ? ( -
    -
    { t('liveGasPricePredictions') }
    - {gasEstimatesLoading - ? - : - } -
    - { t('slower') } - { t('faster') } -
    + {isEthereumNetwork ? ( +
    +
    + {t('liveGasPricePredictions')}
    - ) - :
    { t('chartOnlyAvailableEth') }
    - } + {gasEstimatesLoading ? ( + + ) : ( + + )} +
    + {t('slower')} + {t('faster')} +
    +
    + ) : ( +
    + {t('chartOnlyAvailableEth')} +
    + )}
    ) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss index 039ccab2b..d6b21e04c 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss @@ -18,42 +18,47 @@ &__titles, &__container { + @include H7; + display: flex; flex-flow: row; justify-content: space-between; - font-size: 12px; color: #888ea3; } &__container { - font-size: 16px; + @include Paragraph; + margin-top: 0; } &__fee { - font-size: 16px; + @include Paragraph; + color: #313a5e; } &__time-remaining { + @include Paragraph; + /*rtl:ignore*/ direction: ltr; color: #313a5e; - font-size: 16px; .minutes-num, .seconds-num { - font-size: 16px; + @include Paragraph; } .seconds-num { + @include Paragraph; + margin-left: 7px; - font-size: 16px; } .minutes-label, .seconds-label { - font-size: 16px; + @include Paragraph; } } } @@ -67,12 +72,15 @@ position: relative; &__title { - font-size: 12px; + @include H7; + color: #313a5e; margin-left: 22px; } &__speed-buttons { + @include H8; + position: absolute; bottom: 13px; display: flex; @@ -80,7 +88,6 @@ padding-left: 20px; padding-right: 19px; width: 100%; - font-size: 10px; color: #888ea3; } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js index 4668ced0b..283240cef 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js @@ -17,7 +17,7 @@ describe('AdvancedTabContent Component', function () { } sinon.spy(AdvancedTabContent.prototype, 'renderDataSummary') - wrapper = shallow(( + wrapper = shallow( - )) + />, + ) }) afterEach(function () { @@ -46,14 +46,28 @@ describe('AdvancedTabContent Component', function () { const advancedTabChildren = wrapper.children() assert.equal(advancedTabChildren.length, 2) - assert(advancedTabChildren.at(0).hasClass('advanced-tab__transaction-data-summary')) + assert( + advancedTabChildren + .at(0) + .hasClass('advanced-tab__transaction-data-summary'), + ) assert(advancedTabChildren.at(1).hasClass('advanced-tab__fee-chart')) const feeChartDiv = advancedTabChildren.at(1) - assert(feeChartDiv.childAt(1).childAt(0).hasClass('advanced-tab__fee-chart__title')) + assert( + feeChartDiv + .childAt(1) + .childAt(0) + .hasClass('advanced-tab__fee-chart__title'), + ) assert(feeChartDiv.childAt(1).childAt(1).is(GasPriceChart)) - assert(feeChartDiv.childAt(1).childAt(2).hasClass('advanced-tab__fee-chart__speed-buttons')) + assert( + feeChartDiv + .childAt(1) + .childAt(2) + .hasClass('advanced-tab__fee-chart__speed-buttons'), + ) }) it('should render a loading component instead of the chart if gasEstimatesLoading is true', function () { @@ -61,19 +75,35 @@ describe('AdvancedTabContent Component', function () { const advancedTabChildren = wrapper.children() assert.equal(advancedTabChildren.length, 2) - assert(advancedTabChildren.at(0).hasClass('advanced-tab__transaction-data-summary')) + assert( + advancedTabChildren + .at(0) + .hasClass('advanced-tab__transaction-data-summary'), + ) assert(advancedTabChildren.at(1).hasClass('advanced-tab__fee-chart')) const feeChartDiv = advancedTabChildren.at(1) - assert(feeChartDiv.childAt(1).childAt(0).hasClass('advanced-tab__fee-chart__title')) + assert( + feeChartDiv + .childAt(1) + .childAt(0) + .hasClass('advanced-tab__fee-chart__title'), + ) assert(feeChartDiv.childAt(1).childAt(1).is(Loading)) - assert(feeChartDiv.childAt(1).childAt(2).hasClass('advanced-tab__fee-chart__speed-buttons')) + assert( + feeChartDiv + .childAt(1) + .childAt(2) + .hasClass('advanced-tab__fee-chart__speed-buttons'), + ) }) it('should call renderDataSummary with the expected params', function () { - const renderDataSummaryArgs = AdvancedTabContent.prototype.renderDataSummary.getCall(0).args - assert.deepEqual(renderDataSummaryArgs, ['$0.25', 21500]) + const renderDataSummaryArgs = AdvancedTabContent.prototype.renderDataSummary.getCall( + 0, + ).args + assert.deepEqual(renderDataSummaryArgs, ['$0.25', '21500']) }) }) @@ -81,7 +111,9 @@ describe('AdvancedTabContent Component', function () { let dataSummary beforeEach(function () { - dataSummary = shallow(wrapper.instance().renderDataSummary('mockTotalFee', 'mockMsRemaining')) + dataSummary = shallow( + wrapper.instance().renderDataSummary('mockTotalFee', 'mockMsRemaining'), + ) }) it('should render the transaction-data-summary root node', function () { @@ -90,18 +122,26 @@ describe('AdvancedTabContent Component', function () { it('should render titles of the data', function () { const titlesNode = dataSummary.children().at(0) - assert(titlesNode.hasClass('advanced-tab__transaction-data-summary__titles')) + assert( + titlesNode.hasClass('advanced-tab__transaction-data-summary__titles'), + ) assert.equal(titlesNode.children().at(0).text(), 'newTransactionFee') assert.equal(titlesNode.children().at(1).text(), '~transactionTime') }) it('should render the data', function () { const dataNode = dataSummary.children().at(1) - assert(dataNode.hasClass('advanced-tab__transaction-data-summary__container')) + assert( + dataNode.hasClass('advanced-tab__transaction-data-summary__container'), + ) assert.equal(dataNode.children().at(0).text(), 'mockTotalFee') - assert(dataNode.children().at(1).hasClass('advanced-tab__transaction-data-summary__time-remaining')) + assert( + dataNode + .children() + .at(1) + .hasClass('advanced-tab__transaction-data-summary__time-remaining'), + ) assert.equal(dataNode.children().at(1).text(), 'mockMsRemaining') }) }) - }) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js index 5a80e4c98..7ef85e7a4 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js @@ -12,25 +12,30 @@ export default class BasicTabContent extends Component { gasPriceButtonGroupProps: PropTypes.object, } - render () { + render() { const { t } = this.context const { gasPriceButtonGroupProps } = this.props return (
    -
    { t('estimatedProcessingTimes') }
    -
    { t('selectAHigherGasFee') }
    - {gasPriceButtonGroupProps.loading - ? - : ( - - ) - } -
    { t('acceleratingATransaction') }
    +
    + {t('estimatedProcessingTimes')} +
    +
    + {t('selectAHigherGasFee')} +
    + {gasPriceButtonGroupProps.loading ? ( + + ) : ( + + )} +
    + {t('acceleratingATransaction')} +
    ) } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.scss b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.scss index 113d7f187..734d03876 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.scss +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.scss @@ -8,22 +8,25 @@ border-bottom: 1px solid #d2d8dd; &__title { + @include Paragraph; + margin-top: 19px; - font-size: 16px; color: $black; } &__blurb { + @include H7; + width: 95%; - font-size: 12px; color: $black; margin-top: 5px; margin-bottom: 15px; } &__footer-blurb { + @include H7; + width: 95%; - font-size: 12px; color: #979797; margin-top: 15px; } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js index f8cec8a0c..2c28fc556 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js @@ -32,7 +32,8 @@ const mockGasPriceButtonGroupProps = { gasEstimateType: GAS_ESTIMATE_TYPES.AVERAGE, }, ], - handleGasPriceSelection: (newPrice) => console.log('NewPrice: ', newPrice), + handleGasPriceSelection: ({ gasPrice }) => + console.log('NewPrice: ', gasPrice), noButtonActiveByDefault: true, showCheck: true, } @@ -42,15 +43,20 @@ describe('BasicTabContent Component', function () { let wrapper beforeEach(function () { - wrapper = shallow(( + wrapper = shallow( - )) + />, + ) }) it('should have a title', function () { - assert(wrapper.find('.basic-tab-content').childAt(0).hasClass('basic-tab-content__title')) + assert( + wrapper + .find('.basic-tab-content') + .childAt(0) + .hasClass('basic-tab-content__title'), + ) }) it('should render a GasPriceButtonGroup compenent', function () { @@ -67,17 +73,32 @@ describe('BasicTabContent Component', function () { showCheck, } = wrapper.find(GasPriceButtonGroup).props() assert.equal(wrapper.find(GasPriceButtonGroup).length, 1) - assert.equal(buttonDataLoading, mockGasPriceButtonGroupProps.buttonDataLoading) + assert.equal( + buttonDataLoading, + mockGasPriceButtonGroupProps.buttonDataLoading, + ) assert.equal(className, mockGasPriceButtonGroupProps.className) - assert.equal(noButtonActiveByDefault, mockGasPriceButtonGroupProps.noButtonActiveByDefault) + assert.equal( + noButtonActiveByDefault, + mockGasPriceButtonGroupProps.noButtonActiveByDefault, + ) assert.equal(showCheck, mockGasPriceButtonGroupProps.showCheck) - assert.deepEqual(gasButtonInfo, mockGasPriceButtonGroupProps.gasButtonInfo) - assert.equal(JSON.stringify(handleGasPriceSelection), JSON.stringify(mockGasPriceButtonGroupProps.handleGasPriceSelection)) + assert.deepEqual( + gasButtonInfo, + mockGasPriceButtonGroupProps.gasButtonInfo, + ) + assert.equal( + JSON.stringify(handleGasPriceSelection), + JSON.stringify(mockGasPriceButtonGroupProps.handleGasPriceSelection), + ) }) it('should render a loading component instead of the GasPriceButtonGroup if gasPriceButtonGroupProps.loading is true', function () { wrapper.setProps({ - gasPriceButtonGroupProps: { ...mockGasPriceButtonGroupProps, loading: true }, + gasPriceButtonGroupProps: { + ...mockGasPriceButtonGroupProps, + loading: true, + }, }) assert.equal(wrapper.find(GasPriceButtonGroup).length, 0) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js index 58aa30a87..4fe7ad979 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js @@ -2,10 +2,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import PageContainer from '../../../ui/page-container' import { Tabs, Tab } from '../../../ui/tabs' -import { calcGasTotal } from '../../../../pages/send/send.utils' -import { - sumHexWEIsToRenderableFiat, -} from '../../../../helpers/utils/conversions.util' import AdvancedTabContent from './advanced-tab-content' import BasicTabContent from './basic-tab-content' @@ -34,54 +30,38 @@ export default class GasModalPageContainer extends Component { newTotalEth: PropTypes.string, sendAmount: PropTypes.string, transactionFee: PropTypes.string, - extraInfoRow: PropTypes.shape({ label: PropTypes.string, value: PropTypes.string }), }), onSubmit: PropTypes.func, customModalGasPriceInHex: PropTypes.string, customModalGasLimitInHex: PropTypes.string, cancelAndClose: PropTypes.func, - blockTime: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), + blockTime: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), customPriceIsSafe: PropTypes.bool, isSpeedUp: PropTypes.bool, isRetry: PropTypes.bool, disableSave: PropTypes.bool, isEthereumNetwork: PropTypes.bool, - customGasLimitMessage: PropTypes.string, - customTotalSupplement: PropTypes.string, - isSwap: PropTypes.bool, - value: PropTypes.string, - conversionRate: PropTypes.number, - minimumGasLimit: PropTypes.number.isRequired, } - state = { - selectedTab: 'Basic', - } - - componentDidMount () { + componentDidMount() { const promise = this.props.hideBasic ? Promise.resolve(this.props.blockTime) - : this.props.fetchBasicGasAndTimeEstimates() - .then((basicEstimates) => basicEstimates.blockTime) + : this.props + .fetchBasicGasAndTimeEstimates() + .then((basicEstimates) => basicEstimates.blockTime) - promise - .then((blockTime) => { - this.props.fetchGasEstimates(blockTime) - }) + promise.then((blockTime) => { + this.props.fetchGasEstimates(blockTime) + }) } - renderBasicTabContent (gasPriceButtonGroupProps) { + renderBasicTabContent(gasPriceButtonGroupProps) { return ( - + ) } - renderAdvancedTabContent () { + renderAdvancedTabContent() { const { updateCustomGasPrice, updateCustomGasLimit, @@ -94,12 +74,8 @@ export default class GasModalPageContainer extends Component { customPriceIsSafe, isSpeedUp, isRetry, - infoRowProps: { - transactionFee, - }, + infoRowProps: { transactionFee }, isEthereumNetwork, - customGasLimitMessage, - minimumGasLimit, } = this.props return ( @@ -108,7 +84,6 @@ export default class GasModalPageContainer extends Component { updateCustomGasLimit={updateCustomGasLimit} customModalGasPriceInHex={customModalGasPriceInHex} customModalGasLimitInHex={customModalGasLimitInHex} - customGasLimitMessage={customGasLimitMessage} timeRemaining={currentTimeEstimate} transactionFee={transactionFee} gasChartProps={gasChartProps} @@ -118,52 +93,53 @@ export default class GasModalPageContainer extends Component { isSpeedUp={isSpeedUp} isRetry={isRetry} isEthereumNetwork={isEthereumNetwork} - minimumGasLimit={minimumGasLimit} /> ) } - renderInfoRows (newTotalFiat, newTotalEth, sendAmount, transactionFee, extraInfoRow) { + renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) { return (
    - {this.context.t('sendAmount')} - {sendAmount} + + {this.context.t('sendAmount')} + + + {sendAmount} +
    - {this.context.t('transactionFee')} - {transactionFee} + + {this.context.t('transactionFee')} + + + {transactionFee} +
    - {extraInfoRow && ( -
    - {extraInfoRow.label} - {extraInfoRow.value} -
    - )}
    - {this.context.t('newTotal')} - {newTotalEth} + + {this.context.t('newTotal')} + + + {newTotalEth} +
    - {newTotalFiat} + + {newTotalFiat} +
    ) } - renderTabs () { + renderTabs() { const { gasPriceButtonGroupProps, hideBasic, - infoRowProps: { - newTotalFiat, - newTotalEth, - sendAmount, - transactionFee, - extraInfoRow, - }, + infoRowProps: { newTotalFiat, newTotalEth, sendAmount, transactionFee }, } = this.props let tabsToRender = [ @@ -182,12 +158,17 @@ export default class GasModalPageContainer extends Component { } return ( - this.setState({ selectedTab: tabName })}> + {tabsToRender.map(({ name, content }, i) => (
    - { content } - { this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee, extraInfoRow) } + {content} + {this.renderInfoRows( + newTotalFiat, + newTotalEth, + sendAmount, + transactionFee, + )}
    ))} @@ -195,7 +176,7 @@ export default class GasModalPageContainer extends Component { ) } - render () { + render() { const { cancelAndClose, onSubmit, @@ -224,26 +205,7 @@ export default class GasModalPageContainer extends Component { }, }) } - if (this.props.isSwap) { - const newSwapGasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex) - let speedSet = '' - if (this.state.selectedTab === 'Basic') { - const { gasButtonInfo } = this.props.gasPriceButtonGroupProps - const selectedGasButtonInfo = gasButtonInfo.find(({ priceInHexWei }) => priceInHexWei === customModalGasPriceInHex) - speedSet = selectedGasButtonInfo?.gasEstimateType || '' - } - - this.context.trackEvent({ - event: 'Gas Fees Changed', - category: 'swaps', - properties: { - speed_set: speedSet, - gas_mode: this.state.selectedTab, - gas_fees: sumHexWEIsToRenderableFiat([this.props.value, newSwapGasTotal, this.props.customTotalSupplement], 'usd', this.props.conversionRate)?.slice(1), - }, - }) - } - onSubmit(customModalGasLimitInHex, customModalGasPriceInHex, this.state.selectedTab, this.context.mixPanelTrack) + onSubmit(customModalGasLimitInHex, customModalGasPriceInHex) }} submitText={this.context.t('save')} headerCloseText={this.context.t('close')} diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js index 2be9e0067..e383d1bac 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux' import { captureException } from '@sentry/browser' -import { addHexPrefix } from 'ethereumjs-util' +import { addHexPrefix } from '../../../../../../app/scripts/lib/util' import { hideModal, setGasLimit, @@ -11,7 +11,6 @@ import { updateSendAmount, setGasTotal, updateTransaction, - setSwapsTxGasParams, } from '../../../../store/actions' import { setCustomGasPrice, @@ -55,33 +54,24 @@ import { sumHexWEIsToRenderableFiat, } from '../../../../helpers/utils/conversions.util' import { getRenderableTimeEstimate } from '../../../../helpers/utils/gas-time-estimates.util' -import { - formatETHFee, -} from '../../../../helpers/utils/formatters' +import { formatETHFee } from '../../../../helpers/utils/formatters' import { calcGasTotal, isBalanceSufficient, } from '../../../../pages/send/send.utils' import { MIN_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants' import { calcMaxAmount } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils' +import { TRANSACTION_STATUSES } from '../../../../../../shared/constants/transaction' import GasModalPageContainer from './gas-modal-page-container.component' const mapStateToProps = (state, ownProps) => { const { currentNetworkTxList, send } = state.metamask const { modalState: { props: modalProps } = {} } = state.appState.modal || {} - const { - txData = {}, - isSwap = false, - customGasLimitMessage = '', - customTotalSupplement = '', - extraInfoRow = null, - useFastestButtons = false, - minimumGasLimit = Number(MIN_GAS_LIMIT_DEC), - } = modalProps || {} + const { txData = {} } = modalProps || {} const { transaction = {} } = ownProps - const selectedTransaction = isSwap - ? txData - : currentNetworkTxList.find(({ id }) => id === (transaction.id || txData.id)) + const selectedTransaction = currentNetworkTxList.find( + ({ id }) => id === (transaction.id || txData.id), + ) const buttonDataLoading = getBasicGasEstimateLoadingStatus(state) const gasEstimatesLoading = getGasEstimatesLoadingStatus(state) const sendToken = getSendToken(state) @@ -90,22 +80,29 @@ const mapStateToProps = (state, ownProps) => { const txParams = selectedTransaction?.txParams ? selectedTransaction.txParams : { - gas: send.gasLimit || '0x5208', - gasPrice: send.gasPrice || getFastPriceEstimateInHexWEI(state, true), - value: sendToken ? '0x0' : send.amount, - } + gas: send.gasLimit || '0x5208', + gasPrice: send.gasPrice || getFastPriceEstimateInHexWEI(state, true), + value: sendToken ? '0x0' : send.amount, + } const { gasPrice: currentGasPrice, gas: currentGasLimit, value } = txParams const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice - const customModalGasLimitInHex = getCustomGasLimit(state) || currentGasLimit || '0x5208' - const customGasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex) + const customModalGasLimitInHex = + getCustomGasLimit(state) || currentGasLimit || '0x5208' + const customGasTotal = calcGasTotal( + customModalGasLimitInHex, + customModalGasPriceInHex, + ) - const gasButtonInfo = getRenderableBasicEstimateData(state, customModalGasLimitInHex, useFastestButtons) + const gasButtonInfo = getRenderableBasicEstimateData( + state, + customModalGasLimitInHex, + ) const currentCurrency = getCurrentCurrency(state) const conversionRate = getConversionRate(state) const newTotalFiat = sumHexWEIsToRenderableFiat( - [value, customGasTotal, customTotalSupplement], + [value, customGasTotal], currentCurrency, conversionRate, ) @@ -126,24 +123,32 @@ const mapStateToProps = (state, ownProps) => { const isSendTokenSet = Boolean(sendToken) - const newTotalEth = maxModeOn && !isSendTokenSet - ? sumHexWEIsToRenderableEth([balance, '0x0']) - : sumHexWEIsToRenderableEth([value, customGasTotal, customTotalSupplement]) + const newTotalEth = + maxModeOn && !isSendTokenSet + ? sumHexWEIsToRenderableEth([balance, '0x0']) + : sumHexWEIsToRenderableEth([value, customGasTotal]) - const sendAmount = maxModeOn && !isSendTokenSet - ? subtractHexWEIsFromRenderableEth(balance, customGasTotal) - : sumHexWEIsToRenderableEth([value, '0x0']) + const sendAmount = + maxModeOn && !isSendTokenSet + ? subtractHexWEIsFromRenderableEth(balance, customGasTotal) + : sumHexWEIsToRenderableEth([value, '0x0']) - const insufficientBalance = maxModeOn ? false : !isBalanceSufficient({ - amount: value, - gasTotal: customGasTotal, - balance, - conversionRate, - }) + const insufficientBalance = maxModeOn + ? false + : !isBalanceSufficient({ + amount: value, + gasTotal: customGasTotal, + balance, + conversionRate, + }) let currentTimeEstimate = '' try { - currentTimeEstimate = getRenderableTimeEstimate(customGasPrice, gasPrices, estimatedTimes) + currentTimeEstimate = getRenderableTimeEstimate( + customGasPrice, + gasPrices, + estimatedTimes, + ) } catch (error) { captureException(error) } @@ -151,7 +156,6 @@ const mapStateToProps = (state, ownProps) => { return { hideBasic, isConfirm: isConfirm(state), - isSwap, customModalGasPriceInHex, customModalGasLimitInHex, customGasPrice, @@ -160,11 +164,14 @@ const mapStateToProps = (state, ownProps) => { newTotalFiat, currentTimeEstimate, blockTime: getBasicGasEstimateBlockTime(state), - customPriceIsSafe: isCustomPriceSafe(state, isSwap), + customPriceIsSafe: isCustomPriceSafe(state), maxModeOn, gasPriceButtonGroupProps: { buttonDataLoading, - defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex), + defaultActiveButtonIndex: getDefaultActiveButtonIndex( + gasButtonInfo, + customModalGasPriceInHex, + ), gasButtonInfo, }, gasChartProps: { @@ -176,22 +183,19 @@ const mapStateToProps = (state, ownProps) => { }, infoRowProps: { originalTotalFiat: sumHexWEIsToRenderableFiat( - [value, customGasTotal, customTotalSupplement], + [value, customGasTotal], currentCurrency, conversionRate, ), - originalTotalEth: sumHexWEIsToRenderableEth( - [value, customGasTotal, customTotalSupplement], - ), + originalTotalEth: sumHexWEIsToRenderableEth([value, customGasTotal]), newTotalFiat: showFiat ? newTotalFiat : '', newTotalEth, transactionFee: sumHexWEIsToRenderableEth(['0x0', customGasTotal]), sendAmount, - extraInfoRow, }, transaction: txData || transaction, - isSpeedUp: transaction.status === 'submitted', - isRetry: transaction.status === 'failed', + isSpeedUp: transaction.status === TRANSACTION_STATUSES.SUBMITTED, + isRetry: transaction.status === TRANSACTION_STATUSES.FAILED, txId: transaction.id, insufficientBalance, gasEstimatesLoading, @@ -200,16 +204,14 @@ const mapStateToProps = (state, ownProps) => { sendToken, balance, tokenBalance: getTokenBalance(state), - customGasLimitMessage, conversionRate, value, - customTotalSupplement, - minimumGasLimit, } } const mapDispatchToProps = (dispatch) => { - const updateCustomGasPrice = (newPrice) => dispatch(setCustomGasPrice(addHexPrefix(newPrice))) + const updateCustomGasPrice = (newPrice) => + dispatch(setCustomGasPrice(addHexPrefix(newPrice))) return { cancelAndClose: () => { @@ -218,7 +220,8 @@ const mapDispatchToProps = (dispatch) => { }, hideModal: () => dispatch(hideModal()), updateCustomGasPrice, - updateCustomGasLimit: (newLimit) => dispatch(setCustomGasLimit(addHexPrefix(newLimit))), + updateCustomGasLimit: (newLimit) => + dispatch(setCustomGasLimit(addHexPrefix(newLimit))), setGasData: (newLimit, newPrice) => { dispatch(setGasLimit(newLimit)) dispatch(setGasPrice(newPrice)) @@ -237,15 +240,13 @@ const mapDispatchToProps = (dispatch) => { hideGasButtonGroup: () => dispatch(hideGasButtonGroup()), hideSidebar: () => dispatch(hideSidebar()), fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)), - fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()), + fetchBasicGasAndTimeEstimates: () => + dispatch(fetchBasicGasAndTimeEstimates()), setGasTotal: (total) => dispatch(setGasTotal(total)), setAmountToMax: (maxAmountDataObject) => { dispatch(updateSendErrors({ amount: null })) dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject))) }, - updateSwapTxGas: (gasLimit, gasPrice) => { - dispatch(setSwapsTxGasParams(gasLimit, gasPrice)) - }, } } @@ -254,7 +255,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { gasPriceButtonGroupProps, // eslint-disable-next-line no-shadow isConfirm, - isSwap, txId, isSpeedUp, isRetry, @@ -267,7 +267,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { tokenBalance, customGasLimit, transaction, - minimumGasLimit, } = stateProps const { hideGasButtonGroup: dispatchHideGasButtonGroup, @@ -279,7 +278,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { cancelAndClose: dispatchCancelAndClose, hideModal: dispatchHideModal, setAmountToMax: dispatchSetAmountToMax, - updateSwapTxGas: dispatchUpdateSwapTxGas, ...otherDispatchProps } = dispatchProps @@ -288,10 +286,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { ...otherDispatchProps, ...ownProps, onSubmit: (gasLimit, gasPrice) => { - if (isSwap) { - dispatchUpdateSwapTxGas(gasLimit, gasPrice) - dispatchHideModal() - } else if (isConfirm) { + if (isConfirm) { const updatedTx = { ...transaction, txParams: { @@ -326,7 +321,8 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { }, gasPriceButtonGroupProps: { ...gasPriceButtonGroupProps, - handleGasPriceSelection: otherDispatchProps.updateCustomGasPrice, + handleGasPriceSelection: ({ gasPrice }) => + otherDispatchProps.updateCustomGasPrice(gasPrice), }, cancelAndClose: () => { dispatchCancelAndClose() @@ -334,37 +330,42 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { dispatchHideSidebar() } }, - disableSave: ( + disableSave: insufficientBalance || (isSpeedUp && customGasPrice === 0) || - customGasLimit < minimumGasLimit - ), + customGasLimit < Number(MIN_GAS_LIMIT_DEC), } } -export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(GasModalPageContainer) +export default connect( + mapStateToProps, + mapDispatchToProps, + mergeProps, +)(GasModalPageContainer) -function isConfirm (state) { +function isConfirm(state) { return Boolean(Object.keys(state.confirmTransaction.txData).length) } -function calcCustomGasPrice (customGasPriceInHex) { +function calcCustomGasPrice(customGasPriceInHex) { return Number(hexWEIToDecGWEI(customGasPriceInHex)) } -function calcCustomGasLimit (customGasLimitInHex) { +function calcCustomGasLimit(customGasLimitInHex) { return parseInt(customGasLimitInHex, 16) } -function sumHexWEIsToRenderableEth (hexWEIs) { +function sumHexWEIsToRenderableEth(hexWEIs) { const hexWEIsSum = hexWEIs.filter((n) => n).reduce(addHexes) - return formatETHFee(getValueFromWeiHex({ - value: hexWEIsSum, - toCurrency: 'ETH', - numberOfDecimals: 6, - })) + return formatETHFee( + getValueFromWeiHex({ + value: hexWEIsSum, + toCurrency: 'ETH', + numberOfDecimals: 6, + }), + ) } -function subtractHexWEIsFromRenderableEth (aHexWEI, bHexWEI) { +function subtractHexWEIsFromRenderableEth(aHexWEI, bHexWEI) { return formatETHFee(subtractHexWEIsToDec(aHexWEI, bHexWEI)) } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/index.scss b/ui/app/components/app/gas-customization/gas-modal-page-container/index.scss index 63c2259f8..fd09c01ee 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/index.scss +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/index.scss @@ -31,7 +31,8 @@ } &__header-close-text { - font-size: 14px; + @include H6; + color: #4eade7; position: absolute; top: 4px; @@ -42,10 +43,10 @@ } &__title { + @include H5; + color: $black; - font-size: 16px; font-weight: 500; - line-height: 16px; display: flex; justify-content: center; align-items: flex-start; @@ -56,21 +57,29 @@ display: none; } + &__tab { + margin-right: 0; + } + &__tabs { margin-top: 0; } &__tab { + @include H6; + width: 100%; - font-size: 14px; &:last-of-type { margin-right: 0; } - &--selected { + & button { + font-size: unset; + } + + &.tab--active button { color: $primary-blue; - border-bottom: 2px solid $primary-blue; } } } @@ -88,13 +97,14 @@ &__info-row, &__info-row--fade { + @include H7; + width: 100%; background: $polar; padding: 15px 21px; display: flex; flex-flow: column; color: $scorpion; - font-size: 12px; &__send-info, &__transaction-info, @@ -111,19 +121,20 @@ &__total-info { &__label { - font-size: 16px; + @include Paragraph; @media screen and (max-width: $break-small) { - font-size: 14px; + @include H6; } } &__value { - font-size: 16px; + @include Paragraph; + font-weight: bold; @media screen and (max-width: $break-small) { - font-size: 14px; + @include H6; } } } @@ -131,11 +142,11 @@ &__transaction-info, &__send-info { &__label { - font-size: 12px; + @include H7; } &__value { - font-size: 12px; + @include H6; } } } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js index ef1d1e311..b3c9f98f5 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js @@ -16,7 +16,9 @@ const mockBasicGasEstimates = { const propsMethodSpies = { cancelAndClose: sinon.spy(), onSubmit: sinon.spy(), - fetchBasicGasAndTimeEstimates: sinon.stub().returns(Promise.resolve(mockBasicGasEstimates)), + fetchBasicGasAndTimeEstimates: sinon + .stub() + .returns(Promise.resolve(mockBasicGasEstimates)), fetchGasEstimates: sinon.spy(), } @@ -64,11 +66,13 @@ describe('GasModalPageContainer Component', function () { let wrapper beforeEach(function () { - wrapper = shallow(( + wrapper = shallow( 'mockupdateCustomGasPrice'} updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} @@ -81,8 +85,8 @@ describe('GasModalPageContainer Component', function () { customGasLimitInHex="mockCustomGasLimitInHex" insufficientBalance={false} disableSave={false} - /> - )) + />, + ) }) afterEach(function () { @@ -103,7 +107,10 @@ describe('GasModalPageContainer Component', function () { wrapper.instance().componentDidMount() await timeout(250) assert.equal(propsMethodSpies.fetchGasEstimates.callCount, 1) - assert.equal(propsMethodSpies.fetchGasEstimates.getCall(0).args[0], 'mockBlockTime') + assert.equal( + propsMethodSpies.fetchGasEstimates.getCall(0).args[0], + 'mockBlockTime', + ) }) }) @@ -113,21 +120,14 @@ describe('GasModalPageContainer Component', function () { }) it('should pass correct props to PageContainer', function () { - const { - title, - subtitle, - disabled, - } = wrapper.find(PageContainer).props() + const { title, subtitle, disabled } = wrapper.find(PageContainer).props() assert.equal(title, 'customGas') assert.equal(subtitle, 'customGasSubTitle') assert.equal(disabled, false) }) it('should pass the correct onCancel and onClose methods to PageContainer', function () { - const { - onCancel, - onClose, - } = wrapper.find(PageContainer).props() + const { onCancel, onClose } = wrapper.find(PageContainer).props() assert.equal(propsMethodSpies.cancelAndClose.callCount, 0) onCancel() assert.equal(propsMethodSpies.cancelAndClose.callCount, 1) @@ -137,13 +137,18 @@ describe('GasModalPageContainer Component', function () { it('should pass the correct renderTabs property to PageContainer', function () { sinon.stub(GP, 'renderTabs').returns('mockTabs') - const renderTabsWrapperTester = shallow(( + const renderTabsWrapperTester = shallow( - ), { context: { t: (str1, str2) => (str2 ? str1 + str2 : str1) } }) - const { tabsComponent } = renderTabsWrapperTester.find(PageContainer).props() + />, + { context: { t: (str1, str2) => (str2 ? str1 + str2 : str1) } }, + ) + const { tabsComponent } = renderTabsWrapperTester + .find(PageContainer) + .props() assert.equal(tabsComponent, 'mockTabs') GasModalPageContainer.prototype.renderTabs.restore() }) @@ -184,16 +189,28 @@ describe('GasModalPageContainer Component', function () { assert.equal(GP.renderInfoRows.callCount, 2) - assert.deepEqual(GP.renderInfoRows.getCall(0).args, ['mockNewTotalFiat', 'mockNewTotalEth', 'mockSendAmount', 'mockTransactionFee', { label: 'mockLabel', value: 'mockValue' }]) - assert.deepEqual(GP.renderInfoRows.getCall(1).args, ['mockNewTotalFiat', 'mockNewTotalEth', 'mockSendAmount', 'mockTransactionFee', { label: 'mockLabel', value: 'mockValue' }]) + assert.deepEqual(GP.renderInfoRows.getCall(0).args, [ + 'mockNewTotalFiat', + 'mockNewTotalEth', + 'mockSendAmount', + 'mockTransactionFee', + ]) + assert.deepEqual(GP.renderInfoRows.getCall(1).args, [ + 'mockNewTotalFiat', + 'mockNewTotalEth', + 'mockSendAmount', + 'mockTransactionFee', + ]) }) it('should not render the basic tab if hideBasic is true', function () { - wrapper = shallow(( + wrapper = shallow( 'mockupdateCustomGasPrice'} updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} @@ -207,8 +224,8 @@ describe('GasModalPageContainer Component', function () { insufficientBalance={false} disableSave={false} hideBasic - /> - )) + />, + ) const renderTabsResult = wrapper.instance().renderTabs() const renderedTabs = shallow(renderTabsResult) @@ -220,7 +237,9 @@ describe('GasModalPageContainer Component', function () { describe('renderBasicTabContent', function () { it('should render', function () { - const renderBasicTabContentResult = wrapper.instance().renderBasicTabContent(mockGasPriceButtonGroupProps) + const renderBasicTabContentResult = wrapper + .instance() + .renderBasicTabContent(mockGasPriceButtonGroupProps) assert.deepEqual( renderBasicTabContentResult.props.gasPriceButtonGroupProps, @@ -232,24 +251,35 @@ describe('GasModalPageContainer Component', function () { describe('renderInfoRows', function () { it('should render the info rows with the passed data', function () { const baseClassName = 'gas-modal-content__info-row' - const renderedInfoRowsContainer = shallow(wrapper.instance().renderInfoRows( - 'mockNewTotalFiat', - ' mockNewTotalEth', - ' mockSendAmount', - ' mockTransactionFee', - )) + const renderedInfoRowsContainer = shallow( + wrapper + .instance() + .renderInfoRows( + 'mockNewTotalFiat', + ' mockNewTotalEth', + ' mockSendAmount', + ' mockTransactionFee', + ), + ) assert(renderedInfoRowsContainer.childAt(0).hasClass(baseClassName)) const renderedInfoRows = renderedInfoRowsContainer.childAt(0).children() assert.equal(renderedInfoRows.length, 4) assert(renderedInfoRows.at(0).hasClass(`${baseClassName}__send-info`)) - assert(renderedInfoRows.at(1).hasClass(`${baseClassName}__transaction-info`)) + assert( + renderedInfoRows.at(1).hasClass(`${baseClassName}__transaction-info`), + ) assert(renderedInfoRows.at(2).hasClass(`${baseClassName}__total-info`)) - assert(renderedInfoRows.at(3).hasClass(`${baseClassName}__fiat-total-info`)) + assert( + renderedInfoRows.at(3).hasClass(`${baseClassName}__fiat-total-info`), + ) assert.equal(renderedInfoRows.at(0).text(), 'sendAmount mockSendAmount') - assert.equal(renderedInfoRows.at(1).text(), 'transactionFee mockTransactionFee') + assert.equal( + renderedInfoRows.at(1).text(), + 'transactionFee mockTransactionFee', + ) assert.equal(renderedInfoRows.at(2).text(), 'newTotal mockNewTotalEth') assert.equal(renderedInfoRows.at(3).text(), 'mockNewTotalFiat') }) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js index 00e15f943..49687ee89 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js @@ -1,6 +1,7 @@ import assert from 'assert' import proxyquire from 'proxyquire' import sinon from 'sinon' +import { TRANSACTION_STATUSES } from '../../../../../../../shared/constants/transaction' let mapStateToProps let mapDispatchToProps @@ -36,8 +37,10 @@ proxyquire('../gas-modal-page-container.container.js', { }, }, '../../../../selectors': { - getBasicGasEstimateLoadingStatus: (s) => `mockBasicGasEstimateLoadingStatus:${Object.keys(s).length}`, - getRenderableBasicEstimateData: (s) => `mockRenderableBasicEstimateData:${Object.keys(s).length}`, + getBasicGasEstimateLoadingStatus: (s) => + `mockBasicGasEstimateLoadingStatus:${Object.keys(s).length}`, + getRenderableBasicEstimateData: (s) => + `mockRenderableBasicEstimateData:${Object.keys(s).length}`, getDefaultActiveButtonIndex: (a, b) => a + b, getCurrentEthBalance: (state) => state.metamask.balance || '0x0', getSendToken: () => null, @@ -50,7 +53,6 @@ proxyquire('../gas-modal-page-container.container.js', { }) describe('gas-modal-page-container container', function () { - describe('mapStateToProps()', function () { it('should map the correct properties to props', function () { const baseMockState = { @@ -62,8 +64,6 @@ describe('gas-modal-page-container container', function () { txData: { id: 34, }, - extraInfoRow: { label: 'mockLabel', value: 'mockValue' }, - minimumGasLimit: 21000, }, }, }, @@ -77,20 +77,23 @@ describe('gas-modal-page-container container', function () { }, currentCurrency: 'abc', conversionRate: 50, + usdConversionRate: 123, preferences: { showFiatInTestnets: false, }, provider: { type: 'mainnet', }, - currentNetworkTxList: [{ - id: 34, - txParams: { - gas: '0x1600000', - gasPrice: '0x3200000', - value: '0x640000000000000', + currentNetworkTxList: [ + { + id: 34, + txParams: { + gas: '0x1600000', + gasPrice: '0x3200000', + value: '0x640000000000000', + }, }, - }], + ], }, gas: { basicEstimates: { @@ -128,14 +131,12 @@ describe('gas-modal-page-container container', function () { newTotalFiat: '637.41', blockTime: 12, conversionRate: 50, - customGasLimitMessage: '', - customTotalSupplement: '', customModalGasLimitInHex: 'aaaaaaaa', customModalGasPriceInHex: 'ffffffff', customGasTotal: 'aaaaaaa955555556', customPriceIsSafe: true, gasChartProps: { - 'currentPrice': 4.294967295, + currentPrice: 4.294967295, estimatedTimes: [31, 62, 93, 124], estimatedTimesMax: 31, gasPrices: [3, 4, 5, 6], @@ -149,7 +150,6 @@ describe('gas-modal-page-container container', function () { gasEstimatesLoading: false, hideBasic: true, infoRowProps: { - extraInfoRow: { label: 'mockLabel', value: 'mockValue' }, originalTotalFiat: '637.41', originalTotalEth: '12.748189 ETH', newTotalFiat: '637.41', @@ -160,7 +160,6 @@ describe('gas-modal-page-container container', function () { insufficientBalance: true, isSpeedUp: false, isRetry: false, - isSwap: false, txId: 34, isEthereumNetwork: true, isMainnet: true, @@ -171,20 +170,40 @@ describe('gas-modal-page-container container', function () { id: 34, }, value: '0x640000000000000', - minimumGasLimit: 21000, } const baseMockOwnProps = { transaction: { id: 34 } } const tests = [ - { mockState: baseMockState, expectedResult: baseExpectedResult, mockOwnProps: baseMockOwnProps }, { - mockState: { ...baseMockState, metamask: { ...baseMockState.metamask, balance: '0xfffffffffffffffffffff' } }, - expectedResult: { ...baseExpectedResult, balance: '0xfffffffffffffffffffff', insufficientBalance: false }, + mockState: baseMockState, + expectedResult: baseExpectedResult, + mockOwnProps: baseMockOwnProps, + }, + { + mockState: { + ...baseMockState, + metamask: { + ...baseMockState.metamask, + balance: '0xfffffffffffffffffffff', + }, + }, + expectedResult: { + ...baseExpectedResult, + balance: '0xfffffffffffffffffffff', + insufficientBalance: false, + }, mockOwnProps: baseMockOwnProps, }, { mockState: baseMockState, - mockOwnProps: { ...baseMockOwnProps, transaction: { id: 34, status: 'submitted' } }, - expectedResult: { ...baseExpectedResult, isSpeedUp: true, transaction: { id: 34 } }, + mockOwnProps: { + ...baseMockOwnProps, + transaction: { id: 34, status: TRANSACTION_STATUSES.SUBMITTED }, + }, + expectedResult: { + ...baseExpectedResult, + isSpeedUp: true, + transaction: { id: 34 }, + }, }, { mockState: { @@ -255,10 +274,9 @@ describe('gas-modal-page-container container', function () { let result tests.forEach(({ mockState, mockOwnProps, expectedResult }) => { result = mapStateToProps(mockState, mockOwnProps) - assert.deepEqual(result, expectedResult) + assert.deepStrictEqual(result, expectedResult) }) }) - }) describe('mapDispatchToProps()', function () { @@ -298,14 +316,20 @@ describe('gas-modal-page-container container', function () { mapDispatchToPropsObject.updateCustomGasPrice('ffff') assert(dispatchSpy.calledOnce) assert(gasActionSpies.setCustomGasPrice.calledOnce) - assert.equal(gasActionSpies.setCustomGasPrice.getCall(0).args[0], '0xffff') + assert.strictEqual( + gasActionSpies.setCustomGasPrice.getCall(0).args[0], + '0xffff', + ) }) it('should dispatch a setCustomGasPrice action', function () { mapDispatchToPropsObject.updateCustomGasPrice('0xffff') assert(dispatchSpy.calledOnce) assert(gasActionSpies.setCustomGasPrice.calledOnce) - assert.equal(gasActionSpies.setCustomGasPrice.getCall(0).args[0], '0xffff') + assert.strictEqual( + gasActionSpies.setCustomGasPrice.getCall(0).args[0], + '0xffff', + ) }) }) @@ -314,7 +338,10 @@ describe('gas-modal-page-container container', function () { mapDispatchToPropsObject.updateCustomGasLimit('0x10') assert(dispatchSpy.calledOnce) assert(gasActionSpies.setCustomGasLimit.calledOnce) - assert.equal(gasActionSpies.setCustomGasLimit.getCall(0).args[0], '0x10') + assert.strictEqual( + gasActionSpies.setCustomGasLimit.getCall(0).args[0], + '0x10', + ) }) }) @@ -324,22 +351,21 @@ describe('gas-modal-page-container container', function () { assert(dispatchSpy.calledTwice) assert(actionSpies.setGasPrice.calledOnce) assert(actionSpies.setGasLimit.calledOnce) - assert.equal(actionSpies.setGasLimit.getCall(0).args[0], 'ffff') - assert.equal(actionSpies.setGasPrice.getCall(0).args[0], 'aaaa') + assert.strictEqual(actionSpies.setGasLimit.getCall(0).args[0], 'ffff') + assert.strictEqual(actionSpies.setGasPrice.getCall(0).args[0], 'aaaa') }) }) describe('updateConfirmTxGasAndCalculate()', function () { it('should dispatch a updateGasAndCalculate action with the correct props', function () { mapDispatchToPropsObject.updateConfirmTxGasAndCalculate('ffff', 'aaaa') - assert.equal(dispatchSpy.callCount, 3) + assert.strictEqual(dispatchSpy.callCount, 3) assert(actionSpies.setGasPrice.calledOnce) assert(actionSpies.setGasLimit.calledOnce) - assert.equal(actionSpies.setGasLimit.getCall(0).args[0], 'ffff') - assert.equal(actionSpies.setGasPrice.getCall(0).args[0], 'aaaa') + assert.strictEqual(actionSpies.setGasLimit.getCall(0).args[0], 'ffff') + assert.strictEqual(actionSpies.setGasPrice.getCall(0).args[0], 'aaaa') }) }) - }) describe('mergeProps', function () { @@ -384,77 +410,118 @@ describe('gas-modal-page-container container', function () { it('should return the expected props when isConfirm is true', function () { const result = mergeProps(stateProps, dispatchProps, ownProps) - assert.equal(result.isConfirm, true) - assert.equal(result.someOtherStateProp, 'baz') - assert.equal(result.gasPriceButtonGroupProps.someGasPriceButtonGroupProp, 'foo') - assert.equal(result.gasPriceButtonGroupProps.anotherGasPriceButtonGroupProp, 'bar') - assert.equal(result.someOwnProp, 123) + assert.strictEqual(result.isConfirm, true) + assert.strictEqual(result.someOtherStateProp, 'baz') + assert.strictEqual( + result.gasPriceButtonGroupProps.someGasPriceButtonGroupProp, + 'foo', + ) + assert.strictEqual( + result.gasPriceButtonGroupProps.anotherGasPriceButtonGroupProp, + 'bar', + ) + assert.strictEqual(result.someOwnProp, 123) - assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 0) - assert.equal(dispatchProps.setGasData.callCount, 0) - assert.equal(dispatchProps.hideGasButtonGroup.callCount, 0) - assert.equal(dispatchProps.hideModal.callCount, 0) + assert.strictEqual( + dispatchProps.updateConfirmTxGasAndCalculate.callCount, + 0, + ) + assert.strictEqual(dispatchProps.setGasData.callCount, 0) + assert.strictEqual(dispatchProps.hideGasButtonGroup.callCount, 0) + assert.strictEqual(dispatchProps.hideModal.callCount, 0) result.onSubmit() - assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 1) - assert.equal(dispatchProps.setGasData.callCount, 0) - assert.equal(dispatchProps.hideGasButtonGroup.callCount, 0) - assert.equal(dispatchProps.hideModal.callCount, 1) + assert.strictEqual( + dispatchProps.updateConfirmTxGasAndCalculate.callCount, + 1, + ) + assert.strictEqual(dispatchProps.setGasData.callCount, 0) + assert.strictEqual(dispatchProps.hideGasButtonGroup.callCount, 0) + assert.strictEqual(dispatchProps.hideModal.callCount, 1) - assert.equal(dispatchProps.updateCustomGasPrice.callCount, 0) - result.gasPriceButtonGroupProps.handleGasPriceSelection() - assert.equal(dispatchProps.updateCustomGasPrice.callCount, 1) + assert.strictEqual(dispatchProps.updateCustomGasPrice.callCount, 0) + result.gasPriceButtonGroupProps.handleGasPriceSelection({ + gasPrice: '0x0', + }) + assert.strictEqual(dispatchProps.updateCustomGasPrice.callCount, 1) - assert.equal(dispatchProps.someOtherDispatchProp.callCount, 0) + assert.strictEqual(dispatchProps.someOtherDispatchProp.callCount, 0) result.someOtherDispatchProp() - assert.equal(dispatchProps.someOtherDispatchProp.callCount, 1) + assert.strictEqual(dispatchProps.someOtherDispatchProp.callCount, 1) }) it('should return the expected props when isConfirm is false', function () { - const result = mergeProps({ ...stateProps, isConfirm: false }, dispatchProps, ownProps) + const result = mergeProps( + { ...stateProps, isConfirm: false }, + dispatchProps, + ownProps, + ) - assert.equal(result.isConfirm, false) - assert.equal(result.someOtherStateProp, 'baz') - assert.equal(result.gasPriceButtonGroupProps.someGasPriceButtonGroupProp, 'foo') - assert.equal(result.gasPriceButtonGroupProps.anotherGasPriceButtonGroupProp, 'bar') - assert.equal(result.someOwnProp, 123) + assert.strictEqual(result.isConfirm, false) + assert.strictEqual(result.someOtherStateProp, 'baz') + assert.strictEqual( + result.gasPriceButtonGroupProps.someGasPriceButtonGroupProp, + 'foo', + ) + assert.strictEqual( + result.gasPriceButtonGroupProps.anotherGasPriceButtonGroupProp, + 'bar', + ) + assert.strictEqual(result.someOwnProp, 123) - assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 0) - assert.equal(dispatchProps.setGasData.callCount, 0) - assert.equal(dispatchProps.hideGasButtonGroup.callCount, 0) - assert.equal(dispatchProps.cancelAndClose.callCount, 0) + assert.strictEqual( + dispatchProps.updateConfirmTxGasAndCalculate.callCount, + 0, + ) + assert.strictEqual(dispatchProps.setGasData.callCount, 0) + assert.strictEqual(dispatchProps.hideGasButtonGroup.callCount, 0) + assert.strictEqual(dispatchProps.cancelAndClose.callCount, 0) result.onSubmit('mockNewLimit', 'mockNewPrice') - assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 0) - assert.equal(dispatchProps.setGasData.callCount, 1) - assert.deepEqual(dispatchProps.setGasData.getCall(0).args, ['mockNewLimit', 'mockNewPrice']) - assert.equal(dispatchProps.hideGasButtonGroup.callCount, 1) - assert.equal(dispatchProps.cancelAndClose.callCount, 1) + assert.strictEqual( + dispatchProps.updateConfirmTxGasAndCalculate.callCount, + 0, + ) + assert.strictEqual(dispatchProps.setGasData.callCount, 1) + assert.deepStrictEqual(dispatchProps.setGasData.getCall(0).args, [ + 'mockNewLimit', + 'mockNewPrice', + ]) + assert.strictEqual(dispatchProps.hideGasButtonGroup.callCount, 1) + assert.strictEqual(dispatchProps.cancelAndClose.callCount, 1) - assert.equal(dispatchProps.updateCustomGasPrice.callCount, 0) - result.gasPriceButtonGroupProps.handleGasPriceSelection() - assert.equal(dispatchProps.updateCustomGasPrice.callCount, 1) + assert.strictEqual(dispatchProps.updateCustomGasPrice.callCount, 0) + result.gasPriceButtonGroupProps.handleGasPriceSelection({ + gasPrice: '0x0', + }) + assert.strictEqual(dispatchProps.updateCustomGasPrice.callCount, 1) - assert.equal(dispatchProps.someOtherDispatchProp.callCount, 0) + assert.strictEqual(dispatchProps.someOtherDispatchProp.callCount, 0) result.someOtherDispatchProp() - assert.equal(dispatchProps.someOtherDispatchProp.callCount, 1) + assert.strictEqual(dispatchProps.someOtherDispatchProp.callCount, 1) }) it('should dispatch the expected actions from obSubmit when isConfirm is false and isSpeedUp is true', function () { - const result = mergeProps({ ...stateProps, isSpeedUp: true, isConfirm: false }, dispatchProps, ownProps) + const result = mergeProps( + { ...stateProps, isSpeedUp: true, isConfirm: false }, + dispatchProps, + ownProps, + ) result.onSubmit() - assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 0) - assert.equal(dispatchProps.setGasData.callCount, 0) - assert.equal(dispatchProps.hideGasButtonGroup.callCount, 0) - assert.equal(dispatchProps.cancelAndClose.callCount, 1) + assert.strictEqual( + dispatchProps.updateConfirmTxGasAndCalculate.callCount, + 0, + ) + assert.strictEqual(dispatchProps.setGasData.callCount, 0) + assert.strictEqual(dispatchProps.hideGasButtonGroup.callCount, 0) + assert.strictEqual(dispatchProps.cancelAndClose.callCount, 1) - assert.equal(dispatchProps.createSpeedUpTransaction.callCount, 1) - assert.equal(dispatchProps.hideSidebar.callCount, 1) + assert.strictEqual(dispatchProps.createSpeedUpTransaction.callCount, 1) + assert.strictEqual(dispatchProps.hideSidebar.callCount, 1) }) }) - }) diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js index 8cf327013..044df3dc5 100644 --- a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js +++ b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js @@ -5,7 +5,8 @@ import Button from '../../../ui/button' import { GAS_ESTIMATE_TYPES } from '../../../../helpers/constants/common' const GAS_OBJECT_PROPTYPES_SHAPE = { - gasEstimateType: PropTypes.oneOf(Object.values(GAS_ESTIMATE_TYPES)).isRequired, + gasEstimateType: PropTypes.oneOf(Object.values(GAS_ESTIMATE_TYPES)) + .isRequired, feeInPrimaryCurrency: PropTypes.string, feeInSecondaryCurrency: PropTypes.string, timeEstimate: PropTypes.string, @@ -21,14 +22,16 @@ export default class GasPriceButtonGroup extends Component { buttonDataLoading: PropTypes.bool, className: PropTypes.string, defaultActiveButtonIndex: PropTypes.number, - gasButtonInfo: PropTypes.arrayOf(PropTypes.shape(GAS_OBJECT_PROPTYPES_SHAPE)), + gasButtonInfo: PropTypes.arrayOf( + PropTypes.shape(GAS_OBJECT_PROPTYPES_SHAPE), + ), handleGasPriceSelection: PropTypes.func, newActiveButtonIndex: PropTypes.number, noButtonActiveByDefault: PropTypes.bool, showCheck: PropTypes.bool, } - gasEstimateTypeLabel (gasEstimateType) { + gasEstimateTypeLabel(gasEstimateType) { if (gasEstimateType === GAS_ESTIMATE_TYPES.SLOW) { return this.context.t('slow') } else if (gasEstimateType === GAS_ESTIMATE_TYPES.AVERAGE) { @@ -41,45 +44,72 @@ export default class GasPriceButtonGroup extends Component { throw new Error(`Unrecognized gas estimate type: ${gasEstimateType}`) } - renderButtonContent ({ - gasEstimateType, - feeInPrimaryCurrency, - feeInSecondaryCurrency, - timeEstimate, - }, { - className, - showCheck, - }) { + renderButtonContent( + { + gasEstimateType, + feeInPrimaryCurrency, + feeInSecondaryCurrency, + timeEstimate, + }, + { className, showCheck }, + ) { return (
    - { gasEstimateType &&
    { this.gasEstimateTypeLabel(gasEstimateType) }
    } - { timeEstimate &&
    { timeEstimate }
    } - { feeInPrimaryCurrency &&
    { feeInPrimaryCurrency }
    } - { feeInSecondaryCurrency &&
    { feeInSecondaryCurrency }
    } - { showCheck &&
    } + {gasEstimateType && ( +
    + {this.gasEstimateTypeLabel(gasEstimateType)} +
    + )} + {timeEstimate && ( +
    {timeEstimate}
    + )} + {feeInPrimaryCurrency && ( +
    + {feeInPrimaryCurrency} +
    + )} + {feeInSecondaryCurrency && ( +
    + {feeInSecondaryCurrency} +
    + )} + {showCheck && ( +
    + +
    + )}
    ) } - renderButton ({ - priceInHexWei, - ...renderableGasInfo - }, { - buttonDataLoading: _, - handleGasPriceSelection, - ...buttonContentPropsAndFlags - }, index) { + renderButton( + { priceInHexWei, ...renderableGasInfo }, + { + buttonDataLoading: _, + handleGasPriceSelection, + ...buttonContentPropsAndFlags + }, + index, + ) { return ( ) } - render () { + render() { const { gasButtonInfo, defaultActiveButtonIndex = 1, @@ -89,19 +119,21 @@ export default class GasPriceButtonGroup extends Component { ...buttonPropsAndFlags } = this.props - return ( - buttonDataLoading - ?
    {this.context.t('loading')}
    - : ( - - {gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index))} - - ) + return buttonDataLoading ? ( +
    + {this.context.t('loading')} +
    + ) : ( + + {gasButtonInfo.map((obj, index) => + this.renderButton(obj, buttonPropsAndFlags, index), + )} + ) } } diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/index.scss b/ui/app/components/app/gas-customization/gas-price-button-group/index.scss index fc614d011..2a2b7926c 100644 --- a/ui/app/components/app/gas-customization/gas-price-button-group/index.scss +++ b/ui/app/components/app/gas-customization/gas-price-button-group/index.scss @@ -7,7 +7,8 @@ padding-right: 20px; &__primary-currency { - font-size: 18px; + @include H4; + height: 20.5px; margin-bottom: 7.5px; } @@ -24,15 +25,16 @@ .button-group__button, .button-group__button--active { + @include H7; + height: 130px; max-width: 108px; - font-size: 12px; flex-direction: column; align-items: center; display: flex; padding-top: 17px; border-radius: 4px; - border: 2px solid $spindle; + border-color: $spindle; background: $white; color: $scorpion; @@ -50,7 +52,7 @@ } .button-group__button--active { - border: 2px solid $primary-blue; + border-color: $primary-blue; color: $scorpion; i { @@ -66,18 +68,18 @@ .gas-price-button-group--small { display: flex; justify-content: stretch; - height: 54px; + min-height: 54px; @media screen and (max-width: $break-small) { max-width: 260px; } &__button-fiat-price { - font-size: 13px; + @include H6; } &__button-label { - font-size: 16px; + @include Paragraph; } &__label { @@ -87,22 +89,22 @@ } &__primary-currency { - font-size: 12px; - line-height: 12px; + @include H7; + padding-bottom: 2px; @media screen and (max-width: 575px) { - font-size: 10px; + @include H8; } } &__secondary-currency { - font-size: 12px; - line-height: 12px; + @include H7; + padding-bottom: 2px; @media screen and (max-width: 575px) { - font-size: 10px; + @include H8; } } @@ -114,7 +116,7 @@ .button-group__button--active { background: white; color: $scorpion; - padding: 0 4px; + padding: 4px; div { display: flex; @@ -150,26 +152,28 @@ width: 95%; &__button-fiat-price { - font-size: 13px; + @include H6; } &__button-label { - font-size: 16px; + @include Paragraph; } &__label { + @include H8; + font-weight: 500; - font-size: 10px; text-transform: capitalize; } &__primary-currency { - font-size: 11px; + @include H7; + margin-top: 3px; } &__secondary-currency { - font-size: 11px; + @include H7; } &__loading-container { @@ -177,7 +181,8 @@ } &__time-estimate { - font-size: 12px; + @include H7; + font-weight: 500; margin-top: 4px; color: $black; @@ -217,10 +222,10 @@ .button-group__button--active { background: #f7fcff; - border: 2px solid #2c8bdc; + border-color: #2c8bdc; &:first-child { - border: 2px solid #2c8bdc; + border-color: #2c8bdc; } .button-check-wrapper { @@ -238,9 +243,10 @@ } i { + @include H7; + display: flex; color: $primary-blue; - font-size: 12px; } } } diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js b/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js index 4cd44066c..966d4e115 100644 --- a/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js +++ b/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js @@ -3,6 +3,7 @@ import React from 'react' import sinon from 'sinon' import shallow from '../../../../../../lib/shallow-with-context' import GasPriceButtonGroup from '../gas-price-button-group.component' +import { GAS_ESTIMATE_TYPES } from '../../../../../helpers/constants/common' import ButtonGroup from '../../../../ui/button-group' @@ -17,18 +18,21 @@ describe('GasPriceButtonGroup Component', function () { className: 'gas-price-button-group', gasButtonInfo: [ { + gasEstimateType: GAS_ESTIMATE_TYPES.SLOW, feeInPrimaryCurrency: '$0.52', feeInSecondaryCurrency: '0.0048 ETH', timeEstimate: '~ 1 min 0 sec', priceInHexWei: '0xa1b2c3f', }, { + gasEstimateType: GAS_ESTIMATE_TYPES.AVERAGE, feeInPrimaryCurrency: '$0.39', feeInSecondaryCurrency: '0.004 ETH', timeEstimate: '~ 1 min 30 sec', priceInHexWei: '0xa1b2c39', }, { + gasEstimateType: GAS_ESTIMATE_TYPES.FAST, feeInPrimaryCurrency: '$0.30', feeInSecondaryCurrency: '0.00354 ETH', timeEstimate: '~ 2 min 1 sec', @@ -43,18 +47,15 @@ describe('GasPriceButtonGroup Component', function () { mockButtonPropsAndFlags = { className: mockGasPriceButtonGroupProps.className, - handleGasPriceSelection: mockGasPriceButtonGroupProps.handleGasPriceSelection, + handleGasPriceSelection: + mockGasPriceButtonGroupProps.handleGasPriceSelection, showCheck: mockGasPriceButtonGroupProps.showCheck, } sinon.spy(GasPriceButtonGroup.prototype, 'renderButton') sinon.spy(GasPriceButtonGroup.prototype, 'renderButtonContent') - wrapper = shallow(( - - )) + wrapper = shallow() }) afterEach(function () { @@ -72,13 +73,13 @@ describe('GasPriceButtonGroup Component', function () { defaultActiveButtonIndex, noButtonActiveByDefault, } = wrapper.props() - assert.equal(className, 'gas-price-button-group') - assert.equal(defaultActiveButtonIndex, 2) - assert.equal(noButtonActiveByDefault, true) + assert.strictEqual(className, 'gas-price-button-group') + assert.strictEqual(defaultActiveButtonIndex, 2) + assert.strictEqual(noButtonActiveByDefault, true) }) - function renderButtonArgsTest (i, mockPropsAndFlags) { - assert.deepEqual( + function renderButtonArgsTest(i, mockPropsAndFlags) { + assert.deepStrictEqual( GasPriceButtonGroup.prototype.renderButton.getCall(i).args, [ { ...mockGasPriceButtonGroupProps.gasButtonInfo[i] }, @@ -89,7 +90,10 @@ describe('GasPriceButtonGroup Component', function () { } it('should call this.renderButton 3 times, with the correct args', function () { - assert.equal(GasPriceButtonGroup.prototype.renderButton.callCount, 3) + assert.strictEqual( + GasPriceButtonGroup.prototype.renderButton.callCount, + 3, + ) renderButtonArgsTest(0, mockButtonPropsAndFlags) renderButtonArgsTest(1, mockButtonPropsAndFlags) renderButtonArgsTest(2, mockButtonPropsAndFlags) @@ -99,7 +103,7 @@ describe('GasPriceButtonGroup Component', function () { wrapper.setProps({ buttonDataLoading: true }) assert(wrapper.is('div')) assert(wrapper.hasClass('gas-price-button-group__loading-container')) - assert.equal(wrapper.text(), 'loading') + assert.strictEqual(wrapper.text(), 'loading') }) }) @@ -108,42 +112,59 @@ describe('GasPriceButtonGroup Component', function () { beforeEach(function () { GasPriceButtonGroup.prototype.renderButtonContent.resetHistory() - const renderButtonResult = GasPriceButtonGroup.prototype.renderButton( - { ...mockGasPriceButtonGroupProps.gasButtonInfo[0] }, - mockButtonPropsAndFlags, - ) + const renderButtonResult = wrapper + .instance() + .renderButton( + { ...mockGasPriceButtonGroupProps.gasButtonInfo[0] }, + mockButtonPropsAndFlags, + ) wrappedRenderButtonResult = shallow(renderButtonResult) }) it('should render a button', function () { - assert.equal(wrappedRenderButtonResult.type(), 'button') + assert.strictEqual(wrappedRenderButtonResult.type(), 'button') }) it('should call the correct method when clicked', function () { - assert.equal(mockGasPriceButtonGroupProps.handleGasPriceSelection.callCount, 0) + assert.strictEqual( + mockGasPriceButtonGroupProps.handleGasPriceSelection.callCount, + 0, + ) wrappedRenderButtonResult.props().onClick() - assert.equal(mockGasPriceButtonGroupProps.handleGasPriceSelection.callCount, 1) - assert.deepEqual( + assert.strictEqual( + mockGasPriceButtonGroupProps.handleGasPriceSelection.callCount, + 1, + ) + assert.deepStrictEqual( mockGasPriceButtonGroupProps.handleGasPriceSelection.getCall(0).args, - [mockGasPriceButtonGroupProps.gasButtonInfo[0].priceInHexWei], + [ + { + gasPrice: + mockGasPriceButtonGroupProps.gasButtonInfo[0].priceInHexWei, + gasEstimateType: + mockGasPriceButtonGroupProps.gasButtonInfo[0].gasEstimateType, + }, + ], ) }) it('should call this.renderButtonContent with the correct args', function () { - assert.equal(GasPriceButtonGroup.prototype.renderButtonContent.callCount, 1) + assert.strictEqual( + GasPriceButtonGroup.prototype.renderButtonContent.callCount, + 1, + ) const { feeInPrimaryCurrency, feeInSecondaryCurrency, timeEstimate, + gasEstimateType, } = mockGasPriceButtonGroupProps.gasButtonInfo[0] - const { - showCheck, - className, - } = mockGasPriceButtonGroupProps - assert.deepEqual( + const { showCheck, className } = mockGasPriceButtonGroupProps + assert.deepStrictEqual( GasPriceButtonGroup.prototype.renderButtonContent.getCall(0).args, [ { + gasEstimateType, feeInPrimaryCurrency, feeInSecondaryCurrency, timeEstimate, @@ -159,76 +180,144 @@ describe('GasPriceButtonGroup Component', function () { describe('renderButtonContent', function () { it('should render a label if passed a gasEstimateType', function () { - const renderButtonContentResult = wrapper.instance().renderButtonContent({ - gasEstimateType: 'SLOW', - }, { - className: 'someClass', - }) - const wrappedRenderButtonContentResult = shallow(renderButtonContentResult) - assert.equal(wrappedRenderButtonContentResult.childAt(0).children().length, 1) - assert.equal(wrappedRenderButtonContentResult.find('.someClass__label').text(), 'slow') + const renderButtonContentResult = wrapper.instance().renderButtonContent( + { + gasEstimateType: 'SLOW', + }, + { + className: 'someClass', + }, + ) + const wrappedRenderButtonContentResult = shallow( + renderButtonContentResult, + ) + assert.strictEqual( + wrappedRenderButtonContentResult.childAt(0).children().length, + 1, + ) + assert.strictEqual( + wrappedRenderButtonContentResult.find('.someClass__label').text(), + 'slow', + ) }) it('should render a feeInPrimaryCurrency if passed a feeInPrimaryCurrency', function () { - const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent({ - feeInPrimaryCurrency: 'mockFeeInPrimaryCurrency', - }, { - className: 'someClass', - }) - const wrappedRenderButtonContentResult = shallow(renderButtonContentResult) - assert.equal(wrappedRenderButtonContentResult.childAt(0).children().length, 1) - assert.equal(wrappedRenderButtonContentResult.find('.someClass__primary-currency').text(), 'mockFeeInPrimaryCurrency') + const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent( + { + feeInPrimaryCurrency: 'mockFeeInPrimaryCurrency', + }, + { + className: 'someClass', + }, + ) + const wrappedRenderButtonContentResult = shallow( + renderButtonContentResult, + ) + assert.strictEqual( + wrappedRenderButtonContentResult.childAt(0).children().length, + 1, + ) + assert.strictEqual( + wrappedRenderButtonContentResult + .find('.someClass__primary-currency') + .text(), + 'mockFeeInPrimaryCurrency', + ) }) it('should render a feeInSecondaryCurrency if passed a feeInSecondaryCurrency', function () { - const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent({ - feeInSecondaryCurrency: 'mockFeeInSecondaryCurrency', - }, { - className: 'someClass', - }) - const wrappedRenderButtonContentResult = shallow(renderButtonContentResult) - assert.equal(wrappedRenderButtonContentResult.childAt(0).children().length, 1) - assert.equal(wrappedRenderButtonContentResult.find('.someClass__secondary-currency').text(), 'mockFeeInSecondaryCurrency') + const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent( + { + feeInSecondaryCurrency: 'mockFeeInSecondaryCurrency', + }, + { + className: 'someClass', + }, + ) + const wrappedRenderButtonContentResult = shallow( + renderButtonContentResult, + ) + assert.strictEqual( + wrappedRenderButtonContentResult.childAt(0).children().length, + 1, + ) + assert.strictEqual( + wrappedRenderButtonContentResult + .find('.someClass__secondary-currency') + .text(), + 'mockFeeInSecondaryCurrency', + ) }) it('should render a timeEstimate if passed a timeEstimate', function () { - const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent({ - timeEstimate: 'mockTimeEstimate', - }, { - className: 'someClass', - }) - const wrappedRenderButtonContentResult = shallow(renderButtonContentResult) - assert.equal(wrappedRenderButtonContentResult.childAt(0).children().length, 1) - assert.equal(wrappedRenderButtonContentResult.find('.someClass__time-estimate').text(), 'mockTimeEstimate') + const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent( + { + timeEstimate: 'mockTimeEstimate', + }, + { + className: 'someClass', + }, + ) + const wrappedRenderButtonContentResult = shallow( + renderButtonContentResult, + ) + assert.strictEqual( + wrappedRenderButtonContentResult.childAt(0).children().length, + 1, + ) + assert.strictEqual( + wrappedRenderButtonContentResult + .find('.someClass__time-estimate') + .text(), + 'mockTimeEstimate', + ) }) it('should render a check if showCheck is true', function () { - const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent({}, { - className: 'someClass', - showCheck: true, - }) - const wrappedRenderButtonContentResult = shallow(renderButtonContentResult) - assert.equal(wrappedRenderButtonContentResult.find('.fa-check').length, 1) + const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent( + {}, + { + className: 'someClass', + showCheck: true, + }, + ) + const wrappedRenderButtonContentResult = shallow( + renderButtonContentResult, + ) + assert.strictEqual( + wrappedRenderButtonContentResult.find('.fa-check').length, + 1, + ) }) it('should render all elements if all args passed', function () { - const renderButtonContentResult = wrapper.instance().renderButtonContent({ - gasEstimateType: 'SLOW', - feeInPrimaryCurrency: 'mockFeeInPrimaryCurrency', - feeInSecondaryCurrency: 'mockFeeInSecondaryCurrency', - timeEstimate: 'mockTimeEstimate', - }, { - className: 'someClass', - showCheck: true, - }) - const wrappedRenderButtonContentResult = shallow(renderButtonContentResult) - assert.equal(wrappedRenderButtonContentResult.children().length, 5) + const renderButtonContentResult = wrapper.instance().renderButtonContent( + { + gasEstimateType: 'SLOW', + feeInPrimaryCurrency: 'mockFeeInPrimaryCurrency', + feeInSecondaryCurrency: 'mockFeeInSecondaryCurrency', + timeEstimate: 'mockTimeEstimate', + }, + { + className: 'someClass', + showCheck: true, + }, + ) + const wrappedRenderButtonContentResult = shallow( + renderButtonContentResult, + ) + assert.strictEqual(wrappedRenderButtonContentResult.children().length, 5) }) it('should render no elements if all args passed', function () { - const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent({}, {}) - const wrappedRenderButtonContentResult = shallow(renderButtonContentResult) - assert.equal(wrappedRenderButtonContentResult.children().length, 0) + const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent( + {}, + {}, + ) + const wrappedRenderButtonContentResult = shallow( + renderButtonContentResult, + ) + assert.strictEqual(wrappedRenderButtonContentResult.children().length, 0) }) }) }) diff --git a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.component.js b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.component.js index ef5f9e074..e01001ebf 100644 --- a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.component.js +++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.component.js @@ -24,7 +24,7 @@ export default class GasPriceChart extends Component { updateCustomGasPrice: PropTypes.func, } - renderChart () { + renderChart() { const { currentPrice, gasPrices, @@ -33,7 +33,13 @@ export default class GasPriceChart extends Component { estimatedTimesMax, updateCustomGasPrice, } = this.props - const chart = generateChart(gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax, this.context.t) + const chart = generateChart( + gasPrices, + estimatedTimes, + gasPricesMax, + estimatedTimesMax, + this.context.t, + ) setTimeout(function () { setTickPosition('y', 0, -5, 8) setTickPosition('y', 1, -3, -5) @@ -44,9 +50,15 @@ export default class GasPriceChart extends Component { const { x: yAxisX } = getCoordinateData('.c3-axis-y-label') const { x: tickX } = getCoordinateData('.c3-axis-x .tick') - d3.select('.c3-axis-x .tick').attr('transform', `translate(${(domainX - tickX) / 2}, 0)`) + d3.select('.c3-axis-x .tick').attr( + 'transform', + `translate(${(domainX - tickX) / 2}, 0)`, + ) d3.select('.c3-axis-x-label').attr('transform', 'translate(0,-15)') - d3.select('.c3-axis-y-label').attr('transform', `translate(${domainX - yAxisX - 12}, 2) rotate(-90)`) + d3.select('.c3-axis-y-label').attr( + 'transform', + `translate(${domainX - yAxisX - 12}, 2) rotate(-90)`, + ) d3.select('.c3-xgrid-focus line').attr('y2', 98) d3.select('.c3-chart').on('mouseout', () => { @@ -58,7 +70,9 @@ export default class GasPriceChart extends Component { updateCustomGasPrice(newGasPrice) }) - const { x: chartXStart, width: chartWidth } = getCoordinateData('.c3-areas-data1') + const { x: chartXStart, width: chartWidth } = getCoordinateData( + '.c3-areas-data1', + ) handleChartUpdate({ chart, @@ -82,7 +96,7 @@ export default class GasPriceChart extends Component { this.chart = chart } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { const { gasPrices, currentPrice: newPrice } = this.props if (prevProps.currentPrice !== newPrice) { @@ -95,11 +109,11 @@ export default class GasPriceChart extends Component { } } - componentDidMount () { + componentDidMount() { this.renderChart() } - render () { + render() { return (
    diff --git a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js index 4651b70f8..6fa809686 100644 --- a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js +++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js @@ -8,7 +8,14 @@ import { bigNumDiv, } from '../../../../helpers/utils/gas-time-estimates.util' -export function handleMouseMove ({ xMousePos, chartXStart, chartWidth, gasPrices, estimatedTimes, chart }) { +export function handleMouseMove({ + xMousePos, + chartXStart, + chartWidth, + gasPrices, + estimatedTimes, + chart, +}) { const { currentPosValue, newTimeEstimate } = getNewXandTimeEstimate({ xMousePos, chartXStart, @@ -23,19 +30,26 @@ export function handleMouseMove ({ xMousePos, chartXStart, chartWidth, gasPrices } const indexOfNewCircle = estimatedTimes.length + 1 - const dataUIObj = generateDataUIObj(currentPosValue, indexOfNewCircle, newTimeEstimate) + const dataUIObj = generateDataUIObj( + currentPosValue, + indexOfNewCircle, + newTimeEstimate, + ) chart.internal.overlayPoint(dataUIObj, indexOfNewCircle) - chart.internal.showTooltip([dataUIObj], d3.select('.c3-areas-data1')._groups[0]) + chart.internal.showTooltip( + [dataUIObj], + d3.select('.c3-areas-data1')._groups[0], + ) chart.internal.showXGridFocus([dataUIObj]) } -export function getCoordinateData (selector) { +export function getCoordinateData(selector) { const node = d3.select(selector).node() return node ? node.getBoundingClientRect() : {} } -export function generateDataUIObj (x, index, value) { +export function generateDataUIObj(x, index, value) { return { x, value, @@ -45,7 +59,7 @@ export function generateDataUIObj (x, index, value) { } } -export function handleChartUpdate ({ chart, gasPrices, newPrice, cssId }) { +export function handleChartUpdate({ chart, gasPrices, newPrice, cssId }) { const { closestLowerValueIndex, closestLowerValue, @@ -67,11 +81,20 @@ export function handleChartUpdate ({ chart, gasPrices, newPrice, cssId }) { } } -export function getNewXandTimeEstimate ({ xMousePos, chartXStart, chartWidth, gasPrices, estimatedTimes }) { +export function getNewXandTimeEstimate({ + xMousePos, + chartXStart, + chartWidth, + gasPrices, + estimatedTimes, +}) { const chartMouseXPos = bigNumMinus(xMousePos, chartXStart) const posPercentile = bigNumDiv(chartMouseXPos, chartWidth) - const currentPosValue = (bigNumMinus(gasPrices[gasPrices.length - 1], gasPrices[0])) + const currentPosValue = bigNumMinus( + gasPrices[gasPrices.length - 1], + gasPrices[0], + ) .times(newBigSigDig(posPercentile)) .plus(newBigSigDig(gasPrices[0])) .toNumber() @@ -85,22 +108,22 @@ export function getNewXandTimeEstimate ({ xMousePos, chartXStart, chartWidth, ga return !closestHigherValue || !closestLowerValue ? { - currentPosValue: null, - newTimeEstimate: null, - } + currentPosValue: null, + newTimeEstimate: null, + } : { - currentPosValue, - newTimeEstimate: extrapolateY({ - higherY: estimatedTimes[closestHigherValueIndex], - lowerY: estimatedTimes[closestLowerValueIndex], - higherX: closestHigherValue, - lowerX: closestLowerValue, - xForExtrapolation: currentPosValue, - }), - } + currentPosValue, + newTimeEstimate: extrapolateY({ + higherY: estimatedTimes[closestHigherValueIndex], + lowerY: estimatedTimes[closestLowerValueIndex], + higherX: closestHigherValue, + lowerX: closestLowerValue, + xForExtrapolation: currentPosValue, + }), + } } -export function hideDataUI (chart, dataNodeId) { +export function hideDataUI(chart, dataNodeId) { const overLayedCircle = d3.select(dataNodeId) if (!overLayedCircle.empty()) { overLayedCircle.remove() @@ -109,7 +132,7 @@ export function hideDataUI (chart, dataNodeId) { chart.internal.hideXGridFocus() } -export function setTickPosition (axis, n, newPosition, secondNewPosition) { +export function setTickPosition(axis, n, newPosition, secondNewPosition) { const positionToShift = axis === 'y' ? 'x' : 'y' const secondPositionToShift = axis === 'y' ? 'y' : 'x' d3.select('#chart') @@ -125,14 +148,23 @@ export function setTickPosition (axis, n, newPosition, secondNewPosition) { } /* eslint-disable @babel/no-invalid-this */ -export function appendOrUpdateCircle ({ data, itemIndex, cx, cy, cssId, appendOnly }) { +export function appendOrUpdateCircle({ + data, + itemIndex, + cx, + cy, + cssId, + appendOnly, +}) { const circle = this.main .select(`.c3-selected-circles${this.getTargetSelectorSuffix(data.id)}`) .selectAll(`.c3-selected-circle-${itemIndex}`) if (appendOnly || circle.empty()) { - circle.data([data]) - .enter().append('circle') + circle + .data([data]) + .enter() + .append('circle') .attr('class', () => this.generateClass('c3-selected-circle', itemIndex)) .attr('id', cssId) .attr('cx', cx) @@ -140,14 +172,12 @@ export function appendOrUpdateCircle ({ data, itemIndex, cx, cy, cssId, appendOn .attr('stroke', () => this.color(data)) .attr('r', 6) } else { - circle.data([data]) - .attr('cx', cx) - .attr('cy', cy) + circle.data([data]).attr('cx', cx).attr('cy', cy) } } /* eslint-enable @babel/no-invalid-this */ -export function setSelectedCircle ({ +export function setSelectedCircle({ chart, newPrice, closestLowerValueIndex, @@ -157,8 +187,12 @@ export function setSelectedCircle ({ }) { const numberOfValues = chart.internal.data.xs.data1.length - const { x: lowerX, y: lowerY } = getCoordinateData(`.c3-circle-${closestLowerValueIndex}`) - let { x: higherX, y: higherY } = getCoordinateData(`.c3-circle-${closestHigherValueIndex}`) + const { x: lowerX, y: lowerY } = getCoordinateData( + `.c3-circle-${closestLowerValueIndex}`, + ) + let { x: higherX, y: higherY } = getCoordinateData( + `.c3-circle-${closestHigherValueIndex}`, + ) let count = closestHigherValueIndex + 1 if (lowerX && higherX) { @@ -174,7 +208,13 @@ export function setSelectedCircle ({ .div(bigNumMinus(closestHigherValue, closestLowerValue)) .plus(newBigSigDig(lowerX)) - const newTimeEstimate = extrapolateY({ higherY, lowerY, higherX, lowerX, xForExtrapolation: currentX }) + const newTimeEstimate = extrapolateY({ + higherY, + lowerY, + higherX, + lowerX, + xForExtrapolation: currentX, + }) chart.internal.selectPoint( generateDataUIObj(currentX.toNumber(), numberOfValues, newTimeEstimate), @@ -182,7 +222,12 @@ export function setSelectedCircle ({ ) } -export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax) { +export function generateChart( + gasPrices, + estimatedTimes, + gasPricesMax, + estimatedTimesMax, +) { const gasPricesMaxPadded = gasPricesMax + 1 const chart = c3.generate({ size: { @@ -215,7 +260,7 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate tick: { values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMax)], outer: false, - format (val) { + format(val) { return `${val} GWEI` }, }, @@ -228,7 +273,10 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate y: { padding: { top: 7, bottom: 7 }, tick: { - values: [Math.floor(estimatedTimesMax * 0.05), Math.ceil(estimatedTimesMax * 0.97)], + values: [ + Math.floor(estimatedTimesMax * 0.05), + Math.ceil(estimatedTimesMax * 0.97), + ], outer: false, }, label: { @@ -259,42 +307,59 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate format: { title: (v) => v.toPrecision(4), }, - contents (d) { + contents(d) { const titleFormat = this.config.tooltip_format_title let text d.forEach((el) => { if (el && (el.value || el.value === 0) && !text) { - text = `` + text = `
    ${titleFormat(el.x)}
    ` } }) return `${text}
    ${titleFormat( + el.x, + )}
    ` }, - position () { + position() { if (d3.select('#overlayed-circle').empty()) { return { top: -100, left: -100 } } - const { x: circleX, y: circleY, width: circleWidth } = getCoordinateData('#overlayed-circle') - const { x: chartXStart, y: chartYStart } = getCoordinateData('.c3-chart') + const { + x: circleX, + y: circleY, + width: circleWidth, + } = getCoordinateData('#overlayed-circle') + const { x: chartXStart, y: chartYStart } = getCoordinateData( + '.c3-chart', + ) // TODO: Confirm the below constants work with all data sets and screen sizes const flipTooltip = circleY - circleWidth < chartYStart + 5 - d3 - .select('.tooltip-arrow') - .style('margin-top', flipTooltip ? '-16px' : '4px') + d3.select('.tooltip-arrow').style( + 'margin-top', + flipTooltip ? '-16px' : '4px', + ) return { - top: bigNumMinus(circleY, chartYStart).minus(19).plus(flipTooltip ? circleWidth + 38 : 0).toNumber(), - left: bigNumMinus(circleX, chartXStart).plus(newBigSigDig(circleWidth)).minus(bigNumDiv(gasPricesMaxPadded, 50)).toNumber(), + top: bigNumMinus(circleY, chartYStart) + .minus(19) + .plus(flipTooltip ? circleWidth + 38 : 0) + .toNumber(), + left: bigNumMinus(circleX, chartXStart) + .plus(newBigSigDig(circleWidth)) + .minus(bigNumDiv(gasPricesMaxPadded, 50)) + .toNumber(), } }, show: true, }, }) - chart.internal.selectPoint = function (data, itemIndex = (data.index || 0)) { - const { x: chartXStart, y: chartYStart } = getCoordinateData('.c3-areas-data1') + chart.internal.selectPoint = function (data, itemIndex = data.index || 0) { + const { x: chartXStart, y: chartYStart } = getCoordinateData( + '.c3-areas-data1', + ) d3.select('#set-circle').remove() @@ -319,19 +384,37 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate } chart.internal.showTooltip = function (selectedData, element) { - const dataToShow = selectedData.filter((d) => d && (d.value || d.value === 0)) + const dataToShow = selectedData.filter( + (d) => d && (d.value || d.value === 0), + ) if (dataToShow.length) { - this.tooltip.html( - this.config.tooltip_contents.call(this, selectedData, this.axis.getXAxisTickFormat(), this.getYFormat(), this.color), - ).style('display', 'flex') + this.tooltip + .html( + this.config.tooltip_contents.call( + this, + selectedData, + this.axis.getXAxisTickFormat(), + this.getYFormat(), + this.color, + ), + ) + .style('display', 'flex') // Get tooltip dimensions const tWidth = this.tooltip.property('offsetWidth') const tHeight = this.tooltip.property('offsetHeight') - const position = this.config.tooltip_position.call(this, dataToShow, tWidth, tHeight, element) + const position = this.config.tooltip_position.call( + this, + dataToShow, + tWidth, + tHeight, + element, + ) // Set tooltip - this.tooltip.style('top', `${position.top}px`).style('left', `${position.left}px`) + this.tooltip + .style('top', `${position.top}px`) + .style('left', `${position.left}px`) } } diff --git a/ui/app/components/app/gas-customization/gas-price-chart/index.scss b/ui/app/components/app/gas-customization/gas-price-chart/index.scss index 774760cff..8d204e9ec 100644 --- a/ui/app/components/app/gas-customization/gas-price-chart/index.scss +++ b/ui/app/components/app/gas-customization/gas-price-chart/index.scss @@ -17,10 +17,10 @@ .tick text, .c3-axis-x-label, .c3-axis-y-label { - font-style: normal; - font-weight: bold; + @include H9; + line-height: normal; - font-size: 8px; + font-weight: bold; text-align: center; fill: #9a9ca6 !important; } @@ -52,10 +52,9 @@ } .custom-tooltip th { - font-style: normal; + @include H8; + font-weight: 500; - line-height: normal; - font-size: 10px; text-align: center; padding: 3px; color: #fff; diff --git a/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js index d95fa63a1..6256b8734 100644 --- a/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js +++ b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js @@ -5,7 +5,7 @@ import sinon from 'sinon' import * as d3 from 'd3' import shallow from '../../../../../../lib/shallow-with-context' -function timeout (time) { +function timeout(time) { return new Promise((resolve) => { setTimeout(resolve, time) }) @@ -47,7 +47,9 @@ describe('GasPriceChart Component', function () { generateChart: sinon.stub().returns({ mockChart: true }), generateDataUIObj: sinon.spy(), getAdjacentGasPrices: sinon.spy(), - getCoordinateData: sinon.stub().returns({ x: 'mockCoordinateX', width: 'mockWidth' }), + getCoordinateData: sinon + .stub() + .returns({ x: 'mockCoordinateX', width: 'mockWidth' }), getNewXandTimeEstimate: sinon.spy(), handleChartUpdate: sinon.spy(), hideDataUI: sinon.spy(), @@ -67,13 +69,11 @@ describe('GasPriceChart Component', function () { GasPriceChart = proxyquire('../gas-price-chart.component.js', { './gas-price-chart.utils.js': gasPriceChartUtilsSpies, - 'd3': { + d3: { ...d3, - select (...args) { + select(...args) { const result = d3.select(...args) - return result.empty() - ? mockSelectReturn - : result + return result.empty() ? mockSelectReturn : result }, event: { clientX: 'mockClientX', @@ -118,12 +118,17 @@ describe('GasPriceChart Component', function () { it('should call handleChartUpdate with the correct props', function () { gasPriceChartUtilsSpies.handleChartUpdate.resetHistory() wrapper.instance().componentDidUpdate({ currentPrice: 7 }) - assert.deepEqual(gasPriceChartUtilsSpies.handleChartUpdate.getCall(0).args, [{ - chart: { mockChart: true }, - gasPrices: [1.5, 2.5, 4, 8], - newPrice: 6, - cssId: '#set-circle', - }]) + assert.deepEqual( + gasPriceChartUtilsSpies.handleChartUpdate.getCall(0).args, + [ + { + chart: { mockChart: true }, + gasPrices: [1.5, 2.5, 4, 8], + newPrice: 6, + cssId: '#set-circle', + }, + ], + ) }) it('should not call handleChartUpdate if props.currentPrice has not changed', function () { @@ -141,10 +146,22 @@ describe('GasPriceChart Component', function () { wrapper.instance().renderChart(testProps) await timeout(0) assert.equal(gasPriceChartUtilsSpies.setTickPosition.callCount, 4) - assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(0).args, ['y', 0, -5, 8]) - assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(1).args, ['y', 1, -3, -5]) - assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(2).args, ['x', 0, 3]) - assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(3).args, ['x', 1, 3, -8]) + assert.deepEqual( + gasPriceChartUtilsSpies.setTickPosition.getCall(0).args, + ['y', 0, -5, 8], + ) + assert.deepEqual( + gasPriceChartUtilsSpies.setTickPosition.getCall(1).args, + ['y', 1, -3, -5], + ) + assert.deepEqual( + gasPriceChartUtilsSpies.setTickPosition.getCall(2).args, + ['x', 0, 3], + ) + assert.deepEqual( + gasPriceChartUtilsSpies.setTickPosition.getCall(3).args, + ['x', 1, 3, -8], + ) }) it('should call handleChartUpdate with the correct props', async function () { @@ -152,12 +169,17 @@ describe('GasPriceChart Component', function () { gasPriceChartUtilsSpies.handleChartUpdate.resetHistory() wrapper.instance().renderChart(testProps) await timeout(0) - assert.deepEqual(gasPriceChartUtilsSpies.handleChartUpdate.getCall(0).args, [{ - chart: { mockChart: true }, - gasPrices: [1.5, 2.5, 4, 8], - newPrice: 6, - cssId: '#set-circle', - }]) + assert.deepEqual( + gasPriceChartUtilsSpies.handleChartUpdate.getCall(0).args, + [ + { + chart: { mockChart: true }, + gasPrices: [1.5, 2.5, 4, 8], + newPrice: 6, + cssId: '#set-circle', + }, + ], + ) }) it('should add three events to the chart', async function () { @@ -186,7 +208,10 @@ describe('GasPriceChart Component', function () { assert.equal(gasPriceChartUtilsSpies.hideDataUI.callCount, 0) mouseoutEventArgs[1]() assert.equal(gasPriceChartUtilsSpies.hideDataUI.callCount, 1) - assert.deepEqual(gasPriceChartUtilsSpies.hideDataUI.getCall(0).args, [{ mockChart: true }, '#overlayed-circle']) + assert.deepEqual(gasPriceChartUtilsSpies.hideDataUI.getCall(0).args, [ + { mockChart: true }, + '#overlayed-circle', + ]) }) it('should updateCustomGasPrice on click', async function () { @@ -199,7 +224,10 @@ describe('GasPriceChart Component', function () { assert.equal(propsMethodSpies.updateCustomGasPrice.callCount, 0) mouseoutEventArgs[1]() assert.equal(propsMethodSpies.updateCustomGasPrice.callCount, 1) - assert.equal(propsMethodSpies.updateCustomGasPrice.getCall(0).args[0], 'mockX') + assert.equal( + propsMethodSpies.updateCustomGasPrice.getCall(0).args[0], + 'mockX', + ) }) it('should handle mousemove', async function () { @@ -212,14 +240,19 @@ describe('GasPriceChart Component', function () { assert.equal(gasPriceChartUtilsSpies.handleMouseMove.callCount, 0) mouseoutEventArgs[1]() assert.equal(gasPriceChartUtilsSpies.handleMouseMove.callCount, 1) - assert.deepEqual(gasPriceChartUtilsSpies.handleMouseMove.getCall(0).args, [{ - xMousePos: 'mockClientX', - chartXStart: 'mockCoordinateX', - chartWidth: 'mockWidth', - gasPrices: testProps.gasPrices, - estimatedTimes: testProps.estimatedTimes, - chart: { mockChart: true }, - }]) + assert.deepEqual( + gasPriceChartUtilsSpies.handleMouseMove.getCall(0).args, + [ + { + xMousePos: 'mockClientX', + chartXStart: 'mockCoordinateX', + chartWidth: 'mockWidth', + gasPrices: testProps.gasPrices, + estimatedTimes: testProps.estimatedTimes, + chart: { mockChart: true }, + }, + ], + ) }) }) }) diff --git a/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js b/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js index 1e072d300..db5c01b8b 100644 --- a/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js +++ b/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js @@ -12,16 +12,8 @@ export default class GasSlider extends Component { min: PropTypes.number, } - render () { - const { - onChange, - lowLabel, - highLabel, - value, - step, - max, - min, - } = this.props + render() { + const { onChange, lowLabel, highLabel, value, step, max, min } = this.props return (
    diff --git a/ui/app/components/app/gas-customization/gas-slider/index.scss b/ui/app/components/app/gas-customization/gas-slider/index.scss index aacbea9e6..8eca6cc3b 100644 --- a/ui/app/components/app/gas-customization/gas-slider/index.scss +++ b/ui/app/components/app/gas-customization/gas-slider/index.scss @@ -45,9 +45,10 @@ } &__labels { + @include H7; + display: flex; justify-content: space-between; - font-size: 12px; margin-top: -6px; color: $mid-gray; } diff --git a/ui/app/components/app/home-notification/home-notification.component.js b/ui/app/components/app/home-notification/home-notification.component.js index d2f1598d7..2bb89eafe 100644 --- a/ui/app/components/app/home-notification/home-notification.component.js +++ b/ui/app/components/app/home-notification/home-notification.component.js @@ -34,8 +34,16 @@ export default class HomeNotification extends PureComponent { this.props.onIgnore() } - render () { - const { descriptionText, acceptText, onAccept, ignoreText, onIgnore, infoText, classNames = [] } = this.props + render() { + const { + descriptionText, + acceptText, + onAccept, + ignoreText, + onIgnore, + infoText, + classNames = [], + } = this.props return (
    @@ -46,65 +54,46 @@ export default class HomeNotification extends PureComponent { alt="" src="images/icons/connect.svg" /> -
    - { descriptionText } -
    +
    {descriptionText}
    - { - infoText ? ( - - {infoText} -

    - )} - offset={-36} - distance={36} - animation="none" - position="top" - arrow - theme="tippy-tooltip-home" - > - -
    - ) : ( - null - ) - } + {infoText ? ( + {infoText}

    + } + offset={-36} + distance={36} + animation="none" + position="top" + arrow + theme="tippy-tooltip-home" + > + +
    + ) : null}
    - { - (onAccept && acceptText) ? ( - - ) : ( - null - ) - } - { - (onIgnore && ignoreText) ? ( - - ) : ( - null - ) - } + {onAccept && acceptText ? ( + + ) : null} + {onIgnore && ignoreText ? ( + + ) : null}
    ) diff --git a/ui/app/components/app/home-notification/index.scss b/ui/app/components/app/home-notification/index.scss index 94e48ffea..452681ac2 100644 --- a/ui/app/components/app/home-notification/index.scss +++ b/ui/app/components/app/home-notification/index.scss @@ -39,9 +39,8 @@ } &__text { - font-style: normal; - font-weight: normal; - font-size: 12px; + @include H7; + color: $white; margin-left: 10px; margin-right: 8px; @@ -52,7 +51,7 @@ } & &__ignore-button { - border: 2px solid #6a737d; + border-color: #6a737d; box-sizing: border-box; border-radius: 6px; color: $white; @@ -76,7 +75,7 @@ } & &__accept-button { - border: 2px solid #6a737d; + border-color: #6a737d; box-sizing: border-box; border-radius: 6px; color: $white; @@ -115,9 +114,8 @@ } &__content { - font-style: normal; - font-weight: normal; - font-size: 12px; + @include H7; + color: $white; text-align: left; display: inline-block; diff --git a/ui/app/components/app/info-box/index.scss b/ui/app/components/app/info-box/index.scss index bad69cd53..c7749ecbb 100644 --- a/ui/app/components/app/info-box/index.scss +++ b/ui/app/components/app/info-box/index.scss @@ -19,6 +19,6 @@ } &__description { - font-size: 0.75rem; + @include H7; } } diff --git a/ui/app/components/app/info-box/info-box.component.js b/ui/app/components/app/info-box/info-box.component.js index 163869ef5..84e8c3b63 100644 --- a/ui/app/components/app/info-box/info-box.component.js +++ b/ui/app/components/app/info-box/info-box.component.js @@ -16,7 +16,7 @@ export default class InfoBox extends Component { isShowing: true, } - handleClose () { + handleClose() { const { onClose } = this.props if (onClose) { @@ -26,20 +26,15 @@ export default class InfoBox extends Component { } } - render () { + render() { const { title, description } = this.props - return this.state.isShowing - ? ( -
    -
    this.handleClose()} - /> -
    {title}
    -
    {description}
    -
    - ) - : null + return this.state.isShowing ? ( +
    +
    this.handleClose()} /> +
    {title}
    +
    {description}
    +
    + ) : null } } diff --git a/ui/app/components/app/info-box/tests/info-box.test.js b/ui/app/components/app/info-box/tests/info-box.test.js index 5ad89b4fd..031ac62f4 100644 --- a/ui/app/components/app/info-box/tests/info-box.test.js +++ b/ui/app/components/app/info-box/tests/info-box.test.js @@ -6,7 +6,6 @@ import { shallow } from 'enzyme' import InfoBox from '..' describe('InfoBox', function () { - let wrapper const props = { diff --git a/ui/app/components/app/loading-network-screen/loading-network-screen.component.js b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js index f20916346..1eb95eedf 100644 --- a/ui/app/components/app/loading-network-screen/loading-network-screen.component.js +++ b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js @@ -19,13 +19,19 @@ export default class LoadingNetworkScreen extends PureComponent { providerId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), showNetworkDropdown: PropTypes.func, setProviderArgs: PropTypes.array, - lastSelectedProvider: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + lastSelectedProvider: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object, + ]), setProviderType: PropTypes.func, isLoadingNetwork: PropTypes.bool, } componentDidMount = () => { - this.cancelCallTimeout = setTimeout(this.cancelCall, this.props.cancelTime || 15000) + this.cancelCallTimeout = setTimeout( + this.cancelCall, + this.props.cancelTime || 15000, + ) } getConnectingLabel = function (loadingMessage) { @@ -60,7 +66,7 @@ export default class LoadingNetworkScreen extends PureComponent { return (
    😞 - { this.context.t('somethingWentWrong') } + {this.context.t('somethingWentWrong')}
    @@ -102,7 +111,10 @@ export default class LoadingNetworkScreen extends PureComponent { if (provider.type !== prevProvider.type) { window.clearTimeout(this.cancelCallTimeout) this.setState({ showErrorScreen: false }) - this.cancelCallTimeout = setTimeout(this.cancelCall, this.props.cancelTime || 15000) + this.cancelCallTimeout = setTimeout( + this.cancelCall, + this.props.cancelTime || 15000, + ) } } @@ -110,19 +122,23 @@ export default class LoadingNetworkScreen extends PureComponent { window.clearTimeout(this.cancelCallTimeout) } - render () { + render() { const { lastSelectedProvider, setProviderType } = this.props return ( setProviderType(lastSelectedProvider || 'ropsten')} /> - )} + } showLoadingSpinner={!this.state.showErrorScreen} - loadingMessage={this.state.showErrorScreen ? this.renderErrorScreenContent() : this.getConnectingLabel(this.props.loadingMessage)} + loadingMessage={ + this.state.showErrorScreen + ? this.renderErrorScreenContent() + : this.getConnectingLabel(this.props.loadingMessage) + } /> ) } diff --git a/ui/app/components/app/loading-network-screen/loading-network-screen.container.js b/ui/app/components/app/loading-network-screen/loading-network-screen.container.js index 7662ae64f..994edd175 100644 --- a/ui/app/components/app/loading-network-screen/loading-network-screen.container.js +++ b/ui/app/components/app/loading-network-screen/loading-network-screen.container.js @@ -4,19 +4,12 @@ import { getNetworkIdentifier } from '../../../selectors' import LoadingNetworkScreen from './loading-network-screen.component' const mapStateToProps = (state) => { - const { - loadingMessage, - lastSelectedProvider, - } = state.appState - const { - provider, - network, - } = state.metamask + const { loadingMessage, lastSelectedProvider } = state.appState + const { provider, network } = state.metamask const { rpcUrl, chainId, ticker, nickname, type } = provider - const setProviderArgs = type === 'rpc' - ? [rpcUrl, chainId, ticker, nickname] - : [provider.type] + const setProviderArgs = + type === 'rpc' ? [rpcUrl, chainId, ticker, nickname] : [provider.type] return { isLoadingNetwork: network === 'loading', @@ -37,4 +30,7 @@ const mapDispatchToProps = (dispatch) => { } } -export default connect(mapStateToProps, mapDispatchToProps)(LoadingNetworkScreen) +export default connect( + mapStateToProps, + mapDispatchToProps, +)(LoadingNetworkScreen) diff --git a/ui/app/components/app/menu-bar/account-options-menu.js b/ui/app/components/app/menu-bar/account-options-menu.js index 241f6dc92..60ca1c598 100644 --- a/ui/app/components/app/menu-bar/account-options-menu.js +++ b/ui/app/components/app/menu-bar/account-options-menu.js @@ -7,13 +7,18 @@ import { showModal } from '../../../store/actions' import { CONNECTED_ROUTE } from '../../../helpers/constants/routes' import { Menu, MenuItem } from '../../ui/menu' import getAccountLink from '../../../../lib/account-link' -import { getCurrentKeyring, getCurrentNetwork, getRpcPrefsForCurrentProvider, getSelectedIdentity } from '../../../selectors' +import { + getCurrentKeyring, + getCurrentNetwork, + getRpcPrefsForCurrentProvider, + getSelectedIdentity, +} from '../../../selectors' import { useI18nContext } from '../../../hooks/useI18nContext' import { useMetricEvent } from '../../../hooks/useMetricEvent' import { getEnvironmentType } from '../../../../../app/scripts/lib/util' import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../app/scripts/lib/enums' -export default function AccountOptionsMenu ({ anchorElement, onClose }) { +export default function AccountOptionsMenu({ anchorElement, onClose }) { const t = useI18nContext() const dispatch = useDispatch() const history = useHistory() @@ -60,22 +65,18 @@ export default function AccountOptionsMenu ({ anchorElement, onClose }) { className="account-options-menu" onHide={onClose} > - { - getEnvironmentType() === ENVIRONMENT_TYPE_FULLSCREEN - ? null - : ( - { - openFullscreenEvent() - global.platform.openExtensionInBrowser() - onClose() - }} - iconClassName="fas fa-expand-alt" - > - { t('expandView') } - - ) - } + {getEnvironmentType() === ENVIRONMENT_TYPE_FULLSCREEN ? null : ( + { + openFullscreenEvent() + global.platform.openExtensionInBrowser() + onClose() + }} + iconClassName="fas fa-expand-alt" + > + {t('expandView')} + + )} { @@ -85,30 +86,26 @@ export default function AccountOptionsMenu ({ anchorElement, onClose }) { }} iconClassName="fas fa-qrcode" > - { t('accountDetails') } + {t('accountDetails')} { viewOnEtherscanEvent() - global.platform.openTab({ url: getAccountLink(address, network, rpcPrefs) }) + global.platform.openTab({ + url: getAccountLink(address, network, rpcPrefs), + }) onClose() }} subtitle={ - rpcPrefs.blockExplorerUrl - ? ( - - { rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/u)[1] } - - ) - : null + rpcPrefs.blockExplorerUrl ? ( + + {rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/u)[1]} + + ) : null } iconClassName="fas fa-external-link-alt" > - { - rpcPrefs.blockExplorerUrl - ? t('viewinExplorer') - : t('viewOnEtherscan') - } + {rpcPrefs.blockExplorerUrl ? t('viewinExplorer') : t('viewOnEtherscan')} - { t('connectedSites') } + {t('connectedSites')} - { - isRemovable - ? ( - { - dispatch(showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity: selectedIdentity })) - onClose() - }} - iconClassName="fas fa-trash-alt" - > - { t('removeAccount') } - - ) - : null - } + {isRemovable ? ( + { + dispatch( + showModal({ + name: 'CONFIRM_REMOVE_ACCOUNT', + identity: selectedIdentity, + }), + ) + onClose() + }} + iconClassName="fas fa-trash-alt" + > + {t('removeAccount')} + + ) : null} ) } diff --git a/ui/app/components/app/menu-bar/index.scss b/ui/app/components/app/menu-bar/index.scss index ac99b6b90..673b219c3 100644 --- a/ui/app/components/app/menu-bar/index.scss +++ b/ui/app/components/app/menu-bar/index.scss @@ -29,7 +29,8 @@ } &__explorer-origin { + @include H7; + color: $Grey-500; - font-size: 12px; } } diff --git a/ui/app/components/app/menu-bar/menu-bar.js b/ui/app/components/app/menu-bar/menu-bar.js index 8c4f5c8b9..d9d13fcb1 100644 --- a/ui/app/components/app/menu-bar/menu-bar.js +++ b/ui/app/components/app/menu-bar/menu-bar.js @@ -12,7 +12,7 @@ import { useMetricEvent } from '../../../hooks/useMetricEvent' import { getOriginOfCurrentTab } from '../../../selectors' import AccountOptionsMenu from './account-options-menu' -export default function MenuBar () { +export default function MenuBar() { const t = useI18nContext() const openAccountOptionsEvent = useMetricEvent({ eventOpts: { @@ -22,19 +22,25 @@ export default function MenuBar () { }, }) const history = useHistory() - const [accountOptionsButtonElement, setAccountOptionsButtonElement] = useState(null) + const [ + accountOptionsButtonElement, + setAccountOptionsButtonElement, + ] = useState(null) const [accountOptionsMenuOpen, setAccountOptionsMenuOpen] = useState(false) const origin = useSelector(getOriginOfCurrentTab) - const showStatus = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP && origin && origin !== extension.runtime.id + const showStatus = + getEnvironmentType() === ENVIRONMENT_TYPE_POPUP && + origin && + origin !== extension.runtime.id return (
    - { - showStatus - ? history.push(CONNECTED_ACCOUNTS_ROUTE)} /> - : null - } + {showStatus ? ( + history.push(CONNECTED_ACCOUNTS_ROUTE)} + /> + ) : null} @@ -49,14 +55,12 @@ export default function MenuBar () { }} /> - { - accountOptionsMenuOpen && ( - setAccountOptionsMenuOpen(false)} - /> - ) - } + {accountOptionsMenuOpen && ( + setAccountOptionsMenuOpen(false)} + /> + )}
    ) } diff --git a/ui/app/components/app/menu-bar/tests/menu-bar.test.js b/ui/app/components/app/menu-bar/tests/menu-bar.test.js index 1212977be..9df845409 100644 --- a/ui/app/components/app/menu-bar/tests/menu-bar.test.js +++ b/ui/app/components/app/menu-bar/tests/menu-bar.test.js @@ -19,9 +19,7 @@ const initState = { keyrings: [ { type: 'HD Key Tree', - accounts: [ - '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', - ], + accounts: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'], }, ], frequentRpcListDetail: [], diff --git a/ui/app/components/app/menu-droppo.js b/ui/app/components/app/menu-droppo.js index 35d7b458e..558dacfe8 100644 --- a/ui/app/components/app/menu-droppo.js +++ b/ui/app/components/app/menu-droppo.js @@ -16,7 +16,7 @@ export default class MenuDroppoComponent extends Component { speed: PropTypes.string, } - renderPrimary () { + renderPrimary() { const { isOpen } = this.props if (!isOpen) { return null @@ -31,7 +31,7 @@ export default class MenuDroppoComponent extends Component { ) } - manageListeners () { + manageListeners() { const { isOpen, onClickOutside } = this.props if (isOpen) { @@ -44,14 +44,16 @@ export default class MenuDroppoComponent extends Component { // eslint-disable-next-line react/no-find-dom-node const container = findDOMNode(this) - if (target !== container && + if ( + target !== container && !isDescendant(this.container, event.target) && - this.outsideClickHandler) { + this.outsideClickHandler + ) { this.outsideClickHandler(event) } } - componentDidMount () { + componentDidMount() { if (this && document.body) { document.body.addEventListener('click', this.globalClickOccurred) // eslint-disable-next-line react/no-find-dom-node @@ -60,17 +62,17 @@ export default class MenuDroppoComponent extends Component { } } - componentWillUnmount () { + componentWillUnmount() { if (this && document.body) { document.body.removeEventListener('click', this.globalClickOccurred) } } - render () { + render() { const { containerClassName = '', style } = this.props const speed = this.props.speed || '300ms' const { useCssTransition } = this.props - const zIndex = ('zIndex' in this.props) ? this.props.zIndex : 0 + const zIndex = 'zIndex' in this.props ? this.props.zIndex : 0 this.manageListeners() @@ -81,8 +83,12 @@ export default class MenuDroppoComponent extends Component { } return ( -
    - - { - useCssTransition - ? ( - - {this.renderPrimary()} - - ) - : this.renderPrimary() - } + {useCssTransition ? ( + + {this.renderPrimary()} + + ) : ( + this.renderPrimary() + )}
    ) } } -function isDescendant (parent, child) { +function isDescendant(parent, child) { let node = child.parentNode while (node !== null) { if (node === parent) { diff --git a/ui/app/components/app/modal/modal-content/index.scss b/ui/app/components/app/modal/modal-content/index.scss index 560c2cbd4..d06b2ce85 100644 --- a/ui/app/components/app/modal/modal-content/index.scss +++ b/ui/app/components/app/modal/modal-content/index.scss @@ -6,14 +6,16 @@ padding: 16px 0; &__title { - font-size: 1.5rem; + @include H3; + font-weight: 500; padding: 16px 0; text-align: center; } &__description { + @include H6; + text-align: center; - font-size: 0.875rem; } } diff --git a/ui/app/components/app/modal/modal-content/modal-content.component.js b/ui/app/components/app/modal/modal-content/modal-content.component.js index ecec0ee5b..8f011e18f 100644 --- a/ui/app/components/app/modal/modal-content/modal-content.component.js +++ b/ui/app/components/app/modal/modal-content/modal-content.component.js @@ -7,25 +7,15 @@ export default class ModalContent extends PureComponent { description: PropTypes.string, } - render () { + render() { const { title, description } = this.props return (
    - { - title && ( -
    - { title } -
    - ) - } - { - description && ( -
    - { description } -
    - ) - } + {title &&
    {title}
    } + {description && ( +
    {description}
    + )}
    ) } diff --git a/ui/app/components/app/modal/modal-content/tests/modal-content.component.test.js b/ui/app/components/app/modal/modal-content/tests/modal-content.component.test.js index 058727cc2..de40a39bd 100644 --- a/ui/app/components/app/modal/modal-content/tests/modal-content.component.test.js +++ b/ui/app/components/app/modal/modal-content/tests/modal-content.component.test.js @@ -5,11 +5,7 @@ import ModalContent from '../modal-content.component' describe('ModalContent Component', function () { it('should render a title', function () { - const wrapper = shallow( - , - ) + const wrapper = shallow() assert.equal(wrapper.find('.modal-content__title').length, 1) assert.equal(wrapper.find('.modal-content__title').text(), 'Modal Title') @@ -17,28 +13,27 @@ describe('ModalContent Component', function () { }) it('should render a description', function () { - const wrapper = shallow( - , - ) + const wrapper = shallow() assert.equal(wrapper.find('.modal-content__title').length, 0) assert.equal(wrapper.find('.modal-content__description').length, 1) - assert.equal(wrapper.find('.modal-content__description').text(), 'Modal Description') + assert.equal( + wrapper.find('.modal-content__description').text(), + 'Modal Description', + ) }) it('should render both a title and a description', function () { const wrapper = shallow( - , + , ) assert.equal(wrapper.find('.modal-content__title').length, 1) assert.equal(wrapper.find('.modal-content__title').text(), 'Modal Title') assert.equal(wrapper.find('.modal-content__description').length, 1) - assert.equal(wrapper.find('.modal-content__description').text(), 'Modal Description') + assert.equal( + wrapper.find('.modal-content__description').text(), + 'Modal Description', + ) }) }) diff --git a/ui/app/components/app/modal/modal.component.js b/ui/app/components/app/modal/modal.component.js index 5e2ad00f5..57137270c 100644 --- a/ui/app/components/app/modal/modal.component.js +++ b/ui/app/components/app/modal/modal.component.js @@ -28,7 +28,7 @@ export default class Modal extends PureComponent { cancelType: 'default', } - render () { + render() { const { children, headerText, @@ -47,49 +47,36 @@ export default class Modal extends PureComponent { return (
    - { - headerText && ( -
    -
    - { headerText } -
    -
    -
    - ) - } + {headerText && ( +
    +
    {headerText}
    +
    +
    + )}
    - { children } + {children}
    - { - hideFooter - ? null - : ( -
    - { - onCancel && ( - - ) - } - -
    - ) - } + {hideFooter ? null : ( +
    + {onCancel && ( + + )} + +
    + )}
    ) } diff --git a/ui/app/components/app/modal/tests/modal.component.test.js b/ui/app/components/app/modal/tests/modal.component.test.js index 5dceb4292..fe55b61bd 100644 --- a/ui/app/components/app/modal/tests/modal.component.test.js +++ b/ui/app/components/app/modal/tests/modal.component.test.js @@ -93,7 +93,10 @@ describe('Modal Component', function () { ) assert.ok(wrapper.find('.modal-container__header')) - assert.equal(wrapper.find('.modal-container__header-text').text(), 'My Header') + assert.equal( + wrapper.find('.modal-container__header-text').text(), + 'My Header', + ) assert.equal(handleCancel.callCount, 0) assert.equal(handleSubmit.callCount, 0) wrapper.find('.modal-container__header-close').simulate('click') diff --git a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js index 1e5f634a5..f3b6d5586 100644 --- a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js +++ b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js @@ -20,7 +20,7 @@ export default class AccountDetailsModal extends Component { t: PropTypes.func, } - render () { + render() { const { selectedIdentity, network, @@ -37,7 +37,7 @@ export default class AccountDetailsModal extends Component { let exportPrivateKeyFeatureEnabled = true // This feature is disabled for hardware wallets - if (keyring && keyring.type.search('Hardware') !== -1) { + if (keyring?.type?.search('Hardware') !== -1) { exportPrivateKeyFeatureEnabled = false } @@ -61,27 +61,27 @@ export default class AccountDetailsModal extends Component { type="secondary" className="account-details-modal__button" onClick={() => { - global.platform.openTab({ url: getAccountLink(address, network, rpcPrefs) }) + global.platform.openTab({ + url: getAccountLink(address, network, rpcPrefs), + }) }} > {rpcPrefs.blockExplorerUrl - ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/u)[1]]) - : this.context.t('viewOnEtherscan') - } + ? this.context.t('blockExplorerView', [ + rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/u)[1], + ]) + : this.context.t('viewOnEtherscan')} - {exportPrivateKeyFeatureEnabled - ? ( - - ) - : null - } + {exportPrivateKeyFeatureEnabled ? ( + + ) : null} ) } diff --git a/ui/app/components/app/modals/account-details-modal/account-details-modal.container.js b/ui/app/components/app/modals/account-details-modal/account-details-modal.container.js index 807fad84e..b29e99c54 100644 --- a/ui/app/components/app/modals/account-details-modal/account-details-modal.container.js +++ b/ui/app/components/app/modals/account-details-modal/account-details-modal.container.js @@ -17,8 +17,10 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return { - showExportPrivateKeyModal: () => dispatch(showModal({ name: 'EXPORT_PRIVATE_KEY' })), - setAccountLabel: (address, label) => dispatch(setAccountLabel(address, label)), + showExportPrivateKeyModal: () => + dispatch(showModal({ name: 'EXPORT_PRIVATE_KEY' })), + setAccountLabel: (address, label) => + dispatch(setAccountLabel(address, label)), } } diff --git a/ui/app/components/app/modals/account-details-modal/index.scss b/ui/app/components/app/modals/account-details-modal/index.scss index 1f14aeaac..ecab51311 100644 --- a/ui/app/components/app/modals/account-details-modal/index.scss +++ b/ui/app/components/app/modals/account-details-modal/index.scss @@ -1,7 +1,8 @@ .account-details-modal { &__name { + @include H4; + margin-top: 9px; - font-size: 20px; } & &__button { @@ -18,8 +19,9 @@ } & .qr-header { + @include H4; + margin-top: 9px; - font-size: 20px; } & .qr-wrapper { diff --git a/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js b/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js index 93f4ae25b..2ad338b23 100644 --- a/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js +++ b/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js @@ -3,7 +3,7 @@ import React from 'react' import classnames from 'classnames' import Identicon from '../../../ui/identicon' -export default function AccountModalContainer (props, context) { +export default function AccountModalContainer(props, context) { const { className, selectedIdentity, @@ -14,18 +14,20 @@ export default function AccountModalContainer (props, context) { } = props return ( -
    +
    - +
    {showBackButton && (
    - {context.t('back')} + + {context.t('back')} +
    )}
    - )} - { - privateKey - ? ( - - ) - : ( - - ) - } + {privateKey ? ( + + ) : ( + + )}
    ) } - render () { + render() { const { selectedIdentity, warning, @@ -139,10 +137,7 @@ export default class ExportPrivateKeyModal extends Component { } = this.props const { name, address } = selectedIdentity - const { - privateKey, - showWarning, - } = this.state + const { privateKey, showWarning } = this.state return (
    - {this.context.t('showPrivateKeys')} + + {this.context.t('showPrivateKeys')} +
    {this.renderPasswordLabel(privateKey)} {this.renderPasswordInput(privateKey)} - { - (showWarning && warning) - ? {warning} - : null - } + {showWarning && warning ? ( + + {warning} + + ) : null} +
    +
    + {this.context.t('privateKeyWarning')}
    -
    {this.context.t('privateKeyWarning')}
    {this.renderButtons(privateKey, address, hideModal)} ) diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js index a8c3dd9ca..59a0252a1 100644 --- a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js +++ b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js @@ -1,11 +1,17 @@ import { connect } from 'react-redux' -import { exportAccount, hideWarning, showModal, hideModal, clearAccountDetails } from '../../../../store/actions' +import { + exportAccount, + hideWarning, + showModal, + hideModal, + clearAccountDetails, +} from '../../../../store/actions' import { getSelectedIdentity } from '../../../../selectors' import ExportPrivateKeyModal from './export-private-key-modal.component' -function mapStateToPropsFactory () { +function mapStateToPropsFactory() { let selectedIdentity = null - return function mapStateToProps (state) { + return function mapStateToProps(state) { // We should **not** change the identity displayed here even if it changes from underneath us. // If we do, we will be showing the user one private key and a **different** address and name. // Note that the selected identity **will** change from underneath us when we unlock the keyring @@ -20,20 +26,23 @@ function mapStateToPropsFactory () { } } -function mapDispatchToProps (dispatch) { +function mapDispatchToProps(dispatch) { return { exportAccount: (password, address) => { - return dispatch(exportAccount(password, address)) - .then((res) => { - dispatch(hideWarning()) - return res - }) + return dispatch(exportAccount(password, address)).then((res) => { + dispatch(hideWarning()) + return res + }) }, - showAccountDetailModal: () => dispatch(showModal({ name: 'ACCOUNT_DETAILS' })), + showAccountDetailModal: () => + dispatch(showModal({ name: 'ACCOUNT_DETAILS' })), hideModal: () => dispatch(hideModal()), hideWarning: () => dispatch(hideWarning()), clearAccountDetails: () => dispatch(clearAccountDetails()), } } -export default connect(mapStateToPropsFactory, mapDispatchToProps)(ExportPrivateKeyModal) +export default connect( + mapStateToPropsFactory, + mapDispatchToProps, +)(ExportPrivateKeyModal) diff --git a/ui/app/components/app/modals/export-private-key-modal/index.scss b/ui/app/components/app/modals/export-private-key-modal/index.scss index 8fd3c3133..737b94863 100644 --- a/ui/app/components/app/modals/export-private-key-modal/index.scss +++ b/ui/app/components/app/modals/export-private-key-modal/index.scss @@ -1,8 +1,9 @@ .export-private-key-modal { &__body-title { + @include H4; + margin-top: 16px; margin-bottom: 16px; - font-size: 18px; } &__divider { @@ -13,8 +14,9 @@ } &__account-name { + @include H4; + margin-top: 9px; - font-size: 20px; } &__password { @@ -24,9 +26,9 @@ &__password-label, &__password--error { + @include H6; + color: $scorpion; - font-size: 14px; - line-height: 18px; margin-bottom: 10px; } @@ -36,9 +38,9 @@ } &__password-input { + @include Paragraph; + padding: 10px 0 13px 17px; - font-size: 16px; - line-height: 21px; width: 291px; height: 44px; } @@ -48,11 +50,11 @@ } &__password--warning { + @include H7; + border-radius: 8px; background-color: #fff6f6; - font-size: 12px; font-weight: 500; - line-height: 15px; color: $crimson; width: 292px; padding: 9px 15px; @@ -67,9 +69,9 @@ } &__password-display-textarea { + @include Paragraph; + color: $crimson; - font-size: 16px; - line-height: 21px; border: none; height: 75px; width: 100%; diff --git a/ui/app/components/app/modals/fade-modal.js b/ui/app/components/app/modals/fade-modal.js index 72d1c7288..0b4edeb43 100644 --- a/ui/app/components/app/modals/fade-modal.js +++ b/ui/app/components/app/modals/fade-modal.js @@ -5,7 +5,6 @@ let index = 0 let extraSheet const insertRule = (css) => { - if (!extraSheet) { // First time, create an extra stylesheet for adding rules extraSheet = document.createElement('style') @@ -88,11 +87,11 @@ const animation = { const endEvents = ['transitionend', 'animationend'] -function addEventListener (node, eventName, eventListener) { +function addEventListener(node, eventName, eventListener) { node.addEventListener(eventName, eventListener, false) } -function removeEventListener (node, eventName, eventListener) { +function removeEventListener(node, eventName, eventListener) { node.removeEventListener(eventName, eventListener, false) } @@ -172,7 +171,7 @@ class FadeModal extends Component { return this.state.hidden } - render () { + render() { if (this.state.hidden) { return null } @@ -180,25 +179,31 @@ class FadeModal extends Component { const { willHide } = this.state const { modalStyle } = this.props const backdropStyle = { - animationName: willHide ? animation.hideBackdropAnimation : animation.showBackdropAnimation, - animationTimingFunction: (willHide ? animation.hide : animation.show).animationTimingFunction, ...this.props.backdropStyle, + animationName: willHide + ? animation.hideBackdropAnimation + : animation.showBackdropAnimation, + animationTimingFunction: (willHide ? animation.hide : animation.show) + .animationTimingFunction, + ...this.props.backdropStyle, } const contentStyle = { - animationDuration: (willHide ? animation.hide : animation.show).animationDuration, - animationName: willHide ? animation.hideContentAnimation : animation.showContentAnimation, - animationTimingFunction: (willHide ? animation.hide : animation.show).animationTimingFunction, ...this.props.contentStyle, + animationDuration: (willHide ? animation.hide : animation.show) + .animationDuration, + animationName: willHide + ? animation.hideContentAnimation + : animation.showContentAnimation, + animationTimingFunction: (willHide ? animation.hide : animation.show) + .animationTimingFunction, + ...this.props.contentStyle, } - const backdrop = this.props.backdrop - ? ( -
    - ) : undefined + const backdrop = this.props.backdrop ? ( +
    + ) : undefined if (willHide) { this.addTransitionListener(this.content, this.leave) @@ -219,7 +224,6 @@ class FadeModal extends Component { {backdrop} ) - } leave = () => { @@ -243,9 +247,12 @@ class FadeModal extends Component { hidden: false, }) - setTimeout(function () { - this.addTransitionListener(this.content, this.enter) - }.bind(this), 0) + setTimeout( + function () { + this.addTransitionListener(this.content, this.enter) + }.bind(this), + 0, + ) } hide = () => { @@ -267,9 +274,10 @@ class FadeModal extends Component { } closeOnEsc = (event) => { - if (this.props.keyboard && - (event.key === 'Escape' || - event.keyCode === 27)) { + if ( + this.props.keyboard && + (event.key === 'Escape' || event.keyCode === 27) + ) { this.hide() } } diff --git a/ui/app/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.js b/ui/app/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.js index 2e042a842..592528038 100644 --- a/ui/app/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.js +++ b/ui/app/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.js @@ -5,21 +5,20 @@ import * as actions from '../../../../store/actions' import Identicon from '../../../ui/identicon' import Button from '../../../ui/button' -function mapStateToProps (state) { +function mapStateToProps(state) { return { token: state.appState.modal.modalState.props.token, assetImages: state.metamask.assetImages, } } -function mapDispatchToProps (dispatch) { +function mapDispatchToProps(dispatch) { return { hideModal: () => dispatch(actions.hideModal()), hideToken: (address) => { - dispatch(actions.removeToken(address)) - .then(() => { - dispatch(actions.hideModal()) - }) + dispatch(actions.removeToken(address)).then(() => { + dispatch(actions.hideModal()) + }) }, } } @@ -41,7 +40,7 @@ class HideTokenConfirmationModal extends Component { state = {} - render () { + render() { const { token, hideToken, hideModal, assetImages } = this.props const { symbol, address } = token const image = assetImages[address] @@ -86,4 +85,7 @@ class HideTokenConfirmationModal extends Component { } } -export default connect(mapStateToProps, mapDispatchToProps)(HideTokenConfirmationModal) +export default connect( + mapStateToProps, + mapDispatchToProps, +)(HideTokenConfirmationModal) diff --git a/ui/app/components/app/modals/hide-token-confirmation-modal/index.scss b/ui/app/components/app/modals/hide-token-confirmation-modal/index.scss index 9482caeba..fb82e69ee 100644 --- a/ui/app/components/app/modals/hide-token-confirmation-modal/index.scss +++ b/ui/app/components/app/modals/hide-token-confirmation-modal/index.scss @@ -16,29 +16,29 @@ } &__symbol { + @include Paragraph; + color: $tundora; - font-size: 16px; - line-height: 24px; text-align: center; margin-bottom: 7.5px; } &__title { + @include H3; + height: 30px; width: 271.28px; color: $tundora; - font-size: 22px; - line-height: 30px; text-align: center; margin-bottom: 10.5px; } &__copy { - height: 41px; + @include Paragraph; + + min-height: 41px; width: 318px; color: $scorpion; - font-size: 14px; - line-height: 18px; text-align: center; } diff --git a/ui/app/components/app/modals/loading-network-error/loading-network-error.component.js b/ui/app/components/app/modals/loading-network-error/loading-network-error.component.js index 6cac4942f..9f5d1ad07 100644 --- a/ui/app/components/app/modals/loading-network-error/loading-network-error.component.js +++ b/ui/app/components/app/modals/loading-network-error/loading-network-error.component.js @@ -7,13 +7,8 @@ const LoadingNetworkError = (props, context) => { const { hideModal } = props return ( - hideModal()} - submitText={t('tryAgain')} - > - + hideModal()} submitText={t('tryAgain')}> + ) } diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/index.scss b/ui/app/components/app/modals/metametrics-opt-in-modal/index.scss index 4c99ecc28..08dc6cc15 100644 --- a/ui/app/components/app/modals/metametrics-opt-in-modal/index.scss +++ b/ui/app/components/app/modals/metametrics-opt-in-modal/index.scss @@ -12,7 +12,7 @@ .metametrics-opt-in__title { - font-size: 38px; + @include H1; } .metametrics-opt-in__content { diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js index 3e1610965..df326978d 100644 --- a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js +++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js @@ -14,7 +14,7 @@ export default class MetaMetricsOptInModal extends Component { t: PropTypes.func, } - render () { + render() { const { metricsEvent, t } = this.context const { setParticipateInMetaMetrics, hideModal } = this.props @@ -26,14 +26,18 @@ export default class MetaMetricsOptInModal extends Component {
    -
    Help Us Improve MetaMask
    +
    + Help Us Improve MetaMask +
    - MetaMask would like to gather usage data to better understand how our users interact with the extension. This data - will be used to continually improve the usability and user experience of our product and the Ethereum ecosystem. + MetaMask would like to gather usage data to better understand + how our users interact with the extension. This data will be + used to continually improve the usability and user experience of + our product and the Ethereum ecosystem.
    - MetaMask will.. + MetaMask will..
    @@ -52,73 +56,83 @@ export default class MetaMetricsOptInModal extends Component {
    - Maintain a public aggregate dashboard to educate the community + Maintain a public aggregate dashboard to educate the + community
    - Never collect keys, addresses, transactions, balances, hashes, or any personal information + Never{' '} + collect keys, addresses, transactions, balances, hashes, or + any personal information
    - Never collect your full IP address + Never{' '} + collect your full IP address
    - Never sell data for profit. Ever! + Never sell + data for profit. Ever!
    - This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our  + This data is aggregated and is therefore anonymous for the + purposes of General Data Protection Regulation (EU) 2016/679. For + more information in relation to our privacy practices, please see + our  Privacy Policy here - . + + .
    { - setParticipateInMetaMetrics(false) - .then(() => { - metricsEvent({ + setParticipateInMetaMetrics(false).then(() => { + metricsEvent( + { eventOpts: { category: 'Onboarding', action: 'Metrics Option', name: 'Metrics Opt Out', }, isOptIn: true, - }, { + }, + { excludeMetaMetricsId: true, - }) - hideModal() - }) + }, + ) + hideModal() + }) }} cancelText={t('noThanks')} hideCancel={false} onSubmit={() => { - setParticipateInMetaMetrics(true) - .then(() => { - metricsEvent({ - eventOpts: { - category: 'Onboarding', - action: 'Metrics Option', - name: 'Metrics Opt In', - }, - isOptIn: true, - }) - hideModal() + setParticipateInMetaMetrics(true).then(() => { + metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Metrics Option', + name: 'Metrics Opt In', + }, + isOptIn: true, }) + hideModal() + }) }} submitText={t('affirmAgree')} submitButtonType="confirm" diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js index 8cef4a076..bb1322903 100644 --- a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js +++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js @@ -14,7 +14,8 @@ const mapStateToProps = (_, ownProps) => { const mapDispatchToProps = (dispatch) => { return { - setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), + setParticipateInMetaMetrics: (val) => + dispatch(setParticipateInMetaMetrics(val)), } } diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/tests/metametrics-opt-in-modal.test.js b/ui/app/components/app/modals/metametrics-opt-in-modal/tests/metametrics-opt-in-modal.test.js index 9945ca73f..ec113f108 100644 --- a/ui/app/components/app/modals/metametrics-opt-in-modal/tests/metametrics-opt-in-modal.test.js +++ b/ui/app/components/app/modals/metametrics-opt-in-modal/tests/metametrics-opt-in-modal.test.js @@ -15,14 +15,12 @@ describe('MetaMetrics Opt In', function () { } beforeEach(function () { - wrapper = mount( - , { - context: { - metricsEvent: () => undefined, - t: (key) => messages[key].message, - }, + wrapper = mount(, { + context: { + metricsEvent: () => undefined, + t: (key) => messages[key].message, }, - ) + }) }) afterEach(function () { @@ -43,7 +41,9 @@ describe('MetaMetrics Opt In', function () { }) it('passes true to setParticipateInMetaMetrics and hides modal', function (done) { - const affirmAgree = wrapper.find('.btn-primary.page-container__footer-button') + const affirmAgree = wrapper.find( + '.btn-primary.page-container__footer-button', + ) affirmAgree.simulate('click') setImmediate(() => { @@ -53,5 +53,4 @@ describe('MetaMetrics Opt In', function () { done() }) }) - }) diff --git a/ui/app/components/app/modals/modal.js b/ui/app/components/app/modals/modal.js index bc747f529..46f915b16 100644 --- a/ui/app/components/app/modals/modal.js +++ b/ui/app/components/app/modals/modal.js @@ -10,6 +10,7 @@ import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums' // Modal Components import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container' +import SwapsGasCustomizationModal from '../../../pages/swaps/swaps-gas-customization-modal' import DepositEtherModal from './deposit-ether-modal' import AccountDetailsModal from './account-details-modal' import ExportPrivateKeyModal from './export-private-key-modal' @@ -272,6 +273,31 @@ const MODALS = { }, }, + CUSTOMIZE_METASWAP_GAS: { + contents: , + mobileModalStyle: { + width: '100vw', + height: '100vh', + top: '0', + transform: 'none', + left: '0', + right: '0', + margin: '0 auto', + }, + laptopModalStyle: { + width: 'auto', + height: '0px', + top: '80px', + left: '0px', + transform: 'none', + margin: '0 auto', + position: 'relative', + }, + contentStyle: { + borderRadius: '8px', + }, + }, + EDIT_APPROVAL_PERMISSION: { contents: , mobileModalStyle: { @@ -361,14 +387,14 @@ const BACKDROPSTYLE = { backgroundColor: 'rgba(0, 0, 0, 0.5)', } -function mapStateToProps (state) { +function mapStateToProps(state) { return { active: state.appState.modal.open, modalState: state.appState.modal.modalState, } } -function mapDispatchToProps (dispatch) { +function mapDispatchToProps(dispatch) { return { hideModal: (customOnHideOpts) => { dispatch(actions.hideModal()) @@ -379,7 +405,6 @@ function mapDispatchToProps (dispatch) { hideWarning: () => { dispatch(actions.hideWarning()) }, - } } @@ -391,15 +416,15 @@ class Modal extends Component { modalState: PropTypes.object.isRequired, } - hide () { + hide() { this.modalRef.hide() } - show () { + show() { this.modalRef.show() } - UNSAFE_componentWillReceiveProps (nextProps, _) { + UNSAFE_componentWillReceiveProps(nextProps, _) { if (nextProps.active) { this.show() } else if (this.props.active) { @@ -407,10 +432,11 @@ class Modal extends Component { } } - render () { + render() { const modal = MODALS[this.props.modalState.name || 'DEFAULT'] const { contents: children, disableBackdropClick = false } = modal - const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle'] + const modalStyle = + modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle'] const contentStyle = modal.contentStyle || {} return ( diff --git a/ui/app/components/app/modals/new-account-modal/index.scss b/ui/app/components/app/modals/new-account-modal/index.scss index 3c20a34e5..cf6a42069 100644 --- a/ui/app/components/app/modals/new-account-modal/index.scss +++ b/ui/app/components/app/modals/new-account-modal/index.scss @@ -21,9 +21,10 @@ } &__header-close { + @include H4; + color: #24292e; background: none; - font-size: 1.1rem; } } @@ -33,12 +34,13 @@ } &__input { + @include H4; + background: $white; border: 1px solid $Grey-100; box-sizing: border-box; border-radius: 8px; padding: 0.625rem 0.75rem; - font-size: 1.25rem; margin-top: 0.75rem; &::placeholder { diff --git a/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js b/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js index f560036ad..612947249 100644 --- a/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js +++ b/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types' import Button from '../../../ui/button/button.component' export default class NewAccountModal extends Component { - static contextTypes = { t: PropTypes.func, } @@ -15,7 +14,9 @@ export default class NewAccountModal extends Component { } state = { - alias: this.context.t('newAccountNumberName', [this.props.newAccountNumber]), + alias: this.context.t('newAccountNumberName', [ + this.props.newAccountNumber, + ]), } onChange = (e) => { @@ -25,8 +26,7 @@ export default class NewAccountModal extends Component { } onSubmit = () => { - this.props.onSave(this.state.alias) - .then(this.props.hideModal) + this.props.onSave(this.state.alias).then(this.props.hideModal) } onKeyPress = (e) => { @@ -35,7 +35,7 @@ export default class NewAccountModal extends Component { } } - render () { + render() { const { t } = this.context return ( @@ -62,10 +62,7 @@ export default class NewAccountModal extends Component { />
    - ') + assert.equal( + blockExplorerLink.html(), + '', + ) }) }) diff --git a/ui/app/components/app/modals/transaction-confirmed/index.scss b/ui/app/components/app/modals/transaction-confirmed/index.scss index 6135f9b0c..5e432093c 100644 --- a/ui/app/components/app/modals/transaction-confirmed/index.scss +++ b/ui/app/components/app/modals/transaction-confirmed/index.scss @@ -1,14 +1,16 @@ .transaction-confirmed { &__title { - font-size: 1.5rem; + @include H3; + font-weight: 500; padding: 16px 0; text-align: center; } &__description { + @include H6; + text-align: center; - font-size: 0.875rem; } &__content { diff --git a/ui/app/components/app/modals/transaction-confirmed/tests/transaction-confirmed.test.js b/ui/app/components/app/modals/transaction-confirmed/tests/transaction-confirmed.test.js index 971ed324c..f4d35faca 100644 --- a/ui/app/components/app/modals/transaction-confirmed/tests/transaction-confirmed.test.js +++ b/ui/app/components/app/modals/transaction-confirmed/tests/transaction-confirmed.test.js @@ -11,7 +11,8 @@ describe('Transaction Confirmed', function () { hideModal: sinon.spy(), } const wrapper = mount( - , { + , + { context: { t: (str) => str, }, diff --git a/ui/app/components/app/modals/transaction-confirmed/transaction-confirmed.component.js b/ui/app/components/app/modals/transaction-confirmed/transaction-confirmed.component.js index 0a98eb1a1..e34c4dc4d 100644 --- a/ui/app/components/app/modals/transaction-confirmed/transaction-confirmed.component.js +++ b/ui/app/components/app/modals/transaction-confirmed/transaction-confirmed.component.js @@ -22,21 +22,18 @@ export default class TransactionConfirmed extends PureComponent { } } - render () { + render() { const { t } = this.context return ( - +
    - { `${t('confirmed')}!` } + {`${t('confirmed')}!`}
    - { t('initialTransactionConfirmed') } + {t('initialTransactionConfirmed')}
    diff --git a/ui/app/components/app/multiple-notifications/index.scss b/ui/app/components/app/multiple-notifications/index.scss index c1a52cf95..ea17e9467 100644 --- a/ui/app/components/app/multiple-notifications/index.scss +++ b/ui/app/components/app/multiple-notifications/index.scss @@ -27,8 +27,9 @@ visibility: visible; &:hover { + @include H4; + color: #b0d7f2; - font-size: 1.1rem; } } } diff --git a/ui/app/components/app/multiple-notifications/multiple-notifications.component.js b/ui/app/components/app/multiple-notifications/multiple-notifications.component.js index 410bef6a9..e8dcebf13 100644 --- a/ui/app/components/app/multiple-notifications/multiple-notifications.component.js +++ b/ui/app/components/app/multiple-notifications/multiple-notifications.component.js @@ -17,7 +17,7 @@ export default class MultipleNotifications extends PureComponent { showAll: false, } - render () { + render() { const { showAll } = this.state const { children, classNames } = this.props @@ -33,7 +33,7 @@ export default class MultipleNotifications extends PureComponent { 'home-notification-wrapper--show-first': !showAll, })} > - { childrenToRender } + {childrenToRender}
    this.setState({ showAll: !showAll })} @@ -41,7 +41,7 @@ export default class MultipleNotifications extends PureComponent { {childrenToRender.length > 1 ? ( ) : null} diff --git a/ui/app/components/app/network-display/index.scss b/ui/app/components/app/network-display/index.scss index a17e47bc9..4ded13d54 100644 --- a/ui/app/components/app/network-display/index.scss +++ b/ui/app/components/app/network-display/index.scss @@ -33,7 +33,8 @@ } &__name { - font-size: 0.75rem; + @include H7; + padding-left: 5px; } diff --git a/ui/app/components/app/network-display/network-display.component.js b/ui/app/components/app/network-display/network-display.component.js index eb89755c3..0838ddc57 100644 --- a/ui/app/components/app/network-display/network-display.component.js +++ b/ui/app/components/app/network-display/network-display.component.js @@ -32,49 +32,56 @@ export default class NetworkDisplay extends Component { t: PropTypes.func, } - renderNetworkIcon () { + renderNetworkIcon() { const { network } = this.props const networkClass = networkIdToTypeMap[network] - return networkClass - ?
    - : ( -
    - ) + return networkClass ? ( +
    + ) : ( +
    + ) } - render () { - const { colored, network, provider: { type, nickname } } = this.props + render() { + const { + colored, + network, + provider: { type, nickname }, + } = this.props const networkClass = networkIdToTypeMap[network] return (
    - { - networkClass - ?
    - : ( -
    - ) - } + {networkClass ? ( +
    + ) : ( +
    + )}
    - { type === 'rpc' && nickname ? nickname : this.context.t(type) } + {type === 'rpc' && nickname ? nickname : this.context.t(type)}
    ) diff --git a/ui/app/components/app/network.js b/ui/app/components/app/network.js index f66a0182a..387dbb536 100644 --- a/ui/app/components/app/network.js +++ b/ui/app/components/app/network.js @@ -3,7 +3,13 @@ import React, { Component } from 'react' import classnames from 'classnames' import NetworkDropdownIcon from './dropdowns/components/network-dropdown-icon' -function NetworkIndicator ({ disabled, children, hoverText, onClick, providerName }) { +function NetworkIndicator({ + disabled, + children, + hoverText, + onClick, + providerName, +}) { return (
    + + + + + - { - networkNumber === 'loading' - ? ( - onClick(event)}> - - - ) - : ( - - ) - } + {networkNumber === 'loading' ? ( + onClick(event)} + > + + + ) : ( + + )}
    {providerNick || t('privateNetwork')}
    diff --git a/ui/app/components/app/permission-page-container/index.scss b/ui/app/components/app/permission-page-container/index.scss index 56bc9e583..60e2e3e3b 100644 --- a/ui/app/components/app/permission-page-container/index.scss +++ b/ui/app/components/app/permission-page-container/index.scss @@ -35,7 +35,6 @@ &__title { @include H4; - line-height: 25px; text-align: center; margin-top: 32px; width: 100%; @@ -67,7 +66,8 @@ align-items: center; label { - font-size: 14px; + @include H6; + margin-left: 16px; color: $Black-100; } @@ -88,7 +88,6 @@ &__permissions-header { @include H6; - line-height: 20px; color: #6a737d; } diff --git a/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js b/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js index 94895e92d..650b9333b 100644 --- a/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js +++ b/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js @@ -5,7 +5,6 @@ import Tooltip from '../../../ui/tooltip' import CheckBox from '../../../ui/check-box' export default class PermissionPageContainerContent extends PureComponent { - static propTypes = { domainMetadata: PropTypes.shape({ extensionId: PropTypes.string, @@ -29,19 +28,18 @@ export default class PermissionPageContainerContent extends PureComponent { t: PropTypes.func, } - renderRequestedPermissions () { - const { - selectedPermissions, onPermissionToggle, - } = this.props + renderRequestedPermissions() { + const { selectedPermissions, onPermissionToggle } = this.props const { t } = this.context const items = Object.keys(selectedPermissions).map((permissionName) => { - const description = t(permissionName) // don't allow deselecting eth_accounts const isDisabled = permissionName === 'eth_accounts' const isChecked = Boolean(selectedPermissions[permissionName]) - const title = isChecked ? t('permissionCheckedIconDescription') : t('permissionUncheckedIconDescription') + const title = isChecked + ? t('permissionCheckedIconDescription') + : t('permissionUncheckedIconDescription') return (
    - { selectedIdentities.slice(0, 6).map((identity, index) => { + {selectedIdentities.slice(0, 6).map((identity, index) => { return ( -
    - { this.getAccountDescriptor(identity) } +
    + {this.getAccountDescriptor(identity)}
    ) - }) } - { selectedIdentities.length > 6 + })} + {selectedIdentities.length > 6 ? t('plusXMore', [selectedIdentities.length - 6]) - : null - } + : null}
    - )} + } > - { textContent } + {textContent} ) } - getTitle () { - const { domainMetadata, selectedIdentities, allIdentitiesSelected } = this.props + getTitle() { + const { + domainMetadata, + selectedIdentities, + allIdentitiesSelected, + } = this.props const { t } = this.context if (domainMetadata.extensionId) { return t('externalExtension', [domainMetadata.extensionId]) } else if (allIdentitiesSelected) { - return t( - 'connectToAll', - [this.renderAccountTooltip(t('connectToAllAccounts'))], - ) + return t('connectToAll', [ + this.renderAccountTooltip(t('connectToAllAccounts')), + ]) } else if (selectedIdentities.length > 1) { - return t( - 'connectToMultiple', - [ - this.renderAccountTooltip(t('connectToMultipleNumberOfAccounts', [selectedIdentities.length])), - ], - ) + return t('connectToMultiple', [ + this.renderAccountTooltip( + t('connectToMultipleNumberOfAccounts', [selectedIdentities.length]), + ), + ]) } - return t('connectTo', [ - this.getAccountDescriptor(selectedIdentities[0]), - ]) + return t('connectTo', [this.getAccountDescriptor(selectedIdentities[0])]) } - render () { + render() { const { domainMetadata } = this.props const { t } = this.context @@ -143,14 +142,15 @@ export default class PermissionPageContainerContent extends PureComponent { icon={domainMetadata.icon} iconName={domainMetadata.name} headerTitle={title} - headerText={ domainMetadata.extensionId - ? t('allowExternalExtensionTo', [domainMetadata.extensionId]) - : t('allowThisSiteTo') + headerText={ + domainMetadata.extensionId + ? t('allowExternalExtensionTo', [domainMetadata.extensionId]) + : t('allowThisSiteTo') } siteOrigin={domainMetadata.origin} />
    - { this.renderRequestedPermissions() } + {this.renderRequestedPermissions()}
    diff --git a/ui/app/components/app/permission-page-container/permission-page-container.component.js b/ui/app/components/app/permission-page-container/permission-page-container.component.js index 8598ab976..14441adfa 100644 --- a/ui/app/components/app/permission-page-container/permission-page-container.component.js +++ b/ui/app/components/app/permission-page-container/permission-page-container.component.js @@ -6,7 +6,6 @@ import PermissionsConnectFooter from '../permissions-connect-footer' import { PermissionPageContainerContent } from '.' export default class PermissionPageContainer extends Component { - static propTypes = { approvePermissionsRequest: PropTypes.func.isRequired, rejectPermissionsRequest: PropTypes.func.isRequired, @@ -41,7 +40,7 @@ export default class PermissionPageContainer extends Component { ), } - componentDidUpdate () { + componentDidUpdate() { const newMethodNames = this.getRequestedMethodNames(this.props) if (!isEqual(Object.keys(this.state.selectedPermissions), newMethodNames)) { @@ -52,17 +51,14 @@ export default class PermissionPageContainer extends Component { } } - getRequestedMethodState (methodNames) { - return methodNames.reduce( - (acc, methodName) => { - acc[methodName] = true - return acc - }, - {}, - ) + getRequestedMethodState(methodNames) { + return methodNames.reduce((acc, methodName) => { + acc[methodName] = true + return acc + }, {}) } - getRequestedMethodNames (props) { + getRequestedMethodNames(props) { return Object.keys(props.request.permissions || {}) } @@ -75,7 +71,7 @@ export default class PermissionPageContainer extends Component { }) } - componentDidMount () { + componentDidMount() { this.context.metricsEvent({ eventOpts: { category: 'Auth', @@ -92,7 +88,10 @@ export default class PermissionPageContainer extends Component { onSubmit = () => { const { - request: _request, approvePermissionsRequest, rejectPermissionsRequest, selectedIdentities, + request: _request, + approvePermissionsRequest, + rejectPermissionsRequest, + selectedIdentities, } = this.props const request = { @@ -107,13 +106,16 @@ export default class PermissionPageContainer extends Component { }) if (Object.keys(request.permissions).length > 0) { - approvePermissionsRequest(request, selectedIdentities.map((selectedIdentity) => selectedIdentity.address)) + approvePermissionsRequest( + request, + selectedIdentities.map((selectedIdentity) => selectedIdentity.address), + ) } else { rejectPermissionsRequest(request.metadata.id) } } - render () { + render() { const { requestMetadata, targetDomainMetadata, diff --git a/ui/app/components/app/permission-page-container/permission-page-container.container.js b/ui/app/components/app/permission-page-container/permission-page-container.container.js index bae0f43d9..e0b3ff49f 100644 --- a/ui/app/components/app/permission-page-container/permission-page-container.container.js +++ b/ui/app/components/app/permission-page-container/permission-page-container.container.js @@ -6,7 +6,9 @@ const mapStateToProps = (state, ownProps) => { const { selectedIdentities } = ownProps const allIdentities = getMetaMaskIdentities(state) - const allIdentitiesSelected = Object.keys(selectedIdentities).length === Object.keys(allIdentities).length && selectedIdentities.length > 1 + const allIdentitiesSelected = + Object.keys(selectedIdentities).length === + Object.keys(allIdentities).length && selectedIdentities.length > 1 return { allIdentitiesSelected, diff --git a/ui/app/components/app/permissions-connect-footer/index.scss b/ui/app/components/app/permissions-connect-footer/index.scss index 51ee8a09f..910f1857a 100644 --- a/ui/app/components/app/permissions-connect-footer/index.scss +++ b/ui/app/components/app/permissions-connect-footer/index.scss @@ -7,7 +7,6 @@ &__text { @include H7; - line-height: 17px; color: #6a737d; display: flex; margin-top: 10px; diff --git a/ui/app/components/app/permissions-connect-footer/permissions-connect-footer.component.js b/ui/app/components/app/permissions-connect-footer/permissions-connect-footer.component.js index 7bc2a8afd..5a1aef1a4 100644 --- a/ui/app/components/app/permissions-connect-footer/permissions-connect-footer.component.js +++ b/ui/app/components/app/permissions-connect-footer/permissions-connect-footer.component.js @@ -6,18 +6,22 @@ export default class PermissionsConnectFooter extends Component { t: PropTypes.func, } - render () { + render() { const { t } = this.context return (
    -
    { t('onlyConnectTrust') }
    +
    {t('onlyConnectTrust')}
    { - global.platform.openTab({ url: 'https://medium.com/metamask/privacy-mode-is-now-enabled-by-default-1c1c957f4d57' }) + global.platform.openTab({ + url: + 'https://medium.com/metamask/privacy-mode-is-now-enabled-by-default-1c1c957f4d57', + }) }} - >{ t('learnMore') } + > + {t('learnMore')}
    diff --git a/ui/app/components/app/permissions-connect-header/index.scss b/ui/app/components/app/permissions-connect-header/index.scss index 1ec0628e0..89297a094 100644 --- a/ui/app/components/app/permissions-connect-header/index.scss +++ b/ui/app/components/app/permissions-connect-header/index.scss @@ -30,7 +30,7 @@ text-align: center; color: $Black-100; - margin-top: 14px; + margin-top: 16px; } &__text, @@ -45,6 +45,7 @@ width: 100%; text-overflow: ellipsis; overflow: hidden; + margin-top: 8px; /*rtl:ignore*/ direction: rtl; diff --git a/ui/app/components/app/permissions-connect-header/permissions-connect-header.component.js b/ui/app/components/app/permissions-connect-header/permissions-connect-header.component.js index c1bc5d775..0a71d0d61 100644 --- a/ui/app/components/app/permissions-connect-header/permissions-connect-header.component.js +++ b/ui/app/components/app/permissions-connect-header/permissions-connect-header.component.js @@ -17,28 +17,24 @@ export default class PermissionsConnectHeader extends Component { headerText: '', } - renderHeaderIcon () { + renderHeaderIcon() { const { icon, iconName, siteOrigin } = this.props return (
    - +
    {siteOrigin}
    ) } - render () { + render() { const { headerTitle, headerText } = this.props return (
    - { this.renderHeaderIcon() } -
    - { headerTitle } -
    -
    - { headerText } -
    + {this.renderHeaderIcon()} +
    {headerTitle}
    +
    {headerText}
    ) } diff --git a/ui/app/components/app/selected-account/index.scss b/ui/app/components/app/selected-account/index.scss index c90ef7d78..05757b73e 100644 --- a/ui/app/components/app/selected-account/index.scss +++ b/ui/app/components/app/selected-account/index.scss @@ -10,10 +10,10 @@ } &__name { + @include H5; + width: 100%; - font-size: 1rem; font-weight: 500; - line-height: 19px; color: $black; text-overflow: ellipsis; overflow: hidden; @@ -23,8 +23,8 @@ } &__address { - font-size: 0.75rem; - line-height: 0.75rem; + @include H7; + color: #989a9b; } @@ -37,6 +37,8 @@ padding: 6px 1px; border-radius: 10px; cursor: pointer; + width: 100%; + background-color: unset; &:hover { background-color: $Grey-000; diff --git a/ui/app/components/app/selected-account/selected-account.component.js b/ui/app/components/app/selected-account/selected-account.component.js index c1756b5ee..b3b04b5d0 100644 --- a/ui/app/components/app/selected-account/selected-account.component.js +++ b/ui/app/components/app/selected-account/selected-account.component.js @@ -18,18 +18,18 @@ class SelectedAccount extends Component { selectedIdentity: PropTypes.object.isRequired, } - componentDidMount () { + componentDidMount() { this.copyTimeout = null } - componentWillUnmount () { + componentWillUnmount() { if (this.copyTimeout) { clearTimeout(this.copyTimeout) this.copyTimeout = null } } - render () { + render() { const { t } = this.context const { selectedIdentity } = this.props const checksummedAddress = checksumAddress(selectedIdentity.address) @@ -39,23 +39,28 @@ class SelectedAccount extends Component { -
    { this.setState({ copied: true }) - this.copyTimeout = setTimeout(() => this.setState({ copied: false }), 3000) + this.copyTimeout = setTimeout( + () => this.setState({ copied: false }), + 3000, + ) copyToClipboard(checksummedAddress) }} >
    - { selectedIdentity.name } + {selectedIdentity.name}
    - { shortenAddress(checksummedAddress) } + {shortenAddress(checksummedAddress)}
    -
    +
    ) diff --git a/ui/app/components/app/selected-account/tests/selected-account-component.test.js b/ui/app/components/app/selected-account/tests/selected-account-component.test.js index 943ccf00a..d0a608f3e 100644 --- a/ui/app/components/app/selected-account/tests/selected-account-component.test.js +++ b/ui/app/components/app/selected-account/tests/selected-account-component.test.js @@ -5,13 +5,20 @@ import SelectedAccount from '../selected-account.component' describe('SelectedAccount Component', function () { it('should render checksummed address', function () { - const wrapper = render(( + const wrapper = render( - ), { context: { t: () => undefined } }) + selectedIdentity={{ + name: 'testName', + address: '0x1b82543566f41a7db9a9a75fc933c340ffb55c9d', + }} + />, + { context: { t: () => undefined } }, + ) // Checksummed version of address is displayed - assert.equal(wrapper.find('.selected-account__address').text(), '0x1B82...5C9D') + assert.equal( + wrapper.find('.selected-account__address').text(), + '0x1B82...5C9D', + ) assert.equal(wrapper.find('.selected-account__name').text(), 'testName') }) }) diff --git a/ui/app/components/app/sidebars/sidebar-content.scss b/ui/app/components/app/sidebars/sidebar-content.scss index e67bab43a..91222fdbc 100644 --- a/ui/app/components/app/sidebars/sidebar-content.scss +++ b/ui/app/components/app/sidebars/sidebar-content.scss @@ -57,7 +57,7 @@ max-width: 318px; &__time-estimate { - font-size: 12px; + @include H7; } } } diff --git a/ui/app/components/app/sidebars/sidebar.component.js b/ui/app/components/app/sidebars/sidebar.component.js index 3f514001a..37c1abcd9 100644 --- a/ui/app/components/app/sidebars/sidebar.component.js +++ b/ui/app/components/app/sidebars/sidebar.component.js @@ -4,7 +4,6 @@ import ReactCSSTransitionGroup from 'react-transition-group/CSSTransitionGroup' import CustomizeGas from '../gas-customization/gas-modal-page-container' export default class Sidebar extends Component { - static propTypes = { sidebarOpen: PropTypes.bool, hideSidebar: PropTypes.func, @@ -15,39 +14,42 @@ export default class Sidebar extends Component { onOverlayClose: PropTypes.func, } - renderOverlay () { + renderOverlay() { const { onOverlayClose } = this.props return (
    { - onOverlayClose && onOverlayClose() + onOverlayClose?.() this.props.hideSidebar() }} /> ) } - renderSidebarContent () { + renderSidebarContent() { const { type, sidebarProps = {} } = this.props const { transaction = {} } = sidebarProps switch (type) { case 'customize-gas': - return
    + return ( +
    + +
    + ) default: return null } - } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { if (!prevProps.sidebarShouldClose && this.props.sidebarShouldClose) { this.props.hideSidebar() } } - render () { + render() { const { transitionName, sidebarOpen, sidebarShouldClose } = this.props return ( @@ -57,11 +59,12 @@ export default class Sidebar extends Component { transitionEnterTimeout={300} transitionLeaveTimeout={200} > - { sidebarOpen && !sidebarShouldClose ? this.renderSidebarContent() : null } + {sidebarOpen && !sidebarShouldClose + ? this.renderSidebarContent() + : null} - { sidebarOpen && !sidebarShouldClose ? this.renderOverlay() : null } + {sidebarOpen && !sidebarShouldClose ? this.renderOverlay() : null}
    ) } - } diff --git a/ui/app/components/app/sidebars/tests/sidebars-component.test.js b/ui/app/components/app/sidebars/tests/sidebars-component.test.js index ea9958775..22a7cbc2c 100644 --- a/ui/app/components/app/sidebars/tests/sidebars-component.test.js +++ b/ui/app/components/app/sidebars/tests/sidebars-component.test.js @@ -15,14 +15,14 @@ describe('Sidebar Component', function () { let wrapper beforeEach(function () { - wrapper = shallow(( + wrapper = shallow( - )) + />, + ) }) afterEach(function () { @@ -85,7 +85,16 @@ describe('Sidebar Component', function () { assert(wrapper.children().at(1).hasClass('sidebar-overlay')) assert.equal(wrapper.children().at(0).children().length, 1) assert(wrapper.children().at(0).children().at(0).hasClass('sidebar-left')) - assert(wrapper.children().at(0).children().at(0).children().at(0).is(CustomizeGas)) + assert( + wrapper + .children() + .at(0) + .children() + .at(0) + .children() + .at(0) + .is(CustomizeGas), + ) }) }) }) diff --git a/ui/app/components/app/signature-request-original/index.scss b/ui/app/components/app/signature-request-original/index.scss index 6af476fd6..c3b4aaf3b 100644 --- a/ui/app/components/app/signature-request-original/index.scss +++ b/ui/app/components/app/signature-request-original/index.scss @@ -61,9 +61,9 @@ } &__header__text { + @include H3; + color: #5b5d67; - font-size: 22px; - line-height: 29px; z-index: 3; } @@ -96,14 +96,14 @@ } &__account-text { - font-size: 14px; + @include H6; } &__account-item { + @include H7; + height: 22px; background-color: $white; - line-height: 16px; - font-size: 12px; width: 124px; .account-list-item { @@ -129,8 +129,9 @@ } &__balance-text { + @include H6; + text-align: right; - font-size: 14px; } &__balance-value { @@ -157,19 +158,19 @@ } &__headline { + @include H4; + height: 48px; width: 240px; color: $tundora; - font-size: 18px; - line-height: 24px; text-align: center; margin-top: 20px; } &__notice, &__warning { - font-size: 14px; - line-height: 19px; + @include H6; + text-align: center; margin-top: 41px; margin-bottom: 11px; @@ -200,19 +201,19 @@ } &__row-title { + @include H5; + width: 80px; color: $dusty-gray; - font-size: 16px; - line-height: 22px; margin-top: 12px; margin-left: 18px; width: 100%; } &__row-value { + @include H6; + color: $scorpion; - font-size: 14px; - line-height: 19px; width: 100%; overflow-wrap: break-word; border-bottom: 1px solid #d2d8dd; @@ -227,11 +228,12 @@ } &__footer { + @include H3; + width: 100%; display: flex; align-items: center; justify-content: center; - font-size: 22px; position: relative; flex: 0 0 auto; border-top: 1px solid $geyser; diff --git a/ui/app/components/app/signature-request-original/signature-request-original.component.js b/ui/app/components/app/signature-request-original/signature-request-original.component.js index 31c214e38..b961f4def 100644 --- a/ui/app/components/app/signature-request-original/signature-request-original.component.js +++ b/ui/app/components/app/signature-request-original/signature-request-original.component.js @@ -4,7 +4,10 @@ import ethUtil from 'ethereumjs-util' import classnames from 'classnames' import { ObjectInspector } from 'react-inspector' -import { ENVIRONMENT_TYPE_NOTIFICATION, MESSAGE_TYPE } from '../../../../../app/scripts/lib/enums' +import { + ENVIRONMENT_TYPE_NOTIFICATION, + MESSAGE_TYPE, +} from '../../../../../app/scripts/lib/enums' import { getEnvironmentType } from '../../../../../app/scripts/lib/util' import Identicon from '../../ui/identicon' import AccountListItem from '../account-list-item' @@ -73,7 +76,7 @@ export default class SignatureRequestOriginal extends Component {
    - { this.context.t('sigRequest') } + {this.context.t('sigRequest')}
    @@ -89,13 +92,11 @@ export default class SignatureRequestOriginal extends Component { return (
    - { `${this.context.t('account')}:` } + {`${this.context.t('account')}:`}
    - +
    ) @@ -103,7 +104,9 @@ export default class SignatureRequestOriginal extends Component { renderBalance = () => { const { conversionRate } = this.props - const { fromAccount: { balance } } = this.state + const { + fromAccount: { balance }, + } = this.state const balanceInEther = conversionUtil(balance, { fromNumericBase: 'hex', @@ -116,10 +119,10 @@ export default class SignatureRequestOriginal extends Component { return (
    - { `${this.context.t('balance')}:` } + {`${this.context.t('balance')}:`}
    - { `${balanceInEther} ETH` } + {`${balanceInEther} ETH`}
    ) @@ -130,10 +133,7 @@ export default class SignatureRequestOriginal extends Component { return (
    - +
    ) } @@ -141,9 +141,9 @@ export default class SignatureRequestOriginal extends Component { renderAccountInfo = () => { return (
    - { this.renderAccount() } - { this.renderRequestIcon() } - { this.renderBalance() } + {this.renderAccount()} + {this.renderRequestIcon()} + {this.renderBalance()}
    ) } @@ -152,7 +152,7 @@ export default class SignatureRequestOriginal extends Component { return (
    - { this.context.t('yourSigRequested') } + {this.context.t('yourSigRequested')}
    ) @@ -172,30 +172,22 @@ export default class SignatureRequestOriginal extends Component { const { domain, message } = JSON.parse(data) return (
    - { - domain - ? ( -
    -

    - Domain -

    - -
    - ) - : '' - } - { - message - ? ( -
    -

    - Message -

    - -
    - ) - : '' - } + {domain ? ( +
    +

    Domain

    + +
    + ) : ( + '' + )} + {message ? ( +
    +

    Message

    + +
    + ) : ( + '' + )}
    ) } @@ -205,10 +197,15 @@ export default class SignatureRequestOriginal extends Component { let notice = `${this.context.t('youSign')}:` const { txData } = this.props - const { type, msgParams: { data } } = txData + const { + type, + msgParams: { data }, + } = txData if (type === MESSAGE_TYPE.PERSONAL_SIGN) { - rows = [{ name: this.context.t('message'), value: this.msgHexToText(data) }] + rows = [ + { name: this.context.t('message'), value: this.msgHexToText(data) }, + ] } else if (type === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA) { rows = data } else if (type === MESSAGE_TYPE.ETH_SIGN) { @@ -218,57 +215,57 @@ export default class SignatureRequestOriginal extends Component { return (
    - { this.renderAccountInfo() } - { this.renderRequestInfo() } + {this.renderAccountInfo()} + {this.renderRequestInfo()}
    - { notice } - { - type === MESSAGE_TYPE.ETH_SIGN - ? ( - { - global.platform.openTab({ - url: 'https://metamask.zendesk.com/hc/en-us/articles/360015488751', - }) - }} - > - { this.context.t('learnMore') } - - ) - : null - } + {notice} + {type === MESSAGE_TYPE.ETH_SIGN ? ( + { + global.platform.openTab({ + url: + 'https://metamask.zendesk.com/hc/en-us/articles/360015488751', + }) + }} + > + {this.context.t('learnMore')} + + ) : null}
    - { - rows.map(({ name, value }, index) => { - if (typeof value === 'boolean') { - // eslint-disable-next-line no-param-reassign - value = value.toString() - } - return ( -
    -
    - { `${name}:` } -
    -
    - { value } -
    -
    - ) - }) - } + {rows.map(({ name, value }, index) => { + if (typeof value === 'boolean') { + // eslint-disable-next-line no-param-reassign + value = value.toString() + } + return ( +
    +
    {`${name}:`}
    +
    {value}
    +
    + ) + })}
    ) } renderFooter = () => { - const { cancel, clearConfirmTransaction, history, mostRecentOverviewPage, sign } = this.props + const { + cancel, + clearConfirmTransaction, + history, + mostRecentOverviewPage, + sign, + } = this.props return (
    @@ -290,7 +287,7 @@ export default class SignatureRequestOriginal extends Component { history.push(mostRecentOverviewPage) }} > - { this.context.t('cancel') } + {this.context.t('cancel')}
    ) @@ -320,9 +317,9 @@ export default class SignatureRequestOriginal extends Component { render = () => { return (
    - { this.renderHeader() } - { this.renderBody() } - { this.renderFooter() } + {this.renderHeader()} + {this.renderBody()} + {this.renderFooter()}
    ) } diff --git a/ui/app/components/app/signature-request-original/signature-request-original.container.js b/ui/app/components/app/signature-request-original/signature-request-original.container.js index 8d0a397ef..9a421aa67 100644 --- a/ui/app/components/app/signature-request-original/signature-request-original.container.js +++ b/ui/app/components/app/signature-request-original/signature-request-original.container.js @@ -13,7 +13,7 @@ import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/conf import { getMostRecentOverviewPage } from '../../../ducks/history/history' import SignatureRequestOriginal from './signature-request-original.component' -function mapStateToProps (state) { +function mapStateToProps(state) { return { requester: null, requesterAddress: null, @@ -24,14 +24,14 @@ function mapStateToProps (state) { } } -function mapDispatchToProps (dispatch) { +function mapDispatchToProps(dispatch) { return { goHome: () => dispatch(goHome()), clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), } } -function mergeProps (stateProps, dispatchProps, ownProps) { +function mergeProps(stateProps, dispatchProps, ownProps) { const { signPersonalMessage, signTypedMessage, @@ -45,7 +45,10 @@ function mergeProps (stateProps, dispatchProps, ownProps) { const { allAccounts } = stateProps delete stateProps.allAccounts - const { type, msgParams: { from } } = txData + const { + type, + msgParams: { from }, + } = txData const fromAccount = getAccountByAddress(allAccounts, from) diff --git a/ui/app/components/app/signature-request/index.scss b/ui/app/components/app/signature-request/index.scss index 4de4f249b..16008cbc2 100644 --- a/ui/app/components/app/signature-request/index.scss +++ b/ui/app/components/app/signature-request/index.scss @@ -22,7 +22,8 @@ } .network-display__name { - font-size: 12px; + @include H7; + white-space: nowrap; font-weight: 500; } @@ -38,9 +39,9 @@ min-height: min-content; &__title { - font-style: normal; + @include H5; + font-weight: 500; - font-size: 18px; } &__identicon-container { @@ -73,17 +74,19 @@ } &__info { - font-size: 12px; + @include H7; } &__info--bolded { - font-size: 16px; + @include Paragraph; + font-weight: 500; } p { + @include H6; + color: #999; - font-size: 0.8rem; } .identicon {} diff --git a/ui/app/components/app/signature-request/signature-request-footer/signature-request-footer.component.js b/ui/app/components/app/signature-request/signature-request-footer/signature-request-footer.component.js index 591b9a03a..0f925e3e3 100644 --- a/ui/app/components/app/signature-request/signature-request-footer/signature-request-footer.component.js +++ b/ui/app/components/app/signature-request/signature-request-footer/signature-request-footer.component.js @@ -12,12 +12,16 @@ export default class SignatureRequestFooter extends PureComponent { t: PropTypes.func, } - render () { + render() { const { cancelAction, signAction } = this.props return (
    - - + +
    ) } diff --git a/ui/app/components/app/signature-request/signature-request-header/index.scss b/ui/app/components/app/signature-request/signature-request-header/index.scss index cd94a0e31..b4f3b5479 100644 --- a/ui/app/components/app/signature-request/signature-request-header/index.scss +++ b/ui/app/components/app/signature-request/signature-request-header/index.scss @@ -1,9 +1,10 @@ .signature-request-header { + @include H7; + display: flex; padding: 1rem; border-bottom: 1px solid $geyser; justify-content: space-between; - font-size: 0.75rem; &--account, &--network { @@ -21,7 +22,8 @@ } &__account-name { - font-size: 12px; + @include H7; + font-weight: 500; } diff --git a/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js b/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js index 2ec74748c..42468d276 100644 --- a/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js +++ b/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js @@ -8,17 +8,13 @@ export default class SignatureRequestHeader extends PureComponent { fromAccount: PropTypes.object, } - render () { + render() { const { fromAccount } = this.props return (
    - {fromAccount && ( - - )} + {fromAccount && }
    diff --git a/ui/app/components/app/signature-request/signature-request-message/index.scss b/ui/app/components/app/signature-request/signature-request-message/index.scss index 37c0fba72..fbddf241e 100644 --- a/ui/app/components/app/signature-request/signature-request-message/index.scss +++ b/ui/app/components/app/signature-request/signature-request-message/index.scss @@ -4,16 +4,18 @@ flex-direction: column; &__title { + @include H6; + font-weight: 500; - font-size: 14px; color: #636778; margin-left: 12px; } h2 { + @include H6; + flex: 1 1 0; text-align: left; - font-size: 0.8rem; border-bottom: 1px solid #d2d8dd; padding: 0.5rem; margin: 0; @@ -35,9 +37,8 @@ } &__type-title { - font-style: normal; - font-weight: normal; - font-size: 14px; + @include H6; + margin-left: 12px; margin-top: 6px; margin-bottom: 10px; diff --git a/ui/app/components/app/signature-request/signature-request-message/signature-request-message.component.js b/ui/app/components/app/signature-request/signature-request-message/signature-request-message.component.js index 5c4b455d3..27a3e2563 100644 --- a/ui/app/components/app/signature-request/signature-request-message/signature-request-message.component.js +++ b/ui/app/components/app/signature-request/signature-request-message/signature-request-message.component.js @@ -11,36 +11,45 @@ export default class SignatureRequestMessage extends PureComponent { t: PropTypes.func, } - renderNode (data) { + renderNode(data) { return (
    {Object.entries(data).map(([label, value], i) => (
    - {label}: - { - typeof value === 'object' && value !== null ? - this.renderNode(value) - : {value} - } + + {label}:{' '} + + {typeof value === 'object' && value !== null ? ( + this.renderNode(value) + ) : ( + + {value} + + )}
    ))}
    ) } - render () { + render() { const { data } = this.props return (
    -
    {this.context.t('signatureRequest1')}
    +
    + {this.context.t('signatureRequest1')} +
    -
    {this.context.t('signatureRequest1')}
    +
    + {this.context.t('signatureRequest1')} +
    {this.renderNode(data)}
    diff --git a/ui/app/components/app/signature-request/signature-request.component.js b/ui/app/components/app/signature-request/signature-request.component.js index d3a42107c..712c5e8c2 100644 --- a/ui/app/components/app/signature-request/signature-request.component.js +++ b/ui/app/components/app/signature-request/signature-request.component.js @@ -26,7 +26,7 @@ export default class SignatureRequest extends PureComponent { metricsEvent: PropTypes.func, } - componentDidMount () { + componentDidMount() { const { clearConfirmTransaction, cancel } = this.props const { metricsEvent } = this.context if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) { @@ -44,14 +44,19 @@ export default class SignatureRequest extends PureComponent { } } - formatWallet (wallet) { - return `${wallet.slice(0, 8)}...${wallet.slice(wallet.length - 8, wallet.length)}` + formatWallet(wallet) { + return `${wallet.slice(0, 8)}...${wallet.slice( + wallet.length - 8, + wallet.length, + )}` } - render () { + render() { const { fromAccount, - txData: { msgParams: { data, origin } }, + txData: { + msgParams: { data, origin }, + }, cancel, sign, } = this.props @@ -62,18 +67,23 @@ export default class SignatureRequest extends PureComponent {
    -
    {this.context.t('sigRequest')}
    -
    -
    { domain.name && domain.name[0] }
    -
    - +
    + {this.context.t('sigRequest')} +
    +
    +
    + {domain.name && domain.name[0]} +
    +
    + +
    +
    + {domain.name}
    -
    {domain.name}
    {origin}
    -
    {this.formatWallet(fromAddress)}
    +
    + {this.formatWallet(fromAddress)} +
    diff --git a/ui/app/components/app/signature-request/signature-request.container.js b/ui/app/components/app/signature-request/signature-request.container.js index 939741904..0a521db4b 100644 --- a/ui/app/components/app/signature-request/signature-request.container.js +++ b/ui/app/components/app/signature-request/signature-request.container.js @@ -1,26 +1,24 @@ import { connect } from 'react-redux' import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck' -import { - accountsWithSendEtherInfoSelector, -} from '../../../selectors' +import { accountsWithSendEtherInfoSelector } from '../../../selectors' import { getAccountByAddress } from '../../../helpers/utils/util' import { MESSAGE_TYPE } from '../../../../../app/scripts/lib/enums' import SignatureRequest from './signature-request.component' -function mapStateToProps (state) { +function mapStateToProps(state) { return { // not forwarded to component allAccounts: accountsWithSendEtherInfoSelector(state), } } -function mapDispatchToProps (dispatch) { +function mapDispatchToProps(dispatch) { return { clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), } } -function mergeProps (stateProps, dispatchProps, ownProps) { +function mergeProps(stateProps, dispatchProps, ownProps) { const { allAccounts } = stateProps const { signPersonalMessage, @@ -32,7 +30,10 @@ function mergeProps (stateProps, dispatchProps, ownProps) { txData, } = ownProps - const { type, msgParams: { from } } = txData + const { + type, + msgParams: { from }, + } = txData const fromAccount = getAccountByAddress(allAccounts, from) @@ -60,4 +61,8 @@ function mergeProps (stateProps, dispatchProps, ownProps) { } } -export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(SignatureRequest) +export default connect( + mapStateToProps, + mapDispatchToProps, + mergeProps, +)(SignatureRequest) diff --git a/ui/app/components/app/signature-request/tests/signature-request.test.js b/ui/app/components/app/signature-request/tests/signature-request.test.js index 5d2874833..331abac04 100644 --- a/ui/app/components/app/signature-request/tests/signature-request.test.js +++ b/ui/app/components/app/signature-request/tests/signature-request.test.js @@ -7,7 +7,7 @@ describe('Signature Request Component', function () { describe('render', function () { const fromAddress = '0x123456789abcdef' it('should render a div with one child', function () { - const wrapper = shallow(( + const wrapper = shallow( undefined} cancel={() => undefined} @@ -19,8 +19,8 @@ describe('Signature Request Component', function () { }, }} fromAccount={{ address: fromAddress }} - /> - )) + />, + ) assert(wrapper.is('div')) assert.equal(wrapper.length, 1) diff --git a/ui/app/components/app/tab-bar/index.scss b/ui/app/components/app/tab-bar/index.scss index 0b9d0605d..1e20d1fed 100644 --- a/ui/app/components/app/tab-bar/index.scss +++ b/ui/app/components/app/tab-bar/index.scss @@ -5,16 +5,19 @@ &__tab { + @include Paragraph; + display: flex; flex-flow: row nowrap; align-items: flex-start; min-width: 0; flex: 0 0 auto; box-sizing: border-box; - font-size: 16px; padding: 16px 24px; opacity: 0.5; transition: opacity 200ms ease-in-out; + background-color: unset; + text-align: start; @media screen and (min-width: 576px) { &:hover { @@ -27,7 +30,8 @@ } @media screen and (max-width: 575px) { - font-size: 18px; + @include H4; + padding: 24px; border-bottom: 1px solid $alto; opacity: 1; @@ -41,8 +45,9 @@ display: none; @media screen and (max-width: 575px) { + @include H6; + display: block; - font-size: 14px; font-weight: 300; margin-top: 8px; min-height: 14px; diff --git a/ui/app/components/app/tab-bar/tab-bar.js b/ui/app/components/app/tab-bar/tab-bar.js index 6abd56936..e4f38feac 100644 --- a/ui/app/components/app/tab-bar/tab-bar.js +++ b/ui/app/components/app/tab-bar/tab-bar.js @@ -8,7 +8,7 @@ const TabBar = (props) => { return (
    {tabs.map(({ key, content, description }) => ( -
    { >
    {content}
    -
    {description}
    +
    + {description} +
    -
    + ))}
    ) diff --git a/ui/app/components/app/tests/signature-request.test.js b/ui/app/components/app/tests/signature-request.test.js index 9efd70973..f95bed3af 100644 --- a/ui/app/components/app/tests/signature-request.test.js +++ b/ui/app/components/app/tests/signature-request.test.js @@ -20,9 +20,7 @@ describe('Signature Request', function () { balance: '0x03', }, }, - cachedBalances: { - - }, + cachedBalances: {}, selectedAddress: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', }, } @@ -42,7 +40,8 @@ describe('Signature Request', function () { txData: { msgParams: { id: 1, - data: '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"4","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},"contents":"Hello, Bob!"}}', + data: + '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"4","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},"contents":"Hello, Bob!"}}', from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', origin: 'test.domain', }, @@ -56,7 +55,8 @@ describe('Signature Request', function () { wrapper = mountWithRouter( - , store, + , + store, ) }) @@ -77,5 +77,4 @@ describe('Signature Request', function () { assert(props.sign.calledOnce) }) - }) diff --git a/ui/app/components/app/token-cell/token-cell.js b/ui/app/components/app/token-cell/token-cell.js index f97287d2d..9a7673588 100644 --- a/ui/app/components/app/token-cell/token-cell.js +++ b/ui/app/components/app/token-cell/token-cell.js @@ -7,7 +7,7 @@ import { getSelectedAddress } from '../../../selectors' import { useI18nContext } from '../../../hooks/useI18nContext' import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount' -export default function TokenCell ({ +export default function TokenCell({ address, decimals, outdatedBalance, @@ -21,25 +21,25 @@ export default function TokenCell ({ const formattedFiat = useTokenFiatAmount(address, string, symbol) - const warning = outdatedBalance - ? ( - - { t('troubleTokenBalances') } - - { t('here') } - - - ) - : null + const warning = outdatedBalance ? ( + + {t('troubleTokenBalances')} + + {t('here')} + + + ) : null return ( - ) } diff --git a/ui/app/components/app/token-cell/token-cell.test.js b/ui/app/components/app/token-cell/token-cell.test.js index 0e49a14cc..1e876cfb3 100644 --- a/ui/app/components/app/token-cell/token-cell.test.js +++ b/ui/app/components/app/token-cell/token-cell.test.js @@ -20,7 +20,7 @@ describe('Token Cell', function () { contractExchangeRates: { '0xAnotherToken': 0.015, }, - conversionRate: 7.00, + conversionRate: 7.0, preferences: {}, provider: { chainId: '1', diff --git a/ui/app/components/app/token-list/token-list.js b/ui/app/components/app/token-list/token-list.js index b71e6930b..8e4ead9f9 100644 --- a/ui/app/components/app/token-list/token-list.js +++ b/ui/app/components/app/token-list/token-list.js @@ -9,7 +9,7 @@ import { useTokenTracker } from '../../../hooks/useTokenTracker' import { getAssetImages } from '../../../selectors' import { getTokens } from '../../../ducks/metamask/metamask' -export default function TokenList ({ onTokenClick }) { +export default function TokenList({ onTokenClick }) { const t = useI18nContext() const assetImages = useSelector(getAssetImages) // use `isEqual` comparison function because the token array is serialized diff --git a/ui/app/components/app/transaction-activity-log/index.scss b/ui/app/components/app/transaction-activity-log/index.scss index d5b748d17..7e08f7004 100644 --- a/ui/app/components/app/transaction-activity-log/index.scss +++ b/ui/app/components/app/transaction-activity-log/index.scss @@ -54,8 +54,9 @@ } &__activity-text { + @include H7; + color: $dusty-gray; - font-size: 0.75rem; cursor: pointer; &:hover { @@ -73,7 +74,8 @@ } &__action-link { - font-size: 0.75rem; + @include H7; + cursor: pointer; color: $primary-blue; } diff --git a/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.component.test.js b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.component.test.js index 9b3c39792..ec5cbb3df 100644 --- a/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.component.test.js +++ b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.component.test.js @@ -8,25 +8,32 @@ describe('TransactionActivityLog Component', function () { const activities = [ { eventKey: 'transactionCreated', - hash: '0xe46c7f9b39af2fbf1c53e66f72f80343ab54c2c6dba902d51fb98ada08fe1a63', + hash: + '0xe46c7f9b39af2fbf1c53e66f72f80343ab54c2c6dba902d51fb98ada08fe1a63', id: 2005383477493174, timestamp: 1543957986150, value: '0x2386f26fc10000', - }, { + }, + { eventKey: 'transactionSubmitted', - hash: '0xe46c7f9b39af2fbf1c53e66f72f80343ab54c2c6dba902d51fb98ada08fe1a63', + hash: + '0xe46c7f9b39af2fbf1c53e66f72f80343ab54c2c6dba902d51fb98ada08fe1a63', id: 2005383477493174, timestamp: 1543957987853, value: '0x1319718a5000', - }, { + }, + { eventKey: 'transactionResubmitted', - hash: '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', + hash: + '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', id: 2005383477493175, timestamp: 1543957991563, value: '0x1502634b5800', - }, { + }, + { eventKey: 'transactionConfirmed', - hash: '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', + hash: + '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', id: 2005383477493175, timestamp: 1543958029960, value: '0x1502634b5800', @@ -59,21 +66,26 @@ describe('TransactionActivityLog Component', function () { id: 1, timestamp: 1, value: '0x1', - }, { + }, + { eventKey: 'transactionSubmitted', hash: '0xa', id: 1, timestamp: 2, value: '0x1', - }, { + }, + { eventKey: 'transactionResubmitted', - hash: '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', + hash: + '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', id: 2, timestamp: 3, value: '0x1', - }, { + }, + { eventKey: 'transactionCancelAttempted', - hash: '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', + hash: + '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', id: 3, timestamp: 4, value: '0x1', @@ -97,7 +109,10 @@ describe('TransactionActivityLog Component', function () { assert.ok(wrapper.hasClass('transaction-activity-log')) assert.ok(wrapper.hasClass('test-class')) - assert.equal(wrapper.find('.transaction-activity-log__action-link').length, 2) + assert.equal( + wrapper.find('.transaction-activity-log__action-link').length, + 2, + ) }) it('should not render inline retry and cancel buttons for newer pending transactions', function () { @@ -108,21 +123,26 @@ describe('TransactionActivityLog Component', function () { id: 1, timestamp: 1, value: '0x1', - }, { + }, + { eventKey: 'transactionSubmitted', hash: '0xa', id: 1, timestamp: 2, value: '0x1', - }, { + }, + { eventKey: 'transactionResubmitted', - hash: '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', + hash: + '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', id: 2, timestamp: 3, value: '0x1', - }, { + }, + { eventKey: 'transactionCancelAttempted', - hash: '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', + hash: + '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87', id: 3, timestamp: 4, value: '0x1', @@ -146,6 +166,9 @@ describe('TransactionActivityLog Component', function () { assert.ok(wrapper.hasClass('transaction-activity-log')) assert.ok(wrapper.hasClass('test-class')) - assert.equal(wrapper.find('.transaction-activity-log__action-link').length, 0) + assert.equal( + wrapper.find('.transaction-activity-log__action-link').length, + 0, + ) }) }) diff --git a/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.container.test.js b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.container.test.js index 6d72794f3..b756a5962 100644 --- a/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.container.test.js +++ b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.container.test.js @@ -22,7 +22,10 @@ describe('TransactionActivityLog container', function () { }, } - assert.deepEqual(mapStateToProps(mockState), { conversionRate: 280.45, nativeCurrency: 'ETH' }) + assert.deepEqual(mapStateToProps(mockState), { + conversionRate: 280.45, + nativeCurrency: 'ETH', + }) }) }) }) diff --git a/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.util.test.js b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.util.test.js index 7e4a4313a..5b9ab0662 100644 --- a/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.util.test.js +++ b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.util.test.js @@ -1,5 +1,12 @@ import assert from 'assert' -import { combineTransactionHistories, getActivities } from '../transaction-activity-log.util' +import { + TRANSACTION_STATUSES, + TRANSACTION_TYPES, +} from '../../../../../../shared/constants/transaction' +import { + combineTransactionHistories, + getActivities, +} from '../transaction-activity-log.util' describe('TransactionActivityLog utils', function () { describe('combineTransactionHistories', function () { @@ -10,31 +17,62 @@ describe('TransactionActivityLog utils', function () { it('should return activities for an array of transactions', function () { const transactions = [ { - hash: '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3', + hash: + '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3', history: [ { - 'id': 6400627574331058, - 'time': 1543958845581, - 'status': 'unapproved', - 'metamaskNetworkId': '3', - 'loadingDefaults': true, - 'txParams': { - 'from': '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', - 'to': '0xc5ae6383e126f901dcb06131d97a88745bfa88d6', - 'value': '0x2386f26fc10000', - 'gas': '0x5208', - 'gasPrice': '0x3b9aca00', + id: 6400627574331058, + time: 1543958845581, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: '3', + loadingDefaults: true, + txParams: { + from: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', + to: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6', + value: '0x2386f26fc10000', + gas: '0x5208', + gasPrice: '0x3b9aca00', }, - 'type': 'standard', + type: TRANSACTION_TYPES.STANDARD, }, - [{ 'op': 'replace', 'path': '/status', 'value': 'approved', 'note': 'txStateManager: setting status to approved', 'timestamp': 1543958847813 }], - [{ 'op': 'replace', 'path': '/status', 'value': 'submitted', 'note': 'txStateManager: setting status to submitted', 'timestamp': 1543958848147 }], - [{ 'op': 'replace', 'path': '/status', 'value': 'dropped', 'note': 'txStateManager: setting status to dropped', 'timestamp': 1543958897181 }, { 'op': 'add', 'path': '/replacedBy', 'value': '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33' }], + [ + { + op: 'replace', + path: '/status', + value: TRANSACTION_STATUSES.APPROVED, + note: 'txStateManager: setting status to approved', + timestamp: 1543958847813, + }, + ], + [ + { + op: 'replace', + path: '/status', + value: TRANSACTION_STATUSES.SUBMITTED, + note: 'txStateManager: setting status to submitted', + timestamp: 1543958848147, + }, + ], + [ + { + op: 'replace', + path: '/status', + value: TRANSACTION_STATUSES.DROPPED, + note: 'txStateManager: setting status to dropped', + timestamp: 1543958897181, + }, + { + op: 'add', + path: '/replacedBy', + value: + '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', + }, + ], ], id: 6400627574331058, loadingDefaults: false, metamaskNetworkId: '3', - status: 'dropped', + status: TRANSACTION_STATUSES.DROPPED, submittedTime: 1543958848135, time: 1543958845581, txParams: { @@ -45,38 +83,88 @@ describe('TransactionActivityLog utils', function () { to: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6', value: '0x2386f26fc10000', }, - type: 'standard', - }, { - hash: '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', + type: TRANSACTION_TYPES.STANDARD, + }, + { + hash: + '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', history: [ { - 'id': 6400627574331060, - 'time': 1543958857697, - 'status': 'unapproved', - 'metamaskNetworkId': '3', - 'loadingDefaults': false, - 'txParams': { - 'from': '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', - 'to': '0xc5ae6383e126f901dcb06131d97a88745bfa88d6', - 'value': '0x2386f26fc10000', - 'gas': '0x5208', - 'gasPrice': '0x3b9aca00', - 'nonce': '0x32', + id: 6400627574331060, + time: 1543958857697, + status: TRANSACTION_STATUSES.UNAPPROVED, + metamaskNetworkId: '3', + loadingDefaults: false, + txParams: { + from: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', + to: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6', + value: '0x2386f26fc10000', + gas: '0x5208', + gasPrice: '0x3b9aca00', + nonce: '0x32', }, - 'lastGasPrice': '0x4190ab00', - 'type': 'retry', + lastGasPrice: '0x4190ab00', + type: TRANSACTION_TYPES.RETRY, }, - [{ 'op': 'replace', 'path': '/txParams/gasPrice', 'value': '0x481f2280', 'note': 'confTx: user approved transaction', 'timestamp': 1543958859470 }], - [{ 'op': 'replace', 'path': '/status', 'value': 'approved', 'note': 'txStateManager: setting status to approved', 'timestamp': 1543958859485 }], - [{ 'op': 'replace', 'path': '/status', 'value': 'signed', 'note': 'transactions#publishTransaction', 'timestamp': 1543958859889 }], - [{ 'op': 'replace', 'path': '/status', 'value': 'submitted', 'note': 'txStateManager: setting status to submitted', 'timestamp': 1543958860061 }], [{ 'op': 'add', 'path': '/firstRetryBlockNumber', 'value': '0x45a0fd', 'note': 'transactions/pending-tx-tracker#event: tx:block-update', 'timestamp': 1543958896466 }], - [{ 'op': 'replace', 'path': '/status', 'value': 'confirmed', 'timestamp': 1543958897165 }], + [ + { + op: 'replace', + path: '/txParams/gasPrice', + value: '0x481f2280', + note: 'confTx: user approved transaction', + timestamp: 1543958859470, + }, + ], + [ + { + op: 'replace', + path: '/status', + value: TRANSACTION_STATUSES.APPROVED, + note: 'txStateManager: setting status to approved', + timestamp: 1543958859485, + }, + ], + [ + { + op: 'replace', + path: '/status', + value: TRANSACTION_STATUSES.SIGNED, + note: 'transactions#publishTransaction', + timestamp: 1543958859889, + }, + ], + [ + { + op: 'replace', + path: '/status', + value: TRANSACTION_STATUSES.SUBMITTED, + note: 'txStateManager: setting status to submitted', + timestamp: 1543958860061, + }, + ], + [ + { + op: 'add', + path: '/firstRetryBlockNumber', + value: '0x45a0fd', + note: 'transactions/pending-tx-tracker#event: tx:block-update', + timestamp: 1543958896466, + }, + ], + [ + { + op: 'replace', + path: '/status', + value: TRANSACTION_STATUSES.CONFIRMED, + timestamp: 1543958897165, + }, + ], ], id: 6400627574331060, lastGasPrice: '0x4190ab00', loadingDefaults: false, metamaskNetworkId: '3', - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, submittedTime: 1543958860054, time: 1543958857697, txParams: { @@ -90,32 +178,39 @@ describe('TransactionActivityLog utils', function () { txReceipt: { status: '0x1', }, - type: 'retry', + type: TRANSACTION_TYPES.RETRY, }, ] const expected = [ { id: 6400627574331058, - hash: '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3', + hash: + '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3', eventKey: 'transactionCreated', timestamp: 1543958845581, value: '0x2386f26fc10000', - }, { + }, + { id: 6400627574331058, - hash: '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3', + hash: + '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3', eventKey: 'transactionSubmitted', timestamp: 1543958848147, value: '0x1319718a5000', - }, { + }, + { id: 6400627574331060, - hash: '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', + hash: + '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', eventKey: 'transactionResubmitted', timestamp: 1543958860061, value: '0x171c3a061400', - }, { + }, + { id: 6400627574331060, - hash: '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', + hash: + '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', eventKey: 'transactionConfirmed', timestamp: 1543958897165, value: '0x171c3a061400', @@ -131,7 +226,7 @@ describe('TransactionActivityLog utils', function () { const transaction = { history: [], id: 1, - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, txParams: { from: '0x1', gas: '0x5208', @@ -145,14 +240,14 @@ describe('TransactionActivityLog utils', function () { assert.deepEqual(getActivities(transaction), []) }) - it('should return activities for a transaction\'s history', function () { + it("should return activities for a transaction's history", function () { const transaction = { history: [ { id: 5559712943815343, loadingDefaults: true, metamaskNetworkId: '3', - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, time: 1535507561452, txParams: { from: '0x1', @@ -196,7 +291,7 @@ describe('TransactionActivityLog utils', function () { op: 'replace', path: '/status', timestamp: 1535507564302, - value: 'approved', + value: TRANSACTION_STATUSES.APPROVED, }, ], [ @@ -223,12 +318,13 @@ describe('TransactionActivityLog utils', function () { op: 'replace', path: '/status', timestamp: 1535507564518, - value: 'signed', + value: TRANSACTION_STATUSES.SIGNED, }, { op: 'add', path: '/rawTx', - value: '0xf86b81a4843b9aca008252089450a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706872386f26fc10000802aa007b30119fc4fc5954fad727895b7e3ba80a78d197e95703cc603bcf017879151a01c50beda40ffaee541da9c05b9616247074f25f392800e0ad6c7a835d5366edf', + value: + '0xf86b81a4843b9aca008252089450a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706872386f26fc10000802aa007b30119fc4fc5954fad727895b7e3ba80a78d197e95703cc603bcf017879151a01c50beda40ffaee541da9c05b9616247074f25f392800e0ad6c7a835d5366edf', }, ], [], @@ -238,7 +334,8 @@ describe('TransactionActivityLog utils', function () { op: 'add', path: '/hash', timestamp: 1535507564658, - value: '0x7acc4987b5c0dfa8d423798a8c561138259de1f98a62e3d52e7e83c0e0dd9fb7', + value: + '0x7acc4987b5c0dfa8d423798a8c561138259de1f98a62e3d52e7e83c0e0dd9fb7', }, ], [ @@ -256,7 +353,7 @@ describe('TransactionActivityLog utils', function () { op: 'replace', path: '/status', timestamp: 1535507564665, - value: 'submitted', + value: TRANSACTION_STATUSES.SUBMITTED, }, ], [ @@ -274,12 +371,12 @@ describe('TransactionActivityLog utils', function () { op: 'replace', path: '/status', timestamp: 1535507615993, - value: 'confirmed', + value: TRANSACTION_STATUSES.CONFIRMED, }, ], ], id: 1, - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, txParams: { from: '0x1', gas: '0x5208', @@ -293,25 +390,25 @@ describe('TransactionActivityLog utils', function () { const expectedResult = [ { - 'eventKey': 'transactionCreated', - 'timestamp': 1535507561452, - 'value': '0x2386f26fc10000', - 'id': 1, - 'hash': '0xabc', + eventKey: 'transactionCreated', + timestamp: 1535507561452, + value: '0x2386f26fc10000', + id: 1, + hash: '0xabc', }, { - 'eventKey': 'transactionSubmitted', - 'timestamp': 1535507564665, - 'value': '0x2632e314a000', - 'id': 1, - 'hash': '0xabc', + eventKey: 'transactionSubmitted', + timestamp: 1535507564665, + value: '0x2632e314a000', + id: 1, + hash: '0xabc', }, { - 'eventKey': 'transactionConfirmed', - 'timestamp': 1535507615993, - 'value': '0x2632e314a000', - 'id': 1, - 'hash': '0xabc', + eventKey: 'transactionConfirmed', + timestamp: 1535507615993, + value: '0x2632e314a000', + id: 1, + hash: '0xabc', }, ] diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js index 6124325be..0ba312f8c 100644 --- a/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js +++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js @@ -34,21 +34,13 @@ export default class TransactionActivityLogIcon extends PureComponent { eventKey: PropTypes.oneOf(Object.keys(imageHash)), } - render () { + render() { const { className, eventKey } = this.props const imagePath = imageHash[eventKey] return (
    - { - imagePath && ( - - ) - } + {imagePath && }
    ) } diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log.component.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log.component.js index 9d071b432..9320a1ac2 100644 --- a/ui/app/components/app/transaction-activity-log/transaction-activity-log.component.js +++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log.component.js @@ -1,7 +1,10 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' -import { getEthConversionFromWeiHex, getValueFromWeiHex } from '../../../helpers/utils/conversions.util' +import { + getEthConversionFromWeiHex, + getValueFromWeiHex, +} from '../../../helpers/utils/conversions.util' import { formatDate } from '../../../helpers/utils/util' import { getEtherscanNetworkPrefix } from '../../../../lib/etherscan-prefix-for-network' import TransactionActivityLogIcon from './transaction-activity-log-icon' @@ -36,63 +39,70 @@ export default class TransactionActivityLog extends PureComponent { global.platform.openTab({ url: etherscanUrl }) } - renderInlineRetry (index) { + renderInlineRetry(index) { const { t } = this.context - const { inlineRetryIndex, primaryTransaction = {}, onRetry, isEarliestNonce } = this.props + const { + inlineRetryIndex, + primaryTransaction = {}, + onRetry, + isEarliestNonce, + } = this.props const { status } = primaryTransaction - return isEarliestNonce && status !== CONFIRMED_STATUS && index === inlineRetryIndex - ? ( -
    - { t('speedUpTransaction') } -
    - ) : null + return isEarliestNonce && + status !== CONFIRMED_STATUS && + index === inlineRetryIndex ? ( +
    + {t('speedUpTransaction')} +
    + ) : null } - renderInlineCancel (index) { + renderInlineCancel(index) { const { t } = this.context - const { inlineCancelIndex, primaryTransaction = {}, onCancel, isEarliestNonce } = this.props + const { + inlineCancelIndex, + primaryTransaction = {}, + onCancel, + isEarliestNonce, + } = this.props const { status } = primaryTransaction - return isEarliestNonce && status !== CONFIRMED_STATUS && index === inlineCancelIndex - ? ( -
    - { t('speedUpCancellation') } -
    - ) : null + return isEarliestNonce && + status !== CONFIRMED_STATUS && + index === inlineCancelIndex ? ( +
    + {t('speedUpCancellation')} +
    + ) : null } - renderActivity (activity, index) { + renderActivity(activity, index) { const { conversionRate, nativeCurrency } = this.props const { eventKey, value, timestamp, hash } = activity - const ethValue = index === 0 - ? `${getValueFromWeiHex({ - value, - fromCurrency: 'ETH', - toCurrency: 'ETH', - conversionRate, - numberOfDecimals: 6, - })} ${nativeCurrency}` - : getEthConversionFromWeiHex({ - value, - fromCurrency: 'ETH', - conversionRate, - numberOfDecimals: 3, - }) - const formattedTimestamp = formatDate(timestamp, 'T \'on\' M/d/y') - const activityText = this.context.t(eventKey, [ethValue, formattedTimestamp]) + const ethValue = + index === 0 + ? `${getValueFromWeiHex({ + value, + fromCurrency: 'ETH', + toCurrency: 'ETH', + conversionRate, + numberOfDecimals: 6, + })} ${nativeCurrency}` + : getEthConversionFromWeiHex({ + value, + fromCurrency: 'ETH', + conversionRate, + numberOfDecimals: 3, + }) + const formattedTimestamp = formatDate(timestamp, "T 'on' M/d/y") + const activityText = this.context.t(eventKey, [ + ethValue, + formattedTimestamp, + ]) return ( -
    +
    this.handleActivityClick(hash)} > - { activityText } + {activityText}
    - { this.renderInlineRetry(index) } - { this.renderInlineCancel(index) } + {this.renderInlineRetry(index)} + {this.renderInlineCancel(index)}
    ) } - render () { + render() { const { t } = this.context const { className, activities } = this.props @@ -123,10 +133,12 @@ export default class TransactionActivityLog extends PureComponent { return (
    - { t('activityLog') } + {t('activityLog')}
    - { activities.map((activity, index) => this.renderActivity(activity, index)) } + {activities.map((activity, index) => + this.renderActivity(activity, index), + )}
    ) diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log.container.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log.container.js index df7a3dc20..07339193d 100644 --- a/ui/app/components/app/transaction-activity-log/transaction-activity-log.container.js +++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log.container.js @@ -8,7 +8,8 @@ import { TRANSACTION_CANCEL_ATTEMPTED_EVENT, } from './transaction-activity-log.constants' -const matchesEventKey = (matchEventKey) => ({ eventKey }) => eventKey === matchEventKey +const matchesEventKey = (matchEventKey) => ({ eventKey }) => + eventKey === matchEventKey const mapStateToProps = (state) => { return { @@ -19,16 +20,19 @@ const mapStateToProps = (state) => { const mergeProps = (stateProps, dispatchProps, ownProps) => { const { - transactionGroup: { - transactions = [], - primaryTransaction, - } = {}, + transactionGroup: { transactions = [], primaryTransaction } = {}, ...restOwnProps } = ownProps const activities = combineTransactionHistories(transactions) - const inlineRetryIndex = findLastIndex(activities, matchesEventKey(TRANSACTION_RESUBMITTED_EVENT)) - const inlineCancelIndex = findLastIndex(activities, matchesEventKey(TRANSACTION_CANCEL_ATTEMPTED_EVENT)) + const inlineRetryIndex = findLastIndex( + activities, + matchesEventKey(TRANSACTION_RESUBMITTED_EVENT), + ) + const inlineCancelIndex = findLastIndex( + activities, + matchesEventKey(TRANSACTION_CANCEL_ATTEMPTED_EVENT), + ) return { ...stateProps, @@ -41,4 +45,8 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { } } -export default connect(mapStateToProps, null, mergeProps)(TransactionActivityLog) +export default connect( + mapStateToProps, + null, + mergeProps, +)(TransactionActivityLog) diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js index ed07e1ee4..8b7b7ba24 100644 --- a/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js +++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js @@ -1,9 +1,6 @@ +import { TRANSACTION_TYPES } from '../../../../../shared/constants/transaction' import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util' -import { - TRANSACTION_TYPE_CANCEL, - TRANSACTION_TYPE_RETRY, -} from '../../../../../app/scripts/controllers/transactions/enums' import { // event constants TRANSACTION_CREATED_EVENT, @@ -49,7 +46,7 @@ const statusHash = { * transactionCreated activity. * @returns {Array} */ -export function getActivities (transaction, isFirstTransaction = false) { +export function getActivities(transaction, isFirstTransaction = false) { const { id, hash, @@ -65,7 +62,10 @@ export function getActivities (transaction, isFirstTransaction = false) { const historyActivities = history.reduce((acc, base, index) => { // First history item should be transaction creation if (index === 0 && !Array.isArray(base) && base.txParams) { - const { time: timestamp, txParams: { value, gas = '0x0', gasPrice = '0x0' } = {} } = base + const { + time: timestamp, + txParams: { value, gas = '0x0', gasPrice = '0x0' } = {}, + } = base // The cached gas limit and gas price are used to display the gas fee in the activity log. We // need to cache these values because the status update history events don't provide us with // the latest gas limit and gas price. @@ -94,9 +94,16 @@ export function getActivities (transaction, isFirstTransaction = false) { if (path in eventPathsHash && op === REPLACE_OP) { switch (path) { case STATUS_PATH: { - const gasFee = cachedGasLimit === '0x0' && cachedGasPrice === '0x0' - ? getHexGasTotal({ gasLimit: paramsGasLimit, gasPrice: paramsGasPrice }) - : getHexGasTotal({ gasLimit: cachedGasLimit, gasPrice: cachedGasPrice }) + const gasFee = + cachedGasLimit === '0x0' && cachedGasPrice === '0x0' + ? getHexGasTotal({ + gasLimit: paramsGasLimit, + gasPrice: paramsGasPrice, + }) + : getHexGasTotal({ + gasLimit: cachedGasLimit, + gasPrice: cachedGasPrice, + }) if (value in statusHash) { let eventKey = statusHash[value] @@ -104,13 +111,13 @@ export function getActivities (transaction, isFirstTransaction = false) { // If the status is 'submitted', we need to determine whether the event is a // transaction retry or a cancellation attempt. if (value === SUBMITTED_STATUS) { - if (type === TRANSACTION_TYPE_RETRY) { + if (type === TRANSACTION_TYPES.RETRY) { eventKey = TRANSACTION_RESUBMITTED_EVENT - } else if (type === TRANSACTION_TYPE_CANCEL) { + } else if (type === TRANSACTION_TYPES.CANCEL) { eventKey = TRANSACTION_CANCEL_ATTEMPTED_EVENT } } else if (value === CONFIRMED_STATUS) { - if (type === TRANSACTION_TYPE_CANCEL) { + if (type === TRANSACTION_TYPES.CANCEL) { eventKey = TRANSACTION_CANCEL_SUCCESS_EVENT } } @@ -141,8 +148,10 @@ export function getActivities (transaction, isFirstTransaction = false) { cachedGasPrice = value } - if (lastEventKey === TRANSACTION_SUBMITTED_EVENT || - lastEventKey === TRANSACTION_RESUBMITTED_EVENT) { + if ( + lastEventKey === TRANSACTION_SUBMITTED_EVENT || + lastEventKey === TRANSACTION_RESUBMITTED_EVENT + ) { lastEvent.value = getHexGasTotal({ gasLimit: cachedGasLimit, gasPrice: cachedGasPrice, @@ -173,7 +182,11 @@ export function getActivities (transaction, isFirstTransaction = false) { // If txReceipt.status is '0x0', that means that an on-chain error occurred for the transaction, // so we add an error entry to the Activity Log. return status === '0x0' - ? historyActivities.concat({ id, hash, eventKey: TRANSACTION_ERRORED_EVENT }) + ? historyActivities.concat({ + id, + hash, + eventKey: TRANSACTION_ERRORED_EVENT, + }) : historyActivities } @@ -186,11 +199,15 @@ export function getActivities (transaction, isFirstTransaction = false) { * @param {Array} activities - List of sorted activities generated from the getActivities function. * @returns {Array} */ -function filterSortedActivities (activities) { +function filterSortedActivities(activities) { const filteredActivities = [] - const hasConfirmedActivity = Boolean(activities.find(({ eventKey }) => ( - eventKey === TRANSACTION_CONFIRMED_EVENT || eventKey === TRANSACTION_CANCEL_SUCCESS_EVENT - ))) + const hasConfirmedActivity = Boolean( + activities.find( + ({ eventKey }) => + eventKey === TRANSACTION_CONFIRMED_EVENT || + eventKey === TRANSACTION_CANCEL_SUCCESS_EVENT, + ), + ) let addedDroppedActivity = false activities.forEach((activity) => { @@ -212,7 +229,7 @@ function filterSortedActivities (activities) { * @param {Array} transactions - Array of txMeta transaction objects. * @returns {Array} */ -export function combineTransactionHistories (transactions = []) { +export function combineTransactionHistories(transactions = []) { if (!transactions.length) { return [] } diff --git a/ui/app/components/app/transaction-breakdown/tests/transaction-breakdown.component.test.js b/ui/app/components/app/transaction-breakdown/tests/transaction-breakdown.component.test.js index 608ba34b9..a4baa21b2 100644 --- a/ui/app/components/app/transaction-breakdown/tests/transaction-breakdown.component.test.js +++ b/ui/app/components/app/transaction-breakdown/tests/transaction-breakdown.component.test.js @@ -2,13 +2,14 @@ import assert from 'assert' import React from 'react' import { shallow } from 'enzyme' import TransactionBreakdown from '../transaction-breakdown.component' +import { TRANSACTION_STATUSES } from '../../../../../../shared/constants/transaction' describe('TransactionBreakdown Component', function () { it('should render properly', function () { const transaction = { history: [], id: 1, - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, txParams: { from: '0x1', gas: '0x5208', @@ -20,10 +21,7 @@ describe('TransactionBreakdown Component', function () { } const wrapper = shallow( - , + , { context: { t: (str1, str2) => (str2 ? str1 + str2 : str1) } }, ) diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/index.scss b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/index.scss index b36dc48c8..372a40e4b 100644 --- a/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/index.scss +++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/index.scss @@ -1,5 +1,6 @@ .transaction-breakdown-row { - font-size: 0.75rem; + @include H7; + color: $scorpion; display: flex; justify-content: space-between; diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/tests/transaction-breakdown-row.component.test.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/tests/transaction-breakdown-row.component.test.js index f25773e58..e3083e504 100644 --- a/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/tests/transaction-breakdown-row.component.test.js +++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/tests/transaction-breakdown-row.component.test.js @@ -7,33 +7,36 @@ import Button from '../../../../ui/button' describe('TransactionBreakdownRow Component', function () { it('should render text properly', function () { const wrapper = shallow( - + Test , { context: { t: (str1, str2) => (str2 ? str1 + str2 : str1) } }, ) assert.ok(wrapper.hasClass('transaction-breakdown-row')) - assert.equal(wrapper.find('.transaction-breakdown-row__title').text(), 'test') - assert.equal(wrapper.find('.transaction-breakdown-row__value').text(), 'Test') + assert.equal( + wrapper.find('.transaction-breakdown-row__title').text(), + 'test', + ) + assert.equal( + wrapper.find('.transaction-breakdown-row__value').text(), + 'Test', + ) }) it('should render components properly', function () { const wrapper = shallow( - - + + , { context: { t: (str1, str2) => (str2 ? str1 + str2 : str1) } }, ) assert.ok(wrapper.hasClass('transaction-breakdown-row')) - assert.equal(wrapper.find('.transaction-breakdown-row__title').text(), 'test') + assert.equal( + wrapper.find('.transaction-breakdown-row__title').text(), + 'test', + ) assert.ok(wrapper.find('.transaction-breakdown-row__value').find(Button)) }) }) diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/transaction-breakdown-row.component.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/transaction-breakdown-row.component.js index c11ff8efa..ce652f02d 100644 --- a/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/transaction-breakdown-row.component.js +++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/transaction-breakdown-row.component.js @@ -9,17 +9,13 @@ export default class TransactionBreakdownRow extends PureComponent { className: PropTypes.string, } - render () { + render() { const { title, children, className } = this.props return (
    -
    - { title } -
    -
    - { children } -
    +
    {title}
    +
    {children}
    ) } diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js index 68ac17546..3e4259f4b 100644 --- a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js +++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js @@ -29,75 +29,75 @@ export default class TransactionBreakdown extends PureComponent { showFiat: true, } - render () { + render() { const { t } = this.context - const { gas, gasPrice, primaryCurrency, className, nonce, nativeCurrency, showFiat, totalInHex, gasUsed, isTokenApprove } = this.props + const { + gas, + gasPrice, + primaryCurrency, + className, + nonce, + nativeCurrency, + showFiat, + totalInHex, + gasUsed, + isTokenApprove, + } = this.props return (
    -
    - { t('transaction') } -
    +
    {t('transaction')}
    - {typeof nonce === 'undefined' - ? null - : ( - - ) - } + {typeof nonce === 'undefined' ? null : ( + + )} - {primaryCurrency} + + {primaryCurrency} + - {typeof gas === 'undefined' - ? '?' - : ( - - ) - } + {typeof gas === 'undefined' ? ( + '?' + ) : ( + + )} - { - typeof gasUsed === 'string' && ( - - - - ) - } + {typeof gasUsed === 'string' && ( + + + + )} - {typeof gasPrice === 'undefined' - ? '?' - : ( - - ) - } + {typeof gasPrice === 'undefined' ? ( + '?' + ) : ( + + )}
    @@ -106,15 +106,13 @@ export default class TransactionBreakdown extends PureComponent { type={PRIMARY} value={totalInHex} /> - { - showFiat && ( - - ) - } + {showFiat && ( + + )}
    diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js index 17c00f38f..d4bf56100 100644 --- a/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js +++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js @@ -1,25 +1,34 @@ import { connect } from 'react-redux' -import { getIsMainnet, getNativeCurrency, getPreferences } from '../../../selectors' +import { + getIsMainnet, + getNativeCurrency, + getPreferences, +} from '../../../selectors' import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util' import { sumHexes } from '../../../helpers/utils/transactions.util' -import { TOKEN_METHOD_APPROVE } from '../../../helpers/constants/transactions' +import { TRANSACTION_CATEGORIES } from '../../../../../shared/constants/transaction' import TransactionBreakdown from './transaction-breakdown.component' const mapStateToProps = (state, ownProps) => { const { transaction, transactionCategory } = ownProps - const { txParams: { gas, gasPrice, value } = {}, txReceipt: { gasUsed } = {} } = transaction + const { + txParams: { gas, gasPrice, value } = {}, + txReceipt: { gasUsed } = {}, + } = transaction const { showFiatInTestnets } = getPreferences(state) const isMainnet = getIsMainnet(state) - const isTokenApprove = transactionCategory === TOKEN_METHOD_APPROVE + const isTokenApprove = + transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas - const hexGasTotal = (gasLimit && gasPrice && getHexGasTotal({ gasLimit, gasPrice })) || '0x0' + const hexGasTotal = + (gasLimit && gasPrice && getHexGasTotal({ gasLimit, gasPrice })) || '0x0' const totalInHex = sumHexes(hexGasTotal, value) return { nativeCurrency: getNativeCurrency(state), - showFiat: (isMainnet || Boolean(showFiatInTestnets)), + showFiat: isMainnet || Boolean(showFiatInTestnets), totalInHex, gas, gasPrice, diff --git a/ui/app/components/app/transaction-icon/transaction-icon.js b/ui/app/components/app/transaction-icon/transaction-icon.js index 688215e36..fe6aa0e5a 100644 --- a/ui/app/components/app/transaction-icon/transaction-icon.js +++ b/ui/app/components/app/transaction-icon/transaction-icon.js @@ -7,28 +7,18 @@ import Send from '../../ui/icon/send-icon.component' import Sign from '../../ui/icon/sign-icon.component' import Swap from '../../ui/icon/swap-icon-for-list.component' import { - TRANSACTION_CATEGORY_APPROVAL, - TRANSACTION_CATEGORY_SIGNATURE_REQUEST, - TRANSACTION_CATEGORY_INTERACTION, - TRANSACTION_CATEGORY_SEND, - TRANSACTION_CATEGORY_RECEIVE, - TRANSACTION_CATEGORY_SWAP, - UNAPPROVED_STATUS, - FAILED_STATUS, - REJECTED_STATUS, - CANCELLED_STATUS, - DROPPED_STATUS, - SUBMITTED_STATUS, - APPROVED_STATUS, -} from '../../../helpers/constants/transactions' + TRANSACTION_GROUP_CATEGORIES, + TRANSACTION_GROUP_STATUSES, + TRANSACTION_STATUSES, +} from '../../../../../shared/constants/transaction' const ICON_MAP = { - [TRANSACTION_CATEGORY_APPROVAL]: Approve, - [TRANSACTION_CATEGORY_INTERACTION]: Interaction, - [TRANSACTION_CATEGORY_SEND]: Send, - [TRANSACTION_CATEGORY_SIGNATURE_REQUEST]: Sign, - [TRANSACTION_CATEGORY_RECEIVE]: Receive, - [TRANSACTION_CATEGORY_SWAP]: Swap, + [TRANSACTION_GROUP_CATEGORIES.APPROVAL]: Approve, + [TRANSACTION_GROUP_CATEGORIES.INTERACTION]: Interaction, + [TRANSACTION_GROUP_CATEGORIES.SEND]: Send, + [TRANSACTION_GROUP_CATEGORIES.SIGNATURE_REQUEST]: Sign, + [TRANSACTION_GROUP_CATEGORIES.RECEIVE]: Receive, + [TRANSACTION_GROUP_CATEGORIES.SWAP]: Swap, } const FAIL_COLOR = '#D73A49' @@ -36,17 +26,16 @@ const PENDING_COLOR = '#6A737D' const OK_COLOR = '#2F80ED' const COLOR_MAP = { - [SUBMITTED_STATUS]: PENDING_COLOR, - [UNAPPROVED_STATUS]: PENDING_COLOR, - [APPROVED_STATUS]: PENDING_COLOR, - [FAILED_STATUS]: FAIL_COLOR, - [REJECTED_STATUS]: FAIL_COLOR, - [CANCELLED_STATUS]: FAIL_COLOR, - [DROPPED_STATUS]: FAIL_COLOR, + [TRANSACTION_GROUP_STATUSES.PENDING]: PENDING_COLOR, + [TRANSACTION_STATUSES.UNAPPROVED]: PENDING_COLOR, + [TRANSACTION_STATUSES.APPROVED]: PENDING_COLOR, + [TRANSACTION_STATUSES.FAILED]: FAIL_COLOR, + [TRANSACTION_STATUSES.REJECTED]: FAIL_COLOR, + [TRANSACTION_GROUP_STATUSES.CANCELLED]: FAIL_COLOR, + [TRANSACTION_STATUSES.DROPPED]: FAIL_COLOR, } -export default function TransactionIcon ({ status, category }) { - +export default function TransactionIcon({ status, category }) { const color = COLOR_MAP[status] || OK_COLOR const Icon = ICON_MAP[category] diff --git a/ui/app/components/app/transaction-list-item-details/index.scss b/ui/app/components/app/transaction-list-item-details/index.scss index e6fc0b995..4ad7cd094 100644 --- a/ui/app/components/app/transaction-list-item-details/index.scss +++ b/ui/app/components/app/transaction-list-item-details/index.scss @@ -17,7 +17,7 @@ } & &__header-button { - font-size: 0.625rem; + @include H8; &-tooltip-container { display: flex !important; diff --git a/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js b/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js index d647362f5..9d1e8c18b 100644 --- a/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js +++ b/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js @@ -6,13 +6,14 @@ import Button from '../../../ui/button' import SenderToRecipient from '../../../ui/sender-to-recipient' import TransactionBreakdown from '../../transaction-breakdown' import TransactionActivityLog from '../../transaction-activity-log' +import { TRANSACTION_STATUSES } from '../../../../../../shared/constants/transaction' describe('TransactionListItemDetails Component', function () { it('should render properly', function () { const transaction = { history: [], id: 1, - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, txParams: { from: '0x1', gas: '0x5208', @@ -53,7 +54,7 @@ describe('TransactionListItemDetails Component', function () { const transaction = { history: [], id: 1, - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, txParams: { from: '0x1', gas: '0x5208', @@ -96,7 +97,7 @@ describe('TransactionListItemDetails Component', function () { const transaction = { history: [], id: 1, - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, txParams: { from: '0x1', gas: '0x5208', @@ -137,7 +138,7 @@ describe('TransactionListItemDetails Component', function () { const transaction = { history: [], id: 1, - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, hash: '0xaa', txParams: { from: '0x1', diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js index 7ee511524..debba6573 100644 --- a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js @@ -1,9 +1,7 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import copyToClipboard from 'copy-to-clipboard' -import { - getBlockExplorerUrlForTx, -} from '../../../helpers/utils/transactions.util' +import { getBlockExplorerUrlForTx } from '../../../helpers/utils/transactions.util' import SenderToRecipient from '../../ui/sender-to-recipient' import { FLAT_VARIANT } from '../../ui/sender-to-recipient/sender-to-recipient.constants' import TransactionActivityLog from '../transaction-activity-log' @@ -49,7 +47,10 @@ export default class TransactionListItemDetails extends PureComponent { } handleEtherscanClick = () => { - const { transactionGroup: { primaryTransaction }, rpcPrefs } = this.props + const { + transactionGroup: { primaryTransaction }, + rpcPrefs, + } = this.props const { hash, metamaskNetworkId } = primaryTransaction this.context.metricsEvent({ @@ -60,7 +61,9 @@ export default class TransactionListItemDetails extends PureComponent { }, }) - global.platform.openTab({ url: getBlockExplorerUrlForTx(metamaskNetworkId, hash, rpcPrefs) }) + global.platform.openTab({ + url: getBlockExplorerUrlForTx(metamaskNetworkId, hash, rpcPrefs), + }) } handleCancel = (event) => { @@ -94,7 +97,7 @@ export default class TransactionListItemDetails extends PureComponent { }) } - componentDidMount () { + componentDidMount() { const { recipientAddress, tryReverseResolveAddress } = this.props if (recipientAddress) { @@ -102,44 +105,39 @@ export default class TransactionListItemDetails extends PureComponent { } } - renderCancel () { + renderCancel() { const { t } = this.context - const { - showCancel, - cancelDisabled, - } = this.props + const { showCancel, cancelDisabled } = this.props if (!showCancel) { return null } - return cancelDisabled - ? ( - -
    - -
    -
    - ) - : ( - - ) + return cancelDisabled ? ( + +
    + +
    +
    + ) : ( + + ) } - render () { + render() { const { t } = this.context const { justCopied } = this.state const { @@ -157,31 +155,34 @@ export default class TransactionListItemDetails extends PureComponent { onClose, recipientNickname, } = this.props - const { primaryTransaction: transaction, initialTransaction: { transactionCategory } } = transactionGroup + const { + primaryTransaction: transaction, + initialTransaction: { transactionCategory }, + } = transactionGroup const { hash } = transaction return (
    -
    { t('details') }
    +
    {t('details')}
    - { - showSpeedUp && ( - - ) - } - { this.renderCancel() } + {showSpeedUp && ( + + )} + {this.renderCancel()} - { - showRetry && ( - - - - ) - } + {showRetry && ( + + + + )}
    diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js index 6bea44d2a..55fcf8582 100644 --- a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js +++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js @@ -1,14 +1,15 @@ import { connect } from 'react-redux' import { checksumAddress } from '../../../helpers/utils/util' import { tryReverseResolveAddress } from '../../../store/actions' -import { getAddressBook, getRpcPrefsForCurrentProvider } from '../../../selectors' +import { + getAddressBook, + getRpcPrefsForCurrentProvider, +} from '../../../selectors' import TransactionListItemDetails from './transaction-list-item-details.component' const mapStateToProps = (state, ownProps) => { const { metamask } = state - const { - ensResolutionsByAddress, - } = metamask + const { ensResolutionsByAddress } = metamask const { recipientAddress, senderAddress } = ownProps let recipientEns if (recipientAddress) { @@ -41,4 +42,7 @@ const mapDispatchToProps = (dispatch) => { } } -export default connect(mapStateToProps, mapDispatchToProps)(TransactionListItemDetails) +export default connect( + mapStateToProps, + mapDispatchToProps, +)(TransactionListItemDetails) diff --git a/ui/app/components/app/transaction-list-item/index.scss b/ui/app/components/app/transaction-list-item/index.scss index cfdd441f8..41e4d64a0 100644 --- a/ui/app/components/app/transaction-list-item/index.scss +++ b/ui/app/components/app/transaction-list-item/index.scss @@ -6,7 +6,8 @@ } &__secondary-currency { - font-size: 12px; + @include H7; + margin-top: 4px; color: $Grey-500; } @@ -24,11 +25,11 @@ display: flex; .button { - font-size: 0.625rem; + @include H8; + padding: 8px; width: 75px; white-space: nowrap; - line-height: 1rem; } & > .button:first-child { diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js index 9953df9d4..9c472c11f 100644 --- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js @@ -12,31 +12,37 @@ import Button from '../../ui/button' import Tooltip from '../../ui/tooltip' import TransactionListItemDetails from '../transaction-list-item-details' import { CONFIRM_TRANSACTION_ROUTE } from '../../../helpers/constants/routes' -import { - TRANSACTION_CATEGORY_SIGNATURE_REQUEST, - UNAPPROVED_STATUS, - TRANSACTION_CATEGORY_APPROVAL, - FAILED_STATUS, - DROPPED_STATUS, - REJECTED_STATUS, - TRANSACTION_CATEGORY_SWAP, -} from '../../../helpers/constants/transactions' import { useShouldShowSpeedUp } from '../../../hooks/useShouldShowSpeedUp' import TransactionStatus from '../transaction-status/transaction-status.component' import TransactionIcon from '../transaction-icon' import { useTransactionTimeRemaining } from '../../../hooks/useTransactionTimeRemaining' import IconWithLabel from '../../ui/icon-with-label' +import { + TRANSACTION_GROUP_CATEGORIES, + TRANSACTION_STATUSES, +} from '../../../../../shared/constants/transaction' -export default function TransactionListItem ({ transactionGroup, isEarliestNonce = false }) { +export default function TransactionListItem({ + transactionGroup, + isEarliestNonce = false, +}) { const t = useI18nContext() const history = useHistory() const { hasCancelled } = transactionGroup const [showDetails, setShowDetails] = useState(false) - const { initialTransaction: { id }, primaryTransaction: { err, gasPrice, status, submittedTime } } = transactionGroup - const [cancelEnabled, cancelTransaction] = useCancelTransaction(transactionGroup) + const { + initialTransaction: { id }, + primaryTransaction: { err, gasPrice, status, submittedTime }, + } = transactionGroup + const [cancelEnabled, cancelTransaction] = useCancelTransaction( + transactionGroup, + ) const retryTransaction = useRetryTransaction(transactionGroup) - const shouldShowSpeedUp = useShouldShowSpeedUp(transactionGroup, isEarliestNonce) + const shouldShowSpeedUp = useShouldShowSpeedUp( + transactionGroup, + isEarliestNonce, + ) const { title, @@ -53,15 +59,27 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce isSubmitted, } = useTransactionDisplayData(transactionGroup) - const timeRemaining = useTransactionTimeRemaining(isSubmitted, isEarliestNonce, submittedTime, gasPrice) + const timeRemaining = useTransactionTimeRemaining( + isSubmitted, + isEarliestNonce, + submittedTime, + gasPrice, + ) - const isSignatureReq = category === TRANSACTION_CATEGORY_SIGNATURE_REQUEST - const isApproval = category === TRANSACTION_CATEGORY_APPROVAL - const isUnapproved = displayedStatusKey === UNAPPROVED_STATUS - const isSwap = category === TRANSACTION_CATEGORY_SWAP + const isSignatureReq = + category === TRANSACTION_GROUP_CATEGORIES.SIGNATURE_REQUEST + const isApproval = category === TRANSACTION_GROUP_CATEGORIES.APPROVAL + const isUnapproved = status === TRANSACTION_STATUSES.UNAPPROVED + const isSwap = category === TRANSACTION_GROUP_CATEGORIES.SWAP const className = classnames('transaction-list-item', { - 'transaction-list-item--unconfirmed': isPending || [FAILED_STATUS, DROPPED_STATUS, REJECTED_STATUS].includes(displayedStatusKey), + 'transaction-list-item--unconfirmed': + isPending || + [ + TRANSACTION_STATUSES.FAILED, + TRANSACTION_STATUSES.DROPPED, + TRANSACTION_STATUSES.REJECTED, + ].includes(displayedStatusKey), }) const toggleShowDetails = useCallback(() => { @@ -80,24 +98,28 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce className="transaction-list-item__header-button" disabled={!cancelEnabled} > - { t('cancel') } + {t('cancel')} ) if (hasCancelled || !isPending || isUnapproved) { return null } - return cancelEnabled - ? btn - : ( - -
    - {btn} -
    -
    - ) - - }, [isPending, t, isUnapproved, cancelEnabled, cancelTransaction, hasCancelled]) + return cancelEnabled ? ( + btn + ) : ( + +
    {btn}
    +
    + ) + }, [ + isPending, + t, + isUnapproved, + cancelEnabled, + cancelTransaction, + hasCancelled, + ]) const speedUpButton = useMemo(() => { if (!shouldShowSpeedUp || !isPending || isUnapproved) { @@ -110,7 +132,7 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce onClick={retryTransaction} className="transaction-list-item-details__header-button" > - { t('speedUp') } + {t('speedUp')} ) }, [shouldShowSpeedUp, isUnapproved, t, isPending, retryTransaction]) @@ -121,14 +143,20 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce onClick={toggleShowDetails} className={className} title={title} - titleIcon={!isUnapproved && isPending && isEarliestNonce && ( - } - label={timeRemaining} - /> - )} - icon={} - subtitle={( + titleIcon={ + !isUnapproved && + isPending && + isEarliestNonce && ( + } + label={timeRemaining} + /> + ) + } + icon={ + + } + subtitle={

    - + {subtitle}

    - )} - rightContent={!isSignatureReq && !isApproval && ( - <> -

    {primaryCurrency}

    -

    {secondaryCurrency}

    - - )} + } + rightContent={ + !isSignatureReq && + !isApproval && ( + <> +

    + {primaryCurrency} +

    +

    + {secondaryCurrency} +

    + + ) + } >
    {speedUpButton} @@ -163,7 +208,7 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce senderAddress={senderAddress} recipientAddress={recipientAddress} onRetry={retryTransaction} - showRetry={status === FAILED_STATUS && !isSwap} + showRetry={status === TRANSACTION_STATUSES.FAILED && !isSwap} showSpeedUp={shouldShowSpeedUp} isEarliestNonce={isEarliestNonce} onCancel={cancelTransaction} diff --git a/ui/app/components/app/transaction-list/index.scss b/ui/app/components/app/transaction-list/index.scss index 58feee644..380f91a24 100644 --- a/ui/app/components/app/transaction-list/index.scss +++ b/ui/app/components/app/transaction-list/index.scss @@ -10,9 +10,9 @@ } &__header { + @include H6; + flex: 0 0 auto; - font-size: 14px; - line-height: 20px; color: $Grey-400; border-bottom: 1px solid $Grey-100; padding: 8px 0 8px 20px; diff --git a/ui/app/components/app/transaction-list/transaction-list.component.js b/ui/app/components/app/transaction-list/transaction-list.component.js index 9af3afe31..cfb70244f 100644 --- a/ui/app/components/app/transaction-list/transaction-list.component.js +++ b/ui/app/components/app/transaction-list/transaction-list.component.js @@ -5,23 +5,23 @@ import { nonceSortedCompletedTransactionsSelector, nonceSortedPendingTransactionsSelector, } from '../../../selectors/transactions' -import { - getFeatureFlags, -} from '../../../selectors/selectors' +import { getFeatureFlags } from '../../../selectors/selectors' import * as actions from '../../../ducks/gas/gas.duck' import { useI18nContext } from '../../../hooks/useI18nContext' import TransactionListItem from '../transaction-list-item' import Button from '../../ui/button' -import { TOKEN_CATEGORY_HASH, TRANSACTION_CATEGORY_SWAP } from '../../../helpers/constants/transactions' +import { TOKEN_CATEGORY_HASH } from '../../../helpers/constants/transactions' import { SWAPS_CONTRACT_ADDRESS } from '../../../helpers/constants/swaps' +import { TRANSACTION_CATEGORIES } from '../../../../../shared/constants/transaction' const PAGE_INCREMENT = 10 const getTransactionGroupRecipientAddressFilter = (recipientAddress) => { return ({ initialTransaction: { txParams } }) => { - return txParams?.to === recipientAddress || ( - txParams?.to === SWAPS_CONTRACT_ADDRESS && - txParams.data.match(recipientAddress.slice(2)) + return ( + txParams?.to === recipientAddress || + (txParams?.to === SWAPS_CONTRACT_ADDRESS && + txParams.data.match(recipientAddress.slice(2))) ) } } @@ -35,112 +35,166 @@ const tokenTransactionFilter = ({ }) => { if (TOKEN_CATEGORY_HASH[transactionCategory]) { return false - } else if (transactionCategory === TRANSACTION_CATEGORY_SWAP) { + } else if (transactionCategory === TRANSACTION_CATEGORIES.SWAP) { return destinationTokenSymbol === 'ETH' || sourceTokenSymbol === 'ETH' } return true } -const getFilteredTransactionGroups = (transactionGroups, hideTokenTransactions, tokenAddress) => { +const getFilteredTransactionGroups = ( + transactionGroups, + hideTokenTransactions, + tokenAddress, +) => { if (hideTokenTransactions) { return transactionGroups.filter(tokenTransactionFilter) } else if (tokenAddress) { - return transactionGroups.filter(getTransactionGroupRecipientAddressFilter(tokenAddress)) + return transactionGroups.filter( + getTransactionGroupRecipientAddressFilter(tokenAddress), + ) } return transactionGroups } -export default function TransactionList ({ hideTokenTransactions, tokenAddress }) { +export default function TransactionList({ + hideTokenTransactions, + tokenAddress, +}) { const [limit, setLimit] = useState(PAGE_INCREMENT) const t = useI18nContext() const dispatch = useDispatch() - const unfilteredPendingTransactions = useSelector(nonceSortedPendingTransactionsSelector) - const unfilteredCompletedTransactions = useSelector(nonceSortedCompletedTransactionsSelector) - const { transactionTime: transactionTimeFeatureActive } = useSelector(getFeatureFlags) + const unfilteredPendingTransactions = useSelector( + nonceSortedPendingTransactionsSelector, + ) + const unfilteredCompletedTransactions = useSelector( + nonceSortedCompletedTransactionsSelector, + ) + const { transactionTime: transactionTimeFeatureActive } = useSelector( + getFeatureFlags, + ) const pendingTransactions = useMemo( - () => getFilteredTransactionGroups(unfilteredPendingTransactions, hideTokenTransactions, tokenAddress), + () => + getFilteredTransactionGroups( + unfilteredPendingTransactions, + hideTokenTransactions, + tokenAddress, + ), [hideTokenTransactions, tokenAddress, unfilteredPendingTransactions], ) const completedTransactions = useMemo( - () => getFilteredTransactionGroups(unfilteredCompletedTransactions, hideTokenTransactions, tokenAddress), + () => + getFilteredTransactionGroups( + unfilteredCompletedTransactions, + hideTokenTransactions, + tokenAddress, + ), [hideTokenTransactions, tokenAddress, unfilteredCompletedTransactions], ) - const { fetchGasEstimates, fetchBasicGasAndTimeEstimates } = useMemo(() => ({ - fetchGasEstimates: (blockTime) => dispatch(actions.fetchGasEstimates(blockTime)), - fetchBasicGasAndTimeEstimates: () => dispatch(actions.fetchBasicGasAndTimeEstimates()), - }), [dispatch]) + const { fetchGasEstimates, fetchBasicGasAndTimeEstimates } = useMemo( + () => ({ + fetchGasEstimates: (blockTime) => + dispatch(actions.fetchGasEstimates(blockTime)), + fetchBasicGasAndTimeEstimates: () => + dispatch(actions.fetchBasicGasAndTimeEstimates()), + }), + [dispatch], + ) // keep track of previous values from state. // loaded is used here to determine if our effect has ran at least once. - const prevState = useRef({ loaded: false, pendingTransactions, transactionTimeFeatureActive }) + const prevState = useRef({ + loaded: false, + pendingTransactions, + transactionTimeFeatureActive, + }) useEffect(() => { const { loaded } = prevState.current - const pendingTransactionAdded = pendingTransactions.length > 0 && prevState.current.pendingTransactions.length === 0 - const transactionTimeFeatureWasActivated = !prevState.current.transactionTimeFeatureActive && transactionTimeFeatureActive - if (transactionTimeFeatureActive && pendingTransactions.length > 0 && (loaded === false || transactionTimeFeatureWasActivated || pendingTransactionAdded)) { - fetchBasicGasAndTimeEstimates() - .then(({ blockTime }) => fetchGasEstimates(blockTime)) + const pendingTransactionAdded = + pendingTransactions.length > 0 && + prevState.current.pendingTransactions.length === 0 + const transactionTimeFeatureWasActivated = + !prevState.current.transactionTimeFeatureActive && + transactionTimeFeatureActive + if ( + transactionTimeFeatureActive && + pendingTransactions.length > 0 && + (loaded === false || + transactionTimeFeatureWasActivated || + pendingTransactionAdded) + ) { + fetchBasicGasAndTimeEstimates().then(({ blockTime }) => + fetchGasEstimates(blockTime), + ) } - prevState.current = { loaded: true, pendingTransactions, transactionTimeFeatureActive } - }, [fetchGasEstimates, fetchBasicGasAndTimeEstimates, transactionTimeFeatureActive, pendingTransactions]) + prevState.current = { + loaded: true, + pendingTransactions, + transactionTimeFeatureActive, + } + }, [ + fetchGasEstimates, + fetchBasicGasAndTimeEstimates, + transactionTimeFeatureActive, + pendingTransactions, + ]) - const viewMore = useCallback(() => setLimit((prev) => prev + PAGE_INCREMENT), []) + const viewMore = useCallback( + () => setLimit((prev) => prev + PAGE_INCREMENT), + [], + ) const pendingLength = pendingTransactions.length return (
    - { - pendingLength > 0 && ( -
    -
    - { `${t('queue')} (${pendingTransactions.length})` } -
    - { - pendingTransactions.map((transactionGroup, index) => ( - - )) - } + {pendingLength > 0 && ( +
    +
    + {`${t('queue')} (${pendingTransactions.length})`}
    - ) - } + {pendingTransactions.map((transactionGroup, index) => ( + + ))} +
    + )}
    - { - pendingLength > 0 - ? ( -
    - { t('history') } -
    - ) - : null - } - { - completedTransactions.length > 0 - ? completedTransactions.slice(0, limit).map((transactionGroup, index) => ( + {pendingLength > 0 ? ( +
    {t('history')}
    + ) : null} + {completedTransactions.length > 0 ? ( + completedTransactions + .slice(0, limit) + .map((transactionGroup, index) => ( )) - : ( -
    -
    - { t('noTransactions') } -
    -
    - ) - } + ) : ( +
    +
    + {t('noTransactions')} +
    +
    + )} {completedTransactions.length > limit && ( - + )}
    diff --git a/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js b/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js index 51d02b451..d468d8d5f 100644 --- a/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js +++ b/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js @@ -13,10 +13,7 @@ describe('TransactionStatus Component', function () { it('should render CONFIRMED properly', function () { const wrapper = mount( - , + , ) assert.ok(wrapper) @@ -39,11 +36,7 @@ describe('TransactionStatus Component', function () { it('should render PENDING properly', function () { const wrapper = mount( - , + , ) assert.ok(wrapper) @@ -51,26 +44,24 @@ describe('TransactionStatus Component', function () { }) it('should render QUEUED properly', function () { - const wrapper = mount( - , - ) + const wrapper = mount() assert.ok(wrapper) - assert.ok(wrapper.find('.transaction-status--queued').length, 'queued className not found') + assert.ok( + wrapper.find('.transaction-status--queued').length, + 'queued className not found', + ) assert.equal(wrapper.text(), 'QUEUED') }) it('should render UNAPPROVED properly', function () { - const wrapper = mount( - , - ) + const wrapper = mount() assert.ok(wrapper) - assert.ok(wrapper.find('.transaction-status--unapproved').length, 'unapproved className not found') + assert.ok( + wrapper.find('.transaction-status--unapproved').length, + 'unapproved className not found', + ) assert.equal(wrapper.text(), 'UNAPPROVED') }) diff --git a/ui/app/components/app/transaction-status/transaction-status.component.js b/ui/app/components/app/transaction-status/transaction-status.component.js index 692745db3..5c5dd3515 100644 --- a/ui/app/components/app/transaction-status/transaction-status.component.js +++ b/ui/app/components/app/transaction-status/transaction-status.component.js @@ -3,21 +3,13 @@ import PropTypes from 'prop-types' import classnames from 'classnames' import Tooltip from '../../ui/tooltip' -import { - UNAPPROVED_STATUS, - REJECTED_STATUS, - SUBMITTED_STATUS, - CONFIRMED_STATUS, - FAILED_STATUS, - DROPPED_STATUS, - CANCELLED_STATUS, - APPROVED_STATUS, - SIGNED_STATUS, -} from '../../../helpers/constants/transactions' import { useI18nContext } from '../../../hooks/useI18nContext' +import { + TRANSACTION_GROUP_STATUSES, + TRANSACTION_STATUSES, +} from '../../../../../shared/constants/transaction' const QUEUED_PSEUDO_STATUS = 'queued' -const PENDING_PSEUDO_STATUS = 'pending' /** * A note about status logic for this component: @@ -30,38 +22,51 @@ const PENDING_PSEUDO_STATUS = 'pending' * status label will be the date the transaction was finalized. */ const pendingStatusHash = { - [SUBMITTED_STATUS]: PENDING_PSEUDO_STATUS, - [APPROVED_STATUS]: PENDING_PSEUDO_STATUS, - [SIGNED_STATUS]: PENDING_PSEUDO_STATUS, + [TRANSACTION_STATUSES.SUBMITTED]: TRANSACTION_GROUP_STATUSES.PENDING, + [TRANSACTION_STATUSES.APPROVED]: TRANSACTION_GROUP_STATUSES.PENDING, + [TRANSACTION_STATUSES.SIGNED]: TRANSACTION_GROUP_STATUSES.PENDING, } const statusToClassNameHash = { - [UNAPPROVED_STATUS]: 'transaction-status--unapproved', - [REJECTED_STATUS]: 'transaction-status--rejected', - [FAILED_STATUS]: 'transaction-status--failed', - [DROPPED_STATUS]: 'transaction-status--dropped', - [CANCELLED_STATUS]: 'transaction-status--cancelled', + [TRANSACTION_STATUSES.UNAPPROVED]: 'transaction-status--unapproved', + [TRANSACTION_STATUSES.REJECTED]: 'transaction-status--rejected', + [TRANSACTION_STATUSES.FAILED]: 'transaction-status--failed', + [TRANSACTION_STATUSES.DROPPED]: 'transaction-status--dropped', + [TRANSACTION_GROUP_STATUSES.CANCELLED]: 'transaction-status--cancelled', [QUEUED_PSEUDO_STATUS]: 'transaction-status--queued', - [PENDING_PSEUDO_STATUS]: 'transaction-status--pending', + [TRANSACTION_GROUP_STATUSES.PENDING]: 'transaction-status--pending', } -export default function TransactionStatus ({ status, date, error, isEarliestNonce, className }) { +export default function TransactionStatus({ + status, + date, + error, + isEarliestNonce, + className, +}) { const t = useI18nContext() const tooltipText = error?.rpc?.message || error?.message let statusKey = status if (pendingStatusHash[status]) { - statusKey = isEarliestNonce ? PENDING_PSEUDO_STATUS : QUEUED_PSEUDO_STATUS + statusKey = isEarliestNonce + ? TRANSACTION_GROUP_STATUSES.PENDING + : QUEUED_PSEUDO_STATUS } - const statusText = statusKey === CONFIRMED_STATUS ? date : t(statusKey) + const statusText = + statusKey === TRANSACTION_STATUSES.CONFIRMED ? date : t(statusKey) return ( - { statusText } + {statusText} ) } diff --git a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js index d9f271e10..41b8ac862 100644 --- a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js +++ b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js @@ -11,12 +11,12 @@ describe('UserPreferencedCurrencyDisplay Component', function () { describe('rendering', function () { beforeEach(function () { sinon.stub(currencyHook, 'useCurrencyDisplay').returns(['1', {}]) - sinon.stub(currencyPrefHook, 'useUserPreferencedCurrency').returns({ currency: 'ETH', decimals: 6 }) + sinon + .stub(currencyPrefHook, 'useUserPreferencedCurrency') + .returns({ currency: 'ETH', decimals: 6 }) }) it('should render properly', function () { - const wrapper = shallow( - , - ) + const wrapper = shallow() assert.ok(wrapper) assert.equal(wrapper.find(CurrencyDisplay).length, 1) @@ -24,11 +24,7 @@ describe('UserPreferencedCurrencyDisplay Component', function () { it('should pass all props to the CurrencyDisplay child component', function () { const wrapper = shallow( - , + , ) assert.ok(wrapper) diff --git a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js index fd7072239..c858b66b6 100644 --- a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js +++ b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js @@ -4,7 +4,7 @@ import { PRIMARY, SECONDARY, ETH } from '../../../helpers/constants/common' import CurrencyDisplay from '../../ui/currency-display' import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency' -export default function UserPreferencedCurrencyDisplay ({ +export default function UserPreferencedCurrencyDisplay({ 'data-testid': dataTestId, ethLogoHeight = 12, ethNumberOfDecimals, @@ -14,14 +14,16 @@ export default function UserPreferencedCurrencyDisplay ({ type, ...restProps }) { - const { currency, numberOfDecimals } = useUserPreferencedCurrency(type, { ethNumberOfDecimals, fiatNumberOfDecimals, numberOfDecimals: propsNumberOfDecimals }) + const { currency, numberOfDecimals } = useUserPreferencedCurrency(type, { + ethNumberOfDecimals, + fiatNumberOfDecimals, + numberOfDecimals: propsNumberOfDecimals, + }) const prefixComponent = useMemo(() => { - return currency === ETH && showEthLogo && ( - + return ( + currency === ETH && + showEthLogo && ) }, [currency, showEthLogo, ethLogoHeight]) @@ -48,6 +50,12 @@ UserPreferencedCurrencyDisplay.propTypes = { showEthLogo: PropTypes.bool, ethLogoHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), type: PropTypes.oneOf([PRIMARY, SECONDARY]), - ethNumberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - fiatNumberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + ethNumberOfDecimals: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), + fiatNumberOfDecimals: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), } diff --git a/ui/app/components/app/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js b/ui/app/components/app/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js index 3241008e4..63b9d42a5 100644 --- a/ui/app/components/app/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js +++ b/ui/app/components/app/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js @@ -7,9 +7,7 @@ import CurrencyInput from '../../../ui/currency-input' describe('UserPreferencedCurrencyInput Component', function () { describe('rendering', function () { it('should render properly', function () { - const wrapper = shallow( - , - ) + const wrapper = shallow() assert.ok(wrapper) assert.equal(wrapper.find(CurrencyInput).length, 1) @@ -17,9 +15,7 @@ describe('UserPreferencedCurrencyInput Component', function () { it('should render useFiat for CurrencyInput based on preferences.useNativeCurrencyAsPrimaryCurrency', function () { const wrapper = shallow( - , + , ) assert.ok(wrapper) diff --git a/ui/app/components/app/user-preferenced-currency-input/user-preferenced-currency-input.component.js b/ui/app/components/app/user-preferenced-currency-input/user-preferenced-currency-input.component.js index 7c0ec1734..f92a74b1d 100644 --- a/ui/app/components/app/user-preferenced-currency-input/user-preferenced-currency-input.component.js +++ b/ui/app/components/app/user-preferenced-currency-input/user-preferenced-currency-input.component.js @@ -7,7 +7,7 @@ export default class UserPreferencedCurrencyInput extends PureComponent { useNativeCurrencyAsPrimaryCurrency: PropTypes.bool, } - render () { + render() { const { useNativeCurrencyAsPrimaryCurrency, ...restProps } = this.props return ( diff --git a/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.component.js b/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.component.js index c75cfbf33..6e26b6dec 100644 --- a/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.component.js +++ b/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.component.js @@ -12,7 +12,7 @@ export default class UserPreferencedTokenInput extends PureComponent { useNativeCurrencyAsPrimaryCurrency: PropTypes.bool, } - render () { + render() { const { useNativeCurrencyAsPrimaryCurrency, ...restProps } = this.props return ( diff --git a/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.container.js b/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.container.js index daddded7f..1d8799d30 100644 --- a/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.container.js +++ b/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.container.js @@ -11,7 +11,9 @@ const mapStateToProps = (state) => { } } -const UserPreferencedTokenInputContainer = connect(mapStateToProps)(UserPreferencedTokenInput) +const UserPreferencedTokenInputContainer = connect(mapStateToProps)( + UserPreferencedTokenInput, +) UserPreferencedTokenInputContainer.propTypes = { token: PropTypes.shape({ diff --git a/ui/app/components/app/wallet-overview/eth-overview.js b/ui/app/components/app/wallet-overview/eth-overview.js index ce24a74e4..b707e1924 100644 --- a/ui/app/components/app/wallet-overview/eth-overview.js +++ b/ui/app/components/app/wallet-overview/eth-overview.js @@ -6,18 +6,33 @@ import { useHistory } from 'react-router-dom' import Identicon from '../../ui/identicon' import { I18nContext } from '../../../contexts/i18n' -import { SEND_ROUTE, BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes' -import { useMetricEvent, useNewMetricEvent } from '../../../hooks/useMetricEvent' +import { + SEND_ROUTE, + BUILD_QUOTE_ROUTE, +} from '../../../helpers/constants/routes' +import { + useMetricEvent, + useNewMetricEvent, +} from '../../../hooks/useMetricEvent' import { useSwapsEthToken } from '../../../hooks/useSwapsEthToken' import Tooltip from '../../ui/tooltip' import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display' import { PRIMARY, SECONDARY } from '../../../helpers/constants/common' import { showModal } from '../../../store/actions' -import { isBalanceCached, getSelectedAccount, getShouldShowFiat, getCurrentNetworkId, getCurrentKeyring } from '../../../selectors/selectors' +import { + isBalanceCached, + getSelectedAccount, + getShouldShowFiat, + getCurrentNetworkId, + getCurrentKeyring, +} from '../../../selectors/selectors' import SwapIcon from '../../ui/icon/swap-icon.component' import BuyIcon from '../../ui/icon/overview-buy-icon.component' import SendIcon from '../../ui/icon/overview-send-icon.component' -import { getSwapsFeatureLiveness, setSwapsFromToken } from '../../../ducks/swaps/swaps' +import { + getSwapsFeatureLiveness, + setSwapsFromToken, +} from '../../../ducks/swaps/swaps' import IconButton from '../../ui/icon-button' import { MAINNET_NETWORK_ID } from '../../../../../app/scripts/controllers/network/enums' import WalletOverview from './wallet-overview' @@ -47,14 +62,22 @@ const EthOverview = ({ className }) => { const selectedAccount = useSelector(getSelectedAccount) const { balance } = selectedAccount const networkId = useSelector(getCurrentNetworkId) - const enteredSwapsEvent = useNewMetricEvent({ event: 'Swaps Opened', properties: { source: 'Main View', active_currency: 'ETH' }, category: 'swaps' }) + const enteredSwapsEvent = useNewMetricEvent({ + event: 'Swaps Opened', + properties: { source: 'Main View', active_currency: 'ETH' }, + category: 'swaps', + }) const swapsEnabled = useSelector(getSwapsFeatureLiveness) const swapsEthToken = useSwapsEthToken() return ( + balance={ +
    { ethNumberOfDecimals={4} hideTitle /> - { - balanceIsCached ? * : null - } + {balanceIsCached ? ( + * + ) : null}
    - { - showFiat && ( - - ) - } + {showFiat && ( + + )}
    - )} - buttons={( + } + buttons={ <> { } } }} - label={ t('swap') } + label={t('swap')} tooltipRender={(contents) => ( - + {contents} )} /> ) : null} - )} + } className={className} icon={} /> diff --git a/ui/app/components/app/wallet-overview/index.scss b/ui/app/components/app/wallet-overview/index.scss index b86f12dd6..92a166607 100644 --- a/ui/app/components/app/wallet-overview/index.scss +++ b/ui/app/components/app/wallet-overview/index.scss @@ -34,7 +34,7 @@ } text-transform: uppercase; - font-weight: bold; + font-weight: 500; } .eth-overview { @@ -54,9 +54,9 @@ } &__primary-balance { + @include H2; + color: $black; - font-size: 32px; - line-height: 45px; width: 100%; justify-content: center; } @@ -71,15 +71,14 @@ } &__cached-secondary-balance { + @include Paragraph; + color: rgba(220, 153, 18, 0.6901960784313725); - font-size: 16px; - line-height: 23px; } &__secondary-balance { - font-size: 16px; - line-height: 23px; - font-weight: 400; + @include Paragraph; + color: $Grey-400; } @@ -116,17 +115,16 @@ } &__primary-balance { + @include H2; + color: $black; - font-size: 32px; - line-height: 45px; width: 100%; justify-content: center; } &__secondary-balance { - font-size: 16px; - line-height: 23px; - font-weight: 400; + @include H5; + color: $Grey-400; } diff --git a/ui/app/components/app/wallet-overview/token-overview.js b/ui/app/components/app/wallet-overview/token-overview.js index 1d499e1b4..fe7f29087 100644 --- a/ui/app/components/app/wallet-overview/token-overview.js +++ b/ui/app/components/app/wallet-overview/token-overview.js @@ -7,13 +7,26 @@ import Identicon from '../../ui/identicon' import Tooltip from '../../ui/tooltip' import CurrencyDisplay from '../../ui/currency-display' import { I18nContext } from '../../../contexts/i18n' -import { SEND_ROUTE, BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes' -import { useMetricEvent, useNewMetricEvent } from '../../../hooks/useMetricEvent' +import { + SEND_ROUTE, + BUILD_QUOTE_ROUTE, +} from '../../../helpers/constants/routes' +import { + useMetricEvent, + useNewMetricEvent, +} from '../../../hooks/useMetricEvent' import { useTokenTracker } from '../../../hooks/useTokenTracker' import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount' import { updateSendToken } from '../../../store/actions' -import { getSwapsFeatureLiveness, setSwapsFromToken } from '../../../ducks/swaps/swaps' -import { getAssetImages, getCurrentKeyring, getCurrentNetworkId } from '../../../selectors/selectors' +import { + getSwapsFeatureLiveness, + setSwapsFromToken, +} from '../../../ducks/swaps/swaps' +import { + getAssetImages, + getCurrentKeyring, + getCurrentNetworkId, +} from '../../../selectors/selectors' import { MAINNET_NETWORK_ID } from '../../../../../app/scripts/controllers/network/enums' import SwapIcon from '../../ui/icon/swap-icon.component' @@ -40,34 +53,38 @@ const TokenOverview = ({ className, token }) => { const { tokensWithBalances } = useTokenTracker([token]) const balanceToRender = tokensWithBalances[0]?.string const balance = tokensWithBalances[0]?.balance - const formattedFiatBalance = useTokenFiatAmount(token.address, balanceToRender, token.symbol) + const formattedFiatBalance = useTokenFiatAmount( + token.address, + balanceToRender, + token.symbol, + ) const networkId = useSelector(getCurrentNetworkId) - const enteredSwapsEvent = useNewMetricEvent({ event: 'Swaps Opened', properties: { source: 'Token View', active_currency: token.symbol }, category: 'swaps' }) + const enteredSwapsEvent = useNewMetricEvent({ + event: 'Swaps Opened', + properties: { source: 'Token View', active_currency: token.symbol }, + category: 'swaps', + }) const swapsEnabled = useSelector(getSwapsFeatureLiveness) return ( - { - formattedFiatBalance - ? ( - - ) - : null - } + {formattedFiatBalance ? ( + + ) : null}
    - )} - buttons={( + } + buttons={ <> { onClick={() => { if (networkId === MAINNET_NETWORK_ID) { enteredSwapsEvent() - dispatch(setSwapsFromToken({ - ...token, - iconUrl: assetImages[token.address], - balance, - string: balanceToRender, - })) + dispatch( + setSwapsFromToken({ + ...token, + iconUrl: assetImages[token.address], + balance, + string: balanceToRender, + }), + ) if (usingHardwareWallet) { global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE) } else { @@ -101,24 +120,28 @@ const TokenOverview = ({ className, token }) => { } } }} - label={ t('swap') } + label={t('swap')} tooltipRender={(contents) => ( - + {contents} )} /> ) : null} - )} + } className={className} - icon={( + icon={ - )} + } /> ) } diff --git a/ui/app/components/app/wallet-overview/wallet-overview.js b/ui/app/components/app/wallet-overview/wallet-overview.js index 30bbd1529..b4ca1b4ff 100644 --- a/ui/app/components/app/wallet-overview/wallet-overview.js +++ b/ui/app/components/app/wallet-overview/wallet-overview.js @@ -6,12 +6,10 @@ const WalletOverview = ({ balance, buttons, className, icon }) => { return (
    - { icon } - { balance } -
    -
    - { buttons } + {icon} + {balance}
    +
    {buttons}
    ) } diff --git a/ui/app/components/ui/account-mismatch-warning/account-mismatch-warning.component.js b/ui/app/components/ui/account-mismatch-warning/account-mismatch-warning.component.js index fc20a9900..4e7e9c966 100644 --- a/ui/app/components/ui/account-mismatch-warning/account-mismatch-warning.component.js +++ b/ui/app/components/ui/account-mismatch-warning/account-mismatch-warning.component.js @@ -6,7 +6,7 @@ import { getSelectedAccount } from '../../../selectors' import InfoIcon from '../icon/info-icon.component' import { useI18nContext } from '../../../hooks/useI18nContext' -export default function AccountMismatchWarning ({ address }) { +export default function AccountMismatchWarning({ address }) { const selectedAccount = useSelector(getSelectedAccount) const t = useI18nContext() if (selectedAccount.address === address) { @@ -20,7 +20,9 @@ export default function AccountMismatchWarning ({ address }) { wrapperClassName="account-mismatch-warning__tooltip-wrapper" containerClassName="account-mismatch-warning__tooltip-container" > -
    +
    + +
    ) } diff --git a/ui/app/components/ui/alert-circle-icon/alert-circle-icon.component.js b/ui/app/components/ui/alert-circle-icon/alert-circle-icon.component.js index 8569f873d..7c8a37aaa 100644 --- a/ui/app/components/ui/alert-circle-icon/alert-circle-icon.component.js +++ b/ui/app/components/ui/alert-circle-icon/alert-circle-icon.component.js @@ -18,9 +18,7 @@ export default class AlertCircleIcon extends Component { type: PropTypes.oneOf(Object.keys(typeConfig)).isRequired, } - render () { - return ( - - ) + render() { + return } } diff --git a/ui/app/components/ui/alert-circle-icon/alert-circle-icon.stories.js b/ui/app/components/ui/alert-circle-icon/alert-circle-icon.stories.js index 9f873bf2b..8b2dd5574 100644 --- a/ui/app/components/ui/alert-circle-icon/alert-circle-icon.stories.js +++ b/ui/app/components/ui/alert-circle-icon/alert-circle-icon.stories.js @@ -5,14 +5,6 @@ export default { title: 'AlertCircleIcon', } -export const dangerCircleIcon = () => ( - -) +export const dangerCircleIcon = () => -export const warningCircleIcon = () => ( - -) +export const warningCircleIcon = () => diff --git a/ui/app/components/ui/alert/index.js b/ui/app/components/ui/alert/index.js index 437bf492b..e04778569 100644 --- a/ui/app/components/ui/alert/index.js +++ b/ui/app/components/ui/alert/index.js @@ -9,7 +9,7 @@ class Alert extends Component { className: '', } - UNSAFE_componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (!this.props.visible && nextProps.visible) { this.animateIn(nextProps.msg) } else if (this.props.visible && !nextProps.visible) { @@ -17,7 +17,7 @@ class Alert extends Component { } } - animateIn (msg) { + animateIn(msg) { this.setState({ msg, visible: true, @@ -25,7 +25,7 @@ class Alert extends Component { }) } - animateOut () { + animateOut() { this.setState({ msg: null, className: 'hidden', @@ -34,10 +34,9 @@ class Alert extends Component { setTimeout((_) => { this.setState({ visible: false }) }, 500) - } - render () { + render() { if (this.state.visible) { return (
    @@ -51,7 +50,6 @@ class Alert extends Component { Alert.propTypes = { visible: PropTypes.bool.isRequired, - msg: PropTypes.string, /* eslint-disable-line react/no-unused-prop-types */ + msg: PropTypes.string /* eslint-disable-line react/no-unused-prop-types */, } export default Alert - diff --git a/ui/app/components/ui/alert/index.scss b/ui/app/components/ui/alert/index.scss index 0e58f2aac..737244a2c 100644 --- a/ui/app/components/ui/alert/index.scss +++ b/ui/app/components/ui/alert/index.scss @@ -4,10 +4,11 @@ background-color: #33a4e7; .msg { + @include H7; + width: 100%; display: block; color: white; - font-size: 12px; text-align: center; } } diff --git a/ui/app/components/ui/alert/tests/alert.test.js b/ui/app/components/ui/alert/tests/alert.test.js index 70aaa5e16..f69a59b06 100644 --- a/ui/app/components/ui/alert/tests/alert.test.js +++ b/ui/app/components/ui/alert/tests/alert.test.js @@ -8,9 +8,7 @@ describe('Alert', function () { let wrapper beforeEach(function () { - wrapper = shallow( - , - ) + wrapper = shallow() }) it('renders nothing with no visible boolean in state', function () { diff --git a/ui/app/components/ui/breadcrumbs/breadcrumbs.component.js b/ui/app/components/ui/breadcrumbs/breadcrumbs.component.js index c407e4ead..192fb1f42 100644 --- a/ui/app/components/ui/breadcrumbs/breadcrumbs.component.js +++ b/ui/app/components/ui/breadcrumbs/breadcrumbs.component.js @@ -9,20 +9,22 @@ export default class Breadcrumbs extends PureComponent { total: PropTypes.number, } - render () { + render() { const { className, currentIndex, total } = this.props return (
    - { - Array(total).fill().map((_, i) => ( + {Array(total) + .fill() + .map((_, i) => (
    - )) - } + ))}
    ) } diff --git a/ui/app/components/ui/breadcrumbs/tests/breadcrumbs.component.test.js b/ui/app/components/ui/breadcrumbs/tests/breadcrumbs.component.test.js index 63b9a3f51..cbbbfd8e2 100644 --- a/ui/app/components/ui/breadcrumbs/tests/breadcrumbs.component.test.js +++ b/ui/app/components/ui/breadcrumbs/tests/breadcrumbs.component.test.js @@ -5,18 +5,22 @@ import Breadcrumbs from '../breadcrumbs.component' describe('Breadcrumbs Component', function () { it('should render with the correct colors', function () { - const wrapper = shallow( - , - ) + const wrapper = shallow() assert.ok(wrapper) assert.equal(wrapper.find('.breadcrumbs').length, 1) assert.equal(wrapper.find('.breadcrumb').length, 3) - assert.equal(wrapper.find('.breadcrumb').at(0).props().style.backgroundColor, '#FFFFFF') - assert.equal(wrapper.find('.breadcrumb').at(1).props().style.backgroundColor, '#D8D8D8') - assert.equal(wrapper.find('.breadcrumb').at(2).props().style.backgroundColor, '#FFFFFF') + assert.equal( + wrapper.find('.breadcrumb').at(0).props().style.backgroundColor, + '#FFFFFF', + ) + assert.equal( + wrapper.find('.breadcrumb').at(1).props().style.backgroundColor, + '#D8D8D8', + ) + assert.equal( + wrapper.find('.breadcrumb').at(2).props().style.backgroundColor, + '#FFFFFF', + ) }) }) diff --git a/ui/app/components/ui/button-group/button-group.component.js b/ui/app/components/ui/button-group/button-group.component.js index 2de7730ce..aeb9b37d3 100644 --- a/ui/app/components/ui/button-group/button-group.component.js +++ b/ui/app/components/ui/button-group/button-group.component.js @@ -26,57 +26,67 @@ export default class ButtonGroup extends PureComponent { : this.props.defaultActiveButtonIndex, } - componentDidUpdate (_, prevState) { + componentDidUpdate(_, prevState) { // Provides an API for dynamically updating the activeButtonIndex - if (typeof this.props.newActiveButtonIndex === 'number' && prevState.activeButtonIndex !== this.props.newActiveButtonIndex) { + if ( + typeof this.props.newActiveButtonIndex === 'number' && + prevState.activeButtonIndex !== this.props.newActiveButtonIndex + ) { this.setState({ activeButtonIndex: this.props.newActiveButtonIndex }) } } - handleButtonClick (activeButtonIndex) { + handleButtonClick(activeButtonIndex) { this.setState({ activeButtonIndex }) } - renderButtons () { + renderButtons() { const { children, disabled, variant } = this.props return React.Children.map(children, (child, index) => { - return child && ( - + return ( + child && ( + + ) ) }) } - render () { + render() { const { className, style, variant } = this.props return (
    - { this.renderButtons() } + {this.renderButtons()}
    ) } diff --git a/ui/app/components/ui/button-group/button-group.stories.js b/ui/app/components/ui/button-group/button-group.stories.js index b92eaeb7c..38c538092 100644 --- a/ui/app/components/ui/button-group/button-group.stories.js +++ b/ui/app/components/ui/button-group/button-group.stories.js @@ -15,38 +15,16 @@ export const withButtons = () => ( disabled={boolean('Disabled', false)} defaultActiveButtonIndex={1} > - - - + + + ) export const withDisabledButton = () => ( - - - + @@ -58,16 +36,8 @@ export const radioButtons = () => ( defaultActiveButtonIndex={1} variant="radiogroup" > - - + + , + , , , ] @@ -23,16 +25,16 @@ describe('ButtonGroup Component', function () { }) beforeEach(function () { - wrapper = shallow(( + wrapper = shallow( {mockButtons} - - )) + , + ) }) afterEach(function () { @@ -79,7 +81,7 @@ describe('ButtonGroup Component', function () { assert.deepEqual(childButtons.get(1), activeChildButton.get(0)) }) - it('should call handleButtonClick and the respective button\'s onClick method when a button is clicked', function () { + it("should call handleButtonClick and the respective button's onClick method when a button is clicked", function () { assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 0) assert.equal(childButtonSpies.onClick.callCount, 0) const childButtons = wrapper.find('.button-group__button') @@ -109,7 +111,9 @@ describe('ButtonGroup Component', function () { describe('render', function () { it('should render a div with the expected class and style', function () { assert.equal(wrapper.find('div').at(0).props().className, 'someClassName') - assert.deepEqual(wrapper.find('div').at(0).props().style, { color: 'red' }) + assert.deepEqual(wrapper.find('div').at(0).props().style, { + color: 'red', + }) }) it('should call renderButtons when rendering', function () { diff --git a/ui/app/components/ui/button/button.component.js b/ui/app/components/ui/button/button.component.js index c82e52c2a..52b267517 100644 --- a/ui/app/components/ui/button/button.component.js +++ b/ui/app/components/ui/button/button.component.js @@ -25,7 +25,16 @@ const typeHash = { 'first-time': CLASSNAME_FIRST_TIME, } -const Button = ({ type, submit, large, children, icon, rounded, className, ...buttonProps }) => { +const Button = ({ + type, + submit, + large, + children, + icon, + rounded, + className, + ...buttonProps +}) => { // To support using the Button component to render styled links that are semantic html // we swap the html tag we use to render this component and delete any buttonProps that // we know to be erroneous attributes for a link. We will likely want to extract Link @@ -45,10 +54,10 @@ const Button = ({ type, submit, large, children, icon, rounded, className, ...bu rounded && CLASSNAME_ROUNDED, className, )} - { ...buttonProps } + {...buttonProps} > {icon && {icon}} - { children } + {children} ) } diff --git a/ui/app/components/ui/button/button.stories.js b/ui/app/components/ui/button/button.stories.js index c19551835..25fc853de 100644 --- a/ui/app/components/ui/button/button.stories.js +++ b/ui/app/components/ui/button/button.stories.js @@ -66,4 +66,3 @@ export const dangerPrimaryType = () => ( {text('text', 'Click me')} ) - diff --git a/ui/app/components/ui/button/buttons.scss b/ui/app/components/ui/button/buttons.scss index a8364ca65..864c64ada 100644 --- a/ui/app/components/ui/button/buttons.scss +++ b/ui/app/components/ui/button/buttons.scss @@ -14,7 +14,6 @@ $warning-light-orange: #f8b588; @include H6; font-weight: 500; - line-height: 1.25rem; padding: 0.75rem 1rem; display: flex; justify-content: center; @@ -40,7 +39,7 @@ $warning-light-orange: #f8b588; .btn-secondary { color: $Blue-500; - border: 2px solid $hover-secondary; + border: 1px solid $hover-secondary; background-color: $white; &:hover { @@ -61,7 +60,7 @@ $warning-light-orange: #f8b588; .btn-warning { color: $Orange-500; - border: 2px solid $hover-orange; + border: 1px solid $hover-orange; background-color: $white; &:hover { @@ -82,7 +81,7 @@ $warning-light-orange: #f8b588; .btn-danger { color: $Red-500; - border: 2px solid $hover-red; + border: 1px solid $hover-red; background-color: $white; &:hover { @@ -103,7 +102,7 @@ $warning-light-orange: #f8b588; .btn-danger-primary { color: $white; - border: 2px solid $Red-500; + border: 1px solid $Red-500; background-color: $Red-500; &:hover { @@ -126,7 +125,7 @@ $warning-light-orange: #f8b588; .btn-default { color: $Grey-500; - border: 2px solid $hover-default; + border: 1px solid $hover-default; &:hover { border-color: $Grey-500; @@ -146,7 +145,7 @@ $warning-light-orange: #f8b588; .btn-primary { color: $white; - border: 2px solid $Blue-500; + border: 1px solid $Blue-500; background-color: $Blue-500; &:hover { @@ -170,7 +169,6 @@ $warning-light-orange: #f8b588; @include H4; color: $Blue-500; - line-height: 1.25rem; cursor: pointer; background-color: transparent; @@ -211,11 +209,12 @@ $warning-light-orange: #f8b588; } .btn--first-time { + @include H4; + height: 54px; width: 198px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.14); color: $white; - font-size: 1.25rem; font-weight: 500; transition: 200ms ease-in-out; background-color: rgba(247, 134, 28, 0.9); @@ -228,15 +227,6 @@ input[type="submit"][disabled] { opacity: 0.5; } -button.primary { - padding: 8px 12px; - background: #f7861c; - box-shadow: 0 3px 6px rgba(247, 134, 28, 0.36); - color: $white; - font-size: 1.1em; - text-transform: uppercase; -} - .btn--rounded { border-radius: 100px; will-change: box-shadow; diff --git a/ui/app/components/ui/card/card.component.js b/ui/app/components/ui/card/card.component.js index bb7241da1..c4e96e511 100644 --- a/ui/app/components/ui/card/card.component.js +++ b/ui/app/components/ui/card/card.component.js @@ -10,15 +10,13 @@ export default class Card extends PureComponent { children: PropTypes.node, } - render () { + render() { const { className, overrideClassName, title } = this.props return ( -
    -
    - { title } -
    - { this.props.children } +
    +
    {title}
    + {this.props.children}
    ) } diff --git a/ui/app/components/ui/card/tests/card.component.test.js b/ui/app/components/ui/card/tests/card.component.test.js index 9eb104351..4fc92252a 100644 --- a/ui/app/components/ui/card/tests/card.component.test.js +++ b/ui/app/components/ui/card/tests/card.component.test.js @@ -6,10 +6,7 @@ import Card from '../card.component' describe('Card Component', function () { it('should render a card with a title and child element', function () { const wrapper = shallow( - +
    Child
    , ) diff --git a/ui/app/components/ui/check-box/check-box.component.js b/ui/app/components/ui/check-box/check-box.component.js index 4a7457140..360787902 100644 --- a/ui/app/components/ui/check-box/check-box.component.js +++ b/ui/app/components/ui/check-box/check-box.component.js @@ -13,9 +13,7 @@ export const { CHECKED, INDETERMINATE, UNCHECKED } = CHECKBOX_STATE const CheckBox = ({ className, disabled, id, onClick, checked, title }) => { if (typeof checked === 'boolean') { // eslint-disable-next-line no-param-reassign - checked = checked - ? CHECKBOX_STATE.CHECKED - : CHECKBOX_STATE.UNCHECKED + checked = checked ? CHECKBOX_STATE.CHECKED : CHECKBOX_STATE.UNCHECKED } const ref = useRef(null) useLayoutEffect(() => { @@ -27,17 +25,19 @@ const CheckBox = ({ className, disabled, id, onClick, checked, title }) => { checked={checked === CHECKBOX_STATE.CHECKED} className={classnames('check-box', className, { 'far fa-square': checked === CHECKBOX_STATE.UNCHECKED, - 'fa fa-check-square check-box__checked': checked === CHECKBOX_STATE.CHECKED, - 'fa fa-minus-square check-box__indeterminate': checked === CHECKBOX_STATE.INDETERMINATE, + 'fa fa-check-square check-box__checked': + checked === CHECKBOX_STATE.CHECKED, + 'fa fa-minus-square check-box__indeterminate': + checked === CHECKBOX_STATE.INDETERMINATE, })} disabled={disabled} id={id} onClick={ onClick ? (event) => { - event.preventDefault() - onClick() - } + event.preventDefault() + onClick() + } : null } readOnly @@ -53,7 +53,8 @@ CheckBox.propTypes = { disabled: PropTypes.bool, id: PropTypes.string, onClick: PropTypes.func, - checked: PropTypes.oneOf([...Object.keys(CHECKBOX_STATE), true, false]).isRequired, + checked: PropTypes.oneOf([...Object.keys(CHECKBOX_STATE), true, false]) + .isRequired, title: PropTypes.string, } diff --git a/ui/app/components/ui/check-box/check-box.stories.js b/ui/app/components/ui/check-box/check-box.stories.js index 7a573a14c..20647f780 100644 --- a/ui/app/components/ui/check-box/check-box.stories.js +++ b/ui/app/components/ui/check-box/check-box.stories.js @@ -1,7 +1,11 @@ import React from 'react' import { action } from '@storybook/addon-actions' import { boolean, select, text } from '@storybook/addon-knobs/react' -import CheckBox, { CHECKED, INDETERMINATE, UNCHECKED } from './check-box.component' +import CheckBox, { + CHECKED, + INDETERMINATE, + UNCHECKED, +} from './check-box.component' export default { title: 'Check Box', diff --git a/ui/app/components/ui/check-box/index.js b/ui/app/components/ui/check-box/index.js index fc4dfd2f8..9a7460d6a 100644 --- a/ui/app/components/ui/check-box/index.js +++ b/ui/app/components/ui/check-box/index.js @@ -1 +1,6 @@ -export { default, CHECKED, INDETERMINATE, UNCHECKED } from './check-box.component' +export { + default, + CHECKED, + INDETERMINATE, + UNCHECKED, +} from './check-box.component' diff --git a/ui/app/components/ui/circle-icon/circle-icon.component.js b/ui/app/components/ui/circle-icon/circle-icon.component.js index 047245535..e019aa99e 100644 --- a/ui/app/components/ui/circle-icon/circle-icon.component.js +++ b/ui/app/components/ui/circle-icon/circle-icon.component.js @@ -15,13 +15,8 @@ export default class CircleIcon extends PureComponent { circleClass: '', } - render () { - const { - size, - circleClass, - iconSize, - iconSource, - } = this.props + render() { + const { size, circleClass, iconSize, iconSource } = this.props return (
    - { prefixComponent } - { parts.prefix }{ parts.value } - { - parts.suffix && ( - - { parts.suffix } - - ) - } + {prefixComponent} + + {parts.prefix} + {parts.value} + + {parts.suffix && ( + + {parts.suffix} + + )}
    ) } diff --git a/ui/app/components/ui/currency-display/tests/currency-display.component.test.js b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js index 68bac84cf..9e023aef3 100644 --- a/ui/app/components/ui/currency-display/tests/currency-display.component.test.js +++ b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js @@ -15,27 +15,27 @@ describe('CurrencyDisplay Component', function () { })) }) it('should render text with a className', function () { - const wrapper = shallow(( + const wrapper = shallow( - )) + />, + ) assert.ok(wrapper.hasClass('currency-display')) assert.equal(wrapper.text(), '$123.45') }) it('should render text with a prefix', function () { - const wrapper = shallow(( + const wrapper = shallow( - )) + />, + ) assert.ok(wrapper.hasClass('currency-display')) assert.equal(wrapper.text(), '-$123.45') diff --git a/ui/app/components/ui/currency-input/currency-input.component.js b/ui/app/components/ui/currency-input/currency-input.component.js index f17fb5792..5fc3b991d 100644 --- a/ui/app/components/ui/currency-input/currency-input.component.js +++ b/ui/app/components/ui/currency-input/currency-input.component.js @@ -2,7 +2,10 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import UnitInput from '../unit-input' import CurrencyDisplay from '../currency-display' -import { getValueFromWeiHex, getWeiHexFromDecimalValue } from '../../../helpers/utils/conversions.util' +import { + getValueFromWeiHex, + getWeiHexFromDecimalValue, +} from '../../../helpers/utils/conversions.util' import { ETH } from '../../../helpers/constants/common' /** @@ -28,7 +31,7 @@ export default class CurrencyInput extends PureComponent { nativeSuffix: PropTypes.string, } - constructor (props) { + constructor(props) { super(props) const { value: hexValue } = props @@ -41,26 +44,34 @@ export default class CurrencyInput extends PureComponent { } } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { const { value: prevPropsHexValue } = prevProps const { value: propsHexValue } = this.props const { hexValue: stateHexValue } = this.state - if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) { + if ( + prevPropsHexValue !== propsHexValue && + propsHexValue !== stateHexValue + ) { const decimalValue = this.getDecimalValue(this.props) this.setState({ hexValue: propsHexValue, decimalValue }) } } - getDecimalValue (props) { + getDecimalValue(props) { const { value: hexValue, currentCurrency, conversionRate } = props const decimalValueString = this.shouldUseFiat() ? getValueFromWeiHex({ - value: hexValue, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2, - }) + value: hexValue, + toCurrency: currentCurrency, + conversionRate, + numberOfDecimals: 2, + }) : getValueFromWeiHex({ - value: hexValue, toCurrency: ETH, numberOfDecimals: 6, - }) + value: hexValue, + toCurrency: ETH, + numberOfDecimals: 6, + }) return Number(decimalValueString) || 0 } @@ -84,21 +95,31 @@ export default class CurrencyInput extends PureComponent { } handleChange = (decimalValue) => { - const { currentCurrency: fromCurrency, conversionRate, onChange } = this.props + const { + currentCurrency: fromCurrency, + conversionRate, + onChange, + } = this.props const hexValue = this.shouldUseFiat() ? getWeiHexFromDecimalValue({ - value: decimalValue, fromCurrency, conversionRate, invertConversionRate: true, - }) + value: decimalValue, + fromCurrency, + conversionRate, + invertConversionRate: true, + }) : getWeiHexFromDecimalValue({ - value: decimalValue, fromCurrency: ETH, fromDenomination: ETH, conversionRate, - }) + value: decimalValue, + fromCurrency: ETH, + fromDenomination: ETH, + conversionRate, + }) this.setState({ hexValue, decimalValue }) onChange(hexValue) } - renderConversionComponent () { + renderConversionComponent() { const { currentCurrency, nativeCurrency, hideFiat } = this.props const { hexValue } = this.state let currency, numberOfDecimals @@ -106,7 +127,7 @@ export default class CurrencyInput extends PureComponent { if (hideFiat) { return (
    - { this.context.t('noConversionRateAvailable') } + {this.context.t('noConversionRateAvailable')}
    ) } @@ -131,7 +152,7 @@ export default class CurrencyInput extends PureComponent { ) } - render () { + render() { const { fiatSuffix, nativeSuffix, maxModeOn, ...restProps } = this.props const { decimalValue } = this.state @@ -142,14 +163,11 @@ export default class CurrencyInput extends PureComponent { onChange={this.handleChange} value={decimalValue} maxModeOn={maxModeOn} - actionComponent={( -
    - )} + actionComponent={ +
    + } > - { this.renderConversionComponent() } + {this.renderConversionComponent()} ) } diff --git a/ui/app/components/ui/currency-input/currency-input.container.js b/ui/app/components/ui/currency-input/currency-input.container.js index 581e1c1a2..3a5719929 100644 --- a/ui/app/components/ui/currency-input/currency-input.container.js +++ b/ui/app/components/ui/currency-input/currency-input.container.js @@ -8,7 +8,9 @@ import { import CurrencyInput from './currency-input.component' const mapStateToProps = (state) => { - const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state + const { + metamask: { nativeCurrency, currentCurrency, conversionRate }, + } = state const { showFiatInTestnets } = getPreferences(state) const isMainnet = getIsMainnet(state) const maxModeOn = getSendMaxModeState(state) @@ -17,7 +19,7 @@ const mapStateToProps = (state) => { nativeCurrency, currentCurrency, conversionRate, - hideFiat: (!isMainnet && !showFiatInTestnets), + hideFiat: !isMainnet && !showFiatInTestnets, maxModeOn, } } diff --git a/ui/app/components/ui/currency-input/index.scss b/ui/app/components/ui/currency-input/index.scss index b240ed097..c374e06ae 100644 --- a/ui/app/components/ui/currency-input/index.scss +++ b/ui/app/components/ui/currency-input/index.scss @@ -1,7 +1,7 @@ .currency-input { &__conversion-component { - font-size: 12px; - line-height: 12px; + @include H7; + padding-left: 1px; } diff --git a/ui/app/components/ui/currency-input/tests/currency-input.component.test.js b/ui/app/components/ui/currency-input/tests/currency-input.component.test.js index 1e7936978..d4d88be02 100644 --- a/ui/app/components/ui/currency-input/tests/currency-input.component.test.js +++ b/ui/app/components/ui/currency-input/tests/currency-input.component.test.js @@ -12,9 +12,7 @@ import CurrencyDisplay from '../../currency-display' describe('CurrencyInput Component', function () { describe('rendering', function () { it('should render properly without a suffix', function () { - const wrapper = shallow( - , - ) + const wrapper = shallow() assert.ok(wrapper) assert.equal(wrapper.find(UnitInput).length, 1) @@ -76,7 +74,10 @@ describe('CurrencyInput Component', function () { assert.equal(wrapper.find('.unit-input__suffix').length, 1) assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH') assert.equal(wrapper.find('.unit-input__input').props().value, '1') - assert.equal(wrapper.find('.currency-display-component').text(), '$231.06USD') + assert.equal( + wrapper.find('.currency-display-component').text(), + '$231.06USD', + ) }) it('should render properly with a fiat value', function () { @@ -110,7 +111,10 @@ describe('CurrencyInput Component', function () { assert.equal(wrapper.find('.unit-input__suffix').length, 1) assert.equal(wrapper.find('.unit-input__suffix').text(), 'USD') assert.equal(wrapper.find('.unit-input__input').props().value, '1') - assert.equal(wrapper.find('.currency-display-component').text(), '0.004328ETH') + assert.equal( + wrapper.find('.currency-display-component').text(), + '0.004328ETH', + ) }) it('should render properly with a native value when hideFiat is true', function () { @@ -149,7 +153,10 @@ describe('CurrencyInput Component', function () { assert.equal(wrapper.find('.unit-input__suffix').length, 1) assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH') assert.equal(wrapper.find('.unit-input__input').props().value, '0.004328') - assert.equal(wrapper.find('.currency-input__conversion-component').text(), 'noConversionRateAvailable_t') + assert.equal( + wrapper.find('.currency-input__conversion-component').text(), + 'noConversionRateAvailable_t', + ) }) }) @@ -190,14 +197,20 @@ describe('CurrencyInput Component', function () { const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance() assert.equal(currencyInputInstance.state.decimalValue, 0) assert.equal(currencyInputInstance.state.hexValue, undefined) - assert.equal(wrapper.find('.currency-display-component').text(), '$0.00USD') + assert.equal( + wrapper.find('.currency-display-component').text(), + '$0.00USD', + ) const input = wrapper.find('input') assert.equal(input.props().value, 0) input.simulate('change', { target: { value: 1 } }) assert.equal(handleChangeSpy.callCount, 1) assert.ok(handleChangeSpy.calledWith('de0b6b3a7640000')) - assert.equal(wrapper.find('.currency-display-component').text(), '$231.06USD') + assert.equal( + wrapper.find('.currency-display-component').text(), + '$231.06USD', + ) assert.equal(currencyInputInstance.state.decimalValue, 1) assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000') }) @@ -238,7 +251,10 @@ describe('CurrencyInput Component', function () { input.simulate('change', { target: { value: 1 } }) assert.equal(handleChangeSpy.callCount, 1) assert.ok(handleChangeSpy.calledWith('f602f2234d0ea')) - assert.equal(wrapper.find('.currency-display-component').text(), '0.004328ETH') + assert.equal( + wrapper.find('.currency-display-component').text(), + '0.004328ETH', + ) assert.equal(currencyInputInstance.state.decimalValue, 1) assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea') }) @@ -307,20 +323,29 @@ describe('CurrencyInput Component', function () { const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance() assert.equal(currencyInputInstance.state.decimalValue, 0) assert.equal(currencyInputInstance.state.hexValue, undefined) - assert.equal(wrapper.find('.currency-display-component').text(), '$0.00USD') + assert.equal( + wrapper.find('.currency-display-component').text(), + '$0.00USD', + ) const input = wrapper.find('input') assert.equal(input.props().value, 0) input.simulate('change', { target: { value: 1 } }) assert.equal(handleChangeSpy.callCount, 1) assert.ok(handleChangeSpy.calledWith('de0b6b3a7640000')) - assert.equal(wrapper.find('.currency-display-component').text(), '$231.06USD') + assert.equal( + wrapper.find('.currency-display-component').text(), + '$231.06USD', + ) assert.equal(currencyInputInstance.state.decimalValue, 1) assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000') const swap = wrapper.find('.currency-input__swap-component') swap.simulate('click') - assert.equal(wrapper.find('.currency-display-component').text(), '0.004328ETH') + assert.equal( + wrapper.find('.currency-display-component').text(), + '0.004328ETH', + ) }) }) }) diff --git a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js index 1806d6425..3dac9add0 100644 --- a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js +++ b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js @@ -45,7 +45,8 @@ describe('CurrencyInput container', function () { }, // Test # 2 { - comment: 'should return correct props when not in mainnet and showFiatInTestnets is false', + comment: + 'should return correct props when not in mainnet and showFiatInTestnets is false', mockState: { metamask: { conversionRate: 280.45, @@ -72,7 +73,8 @@ describe('CurrencyInput container', function () { }, // Test # 3 { - comment: 'should return correct props when not in mainnet and showFiatInTestnets is true', + comment: + 'should return correct props when not in mainnet and showFiatInTestnets is true', mockState: { metamask: { conversionRate: 280.45, @@ -99,7 +101,8 @@ describe('CurrencyInput container', function () { }, // Test # 4 { - comment: 'should return correct props when in mainnet and showFiatInTestnets is true', + comment: + 'should return correct props when in mainnet and showFiatInTestnets is true', mockState: { metamask: { conversionRate: 280.45, @@ -179,10 +182,19 @@ describe('CurrencyInput container', function () { }, ] - tests.forEach(({ mock: { stateProps, dispatchProps, ownProps }, expected, comment }) => { - it(comment, function () { - assert.deepEqual(mergeProps(stateProps, dispatchProps, ownProps), expected) - }) - }) + tests.forEach( + ({ + mock: { stateProps, dispatchProps, ownProps }, + expected, + comment, + }) => { + it(comment, function () { + assert.deepEqual( + mergeProps(stateProps, dispatchProps, ownProps), + expected, + ) + }) + }, + ) }) }) diff --git a/ui/app/components/ui/dialog/dialog.scss b/ui/app/components/ui/dialog/dialog.scss index d113947ad..72d3b8655 100644 --- a/ui/app/components/ui/dialog/dialog.scss +++ b/ui/app/components/ui/dialog/dialog.scss @@ -1,6 +1,6 @@ .dialog { - font-size: 0.75rem; - line-height: 1rem; + @include H7; + padding: 1rem; border: 1px solid $black; box-sizing: border-box; diff --git a/ui/app/components/ui/dialog/index.js b/ui/app/components/ui/dialog/index.js index 50b777a7a..672d83337 100644 --- a/ui/app/components/ui/dialog/index.js +++ b/ui/app/components/ui/dialog/index.js @@ -2,7 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' -export default function Dialog (props) { +export default function Dialog(props) { const { children, type, className, onClick } = props return (
    - { children } + {children}
    ) } diff --git a/ui/app/components/ui/dropdown/dropdown.js b/ui/app/components/ui/dropdown/dropdown.js index be4a052a4..db75be922 100644 --- a/ui/app/components/ui/dropdown/dropdown.js +++ b/ui/app/components/ui/dropdown/dropdown.js @@ -2,7 +2,15 @@ import React, { useCallback } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' -const Dropdown = ({ className, disabled, onChange, options, selectedOption, style, title }) => { +const Dropdown = ({ + className, + disabled, + onChange, + options, + selectedOption, + style, + title, +}) => { const _onChange = useCallback( (event) => { event.preventDefault() @@ -21,18 +29,13 @@ const Dropdown = ({ className, disabled, onChange, options, selectedOption, styl style={style} value={selectedOption} > - { - options.map((option) => { - return ( - - ) - }) - } + {options.map((option) => { + return ( + + ) + })} ) } diff --git a/ui/app/components/ui/dropdown/dropdown.scss b/ui/app/components/ui/dropdown/dropdown.scss index 49a684bb7..e5d555ef9 100644 --- a/ui/app/components/ui/dropdown/dropdown.scss +++ b/ui/app/components/ui/dropdown/dropdown.scss @@ -1,4 +1,6 @@ .dropdown { + @include H6; + appearance: none; // TODO: remove these after getting autoprefixer working in Storybook @@ -11,7 +13,6 @@ background-position: right 18px top 50%; background-color: white; padding: 8px 32px 8px 16px; - font-size: 14px; [dir='rtl'] & { background-position: left 18px top 50%; diff --git a/ui/app/components/ui/dropdown/dropdown.stories.js b/ui/app/components/ui/dropdown/dropdown.stories.js index 1ecb31bc9..e5771623f 100644 --- a/ui/app/components/ui/dropdown/dropdown.stories.js +++ b/ui/app/components/ui/dropdown/dropdown.stories.js @@ -16,7 +16,10 @@ const namedOptions = unnamedOptions.map((option, index) => { }) const namedOptionsWithVeryLongNames = unnamedOptions.map((option, index) => { - return { ...option, name: `Option ${index} with a very${', very'.repeat(index)} long name` } + return { + ...option, + name: `Option ${index} with a very${', very'.repeat(index)} long name`, + } }) export const simple = () => ( @@ -26,13 +29,11 @@ export const simple = () => ( onChange={action('Selection changed')} options={namedOptions} required={boolean('Required', false)} - selectedOption={ - select( - 'Selected Option', - namedOptions.map((option) => option.value), - namedOptions[0].value, - ) - } + selectedOption={select( + 'Selected Option', + namedOptions.map((option) => option.value), + namedOptions[0].value, + )} /> ) @@ -43,13 +44,11 @@ export const optionsWithoutNames = () => ( onChange={action('Selection changed')} options={unnamedOptions} required={boolean('Required', false)} - selectedOption={ - select( - 'Selected Option', - unnamedOptions.map((option) => option.value), - unnamedOptions[0].value, - ) - } + selectedOption={select( + 'Selected Option', + unnamedOptions.map((option) => option.value), + unnamedOptions[0].value, + )} /> ) @@ -60,13 +59,11 @@ export const optionsWithLongNames = () => ( onChange={action('Selection changed')} options={namedOptionsWithVeryLongNames} required={boolean('Required', false)} - selectedOption={ - select( - 'Selected Option', - namedOptionsWithVeryLongNames.map((option) => option.value), - namedOptionsWithVeryLongNames[0].value, - ) - } + selectedOption={select( + 'Selected Option', + namedOptionsWithVeryLongNames.map((option) => option.value), + namedOptionsWithVeryLongNames[0].value, + )} /> ) @@ -77,13 +74,11 @@ export const optionsWithLongNamesAndShortWidth = () => ( onChange={action('Selection changed')} options={namedOptionsWithVeryLongNames} required={boolean('Required', false)} - selectedOption={ - select( - 'Selected Option', - namedOptionsWithVeryLongNames.map((option) => option.value), - namedOptionsWithVeryLongNames[0].value, - ) - } + selectedOption={select( + 'Selected Option', + namedOptionsWithVeryLongNames.map((option) => option.value), + namedOptionsWithVeryLongNames[0].value, + )} style={{ width: '200px' }} /> ) diff --git a/ui/app/components/ui/editable-label/editable-label.js b/ui/app/components/ui/editable-label/editable-label.js index d1788472e..e082082b1 100644 --- a/ui/app/components/ui/editable-label/editable-label.js +++ b/ui/app/components/ui/editable-label/editable-label.js @@ -14,21 +14,22 @@ class EditableLabel extends Component { value: this.props.defaultValue || '', } - handleSubmit () { + handleSubmit() { const { value } = this.state if (value === '') { return } - Promise.resolve(this.props.onSubmit(value)) - .then(() => this.setState({ isEditing: false })) + Promise.resolve(this.props.onSubmit(value)).then(() => + this.setState({ isEditing: false }), + ) } - renderEditing () { + renderEditing() { const { value } = this.state - return [( + return [ - ), ( + />,
    - this.handleSubmit()} /> -
    - )] + this.handleSubmit()} + /> +
    , + ] } - renderReadonly () { - return [( -
    {this.state.value}
    - ), ( + renderReadonly() { + return [ +
    + {this.state.value} +
    ,
    - this.setState({ isEditing: true })} /> -
    - )] + this.setState({ isEditing: true })} + /> +
    , + ] } - render () { + render() { const { isEditing } = this.state const { className } = this.props return (
    - { - isEditing - ? this.renderEditing() - : this.renderReadonly() - } + {isEditing ? this.renderEditing() : this.renderReadonly()}
    ) } diff --git a/ui/app/components/ui/editable-label/index.scss b/ui/app/components/ui/editable-label/index.scss index c69ea1428..fdb430c7d 100644 --- a/ui/app/components/ui/editable-label/index.scss +++ b/ui/app/components/ui/editable-label/index.scss @@ -12,8 +12,9 @@ } &__input { + @include H6; + width: 250px; - font-size: 14px; text-align: center; border: 1px solid $alto; diff --git a/ui/app/components/ui/error-message/error-message.component.js b/ui/app/components/ui/error-message/error-message.component.js index b4464c33b..e4d39ee4f 100644 --- a/ui/app/components/ui/error-message/error-message.component.js +++ b/ui/app/components/ui/error-message/error-message.component.js @@ -7,13 +7,8 @@ const ErrorMessage = (props, context) => { return (
    - -
    - { `ALERT: ${error}` } -
    + +
    {`ALERT: ${error}`}
    ) } diff --git a/ui/app/components/ui/error-message/index.scss b/ui/app/components/ui/error-message/index.scss index 8b3860c37..eba500931 100644 --- a/ui/app/components/ui/error-message/index.scss +++ b/ui/app/components/ui/error-message/index.scss @@ -1,10 +1,11 @@ .error-message { + @include H7; + min-height: 32px; border: 1px solid $monzo; color: $monzo; background: lighten($monzo, 56%); border-radius: 4px; - font-size: 0.75rem; display: flex; justify-content: flex-start; align-items: center; diff --git a/ui/app/components/ui/error-message/tests/error-message.component.test.js b/ui/app/components/ui/error-message/tests/error-message.component.test.js index 8e4025bc7..5bf05d876 100644 --- a/ui/app/components/ui/error-message/tests/error-message.component.test.js +++ b/ui/app/components/ui/error-message/tests/error-message.component.test.js @@ -7,30 +7,30 @@ describe('ErrorMessage Component', function () { const t = (key) => `translate ${key}` it('should render a message from props.errorMessage', function () { - const wrapper = shallow( - , - { context: { t } }, - ) + const wrapper = shallow(, { + context: { t }, + }) assert.ok(wrapper) assert.equal(wrapper.find('.error-message').length, 1) assert.equal(wrapper.find('.error-message__icon').length, 1) - assert.equal(wrapper.find('.error-message__text').text(), 'ALERT: This is an error.') + assert.equal( + wrapper.find('.error-message__text').text(), + 'ALERT: This is an error.', + ) }) it('should render a message translated from props.errorKey', function () { - const wrapper = shallow( - , - { context: { t } }, - ) + const wrapper = shallow(, { + context: { t }, + }) assert.ok(wrapper) assert.equal(wrapper.find('.error-message').length, 1) assert.equal(wrapper.find('.error-message__icon').length, 1) - assert.equal(wrapper.find('.error-message__text').text(), 'ALERT: translate testKey') + assert.equal( + wrapper.find('.error-message__text').text(), + 'ALERT: translate testKey', + ) }) }) diff --git a/ui/app/components/ui/export-text-container/export-text-container.component.js b/ui/app/components/ui/export-text-container/export-text-container.component.js index 1bd4d4e2f..2afa20575 100644 --- a/ui/app/components/ui/export-text-container/export-text-container.component.js +++ b/ui/app/components/ui/export-text-container/export-text-container.component.js @@ -5,7 +5,7 @@ import Copy from '../icon/copy-icon.component' import { useI18nContext } from '../../../hooks/useI18nContext' import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard' -function ExportTextContainer ({ text = '' }) { +function ExportTextContainer({ text = '' }) { const t = useI18nContext() const [copied, handleCopy] = useCopyToClipboard() diff --git a/ui/app/components/ui/export-text-container/index.scss b/ui/app/components/ui/export-text-container/index.scss index c3e32895f..ee225e5db 100644 --- a/ui/app/components/ui/export-text-container/index.scss +++ b/ui/app/components/ui/export-text-container/index.scss @@ -17,10 +17,11 @@ } &__text { + @include H4; + resize: none; border: none; background: $alabaster; - font-size: 20px; text-align: center; } @@ -32,12 +33,13 @@ } &__button { + @include H7; + padding: 10px; flex: 1; display: flex; justify-content: center; align-items: center; - font-size: 12px; cursor: pointer; color: $primary-blue; diff --git a/ui/app/components/ui/hex-to-decimal/hex-to-decimal.component.js b/ui/app/components/ui/hex-to-decimal/hex-to-decimal.component.js index f03aaf255..69ddea055 100644 --- a/ui/app/components/ui/hex-to-decimal/hex-to-decimal.component.js +++ b/ui/app/components/ui/hex-to-decimal/hex-to-decimal.component.js @@ -8,14 +8,10 @@ export default class HexToDecimal extends PureComponent { value: PropTypes.string, } - render () { + render() { const { className, value } = this.props const decimalValue = hexToDecimal(value) - return ( -
    - { decimalValue } -
    - ) + return
    {decimalValue}
    } } diff --git a/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js b/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js index 683bb29d5..3d8086630 100644 --- a/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js +++ b/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js @@ -5,24 +5,18 @@ import HexToDecimal from '../hex-to-decimal.component' describe('HexToDecimal Component', function () { it('should render a prefixed hex as a decimal with a className', function () { - const wrapper = shallow(( - - )) + const wrapper = shallow( + , + ) assert.ok(wrapper.hasClass('hex-to-decimal')) assert.equal(wrapper.text(), '12345') }) it('should render an unprefixed hex as a decimal with a className', function () { - const wrapper = shallow(( - - )) + const wrapper = shallow( + , + ) assert.ok(wrapper.hasClass('hex-to-decimal')) assert.equal(wrapper.text(), '6789') diff --git a/ui/app/components/ui/icon-border/icon-border.js b/ui/app/components/ui/icon-border/icon-border.js index 5911cf918..f320a04b8 100644 --- a/ui/app/components/ui/icon-border/icon-border.js +++ b/ui/app/components/ui/icon-border/icon-border.js @@ -1,11 +1,11 @@ import React from 'react' import PropTypes from 'prop-types' -export default function IconBorder ({ children, size }) { +export default function IconBorder({ children, size }) { const borderStyle = { height: `${size}px`, width: `${size}px` } return (
    - { children } + {children}
    ) } diff --git a/ui/app/components/ui/icon-button/icon-button.js b/ui/app/components/ui/icon-button/icon-button.js index 16767bf5e..7d95fa8d4 100644 --- a/ui/app/components/ui/icon-button/icon-button.js +++ b/ui/app/components/ui/icon-button/icon-button.js @@ -4,20 +4,28 @@ import classNames from 'classnames' const defaultRender = (inner) => inner -export default function IconButton ({ onClick, Icon, disabled, label, tooltipRender, className, ...props }) { +export default function IconButton({ + onClick, + Icon, + disabled, + label, + tooltipRender, + className, + ...props +}) { const renderWrapper = tooltipRender ?? defaultRender return ( + )} {titleIcon && ( -
    - {titleIcon} -
    +
    {titleIcon}
    )}
    - {subtitle && ( -
    - {subtitle} -
    - )} - {children && ( -
    - { children } -
    - )} - {midContent && ( -
    - {midContent} -
    - )} + {subtitle &&
    {subtitle}
    } + {children &&
    {children}
    } + {midContent &&
    {midContent}
    } {rightContent && ( -
    - {rightContent} -
    +
    {rightContent}
    )}
    ) diff --git a/ui/app/components/ui/list-item/list-item.stories.js b/ui/app/components/ui/list-item/list-item.stories.js index 197a06296..80a979b5b 100644 --- a/ui/app/components/ui/list-item/list-item.stories.js +++ b/ui/app/components/ui/list-item/list-item.stories.js @@ -13,7 +13,7 @@ export default { title: 'ListItem', } -function Currencies ({ primary, secondary }) { +function Currencies({ primary, secondary }) { return (
    {primary}
    @@ -33,27 +33,22 @@ const failColor = '#D73A49' export const send = () => ( } - titleIcon={ ( - - )} + titleIcon={} title={text('title', 'Send DAI')} className="list-item" subtitle={text('subtitle', 'Sept 20 · To: 00X4...3058')} - rightContent={} + rightContent={ + + } >
    - - +
    ) @@ -63,9 +58,18 @@ export const pending = () => ( icon={} title={text('title', 'Hatch Turtles')} className="list-item" - subtitleStatus={Unapproved · } + subtitleStatus={ + + Unapproved ·{' '} + + } subtitle={text('subtitle', 'Turtlefarm.com')} - rightContent={} + rightContent={ + + } /> ) @@ -75,7 +79,12 @@ export const approve = () => ( title={text('title', 'Approve spend limit')} className="list-item" subtitle={text('subtitle', 'Sept 20 · oxuniverse.com')} - rightContent={} + rightContent={ + + } /> ) @@ -85,6 +94,11 @@ export const receive = () => ( title={text('title', 'Hatch Turtles')} className="list-item" subtitle={text('subtitle', 'Sept 20 · From: 00X4...3058')} - rightContent={} + rightContent={ + + } /> ) diff --git a/ui/app/components/ui/list-item/tests/list-item.test.js b/ui/app/components/ui/list-item/tests/list-item.test.js index fc998e369..94d42ea3c 100644 --- a/ui/app/components/ui/list-item/tests/list-item.test.js +++ b/ui/app/components/ui/list-item/tests/list-item.test.js @@ -41,19 +41,31 @@ describe('ListItem', function () { assert.equal(wrapper.find('.list-item__heading h2').text(), TITLE) }) it(`renders "I am a list item" subtitle`, function () { - assert.equal(wrapper.find('.list-item__subheading').text(), 'I am a list item') + assert.equal( + wrapper.find('.list-item__subheading').text(), + 'I am a list item', + ) }) it('attaches external className', function () { assert(wrapper.props().className.includes(CLASSNAME)) }) it('renders content on the right side of the list item', function () { - assert.equal(wrapper.find('.list-item__right-content p').text(), 'Content rendered to the right') + assert.equal( + wrapper.find('.list-item__right-content p').text(), + 'Content rendered to the right', + ) }) it('renders content in the middle of the list item', function () { - assert.equal(wrapper.find('.list-item__mid-content p').text(), 'Content rendered in the middle') + assert.equal( + wrapper.find('.list-item__mid-content p').text(), + 'Content rendered in the middle', + ) }) it('renders list item actions', function () { - assert.equal(wrapper.find('.list-item__actions button').text(), 'I am a button') + assert.equal( + wrapper.find('.list-item__actions button').text(), + 'I am a button', + ) }) it('renders the title icon', function () { assert(wrapper.find(Preloader)) diff --git a/ui/app/components/ui/loading-screen/index.scss b/ui/app/components/ui/loading-screen/index.scss index d05286cee..dd3f40d58 100644 --- a/ui/app/components/ui/loading-screen/index.scss +++ b/ui/app/components/ui/loading-screen/index.scss @@ -26,13 +26,6 @@ align-items: center; } - &__message { - margin-top: 32px; - font-weight: 400; - font-size: 20px; - color: $manatee; - } - &__error-screen { display: flex; flex-direction: column; diff --git a/ui/app/components/ui/loading-screen/loading-screen.component.js b/ui/app/components/ui/loading-screen/loading-screen.component.js index 457f2dcbd..279d54c12 100644 --- a/ui/app/components/ui/loading-screen/loading-screen.component.js +++ b/ui/app/components/ui/loading-screen/loading-screen.component.js @@ -9,27 +9,33 @@ class LoadingScreen extends Component { } static propTypes = { - loadingMessage: PropTypes.oneOf([PropTypes.string, PropTypes.element]), + loadingMessage: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), showLoadingSpinner: PropTypes.bool, header: PropTypes.element, } - renderMessage () { + renderMessage() { const { loadingMessage } = this.props if (!loadingMessage) { return null } - return isValidElement(loadingMessage) ? loadingMessage : {loadingMessage} + return isValidElement(loadingMessage) ? ( + loadingMessage + ) : ( + {loadingMessage} + ) } - render () { + render() { return (
    {this.props.header}
    - {this.props.showLoadingSpinner && } + {this.props.showLoadingSpinner && ( + + )} {this.renderMessage()}
    diff --git a/ui/app/components/ui/lock-icon/lock-icon.component.js b/ui/app/components/ui/lock-icon/lock-icon.component.js index 7724b75dd..bc8828e20 100644 --- a/ui/app/components/ui/lock-icon/lock-icon.component.js +++ b/ui/app/components/ui/lock-icon/lock-icon.component.js @@ -1,6 +1,6 @@ import React from 'react' -export default function LockIcon (props) { +export default function LockIcon(props) { return ( { - const horizontalMiddle = left + (width / 2) - const verticalMiddle = top + (height / 2) + const horizontalMiddle = left + width / 2 + const verticalMiddle = top + height / 2 return { up: { x: horizontalMiddle, y: top - height }, - down: { x: horizontalMiddle, y: top + (height * 2) }, + down: { x: horizontalMiddle, y: top + height * 2 }, left: { x: left - width, y: verticalMiddle }, - right: { x: left + (width * 2), y: verticalMiddle }, + right: { x: left + width * 2, y: verticalMiddle }, middle: { x: horizontalMiddle, y: verticalMiddle }, } } @@ -33,7 +33,7 @@ export default class Mascot extends Component { lookAtDirection: null, } - constructor (props) { + constructor(props) { super(props) const { width, height, followMouse } = props @@ -47,29 +47,37 @@ export default class Mascot extends Component { this.mascotContainer = createRef() - this.refollowMouse = debounce(this.logo.setFollowMouse.bind(this.logo, true), 1000) + this.refollowMouse = debounce( + this.logo.setFollowMouse.bind(this.logo, true), + 1000, + ) this.unfollowMouse = this.logo.setFollowMouse.bind(this.logo, false) } - handleAnimationEvents () { + handleAnimationEvents() { // only setup listeners once if (this.animations) { return } this.animations = this.props.animationEventEmitter this.animations.on('point', this.lookAt.bind(this)) - this.animations.on('setFollowMouse', this.logo.setFollowMouse.bind(this.logo)) + this.animations.on( + 'setFollowMouse', + this.logo.setFollowMouse.bind(this.logo), + ) } - lookAt (target) { + lookAt(target) { this.unfollowMouse() this.logo.lookAt(target) this.refollowMouse() } - componentDidMount () { + componentDidMount() { this.mascotContainer.current.appendChild(this.logo.container) - this.directionTargetMap = directionTargetGenerator(this.mascotContainer.current.getBoundingClientRect()) + this.directionTargetMap = directionTargetGenerator( + this.mascotContainer.current.getBoundingClientRect(), + ) const { lookAtTarget, lookAtDirection } = this.props @@ -80,13 +88,20 @@ export default class Mascot extends Component { } } - componentDidUpdate (prevProps) { - const { lookAtTarget: prevTarget = {}, lookAtDirection: prevDirection = null, followMouse: prevFollowMouse } = prevProps + componentDidUpdate(prevProps) { + const { + lookAtTarget: prevTarget = {}, + lookAtDirection: prevDirection = null, + followMouse: prevFollowMouse, + } = prevProps const { lookAtTarget = {}, followMouse, lookAtDirection } = this.props if (lookAtDirection && prevDirection !== lookAtDirection) { this.logo.lookAtAndRender(this.directionTargetMap[lookAtDirection]) - } else if (lookAtTarget?.x !== prevTarget?.x || lookAtTarget?.y !== prevTarget?.y) { + } else if ( + lookAtTarget?.x !== prevTarget?.x || + lookAtTarget?.y !== prevTarget?.y + ) { this.logo.lookAtAndRender(lookAtTarget) } if (prevFollowMouse !== followMouse) { @@ -95,23 +110,18 @@ export default class Mascot extends Component { } } - componentWillUnmount () { + componentWillUnmount() { this.animations = this.props.animationEventEmitter this.animations.removeAllListeners() this.logo.container.remove() this.logo.stopAnimation() } - render () { + render() { // this is a bit hacky // the event emitter is on `this.props` // and we dont get that until render this.handleAnimationEvents() - return ( -
    - ) + return
    } } diff --git a/ui/app/components/ui/mascot/mascot.stories.js b/ui/app/components/ui/mascot/mascot.stories.js index 0ce1ab09a..ad698370b 100644 --- a/ui/app/components/ui/mascot/mascot.stories.js +++ b/ui/app/components/ui/mascot/mascot.stories.js @@ -24,7 +24,7 @@ export default { title: 'Mascot', } -export function Demo () { +export function Demo() { const [lookAtDirection, setLookAtDirection] = useState(null) const [followMouseMode, setFollowMouseMode] = useState(false) const [clickToLookMode, setClickToLookMode] = useState(false) @@ -40,7 +40,9 @@ export function Demo () {
    { - const isButtonClick = event.target.classList.contains('button-group__button') + const isButtonClick = event.target.classList.contains( + 'button-group__button', + ) if (clickToLookMode && !isButtonClick) { setLookAtDirection(null) setClickedTarget({ x: event.clientX, y: event.clientY }) @@ -60,21 +62,11 @@ export function Demo () { style={{ width: '300px', flexFlow: 'column' }} defaultActiveButtonIndex={4} > - - - - - + + + + + ) diff --git a/ui/app/components/ui/menu/menu.js b/ui/app/components/ui/menu/menu.js index 9682a8cc6..c093cf80c 100644 --- a/ui/app/components/ui/menu/menu.js +++ b/ui/app/components/ui/menu/menu.js @@ -4,11 +4,23 @@ import { createPortal } from 'react-dom' import { usePopper } from 'react-popper' import classnames from 'classnames' -const Menu = ({ anchorElement, children, className, onHide, popperOptions }) => { +const Menu = ({ + anchorElement, + children, + className, + onHide, + popperOptions, +}) => { const [popperElement, setPopperElement] = useState(null) - const popoverContainerElement = useRef(document.getElementById('popover-content')) + const popoverContainerElement = useRef( + document.getElementById('popover-content'), + ) - const { attributes, styles } = usePopper(anchorElement, popperElement, popperOptions) + const { attributes, styles } = usePopper( + anchorElement, + popperElement, + popperOptions, + ) return createPortal( <> @@ -19,7 +31,7 @@ const Menu = ({ anchorElement, children, className, onHide, popperOptions }) => style={styles.popper} {...attributes.popper} > - { children } + {children}
    , popoverContainerElement.current, diff --git a/ui/app/components/ui/menu/menu.scss b/ui/app/components/ui/menu/menu.scss index a34deda53..4f89b44f5 100644 --- a/ui/app/components/ui/menu/menu.scss +++ b/ui/app/components/ui/menu/menu.scss @@ -1,5 +1,7 @@ .menu { &__container { + @include H6; + background: $white; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.214); border-radius: 8px; @@ -9,9 +11,6 @@ flex-direction: column; align-items: center; padding: 0 16px; - font-size: 14px; - font-weight: normal; - line-height: 20px; z-index: 1050; } diff --git a/ui/app/components/ui/menu/menu.stories.js b/ui/app/components/ui/menu/menu.stories.js index 02eb2186a..59b73b701 100644 --- a/ui/app/components/ui/menu/menu.stories.js +++ b/ui/app/components/ui/menu/menu.stories.js @@ -8,12 +8,14 @@ export default { export const Basic = () => { return ( - - Menu Item 1 + + + Menu Item 1 + Menu Item 2 - Menu Item 3 + + Menu Item 3 + ) } @@ -23,13 +25,17 @@ export const Anchored = () => { return ( <> - - Menu Item 1 + + + Menu Item 1 + Menu Item 2 - Menu Item 3 + + Menu Item 3 + ) diff --git a/ui/app/components/ui/metafox-logo/metafox-logo.component.js b/ui/app/components/ui/metafox-logo/metafox-logo.component.js index 4b1509c6e..6a9838543 100644 --- a/ui/app/components/ui/metafox-logo/metafox-logo.component.js +++ b/ui/app/components/ui/metafox-logo/metafox-logo.component.js @@ -12,7 +12,7 @@ export default class MetaFoxLogo extends PureComponent { onClick: undefined, } - render () { + render() { const { onClick, unsetIconHeight } = this.props const iconProps = unsetIconHeight ? {} : { height: 42, width: 42 } @@ -26,12 +26,18 @@ export default class MetaFoxLogo extends PureComponent {
    ) diff --git a/ui/app/components/ui/metafox-logo/tests/metafox-logo.component.test.js b/ui/app/components/ui/metafox-logo/tests/metafox-logo.component.test.js index acdf4c1c7..ab2fd753b 100644 --- a/ui/app/components/ui/metafox-logo/tests/metafox-logo.component.test.js +++ b/ui/app/components/ui/metafox-logo/tests/metafox-logo.component.test.js @@ -4,22 +4,29 @@ import { mount } from 'enzyme' import MetaFoxLogo from '..' describe('MetaFoxLogo', function () { - it('sets icon height and width to 42 by default', function () { - const wrapper = mount( - , - ) + const wrapper = mount() - assert.equal(wrapper.find('img.app-header__metafox-logo--icon').prop('width'), 42) - assert.equal(wrapper.find('img.app-header__metafox-logo--icon').prop('height'), 42) + assert.equal( + wrapper.find('img.app-header__metafox-logo--icon').prop('width'), + 42, + ) + assert.equal( + wrapper.find('img.app-header__metafox-logo--icon').prop('height'), + 42, + ) }) it('does not set icon height and width when unsetIconHeight is true', function () { - const wrapper = mount( - , - ) + const wrapper = mount() - assert.equal(wrapper.find('img.app-header__metafox-logo--icon').prop('width'), null) - assert.equal(wrapper.find('img.app-header__metafox-logo--icon').prop('height'), null) + assert.equal( + wrapper.find('img.app-header__metafox-logo--icon').prop('width'), + null, + ) + assert.equal( + wrapper.find('img.app-header__metafox-logo--icon').prop('height'), + null, + ) }) }) diff --git a/ui/app/components/ui/page-container/index.scss b/ui/app/components/ui/page-container/index.scss index 4c4969a2b..df4f9067a 100644 --- a/ui/app/components/ui/page-container/index.scss +++ b/ui/app/components/ui/page-container/index.scss @@ -73,9 +73,10 @@ a, a:hover { + @include H7; + text-decoration: none; cursor: pointer; - font-size: 0.75rem; text-transform: uppercase; color: #2f9ae0; } @@ -91,24 +92,24 @@ } &__back-button { + @include Paragraph; + color: #2f9ae0; - font-size: 1rem; cursor: pointer; - font-weight: 400; } &__title { + @include H2; + color: $black; - font-size: 2rem; font-weight: 500; - line-height: 2rem; margin-right: 1.5rem; } &__subtitle { + @include H6; + padding-top: 0.5rem; - line-height: initial; - font-size: 0.9rem; color: $gray; } @@ -118,9 +119,10 @@ } &__tab { + @include Paragraph; + min-width: 5rem; color: $dusty-gray; - font-size: 1rem; border-bottom: none; margin-right: 16px; diff --git a/ui/app/components/ui/page-container/page-container-content.component.js b/ui/app/components/ui/page-container/page-container-content.component.js index 476e25e5c..c833c76bf 100644 --- a/ui/app/components/ui/page-container/page-container-content.component.js +++ b/ui/app/components/ui/page-container/page-container-content.component.js @@ -2,17 +2,11 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' export default class PageContainerContent extends Component { - static propTypes = { children: PropTypes.node.isRequired, } - render () { - return ( -
    - {this.props.children} -
    - ) + render() { + return
    {this.props.children}
    } - } diff --git a/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js index 3f59498c6..9278f4e25 100644 --- a/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js +++ b/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js @@ -4,7 +4,6 @@ import classnames from 'classnames' import Button from '../../button' export default class PageContainerFooter extends Component { - static propTypes = { children: PropTypes.node, onCancel: PropTypes.func, @@ -24,7 +23,7 @@ export default class PageContainerFooter extends Component { t: PropTypes.func, } - render () { + render() { const { children, onCancel, @@ -42,40 +41,41 @@ export default class PageContainerFooter extends Component { return (
    -
    {!hideCancel && ( )}
    {children && ( -
    - {children} -
    +
    {children}
    )} -
    ) } - } diff --git a/ui/app/components/ui/page-container/page-container-footer/tests/page-container-footer.component.test.js b/ui/app/components/ui/page-container/page-container-footer/tests/page-container-footer.component.test.js index 8a5cf1114..ff41417a3 100644 --- a/ui/app/components/ui/page-container/page-container-footer/tests/page-container-footer.component.test.js +++ b/ui/app/components/ui/page-container/page-container-footer/tests/page-container-footer.component.test.js @@ -11,7 +11,7 @@ describe('Page Footer', function () { const onSubmit = sinon.spy() beforeEach(function () { - wrapper = shallow(( + wrapper = shallow( - )) + />, + ) }) it('renders page container footer', function () { @@ -43,34 +43,46 @@ describe('Page Footer', function () { }) describe('Cancel Button', function () { - it('has button type of default', function () { - assert.equal(wrapper.find('.page-container__footer-button').first().prop('type'), 'default') + assert.equal( + wrapper.find('.page-container__footer-button').first().prop('type'), + 'default', + ) }) it('has children text of Cancel', function () { - assert.equal(wrapper.find('.page-container__footer-button').first().prop('children'), 'Cancel') + assert.equal( + wrapper.find('.page-container__footer-button').first().prop('children'), + 'Cancel', + ) }) it('should call cancel when click is simulated', function () { wrapper.find('.page-container__footer-button').first().prop('onClick')() assert.equal(onCancel.callCount, 1) }) - }) describe('Submit Button', function () { - it('assigns button type based on props', function () { - assert.equal(wrapper.find('.page-container__footer-button').last().prop('type'), 'Test Type') + assert.equal( + wrapper.find('.page-container__footer-button').last().prop('type'), + 'Test Type', + ) }) it('has disabled prop', function () { - assert.equal(wrapper.find('.page-container__footer-button').last().prop('disabled'), false) + assert.equal( + wrapper.find('.page-container__footer-button').last().prop('disabled'), + false, + ) }) it('has children text when submitText prop exists', function () { - assert.equal(wrapper.find('.page-container__footer-button').last().prop('children'), 'Submit') + assert.equal( + wrapper.find('.page-container__footer-button').last().prop('children'), + 'Submit', + ) }) it('should call submit when click is simulated', function () { diff --git a/ui/app/components/ui/page-container/page-container-header/page-container-header.component.js b/ui/app/components/ui/page-container/page-container-header/page-container-header.component.js index 5d5a88a9e..0f715a027 100644 --- a/ui/app/components/ui/page-container/page-container-header/page-container-header.component.js +++ b/ui/app/components/ui/page-container/page-container-header/page-container-header.component.js @@ -17,34 +17,44 @@ export default class PageContainerHeader extends Component { className: PropTypes.string, } - renderTabs () { + renderTabs() { const { tabs } = this.props - return tabs && ( -
      - { tabs } -
    + return tabs &&
      {tabs}
    + } + + renderHeaderRow() { + const { + showBackButton, + onBackButtonClick, + backButtonStyles, + backButtonString, + } = this.props + + return ( + showBackButton && ( +
    + + {backButtonString || 'Back'} + +
    + ) ) } - renderHeaderRow () { - const { showBackButton, onBackButtonClick, backButtonStyles, backButtonString } = this.props - - return showBackButton && ( -
    - - { backButtonString || 'Back' } - -
    - ) - } - - render () { - const { title, subtitle, onClose, tabs, headerCloseText, className } = this.props + render() { + const { + title, + subtitle, + onClose, + tabs, + headerCloseText, + className, + } = this.props return (
    + {this.renderHeaderRow()} - { this.renderHeaderRow() } + {title &&
    {title}
    } - { - title && ( -
    - { title } -
    + {subtitle &&
    {subtitle}
    } + + {onClose && headerCloseText ? ( + + ) : ( + onClose && ( +
    onClose()} + /> ) - } + )} - { - subtitle && ( -
    - { subtitle } -
    - ) - } - - { - onClose && headerCloseText - ? - : onClose && ( -
    onClose()} - /> - ) - } - - { this.renderTabs() } + {this.renderTabs()}
    ) } - } diff --git a/ui/app/components/ui/page-container/page-container-header/tests/page-container-header.component.test.js b/ui/app/components/ui/page-container/page-container-header/tests/page-container-header.component.test.js index b544e6f4c..ab035c641 100644 --- a/ui/app/components/ui/page-container/page-container-header/tests/page-container-header.component.test.js +++ b/ui/app/components/ui/page-container/page-container-header/tests/page-container-header.component.test.js @@ -12,7 +12,7 @@ describe('Page Container Header', function () { onBackButtonClick = sinon.spy() onClose = sinon.spy() - wrapper = shallow(( + wrapper = shallow( - )) + />, + ) }) describe('Render Header Row', function () { - it('renders back button', function () { assert.equal(wrapper.find('.page-container__back-button').length, 1) assert.equal(wrapper.find('.page-container__back-button').text(), 'Back') }) it('ensures style prop', function () { - assert.equal(wrapper.find('.page-container__back-button').props().style, style) + assert.equal( + wrapper.find('.page-container__back-button').props().style, + style, + ) }) it('should call back button when click is simulated', function () { @@ -80,5 +82,4 @@ describe('Page Container Header', function () { assert.equal(onClose.callCount, 1) }) }) - }) diff --git a/ui/app/components/ui/page-container/page-container.component.js b/ui/app/components/ui/page-container/page-container.component.js index fd3b2eede..eb1280b3b 100644 --- a/ui/app/components/ui/page-container/page-container.component.js +++ b/ui/app/components/ui/page-container/page-container.component.js @@ -33,11 +33,11 @@ export default class PageContainer extends PureComponent { activeTabIndex: this.props.defaultActiveTabIndex || 0, } - handleTabClick (activeTabIndex) { + handleTabClick(activeTabIndex) { this.setState({ activeTabIndex }) } - renderTabs () { + renderTabs() { const { tabsComponent } = this.props if (!tabsComponent) { @@ -46,18 +46,25 @@ export default class PageContainer extends PureComponent { const numberOfTabs = React.Children.count(tabsComponent.props.children) - return React.Children.map(tabsComponent.props.children, (child, tabIndex) => { - return child && React.cloneElement(child, { - onClick: (index) => this.handleTabClick(index), - tabIndex, - isActive: numberOfTabs > 1 && tabIndex === this.state.activeTabIndex, - key: tabIndex, - className: 'page-container__tab', - }) - }) + return React.Children.map( + tabsComponent.props.children, + (child, tabIndex) => { + return ( + child && + React.cloneElement(child, { + onClick: (index) => this.handleTabClick(index), + tabIndex, + isActive: + numberOfTabs > 1 && tabIndex === this.state.activeTabIndex, + key: tabIndex, + className: 'page-container__tab', + }) + ) + }, + ) } - renderActiveTabContent () { + renderActiveTabContent() { const { tabsComponent } = this.props let { children } = tabsComponent.props children = children.filter((child) => child) @@ -68,7 +75,7 @@ export default class PageContainer extends PureComponent { : children.props.children } - renderContent () { + renderContent() { const { contentComponent, tabsComponent } = this.props if (contentComponent) { @@ -79,7 +86,7 @@ export default class PageContainer extends PureComponent { return null } - render () { + render() { const { title, subtitle, @@ -111,9 +118,7 @@ export default class PageContainer extends PureComponent { headerCloseText={headerCloseText} />
    -
    - { this.renderContent() } -
    +
    {this.renderContent()}
    - { CustomBackground - ? - :
    - } + {CustomBackground ? ( + + ) : ( +
    + )}
    - { showArrow ?
    : null} + {showArrow ?
    : null}

    - { - onBack - ? ( -

    - { subtitle ?

    {subtitle}

    : null } + {subtitle ? ( +

    {subtitle}

    + ) : null}
    - { - children - ? ( -
    - {children} -
    - ) - : null - } - { - footer - ? ( -
    - {footer} -
    - ) - : null - } + {children ? ( +
    + {children} +
    + ) : null} + {footer ? ( +
    + {footer} +
    + ) : null}
    ) @@ -94,7 +85,7 @@ export default class PopoverPortal extends PureComponent { instanceNode = document.createElement('div') - componentDidMount () { + componentDidMount() { if (!this.rootNode) { return } @@ -102,7 +93,7 @@ export default class PopoverPortal extends PureComponent { this.rootNode.appendChild(this.instanceNode) } - componentWillUnmount () { + componentWillUnmount() { if (!this.rootNode) { return } @@ -110,7 +101,7 @@ export default class PopoverPortal extends PureComponent { this.rootNode.removeChild(this.instanceNode) } - render () { + render() { const children = return this.rootNode ? ReactDOM.createPortal(children, this.instanceNode) diff --git a/ui/app/components/ui/popover/popover.stories.js b/ui/app/components/ui/popover/popover.stories.js index 5108e1f96..f81aa69e2 100644 --- a/ui/app/components/ui/popover/popover.stories.js +++ b/ui/app/components/ui/popover/popover.stories.js @@ -27,8 +27,37 @@ export const approve = () => ( footer={} >
    -

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Semper eget duis at tellus at urna condimentum. Posuere urna nec tincidunt praesent semper. Arcu dictum varius duis at. A lacus vestibulum sed arcu. Orci porta non pulvinar neque laoreet suspendisse interdum. Pretium fusce id velit ut. Ut consequat semper viverra nam libero justo laoreet sit. In ante metus dictum at tempor commodo ullamcorper a lacus. Posuere morbi leo urna molestie at elementum eu facilisis sed. Libero enim sed faucibus turpis in eu mi bibendum neque. Amet massa vitae tortor condimentum lacinia quis. Pretium viverra suspendisse potenti nullam ac. Pellentesque elit eget gravida cum sociis natoque penatibus. Proin libero nunc consequat interdum varius sit amet. Est ultricies integer quis auctor elit sed vulputate. Ornare arcu odio ut sem nulla pharetra. Eget nullam non nisi est sit. Leo vel fringilla est ullamcorper eget nulla.

    -

    Mattis pellentesque id nibh tortor id. Commodo sed egestas egestas fringilla phasellus. Semper eget duis at tellus at urna. Tristique nulla aliquet enim tortor at auctor urna nunc. Pellentesque habitant morbi tristique senectus et netus et. Turpis egestas sed tempus urna et pharetra pharetra massa massa. Mi eget mauris pharetra et ultrices neque ornare aenean. Facilisis volutpat est velit egestas dui id ornare arcu odio. Lacus sed turpis tincidunt id aliquet risus feugiat in. Cras tincidunt lobortis feugiat vivamus. Blandit libero volutpat sed cras ornare arcu. Facilisi morbi tempus iaculis urna id volutpat. Risus viverra adipiscing at in tellus. Leo vel orci porta non pulvinar neque. Malesuada fames ac turpis egestas integer. Euismod nisi porta lorem mollis aliquam.

    +

    + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Semper + eget duis at tellus at urna condimentum. Posuere urna nec tincidunt + praesent semper. Arcu dictum varius duis at. A lacus vestibulum sed + arcu. Orci porta non pulvinar neque laoreet suspendisse interdum. + Pretium fusce id velit ut. Ut consequat semper viverra nam libero + justo laoreet sit. In ante metus dictum at tempor commodo ullamcorper + a lacus. Posuere morbi leo urna molestie at elementum eu facilisis + sed. Libero enim sed faucibus turpis in eu mi bibendum neque. Amet + massa vitae tortor condimentum lacinia quis. Pretium viverra + suspendisse potenti nullam ac. Pellentesque elit eget gravida cum + sociis natoque penatibus. Proin libero nunc consequat interdum varius + sit amet. Est ultricies integer quis auctor elit sed vulputate. Ornare + arcu odio ut sem nulla pharetra. Eget nullam non nisi est sit. Leo vel + fringilla est ullamcorper eget nulla. +

    +

    + Mattis pellentesque id nibh tortor id. Commodo sed egestas egestas + fringilla phasellus. Semper eget duis at tellus at urna. Tristique + nulla aliquet enim tortor at auctor urna nunc. Pellentesque habitant + morbi tristique senectus et netus et. Turpis egestas sed tempus urna + et pharetra pharetra massa massa. Mi eget mauris pharetra et ultrices + neque ornare aenean. Facilisis volutpat est velit egestas dui id + ornare arcu odio. Lacus sed turpis tincidunt id aliquet risus feugiat + in. Cras tincidunt lobortis feugiat vivamus. Blandit libero volutpat + sed cras ornare arcu. Facilisi morbi tempus iaculis urna id volutpat. + Risus viverra adipiscing at in tellus. Leo vel orci porta non pulvinar + neque. Malesuada fames ac turpis egestas integer. Euismod nisi porta + lorem mollis aliquam. +

    diff --git a/ui/app/components/ui/pulse-loader/pulse-loader.js b/ui/app/components/ui/pulse-loader/pulse-loader.js index b1cc13731..afc8af89d 100644 --- a/ui/app/components/ui/pulse-loader/pulse-loader.js +++ b/ui/app/components/ui/pulse-loader/pulse-loader.js @@ -1,6 +1,6 @@ import React from 'react' -export default function PulseLoader () { +export default function PulseLoader() { return (
    diff --git a/ui/app/components/ui/pulse-loader/pulse-loader.stories.js b/ui/app/components/ui/pulse-loader/pulse-loader.stories.js index 4823ac604..6adb4f441 100644 --- a/ui/app/components/ui/pulse-loader/pulse-loader.stories.js +++ b/ui/app/components/ui/pulse-loader/pulse-loader.stories.js @@ -5,4 +5,4 @@ export default { title: 'PulseLoader', } -export const common = () => () +export const common = () => diff --git a/ui/app/components/ui/qr-code/index.scss b/ui/app/components/ui/qr-code/index.scss index 86bd5af43..521075f64 100644 --- a/ui/app/components/ui/qr-code/index.scss +++ b/ui/app/components/ui/qr-code/index.scss @@ -5,13 +5,15 @@ align-items: center; &__message-container > div:first-child { + @include Paragraph; + margin-top: 18px; - font-size: 15px; color: #4d4d4d; } &__message { - font-size: 12px; + @include H7; + color: #f7861c; } diff --git a/ui/app/components/ui/qr-code/qr-code.js b/ui/app/components/ui/qr-code/qr-code.js index c00885341..b8990186e 100644 --- a/ui/app/components/ui/qr-code/qr-code.js +++ b/ui/app/components/ui/qr-code/qr-code.js @@ -8,49 +8,39 @@ import { checksumAddress } from '../../../helpers/utils/util' export default connect(mapStateToProps)(QrCodeView) -function mapStateToProps (state) { +function mapStateToProps(state) { + const { buyView, warning } = state.appState return { // Qr code is not fetched from state. 'message' and 'data' props are passed instead. - buyView: state.appState.buyView, - warning: state.appState.warning, + buyView, + warning, } } -function QrCodeView (props) { - const { message, data } = props.Qr - const address = `${isHexPrefixed(data) ? 'ethereum:' : ''}${checksumAddress(data)}` +function QrCodeView(props) { + const { Qr, warning } = props + const { message, data } = Qr + const address = `${isHexPrefixed(data) ? 'ethereum:' : ''}${checksumAddress( + data, + )}` const qrImage = qrCode(4, 'M') qrImage.addData(address) qrImage.make() return (
    - { - Array.isArray(message) - ? ( -
    - {props.Qr.message.map((msg, index) => ( -
    - {msg} -
    - ))} + {Array.isArray(message) ? ( +
    + {message.map((msg, index) => ( +
    + {msg}
    - ) - : message && ( -
    - {message} -
    - ) - } - { - props.warning - ? (props.warning && ( - - {props.warning} - - )) - : null - } + ))} +
    + ) : ( + message &&
    {message}
    + )} + {warning && {warning}}
    + diff --git a/ui/app/components/ui/sender-to-recipient/index.scss b/ui/app/components/ui/sender-to-recipient/index.scss index 3c0f7af2d..5c2c3cf5e 100644 --- a/ui/app/components/ui/sender-to-recipient/index.scss +++ b/ui/app/components/ui/sender-to-recipient/index.scss @@ -69,11 +69,12 @@ } &__name { + @include H6; + padding-left: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - font-size: 0.875rem; [dir='rtl'] & { /*rtl:ignore*/ @@ -117,10 +118,11 @@ } &__name { + @include H9; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - font-size: 0.5rem; [dir='rtl'] & { /*rtl:ignore*/ @@ -171,10 +173,11 @@ } &__name { + @include H8; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - font-size: 0.6875rem; [dir='rtl'] & { /*rtl:ignore*/ diff --git a/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js index de7e54745..242ccb3fd 100644 --- a/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js +++ b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js @@ -7,7 +7,11 @@ import Identicon from '../identicon' import { checksumAddress, shortenAddress } from '../../../helpers/utils/util' import AccountMismatchWarning from '../account-mismatch-warning/account-mismatch-warning.component' import { useI18nContext } from '../../../hooks/useI18nContext' -import { DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT } from './sender-to-recipient.constants' +import { + DEFAULT_VARIANT, + CARDS_VARIANT, + FLAT_VARIANT, +} from './sender-to-recipient.constants' const variantHash = { [DEFAULT_VARIANT]: 'sender-to-recipient--default', @@ -15,7 +19,7 @@ const variantHash = { [FLAT_VARIANT]: 'sender-to-recipient--flat', } -function SenderAddress ({ +function SenderAddress({ addressOnly, checksummedSenderAddress, senderName, @@ -27,18 +31,21 @@ function SenderAddress ({ const [addressCopied, setAddressCopied] = useState(false) let tooltipHtml =

    {t('copiedExclamation')}

    if (!addressCopied) { - tooltipHtml = addressOnly - ?

    {t('copyAddress')}

    - : ( -

    - {shortenAddress(checksummedSenderAddress)}
    - {t('copyAddress')} -

    - ) + tooltipHtml = addressOnly ? ( +

    {t('copyAddress')}

    + ) : ( +

    + {shortenAddress(checksummedSenderAddress)} +
    + {t('copyAddress')} +

    + ) } return (
    { setAddressCopied(true) copyToClipboard(checksummedSenderAddress) @@ -49,10 +56,7 @@ function SenderAddress ({ > {!addressOnly && (
    - +
    )} setAddressCopied(false)} >
    - { - addressOnly - ? {`${t('from')}: ${senderName || checksummedSenderAddress}`} - : senderName - } + {addressOnly ? ( + + {`${t('from')}: ${senderName || checksummedSenderAddress}`} + + ) : ( + senderName + )}
    - {warnUserOnAccountMismatch && } + {warnUserOnAccountMismatch && ( + + )}
    ) } @@ -84,7 +92,7 @@ SenderAddress.propTypes = { warnUserOnAccountMismatch: PropTypes.bool, } -function RecipientWithAddress ({ +function RecipientWithAddress({ checksummedRecipientAddress, assetImage, onRecipientClick, @@ -103,7 +111,8 @@ function RecipientWithAddress ({ } else { tooltipHtml = (

    - {shortenAddress(checksummedRecipientAddress)}
    + {shortenAddress(checksummedRecipientAddress)} +
    {t('copyAddress')}

    ) @@ -138,12 +147,13 @@ function RecipientWithAddress ({ onHidden={() => setAddressCopied(false)} >
    - { addressOnly ? `${t('to')}: ` : '' } - { - addressOnly - ? (recipientNickname || recipientEns || checksummedRecipientAddress) - : (recipientNickname || recipientEns || recipientName || t('newContract')) - } + {addressOnly ? `${t('to')}: ` : ''} + {addressOnly + ? recipientNickname || recipientEns || checksummedRecipientAddress + : recipientNickname || + recipientEns || + recipientName || + t('newContract')}
    @@ -160,33 +170,25 @@ RecipientWithAddress.propTypes = { onRecipientClick: PropTypes.func, } -function Arrow ({ variant }) { - return variant === DEFAULT_VARIANT - ? ( -
    -
    - -
    +function Arrow({ variant }) { + return variant === DEFAULT_VARIANT ? ( +
    +
    +
    - ) : ( -
    - -
    - ) +
    + ) : ( +
    + +
    + ) } Arrow.propTypes = { variant: PropTypes.oneOf([DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT]), } -export default function SenderToRecipient ({ +export default function SenderToRecipient({ senderAddress, addressOnly, assetImage, @@ -215,27 +217,22 @@ export default function SenderToRecipient ({ warnUserOnAccountMismatch={warnUserOnAccountMismatch} /> - {recipientAddress - ? ( - - ) - : ( -
    - { !addressOnly && } -
    - {t('newContract') } -
    -
    - ) - } + {recipientAddress ? ( + + ) : ( +
    + {!addressOnly && } +
    {t('newContract')}
    +
    + )}
    ) } diff --git a/ui/app/components/ui/site-icon/site-icon.js b/ui/app/components/ui/site-icon/site-icon.js index 2ddc9f344..e0a690c6e 100644 --- a/ui/app/components/ui/site-icon/site-icon.js +++ b/ui/app/components/ui/site-icon/site-icon.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import IconBorder from '../icon-border' import IconWithFallback from '../icon-with-fallback' -export default function SiteIcon ({ icon, name, size }) { +export default function SiteIcon({ icon, name, size }) { const iconSize = Math.floor(size * 0.75) return ( diff --git a/ui/app/components/ui/snackbar/index.scss b/ui/app/components/ui/snackbar/index.scss index d2aac4879..d3f185e7b 100644 --- a/ui/app/components/ui/snackbar/index.scss +++ b/ui/app/components/ui/snackbar/index.scss @@ -1,6 +1,7 @@ .snackbar { + @include H7; + padding: 0.75rem 1rem; - font-size: 0.75rem; color: $Blue-600; min-width: 360px; width: fit-content; diff --git a/ui/app/components/ui/snackbar/snackbar.component.js b/ui/app/components/ui/snackbar/snackbar.component.js index 48b39c59f..c9da15dba 100644 --- a/ui/app/components/ui/snackbar/snackbar.component.js +++ b/ui/app/components/ui/snackbar/snackbar.component.js @@ -3,11 +3,7 @@ import PropTypes from 'prop-types' import classnames from 'classnames' const Snackbar = ({ className = '', content }) => { - return ( -
    - { content } -
    - ) + return
    {content}
    } Snackbar.propTypes = { diff --git a/ui/app/components/ui/spinner/spinner.component.js b/ui/app/components/ui/spinner/spinner.component.js index 4ad2a21fe..4e002bfb2 100644 --- a/ui/app/components/ui/spinner/spinner.component.js +++ b/ui/app/components/ui/spinner/spinner.component.js @@ -4,65 +4,146 @@ import PropTypes from 'prop-types' const Spinner = ({ className = '', color = '#000000' }) => { return (
    - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/ui/app/components/ui/tabs/tab/index.scss b/ui/app/components/ui/tabs/tab/index.scss index b0fb0d30e..0753ba9dd 100644 --- a/ui/app/components/ui/tabs/tab/index.scss +++ b/ui/app/components/ui/tabs/tab/index.scss @@ -1,8 +1,15 @@ .tab { - cursor: pointer; - padding: 8px; - min-width: 50px; - text-align: center; + button { + cursor: pointer; + padding: 8px; + min-width: 50px; + text-align: center; + display: block; + width: 100%; + background-color: unset; + font-size: unset; + color: unset; + } &--active { color: $black; diff --git a/ui/app/components/ui/tabs/tab/tab.component.js b/ui/app/components/ui/tabs/tab/tab.component.js index 55f1533b6..e88deaca4 100644 --- a/ui/app/components/ui/tabs/tab/tab.component.js +++ b/ui/app/components/ui/tabs/tab/tab.component.js @@ -15,21 +15,17 @@ const Tab = (props) => { return (
  • { event.preventDefault() onClick(tabIndex) }} > - { name } +
  • ) } diff --git a/ui/app/components/ui/tabs/tabs.component.js b/ui/app/components/ui/tabs/tabs.component.js index c524759fc..2f1f7583e 100644 --- a/ui/app/components/ui/tabs/tabs.component.js +++ b/ui/app/components/ui/tabs/tabs.component.js @@ -17,38 +17,47 @@ export default class Tabs extends Component { } state = { - activeTabIndex: Math.max(this._findChildByName(this.props.defaultActiveTabName), 0), + activeTabIndex: Math.max( + this._findChildByName(this.props.defaultActiveTabName), + 0, + ), } - handleTabClick (tabIndex, tabName) { + handleTabClick(tabIndex, tabName) { const { onTabClick } = this.props const { activeTabIndex } = this.state if (tabIndex !== activeTabIndex) { - this.setState({ - activeTabIndex: tabIndex, - }, () => { - if (onTabClick) { - onTabClick(tabName) - } - }) + this.setState( + { + activeTabIndex: tabIndex, + }, + () => { + if (onTabClick) { + onTabClick(tabName) + } + }, + ) } } - renderTabs () { + renderTabs() { const numberOfTabs = React.Children.count(this.props.children) return React.Children.map(this.props.children, (child, index) => { const tabName = child?.props.name - return child && React.cloneElement(child, { - onClick: (idx) => this.handleTabClick(idx, tabName), - tabIndex: index, - isActive: numberOfTabs > 1 && index === this.state.activeTabIndex, - }) + return ( + child && + React.cloneElement(child, { + onClick: (idx) => this.handleTabClick(idx, tabName), + tabIndex: index, + isActive: numberOfTabs > 1 && index === this.state.activeTabIndex, + }) + ) }) } - renderActiveTabContent () { + renderActiveTabContent() { const { children } = this.props const { activeTabIndex } = this.state @@ -64,16 +73,14 @@ export default class Tabs extends Component { : children.props.children } - render () { + render() { const { tabsClassName } = this.props return (
      - { this.renderTabs() } + {this.renderTabs()}
    -
    - { this.renderActiveTabContent() } -
    +
    {this.renderActiveTabContent()}
    ) } @@ -84,7 +91,9 @@ export default class Tabs extends Component { * @returns {number} the index of the child with the given name * @private */ - _findChildByName (name) { - return React.Children.toArray(this.props.children).findIndex((c) => c?.props.name === name) + _findChildByName(name) { + return React.Children.toArray(this.props.children).findIndex( + (c) => c?.props.name === name, + ) } } diff --git a/ui/app/components/ui/tabs/tabs.stories.js b/ui/app/components/ui/tabs/tabs.stories.js index 2cf40736c..f69d38103 100644 --- a/ui/app/components/ui/tabs/tabs.stories.js +++ b/ui/app/components/ui/tabs/tabs.stories.js @@ -7,45 +7,26 @@ export default { title: 'Tabs', } -function renderTab (id) { +function renderTab(id) { return ( - + {text(`Tab ${id} Contents`, `Contents of Tab ${id}`)} ) } export const twoTabs = () => { - return ( - - { - ['A', 'B'] - .map(renderTab) - } - - ) + return {['A', 'B'].map(renderTab)} } export const manyTabs = () => { - return ( - - { - ['A', 'B', 'C', 'D', 'E'] - .map(renderTab) - } - - ) + return {['A', 'B', 'C', 'D', 'E'].map(renderTab)} } export const singleTab = () => { return ( - + {text('Contents', 'Contents of tab')} diff --git a/ui/app/components/ui/text-field/text-field.component.js b/ui/app/components/ui/text-field/text-field.component.js index 9e80e3bc8..88d51d6e6 100644 --- a/ui/app/components/ui/text-field/text-field.component.js +++ b/ui/app/components/ui/text-field/text-field.component.js @@ -61,7 +61,7 @@ const styles = { 'label + &': { marginTop: '9px', }, - border: '2px solid #BBC0C5', + border: '1px solid #BBC0C5', height: '48px', borderRadius: '6px', padding: '0 16px', @@ -109,7 +109,12 @@ const getMaterialThemeInputProps = ({ const getMaterialWhitePaddedThemeInputProps = ({ dir, - classes: { materialWhitePaddedRoot, materialWhitePaddedFocused, materialWhitePaddedInput, materialWhitePaddedUnderline }, + classes: { + materialWhitePaddedRoot, + materialWhitePaddedFocused, + materialWhitePaddedInput, + materialWhitePaddedUnderline, + }, startAdornment, }) => ({ InputProps: { @@ -128,7 +133,16 @@ const getMaterialWhitePaddedThemeInputProps = ({ const getBorderedThemeInputProps = ({ dir, - classes: { formLabel, formLabelFocused, materialError, largeInputLabel, inputLabel, inputRoot, input, inputFocused }, + classes: { + formLabel, + formLabelFocused, + materialError, + largeInputLabel, + inputLabel, + inputRoot, + input, + inputFocused, + }, largeLabel, startAdornment, }) => ({ @@ -156,8 +170,8 @@ const getBorderedThemeInputProps = ({ }) const themeToInputProps = { - 'material': getMaterialThemeInputProps, - 'bordered': getBorderedThemeInputProps, + material: getMaterialThemeInputProps, + bordered: getBorderedThemeInputProps, 'material-white-padded': getMaterialWhitePaddedThemeInputProps, } @@ -170,7 +184,12 @@ const TextField = ({ dir, ...textFieldProps }) => { - const inputProps = themeToInputProps[theme]({ classes, startAdornment, largeLabel, dir }) + const inputProps = themeToInputProps[theme]({ + classes, + startAdornment, + largeLabel, + dir, + }) return ( ( - -) +export const text = () => -export const password = () => ( - -) +export const password = () => export const error = () => ( - + ) export const mascaraText = () => ( - + ) export const materialText = () => ( - + ) export const materialPassword = () => ( - + ) export const materialError = () => ( - + ) diff --git a/ui/app/components/ui/toggle-button/index.scss b/ui/app/components/ui/toggle-button/index.scss index 09827888c..e37833c1c 100644 --- a/ui/app/components/ui/toggle-button/index.scss +++ b/ui/app/components/ui/toggle-button/index.scss @@ -3,10 +3,8 @@ $self: &; &__status { - font-style: normal; - font-weight: normal; - font-size: 16px; - line-height: 23px; + @include Paragraph; + display: flex; align-items: center; text-transform: uppercase; diff --git a/ui/app/components/ui/token-balance/token-balance.js b/ui/app/components/ui/token-balance/token-balance.js index e439c2d36..dcb7aaa99 100644 --- a/ui/app/components/ui/token-balance/token-balance.js +++ b/ui/app/components/ui/token-balance/token-balance.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import CurrencyDisplay from '../currency-display' import { useTokenTracker } from '../../../hooks/useTokenTracker' -export default function TokenBalance ({ className, token }) { +export default function TokenBalance({ className, token }) { const { tokensWithBalances } = useTokenTracker([token]) const { string, symbol } = tokensWithBalances[0] || {} diff --git a/ui/app/components/ui/token-currency-display/token-currency-display.component.js b/ui/app/components/ui/token-currency-display/token-currency-display.component.js index b8e46614a..69a314e47 100644 --- a/ui/app/components/ui/token-currency-display/token-currency-display.component.js +++ b/ui/app/components/ui/token-currency-display/token-currency-display.component.js @@ -3,7 +3,12 @@ import PropTypes from 'prop-types' import CurrencyDisplay from '../currency-display' import { useTokenDisplayValue } from '../../../hooks/useTokenDisplayValue' -export default function TokenCurrencyDisplay ({ className, transactionData, token, prefix }) { +export default function TokenCurrencyDisplay({ + className, + transactionData, + token, + prefix, +}) { const displayValue = useTokenDisplayValue(transactionData, token) return ( diff --git a/ui/app/components/ui/token-input/tests/token-input.component.test.js b/ui/app/components/ui/token-input/tests/token-input.component.test.js index a7cda504f..52668a1cc 100644 --- a/ui/app/components/ui/token-input/tests/token-input.component.test.js +++ b/ui/app/components/ui/token-input/tests/token-input.component.test.js @@ -43,8 +43,14 @@ describe('TokenInput Component', function () { assert.ok(wrapper) assert.equal(wrapper.find('.unit-input__suffix').length, 1) assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC') - assert.equal(wrapper.find('.currency-input__conversion-component').length, 1) - assert.equal(wrapper.find('.currency-input__conversion-component').text(), 'translate noConversionRateAvailable') + assert.equal( + wrapper.find('.currency-input__conversion-component').length, + 1, + ) + assert.equal( + wrapper.find('.currency-input__conversion-component').text(), + 'translate noConversionRateAvailable', + ) }) it('should render properly with tokenExchangeRates', function () { @@ -145,7 +151,10 @@ describe('TokenInput Component', function () { assert.equal(wrapper.find('.unit-input__suffix').length, 1) assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC') assert.equal(wrapper.find('.unit-input__input').props().value, '1') - assert.equal(wrapper.find('.currency-display-component').text(), '$462.12USD') + assert.equal( + wrapper.find('.currency-display-component').text(), + '$462.12USD', + ) }) it('should render properly with a token value for fiat, but hideConversion is true', function () { @@ -186,7 +195,10 @@ describe('TokenInput Component', function () { assert.equal(wrapper.find('.unit-input__suffix').length, 1) assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC') assert.equal(wrapper.find('.unit-input__input').props().value, '1') - assert.equal(wrapper.find('.currency-input__conversion-component').text(), 'translate noConversionRateAvailable') + assert.equal( + wrapper.find('.currency-input__conversion-component').text(), + 'translate noConversionRateAvailable', + ) }) }) @@ -270,14 +282,20 @@ describe('TokenInput Component', function () { const tokenInputInstance = wrapper.find(TokenInput).at(0).instance() assert.equal(tokenInputInstance.state.decimalValue, 0) assert.equal(tokenInputInstance.state.hexValue, undefined) - assert.equal(wrapper.find('.currency-display-component').text(), '$0.00USD') + assert.equal( + wrapper.find('.currency-display-component').text(), + '$0.00USD', + ) const input = wrapper.find('input') assert.equal(input.props().value, 0) input.simulate('change', { target: { value: 1 } }) assert.equal(handleChangeSpy.callCount, 1) assert.ok(handleChangeSpy.calledWith('2710')) - assert.equal(wrapper.find('.currency-display-component').text(), '$462.12USD') + assert.equal( + wrapper.find('.currency-display-component').text(), + '$462.12USD', + ) assert.equal(tokenInputInstance.state.decimalValue, 1) assert.equal(tokenInputInstance.state.hexValue, '2710') }) diff --git a/ui/app/components/ui/token-input/token-input.component.js b/ui/app/components/ui/token-input/token-input.component.js index 785601171..4d85ed4a5 100644 --- a/ui/app/components/ui/token-input/token-input.component.js +++ b/ui/app/components/ui/token-input/token-input.component.js @@ -1,11 +1,14 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' -import ethUtil from 'ethereumjs-util' import UnitInput from '../unit-input' import CurrencyDisplay from '../currency-display' import { getWeiHexFromDecimalValue } from '../../../helpers/utils/conversions.util' -import { conversionUtil, multiplyCurrencies } from '../../../helpers/utils/conversion-util' +import { + conversionUtil, + multiplyCurrencies, +} from '../../../helpers/utils/conversion-util' import { ETH } from '../../../helpers/constants/common' +import { addHexPrefix } from '../../../../../app/scripts/lib/util' /** * Component that allows user to enter token values as a number, and props receive a converted @@ -31,7 +34,7 @@ export default class TokenInput extends PureComponent { tokenExchangeRates: PropTypes.object, } - constructor (props) { + constructor(props) { super(props) const { value: hexValue } = props @@ -43,22 +46,25 @@ export default class TokenInput extends PureComponent { } } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { const { value: prevPropsHexValue } = prevProps const { value: propsHexValue } = this.props const { hexValue: stateHexValue } = this.state - if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) { + if ( + prevPropsHexValue !== propsHexValue && + propsHexValue !== stateHexValue + ) { const decimalValue = this.getValue(this.props) this.setState({ hexValue: propsHexValue, decimalValue }) } } - getValue (props) { + getValue(props) { const { value: hexValue, token: { decimals, symbol } = {} } = props const multiplier = Math.pow(10, Number(decimals || 0)) - const decimalValueString = conversionUtil(ethUtil.addHexPrefix(hexValue), { + const decimalValueString = conversionUtil(addHexPrefix(hexValue), { fromNumericBase: 'hex', toNumericBase: 'dec', toCurrency: symbol, @@ -73,14 +79,24 @@ export default class TokenInput extends PureComponent { const { token: { decimals } = {}, onChange } = this.props const multiplier = Math.pow(10, Number(decimals || 0)) - const hexValue = multiplyCurrencies(decimalValue || 0, multiplier, { toNumericBase: 'hex' }) + const hexValue = multiplyCurrencies(decimalValue || 0, multiplier, { + multiplicandBase: 10, + multiplierBase: 10, + toNumericBase: 'hex', + }) this.setState({ hexValue, decimalValue }) onChange(hexValue) } - renderConversionComponent () { - const { tokenExchangeRates, showFiat, currentCurrency, hideConversion, token } = this.props + renderConversionComponent() { + const { + tokenExchangeRates, + showFiat, + currentCurrency, + hideConversion, + token, + } = this.props const { decimalValue } = this.state const tokenExchangeRate = tokenExchangeRates?.[token.address] || 0 @@ -89,7 +105,7 @@ export default class TokenInput extends PureComponent { if (hideConversion) { return (
    - { this.context.t('noConversionRateAvailable') } + {this.context.t('noConversionRateAvailable')}
    ) } @@ -104,29 +120,28 @@ export default class TokenInput extends PureComponent { numberOfDecimals = 6 } - const decimalEthValue = (decimalValue * tokenExchangeRate) || 0 + const decimalEthValue = decimalValue * tokenExchangeRate || 0 const hexWeiValue = getWeiHexFromDecimalValue({ value: decimalEthValue, fromCurrency: ETH, fromDenomination: ETH, }) - return tokenExchangeRate - ? ( - - ) : ( -
    - { this.context.t('noConversionRateAvailable') } -
    - ) + return tokenExchangeRate ? ( + + ) : ( +
    + {this.context.t('noConversionRateAvailable')} +
    + ) } - render () { + render() { const { token, ...restProps } = this.props const { decimalValue } = this.state @@ -137,7 +152,7 @@ export default class TokenInput extends PureComponent { onChange={this.handleChange} value={decimalValue} > - { this.renderConversionComponent() } + {this.renderConversionComponent()} ) } diff --git a/ui/app/components/ui/token-input/token-input.container.js b/ui/app/components/ui/token-input/token-input.container.js index f20670d31..eac410000 100644 --- a/ui/app/components/ui/token-input/token-input.container.js +++ b/ui/app/components/ui/token-input/token-input.container.js @@ -8,14 +8,16 @@ import { import TokenInput from './token-input.component' const mapStateToProps = (state) => { - const { metamask: { currentCurrency } } = state + const { + metamask: { currentCurrency }, + } = state const { showFiatInTestnets } = getPreferences(state) const isMainnet = getIsMainnet(state) return { currentCurrency, tokenExchangeRates: getTokenExchangeRates(state), - hideConversion: (!isMainnet && !showFiatInTestnets), + hideConversion: !isMainnet && !showFiatInTestnets, } } diff --git a/ui/app/components/ui/tooltip/tooltip.js b/ui/app/components/ui/tooltip/tooltip.js index e6b5726d4..b9319f166 100644 --- a/ui/app/components/ui/tooltip/tooltip.js +++ b/ui/app/components/ui/tooltip/tooltip.js @@ -28,15 +28,8 @@ export default class Tooltip extends PureComponent { interactive: PropTypes.bool, offset: PropTypes.number, onHidden: PropTypes.func, - position: PropTypes.oneOf([ - 'top', - 'right', - 'bottom', - 'left', - ]), - size: PropTypes.oneOf([ - 'small', 'regular', 'big', - ]), + position: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), + size: PropTypes.oneOf(['small', 'regular', 'big']), title: PropTypes.string, trigger: PropTypes.any, wrapperClassName: PropTypes.string, @@ -44,7 +37,7 @@ export default class Tooltip extends PureComponent { theme: PropTypes.string, } - render () { + render() { const { arrow, children, @@ -64,11 +57,7 @@ export default class Tooltip extends PureComponent { } = this.props if (!title && !html) { - return ( -
    - {children} -
    - ) + return
    {children}
    } return ( diff --git a/ui/app/components/ui/unit-input/index.scss b/ui/app/components/ui/unit-input/index.scss index c86f487b5..35d54dc6a 100644 --- a/ui/app/components/ui/unit-input/index.scss +++ b/ui/app/components/ui/unit-input/index.scss @@ -1,4 +1,6 @@ .unit-input { + @include Paragraph; + display: flex; flex-flow: row nowrap; align-items: center; @@ -7,7 +9,6 @@ border-radius: 4px; background-color: #fff; color: #4d4d4d; - font-size: 16px; padding: 8px 10px; position: relative; @@ -34,12 +35,14 @@ } &__input { + @include Paragraph; + color: #4d4d4d; - font-size: 1rem; border: none; - max-width: 22ch; + max-width: 15ch; + overflow: hidden; + text-overflow: ellipsis; height: 16px; - line-height: 18px; &__disabled { background-color: rgb(222, 222, 222); @@ -48,14 +51,14 @@ &__input-container { display: flex; - align-items: flex-start; - padding-bottom: 4px; + align-items: center; + padding-bottom: 2px; } &__suffix { + @include Paragraph; + margin-left: 3px; - font-size: 1rem; - line-height: 1rem; } &--error { diff --git a/ui/app/components/ui/unit-input/tests/unit-input.component.test.js b/ui/app/components/ui/unit-input/tests/unit-input.component.test.js index 0b92fc5a0..c759f496f 100644 --- a/ui/app/components/ui/unit-input/tests/unit-input.component.test.js +++ b/ui/app/components/ui/unit-input/tests/unit-input.component.test.js @@ -7,20 +7,14 @@ import UnitInput from '../unit-input.component' describe('UnitInput Component', function () { describe('rendering', function () { it('should render properly without a suffix', function () { - const wrapper = shallow( - , - ) + const wrapper = shallow() assert.ok(wrapper) assert.equal(wrapper.find('.unit-input__suffix').length, 0) }) it('should render properly with a suffix', function () { - const wrapper = shallow( - , - ) + const wrapper = shallow() assert.ok(wrapper) assert.equal(wrapper.find('.unit-input__suffix').length, 1) @@ -30,9 +24,7 @@ describe('UnitInput Component', function () { it('should render properly with a child component', function () { const wrapper = shallow( -
    - TESTCOMPONENT -
    +
    TESTCOMPONENT
    , ) @@ -42,11 +34,7 @@ describe('UnitInput Component', function () { }) it('should render with an error class when props.error === true', function () { - const wrapper = shallow( - , - ) + const wrapper = shallow() assert.ok(wrapper) assert.equal(wrapper.find('.unit-input--error').length, 1) @@ -63,9 +51,7 @@ describe('UnitInput Component', function () { }) it('should focus the input on component click', function () { - const wrapper = mount( - , - ) + const wrapper = mount() assert.ok(wrapper) const handleFocusSpy = sinon.spy(wrapper.instance(), 'handleFocus') @@ -77,11 +63,7 @@ describe('UnitInput Component', function () { }) it('should call onChange on input changes with the value', function () { - const wrapper = mount( - , - ) + const wrapper = mount() assert.ok(wrapper) assert.equal(handleChangeSpy.callCount, 0) @@ -93,22 +75,14 @@ describe('UnitInput Component', function () { }) it('should set the component state value with props.value', function () { - const wrapper = mount( - , - ) + const wrapper = mount() assert.ok(wrapper) assert.equal(wrapper.state('value'), 123) }) it('should update the component state value with props.value', function () { - const wrapper = mount( - , - ) + const wrapper = mount() assert.ok(wrapper) assert.equal(handleChangeSpy.callCount, 0) diff --git a/ui/app/components/ui/unit-input/unit-input.component.js b/ui/app/components/ui/unit-input/unit-input.component.js index 6a9b76e7c..7e084a3bb 100644 --- a/ui/app/components/ui/unit-input/unit-input.component.js +++ b/ui/app/components/ui/unit-input/unit-input.component.js @@ -29,7 +29,7 @@ export default class UnitInput extends PureComponent { value: this.props.value, } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { const { value: prevPropsValue } = prevProps const { value: propsValue } = this.props const { value: stateValue } = this.state @@ -55,20 +55,31 @@ export default class UnitInput extends PureComponent { this.props.onChange(value) } - getInputWidth (value) { + getInputWidth(value) { const valueString = String(value) const valueLength = valueString.length || 1 const decimalPointDeficit = valueString.match(/\./u) ? -0.5 : 0 return `${valueLength + decimalPointDeficit + 0.5}ch` } - render () { - const { error, placeholder, suffix, actionComponent, children, maxModeOn } = this.props + render() { + const { + error, + placeholder, + suffix, + actionComponent, + children, + maxModeOn, + } = this.props const { value } = this.state return (
    @@ -76,7 +87,9 @@ export default class UnitInput extends PureComponent { - { - suffix && ( -
    - { suffix } -
    - ) - } + {suffix &&
    {suffix}
    }
    - { children } + {children}
    {actionComponent}
    diff --git a/ui/app/components/ui/url-icon/url-icon.js b/ui/app/components/ui/url-icon/url-icon.js index 3dfc1dbb4..6dcfcc06f 100644 --- a/ui/app/components/ui/url-icon/url-icon.js +++ b/ui/app/components/ui/url-icon/url-icon.js @@ -3,11 +3,7 @@ import PropTypes from 'prop-types' import classnames from 'classnames' import IconWithFallback from '../icon-with-fallback' -export default function UrlIcon ({ - url, - className, - name, -}) { +export default function UrlIcon({ url, className, name }) { return ( `[${key}]`) @@ -13,17 +16,12 @@ export const I18nProvider = (props) => { const en = useSelector(getEnLocaleMessages) const t = useMemo(() => { - return (key, ...args) => ( + return (key, ...args) => getMessage(currentLocale, current, key, ...args) || getMessage(currentLocale, en, key, ...args) - ) }, [currentLocale, current, en]) - return ( - - { props.children } - - ) + return {props.children} } I18nProvider.propTypes = { @@ -49,13 +47,13 @@ export class LegacyI18nProvider extends Component { t: PropTypes.func, } - getChildContext () { + getChildContext() { return { t: this.context, } } - render () { + render() { return this.props.children } } diff --git a/ui/app/contexts/metametrics.js b/ui/app/contexts/metametrics.js index eb0c8e476..835f0caea 100644 --- a/ui/app/contexts/metametrics.js +++ b/ui/app/contexts/metametrics.js @@ -1,4 +1,11 @@ -import React, { Component, createContext, useEffect, useCallback, useState, useMemo } from 'react' +import React, { + Component, + createContext, + useEffect, + useCallback, + useState, + useMemo, +} from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import { useHistory } from 'react-router-dom' @@ -12,20 +19,20 @@ import { getCurrentChainId, } from '../selectors/selectors' import { getSendToken } from '../selectors/send' -import { - txDataSelector, -} from '../selectors/confirm-transaction' +import { txDataSelector } from '../selectors/confirm-transaction' import { getEnvironmentType } from '../../../app/scripts/lib/util' import { getTrackMetaMetricsEvent } from '../../../shared/modules/metametrics' import { getCurrentLocale } from '../ducks/metamask/metamask' export const MetaMetricsContext = createContext(() => { captureException( - Error(`MetaMetrics context function was called from a react node that is not a descendant of a MetaMetrics context provider`), + Error( + `MetaMetrics context function was called from a react node that is not a descendant of a MetaMetrics context provider`, + ), ) }) -export function MetaMetricsProvider ({ children }) { +export function MetaMetricsProvider({ children }) { const txData = useSelector(txDataSelector) || {} const network = useSelector(getCurrentNetworkId) const environmentType = getEnvironmentType() @@ -35,23 +42,29 @@ export function MetaMetricsProvider ({ children }) { const accountType = useSelector(getAccountType) const confirmTransactionOrigin = txData.origin const metaMetricsId = useSelector((state) => state.metamask.metaMetricsId) - const participateInMetaMetrics = useSelector((state) => state.metamask.participateInMetaMetrics) - const metaMetricsSendCount = useSelector((state) => state.metamask.metaMetricsSendCount) + const participateInMetaMetrics = useSelector( + (state) => state.metamask.participateInMetaMetrics, + ) + const metaMetricsSendCount = useSelector( + (state) => state.metamask.metaMetricsSendCount, + ) const numberOfTokens = useSelector(getNumberOfTokens) const numberOfAccounts = useSelector(getNumberOfAccounts) const history = useHistory() const [state, setState] = useState(() => ({ - currentPath: (new URL(window.location.href)).pathname, + currentPath: new URL(window.location.href).pathname, previousPath: '', })) const { currentPath } = state useEffect(() => { - const unlisten = history.listen(() => setState((prevState) => ({ - currentPath: (new URL(window.location.href)).pathname, - previousPath: prevState.currentPath, - }))) + const unlisten = history.listen(() => + setState((prevState) => ({ + currentPath: new URL(window.location.href).pathname, + previousPath: prevState.currentPath, + })), + ) // remove this listener if the component is no longer mounted return unlisten }, [history]) @@ -59,11 +72,13 @@ export function MetaMetricsProvider ({ children }) { /** * track a metametrics event * - * @param {import('../../../shared/modules/metametrics').MetaMetricsEventPayload} - payload for event + * @param {import('../../../shared/modules/metametrics').MetaMetricsEventPayload} payload - payload for event * @returns undefined */ const trackEvent = useMemo(() => { - const referrer = confirmTransactionOrigin ? { url: confirmTransactionOrigin } : undefined + const referrer = confirmTransactionOrigin + ? { url: confirmTransactionOrigin } + : undefined const page = { path: currentPath, } @@ -80,41 +95,51 @@ export function MetaMetricsProvider ({ children }) { metaMetricsId, metaMetricsSendCount, })) - }, [network, chainId, locale, environmentType, participateInMetaMetrics, currentPath, confirmTransactionOrigin, metaMetricsId, metaMetricsSendCount]) - - const metricsEvent = useCallback((config = {}, overrides = {}) => { - const { eventOpts = {} } = config - - return trackEvent({ - event: eventOpts.name, - category: eventOpts.category, - isOptIn: config.isOptIn, - excludeMetaMetricsId: eventOpts.excludeMetaMetricsId ?? overrides.excludeMetaMetricsId ?? false, - metaMetricsId: config.metaMetricsId, - matomoEvent: true, - properties: { - action: eventOpts.action, - number_of_tokens: numberOfTokens, - number_of_accounts: numberOfAccounts, - active_currency: activeCurrency, - account_type: accountType, - is_new_visit: config.is_new_visit, - // the properties coming from this key will not match our standards for - // snake_case on properties, and they may be redundant and/or not in the - // proper location (origin not as a referrer, for example). This is a temporary - // solution to not lose data, and the entire event system will be reworked in - // forthcoming PRs to deprecate the old Matomo events in favor of the new schema. - ...config.customVariables, - }, - }) }, [ - accountType, - activeCurrency, - numberOfTokens, - numberOfAccounts, - trackEvent, + network, + chainId, + locale, + environmentType, + participateInMetaMetrics, + currentPath, + confirmTransactionOrigin, + metaMetricsId, + metaMetricsSendCount, ]) + const metricsEvent = useCallback( + (config = {}, overrides = {}) => { + const { eventOpts = {} } = config + + return trackEvent({ + event: eventOpts.name, + category: eventOpts.category, + isOptIn: config.isOptIn, + excludeMetaMetricsId: + eventOpts.excludeMetaMetricsId ?? + overrides.excludeMetaMetricsId ?? + false, + metaMetricsId: config.metaMetricsId, + matomoEvent: true, + properties: { + action: eventOpts.action, + number_of_tokens: numberOfTokens, + number_of_accounts: numberOfAccounts, + active_currency: activeCurrency, + account_type: accountType, + is_new_visit: config.is_new_visit, + // the properties coming from this key will not match our standards for + // snake_case on properties, and they may be redundant and/or not in the + // proper location (origin not as a referrer, for example). This is a temporary + // solution to not lose data, and the entire event system will be reworked in + // forthcoming PRs to deprecate the old Matomo events in favor of the new schema. + ...config.customVariables, + }, + }) + }, + [accountType, activeCurrency, numberOfTokens, numberOfAccounts, trackEvent], + ) + return ( {children} @@ -139,13 +164,13 @@ export class LegacyMetaMetricsProvider extends Component { metricsEvent: PropTypes.func, } - getChildContext () { + getChildContext() { return { metricsEvent: this.context, } } - render () { + render() { return this.props.children } } diff --git a/ui/app/contexts/metametrics.new.js b/ui/app/contexts/metametrics.new.js index 2afbb712a..dd8759458 100644 --- a/ui/app/contexts/metametrics.new.js +++ b/ui/app/contexts/metametrics.new.js @@ -3,7 +3,13 @@ * MetaMetrics is our own brand, and should remain aptly named regardless of the underlying * metrics system. This file implements Segment analytics tracking. */ -import React, { useRef, Component, createContext, useEffect, useMemo } from 'react' +import React, { + useRef, + Component, + createContext, + useEffect, + useMemo, +} from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import { useLocation, matchPath, useRouteMatch } from 'react-router-dom' @@ -14,31 +20,49 @@ import { omit } from 'lodash' import { getEnvironmentType } from '../../../app/scripts/lib/util' import { PATH_NAME_MAP } from '../helpers/constants/routes' import { getCurrentLocale } from '../ducks/metamask/metamask' -import { getCurrentChainId, getMetricsNetworkIdentifier, txDataSelector } from '../selectors' -import { getTrackMetaMetricsEvent, METAMETRICS_ANONYMOUS_ID, segment } from '../../../shared/modules/metametrics' +import { + getCurrentChainId, + getMetricsNetworkIdentifier, + txDataSelector, +} from '../selectors' +import { + getTrackMetaMetricsEvent, + METAMETRICS_ANONYMOUS_ID, + segment, +} from '../../../shared/modules/metametrics' export const MetaMetricsContext = createContext(() => { captureException( - Error(`MetaMetrics context function was called from a react node that is not a descendant of a MetaMetrics context provider`), + Error( + `MetaMetrics context function was called from a react node that is not a descendant of a MetaMetrics context provider`, + ), ) }) const PATHS_TO_CHECK = Object.keys(PATH_NAME_MAP) -function useSegmentContext () { - const match = useRouteMatch({ path: PATHS_TO_CHECK, exact: true, strict: true }) +function useSegmentContext() { + const match = useRouteMatch({ + path: PATHS_TO_CHECK, + exact: true, + strict: true, + }) const txData = useSelector(txDataSelector) || {} const confirmTransactionOrigin = txData.origin - const referrer = confirmTransactionOrigin ? { - url: confirmTransactionOrigin, - } : undefined + const referrer = confirmTransactionOrigin + ? { + url: confirmTransactionOrigin, + } + : undefined - const page = match ? { - path: match.path, - title: PATH_NAME_MAP[match.path], - url: match.path, - } : undefined + const page = match + ? { + path: match.path, + title: PATH_NAME_MAP[match.path], + url: match.path, + } + : undefined return { page, @@ -46,10 +70,14 @@ function useSegmentContext () { } } -export function MetaMetricsProvider ({ children }) { +export function MetaMetricsProvider({ children }) { const metaMetricsId = useSelector((state) => state.metamask.metaMetricsId) - const participateInMetaMetrics = useSelector((state) => state.metamask.participateInMetaMetrics) - const metaMetricsSendCount = useSelector((state) => state.metamask.metaMetricsSendCount) + const participateInMetaMetrics = useSelector( + (state) => state.metamask.participateInMetaMetrics, + ) + const metaMetricsSendCount = useSelector( + (state) => state.metamask.metaMetricsSendCount, + ) const locale = useSelector(getCurrentLocale) const location = useLocation() const context = useSegmentContext() @@ -59,7 +87,7 @@ export function MetaMetricsProvider ({ children }) { /** * track a metametrics event * - * @param {import('../../../shared/modules/metametrics').MetaMetricsEventPayload} - payload for event + * @param {import('../../../shared/modules/metametrics').MetaMetricsEventPayload} payload - payload for event * @returns undefined */ const trackEvent = useMemo(() => { @@ -73,7 +101,15 @@ export function MetaMetricsProvider ({ children }) { metaMetricsId, metaMetricsSendCount, })) - }, [network, participateInMetaMetrics, locale, metaMetricsId, metaMetricsSendCount, chainId, context]) + }, [ + network, + participateInMetaMetrics, + locale, + metaMetricsId, + metaMetricsSendCount, + chainId, + context, + ]) // Used to prevent double tracking page calls const previousMatch = useRef() @@ -87,13 +123,18 @@ export function MetaMetricsProvider ({ children }) { useEffect(() => { const environmentType = getEnvironmentType() if ( - (participateInMetaMetrics === null && location.pathname.startsWith('/initialize')) || + (participateInMetaMetrics === null && + location.pathname.startsWith('/initialize')) || participateInMetaMetrics ) { // Events that happen during initialization before the user opts into MetaMetrics will be anonymous const idTrait = metaMetricsId ? 'userId' : 'anonymousId' const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID - const match = matchPath(location.pathname, { path: PATHS_TO_CHECK, exact: true, strict: true }) + const match = matchPath(location.pathname, { + path: PATHS_TO_CHECK, + exact: true, + strict: true, + }) // Start by checking for a missing match route. If this falls through to the else if, then we know we // have a matched route for tracking. if (!match) { @@ -109,7 +150,11 @@ export function MetaMetricsProvider ({ children }) { } } else if ( previousMatch.current !== match.path && - !(environmentType === 'notification' && match.path === '/' && previousMatch.current === undefined) + !( + environmentType === 'notification' && + match.path === '/' && + previousMatch.current === undefined + ) ) { // When a notification window is open by a Dapp we do not want to track the initial home route load that can // sometimes happen. To handle this we keep track of the previousMatch, and we skip the event track in the event @@ -123,6 +168,7 @@ export function MetaMetricsProvider ({ children }) { // We do not want to send addresses or accounts in any events // Some routes include these as params. params: omit(params, ['account', 'address']), + locale: locale.replace('_', '-'), network, environment_type: environmentType, }, @@ -131,7 +177,14 @@ export function MetaMetricsProvider ({ children }) { } previousMatch.current = match?.path } - }, [location, context, network, metaMetricsId, participateInMetaMetrics]) + }, [ + location, + locale, + context, + network, + metaMetricsId, + participateInMetaMetrics, + ]) return ( @@ -160,13 +213,13 @@ export class LegacyMetaMetricsProvider extends Component { trackEvent: PropTypes.func, } - getChildContext () { + getChildContext() { return { trackEvent: this.context, } } - render () { + render() { return this.props.children } } diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss index 31ca8c201..34f905587 100644 --- a/ui/app/css/itcss/components/network.scss +++ b/ui/app/css/itcss/components/network.scss @@ -7,7 +7,7 @@ } .network-component.pointer { - border: 2px solid $Grey-200; + border: 1px solid $Grey-200; border-radius: 82px; padding: 6px 3px; flex: 0 0 auto; @@ -42,9 +42,10 @@ } .network-indicator { + @include H8; + display: flex; align-items: center; - font-size: 0.6em; &__down-arrow { height: 8px; @@ -58,16 +59,16 @@ } .fa-question-circle { + font-size: $font-size-paragraph; margin: 0 4px 0 6px; - font-size: 1rem; flex: 0 0 auto; } } .network-name { + @include H7; + padding: 0 4px; - font-size: 12px; - line-height: 14px; flex: 1 1 auto; color: $tundora; font-weight: 500; @@ -163,20 +164,20 @@ } .network-dropdown-title { + @include H4; + height: 25px; width: 120px; color: $white; - font-size: 18px; - line-height: 25px; text-align: center; } .network-dropdown-content { + @include H6; + min-height: 36px; width: 265px; color: $dusty-gray; - font-size: 14px; - line-height: 18px; } .network-caret { diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss index df18572cd..49fcb3955 100644 --- a/ui/app/css/itcss/components/send.scss +++ b/ui/app/css/itcss/components/send.scss @@ -39,8 +39,9 @@ } .send-screen input { + @include H7; + width: 100%; - font-size: 12px; } .send-eth-icon { @@ -70,11 +71,11 @@ } .large-input { + @include Paragraph; + border: 1px solid $dusty-gray; border-radius: 4px; margin: 4px 0 20px; - font-size: 16px; - line-height: 22.4px; } .send-screen-gas-input { @@ -92,22 +93,22 @@ } .send-screen-input-wrapper__error-message { + @include H7; + display: block; position: absolute; bottom: 4px; - font-size: 12px; - line-height: 12px; left: 8px; color: $red; } } .send-screen-input-wrapper__error-message { + @include H7; + display: block; position: absolute; bottom: 4px; - font-size: 12px; - line-height: 12px; left: 8px; color: $red; } @@ -118,6 +119,8 @@ } .send-screen-gas-input { + @include Paragraph; + width: 100%; height: 41px; border-radius: 3px; @@ -129,7 +132,6 @@ align-items: center; padding-left: 10px; padding-right: 12px; - font-size: 16px; color: $scorpion; } @@ -158,8 +160,9 @@ } .send-screen-gas-input-customize { + @include H7; + color: $primary-blue; - font-size: 12px; cursor: pointer; } @@ -173,6 +176,8 @@ } .customize-gas-tooltip-container { + @include Paragraph; + position: absolute; bottom: 50px; width: 237px; @@ -182,7 +187,6 @@ box-shadow: $alto 0 0 5px; z-index: 1050; padding: 13px 19px; - font-size: 16px; border-radius: 4px; font-weight: 500; } @@ -219,7 +223,8 @@ } .gas-tooltip-label { - font-size: 16px; + @include Paragraph; + color: $tundora; } @@ -237,10 +242,11 @@ } .customize-gas-input { + @include Paragraph; + width: 178px; height: 28px; border: 1px solid $alto; - font-size: 16px; color: $nile-blue; padding-left: 8px; } @@ -250,14 +256,17 @@ } .gas-tooltip-input-detail { + @include H7; + position: absolute; top: 4px; right: 26px; - font-size: 12px; color: $silver-chalice; } .gas-tooltip-input-arrows { + @include H6; + position: absolute; top: 0; right: 4px; @@ -268,7 +277,6 @@ display: flex; flex-direction: column; color: #9b9b9b; - font-size: 0.8em; padding: 1px 4px; cursor: pointer; } @@ -286,15 +294,15 @@ .send-screen { &__title { + @include H4; + color: $scorpion; - font-size: 18px; - line-height: 29px; } &__subtitle { + @include H6; + margin: 10px 0 20px; - font-size: 14px; - line-height: 24px; } &__send-button, @@ -347,23 +355,23 @@ } &__title { + @include H4; + color: $scorpion; - font-size: 18px; - line-height: 29px; } &__description, &__balance-text, &__token-symbol { + @include H6; + margin-top: 10px; - font-size: 14px; - line-height: 24px; text-align: center; } &__token-balance { - font-size: 40px; - line-height: 40px; + @include H1; + margin-top: 13px; .token-balance__amount { @@ -433,12 +441,13 @@ } &__send-arrow-icon { + @include H4; + color: #f28930; transform: rotate(-45deg); position: absolute; top: -2px; left: 0; - font-size: 1.12em; } &__arrow-background { @@ -489,25 +498,25 @@ } &__title { + @include H3; + color: $scorpion; - font-size: 22px; - line-height: 29px; text-align: center; margin-top: 25px; } &__copy { + @include H6; + color: $gray; - font-size: 14px; - line-height: 19px; text-align: center; margin-top: 10px; width: 287px; } &__error { - font-size: 12px; - line-height: 12px; + @include H7; + left: 8px; color: $red; } @@ -517,8 +526,8 @@ } &__warning { - font-size: 12px; - line-height: 12px; + @include H7; + left: 8px; color: $orange; } @@ -549,6 +558,7 @@ flex-flow: row; flex: 1 0 auto; justify-content: space-between; + align-items: center; } &__form-field-container { @@ -582,22 +592,21 @@ } &__form-label { + @include Paragraph; + color: $scorpion; - font-size: 16px; - line-height: 22px; width: 95px; - font-weight: 400; flex: 0 0 auto; } &__from-dropdown, &__asset-dropdown { + @include H7; + width: 100%; border: 1px solid $alto; border-radius: 4px; background-color: $white; - line-height: 16px; - font-size: 12px; color: $tundora; position: relative; @@ -670,14 +679,16 @@ } &__symbol { - font-size: 16px; + @include Paragraph; + margin-bottom: 2px; } &__name { + @include H7; + display: flex; flex-flow: row nowrap; - font-size: 12px; &__label { margin-right: 0.25rem; @@ -729,6 +740,8 @@ } &__input { + @include Paragraph; + z-index: 1025; position: relative; height: 54px; @@ -738,14 +751,14 @@ background-color: $white; color: $tundora; padding: 10px; - font-size: 16px; - line-height: 21px; } } &__memo-text-area, &__hex-data { &__input { + @include Paragraph; + z-index: 1025; position: relative; height: 54px; @@ -755,13 +768,12 @@ background-color: $white; color: $tundora; padding: 10px; - font-size: 16px; - line-height: 21px; } } &__amount-max { - font-size: 12px; + @include H7; + position: relative; display: inline-block; width: 56px; @@ -772,7 +784,7 @@ width: 56px; height: 20px; position: absolute; - border: 2px solid #b0d7f2; + border: 1px solid #b0d7f2; border-radius: 6px; cursor: pointer; top: 0; @@ -792,7 +804,7 @@ input:checked + &__button { background-color: $primary-blue; - border: 2px solid $primary-blue; + border: 1px solid $primary-blue; color: #fff; } } @@ -815,6 +827,8 @@ } &__sliders-icon-container { + @include Paragraph; + display: flex; align-items: center; justify-content: center; @@ -827,7 +841,6 @@ right: 15px; top: 14px; cursor: pointer; - font-size: 1em; } &__sliders-icon { @@ -872,12 +885,13 @@ } &__header { + @include H3; + height: 52px; border-bottom: 1px solid $alto; display: flex; align-items: center; justify-content: space-between; - font-size: 22px; @media screen and (max-width: $break-small) { flex: 0 0 auto; @@ -913,12 +927,13 @@ } &__footer { + @include H3; + height: 75px; border-top: 1px solid $alto; display: flex; align-items: center; justify-content: space-between; - font-size: 22px; position: relative; @media screen and (max-width: $break-small) { @@ -944,8 +959,9 @@ } &__revert { + @include Paragraph; + color: $silver-chalice; - font-size: 16px; margin-left: 21.25px; } @@ -962,12 +978,12 @@ } &__error-message { + @include H7; + display: block; position: absolute; top: -18px; right: 0; - font-size: 12px; - line-height: 12px; color: $red; width: 100%; text-align: center; @@ -986,19 +1002,19 @@ padding-left: 20px; &__title { + @include H4; + height: 26px; color: $tundora; - font-size: 20px; - line-height: 26px; margin-top: 17px; } &__copy { + @include Paragraph; + height: 38px; width: 314px; color: $tundora; - font-size: 14px; - line-height: 19px; margin-top: 17px; } @@ -1015,10 +1031,11 @@ } .gas-tooltip-input-arrows { + @include H4; + width: 32px; height: 54px; border-left: 1px solid #dadada; - font-size: 18px; color: $tundora; right: 0; padding: 0; @@ -1049,15 +1066,18 @@ } .advanced-gas-options-btn { + @include H6; + display: flex; justify-content: flex-end; - font-size: 14px; color: #2f9ae0; cursor: pointer; margin-top: 5px; } .sliders-icon-container { + @include Paragraph; + display: flex; align-items: center; justify-content: center; @@ -1070,10 +1090,11 @@ right: 15px; top: 14px; cursor: pointer; - font-size: 1em; } .gas-fee-reset { + @include H6; + display: flex; align-items: center; justify-content: center; @@ -1084,8 +1105,6 @@ right: 12px; top: 14px; cursor: pointer; - font-size: 1em; - font-size: 14px; color: #2f9ae0; } diff --git a/ui/app/css/itcss/components/simple-dropdown.scss b/ui/app/css/itcss/components/simple-dropdown.scss index d815de8df..d29aaa55c 100644 --- a/ui/app/css/itcss/components/simple-dropdown.scss +++ b/ui/app/css/itcss/components/simple-dropdown.scss @@ -1,4 +1,6 @@ .simple-dropdown { + @include Paragraph; + height: 56px; display: flex; justify-content: flex-start; @@ -6,7 +8,6 @@ border: 1px solid $alto; border-radius: 4px; background-color: $white; - font-size: 16px; color: #4d4d4d; cursor: pointer; position: relative; diff --git a/ui/app/ducks/alerts/invalid-custom-network.js b/ui/app/ducks/alerts/invalid-custom-network.js index 62e06f14c..aa7ba3cdb 100644 --- a/ui/app/ducks/alerts/invalid-custom-network.js +++ b/ui/app/ducks/alerts/invalid-custom-network.js @@ -43,9 +43,6 @@ export const alertIsOpen = (state) => state[name].state !== ALERT_STATE.CLOSED // Actions / action-creators -const { - openAlert, - dismissAlert, -} = actions +const { openAlert, dismissAlert } = actions export { openAlert, dismissAlert } diff --git a/ui/app/ducks/alerts/unconnected-account.js b/ui/app/ducks/alerts/unconnected-account.js index 9b150dc71..58ca9a569 100644 --- a/ui/app/ducks/alerts/unconnected-account.js +++ b/ui/app/ducks/alerts/unconnected-account.js @@ -8,10 +8,7 @@ import { setAlertEnabledness, setSelectedAddress, } from '../../store/actions' -import { - getOriginOfCurrentTab, - getSelectedAddress, -} from '../../selectors' +import { getOriginOfCurrentTab, getSelectedAddress } from '../../selectors' import { ALERT_STATE } from './enums' // Constants diff --git a/ui/app/ducks/app/app.js b/ui/app/ducks/app/app.js index 2b905a1ac..913142761 100644 --- a/ui/app/ducks/app/app.js +++ b/ui/app/ducks/app/app.js @@ -3,7 +3,7 @@ import * as actionConstants from '../../store/actionConstants' // actionConstants const SET_THREEBOX_LAST_UPDATED = 'metamask/app/SET_THREEBOX_LAST_UPDATED' -export default function reduceApp (state = {}, action) { +export default function reduceApp(state = {}, action) { // default state const appState = { shouldClose: false, @@ -161,7 +161,7 @@ export default function reduceApp (state = {}, action) { warning: null, } - // accounts + // accounts case actionConstants.GO_HOME: return { @@ -177,7 +177,9 @@ export default function reduceApp (state = {}, action) { case actionConstants.SHOW_ACCOUNT_DETAIL: return { ...appState, - forgottenPassword: appState.forgottenPassword ? !appState.forgottenPassword : null, + forgottenPassword: appState.forgottenPassword + ? !appState.forgottenPassword + : null, accountDetail: { subview: 'transactions', accountExport: 'none', @@ -366,7 +368,7 @@ export default function reduceApp (state = {}, action) { } // Action Creators -export function setThreeBoxLastUpdated (lastUpdated) { +export function setThreeBoxLastUpdated(lastUpdated) { return { type: SET_THREEBOX_LAST_UPDATED, value: lastUpdated, diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js index b62110fdc..5125f17d9 100644 --- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js +++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js @@ -1,4 +1,4 @@ -import { addHexPrefix } from 'ethereumjs-util' +import { addHexPrefix } from '../../../../app/scripts/lib/util' import { conversionRateSelector, currentCurrencySelector, @@ -16,10 +16,7 @@ import { hexGreaterThan, } from '../../helpers/utils/confirm-tx.util' -import { - getTokenData, - sumHexes, -} from '../../helpers/utils/transactions.util' +import { getTokenData, sumHexes } from '../../helpers/utils/transactions.util' import { conversionUtil } from '../../helpers/utils/conversion-util' @@ -33,7 +30,9 @@ const CLEAR_TOKEN_DATA = createActionType('CLEAR_TOKEN_DATA') const UPDATE_METHOD_DATA = createActionType('UPDATE_METHOD_DATA') const CLEAR_METHOD_DATA = createActionType('CLEAR_METHOD_DATA') const CLEAR_CONFIRM_TRANSACTION = createActionType('CLEAR_CONFIRM_TRANSACTION') -const UPDATE_TRANSACTION_AMOUNTS = createActionType('UPDATE_TRANSACTION_AMOUNTS') +const UPDATE_TRANSACTION_AMOUNTS = createActionType( + 'UPDATE_TRANSACTION_AMOUNTS', +) const UPDATE_TRANSACTION_FEES = createActionType('UPDATE_TRANSACTION_FEES') const UPDATE_TRANSACTION_TOTALS = createActionType('UPDATE_TRANSACTION_TOTALS') const UPDATE_TOKEN_PROPS = createActionType('UPDATE_TOKEN_PROPS') @@ -66,7 +65,7 @@ const initState = { } // Reducer -export default function reducer (state = initState, action = {}) { +export default function reducer(state = initState, action = {}) { switch (action.type) { case UPDATE_TX_DATA: return { @@ -105,16 +104,27 @@ export default function reducer (state = initState, action = {}) { methodData: {}, } case UPDATE_TRANSACTION_AMOUNTS: { - const { fiatTransactionAmount, ethTransactionAmount, hexTransactionAmount } = action.payload + const { + fiatTransactionAmount, + ethTransactionAmount, + hexTransactionAmount, + } = action.payload return { ...state, - fiatTransactionAmount: fiatTransactionAmount || state.fiatTransactionAmount, - ethTransactionAmount: ethTransactionAmount || state.ethTransactionAmount, - hexTransactionAmount: hexTransactionAmount || state.hexTransactionAmount, + fiatTransactionAmount: + fiatTransactionAmount || state.fiatTransactionAmount, + ethTransactionAmount: + ethTransactionAmount || state.ethTransactionAmount, + hexTransactionAmount: + hexTransactionAmount || state.hexTransactionAmount, } } case UPDATE_TRANSACTION_FEES: { - const { fiatTransactionFee, ethTransactionFee, hexTransactionFee } = action.payload + const { + fiatTransactionFee, + ethTransactionFee, + hexTransactionFee, + } = action.payload return { ...state, fiatTransactionFee: fiatTransactionFee || state.fiatTransactionFee, @@ -123,10 +133,15 @@ export default function reducer (state = initState, action = {}) { } } case UPDATE_TRANSACTION_TOTALS: { - const { fiatTransactionTotal, ethTransactionTotal, hexTransactionTotal } = action.payload + const { + fiatTransactionTotal, + ethTransactionTotal, + hexTransactionTotal, + } = action.payload return { ...state, - fiatTransactionTotal: fiatTransactionTotal || state.fiatTransactionTotal, + fiatTransactionTotal: + fiatTransactionTotal || state.fiatTransactionTotal, ethTransactionTotal: ethTransactionTotal || state.ethTransactionTotal, hexTransactionTotal: hexTransactionTotal || state.hexTransactionTotal, } @@ -170,96 +185,98 @@ export default function reducer (state = initState, action = {}) { } // Action Creators -export function updateTxData (txData) { +export function updateTxData(txData) { return { type: UPDATE_TX_DATA, payload: txData, } } -export function clearTxData () { +export function clearTxData() { return { type: CLEAR_TX_DATA, } } -export function updateTokenData (tokenData) { +export function updateTokenData(tokenData) { return { type: UPDATE_TOKEN_DATA, payload: tokenData, } } -export function clearTokenData () { +export function clearTokenData() { return { type: CLEAR_TOKEN_DATA, } } -export function updateMethodData (methodData) { +export function updateMethodData(methodData) { return { type: UPDATE_METHOD_DATA, payload: methodData, } } -export function clearMethodData () { +export function clearMethodData() { return { type: CLEAR_METHOD_DATA, } } -export function updateTransactionAmounts (amounts) { +export function updateTransactionAmounts(amounts) { return { type: UPDATE_TRANSACTION_AMOUNTS, payload: amounts, } } -export function updateTransactionFees (fees) { +export function updateTransactionFees(fees) { return { type: UPDATE_TRANSACTION_FEES, payload: fees, } } -export function updateTransactionTotals (totals) { +export function updateTransactionTotals(totals) { return { type: UPDATE_TRANSACTION_TOTALS, payload: totals, } } -export function updateTokenProps (tokenProps) { +export function updateTokenProps(tokenProps) { return { type: UPDATE_TOKEN_PROPS, payload: tokenProps, } } -export function updateNonce (nonce) { +export function updateNonce(nonce) { return { type: UPDATE_NONCE, payload: nonce, } } -export function updateToSmartContract (toSmartContract) { +export function updateToSmartContract(toSmartContract) { return { type: UPDATE_TO_SMART_CONTRACT, payload: toSmartContract, } } -export function setFetchingData (isFetching) { +export function setFetchingData(isFetching) { return { type: isFetching ? FETCH_DATA_START : FETCH_DATA_END, } } -export function updateGasAndCalculate ({ gasLimit, gasPrice }) { +export function updateGasAndCalculate({ gasLimit, gasPrice }) { return (dispatch, getState) => { - const { confirmTransaction: { txData } } = getState() + const { + confirmTransaction: { txData }, + } = getState() const newTxData = { ...txData, txParams: { @@ -273,13 +290,16 @@ export function updateGasAndCalculate ({ gasLimit, gasPrice }) { } } -function increaseFromLastGasPrice (txData) { +function increaseFromLastGasPrice(txData) { const { lastGasPrice, txParams: { gasPrice: previousGasPrice } = {} } = txData // Set the minimum to a 10% increase from the lastGasPrice. const minimumGasPrice = increaseLastGasPrice(lastGasPrice) const gasPriceBelowMinimum = hexGreaterThan(minimumGasPrice, previousGasPrice) - const gasPrice = (!previousGasPrice || gasPriceBelowMinimum) ? minimumGasPrice : previousGasPrice + const gasPrice = + !previousGasPrice || gasPriceBelowMinimum + ? minimumGasPrice + : previousGasPrice return { ...txData, @@ -290,7 +310,7 @@ function increaseFromLastGasPrice (txData) { } } -export function updateTxDataAndCalculate (txData) { +export function updateTxDataAndCalculate(txData) { return (dispatch, getState) => { const state = getState() const currentCurrency = currentCurrencySelector(state) @@ -299,20 +319,32 @@ export function updateTxDataAndCalculate (txData) { dispatch(updateTxData(txData)) - const { txParams: { value = '0x0', gas: gasLimit = '0x0', gasPrice = '0x0' } = {} } = txData + const { + txParams: { value = '0x0', gas: gasLimit = '0x0', gasPrice = '0x0' } = {}, + } = txData const fiatTransactionAmount = getValueFromWeiHex({ - value, fromCurrency: nativeCurrency, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2, + value, + fromCurrency: nativeCurrency, + toCurrency: currentCurrency, + conversionRate, + numberOfDecimals: 2, }) const ethTransactionAmount = getValueFromWeiHex({ - value, fromCurrency: nativeCurrency, toCurrency: nativeCurrency, conversionRate, numberOfDecimals: 6, + value, + fromCurrency: nativeCurrency, + toCurrency: nativeCurrency, + conversionRate, + numberOfDecimals: 6, }) - dispatch(updateTransactionAmounts({ - fiatTransactionAmount, - ethTransactionAmount, - hexTransactionAmount: value, - })) + dispatch( + updateTransactionAmounts({ + fiatTransactionAmount, + ethTransactionAmount, + hexTransactionAmount: value, + }), + ) const hexTransactionFee = getHexGasTotal({ gasLimit, gasPrice }) @@ -331,24 +363,37 @@ export function updateTxDataAndCalculate (txData) { conversionRate, }) - dispatch(updateTransactionFees({ fiatTransactionFee, ethTransactionFee, hexTransactionFee })) + dispatch( + updateTransactionFees({ + fiatTransactionFee, + ethTransactionFee, + hexTransactionFee, + }), + ) - const fiatTransactionTotal = addFiat(fiatTransactionFee, fiatTransactionAmount) + const fiatTransactionTotal = addFiat( + fiatTransactionFee, + fiatTransactionAmount, + ) const ethTransactionTotal = addEth(ethTransactionFee, ethTransactionAmount) const hexTransactionTotal = sumHexes(value, hexTransactionFee) - dispatch(updateTransactionTotals({ - fiatTransactionTotal, - ethTransactionTotal, - hexTransactionTotal, - })) + dispatch( + updateTransactionTotals({ + fiatTransactionTotal, + ethTransactionTotal, + hexTransactionTotal, + }), + ) } } -export function setTransactionToConfirm (transactionId) { +export function setTransactionToConfirm(transactionId) { return (dispatch, getState) => { const state = getState() - const unconfirmedTransactionsHash = unconfirmedTransactionsHashSelector(state) + const unconfirmedTransactionsHash = unconfirmedTransactionsHashSelector( + state, + ) const transaction = unconfirmedTransactionsHash[transactionId] if (!transaction) { @@ -358,7 +403,9 @@ export function setTransactionToConfirm (transactionId) { if (transaction.txParams) { const { lastGasPrice } = transaction - const txData = lastGasPrice ? increaseFromLastGasPrice(transaction) : transaction + const txData = lastGasPrice + ? increaseFromLastGasPrice(transaction) + : transaction dispatch(updateTxDataAndCalculate(txData)) const { txParams } = transaction @@ -384,7 +431,7 @@ export function setTransactionToConfirm (transactionId) { } } -export function clearConfirmTransaction () { +export function clearConfirmTransaction() { return { type: CLEAR_CONFIRM_TRANSACTION, } diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js index 60ada9797..4d2d56544 100644 --- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js +++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js @@ -2,6 +2,10 @@ import assert from 'assert' import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import sinon from 'sinon' +import { + TRANSACTION_CATEGORIES, + TRANSACTION_STATUSES, +} from '../../../../shared/constants/transaction' import ConfirmTransactionReducer, * as actions from './confirm-transaction.duck' @@ -33,15 +37,20 @@ const UPDATE_TOKEN_DATA = 'metamask/confirm-transaction/UPDATE_TOKEN_DATA' const CLEAR_TOKEN_DATA = 'metamask/confirm-transaction/CLEAR_TOKEN_DATA' const UPDATE_METHOD_DATA = 'metamask/confirm-transaction/UPDATE_METHOD_DATA' const CLEAR_METHOD_DATA = 'metamask/confirm-transaction/CLEAR_METHOD_DATA' -const UPDATE_TRANSACTION_AMOUNTS = 'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS' -const UPDATE_TRANSACTION_FEES = 'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES' -const UPDATE_TRANSACTION_TOTALS = 'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS' +const UPDATE_TRANSACTION_AMOUNTS = + 'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS' +const UPDATE_TRANSACTION_FEES = + 'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES' +const UPDATE_TRANSACTION_TOTALS = + 'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS' const UPDATE_TOKEN_PROPS = 'metamask/confirm-transaction/UPDATE_TOKEN_PROPS' const UPDATE_NONCE = 'metamask/confirm-transaction/UPDATE_NONCE' -const UPDATE_TO_SMART_CONTRACT = 'metamask/confirm-transaction/UPDATE_TO_SMART_CONTRACT' +const UPDATE_TO_SMART_CONTRACT = + 'metamask/confirm-transaction/UPDATE_TO_SMART_CONTRACT' const FETCH_DATA_START = 'metamask/confirm-transaction/FETCH_DATA_START' const FETCH_DATA_END = 'metamask/confirm-transaction/FETCH_DATA_END' -const CLEAR_CONFIRM_TRANSACTION = 'metamask/confirm-transaction/CLEAR_CONFIRM_TRANSACTION' +const CLEAR_CONFIRM_TRANSACTION = + 'metamask/confirm-transaction/CLEAR_CONFIRM_TRANSACTION' describe('Confirm Transaction Duck', function () { describe('State changes', function () { @@ -53,7 +62,7 @@ describe('Confirm Transaction Duck', function () { name: 'abcToken', }, methodData: { - name: 'approve', + name: TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE, }, tokenProps: { tokenDecimals: '3', @@ -293,13 +302,21 @@ describe('Confirm Transaction Duck', function () { it('should set fetchingData to false when receiving a FETCH_DATA_END action', function () { assert.deepEqual( - ConfirmTransactionReducer({ fetchingData: true }, { type: FETCH_DATA_END }), + ConfirmTransactionReducer( + { fetchingData: true }, + { type: FETCH_DATA_END }, + ), { fetchingData: false }, ) }) it('should clear confirmTransaction when receiving a FETCH_DATA_END action', function () { - assert.deepEqual(ConfirmTransactionReducer(mockState, { type: CLEAR_CONFIRM_TRANSACTION }), initialState) + assert.deepEqual( + ConfirmTransactionReducer(mockState, { + type: CLEAR_CONFIRM_TRANSACTION, + }), + initialState, + ) }) }) @@ -311,10 +328,7 @@ describe('Confirm Transaction Duck', function () { payload: txData, } - assert.deepEqual( - actions.updateTxData(txData), - expectedAction, - ) + assert.deepEqual(actions.updateTxData(txData), expectedAction) }) it('should create an action to clear txData', function () { @@ -322,10 +336,7 @@ describe('Confirm Transaction Duck', function () { type: CLEAR_TX_DATA, } - assert.deepEqual( - actions.clearTxData(), - expectedAction, - ) + assert.deepEqual(actions.clearTxData(), expectedAction) }) it('should create an action to update tokenData', function () { @@ -335,10 +346,7 @@ describe('Confirm Transaction Duck', function () { payload: tokenData, } - assert.deepEqual( - actions.updateTokenData(tokenData), - expectedAction, - ) + assert.deepEqual(actions.updateTokenData(tokenData), expectedAction) }) it('should create an action to clear tokenData', function () { @@ -346,10 +354,7 @@ describe('Confirm Transaction Duck', function () { type: CLEAR_TOKEN_DATA, } - assert.deepEqual( - actions.clearTokenData(), - expectedAction, - ) + assert.deepEqual(actions.clearTokenData(), expectedAction) }) it('should create an action to update methodData', function () { @@ -359,10 +364,7 @@ describe('Confirm Transaction Duck', function () { payload: methodData, } - assert.deepEqual( - actions.updateMethodData(methodData), - expectedAction, - ) + assert.deepEqual(actions.updateMethodData(methodData), expectedAction) }) it('should create an action to clear methodData', function () { @@ -370,10 +372,7 @@ describe('Confirm Transaction Duck', function () { type: CLEAR_METHOD_DATA, } - assert.deepEqual( - actions.clearMethodData(), - expectedAction, - ) + assert.deepEqual(actions.clearMethodData(), expectedAction) }) it('should create an action to update transaction amounts', function () { @@ -425,10 +424,7 @@ describe('Confirm Transaction Duck', function () { payload: tokenProps, } - assert.deepEqual( - actions.updateTokenProps(tokenProps), - expectedAction, - ) + assert.deepEqual(actions.updateTokenProps(tokenProps), expectedAction) }) it('should create an action to update nonce', function () { @@ -438,10 +434,7 @@ describe('Confirm Transaction Duck', function () { payload: nonce, } - assert.deepEqual( - actions.updateNonce(nonce), - expectedAction, - ) + assert.deepEqual(actions.updateNonce(nonce), expectedAction) }) it('should create an action to set fetchingData to true', function () { @@ -449,10 +442,7 @@ describe('Confirm Transaction Duck', function () { type: FETCH_DATA_START, } - assert.deepEqual( - actions.setFetchingData(true), - expectedAction, - ) + assert.deepEqual(actions.setFetchingData(true), expectedAction) }) it('should create an action to set fetchingData to false', function () { @@ -460,10 +450,7 @@ describe('Confirm Transaction Duck', function () { type: FETCH_DATA_END, } - assert.deepEqual( - actions.setFetchingData(false), - expectedAction, - ) + assert.deepEqual(actions.setFetchingData(false), expectedAction) }) it('should create an action to clear confirmTransaction', function () { @@ -471,19 +458,18 @@ describe('Confirm Transaction Duck', function () { type: CLEAR_CONFIRM_TRANSACTION, } - assert.deepEqual( - actions.clearConfirmTransaction(), - expectedAction, - ) + assert.deepEqual(actions.clearConfirmTransaction(), expectedAction) }) }) describe('Thunk actions', function () { beforeEach(function () { global.eth = { - getCode: sinon.stub().callsFake( - (address) => Promise.resolve(address && address.match(/isContract/u) ? 'not-0x' : '0x'), - ), + getCode: sinon + .stub() + .callsFake((address) => + Promise.resolve(address?.match(/isContract/u) ? 'not-0x' : '0x'), + ), } }) @@ -519,7 +505,7 @@ describe('Confirm Transaction Duck', function () { loadingDefaults: false, metamaskNetworkId: '3', origin: 'faucet.metamask.io', - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, time: 1530838113716, }, }, @@ -535,11 +521,15 @@ describe('Confirm Transaction Duck', function () { 'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS', ] - store.dispatch(actions.updateGasAndCalculate({ gasLimit: '0x2', gasPrice: '0x25' })) + store.dispatch( + actions.updateGasAndCalculate({ gasLimit: '0x2', gasPrice: '0x25' }), + ) const storeActions = store.getActions() assert.equal(storeActions.length, expectedActions.length) - storeActions.forEach((action, index) => assert.equal(action.type, expectedActions[index])) + storeActions.forEach((action, index) => + assert.equal(action.type, expectedActions[index]), + ) }) it('updates txData and updates gas values in confirmTransaction', function () { @@ -549,7 +539,7 @@ describe('Confirm Transaction Duck', function () { loadingDefaults: false, metamaskNetworkId: '3', origin: 'faucet.metamask.io', - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, time: 1530838113716, txParams: { from: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6', @@ -603,7 +593,9 @@ describe('Confirm Transaction Duck', function () { const storeActions = store.getActions() assert.equal(storeActions.length, expectedActions.length) - storeActions.forEach((action, index) => assert.equal(action.type, expectedActions[index])) + storeActions.forEach((action, index) => + assert.equal(action.type, expectedActions[index]), + ) }) it('updates confirmTransaction transaction', function () { @@ -619,7 +611,7 @@ describe('Confirm Transaction Duck', function () { loadingDefaults: false, metamaskNetworkId: '3', origin: 'faucet.metamask.io', - status: 'unapproved', + status: TRANSACTION_STATUSES.UNAPPROVED, time: 1530838113716, txParams: { from: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6', @@ -648,7 +640,9 @@ describe('Confirm Transaction Duck', function () { const storeActions = store.getActions() assert.equal(storeActions.length, expectedActions.length) - storeActions.forEach((action, index) => assert.equal(action.type, expectedActions[index])) + storeActions.forEach((action, index) => + assert.equal(action.type, expectedActions[index]), + ) }) }) }) diff --git a/ui/app/ducks/gas/gas-duck.test.js b/ui/app/ducks/gas/gas-duck.test.js index d7ae9b873..6db54d12a 100644 --- a/ui/app/ducks/gas/gas-duck.test.js +++ b/ui/app/ducks/gas/gas-duck.test.js @@ -45,35 +45,116 @@ describe('Gas Duck', function () { standard: 20, } const mockPredictTableResponse = [ - { expectedTime: 400, expectedWait: 40, gasprice: 0.25, somethingElse: 'foobar' }, - { expectedTime: 200, expectedWait: 20, gasprice: 0.5, somethingElse: 'foobar' }, - { expectedTime: 100, expectedWait: 10, gasprice: 1, somethingElse: 'foobar' }, - { expectedTime: 75, expectedWait: 7.5, gasprice: 1.5, somethingElse: 'foobar' }, + { + expectedTime: 400, + expectedWait: 40, + gasprice: 0.25, + somethingElse: 'foobar', + }, + { + expectedTime: 200, + expectedWait: 20, + gasprice: 0.5, + somethingElse: 'foobar', + }, + { + expectedTime: 100, + expectedWait: 10, + gasprice: 1, + somethingElse: 'foobar', + }, + { + expectedTime: 75, + expectedWait: 7.5, + gasprice: 1.5, + somethingElse: 'foobar', + }, { expectedTime: 50, expectedWait: 5, gasprice: 2, somethingElse: 'foobar' }, - { expectedTime: 35, expectedWait: 4.5, gasprice: 3, somethingElse: 'foobar' }, - { expectedTime: 34, expectedWait: 4.4, gasprice: 3.1, somethingElse: 'foobar' }, - { expectedTime: 25, expectedWait: 4.2, gasprice: 3.5, somethingElse: 'foobar' }, + { + expectedTime: 35, + expectedWait: 4.5, + gasprice: 3, + somethingElse: 'foobar', + }, + { + expectedTime: 34, + expectedWait: 4.4, + gasprice: 3.1, + somethingElse: 'foobar', + }, + { + expectedTime: 25, + expectedWait: 4.2, + gasprice: 3.5, + somethingElse: 'foobar', + }, { expectedTime: 20, expectedWait: 4, gasprice: 4, somethingElse: 'foobar' }, - { expectedTime: 19, expectedWait: 3.9, gasprice: 4.1, somethingElse: 'foobar' }, + { + expectedTime: 19, + expectedWait: 3.9, + gasprice: 4.1, + somethingElse: 'foobar', + }, { expectedTime: 15, expectedWait: 3, gasprice: 7, somethingElse: 'foobar' }, - { expectedTime: 14, expectedWait: 2.9, gasprice: 7.1, somethingElse: 'foobar' }, - { expectedTime: 12, expectedWait: 2.5, gasprice: 8, somethingElse: 'foobar' }, - { expectedTime: 10, expectedWait: 2, gasprice: 10, somethingElse: 'foobar' }, - { expectedTime: 9, expectedWait: 1.9, gasprice: 10.1, somethingElse: 'foobar' }, + { + expectedTime: 14, + expectedWait: 2.9, + gasprice: 7.1, + somethingElse: 'foobar', + }, + { + expectedTime: 12, + expectedWait: 2.5, + gasprice: 8, + somethingElse: 'foobar', + }, + { + expectedTime: 10, + expectedWait: 2, + gasprice: 10, + somethingElse: 'foobar', + }, + { + expectedTime: 9, + expectedWait: 1.9, + gasprice: 10.1, + somethingElse: 'foobar', + }, { expectedTime: 5, expectedWait: 1, gasprice: 15, somethingElse: 'foobar' }, - { expectedTime: 4, expectedWait: 0.9, gasprice: 15.1, somethingElse: 'foobar' }, - { expectedTime: 2, expectedWait: 0.8, gasprice: 17, somethingElse: 'foobar' }, - { expectedTime: 1.1, expectedWait: 0.6, gasprice: 19.9, somethingElse: 'foobar' }, - { expectedTime: 1, expectedWait: 0.5, gasprice: 20, somethingElse: 'foobar' }, + { + expectedTime: 4, + expectedWait: 0.9, + gasprice: 15.1, + somethingElse: 'foobar', + }, + { + expectedTime: 2, + expectedWait: 0.8, + gasprice: 17, + somethingElse: 'foobar', + }, + { + expectedTime: 1.1, + expectedWait: 0.6, + gasprice: 19.9, + somethingElse: 'foobar', + }, + { + expectedTime: 1, + expectedWait: 0.5, + gasprice: 20, + somethingElse: 'foobar', + }, ] - const fakeFetch = (url) => new Promise((resolve) => { - const dataToResolve = url.match(/ethgasAPI/u) - ? mockEthGasApiResponse - : mockPredictTableResponse - resolve({ - json: () => Promise.resolve(dataToResolve), + const fakeFetch = (url) => + new Promise((resolve) => { + const dataToResolve = url.match(/ethgasAPI/u) + ? mockEthGasApiResponse + : mockPredictTableResponse + resolve({ + json: () => Promise.resolve(dataToResolve), + }) }) - }) beforeEach(function () { tempFetch = window.fetch @@ -121,20 +202,28 @@ describe('Gas Duck', function () { basicPriceAndTimeEstimatesLastRetrieved: 0, basicPriceEstimatesLastRetrieved: 0, } - const BASIC_GAS_ESTIMATE_LOADING_FINISHED = 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_FINISHED' - const BASIC_GAS_ESTIMATE_LOADING_STARTED = 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_STARTED' - const GAS_ESTIMATE_LOADING_FINISHED = 'metamask/gas/GAS_ESTIMATE_LOADING_FINISHED' - const GAS_ESTIMATE_LOADING_STARTED = 'metamask/gas/GAS_ESTIMATE_LOADING_STARTED' + const BASIC_GAS_ESTIMATE_LOADING_FINISHED = + 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_FINISHED' + const BASIC_GAS_ESTIMATE_LOADING_STARTED = + 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_STARTED' + const GAS_ESTIMATE_LOADING_FINISHED = + 'metamask/gas/GAS_ESTIMATE_LOADING_FINISHED' + const GAS_ESTIMATE_LOADING_STARTED = + 'metamask/gas/GAS_ESTIMATE_LOADING_STARTED' const RESET_CUSTOM_GAS_STATE = 'metamask/gas/RESET_CUSTOM_GAS_STATE' const SET_BASIC_GAS_ESTIMATE_DATA = 'metamask/gas/SET_BASIC_GAS_ESTIMATE_DATA' const SET_CUSTOM_GAS_ERRORS = 'metamask/gas/SET_CUSTOM_GAS_ERRORS' const SET_CUSTOM_GAS_LIMIT = 'metamask/gas/SET_CUSTOM_GAS_LIMIT' const SET_CUSTOM_GAS_PRICE = 'metamask/gas/SET_CUSTOM_GAS_PRICE' const SET_CUSTOM_GAS_TOTAL = 'metamask/gas/SET_CUSTOM_GAS_TOTAL' - const SET_PRICE_AND_TIME_ESTIMATES = 'metamask/gas/SET_PRICE_AND_TIME_ESTIMATES' - const SET_API_ESTIMATES_LAST_RETRIEVED = 'metamask/gas/SET_API_ESTIMATES_LAST_RETRIEVED' - const SET_BASIC_API_ESTIMATES_LAST_RETRIEVED = 'metamask/gas/SET_BASIC_API_ESTIMATES_LAST_RETRIEVED' - const SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED = 'metamask/gas/SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED' + const SET_PRICE_AND_TIME_ESTIMATES = + 'metamask/gas/SET_PRICE_AND_TIME_ESTIMATES' + const SET_API_ESTIMATES_LAST_RETRIEVED = + 'metamask/gas/SET_API_ESTIMATES_LAST_RETRIEVED' + const SET_BASIC_API_ESTIMATES_LAST_RETRIEVED = + 'metamask/gas/SET_BASIC_API_ESTIMATES_LAST_RETRIEVED' + const SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED = + 'metamask/gas/SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED' describe('GasReducer()', function () { it('should initialize state', function () { @@ -245,7 +334,10 @@ describe('Gas Duck', function () { type: SET_BASIC_API_ESTIMATES_LAST_RETRIEVED, value: 1700000000000, }), - { basicPriceAndTimeEstimatesLastRetrieved: 1700000000000, ...mockState }, + { + basicPriceAndTimeEstimatesLastRetrieved: 1700000000000, + ...mockState, + }, ) }) @@ -269,13 +361,17 @@ describe('Gas Duck', function () { describe('basicGasEstimatesLoadingStarted', function () { it('should create the correct action', function () { - assert.deepEqual(basicGasEstimatesLoadingStarted(), { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }) + assert.deepEqual(basicGasEstimatesLoadingStarted(), { + type: BASIC_GAS_ESTIMATE_LOADING_STARTED, + }) }) }) describe('basicGasEstimatesLoadingFinished', function () { it('should create the correct action', function () { - assert.deepEqual(basicGasEstimatesLoadingFinished(), { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }) + assert.deepEqual(basicGasEstimatesLoadingFinished(), { + type: BASIC_GAS_ESTIMATE_LOADING_FINISHED, + }) }) }) @@ -286,21 +382,20 @@ describe('Gas Duck', function () { await fetchBasicGasEstimates()(mockDistpatch, () => ({ gas: { ...initState, basicPriceAEstimatesLastRetrieved: 1000000 }, })) - assert.deepEqual( - mockDistpatch.getCall(0).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_STARTED }], - ) + assert.deepEqual(mockDistpatch.getCall(0).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }, + ]) assert.ok( - window.fetch.getCall(0).args[0].startsWith('https://ethgasstation.info/json/ethgasAPI.json'), + window.fetch + .getCall(0) + .args[0].startsWith('https://ethgasstation.info/json/ethgasAPI.json'), 'should fetch ETH Gas Station', ) - assert.deepEqual( - mockDistpatch.getCall(1).args, - [{ type: SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED, value: 2000000 }], - ) - assert.deepEqual( - mockDistpatch.getCall(2).args, - [{ + assert.deepEqual(mockDistpatch.getCall(1).args, [ + { type: SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED, value: 2000000 }, + ]) + assert.deepEqual(mockDistpatch.getCall(2).args, [ + { type: SET_BASIC_GAS_ESTIMATE_DATA, value: { average: 2, @@ -310,12 +405,11 @@ describe('Gas Duck', function () { fastest: 4, safeLow: 1, }, - }], - ) - assert.deepEqual( - mockDistpatch.getCall(3).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }], - ) + }, + ]) + assert.deepEqual(mockDistpatch.getCall(3).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }, + ]) }) it('should fetch recently retrieved estimates from local storage', async function () { @@ -334,15 +428,15 @@ describe('Gas Duck', function () { safeLow: 15, }) - await fetchBasicGasEstimates()(mockDistpatch, () => ({ gas: { ...initState } })) - assert.deepEqual( - mockDistpatch.getCall(0).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_STARTED }], - ) + await fetchBasicGasEstimates()(mockDistpatch, () => ({ + gas: { ...initState }, + })) + assert.deepEqual(mockDistpatch.getCall(0).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }, + ]) assert.ok(window.fetch.notCalled) - assert.deepEqual( - mockDistpatch.getCall(1).args, - [{ + assert.deepEqual(mockDistpatch.getCall(1).args, [ + { type: SET_BASIC_GAS_ESTIMATE_DATA, value: { average: 25, @@ -352,12 +446,11 @@ describe('Gas Duck', function () { fastest: 45, safeLow: 15, }, - }], - ) - assert.deepEqual( - mockDistpatch.getCall(2).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }], - ) + }, + ]) + assert.deepEqual(mockDistpatch.getCall(2).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }, + ]) }) it('should fallback to network if retrieving estimates from local storage fails', async function () { @@ -366,22 +459,23 @@ describe('Gas Duck', function () { .withArgs('BASIC_PRICE_ESTIMATES_LAST_RETRIEVED') .returns(2000000 - 1) // one second ago from "now" - await fetchBasicGasEstimates()(mockDistpatch, () => ({ gas: { ...initState } })) - assert.deepEqual( - mockDistpatch.getCall(0).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_STARTED }], - ) + await fetchBasicGasEstimates()(mockDistpatch, () => ({ + gas: { ...initState }, + })) + assert.deepEqual(mockDistpatch.getCall(0).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }, + ]) assert.ok( - window.fetch.getCall(0).args[0].startsWith('https://ethgasstation.info/json/ethgasAPI.json'), + window.fetch + .getCall(0) + .args[0].startsWith('https://ethgasstation.info/json/ethgasAPI.json'), 'should fetch ETH Gas Station', ) - assert.deepEqual( - mockDistpatch.getCall(1).args, - [{ type: SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED, value: 2000000 }], - ) - assert.deepEqual( - mockDistpatch.getCall(2).args, - [{ + assert.deepEqual(mockDistpatch.getCall(1).args, [ + { type: SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED, value: 2000000 }, + ]) + assert.deepEqual(mockDistpatch.getCall(2).args, [ + { type: SET_BASIC_GAS_ESTIMATE_DATA, value: { average: 2, @@ -391,12 +485,11 @@ describe('Gas Duck', function () { fastest: 4, safeLow: 1, }, - }], - ) - assert.deepEqual( - mockDistpatch.getCall(3).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }], - ) + }, + ]) + assert.deepEqual(mockDistpatch.getCall(3).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }, + ]) }) }) @@ -411,23 +504,22 @@ describe('Gas Duck', function () { }, metamask: { provider: { type: 'ropsten' } }, })) - assert.deepEqual( - mockDistpatch.getCall(0).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_STARTED }], - ) + assert.deepEqual(mockDistpatch.getCall(0).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }, + ]) assert.ok( - window.fetch.getCall(0).args[0].startsWith('https://ethgasstation.info/json/ethgasAPI.json'), + window.fetch + .getCall(0) + .args[0].startsWith('https://ethgasstation.info/json/ethgasAPI.json'), 'should fetch ETH Gas Station', ) - assert.deepEqual( - mockDistpatch.getCall(1).args, - [{ type: SET_BASIC_API_ESTIMATES_LAST_RETRIEVED, value: 2000000 }], - ) + assert.deepEqual(mockDistpatch.getCall(1).args, [ + { type: SET_BASIC_API_ESTIMATES_LAST_RETRIEVED, value: 2000000 }, + ]) - assert.deepEqual( - mockDistpatch.getCall(2).args, - [{ + assert.deepEqual(mockDistpatch.getCall(2).args, [ + { type: SET_BASIC_GAS_ESTIMATE_DATA, value: { average: 2, @@ -442,12 +534,11 @@ describe('Gas Duck', function () { safeLowWait: 'mockSafeLowWait', speed: 'mockSpeed', }, - }], - ) - assert.deepEqual( - mockDistpatch.getCall(3).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }], - ) + }, + ]) + assert.deepEqual(mockDistpatch.getCall(3).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }, + ]) }) it('should fetch recently retrieved estimates from local storage', async function () { @@ -477,15 +568,13 @@ describe('Gas Duck', function () { }, metamask: { provider: { type: 'ropsten' } }, })) - assert.deepEqual( - mockDistpatch.getCall(0).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_STARTED }], - ) + assert.deepEqual(mockDistpatch.getCall(0).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }, + ]) assert.ok(window.fetch.notCalled) - assert.deepEqual( - mockDistpatch.getCall(1).args, - [{ + assert.deepEqual(mockDistpatch.getCall(1).args, [ + { type: SET_BASIC_GAS_ESTIMATE_DATA, value: { average: 5, @@ -500,12 +589,11 @@ describe('Gas Duck', function () { safeLowWait: 'mockSafeLowWait', speed: 'mockSpeed', }, - }], - ) - assert.deepEqual( - mockDistpatch.getCall(2).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }], - ) + }, + ]) + assert.deepEqual(mockDistpatch.getCall(2).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }, + ]) }) it('should fallback to network if retrieving estimates from local storage fails', async function () { @@ -520,23 +608,22 @@ describe('Gas Duck', function () { }, metamask: { provider: { type: 'ropsten' } }, })) - assert.deepEqual( - mockDistpatch.getCall(0).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_STARTED }], - ) + assert.deepEqual(mockDistpatch.getCall(0).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }, + ]) assert.ok( - window.fetch.getCall(0).args[0].startsWith('https://ethgasstation.info/json/ethgasAPI.json'), + window.fetch + .getCall(0) + .args[0].startsWith('https://ethgasstation.info/json/ethgasAPI.json'), 'should fetch ETH Gas Station', ) - assert.deepEqual( - mockDistpatch.getCall(1).args, - [{ type: SET_BASIC_API_ESTIMATES_LAST_RETRIEVED, value: 2000000 }], - ) + assert.deepEqual(mockDistpatch.getCall(1).args, [ + { type: SET_BASIC_API_ESTIMATES_LAST_RETRIEVED, value: 2000000 }, + ]) - assert.deepEqual( - mockDistpatch.getCall(2).args, - [{ + assert.deepEqual(mockDistpatch.getCall(2).args, [ + { type: SET_BASIC_GAS_ESTIMATE_DATA, value: { average: 2, @@ -551,12 +638,11 @@ describe('Gas Duck', function () { safeLowWait: 'mockSafeLowWait', speed: 'mockSpeed', }, - }], - ) - assert.deepEqual( - mockDistpatch.getCall(3).args, - [{ type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }], - ) + }, + ]) + assert.deepEqual(mockDistpatch.getCall(3).args, [ + { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }, + ]) }) }) @@ -571,31 +657,46 @@ describe('Gas Duck', function () { }, metamask: { provider: { type: 'ropsten' } }, })) - assert.deepEqual( - mockDistpatch.getCall(0).args, - [{ type: GAS_ESTIMATE_LOADING_STARTED }], - ) + assert.deepEqual(mockDistpatch.getCall(0).args, [ + { type: GAS_ESTIMATE_LOADING_STARTED }, + ]) assert.ok( - window.fetch.getCall(0).args[0].startsWith('https://ethgasstation.info/json/predictTable.json'), + window.fetch + .getCall(0) + .args[0].startsWith( + 'https://ethgasstation.info/json/predictTable.json', + ), 'should fetch ETH Gas Station', ) - assert.deepEqual( - mockDistpatch.getCall(1).args, - [{ type: SET_API_ESTIMATES_LAST_RETRIEVED, value: 2000000 }], - ) + assert.deepEqual(mockDistpatch.getCall(1).args, [ + { type: SET_API_ESTIMATES_LAST_RETRIEVED, value: 2000000 }, + ]) - const { type: thirdDispatchCallType, value: priceAndTimeEstimateResult } = mockDistpatch.getCall(2).args[0] + const { + type: thirdDispatchCallType, + value: priceAndTimeEstimateResult, + } = mockDistpatch.getCall(2).args[0] assert.equal(thirdDispatchCallType, SET_PRICE_AND_TIME_ESTIMATES) - assert(priceAndTimeEstimateResult.length < ((mockPredictTableResponse.length * 3) - 2)) - assert(!priceAndTimeEstimateResult.find((d) => d.expectedTime > 100)) - assert(!priceAndTimeEstimateResult.find((d, _, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime)) - assert(!priceAndTimeEstimateResult.find((d, _, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice)) - - assert.deepEqual( - mockDistpatch.getCall(3).args, - [{ type: GAS_ESTIMATE_LOADING_FINISHED }], + assert( + priceAndTimeEstimateResult.length < + mockPredictTableResponse.length * 3 - 2, ) + assert(!priceAndTimeEstimateResult.find((d) => d.expectedTime > 100)) + assert( + !priceAndTimeEstimateResult.find( + (d, _, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime, + ), + ) + assert( + !priceAndTimeEstimateResult.find( + (d, _, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice, + ), + ) + + assert.deepEqual(mockDistpatch.getCall(3).args, [ + { type: GAS_ESTIMATE_LOADING_FINISHED }, + ]) }) it('should not call fetch if the estimates were retrieved < 75000 ms ago', async function () { @@ -605,23 +706,23 @@ describe('Gas Duck', function () { gas: { ...initState, priceAndTimeEstimatesLastRetrieved: Date.now(), - priceAndTimeEstimates: [{ - expectedTime: '10', - expectedWait: 2, - gasprice: 50, - }], + priceAndTimeEstimates: [ + { + expectedTime: '10', + expectedWait: 2, + gasprice: 50, + }, + ], }, metamask: { provider: { type: 'ropsten' } }, })) - assert.deepEqual( - mockDistpatch.getCall(0).args, - [{ type: GAS_ESTIMATE_LOADING_STARTED }], - ) + assert.deepEqual(mockDistpatch.getCall(0).args, [ + { type: GAS_ESTIMATE_LOADING_STARTED }, + ]) assert.equal(window.fetch.callCount, 0) - assert.deepEqual( - mockDistpatch.getCall(1).args, - [{ + assert.deepEqual(mockDistpatch.getCall(1).args, [ + { type: SET_PRICE_AND_TIME_ESTIMATES, value: [ { @@ -630,31 +731,27 @@ describe('Gas Duck', function () { gasprice: 50, }, ], - - }], - ) - assert.deepEqual( - mockDistpatch.getCall(2).args, - [{ type: GAS_ESTIMATE_LOADING_FINISHED }], - ) + }, + ]) + assert.deepEqual(mockDistpatch.getCall(2).args, [ + { type: GAS_ESTIMATE_LOADING_FINISHED }, + ]) }) }) describe('gasEstimatesLoadingStarted', function () { it('should create the correct action', function () { - assert.deepEqual( - gasEstimatesLoadingStarted(), - { type: GAS_ESTIMATE_LOADING_STARTED }, - ) + assert.deepEqual(gasEstimatesLoadingStarted(), { + type: GAS_ESTIMATE_LOADING_STARTED, + }) }) }) describe('gasEstimatesLoadingFinished', function () { it('should create the correct action', function () { - assert.deepEqual( - gasEstimatesLoadingFinished(), - { type: GAS_ESTIMATE_LOADING_FINISHED }, - ) + assert.deepEqual(gasEstimatesLoadingFinished(), { + type: GAS_ESTIMATE_LOADING_FINISHED, + }) }) }) @@ -662,71 +759,71 @@ describe('Gas Duck', function () { it('should create the correct action', function () { assert.deepEqual( setPricesAndTimeEstimates('mockPricesAndTimeEstimates'), - { type: SET_PRICE_AND_TIME_ESTIMATES, value: 'mockPricesAndTimeEstimates' }, + { + type: SET_PRICE_AND_TIME_ESTIMATES, + value: 'mockPricesAndTimeEstimates', + }, ) }) }) describe('setBasicGasEstimateData', function () { it('should create the correct action', function () { - assert.deepEqual( - setBasicGasEstimateData('mockBasicEstimatData'), - { type: SET_BASIC_GAS_ESTIMATE_DATA, value: 'mockBasicEstimatData' }, - ) + assert.deepEqual(setBasicGasEstimateData('mockBasicEstimatData'), { + type: SET_BASIC_GAS_ESTIMATE_DATA, + value: 'mockBasicEstimatData', + }) }) }) describe('setCustomGasPrice', function () { it('should create the correct action', function () { - assert.deepEqual( - setCustomGasPrice('mockCustomGasPrice'), - { type: SET_CUSTOM_GAS_PRICE, value: 'mockCustomGasPrice' }, - ) + assert.deepEqual(setCustomGasPrice('mockCustomGasPrice'), { + type: SET_CUSTOM_GAS_PRICE, + value: 'mockCustomGasPrice', + }) }) }) describe('setCustomGasLimit', function () { it('should create the correct action', function () { - assert.deepEqual( - setCustomGasLimit('mockCustomGasLimit'), - { type: SET_CUSTOM_GAS_LIMIT, value: 'mockCustomGasLimit' }, - ) + assert.deepEqual(setCustomGasLimit('mockCustomGasLimit'), { + type: SET_CUSTOM_GAS_LIMIT, + value: 'mockCustomGasLimit', + }) }) }) describe('setCustomGasTotal', function () { it('should create the correct action', function () { - assert.deepEqual( - setCustomGasTotal('mockCustomGasTotal'), - { type: SET_CUSTOM_GAS_TOTAL, value: 'mockCustomGasTotal' }, - ) + assert.deepEqual(setCustomGasTotal('mockCustomGasTotal'), { + type: SET_CUSTOM_GAS_TOTAL, + value: 'mockCustomGasTotal', + }) }) }) describe('setCustomGasErrors', function () { it('should create the correct action', function () { - assert.deepEqual( - setCustomGasErrors('mockErrorObject'), - { type: SET_CUSTOM_GAS_ERRORS, value: 'mockErrorObject' }, - ) + assert.deepEqual(setCustomGasErrors('mockErrorObject'), { + type: SET_CUSTOM_GAS_ERRORS, + value: 'mockErrorObject', + }) }) }) describe('setApiEstimatesLastRetrieved', function () { it('should create the correct action', function () { - assert.deepEqual( - setApiEstimatesLastRetrieved(1234), - { type: SET_API_ESTIMATES_LAST_RETRIEVED, value: 1234 }, - ) + assert.deepEqual(setApiEstimatesLastRetrieved(1234), { + type: SET_API_ESTIMATES_LAST_RETRIEVED, + value: 1234, + }) }) }) describe('resetCustomGasState', function () { it('should create the correct action', function () { - assert.deepEqual( - resetCustomGasState(), - { type: RESET_CUSTOM_GAS_STATE }, - ) + assert.deepEqual(resetCustomGasState(), { type: RESET_CUSTOM_GAS_STATE }) }) }) }) diff --git a/ui/app/ducks/gas/gas.duck.js b/ui/app/ducks/gas/gas.duck.js index 9d83a66e0..5a937e744 100644 --- a/ui/app/ducks/gas/gas.duck.js +++ b/ui/app/ducks/gas/gas.duck.js @@ -4,17 +4,16 @@ import { loadLocalStorageData, saveLocalStorageData, } from '../../../lib/local-storage-helpers' -import { - decGWEIToHexWEI, -} from '../../helpers/utils/conversions.util' -import { - isEthereumNetwork, -} from '../../selectors' +import { decGWEIToHexWEI } from '../../helpers/utils/conversions.util' +import { isEthereumNetwork } from '../../selectors' // Actions -const BASIC_GAS_ESTIMATE_LOADING_FINISHED = 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_FINISHED' -const BASIC_GAS_ESTIMATE_LOADING_STARTED = 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_STARTED' -const GAS_ESTIMATE_LOADING_FINISHED = 'metamask/gas/GAS_ESTIMATE_LOADING_FINISHED' +const BASIC_GAS_ESTIMATE_LOADING_FINISHED = + 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_FINISHED' +const BASIC_GAS_ESTIMATE_LOADING_STARTED = + 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_STARTED' +const GAS_ESTIMATE_LOADING_FINISHED = + 'metamask/gas/GAS_ESTIMATE_LOADING_FINISHED' const GAS_ESTIMATE_LOADING_STARTED = 'metamask/gas/GAS_ESTIMATE_LOADING_STARTED' const RESET_CUSTOM_GAS_STATE = 'metamask/gas/RESET_CUSTOM_GAS_STATE' const RESET_CUSTOM_DATA = 'metamask/gas/RESET_CUSTOM_DATA' @@ -24,9 +23,12 @@ const SET_CUSTOM_GAS_LIMIT = 'metamask/gas/SET_CUSTOM_GAS_LIMIT' const SET_CUSTOM_GAS_PRICE = 'metamask/gas/SET_CUSTOM_GAS_PRICE' const SET_CUSTOM_GAS_TOTAL = 'metamask/gas/SET_CUSTOM_GAS_TOTAL' const SET_PRICE_AND_TIME_ESTIMATES = 'metamask/gas/SET_PRICE_AND_TIME_ESTIMATES' -const SET_API_ESTIMATES_LAST_RETRIEVED = 'metamask/gas/SET_API_ESTIMATES_LAST_RETRIEVED' -const SET_BASIC_API_ESTIMATES_LAST_RETRIEVED = 'metamask/gas/SET_BASIC_API_ESTIMATES_LAST_RETRIEVED' -const SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED = 'metamask/gas/SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED' +const SET_API_ESTIMATES_LAST_RETRIEVED = + 'metamask/gas/SET_API_ESTIMATES_LAST_RETRIEVED' +const SET_BASIC_API_ESTIMATES_LAST_RETRIEVED = + 'metamask/gas/SET_BASIC_API_ESTIMATES_LAST_RETRIEVED' +const SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED = + 'metamask/gas/SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED' const initState = { customData: { @@ -56,7 +58,7 @@ const initState = { } // Reducer -export default function reducer (state = initState, action) { +export default function reducer(state = initState, action) { switch (action.type) { case BASIC_GAS_ESTIMATE_LOADING_STARTED: return { @@ -148,60 +150,67 @@ export default function reducer (state = initState, action) { } // Action Creators -export function basicGasEstimatesLoadingStarted () { +export function basicGasEstimatesLoadingStarted() { return { type: BASIC_GAS_ESTIMATE_LOADING_STARTED, } } -export function basicGasEstimatesLoadingFinished () { +export function basicGasEstimatesLoadingFinished() { return { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED, } } -export function gasEstimatesLoadingStarted () { +export function gasEstimatesLoadingStarted() { return { type: GAS_ESTIMATE_LOADING_STARTED, } } -export function gasEstimatesLoadingFinished () { +export function gasEstimatesLoadingFinished() { return { type: GAS_ESTIMATE_LOADING_FINISHED, } } -async function queryEthGasStationBasic () { - const apiKey = process.env.ETH_GAS_STATION_API_KEY ? `?api-key=${process.env.ETH_GAS_STATION_API_KEY}` : '' +async function queryEthGasStationBasic() { + const apiKey = process.env.ETH_GAS_STATION_API_KEY + ? `?api-key=${process.env.ETH_GAS_STATION_API_KEY}` + : '' const url = `https://ethgasstation.info/json/ethgasAPI.json${apiKey}` return await window.fetch(url, { - 'headers': {}, - 'referrer': 'http://ethgasstation.info/json/', - 'referrerPolicy': 'no-referrer-when-downgrade', - 'body': null, - 'method': 'GET', - 'mode': 'cors', + headers: {}, + referrer: 'http://ethgasstation.info/json/', + referrerPolicy: 'no-referrer-when-downgrade', + body: null, + method: 'GET', + mode: 'cors', }) } -async function queryEthGasStationPredictionTable () { - const apiKey = process.env.ETH_GAS_STATION_API_KEY ? `?api-key=${process.env.ETH_GAS_STATION_API_KEY}` : '' +async function queryEthGasStationPredictionTable() { + const apiKey = process.env.ETH_GAS_STATION_API_KEY + ? `?api-key=${process.env.ETH_GAS_STATION_API_KEY}` + : '' const url = `https://ethgasstation.info/json/predictTable.json${apiKey}` return await window.fetch(url, { - 'headers': {}, - 'referrer': 'http://ethgasstation.info/json/', - 'referrerPolicy': 'no-referrer-when-downgrade', - 'body': null, - 'method': 'GET', - 'mode': 'cors', + headers: {}, + referrer: 'http://ethgasstation.info/json/', + referrerPolicy: 'no-referrer-when-downgrade', + body: null, + method: 'GET', + mode: 'cors', }) } -export function fetchBasicGasEstimates () { +export function fetchBasicGasEstimates() { return async (dispatch, getState) => { const { basicPriceEstimatesLastRetrieved } = getState().gas - const timeLastRetrieved = basicPriceEstimatesLastRetrieved || loadLocalStorageData('BASIC_PRICE_ESTIMATES_LAST_RETRIEVED') || 0 + const timeLastRetrieved = + basicPriceEstimatesLastRetrieved || + loadLocalStorageData('BASIC_PRICE_ESTIMATES_LAST_RETRIEVED') || + 0 dispatch(basicGasEstimatesLoadingStarted()) @@ -210,7 +219,8 @@ export function fetchBasicGasEstimates () { basicEstimates = await fetchExternalBasicGasEstimates(dispatch) } else { const cachedBasicEstimates = loadLocalStorageData('BASIC_PRICE_ESTIMATES') - basicEstimates = cachedBasicEstimates || await fetchExternalBasicGasEstimates(dispatch) + basicEstimates = + cachedBasicEstimates || (await fetchExternalBasicGasEstimates(dispatch)) } dispatch(setBasicGasEstimateData(basicEstimates)) @@ -220,7 +230,7 @@ export function fetchBasicGasEstimates () { } } -async function fetchExternalBasicGasEstimates (dispatch) { +async function fetchExternalBasicGasEstimates(dispatch) { const response = await queryEthGasStationBasic() const { @@ -237,7 +247,7 @@ async function fetchExternalBasicGasEstimates (dispatch) { fastTimes10, fastestTimes10, safeLowTimes10, - ].map((price) => (new BigNumber(price)).div(10).toNumber()) + ].map((price) => new BigNumber(price).div(10).toNumber()) const basicEstimates = { safeLow, @@ -256,10 +266,13 @@ async function fetchExternalBasicGasEstimates (dispatch) { return basicEstimates } -export function fetchBasicGasAndTimeEstimates () { +export function fetchBasicGasAndTimeEstimates() { return async (dispatch, getState) => { const { basicPriceAndTimeEstimatesLastRetrieved } = getState().gas - const timeLastRetrieved = basicPriceAndTimeEstimatesLastRetrieved || loadLocalStorageData('BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED') || 0 + const timeLastRetrieved = + basicPriceAndTimeEstimatesLastRetrieved || + loadLocalStorageData('BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED') || + 0 dispatch(basicGasEstimatesLoadingStarted()) @@ -267,8 +280,12 @@ export function fetchBasicGasAndTimeEstimates () { if (Date.now() - timeLastRetrieved > 75000) { basicEstimates = await fetchExternalBasicGasAndTimeEstimates(dispatch) } else { - const cachedBasicEstimates = loadLocalStorageData('BASIC_GAS_AND_TIME_API_ESTIMATES') - basicEstimates = cachedBasicEstimates || await fetchExternalBasicGasAndTimeEstimates(dispatch) + const cachedBasicEstimates = loadLocalStorageData( + 'BASIC_GAS_AND_TIME_API_ESTIMATES', + ) + basicEstimates = + cachedBasicEstimates || + (await fetchExternalBasicGasAndTimeEstimates(dispatch)) } dispatch(setBasicGasEstimateData(basicEstimates)) @@ -277,7 +294,7 @@ export function fetchBasicGasAndTimeEstimates () { } } -async function fetchExternalBasicGasAndTimeEstimates (dispatch) { +async function fetchExternalBasicGasAndTimeEstimates(dispatch) { const response = await queryEthGasStationBasic() const { @@ -298,7 +315,7 @@ async function fetchExternalBasicGasAndTimeEstimates (dispatch) { fastTimes10, fastestTimes10, safeLowTimes10, - ].map((price) => (new BigNumber(price)).div(10).toNumber()) + ].map((price) => new BigNumber(price).div(10).toNumber()) const basicEstimates = { average, @@ -316,13 +333,16 @@ async function fetchExternalBasicGasAndTimeEstimates (dispatch) { const timeRetrieved = Date.now() saveLocalStorageData(basicEstimates, 'BASIC_GAS_AND_TIME_API_ESTIMATES') - saveLocalStorageData(timeRetrieved, 'BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED') + saveLocalStorageData( + timeRetrieved, + 'BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED', + ) dispatch(setBasicApiEstimatesLastRetrieved(timeRetrieved)) return basicEstimates } -function extrapolateY ({ higherY, lowerY, higherX, lowerX, xForExtrapolation }) { +function extrapolateY({ higherY, lowerY, higherX, lowerX, xForExtrapolation }) { /* eslint-disable no-param-reassign */ higherY = new BigNumber(higherY, 10) lowerY = new BigNumber(lowerY, 10) @@ -330,29 +350,35 @@ function extrapolateY ({ higherY, lowerY, higherX, lowerX, xForExtrapolation }) lowerX = new BigNumber(lowerX, 10) xForExtrapolation = new BigNumber(xForExtrapolation, 10) /* eslint-enable no-param-reassign */ - const slope = (higherY.minus(lowerY)).div(higherX.minus(lowerX)) - const newTimeEstimate = slope.times(higherX.minus(xForExtrapolation)).minus(higherY).negated() + const slope = higherY.minus(lowerY).div(higherX.minus(lowerX)) + const newTimeEstimate = slope + .times(higherX.minus(xForExtrapolation)) + .minus(higherY) + .negated() return Number(newTimeEstimate.toPrecision(10)) } -function getRandomArbitrary (minStr, maxStr) { +function getRandomArbitrary(minStr, maxStr) { const min = new BigNumber(minStr, 10) const max = new BigNumber(maxStr, 10) const random = new BigNumber(String(Math.random()), 10) return new BigNumber(random.times(max.minus(min)).plus(min)).toPrecision(10) } -function calcMedian (list) { - const medianPos = (Math.floor(list.length / 2) + Math.ceil(list.length / 2)) / 2 +function calcMedian(list) { + const medianPos = + (Math.floor(list.length / 2) + Math.ceil(list.length / 2)) / 2 return medianPos === Math.floor(medianPos) ? (list[medianPos - 1] + list[medianPos]) / 2 : list[Math.floor(medianPos)] } -function quartiles (data) { +function quartiles(data) { const lowerHalf = data.slice(0, Math.floor(data.length / 2)) - const upperHalf = data.slice(Math.floor(data.length / 2) + (data.length % 2 === 0 ? 0 : 1)) + const upperHalf = data.slice( + Math.floor(data.length / 2) + (data.length % 2 === 0 ? 0 : 1), + ) const median = calcMedian(data) const lowerQuartile = calcMedian(lowerHalf) const upperQuartile = calcMedian(upperHalf) @@ -363,18 +389,20 @@ function quartiles (data) { } } -function inliersByIQR (data, prop) { - const { lowerQuartile, upperQuartile } = quartiles(data.map((d) => (prop ? d[prop] : d))) +function inliersByIQR(data, prop) { + const { lowerQuartile, upperQuartile } = quartiles( + data.map((d) => (prop ? d[prop] : d)), + ) const IQR = upperQuartile - lowerQuartile - const lowerBound = lowerQuartile - (1.5 * IQR) - const upperBound = upperQuartile + (1.5 * IQR) + const lowerBound = lowerQuartile - 1.5 * IQR + const upperBound = upperQuartile + 1.5 * IQR return data.filter((d) => { const value = prop ? d[prop] : d return value >= lowerBound && value <= upperBound }) } -export function fetchGasEstimates (blockTime) { +export function fetchGasEstimates(blockTime) { return (dispatch, getState) => { const state = getState() @@ -386,63 +414,104 @@ export function fetchGasEstimates (blockTime) { priceAndTimeEstimatesLastRetrieved, priceAndTimeEstimates, } = state.gas - const timeLastRetrieved = priceAndTimeEstimatesLastRetrieved || loadLocalStorageData('GAS_API_ESTIMATES_LAST_RETRIEVED') || 0 + const timeLastRetrieved = + priceAndTimeEstimatesLastRetrieved || + loadLocalStorageData('GAS_API_ESTIMATES_LAST_RETRIEVED') || + 0 dispatch(gasEstimatesLoadingStarted()) - const promiseToFetch = Date.now() - timeLastRetrieved > 75000 - ? queryEthGasStationPredictionTable() - .then((r) => r.json()) - .then((r) => { - const estimatedPricesAndTimes = r.map(({ expectedTime, expectedWait, gasprice }) => ({ expectedTime, expectedWait, gasprice })) - const estimatedTimeWithUniquePrices = uniqBy(estimatedPricesAndTimes, ({ expectedTime }) => expectedTime) + const promiseToFetch = + Date.now() - timeLastRetrieved > 75000 + ? queryEthGasStationPredictionTable() + .then((r) => r.json()) + .then((r) => { + const estimatedPricesAndTimes = r.map( + ({ expectedTime, expectedWait, gasprice }) => ({ + expectedTime, + expectedWait, + gasprice, + }), + ) + const estimatedTimeWithUniquePrices = uniqBy( + estimatedPricesAndTimes, + ({ expectedTime }) => expectedTime, + ) - const withSupplementalTimeEstimates = flatten(estimatedTimeWithUniquePrices.map(({ expectedWait, gasprice }, i, arr) => { - const next = arr[i + 1] - if (!next) { - return [{ expectedWait, gasprice }] - } - const supplementalPrice = getRandomArbitrary(gasprice, next.gasprice) - const supplementalTime = extrapolateY({ - higherY: next.expectedWait, - lowerY: expectedWait, - higherX: next.gasprice, - lowerX: gasprice, - xForExtrapolation: supplementalPrice, + const withSupplementalTimeEstimates = flatten( + estimatedTimeWithUniquePrices.map( + ({ expectedWait, gasprice }, i, arr) => { + const next = arr[i + 1] + if (!next) { + return [{ expectedWait, gasprice }] + } + const supplementalPrice = getRandomArbitrary( + gasprice, + next.gasprice, + ) + const supplementalTime = extrapolateY({ + higherY: next.expectedWait, + lowerY: expectedWait, + higherX: next.gasprice, + lowerX: gasprice, + xForExtrapolation: supplementalPrice, + }) + const supplementalPrice2 = getRandomArbitrary( + supplementalPrice, + next.gasprice, + ) + const supplementalTime2 = extrapolateY({ + higherY: next.expectedWait, + lowerY: supplementalTime, + higherX: next.gasprice, + lowerX: supplementalPrice, + xForExtrapolation: supplementalPrice2, + }) + return [ + { expectedWait, gasprice }, + { + expectedWait: supplementalTime, + gasprice: supplementalPrice, + }, + { + expectedWait: supplementalTime2, + gasprice: supplementalPrice2, + }, + ] + }, + ), + ) + const withOutliersRemoved = inliersByIQR( + withSupplementalTimeEstimates.slice(0).reverse(), + 'expectedWait', + ).reverse() + const timeMappedToSeconds = withOutliersRemoved.map( + ({ expectedWait, gasprice }) => { + const expectedTime = new BigNumber(expectedWait) + .times(Number(blockTime), 10) + .toNumber() + return { + expectedTime, + gasprice: new BigNumber(gasprice, 10).toNumber(), + } + }, + ) + + const timeRetrieved = Date.now() + dispatch(setApiEstimatesLastRetrieved(timeRetrieved)) + saveLocalStorageData( + timeRetrieved, + 'GAS_API_ESTIMATES_LAST_RETRIEVED', + ) + saveLocalStorageData(timeMappedToSeconds, 'GAS_API_ESTIMATES') + + return timeMappedToSeconds }) - const supplementalPrice2 = getRandomArbitrary(supplementalPrice, next.gasprice) - const supplementalTime2 = extrapolateY({ - higherY: next.expectedWait, - lowerY: supplementalTime, - higherX: next.gasprice, - lowerX: supplementalPrice, - xForExtrapolation: supplementalPrice2, - }) - return [ - { expectedWait, gasprice }, - { expectedWait: supplementalTime, gasprice: supplementalPrice }, - { expectedWait: supplementalTime2, gasprice: supplementalPrice2 }, - ] - })) - const withOutliersRemoved = inliersByIQR(withSupplementalTimeEstimates.slice(0).reverse(), 'expectedWait').reverse() - const timeMappedToSeconds = withOutliersRemoved.map(({ expectedWait, gasprice }) => { - const expectedTime = (new BigNumber(expectedWait)).times(Number(blockTime), 10).toNumber() - return { - expectedTime, - gasprice: (new BigNumber(gasprice, 10).toNumber()), - } - }) - - const timeRetrieved = Date.now() - dispatch(setApiEstimatesLastRetrieved(timeRetrieved)) - saveLocalStorageData(timeRetrieved, 'GAS_API_ESTIMATES_LAST_RETRIEVED') - saveLocalStorageData(timeMappedToSeconds, 'GAS_API_ESTIMATES') - - return timeMappedToSeconds - }) - : Promise.resolve(priceAndTimeEstimates.length - ? priceAndTimeEstimates - : loadLocalStorageData('GAS_API_ESTIMATES')) + : Promise.resolve( + priceAndTimeEstimates.length + ? priceAndTimeEstimates + : loadLocalStorageData('GAS_API_ESTIMATES'), + ) return promiseToFetch.then((estimates) => { dispatch(setPricesAndTimeEstimates(estimates)) @@ -451,7 +520,7 @@ export function fetchGasEstimates (blockTime) { } } -export function setCustomGasPriceForRetry (newPrice) { +export function setCustomGasPriceForRetry(newPrice) { return (dispatch) => { if (newPrice === '0x0') { const { fast } = loadLocalStorageData('BASIC_PRICE_ESTIMATES') @@ -462,73 +531,73 @@ export function setCustomGasPriceForRetry (newPrice) { } } -export function setBasicGasEstimateData (basicGasEstimateData) { +export function setBasicGasEstimateData(basicGasEstimateData) { return { type: SET_BASIC_GAS_ESTIMATE_DATA, value: basicGasEstimateData, } } -export function setPricesAndTimeEstimates (estimatedPricesAndTimes) { +export function setPricesAndTimeEstimates(estimatedPricesAndTimes) { return { type: SET_PRICE_AND_TIME_ESTIMATES, value: estimatedPricesAndTimes, } } -export function setCustomGasPrice (newPrice) { +export function setCustomGasPrice(newPrice) { return { type: SET_CUSTOM_GAS_PRICE, value: newPrice, } } -export function setCustomGasLimit (newLimit) { +export function setCustomGasLimit(newLimit) { return { type: SET_CUSTOM_GAS_LIMIT, value: newLimit, } } -export function setCustomGasTotal (newTotal) { +export function setCustomGasTotal(newTotal) { return { type: SET_CUSTOM_GAS_TOTAL, value: newTotal, } } -export function setCustomGasErrors (newErrors) { +export function setCustomGasErrors(newErrors) { return { type: SET_CUSTOM_GAS_ERRORS, value: newErrors, } } -export function setApiEstimatesLastRetrieved (retrievalTime) { +export function setApiEstimatesLastRetrieved(retrievalTime) { return { type: SET_API_ESTIMATES_LAST_RETRIEVED, value: retrievalTime, } } -export function setBasicApiEstimatesLastRetrieved (retrievalTime) { +export function setBasicApiEstimatesLastRetrieved(retrievalTime) { return { type: SET_BASIC_API_ESTIMATES_LAST_RETRIEVED, value: retrievalTime, } } -export function setBasicPriceEstimatesLastRetrieved (retrievalTime) { +export function setBasicPriceEstimatesLastRetrieved(retrievalTime) { return { type: SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED, value: retrievalTime, } } -export function resetCustomGasState () { +export function resetCustomGasState() { return { type: RESET_CUSTOM_GAS_STATE } } -export function resetCustomData () { +export function resetCustomData() { return { type: RESET_CUSTOM_DATA } } diff --git a/ui/app/ducks/history/history.js b/ui/app/ducks/history/history.js index 9b716375b..e74576f7f 100644 --- a/ui/app/ducks/history/history.js +++ b/ui/app/ducks/history/history.js @@ -31,7 +31,8 @@ export default reducer // Selectors -export const getMostRecentOverviewPage = (state) => state[name].mostRecentOverviewPage +export const getMostRecentOverviewPage = (state) => + state[name].mostRecentOverviewPage // Actions / action-creators diff --git a/ui/app/ducks/locale/locale.js b/ui/app/ducks/locale/locale.js index 5eecbad9b..e7f14c40a 100644 --- a/ui/app/ducks/locale/locale.js +++ b/ui/app/ducks/locale/locale.js @@ -1,6 +1,6 @@ import * as actionConstants from '../../store/actionConstants' -export default function reduceLocaleMessages (state = {}, { type, value }) { +export default function reduceLocaleMessages(state = {}, { type, value }) { switch (type) { case actionConstants.SET_CURRENT_LOCALE: return { diff --git a/ui/app/ducks/metamask/metamask.js b/ui/app/ducks/metamask/metamask.js index f9c259f82..61abf2559 100644 --- a/ui/app/ducks/metamask/metamask.js +++ b/ui/app/ducks/metamask/metamask.js @@ -1,7 +1,7 @@ import * as actionConstants from '../../store/actionConstants' import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert' -export default function reduceMetamask (state = {}, action) { +export default function reduceMetamask(state = {}, action) { const metamaskState = { isInitialized: false, isUnlocked: false, @@ -50,7 +50,6 @@ export default function reduceMetamask (state = {}, action) { } switch (action.type) { - case actionConstants.UPDATE_METAMASK_STATE: return { ...metamaskState, ...action.value } @@ -206,7 +205,8 @@ export default function reduceMetamask (state = {}, action) { } // erase token-related state when switching back to native currency if (newSend.editingTransactionId && !newSend.token) { - const unapprovedTx = newSend?.unapprovedTxs?.[newSend.editingTransactionId] || {} + const unapprovedTx = + newSend?.unapprovedTxs?.[newSend.editingTransactionId] || {} const txParams = unapprovedTx.txParams || {} Object.assign(newSend, { tokenBalance: null, @@ -375,10 +375,13 @@ export const getCurrentLocale = (state) => state.metamask.currentLocale export const getAlertEnabledness = (state) => state.metamask.alertEnabledness -export const getInvalidCustomNetworkAlertEnabledness = (state) => getAlertEnabledness(state)[ALERT_TYPES.invalidCustomNetwork] +export const getInvalidCustomNetworkAlertEnabledness = (state) => + getAlertEnabledness(state)[ALERT_TYPES.invalidCustomNetwork] -export const getUnconnectedAccountAlertEnabledness = (state) => getAlertEnabledness(state)[ALERT_TYPES.unconnectedAccount] +export const getUnconnectedAccountAlertEnabledness = (state) => + getAlertEnabledness(state)[ALERT_TYPES.unconnectedAccount] -export const getUnconnectedAccountAlertShown = (state) => state.metamask.unconnectedAccountAlertShownOrigins +export const getUnconnectedAccountAlertShown = (state) => + state.metamask.unconnectedAccountAlertShownOrigins export const getTokens = (state) => state.metamask.tokens diff --git a/ui/app/ducks/send/send-duck.test.js b/ui/app/ducks/send/send-duck.test.js index 9731d5b2c..3c7ca415a 100644 --- a/ui/app/ducks/send/send-duck.test.js +++ b/ui/app/ducks/send/send-duck.test.js @@ -59,7 +59,10 @@ describe('Send Duck', function () { it('should set gasButtonGroupShown to true when receiving a SHOW_GAS_BUTTON_GROUP action', function () { assert.deepEqual( - SendReducer({ ...mockState, gasButtonGroupShown: false }, { type: SHOW_GAS_BUTTON_GROUP }), + SendReducer( + { ...mockState, gasButtonGroupShown: false }, + { type: SHOW_GAS_BUTTON_GROUP }, + ), { gasButtonGroupShown: true, ...mockState }, ) }) @@ -120,6 +123,9 @@ describe('Send Duck', function () { }) describe('updateSendErrors', function () { - assert.deepEqual(updateSendErrors('mockErrorObject'), { type: UPDATE_SEND_ERRORS, value: 'mockErrorObject' }) + assert.deepEqual(updateSendErrors('mockErrorObject'), { + type: UPDATE_SEND_ERRORS, + value: 'mockErrorObject', + }) }) }) diff --git a/ui/app/ducks/send/send.duck.js b/ui/app/ducks/send/send.duck.js index fce3ee3e9..c5202d442 100644 --- a/ui/app/ducks/send/send.duck.js +++ b/ui/app/ducks/send/send.duck.js @@ -13,7 +13,7 @@ const initState = { } // Reducer -export default function reducer (state = initState, action) { +export default function reducer(state = initState, action) { switch (action.type) { case OPEN_TO_DROPDOWN: return { @@ -51,29 +51,29 @@ export default function reducer (state = initState, action) { } // Action Creators -export function openToDropdown () { +export function openToDropdown() { return { type: OPEN_TO_DROPDOWN } } -export function closeToDropdown () { +export function closeToDropdown() { return { type: CLOSE_TO_DROPDOWN } } -export function showGasButtonGroup () { +export function showGasButtonGroup() { return { type: SHOW_GAS_BUTTON_GROUP } } -export function hideGasButtonGroup () { +export function hideGasButtonGroup() { return { type: HIDE_GAS_BUTTON_GROUP } } -export function updateSendErrors (errorObject) { +export function updateSendErrors(errorObject) { return { type: UPDATE_SEND_ERRORS, value: errorObject, } } -export function resetSendState () { +export function resetSendState() { return { type: RESET_SEND_STATE } } diff --git a/ui/app/ducks/swaps/swaps.js b/ui/app/ducks/swaps/swaps.js index 94c2d2af2..998648b35 100644 --- a/ui/app/ducks/swaps/swaps.js +++ b/ui/app/ducks/swaps/swaps.js @@ -2,6 +2,10 @@ import { createSlice } from '@reduxjs/toolkit' import BigNumber from 'bignumber.js' import log from 'loglevel' +import { + loadLocalStorageData, + saveLocalStorageData, +} from '../../../lib/local-storage-helpers' import { addToken, addUnapprovedTransaction, @@ -19,17 +23,35 @@ import { updateTransaction, resetBackgroundSwapsState, setSwapsLiveness, + setSelectedQuoteAggId, + setSwapsTxGasLimit, } from '../../store/actions' -import { AWAITING_SWAP_ROUTE, BUILD_QUOTE_ROUTE, LOADING_QUOTES_ROUTE, SWAPS_ERROR_ROUTE, SWAPS_MAINTENANCE_ROUTE } from '../../helpers/constants/routes' -import { fetchSwapsFeatureLiveness } from '../../pages/swaps/swaps.util' +import { + AWAITING_SWAP_ROUTE, + BUILD_QUOTE_ROUTE, + LOADING_QUOTES_ROUTE, + SWAPS_ERROR_ROUTE, + SWAPS_MAINTENANCE_ROUTE, +} from '../../helpers/constants/routes' +import { + fetchSwapsFeatureLiveness, + fetchSwapsGasPrices, +} from '../../pages/swaps/swaps.util' import { calcGasTotal } from '../../pages/send/send.utils' -import { decimalToHex, getValueFromWeiHex, hexMax, decGWEIToHexWEI, hexToDecimal, hexWEIToDecGWEI } from '../../helpers/utils/conversions.util' +import { + decimalToHex, + getValueFromWeiHex, + hexMax, + decGWEIToHexWEI, + hexToDecimal, + hexWEIToDecGWEI, +} from '../../helpers/utils/conversions.util' +import { conversionLessThan } from '../../helpers/utils/conversion-util' import { calcTokenAmount } from '../../helpers/utils/token-util' import { - getFastPriceEstimateInHexWEI, getSelectedAccount, getTokenExchangeRates, - conversionRateSelector as getConversionRate, + getUSDConversionRate, } from '../../selectors' import { ERROR_FETCHING_QUOTES, @@ -38,9 +60,14 @@ import { SWAP_FAILED_ERROR, SWAPS_FETCH_ORDER_CONFLICT, } from '../../helpers/constants/swaps' -import { SWAP, SWAP_APPROVAL } from '../../helpers/constants/transactions' -import { fetchBasicGasAndTimeEstimates, fetchGasEstimates, resetCustomGasState } from '../gas/gas.duck' -import { formatCurrency } from '../../helpers/utils/confirm-tx.util' +import { TRANSACTION_CATEGORIES } from '../../../../shared/constants/transaction' + +const GAS_PRICES_LOADING_STATES = { + INITIAL: 'INITIAL', + LOADING: 'LOADING', + FAILED: 'FAILED', + COMPLETED: 'COMPLETED', +} const initialState = { aggregatorMetadata: null, @@ -51,6 +78,14 @@ const initialState = { quotesFetchStartTime: null, topAssets: {}, toToken: null, + customGas: { + price: null, + limit: null, + loading: GAS_PRICES_LOADING_STATES.INITIAL, + priceEstimates: {}, + priceEstimatesLastRetrieved: 0, + fallBackPrice: null, + }, } const slice = createSlice({ @@ -62,6 +97,8 @@ const slice = createSlice({ state.approveTxId = null state.balanceError = false state.fetchingQuotes = false + state.customGas.limit = null + state.customGas.price = null }, retriedGetQuotes: (state) => { state.approveTxId = null @@ -89,6 +126,31 @@ const slice = createSlice({ setToToken: (state, action) => { state.toToken = action.payload }, + swapCustomGasModalClosed: (state) => { + state.customGas.price = null + state.customGas.limit = null + }, + swapCustomGasModalPriceEdited: (state, action) => { + state.customGas.price = action.payload + }, + swapCustomGasModalLimitEdited: (state, action) => { + state.customGas.limit = action.payload + }, + swapGasPriceEstimatesFetchStarted: (state) => { + state.customGas.loading = GAS_PRICES_LOADING_STATES.LOADING + }, + swapGasPriceEstimatesFetchFailed: (state) => { + state.customGas.loading = GAS_PRICES_LOADING_STATES.FAILED + }, + swapGasPriceEstimatesFetchCompleted: (state, action) => { + state.customGas.priceEstimates = action.payload.priceEstimates + state.customGas.loading = GAS_PRICES_LOADING_STATES.COMPLETED + state.customGas.priceEstimatesLastRetrieved = + action.payload.priceEstimatesLastRetrieved + }, + retrievedFallbackSwapsGasPrice: (state, action) => { + state.customGas.fallBackPrice = action.payload + }, }, }) @@ -110,25 +172,74 @@ export const getToToken = (state) => state.swaps.toToken export const getFetchingQuotes = (state) => state.swaps.fetchingQuotes -export const getQuotesFetchStartTime = (state) => state.swaps.quotesFetchStartTime +export const getQuotesFetchStartTime = (state) => + state.swaps.quotesFetchStartTime + +export const getSwapsCustomizationModalPrice = (state) => + state.swaps.customGas.price + +export const getSwapsCustomizationModalLimit = (state) => + state.swaps.customGas.limit + +export const swapGasPriceEstimateIsLoading = (state) => + state.swaps.customGas.loading === GAS_PRICES_LOADING_STATES.LOADING + +export const swapGasEstimateLoadingHasFailed = (state) => + state.swaps.customGas.loading === GAS_PRICES_LOADING_STATES.INITIAL + +export const getSwapGasPriceEstimateData = (state) => + state.swaps.customGas.priceEstimates + +export const getSwapsPriceEstimatesLastRetrieved = (state) => + state.swaps.customGas.priceEstimatesLastRetrieved + +export const getSwapsFallbackGasPrice = (state) => + state.swaps.customGas.fallBackPrice + +export function shouldShowCustomPriceTooLowWarning(state) { + const { average } = getSwapGasPriceEstimateData(state) + + const customGasPrice = getSwapsCustomizationModalPrice(state) + + if (!customGasPrice || average === undefined) { + return false + } + + const customPriceRisksSwapFailure = conversionLessThan( + { + value: customGasPrice, + fromNumericBase: 'hex', + fromDenomination: 'WEI', + toDenomination: 'GWEI', + }, + { value: average, fromNumericBase: 'dec' }, + ) + + return customPriceRisksSwapFailure +} // Background selectors const getSwapsState = (state) => state.metamask.swapsState -export const getSwapsFeatureLiveness = (state) => state.metamask.swapsState.swapsFeatureIsLive +export const getSwapsFeatureLiveness = (state) => + state.metamask.swapsState.swapsFeatureIsLive -export const getBackgroundSwapRouteState = (state) => state.metamask.swapsState.routeState +export const getBackgroundSwapRouteState = (state) => + state.metamask.swapsState.routeState -export const getCustomSwapsGas = (state) => state.metamask.swapsState.customMaxGas +export const getCustomSwapsGas = (state) => + state.metamask.swapsState.customMaxGas -export const getCustomSwapsGasPrice = (state) => state.metamask.swapsState.customGasPrice +export const getCustomSwapsGasPrice = (state) => + state.metamask.swapsState.customGasPrice export const getFetchParams = (state) => state.metamask.swapsState.fetchParams export const getQuotes = (state) => state.metamask.swapsState.quotes -export const getQuotesLastFetched = (state) => state.metamask.swapsState.quotesLastFetched +export const getQuotesLastFetched = (state) => + state.metamask.swapsState.quotesLastFetched export const getSelectedQuote = (state) => { const { selectedAggId, quotes } = getSwapsState(state) @@ -137,11 +248,13 @@ export const getSelectedQuote = (state) => { export const getSwapsErrorKey = (state) => getSwapsState(state)?.errorKey -export const getShowQuoteLoadingScreen = (state) => state.swaps.showQuoteLoadingScreen +export const getShowQuoteLoadingScreen = (state) => + state.swaps.showQuoteLoadingScreen export const getSwapsTokens = (state) => state.metamask.swapsState.tokens -export const getSwapsWelcomeMessageSeenStatus = (state) => state.metamask.swapsWelcomeMessageHasBeenShown +export const getSwapsWelcomeMessageSeenStatus = (state) => + state.metamask.swapsWelcomeMessageHasBeenShown export const getTopQuote = (state) => { const { topAggId, quotes } = getSwapsState(state) @@ -152,23 +265,16 @@ export const getApproveTxId = (state) => state.metamask.swapsState.approveTxId export const getTradeTxId = (state) => state.metamask.swapsState.tradeTxId -export const getUsedQuote = (state) => getSelectedQuote(state) || getTopQuote(state) +export const getUsedQuote = (state) => + getSelectedQuote(state) || getTopQuote(state) // Compound selectors -export const getDestinationTokenInfo = (state) => getFetchParams(state)?.metaData?.destinationTokenInfo +export const getDestinationTokenInfo = (state) => + getFetchParams(state)?.metaData?.destinationTokenInfo -export const getSwapsTradeTxParams = (state) => { - const { selectedAggId, topAggId, quotes } = getSwapsState(state) - const usedQuote = selectedAggId ? quotes[selectedAggId] : quotes[topAggId] - if (!usedQuote) { - return null - } - const { trade } = usedQuote - const gas = getCustomSwapsGas(state) || trade.gas - const gasPrice = getCustomSwapsGasPrice(state) || trade.gasPrice - return { ...trade, gas, gasPrice } -} +export const getUsedSwapsGasPrice = (state) => + getCustomSwapsGasPrice(state) || getSwapsFallbackGasPrice(state) export const getApproveTxParams = (state) => { const { approvalNeeded } = getSelectedQuote(state) || getTopQuote(state) || {} @@ -188,6 +294,9 @@ const { clearSwapsState, navigatedBackToBuildQuote, retriedGetQuotes, + swapGasPriceEstimatesFetchCompleted, + swapGasPriceEstimatesFetchStarted, + swapGasPriceEstimatesFetchFailed, setAggregatorMetadata, setBalanceError, setFetchingQuotes, @@ -195,6 +304,10 @@ const { setQuotesFetchStartTime, setTopAssets, setToToken, + swapCustomGasModalPriceEdited, + swapCustomGasModalLimitEdited, + retrievedFallbackSwapsGasPrice, + swapCustomGasModalClosed, } = actions export { @@ -206,6 +319,9 @@ export { setQuotesFetchStartTime as setSwapQuotesFetchStartTime, setTopAssets, setToToken as setSwapToToken, + swapCustomGasModalPriceEdited, + swapCustomGasModalLimitEdited, + swapCustomGasModalClosed, } export const navigateBackToBuildQuote = (history) => { @@ -228,23 +344,35 @@ export const prepareForRetryGetQuotes = () => { export const prepareToLeaveSwaps = () => { return async (dispatch) => { - dispatch(resetCustomGasState()) dispatch(clearSwapsState()) await dispatch(resetBackgroundSwapsState()) + } +} +export const swapsQuoteSelected = (aggId) => { + return (dispatch) => { + dispatch(swapCustomGasModalLimitEdited(null)) + dispatch(setSelectedQuoteAggId(aggId)) + dispatch(setSwapsTxGasLimit('')) } } export const fetchAndSetSwapsGasPriceInfo = () => { return async (dispatch) => { - const basicEstimates = await dispatch(fetchBasicGasAndTimeEstimates()) - dispatch(setSwapsTxGasPrice(decGWEIToHexWEI(basicEstimates.fastest))) - await dispatch(fetchGasEstimates(basicEstimates.blockTime)) + const basicEstimates = await dispatch(fetchMetaSwapsGasPriceEstimates()) + if (basicEstimates?.fast) { + dispatch(setSwapsTxGasPrice(decGWEIToHexWEI(basicEstimates.fast))) + } } } -export const fetchQuotesAndSetQuoteState = (history, inputValue, maxSlippage, metaMetricsEvent) => { +export const fetchQuotesAndSetQuoteState = ( + history, + inputValue, + maxSlippage, + metaMetricsEvent, +) => { return async (dispatch, getState) => { let swapsFeatureIsLive = false try { @@ -263,15 +391,21 @@ export const fetchQuotesAndSetQuoteState = (history, inputValue, maxSlippage, me const fetchParams = getFetchParams(state) const selectedAccount = getSelectedAccount(state) const balanceError = getBalanceError(state) - const fetchParamsFromToken = fetchParams?.metaData?.sourceTokenInfo?.symbol === 'ETH' ? - { - ...ETH_SWAPS_TOKEN_OBJECT, - string: getValueFromWeiHex({ value: selectedAccount.balance, numberOfDecimals: 4, toDenomination: 'ETH' }), - balance: hexToDecimal(selectedAccount.balance), - } : - fetchParams?.metaData?.sourceTokenInfo + const fetchParamsFromToken = + fetchParams?.metaData?.sourceTokenInfo?.symbol === 'ETH' + ? { + ...ETH_SWAPS_TOKEN_OBJECT, + string: getValueFromWeiHex({ + value: selectedAccount.balance, + numberOfDecimals: 4, + toDenomination: 'ETH', + }), + balance: hexToDecimal(selectedAccount.balance), + } + : fetchParams?.metaData?.sourceTokenInfo const selectedFromToken = getFromToken(state) || fetchParamsFromToken || {} - const selectedToToken = getToToken(state) || fetchParams?.metaData?.destinationTokenInfo || {} + const selectedToToken = + getToToken(state) || fetchParams?.metaData?.destinationTokenInfo || {} const { address: fromTokenAddress, symbol: fromTokenSymbol, @@ -294,16 +428,41 @@ export const fetchQuotesAndSetQuoteState = (history, inputValue, maxSlippage, me let destinationTokenAddedForSwap = false if (toTokenSymbol !== 'ETH' && !contractExchangeRates[toTokenAddress]) { destinationTokenAddedForSwap = true - await dispatch(addToken(toTokenAddress, toTokenSymbol, toTokenDecimals, toTokenIconUrl, true)) + await dispatch( + addToken( + toTokenAddress, + toTokenSymbol, + toTokenDecimals, + toTokenIconUrl, + true, + ), + ) } - if (fromTokenSymbol !== 'ETH' && !contractExchangeRates[fromTokenAddress] && fromTokenBalance && (new BigNumber(fromTokenBalance, 16)).gt(0)) { - dispatch(addToken(fromTokenAddress, fromTokenSymbol, fromTokenDecimals, fromTokenIconUrl, true)) + if ( + fromTokenSymbol !== 'ETH' && + !contractExchangeRates[fromTokenAddress] && + fromTokenBalance && + new BigNumber(fromTokenBalance, 16).gt(0) + ) { + dispatch( + addToken( + fromTokenAddress, + fromTokenSymbol, + fromTokenDecimals, + fromTokenIconUrl, + true, + ), + ) } const swapsTokens = getSwapsTokens(state) - const sourceTokenInfo = swapsTokens?.find(({ address }) => address === fromTokenAddress) || selectedFromToken - const destinationTokenInfo = swapsTokens?.find(({ address }) => address === toTokenAddress) || selectedToToken + const sourceTokenInfo = + swapsTokens?.find(({ address }) => address === fromTokenAddress) || + selectedFromToken + const destinationTokenInfo = + swapsTokens?.find(({ address }) => address === toTokenAddress) || + selectedToToken dispatch(setFromToken(selectedFromToken)) @@ -330,27 +489,32 @@ export const fetchQuotesAndSetQuoteState = (history, inputValue, maxSlippage, me const fetchStartTime = Date.now() dispatch(setQuotesFetchStartTime(fetchStartTime)) - const fetchAndSetQuotesPromise = dispatch(fetchAndSetQuotes( - { - slippage: maxSlippage, - sourceToken: fromTokenAddress, - destinationToken: toTokenAddress, - value: inputValue, - fromAddress: selectedAccount.address, - destinationTokenAddedForSwap, - balanceError, - sourceDecimals: fromTokenDecimals, - }, - { - sourceTokenInfo, - destinationTokenInfo, - accountBalance: selectedAccount.balance, - }, - )) + const fetchAndSetQuotesPromise = dispatch( + fetchAndSetQuotes( + { + slippage: maxSlippage, + sourceToken: fromTokenAddress, + destinationToken: toTokenAddress, + value: inputValue, + fromAddress: selectedAccount.address, + destinationTokenAddedForSwap, + balanceError, + sourceDecimals: fromTokenDecimals, + }, + { + sourceTokenInfo, + destinationTokenInfo, + accountBalance: selectedAccount.balance, + }, + ), + ) const gasPriceFetchPromise = dispatch(fetchAndSetSwapsGasPriceInfo()) - const [[fetchedQuotes, selectedAggId]] = await Promise.all([fetchAndSetQuotesPromise, gasPriceFetchPromise]) + const [[fetchedQuotes, selectedAggId]] = await Promise.all([ + fetchAndSetQuotesPromise, + gasPriceFetchPromise, + ]) if (Object.values(fetchedQuotes)?.length === 0) { metaMetricsEvent({ @@ -386,7 +550,10 @@ export const fetchQuotesAndSetQuoteState = (history, inputValue, maxSlippage, me token_from: fromTokenSymbol, token_from_amount: String(inputValue), token_to: toTokenSymbol, - token_to_amount: calcTokenAmount(newSelectedQuote.destinationAmount, newSelectedQuote.decimals || 18), + token_to_amount: calcTokenAmount( + newSelectedQuote.destinationAmount, + newSelectedQuote.decimals || 18, + ), request_type: balanceError ? 'Quote' : 'Order', slippage: maxSlippage, custom_slippage: maxSlippage !== 2, @@ -407,6 +574,7 @@ export const fetchQuotesAndSetQuoteState = (history, inputValue, maxSlippage, me } // TODO: Check for any errors we should expect to occur in production, and report others to Sentry log.error(`Error fetching quotes: `, e) + dispatch(setSwapsErrorKey(ERROR_FETCHING_QUOTES)) } @@ -438,28 +606,45 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => { await dispatch(stopPollingForQuotes()) history.push(AWAITING_SWAP_ROUTE) + const { fast: fastGasEstimate } = getSwapGasPriceEstimateData(state) + const usedQuote = getUsedQuote(state) const usedTradeTxParams = usedQuote.trade - const estimatedGasLimit = new BigNumber(usedQuote?.gasEstimate || decimalToHex(usedQuote?.averageGas || 0), 16) - const estimatedGasLimitWithMultiplier = estimatedGasLimit.times(1.4, 10).round(0).toString(16) - const maxGasLimit = customSwapsGas || hexMax((`0x${decimalToHex(usedQuote?.maxGas || 0)}`), estimatedGasLimitWithMultiplier) - usedTradeTxParams.gas = maxGasLimit + const estimatedGasLimit = new BigNumber( + usedQuote?.gasEstimate || decimalToHex(usedQuote?.averageGas || 0), + 16, + ) + const estimatedGasLimitWithMultiplier = estimatedGasLimit + .times(1.4, 10) + .round(0) + .toString(16) + const maxGasLimit = + customSwapsGas || + hexMax( + `0x${decimalToHex(usedQuote?.maxGas || 0)}`, + estimatedGasLimitWithMultiplier, + ) - const customConvertGasPrice = getCustomSwapsGasPrice(state) - const tradeTxParams = getSwapsTradeTxParams(state) - const fastGasEstimate = getFastPriceEstimateInHexWEI(state) - const usedGasPrice = customConvertGasPrice || tradeTxParams?.gasPrice || fastGasEstimate + const usedGasPrice = getUsedSwapsGasPrice(state) + usedTradeTxParams.gas = maxGasLimit usedTradeTxParams.gasPrice = usedGasPrice - const conversionRate = getConversionRate(state) - const destinationValue = calcTokenAmount(usedQuote.destinationAmount, destinationTokenInfo.decimals || 18).toPrecision(8) - const usedGasLimitEstimate = usedQuote?.gasEstimateWithRefund || (`0x${decimalToHex(usedQuote?.averageGas || 0)}`) - const totalGasLimitEstimate = (new BigNumber(usedGasLimitEstimate, 16)).plus(usedQuote.approvalNeeded?.gas || '0x0', 16).toString(16) - const gasEstimateTotalInEth = getValueFromWeiHex({ + const usdConversionRate = getUSDConversionRate(state) + const destinationValue = calcTokenAmount( + usedQuote.destinationAmount, + destinationTokenInfo.decimals || 18, + ).toPrecision(8) + const usedGasLimitEstimate = + usedQuote?.gasEstimateWithRefund || + `0x${decimalToHex(usedQuote?.averageGas || 0)}` + const totalGasLimitEstimate = new BigNumber(usedGasLimitEstimate, 16) + .plus(usedQuote.approvalNeeded?.gas || '0x0', 16) + .toString(16) + const gasEstimateTotalInUSD = getValueFromWeiHex({ value: calcGasTotal(totalGasLimitEstimate, usedGasPrice), toCurrency: 'usd', - conversionRate, + conversionRate: usdConversionRate, numberOfDecimals: 6, }) @@ -472,13 +657,20 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => { custom_slippage: slippage !== 2, best_quote_source: getTopQuote(state)?.aggregator, available_quotes: getQuotes(state)?.length, - other_quote_selected: usedQuote.aggregator !== getTopQuote(state)?.aggregator, - other_quote_selected_source: usedQuote.aggregator === getTopQuote(state)?.aggregator ? '' : usedQuote.aggregator, - gas_fees: formatCurrency(gasEstimateTotalInEth, 'usd')?.slice(1), + other_quote_selected: + usedQuote.aggregator !== getTopQuote(state)?.aggregator, + other_quote_selected_source: + usedQuote.aggregator === getTopQuote(state)?.aggregator + ? '' + : usedQuote.aggregator, + gas_fees: gasEstimateTotalInUSD, estimated_gas: estimatedGasLimit.toString(10), - suggested_gas_price: hexWEIToDecGWEI(usedGasPrice), - used_gas_price: hexWEIToDecGWEI(fastGasEstimate), - average_savings: usedQuote.savings?.performance, + suggested_gas_price: fastGasEstimate, + used_gas_price: hexWEIToDecGWEI(usedGasPrice), + average_savings: usedQuote.savings?.total, + performance_savings: usedQuote.savings?.performance, + fee_savings: usedQuote.savings?.fee, + median_metamask_fee: usedQuote.savings?.medianMetaMaskFee, } const metaMetricsConfig = { @@ -487,18 +679,32 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => { } metaMetricsEvent({ ...metaMetricsConfig }) - metaMetricsEvent({ ...metaMetricsConfig, excludeMetaMetricsId: true, properties: swapMetaData }) + metaMetricsEvent({ + ...metaMetricsConfig, + excludeMetaMetricsId: true, + properties: swapMetaData, + }) let finalApproveTxMeta const approveTxParams = getApproveTxParams(state) if (approveTxParams) { - const approveTxMeta = await dispatch(addUnapprovedTransaction({ ...approveTxParams, amount: '0x0' }, 'metamask')) + const approveTxMeta = await dispatch( + addUnapprovedTransaction( + { ...approveTxParams, amount: '0x0' }, + 'metamask', + ), + ) await dispatch(setApproveTxId(approveTxMeta.id)) - finalApproveTxMeta = await (dispatch(updateTransaction({ - ...approveTxMeta, - transactionCategory: SWAP_APPROVAL, - sourceTokenSymbol: sourceTokenInfo.symbol, - }, true))) + finalApproveTxMeta = await dispatch( + updateTransaction( + { + ...approveTxMeta, + transactionCategory: TRANSACTION_CATEGORIES.SWAP_APPROVAL, + sourceTokenSymbol: sourceTokenInfo.symbol, + }, + true, + ), + ) try { await dispatch(updateAndApproveTx(finalApproveTxMeta, true)) } catch (e) { @@ -508,19 +714,26 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => { } } - const tradeTxMeta = await dispatch(addUnapprovedTransaction(usedTradeTxParams, 'metamask')) + const tradeTxMeta = await dispatch( + addUnapprovedTransaction(usedTradeTxParams, 'metamask'), + ) dispatch(setTradeTxId(tradeTxMeta.id)) - const finalTradeTxMeta = await (dispatch(updateTransaction({ - ...tradeTxMeta, - sourceTokenSymbol: sourceTokenInfo.symbol, - destinationTokenSymbol: destinationTokenInfo.symbol, - transactionCategory: SWAP, - destinationTokenDecimals: destinationTokenInfo.decimals, - destinationTokenAddress: destinationTokenInfo.address, - swapMetaData, - swapTokenValue, - approvalTxId: finalApproveTxMeta?.id, - }, true))) + const finalTradeTxMeta = await dispatch( + updateTransaction( + { + ...tradeTxMeta, + sourceTokenSymbol: sourceTokenInfo.symbol, + destinationTokenSymbol: destinationTokenInfo.symbol, + transactionCategory: TRANSACTION_CATEGORIES.SWAP, + destinationTokenDecimals: destinationTokenInfo.decimals, + destinationTokenAddress: destinationTokenInfo.address, + swapMetaData, + swapTokenValue, + approvalTxId: finalApproveTxMeta?.id, + }, + true, + ), + ) try { await dispatch(updateAndApproveTx(finalTradeTxMeta, true)) } catch (e) { @@ -532,3 +745,68 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => { await forceUpdateMetamaskState(dispatch) } } + +export function fetchMetaSwapsGasPriceEstimates() { + return async (dispatch, getState) => { + const state = getState() + const priceEstimatesLastRetrieved = getSwapsPriceEstimatesLastRetrieved( + state, + ) + const timeLastRetrieved = + priceEstimatesLastRetrieved || + loadLocalStorageData('METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED') || + 0 + + dispatch(swapGasPriceEstimatesFetchStarted()) + + let priceEstimates + try { + if (Date.now() - timeLastRetrieved > 30000) { + priceEstimates = await fetchSwapsGasPrices() + } else { + const cachedPriceEstimates = loadLocalStorageData( + 'METASWAP_GAS_PRICE_ESTIMATES', + ) + priceEstimates = cachedPriceEstimates || (await fetchSwapsGasPrices()) + } + } catch (e) { + log.warn('Fetching swaps gas prices failed:', e) + + if (!e.message?.match(/NetworkError|Fetch failed with status:/u)) { + throw e + } + + dispatch(swapGasPriceEstimatesFetchFailed()) + + try { + const gasPrice = await global.ethQuery.gasPrice() + const gasPriceInDecGWEI = hexWEIToDecGWEI(gasPrice.toString(10)) + + dispatch(retrievedFallbackSwapsGasPrice(gasPriceInDecGWEI)) + return null + } catch (networkGasPriceError) { + console.error( + `Failed to retrieve fallback gas price: `, + networkGasPriceError, + ) + return null + } + } + + const timeRetrieved = Date.now() + + saveLocalStorageData(priceEstimates, 'METASWAP_GAS_PRICE_ESTIMATES') + saveLocalStorageData( + timeRetrieved, + 'METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED', + ) + + dispatch( + swapGasPriceEstimatesFetchCompleted({ + priceEstimates, + priceEstimatesLastRetrieved: timeRetrieved, + }), + ) + return priceEstimates + } +} diff --git a/ui/app/helpers/constants/connected-sites.js b/ui/app/helpers/constants/connected-sites.js index b5d6ad6bd..717460f29 100644 --- a/ui/app/helpers/constants/connected-sites.js +++ b/ui/app/helpers/constants/connected-sites.js @@ -1,3 +1,4 @@ export const STATUS_CONNECTED = 'STATUS_CONNECTED' -export const STATUS_CONNECTED_TO_ANOTHER_ACCOUNT = 'STATUS_CONNECTED_TO_ANOTHER_ACCOUNT' +export const STATUS_CONNECTED_TO_ANOTHER_ACCOUNT = + 'STATUS_CONNECTED_TO_ANOTHER_ACCOUNT' export const STATUS_NOT_CONNECTED = 'STATUS_NOT_CONNECTED' diff --git a/ui/app/helpers/constants/routes.js b/ui/app/helpers/constants/routes.js index 42ea9d060..efd09c151 100644 --- a/ui/app/helpers/constants/routes.js +++ b/ui/app/helpers/constants/routes.js @@ -9,6 +9,7 @@ const SECURITY_ROUTE = '/settings/security' 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 CONTACT_LIST_ROUTE = '/settings/contact-list' const CONTACT_EDIT_ROUTE = '/settings/contact-list/edit-contact' const CONTACT_ADD_ROUTE = '/settings/contact-list/add-contact' @@ -42,7 +43,8 @@ const INITIALIZE_ROUTE = '/initialize' const INITIALIZE_WELCOME_ROUTE = '/initialize/welcome' const INITIALIZE_UNLOCK_ROUTE = '/initialize/unlock' const INITIALIZE_CREATE_PASSWORD_ROUTE = '/initialize/create-password' -const INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE = '/initialize/create-password/import-with-seed-phrase' +const INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE = + '/initialize/create-password/import-with-seed-phrase' const INITIALIZE_SELECT_ACTION_ROUTE = '/initialize/select-action' const INITIALIZE_SEED_PHRASE_ROUTE = '/initialize/seed-phrase' const INITIALIZE_BACKUP_SEED_PHRASE_ROUTE = '/initialize/backup-seed-phrase' @@ -75,6 +77,7 @@ const PATH_NAME_MAP = { [ABOUT_US_ROUTE]: 'About Us Page', [ALERTS_ROUTE]: 'Alerts Settings Page', [NETWORKS_ROUTE]: 'Network Settings Page', + [NETWORKS_FORM_ROUTE]: 'Network 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', @@ -109,12 +112,16 @@ const PATH_NAME_MAP = { [INITIALIZE_WELCOME_ROUTE]: 'Install Welcome Page', [INITIALIZE_UNLOCK_ROUTE]: 'Initialization Unlock page', [INITIALIZE_CREATE_PASSWORD_ROUTE]: 'Initialization Create Password Page', - [INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE]: 'Initialization Import Account With Seed Phrase Page', - [INITIALIZE_SELECT_ACTION_ROUTE]: 'Initialization Choose Restore or New Account Page', + [INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE]: + 'Initialization Import Account With Seed Phrase Page', + [INITIALIZE_SELECT_ACTION_ROUTE]: + 'Initialization Choose Restore or New Account Page', [INITIALIZE_SEED_PHRASE_ROUTE]: 'Initialization Seed Phrase Page', - [INITIALIZE_BACKUP_SEED_PHRASE_ROUTE]: 'Initialization Backup Seed Phrase Page', + [INITIALIZE_BACKUP_SEED_PHRASE_ROUTE]: + 'Initialization Backup Seed Phrase Page', [INITIALIZE_END_OF_FLOW_ROUTE]: 'End of Initialization Page', - [INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE]: 'Initialization Confirm Seed Phrase Page', + [INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE]: + 'Initialization Confirm Seed Phrase Page', [INITIALIZE_METAMETRICS_OPT_IN_ROUTE]: 'MetaMetrics Opt In Page', [BUILD_QUOTE_ROUTE]: 'Swaps Build Quote Page', [VIEW_QUOTE_ROUTE]: 'Swaps View Quotes Page', @@ -172,6 +179,7 @@ export { CONTACT_MY_ACCOUNTS_VIEW_ROUTE, CONTACT_MY_ACCOUNTS_EDIT_ROUTE, NETWORKS_ROUTE, + NETWORKS_FORM_ROUTE, INITIALIZE_BACKUP_SEED_PHRASE_ROUTE, CONNECT_ROUTE, CONNECT_CONFIRM_PERMISSIONS_ROUTE, diff --git a/ui/app/helpers/constants/swaps.js b/ui/app/helpers/constants/swaps.js index a20000a0c..af22f84ea 100644 --- a/ui/app/helpers/constants/swaps.js +++ b/ui/app/helpers/constants/swaps.js @@ -1,5 +1,6 @@ // An address that the metaswap-api recognizes as ETH, in place of the token address that ERC-20 tokens have -export const ETH_SWAPS_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000' +export const ETH_SWAPS_TOKEN_ADDRESS = + '0x0000000000000000000000000000000000000000' export const ETH_SWAPS_TOKEN_OBJECT = { symbol: 'ETH', @@ -19,4 +20,5 @@ export const SWAPS_FETCH_ORDER_CONFLICT = 'swaps-fetch-order-conflict' // A gas value for ERC20 approve calls that should be sufficient for all ERC20 approve implementations export const DEFAULT_ERC20_APPROVE_GAS = '0x1d4c0' -export const SWAPS_CONTRACT_ADDRESS = '0x881d40237659c251811cec9c364ef91dc08d300c' +export const SWAPS_CONTRACT_ADDRESS = + '0x881d40237659c251811cec9c364ef91dc08d300c' diff --git a/ui/app/helpers/constants/transactions.js b/ui/app/helpers/constants/transactions.js index 848938852..e344587ba 100644 --- a/ui/app/helpers/constants/transactions.js +++ b/ui/app/helpers/constants/transactions.js @@ -1,56 +1,21 @@ -export const UNAPPROVED_STATUS = 'unapproved' -export const REJECTED_STATUS = 'rejected' -export const APPROVED_STATUS = 'approved' -export const SIGNED_STATUS = 'signed' -export const SUBMITTED_STATUS = 'submitted' -export const CONFIRMED_STATUS = 'confirmed' -export const FAILED_STATUS = 'failed' -export const DROPPED_STATUS = 'dropped' -export const CANCELLED_STATUS = 'cancelled' +import { + TRANSACTION_CATEGORIES, + TRANSACTION_STATUSES, +} from '../../../../shared/constants/transaction' export const PENDING_STATUS_HASH = { - [UNAPPROVED_STATUS]: true, - [APPROVED_STATUS]: true, - [SUBMITTED_STATUS]: true, + [TRANSACTION_STATUSES.UNAPPROVED]: true, + [TRANSACTION_STATUSES.APPROVED]: true, + [TRANSACTION_STATUSES.SUBMITTED]: true, } export const PRIORITY_STATUS_HASH = { ...PENDING_STATUS_HASH, - [CONFIRMED_STATUS]: true, + [TRANSACTION_STATUSES.CONFIRMED]: true, } -export const TOKEN_METHOD_TRANSFER = 'transfer' -export const TOKEN_METHOD_APPROVE = 'approve' -export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom' - export const TOKEN_CATEGORY_HASH = { - [TOKEN_METHOD_APPROVE]: true, - [TOKEN_METHOD_TRANSFER]: true, - [TOKEN_METHOD_TRANSFER_FROM]: true, + [TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE]: true, + [TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER]: true, + [TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM]: true, } - -export const SWAP = 'swap' -export const SWAP_APPROVAL = 'swapApproval' - -export const INCOMING_TRANSACTION = 'incoming' - -export const SEND_ETHER_ACTION_KEY = 'sentEther' -export const DEPLOY_CONTRACT_ACTION_KEY = 'contractDeployment' -export const APPROVE_ACTION_KEY = 'approve' -export const SEND_TOKEN_ACTION_KEY = 'sentTokens' -export const TRANSFER_FROM_ACTION_KEY = 'transferFrom' -export const SIGNATURE_REQUEST_KEY = 'signatureRequest' -export const DECRYPT_REQUEST_KEY = 'decryptRequest' -export const ENCRYPTION_PUBLIC_KEY_REQUEST_KEY = 'encryptionPublicKeyRequest' -export const CONTRACT_INTERACTION_KEY = 'contractInteraction' -export const CANCEL_ATTEMPT_ACTION_KEY = 'cancelAttempt' -export const DEPOSIT_TRANSACTION_KEY = 'deposit' - -// Transaction List Item Categories -// Used for UI distinction between transactions in the history list -export const TRANSACTION_CATEGORY_SEND = 'send' -export const TRANSACTION_CATEGORY_RECEIVE = 'receive' -export const TRANSACTION_CATEGORY_INTERACTION = 'interaction' -export const TRANSACTION_CATEGORY_APPROVAL = 'approval' -export const TRANSACTION_CATEGORY_SIGNATURE_REQUEST = 'signature-request' -export const TRANSACTION_CATEGORY_SWAP = 'swap' diff --git a/ui/app/helpers/higher-order-components/authenticated/authenticated.component.js b/ui/app/helpers/higher-order-components/authenticated/authenticated.component.js index c195d0e21..585e34707 100644 --- a/ui/app/helpers/higher-order-components/authenticated/authenticated.component.js +++ b/ui/app/helpers/higher-order-components/authenticated/authenticated.component.js @@ -3,12 +3,12 @@ import PropTypes from 'prop-types' import { Redirect, Route } from 'react-router-dom' import { UNLOCK_ROUTE, INITIALIZE_ROUTE } from '../../constants/routes' -export default function Authenticated (props) { +export default function Authenticated(props) { const { isUnlocked, completedOnboarding } = props switch (true) { case isUnlocked && completedOnboarding: - return + return case !completedOnboarding: return default: diff --git a/ui/app/helpers/higher-order-components/authenticated/authenticated.container.js b/ui/app/helpers/higher-order-components/authenticated/authenticated.container.js index 3b67b2cfe..1f956957b 100644 --- a/ui/app/helpers/higher-order-components/authenticated/authenticated.container.js +++ b/ui/app/helpers/higher-order-components/authenticated/authenticated.container.js @@ -2,7 +2,9 @@ import { connect } from 'react-redux' import Authenticated from './authenticated.component' const mapStateToProps = (state) => { - const { metamask: { isUnlocked, completedOnboarding } } = state + const { + metamask: { isUnlocked, completedOnboarding }, + } = state return { isUnlocked, diff --git a/ui/app/helpers/higher-order-components/feature-toggled-route.js b/ui/app/helpers/higher-order-components/feature-toggled-route.js index ed325f283..bcfca050b 100644 --- a/ui/app/helpers/higher-order-components/feature-toggled-route.js +++ b/ui/app/helpers/higher-order-components/feature-toggled-route.js @@ -2,8 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import { Redirect, Route } from 'react-router-dom' -export default function FeatureToggledRoute ({ flag, redirectRoute, ...props }) { - +export default function FeatureToggledRoute({ flag, redirectRoute, ...props }) { if (flag) { return } diff --git a/ui/app/helpers/higher-order-components/initialized/initialized.component.js b/ui/app/helpers/higher-order-components/initialized/initialized.component.js index 2042c0046..88a4deb1d 100644 --- a/ui/app/helpers/higher-order-components/initialized/initialized.component.js +++ b/ui/app/helpers/higher-order-components/initialized/initialized.component.js @@ -3,10 +3,12 @@ import PropTypes from 'prop-types' import { Redirect, Route } from 'react-router-dom' import { INITIALIZE_ROUTE } from '../../constants/routes' -export default function Initialized (props) { - return props.completedOnboarding - ? - : +export default function Initialized(props) { + return props.completedOnboarding ? ( + + ) : ( + + ) } Initialized.propTypes = { diff --git a/ui/app/helpers/higher-order-components/initialized/initialized.container.js b/ui/app/helpers/higher-order-components/initialized/initialized.container.js index 245655962..d7b5633a4 100644 --- a/ui/app/helpers/higher-order-components/initialized/initialized.container.js +++ b/ui/app/helpers/higher-order-components/initialized/initialized.container.js @@ -2,7 +2,9 @@ import { connect } from 'react-redux' import Initialized from './initialized.component' const mapStateToProps = (state) => { - const { metamask: { completedOnboarding } } = state + const { + metamask: { completedOnboarding }, + } = state return { completedOnboarding, diff --git a/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js b/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js index e14adb2ce..df1b20cb4 100644 --- a/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js +++ b/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js @@ -1,4 +1,3 @@ - import assert from 'assert' import configureMockStore from 'redux-mock-store' import { mount } from 'enzyme' @@ -21,14 +20,10 @@ const mockState = { describe('withModalProps', function () { it('should return a component wrapped with modal state props', function () { - const TestComponent = () => ( -
    Testing
    - ) + const TestComponent = () =>
    Testing
    const WrappedComponent = withModalProps(TestComponent) const store = configureMockStore()(mockState) - const wrapper = mount( - , - ) + const wrapper = mount() assert.ok(wrapper) const testComponent = wrapper.find(TestComponent).at(0) diff --git a/ui/app/helpers/higher-order-components/with-modal-props/with-modal-props.js b/ui/app/helpers/higher-order-components/with-modal-props/with-modal-props.js index b702ff24f..88dbfa925 100644 --- a/ui/app/helpers/higher-order-components/with-modal-props/with-modal-props.js +++ b/ui/app/helpers/higher-order-components/with-modal-props/with-modal-props.js @@ -16,6 +16,6 @@ const mapDispatchToProps = (dispatch) => { } } -export default function withModalProps (Component) { +export default function withModalProps(Component) { return connect(mapStateToProps, mapDispatchToProps)(Component) } diff --git a/ui/app/helpers/utils/common.util.js b/ui/app/helpers/utils/common.util.js index 4df816941..0c0d49c15 100644 --- a/ui/app/helpers/utils/common.util.js +++ b/ui/app/helpers/utils/common.util.js @@ -1,5 +1,3 @@ -export function camelCaseToCapitalize (str = '') { - return str - .replace(/([A-Z])/ug, ' $1') - .replace(/^./u, (s) => s.toUpperCase()) +export function camelCaseToCapitalize(str = '') { + return str.replace(/([A-Z])/gu, ' $1').replace(/^./u, (s) => s.toUpperCase()) } diff --git a/ui/app/helpers/utils/confirm-tx.util.js b/ui/app/helpers/utils/confirm-tx.util.js index 272f56fcc..8ff39e498 100644 --- a/ui/app/helpers/utils/confirm-tx.util.js +++ b/ui/app/helpers/utils/confirm-tx.util.js @@ -1,7 +1,7 @@ import currencyFormatter from 'currency-formatter' import currencies from 'currency-formatter/currencies' -import ethUtil from 'ethereumjs-util' import BigNumber from 'bignumber.js' +import { addHexPrefix } from '../../../../app/scripts/lib/util' import { unconfirmedTransactionsCountSelector } from '../../selectors' import { @@ -11,48 +11,56 @@ import { conversionGreaterThan, } from './conversion-util' -export function increaseLastGasPrice (lastGasPrice) { - return ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice || '0x0', 1.1, { - multiplicandBase: 16, - multiplierBase: 10, - toNumericBase: 'hex', - })) +export function increaseLastGasPrice(lastGasPrice) { + return addHexPrefix( + multiplyCurrencies(lastGasPrice || '0x0', 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + }), + ) } -export function hexGreaterThan (a, b) { +export function hexGreaterThan(a, b) { return conversionGreaterThan( { value: a, fromNumericBase: 'hex' }, { value: b, fromNumericBase: 'hex' }, ) } -export function getHexGasTotal ({ gasLimit, gasPrice }) { - return ethUtil.addHexPrefix(multiplyCurrencies(gasLimit || '0x0', gasPrice || '0x0', { - toNumericBase: 'hex', - multiplicandBase: 16, - multiplierBase: 16, - })) +export function getHexGasTotal({ gasLimit, gasPrice }) { + return addHexPrefix( + multiplyCurrencies(gasLimit || '0x0', gasPrice || '0x0', { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 16, + }), + ) } -export function addEth (...args) { - return args.reduce((acc, base) => { - return addCurrencies(acc, base, { +export function addEth(...args) { + return args.reduce((acc, ethAmount) => { + return addCurrencies(acc, ethAmount, { toNumericBase: 'dec', numberOfDecimals: 6, + aBase: 10, + bBase: 10, }) }) } -export function addFiat (...args) { - return args.reduce((acc, base) => { - return addCurrencies(acc, base, { +export function addFiat(...args) { + return args.reduce((acc, fiatAmount) => { + return addCurrencies(acc, fiatAmount, { toNumericBase: 'dec', numberOfDecimals: 2, + aBase: 10, + bBase: 10, }) }) } -export function getValueFromWeiHex ({ +export function getValueFromWeiHex({ value, fromCurrency = 'ETH', toCurrency, @@ -72,7 +80,7 @@ export function getValueFromWeiHex ({ }) } -export function getTransactionFee ({ +export function getTransactionFee({ value, fromCurrency = 'ETH', toCurrency, @@ -90,15 +98,18 @@ export function getTransactionFee ({ }) } -export function formatCurrency (value, currencyCode) { +export function formatCurrency(value, currencyCode) { const upperCaseCurrencyCode = currencyCode.toUpperCase() return currencies.find((currency) => currency.code === upperCaseCurrencyCode) - ? currencyFormatter.format(Number(value), { code: upperCaseCurrencyCode, style: 'currency' }) + ? currencyFormatter.format(Number(value), { + code: upperCaseCurrencyCode, + style: 'currency', + }) : value } -export function convertTokenToFiat ({ +export function convertTokenToFiat({ value, fromCurrency = 'ETH', toCurrency, @@ -117,7 +128,7 @@ export function convertTokenToFiat ({ }) } -export function hasUnconfirmedTransactions (state) { +export function hasUnconfirmedTransactions(state) { return unconfirmedTransactionsCountSelector(state) > 0 } @@ -128,10 +139,12 @@ export function hasUnconfirmedTransactions (state) { * @returns {string} The rounded number, or the original number if no * rounding was necessary. */ -export function roundExponential (decimalString) { +export function roundExponential(decimalString) { const PRECISION = 4 const bigNumberValue = new BigNumber(decimalString) // In JS, numbers with exponentials greater than 20 get displayed as an exponential. - return bigNumberValue.e > 20 ? bigNumberValue.toPrecision(PRECISION) : decimalString + return bigNumberValue.e > 20 + ? bigNumberValue.toPrecision(PRECISION) + : decimalString } diff --git a/ui/app/helpers/utils/confirm-tx.util.test.js b/ui/app/helpers/utils/confirm-tx.util.test.js index 065631c7d..0db49ef89 100644 --- a/ui/app/helpers/utils/confirm-tx.util.test.js +++ b/ui/app/helpers/utils/confirm-tx.util.test.js @@ -16,31 +16,19 @@ describe('Confirm Transaction utils', function () { describe('hexGreaterThan', function () { it('should return true if the first value is greater than the second value', function () { - assert.equal( - utils.hexGreaterThan('0xb', '0xa'), - true, - ) + assert.equal(utils.hexGreaterThan('0xb', '0xa'), true) }) it('should return false if the first value is less than the second value', function () { - assert.equal( - utils.hexGreaterThan('0xa', '0xb'), - false, - ) + assert.equal(utils.hexGreaterThan('0xa', '0xb'), false) }) it('should return false if the first value is equal to the second value', function () { - assert.equal( - utils.hexGreaterThan('0xa', '0xa'), - false, - ) + assert.equal(utils.hexGreaterThan('0xa', '0xa'), false) }) it('should correctly compare prefixed and non-prefixed hex values', function () { - assert.equal( - utils.hexGreaterThan('0xb', 'a'), - true, - ) + assert.equal(utils.hexGreaterThan('0xb', 'a'), true) }) }) @@ -62,15 +50,20 @@ describe('Confirm Transaction utils', function () { describe('addEth', function () { it('should add two values together rounding to 6 decimal places', function () { - assert.equal( - utils.addEth('0.12345678', '0'), - '0.123457', - ) + assert.equal(utils.addEth('0.12345678', '0'), '0.123457') }) it('should add any number of values together rounding to 6 decimal places', function () { assert.equal( - utils.addEth('0.1', '0.02', '0.003', '0.0004', '0.00005', '0.000006', '0.0000007'), + utils.addEth( + '0.1', + '0.02', + '0.003', + '0.0004', + '0.00005', + '0.000006', + '0.0000007', + ), '0.123457', ) }) @@ -78,15 +71,20 @@ describe('Confirm Transaction utils', function () { describe('addFiat', function () { it('should add two values together rounding to 2 decimal places', function () { - assert.equal( - utils.addFiat('0.12345678', '0'), - '0.12', - ) + assert.equal(utils.addFiat('0.12345678', '0'), '0.12') }) it('should add any number of values together rounding to 2 decimal places', function () { assert.equal( - utils.addFiat('0.1', '0.02', '0.003', '0.0004', '0.00005', '0.000006', '0.0000007'), + utils.addFiat( + '0.1', + '0.02', + '0.003', + '0.0004', + '0.00005', + '0.000006', + '0.0000007', + ), '0.12', ) }) @@ -95,7 +93,10 @@ describe('Confirm Transaction utils', function () { describe('getValueFromWeiHex', function () { it('should get the transaction amount in ETH', function () { const ethTransactionAmount = utils.getValueFromWeiHex({ - value: '0xde0b6b3a7640000', toCurrency: 'ETH', conversionRate: 468.58, numberOfDecimals: 6, + value: '0xde0b6b3a7640000', + toCurrency: 'ETH', + conversionRate: 468.58, + numberOfDecimals: 6, }) assert.equal(ethTransactionAmount, '1') @@ -103,7 +104,10 @@ describe('Confirm Transaction utils', function () { it('should get the transaction amount in fiat', function () { const fiatTransactionAmount = utils.getValueFromWeiHex({ - value: '0xde0b6b3a7640000', toCurrency: 'usd', conversionRate: 468.58, numberOfDecimals: 2, + value: '0xde0b6b3a7640000', + toCurrency: 'usd', + conversionRate: 468.58, + numberOfDecimals: 2, }) assert.equal(fiatTransactionAmount, '468.58') @@ -113,7 +117,10 @@ describe('Confirm Transaction utils', function () { describe('getTransactionFee', function () { it('should get the transaction fee in ETH', function () { const ethTransactionFee = utils.getTransactionFee({ - value: '0x1319718a5000', toCurrency: 'ETH', conversionRate: 468.58, numberOfDecimals: 6, + value: '0x1319718a5000', + toCurrency: 'ETH', + conversionRate: 468.58, + numberOfDecimals: 6, }) assert.equal(ethTransactionFee, '0.000021') @@ -121,7 +128,10 @@ describe('Confirm Transaction utils', function () { it('should get the transaction fee in fiat', function () { const fiatTransactionFee = utils.getTransactionFee({ - value: '0x1319718a5000', toCurrency: 'usd', conversionRate: 468.58, numberOfDecimals: 2, + value: '0x1319718a5000', + toCurrency: 'usd', + conversionRate: 468.58, + numberOfDecimals: 2, }) assert.equal(fiatTransactionFee, '0.01') diff --git a/ui/app/helpers/utils/conversion-util.js b/ui/app/helpers/utils/conversion-util.js index 152913b50..ca6bad62b 100644 --- a/ui/app/helpers/utils/conversion-util.js +++ b/ui/app/helpers/utils/conversion-util.js @@ -1,25 +1,25 @@ /* Currency Conversion Utility -* This utility function can be used for converting currency related values within metamask. -* The caller should be able to pass it a value, along with information about the value's -* numeric base, denomination and currency, and the desired numeric base, denomination and -* currency. It should return a single value. -* -* @param {(number | string | BN)} value - The value to convert. -* @param {Object} [options] Options to specify details of the conversion -* @param {string} [options.fromCurrency = 'ETH' | 'USD'] The currency of the passed value -* @param {string} [options.toCurrency = 'ETH' | 'USD'] The desired currency of the result -* @param {string} [options.fromNumericBase = 'hex' | 'dec' | 'BN'] The numeric basic of the passed value. -* @param {string} [options.toNumericBase = 'hex' | 'dec' | 'BN'] The desired numeric basic of the result. -* @param {string} [options.fromDenomination = 'WEI'] The denomination of the passed value -* @param {string} [options.numberOfDecimals] The desired number of decimals in the result -* @param {string} [options.roundDown] The desired number of decimals to round down to -* @param {number} [options.conversionRate] The rate to use to make the fromCurrency -> toCurrency conversion -* @returns {(number | string | BN)} -* -* The utility passes value along with the options as a single object to the `converter` function. -* `converter` conditional modifies the supplied `value` property, depending -* on the accompanying options. -*/ + * This utility function can be used for converting currency related values within metamask. + * The caller should be able to pass it a value, along with information about the value's + * numeric base, denomination and currency, and the desired numeric base, denomination and + * currency. It should return a single value. + * + * @param {(number | string | BN)} value - The value to convert. + * @param {Object} [options] - Options to specify details of the conversion + * @param {string} [options.fromCurrency = 'ETH' | 'USD'] - The currency of the passed value + * @param {string} [options.toCurrency = 'ETH' | 'USD'] - The desired currency of the result + * @param {string} [options.fromNumericBase = 'hex' | 'dec' | 'BN'] - The numeric basic of the passed value. + * @param {string} [options.toNumericBase = 'hex' | 'dec' | 'BN'] - The desired numeric basic of the result. + * @param {string} [options.fromDenomination = 'WEI'] - The denomination of the passed value + * @param {string} [options.numberOfDecimals] - The desired number of decimals in the result + * @param {string} [options.roundDown] - The desired number of decimals to round down to + * @param {number} [options.conversionRate] - The rate to use to make the fromCurrency -> toCurrency conversion + * @returns {(number | string | BN)} + * + * The utility passes value along with the options as a single object to the `converter` function. + * `converter` conditional modifies the supplied `value` property, depending + * on the accompanying options. + */ import BigNumber from 'bignumber.js' @@ -50,10 +50,15 @@ const toSpecifiedDenomination = { } const baseChange = { hex: (n) => n.toString(16), - dec: (n) => (new BigNumber(n)).toString(10), + dec: (n) => new BigNumber(n).toString(10), BN: (n) => new BN(n.toString(16)), } +// Utility function for checking base types +const isValidBase = (base) => { + return Number.isInteger(base) && base > 1 +} + /** * Defines the base type of numeric value * @typedef {('hex' | 'dec' | 'BN')} NumericBase @@ -92,7 +97,9 @@ const converter = ({ invertConversionRate, roundDown, }) => { - let convertedValue = fromNumericBase ? toBigNumber[fromNumericBase](value) : value + let convertedValue = fromNumericBase + ? toBigNumber[fromNumericBase](value) + : value if (fromDenomination) { convertedValue = toNormalizedDenomination[fromDenomination](convertedValue) @@ -100,7 +107,9 @@ const converter = ({ if (fromCurrency !== toCurrency) { if (conversionRate === null || conversionRate === undefined) { - throw new Error(`Converting from ${fromCurrency} to ${toCurrency} requires a conversionRate, but one was not provided`) + throw new Error( + `Converting from ${fromCurrency} to ${toCurrency} requires a conversionRate, but one was not provided`, + ) } let rate = toBigNumber.dec(conversionRate) if (invertConversionRate) { @@ -114,7 +123,10 @@ const converter = ({ } if (numberOfDecimals) { - convertedValue = convertedValue.round(numberOfDecimals, BigNumber.ROUND_HALF_DOWN) + convertedValue = convertedValue.round( + numberOfDecimals, + BigNumber.ROUND_HALF_DOWN, + ) } if (roundDown) { @@ -127,36 +139,41 @@ const converter = ({ return convertedValue } -const conversionUtil = (value, { - fromCurrency = null, - toCurrency = fromCurrency, - fromNumericBase, - toNumericBase, - fromDenomination, - toDenomination, - numberOfDecimals, - conversionRate, - invertConversionRate, -}) => converter({ - fromCurrency, - toCurrency, - fromNumericBase, - toNumericBase, - fromDenomination, - toDenomination, - numberOfDecimals, - conversionRate, - invertConversionRate, - value: value || '0', -}) +const conversionUtil = ( + value, + { + fromCurrency = null, + toCurrency = fromCurrency, + fromNumericBase, + toNumericBase, + fromDenomination, + toDenomination, + numberOfDecimals, + conversionRate, + invertConversionRate, + }, +) => + converter({ + fromCurrency, + toCurrency, + fromNumericBase, + toNumericBase, + fromDenomination, + toDenomination, + numberOfDecimals, + conversionRate, + invertConversionRate, + value: value || '0', + }) const getBigNumber = (value, base) => { + if (!isValidBase(base)) { + throw new Error('Must specificy valid base') + } + // We don't include 'number' here, because BigNumber will throw if passed // a number primitive it considers unsafe. - if ( - typeof value === 'string' || - value instanceof BigNumber - ) { + if (typeof value === 'string' || value instanceof BigNumber) { return new BigNumber(value, base) } @@ -164,11 +181,12 @@ const getBigNumber = (value, base) => { } const addCurrencies = (a, b, options = {}) => { - const { - aBase, - bBase, - ...conversionOptions - } = options + const { aBase, bBase, ...conversionOptions } = options + + if (!isValidBase(aBase) || !isValidBase(bBase)) { + throw new Error('Must specify valid aBase and bBase') + } + const value = getBigNumber(a, aBase).add(getBigNumber(b, bBase)) return converter({ @@ -178,13 +196,13 @@ const addCurrencies = (a, b, options = {}) => { } const subtractCurrencies = (a, b, options = {}) => { - const { - aBase, - bBase, - ...conversionOptions - } = options - const value = getBigNumber(a, aBase) - .minus(getBigNumber(b, bBase)) + const { aBase, bBase, ...conversionOptions } = options + + if (!isValidBase(aBase) || !isValidBase(bBase)) { + throw new Error('Must specify valid aBase and bBase') + } + + const value = getBigNumber(a, aBase).minus(getBigNumber(b, bBase)) return converter({ value, @@ -193,14 +211,15 @@ const subtractCurrencies = (a, b, options = {}) => { } const multiplyCurrencies = (a, b, options = {}) => { - const { - multiplicandBase, - multiplierBase, - ...conversionOptions - } = options + const { multiplicandBase, multiplierBase, ...conversionOptions } = options - const value = getBigNumber(a, multiplicandBase) - .times(getBigNumber(b, multiplierBase)) + if (!isValidBase(multiplicandBase) || !isValidBase(multiplierBase)) { + throw new Error('Must specify valid multiplicandBase and multiplierBase') + } + + const value = getBigNumber(a, multiplicandBase).times( + getBigNumber(b, multiplierBase), + ) return converter({ value, @@ -208,30 +227,21 @@ const multiplyCurrencies = (a, b, options = {}) => { }) } -const conversionGreaterThan = ( - { ...firstProps }, - { ...secondProps }, -) => { +const conversionGreaterThan = ({ ...firstProps }, { ...secondProps }) => { const firstValue = converter({ ...firstProps }) const secondValue = converter({ ...secondProps }) return firstValue.gt(secondValue) } -const conversionLessThan = ( - { ...firstProps }, - { ...secondProps }, -) => { +const conversionLessThan = ({ ...firstProps }, { ...secondProps }) => { const firstValue = converter({ ...firstProps }) const secondValue = converter({ ...secondProps }) return firstValue.lt(secondValue) } -const conversionMax = ( - { ...firstProps }, - { ...secondProps }, -) => { +const conversionMax = ({ ...firstProps }, { ...secondProps }) => { const firstIsGreater = conversionGreaterThan( { ...firstProps }, { ...secondProps }, @@ -240,19 +250,13 @@ const conversionMax = ( return firstIsGreater ? firstProps.value : secondProps.value } -const conversionGTE = ( - { ...firstProps }, - { ...secondProps }, -) => { +const conversionGTE = ({ ...firstProps }, { ...secondProps }) => { const firstValue = converter({ ...firstProps }) const secondValue = converter({ ...secondProps }) return firstValue.greaterThanOrEqualTo(secondValue) } -const conversionLTE = ( - { ...firstProps }, - { ...secondProps }, -) => { +const conversionLTE = ({ ...firstProps }, { ...secondProps }) => { const firstValue = converter({ ...firstProps }) const secondValue = converter({ ...secondProps }) return firstValue.lessThanOrEqualTo(secondValue) diff --git a/ui/app/helpers/utils/conversion-util.test.js b/ui/app/helpers/utils/conversion-util.test.js index 9c570f95d..34a7a5308 100644 --- a/ui/app/helpers/utils/conversion-util.test.js +++ b/ui/app/helpers/utils/conversion-util.test.js @@ -5,55 +5,180 @@ import { addCurrencies, conversionUtil } from './conversion-util' describe('conversion utils', function () { describe('addCurrencies()', function () { it('add whole numbers', function () { - const result = addCurrencies(3, 9) + const result = addCurrencies(3, 9, { + aBase: 10, + bBase: 10, + }) assert.equal(result.toNumber(), 12) }) it('add decimals', function () { - const result = addCurrencies(1.3, 1.9) + const result = addCurrencies(1.3, 1.9, { + aBase: 10, + bBase: 10, + }) assert.equal(result.toNumber(), 3.2) }) it('add repeating decimals', function () { - const result = addCurrencies(1 / 3, 1 / 9) + const result = addCurrencies(1 / 3, 1 / 9, { + aBase: 10, + bBase: 10, + }) assert.equal(result.toNumber(), 0.4444444444444444) }) }) describe('conversionUtil', function () { it('Returns expected types', function () { - const conv1 = conversionUtil(1000000000000000000, { fromNumericBase: 'dec', toNumericBase: 'hex' }) - const conv2 = conversionUtil(1, { fromNumericBase: 'dec', fromDenomination: 'ETH', toDenomination: 'WEI' }) - assert(typeof conv1 === 'string', 'conversion 1 should return type string') + const conv1 = conversionUtil(1000000000000000000, { + fromNumericBase: 'dec', + toNumericBase: 'hex', + }) + const conv2 = conversionUtil(1, { + fromNumericBase: 'dec', + fromDenomination: 'ETH', + toDenomination: 'WEI', + }) + assert( + typeof conv1 === 'string', + 'conversion 1 should return type string', + ) assert(conv2 instanceof BigNumber, 'conversion 2 should be a BigNumber') }) it('Converts from dec to hex', function () { - assert.equal(conversionUtil('1000000000000000000', { fromNumericBase: 'dec', toNumericBase: 'hex' }), 'de0b6b3a7640000') - assert.equal(conversionUtil('1500000000000000000', { fromNumericBase: 'dec', toNumericBase: 'hex' }), '14d1120d7b160000') + assert.equal( + conversionUtil('1000000000000000000', { + fromNumericBase: 'dec', + toNumericBase: 'hex', + }), + 'de0b6b3a7640000', + ) + assert.equal( + conversionUtil('1500000000000000000', { + fromNumericBase: 'dec', + toNumericBase: 'hex', + }), + '14d1120d7b160000', + ) }) it('Converts hex formatted numbers to dec', function () { - assert.equal(conversionUtil('0xde0b6b3a7640000', { fromNumericBase: 'hex', toNumericBase: 'dec' }), 1000000000000000000) - assert.equal(conversionUtil('0x14d1120d7b160000', { fromNumericBase: 'hex', toNumericBase: 'dec' }), 1500000000000000000) + assert.equal( + conversionUtil('0xde0b6b3a7640000', { + fromNumericBase: 'hex', + toNumericBase: 'dec', + }), + 1000000000000000000, + ) + assert.equal( + conversionUtil('0x14d1120d7b160000', { + fromNumericBase: 'hex', + toNumericBase: 'dec', + }), + 1500000000000000000, + ) }) it('Converts WEI to ETH', function () { - assert.equal(conversionUtil('0xde0b6b3a7640000', { fromNumericBase: 'hex', toNumericBase: 'dec', fromDenomination: 'WEI', toDenomination: 'ETH' }), 1) - assert.equal(conversionUtil('0x14d1120d7b160000', { fromNumericBase: 'hex', toNumericBase: 'dec', fromDenomination: 'WEI', toDenomination: 'ETH' }), 1.5) + assert.equal( + conversionUtil('0xde0b6b3a7640000', { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromDenomination: 'WEI', + toDenomination: 'ETH', + }), + 1, + ) + assert.equal( + conversionUtil('0x14d1120d7b160000', { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromDenomination: 'WEI', + toDenomination: 'ETH', + }), + 1.5, + ) }) it('Converts ETH to WEI', function () { - assert.equal(conversionUtil('1', { fromNumericBase: 'dec', fromDenomination: 'ETH', toDenomination: 'WEI' }), 1000000000000000000) - assert.equal(conversionUtil('1.5', { fromNumericBase: 'dec', fromDenomination: 'ETH', toDenomination: 'WEI' }), 1500000000000000000) + assert.equal( + conversionUtil('1', { + fromNumericBase: 'dec', + fromDenomination: 'ETH', + toDenomination: 'WEI', + }), + 1000000000000000000, + ) + assert.equal( + conversionUtil('1.5', { + fromNumericBase: 'dec', + fromDenomination: 'ETH', + toDenomination: 'WEI', + }), + 1500000000000000000, + ) }) it('Converts ETH to GWEI', function () { - assert.equal(conversionUtil('1', { fromNumericBase: 'dec', fromDenomination: 'ETH', toDenomination: 'GWEI' }), 1000000000) - assert.equal(conversionUtil('1.5', { fromNumericBase: 'dec', fromDenomination: 'ETH', toDenomination: 'GWEI' }), 1500000000) + assert.equal( + conversionUtil('1', { + fromNumericBase: 'dec', + fromDenomination: 'ETH', + toDenomination: 'GWEI', + }), + 1000000000, + ) + assert.equal( + conversionUtil('1.5', { + fromNumericBase: 'dec', + fromDenomination: 'ETH', + toDenomination: 'GWEI', + }), + 1500000000, + ) }) it('Converts ETH to USD', function () { - assert.equal(conversionUtil('1', { fromNumericBase: 'dec', toNumericBase: 'dec', toCurrency: 'usd', conversionRate: 468.58, numberOfDecimals: 2 }), 468.58) - assert.equal(conversionUtil('1.5', { fromNumericBase: 'dec', toNumericBase: 'dec', toCurrency: 'usd', conversionRate: 468.58, numberOfDecimals: 2 }), 702.87) + assert.equal( + conversionUtil('1', { + fromNumericBase: 'dec', + toNumericBase: 'dec', + toCurrency: 'usd', + conversionRate: 468.58, + numberOfDecimals: 2, + }), + 468.58, + ) + assert.equal( + conversionUtil('1.5', { + fromNumericBase: 'dec', + toNumericBase: 'dec', + toCurrency: 'usd', + conversionRate: 468.58, + numberOfDecimals: 2, + }), + 702.87, + ) }) it('Converts USD to ETH', function () { - assert.equal(conversionUtil('468.58', { fromNumericBase: 'dec', toNumericBase: 'dec', toCurrency: 'usd', conversionRate: 468.58, numberOfDecimals: 2, invertConversionRate: true }), 1) - assert.equal(conversionUtil('702.87', { fromNumericBase: 'dec', toNumericBase: 'dec', toCurrency: 'usd', conversionRate: 468.58, numberOfDecimals: 2, invertConversionRate: true }), 1.5) + assert.equal( + conversionUtil('468.58', { + fromNumericBase: 'dec', + toNumericBase: 'dec', + toCurrency: 'usd', + conversionRate: 468.58, + numberOfDecimals: 2, + invertConversionRate: true, + }), + 1, + ) + assert.equal( + conversionUtil('702.87', { + fromNumericBase: 'dec', + toNumericBase: 'dec', + toCurrency: 'usd', + conversionRate: 468.58, + numberOfDecimals: 2, + invertConversionRate: true, + }), + 1.5, + ) }) }) }) diff --git a/ui/app/helpers/utils/conversions.util.js b/ui/app/helpers/utils/conversions.util.js index dca6b04f6..a61043dbc 100644 --- a/ui/app/helpers/utils/conversions.util.js +++ b/ui/app/helpers/utils/conversions.util.js @@ -1,30 +1,37 @@ -import ethUtil from 'ethereumjs-util' import BigNumber from 'bignumber.js' import { ETH, GWEI, WEI } from '../constants/common' -import { conversionUtil, addCurrencies, subtractCurrencies } from './conversion-util' +import { addHexPrefix } from '../../../../app/scripts/lib/util' import { - formatCurrency, -} from './confirm-tx.util' + conversionUtil, + addCurrencies, + subtractCurrencies, +} from './conversion-util' +import { formatCurrency } from './confirm-tx.util' -export function bnToHex (inputBn) { - return ethUtil.addHexPrefix(inputBn.toString(16)) +export function bnToHex(inputBn) { + return addHexPrefix(inputBn.toString(16)) } -export function hexToDecimal (hexValue) { +export function hexToDecimal(hexValue) { return conversionUtil(hexValue, { fromNumericBase: 'hex', toNumericBase: 'dec', }) } -export function decimalToHex (decimal) { +export function decimalToHex(decimal) { return conversionUtil(decimal, { fromNumericBase: 'dec', toNumericBase: 'hex', }) } -export function getEthConversionFromWeiHex ({ value, fromCurrency = ETH, conversionRate, numberOfDecimals = 6 }) { +export function getEthConversionFromWeiHex({ + value, + fromCurrency = ETH, + conversionRate, + numberOfDecimals = 6, +}) { const denominations = [fromCurrency, GWEI, WEI] let nonZeroDenomination @@ -48,7 +55,7 @@ export function getEthConversionFromWeiHex ({ value, fromCurrency = ETH, convers return nonZeroDenomination } -export function getValueFromWeiHex ({ +export function getValueFromWeiHex({ value, fromCurrency = ETH, toCurrency, @@ -68,7 +75,7 @@ export function getValueFromWeiHex ({ }) } -export function getWeiHexFromDecimalValue ({ +export function getWeiHexFromDecimalValue({ value, fromCurrency, conversionRate, @@ -87,7 +94,7 @@ export function getWeiHexFromDecimalValue ({ }) } -export function addHexWEIsToDec (aHexWEI, bHexWEI) { +export function addHexWEIsToDec(aHexWEI, bHexWEI) { return addCurrencies(aHexWEI, bHexWEI, { aBase: 16, bBase: 16, @@ -96,7 +103,7 @@ export function addHexWEIsToDec (aHexWEI, bHexWEI) { }) } -export function subtractHexWEIsToDec (aHexWEI, bHexWEI) { +export function subtractHexWEIsToDec(aHexWEI, bHexWEI) { return subtractCurrencies(aHexWEI, bHexWEI, { aBase: 16, bBase: 16, @@ -105,7 +112,11 @@ export function subtractHexWEIsToDec (aHexWEI, bHexWEI) { }) } -export function decEthToConvertedCurrency (ethTotal, convertedCurrency, conversionRate) { +export function decEthToConvertedCurrency( + ethTotal, + convertedCurrency, + conversionRate, +) { return conversionUtil(ethTotal, { fromNumericBase: 'dec', toNumericBase: 'dec', @@ -116,7 +127,7 @@ export function decEthToConvertedCurrency (ethTotal, convertedCurrency, conversi }) } -export function decGWEIToHexWEI (decGWEI) { +export function decGWEIToHexWEI(decGWEI) { return conversionUtil(decGWEI, { fromNumericBase: 'dec', toNumericBase: 'hex', @@ -125,7 +136,7 @@ export function decGWEIToHexWEI (decGWEI) { }) } -export function hexWEIToDecGWEI (decGWEI) { +export function hexWEIToDecGWEI(decGWEI) { return conversionUtil(decGWEI, { fromNumericBase: 'hex', toNumericBase: 'dec', @@ -134,7 +145,7 @@ export function hexWEIToDecGWEI (decGWEI) { }) } -export function decETHToDecWEI (decEth) { +export function decETHToDecWEI(decEth) { return conversionUtil(decEth, { fromNumericBase: 'dec', toNumericBase: 'dec', @@ -143,7 +154,7 @@ export function decETHToDecWEI (decEth) { }) } -export function hexWEIToDecETH (hexWEI) { +export function hexWEIToDecETH(hexWEI) { return conversionUtil(hexWEI, { fromNumericBase: 'hex', toNumericBase: 'dec', @@ -152,17 +163,17 @@ export function hexWEIToDecETH (hexWEI) { }) } -export function hexMax (...hexNumbers) { +export function hexMax(...hexNumbers) { let max = hexNumbers[0] hexNumbers.slice(1).forEach((hexNumber) => { - if ((new BigNumber(hexNumber, 16)).gt(max, 16)) { + if (new BigNumber(hexNumber, 16).gt(max, 16)) { max = hexNumber } }) return max } -export function addHexes (aHexWEI, bHexWEI) { +export function addHexes(aHexWEI, bHexWEI) { return addCurrencies(aHexWEI, bHexWEI, { aBase: 16, bBase: 16, @@ -171,14 +182,37 @@ export function addHexes (aHexWEI, bHexWEI) { }) } -export function sumHexWEIsToRenderableFiat (hexWEIs, convertedCurrency, conversionRate) { - const hexWEIsSum = hexWEIs.filter((n) => n).reduce(addHexes) - const ethTotal = decEthToConvertedCurrency( +export function sumHexWEIs(hexWEIs) { + return hexWEIs.filter((n) => n).reduce(addHexes) +} + +export function sumHexWEIsToUnformattedFiat( + hexWEIs, + convertedCurrency, + conversionRate, +) { + const hexWEIsSum = sumHexWEIs(hexWEIs) + const convertedTotal = decEthToConvertedCurrency( getValueFromWeiHex({ - value: hexWEIsSum, toCurrency: 'ETH', numberOfDecimals: 4, + value: hexWEIsSum, + toCurrency: 'ETH', + numberOfDecimals: 4, }), convertedCurrency, conversionRate, ) - return formatCurrency(ethTotal, convertedCurrency) + return convertedTotal +} + +export function sumHexWEIsToRenderableFiat( + hexWEIs, + convertedCurrency, + conversionRate, +) { + const convertedTotal = sumHexWEIsToUnformattedFiat( + hexWEIs, + convertedCurrency, + conversionRate, + ) + return formatCurrency(convertedTotal, convertedCurrency) } diff --git a/ui/app/helpers/utils/fetch-with-cache.js b/ui/app/helpers/utils/fetch-with-cache.js index 470f2a8d6..c04d54086 100644 --- a/ui/app/helpers/utils/fetch-with-cache.js +++ b/ui/app/helpers/utils/fetch-with-cache.js @@ -4,8 +4,15 @@ import { } from '../../../lib/local-storage-helpers' import fetchWithTimeout from '../../../../app/scripts/lib/fetch-with-timeout' -const fetchWithCache = async (url, fetchOptions = {}, { cacheRefreshTime = 360000, timeout = 30000 } = {}) => { - if (fetchOptions.body || (fetchOptions.method && fetchOptions.method !== 'GET')) { +const fetchWithCache = async ( + url, + fetchOptions = {}, + { cacheRefreshTime = 360000, timeout = 30000 } = {}, +) => { + if ( + fetchOptions.body || + (fetchOptions.method && fetchOptions.method !== 'GET') + ) { throw new Error('fetchWithCache only supports GET requests') } if (!(fetchOptions.headers instanceof window.Headers)) { @@ -26,9 +33,7 @@ const fetchWithCache = async (url, fetchOptions = {}, { cacheRefreshTime = 36000 return cachedResponse } fetchOptions.headers.set('Content-Type', 'application/json') - const _fetch = timeout - ? fetchWithTimeout({ timeout }) - : window.fetch + const _fetch = timeout ? fetchWithTimeout({ timeout }) : window.fetch const response = await _fetch(url, { referrerPolicy: 'no-referrer-when-downgrade', body: null, @@ -37,7 +42,9 @@ const fetchWithCache = async (url, fetchOptions = {}, { cacheRefreshTime = 36000 ...fetchOptions, }) if (!response.ok) { - throw new Error(`Fetch failed with status '${response.status}': '${response.statusText}'`) + throw new Error( + `Fetch failed with status '${response.status}': '${response.statusText}'`, + ) } const responseJson = await response.json() const cacheEntry = { diff --git a/ui/app/helpers/utils/fetch-with-cache.test.js b/ui/app/helpers/utils/fetch-with-cache.test.js index 3c57c3794..c5981abd9 100644 --- a/ui/app/helpers/utils/fetch-with-cache.test.js +++ b/ui/app/helpers/utils/fetch-with-cache.test.js @@ -23,7 +23,9 @@ describe('Fetch with cache', function () { .get('/price') .reply(200, '{"average": 1}') - const response = await fetchWithCache('https://fetchwithcache.metamask.io/price') + const response = await fetchWithCache( + 'https://fetchwithcache.metamask.io/price', + ) assert.deepEqual(response, { average: 1, }) @@ -41,7 +43,9 @@ describe('Fetch with cache', function () { }, }) - const response = await fetchWithCache('https://fetchwithcache.metamask.io/price') + const response = await fetchWithCache( + 'https://fetchwithcache.metamask.io/price', + ) assert.deepEqual(response, { average: 1, }) @@ -59,7 +63,11 @@ describe('Fetch with cache', function () { }, }) - const response = await fetchWithCache('https://fetchwithcache.metamask.io/price', {}, { cacheRefreshTime: 123 }) + const response = await fetchWithCache( + 'https://fetchwithcache.metamask.io/price', + {}, + { cacheRefreshTime: 123 }, + ) assert.deepEqual(response, { average: 3, }) @@ -72,7 +80,12 @@ describe('Fetch with cache', function () { .reply(200, '{"average": 4}') await assert.rejects( - () => fetchWithCache('https://fetchwithcache.metamask.io/price', {}, { timeout: 20 }), + () => + fetchWithCache( + 'https://fetchwithcache.metamask.io/price', + {}, + { timeout: 20 }, + ), { name: 'AbortError', message: 'Aborted' }, ) }) @@ -82,8 +95,8 @@ describe('Fetch with cache', function () { .get('/price') .reply(500, '{"average": 6}') - await assert.rejects( - () => fetchWithCache('https://fetchwithcache.metamask.io/price'), + await assert.rejects(() => + fetchWithCache('https://fetchwithcache.metamask.io/price'), ) }) @@ -92,8 +105,10 @@ describe('Fetch with cache', function () { .post('/price') .reply(200, '{"average": 7}') - await assert.rejects( - () => fetchWithCache('https://fetchwithcache.metamask.io/price', { method: 'POST' }), + await assert.rejects(() => + fetchWithCache('https://fetchwithcache.metamask.io/price', { + method: 'POST', + }), ) }) @@ -102,8 +117,8 @@ describe('Fetch with cache', function () { .get('/price') .reply(200, '{"average": 8}') - await assert.rejects( - () => fetchWithCache('https://fetchwithcache.metamask.io/price', { body: 1 }), + await assert.rejects(() => + fetchWithCache('https://fetchwithcache.metamask.io/price', { body: 1 }), ) }) @@ -113,7 +128,10 @@ describe('Fetch with cache', function () { .reply(200, '{"average": 9}') await assert.rejects( - () => fetchWithCache('https://fetchwithcache.metamask.io/price', { headers: { 'Content-Type': 'text/plain' } }), + () => + fetchWithCache('https://fetchwithcache.metamask.io/price', { + headers: { 'Content-Type': 'text/plain' }, + }), { message: 'fetchWithCache only supports JSON responses' }, ) }) diff --git a/ui/app/helpers/utils/formatters.js b/ui/app/helpers/utils/formatters.js index d722ef09b..f4a120044 100644 --- a/ui/app/helpers/utils/formatters.js +++ b/ui/app/helpers/utils/formatters.js @@ -1,3 +1,3 @@ -export function formatETHFee (ethFee) { +export function formatETHFee(ethFee) { return `${ethFee} ETH` } diff --git a/ui/app/helpers/utils/gas-time-estimates.util.js b/ui/app/helpers/utils/gas-time-estimates.util.js index 5aadacf3a..7757f69ff 100644 --- a/ui/app/helpers/utils/gas-time-estimates.util.js +++ b/ui/app/helpers/utils/gas-time-estimates.util.js @@ -1,29 +1,42 @@ import BigNumber from 'bignumber.js' -export function newBigSigDig (n) { - return new BigNumber((new BigNumber(String(n))).toPrecision(15)) +export function newBigSigDig(n) { + return new BigNumber(new BigNumber(String(n)).toPrecision(15)) } -const createOp = (a, b, op) => (newBigSigDig(a))[op](newBigSigDig(b)) +const createOp = (a, b, op) => newBigSigDig(a)[op](newBigSigDig(b)) -export function bigNumMinus (a = 0, b = 0) { +export function bigNumMinus(a = 0, b = 0) { return createOp(a, b, 'minus') } -export function bigNumDiv (a = 0, b = 1) { +export function bigNumDiv(a = 0, b = 1) { return createOp(a, b, 'div') } -export function extrapolateY ({ higherY = 0, lowerY = 0, higherX = 0, lowerX = 0, xForExtrapolation = 0 }) { +export function extrapolateY({ + higherY = 0, + lowerY = 0, + higherX = 0, + lowerX = 0, + xForExtrapolation = 0, +}) { const slope = bigNumMinus(higherY, lowerY).div(bigNumMinus(higherX, lowerX)) - const newTimeEstimate = slope.times(bigNumMinus(higherX, xForExtrapolation)).minus(newBigSigDig(higherY)).negated() + const newTimeEstimate = slope + .times(bigNumMinus(higherX, xForExtrapolation)) + .minus(newBigSigDig(higherY)) + .negated() return newTimeEstimate.toNumber() } -export function getAdjacentGasPrices ({ gasPrices, priceToPosition }) { - const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => e <= priceToPosition && a[i + 1] >= priceToPosition) - const closestHigherValueIndex = gasPrices.findIndex((e) => e > priceToPosition) +export function getAdjacentGasPrices({ gasPrices, priceToPosition }) { + const closestLowerValueIndex = gasPrices.findIndex( + (e, i, a) => e <= priceToPosition && a[i + 1] >= priceToPosition, + ) + const closestHigherValueIndex = gasPrices.findIndex( + (e) => e > priceToPosition, + ) return { closestLowerValueIndex, closestHigherValueIndex, @@ -32,7 +45,7 @@ export function getAdjacentGasPrices ({ gasPrices, priceToPosition }) { } } -export function formatTimeEstimate (totalSeconds, greaterThanMax, lessThanMin) { +export function formatTimeEstimate(totalSeconds, greaterThanMax, lessThanMin) { const minutes = Math.floor(totalSeconds / 60) const seconds = Math.floor(totalSeconds % 60) @@ -49,14 +62,19 @@ export function formatTimeEstimate (totalSeconds, greaterThanMax, lessThanMin) { const formattedMin = `${minutes ? `${minutes} min` : ''}` const formattedSec = `${seconds ? `${seconds} sec` : ''}` - const formattedCombined = formattedMin && formattedSec - ? `${symbol}${formattedMin} ${formattedSec}` - : symbol + (formattedMin || formattedSec) + const formattedCombined = + formattedMin && formattedSec + ? `${symbol}${formattedMin} ${formattedSec}` + : symbol + (formattedMin || formattedSec) return formattedCombined } -export function getRawTimeEstimateData (currentGasPrice, gasPrices, estimatedTimes) { +export function getRawTimeEstimateData( + currentGasPrice, + gasPrices, + estimatedTimes, +) { const minGasPrice = gasPrices[0] const maxGasPrice = gasPrices[gasPrices.length - 1] let priceForEstimation = currentGasPrice @@ -88,12 +106,20 @@ export function getRawTimeEstimateData (currentGasPrice, gasPrices, estimatedTim } } -export function getRenderableTimeEstimate (currentGasPrice, gasPrices, estimatedTimes) { - const { - newTimeEstimate, - minGasPrice, - maxGasPrice, - } = getRawTimeEstimateData(currentGasPrice, gasPrices, estimatedTimes) +export function getRenderableTimeEstimate( + currentGasPrice, + gasPrices, + estimatedTimes, +) { + const { newTimeEstimate, minGasPrice, maxGasPrice } = getRawTimeEstimateData( + currentGasPrice, + gasPrices, + estimatedTimes, + ) - return formatTimeEstimate(newTimeEstimate, currentGasPrice > maxGasPrice, currentGasPrice < minGasPrice) + return formatTimeEstimate( + newTimeEstimate, + currentGasPrice > maxGasPrice, + currentGasPrice < minGasPrice, + ) } diff --git a/ui/app/helpers/utils/i18n-helper.js b/ui/app/helpers/utils/i18n-helper.js index fab317705..140a12fff 100644 --- a/ui/app/helpers/utils/i18n-helper.js +++ b/ui/app/helpers/utils/i18n-helper.js @@ -14,7 +14,7 @@ const missingSubstitutionErrors = {} * @param {Object} localeMessages - The map of messages for the current locale * @param {string} key - The message key * @param {string[]} substitutions - A list of message substitution replacements - * @returns {null|string} - The localized message + * @returns {null|string} The localized message */ export const getMessage = (localeCode, localeMessages, key, substitutions) => { if (!localeMessages) { @@ -23,7 +23,9 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => { if (!localeMessages[key]) { if (localeCode === 'en') { if (!missingMessageErrors[key]) { - missingMessageErrors[key] = new Error(`Unable to find value of key "${key}" for locale "${localeCode}"`) + missingMessageErrors[key] = new Error( + `Unable to find value of key "${key}" for locale "${localeCode}"`, + ) Sentry.captureException(missingMessageErrors[key]) log.error(missingMessageErrors[key]) if (process.env.IN_TEST === 'true') { @@ -35,7 +37,9 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => { warned[localeCode] = {} } warned[localeCode][key] = true - log.warn(`Translator - Unable to find value of key "${key}" for locale "${localeCode}"`) + log.warn( + `Translator - Unable to find value of key "${key}" for locale "${localeCode}"`, + ) } return null } @@ -43,12 +47,17 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => { let phrase = entry.message const hasSubstitutions = Boolean(substitutions && substitutions.length) - const hasReactSubstitutions = hasSubstitutions && - substitutions.some((element) => element !== null && (typeof element === 'function' || typeof element === 'object')) + const hasReactSubstitutions = + hasSubstitutions && + substitutions.some( + (element) => + element !== null && + (typeof element === 'function' || typeof element === 'object'), + ) // perform substitutions if (hasSubstitutions) { - const parts = phrase.split(/(\$\d)/ug) + const parts = phrase.split(/(\$\d)/gu) const substitutedParts = parts.map((part) => { const subMatch = part.match(/\$(\d)/u) @@ -57,31 +66,38 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => { } const substituteIndex = Number(subMatch[1]) - 1 if ( - (substitutions[substituteIndex] === null || substitutions[substituteIndex] === undefined) && + (substitutions[substituteIndex] === null || + substitutions[substituteIndex] === undefined) && !missingSubstitutionErrors[localeCode]?.[key] ) { if (!missingSubstitutionErrors[localeCode]) { missingSubstitutionErrors[localeCode] = {} } missingSubstitutionErrors[localeCode][key] = true - const error = new Error(`Insufficient number of substitutions for key "${key}" with locale "${localeCode}"`) + const error = new Error( + `Insufficient number of substitutions for key "${key}" with locale "${localeCode}"`, + ) log.error(error) Sentry.captureException(error) } return substitutions[substituteIndex] }) - phrase = hasReactSubstitutions - ? { substitutedParts } - : substitutedParts.join('') + phrase = hasReactSubstitutions ? ( + {substitutedParts} + ) : ( + substitutedParts.join('') + ) } return phrase } -export async function fetchLocale (localeCode) { +export async function fetchLocale(localeCode) { try { - const response = await window.fetch(`./_locales/${localeCode}/messages.json`) + const response = await window.fetch( + `./_locales/${localeCode}/messages.json`, + ) return await response.json() } catch (error) { log.error(`failed to fetch ${localeCode} locale because of ${error}`) @@ -91,7 +107,7 @@ export async function fetchLocale (localeCode) { const relativeTimeFormatLocaleData = new Set() -export async function loadRelativeTimeFormatLocaleData (localeCode) { +export async function loadRelativeTimeFormatLocaleData(localeCode) { const languageTag = localeCode.split('_')[0] if ( Intl.RelativeTimeFormat && @@ -103,7 +119,9 @@ export async function loadRelativeTimeFormatLocaleData (localeCode) { } } -async function fetchRelativeTimeFormatData (languageTag) { - const response = await window.fetch(`./intl/${languageTag}/relative-time-format-data.json`) +async function fetchRelativeTimeFormatData(languageTag) { + const response = await window.fetch( + `./intl/${languageTag}/relative-time-format-data.json`, + ) return await response.json() } diff --git a/ui/app/helpers/utils/i18n-helper.test.js b/ui/app/helpers/utils/i18n-helper.test.js index 4e41f8c20..3c0cca220 100644 --- a/ui/app/helpers/utils/i18n-helper.test.js +++ b/ui/app/helpers/utils/i18n-helper.test.js @@ -44,70 +44,56 @@ describe('i18n helper', function () { message: '$1 - $2 - $3', }, [TEST_KEY_6]: { - 'message': 'Testing a react substitution $1.', + message: 'Testing a react substitution $1.', }, [TEST_KEY_6_HELPER]: { - 'message': TEST_SUBSTITUTION_1, + message: TEST_SUBSTITUTION_1, }, [TEST_KEY_7]: { - 'message': 'Testing a react substitution $1 and another $2.', + message: 'Testing a react substitution $1 and another $2.', }, [TEST_KEY_7_HELPER_1]: { - 'message': TEST_SUBSTITUTION_1, + message: TEST_SUBSTITUTION_1, }, [TEST_KEY_7_HELPER_2]: { - 'message': TEST_SUBSTITUTION_2, + message: TEST_SUBSTITUTION_2, }, [TEST_KEY_8]: { - 'message': 'Testing a mix $1 of react substitutions $2 and string substitutions $3 + $4.', + message: + 'Testing a mix $1 of react substitutions $2 and string substitutions $3 + $4.', }, [TEST_KEY_8_HELPER_1]: { - 'message': TEST_SUBSTITUTION_3, + message: TEST_SUBSTITUTION_3, }, [TEST_KEY_8_HELPER_2]: { - 'message': TEST_SUBSTITUTION_4, + message: TEST_SUBSTITUTION_4, }, } const t = getMessage.bind(null, TEST_LOCALE_CODE, testLocaleMessages) const TEST_SUBSTITUTION_6 = ( -
    - { t(TEST_KEY_6_HELPER) } +
    + {t(TEST_KEY_6_HELPER)}
    ) const TEST_SUBSTITUTION_7_1 = ( -
    - { t(TEST_KEY_7_HELPER_1) } +
    + {t(TEST_KEY_7_HELPER_1)}
    ) const TEST_SUBSTITUTION_7_2 = ( -
    - { t(TEST_KEY_7_HELPER_2) } +
    + {t(TEST_KEY_7_HELPER_2)}
    ) const TEST_SUBSTITUTION_8_1 = ( -
    - { t(TEST_KEY_8_HELPER_1) } +
    + {t(TEST_KEY_8_HELPER_1)}
    ) const TEST_SUBSTITUTION_8_2 = ( -
    - { t(TEST_KEY_8_HELPER_2) } +
    + {t(TEST_KEY_8_HELPER_2)}
    ) @@ -119,17 +105,32 @@ describe('i18n helper', function () { it('should return the correct message when a single non-react substitution is made', function () { const result = t(TEST_KEY_2, [TEST_SUBSTITUTION_1]) - assert.equal(result, `This is a message with a single non-react substitution ${TEST_SUBSTITUTION_1}.`) + assert.equal( + result, + `This is a message with a single non-react substitution ${TEST_SUBSTITUTION_1}.`, + ) }) it('should return the correct message when two non-react substitutions are made', function () { const result = t(TEST_KEY_3, [TEST_SUBSTITUTION_1, TEST_SUBSTITUTION_2]) - assert.equal(result, `This is a message with two non-react substitutions ${TEST_SUBSTITUTION_1} and ${TEST_SUBSTITUTION_2}.`) + assert.equal( + result, + `This is a message with two non-react substitutions ${TEST_SUBSTITUTION_1} and ${TEST_SUBSTITUTION_2}.`, + ) }) it('should return the correct message when multiple non-react substitutions are made', function () { - const result = t(TEST_KEY_4, [TEST_SUBSTITUTION_1, TEST_SUBSTITUTION_2, TEST_SUBSTITUTION_3, TEST_SUBSTITUTION_4, TEST_SUBSTITUTION_5]) - assert.equal(result, `${TEST_SUBSTITUTION_1} - ${TEST_SUBSTITUTION_2} - ${TEST_SUBSTITUTION_3} - ${TEST_SUBSTITUTION_4} - ${TEST_SUBSTITUTION_5}`) + const result = t(TEST_KEY_4, [ + TEST_SUBSTITUTION_1, + TEST_SUBSTITUTION_2, + TEST_SUBSTITUTION_3, + TEST_SUBSTITUTION_4, + TEST_SUBSTITUTION_5, + ]) + assert.equal( + result, + `${TEST_SUBSTITUTION_1} - ${TEST_SUBSTITUTION_2} - ${TEST_SUBSTITUTION_3} - ${TEST_SUBSTITUTION_4} - ${TEST_SUBSTITUTION_5}`, + ) }) it('should correctly render falsey substitutions', function () { @@ -144,17 +145,34 @@ describe('i18n helper', function () { it('should return the correct message when a single react substitution is made', function () { const result = t(TEST_KEY_6, [TEST_SUBSTITUTION_6]) - assert.equal(shallow(result).html(), ' Testing a react substitution
    TEST_SUBSTITUTION_1
    .
    ') + assert.equal( + shallow(result).html(), + ' Testing a react substitution
    TEST_SUBSTITUTION_1
    .
    ', + ) }) it('should return the correct message when two react substitutions are made', function () { - const result = t(TEST_KEY_7, [TEST_SUBSTITUTION_7_1, TEST_SUBSTITUTION_7_2]) - assert.equal(shallow(result).html(), ' Testing a react substitution
    TEST_SUBSTITUTION_1
    and another
    TEST_SUBSTITUTION_2
    .
    ') + const result = t(TEST_KEY_7, [ + TEST_SUBSTITUTION_7_1, + TEST_SUBSTITUTION_7_2, + ]) + assert.equal( + shallow(result).html(), + ' Testing a react substitution
    TEST_SUBSTITUTION_1
    and another
    TEST_SUBSTITUTION_2
    .
    ', + ) }) it('should return the correct message when substituting a mix of react elements and strings', function () { - const result = t(TEST_KEY_8, [TEST_SUBSTITUTION_1, TEST_SUBSTITUTION_8_1, TEST_SUBSTITUTION_2, TEST_SUBSTITUTION_8_2]) - assert.equal(shallow(result).html(), ' Testing a mix TEST_SUBSTITUTION_1 of react substitutions
    TEST_SUBSTITUTION_3
    and string substitutions TEST_SUBSTITUTION_2 +
    TEST_SUBSTITUTION_4
    .
    ') + const result = t(TEST_KEY_8, [ + TEST_SUBSTITUTION_1, + TEST_SUBSTITUTION_8_1, + TEST_SUBSTITUTION_2, + TEST_SUBSTITUTION_8_2, + ]) + assert.equal( + shallow(result).html(), + ' Testing a mix TEST_SUBSTITUTION_1 of react substitutions
    TEST_SUBSTITUTION_3
    and string substitutions TEST_SUBSTITUTION_2 +
    TEST_SUBSTITUTION_4
    .
    ', + ) }) }) }) diff --git a/ui/app/helpers/utils/switch-direction.js b/ui/app/helpers/utils/switch-direction.js index 81170f43c..5d4075896 100644 --- a/ui/app/helpers/utils/switch-direction.js +++ b/ui/app/helpers/utils/switch-direction.js @@ -24,7 +24,8 @@ const switchDirection = async (direction) => { updatedLink.onload = () => { resolve() } - updatedLink.onerror = () => reject(new Error(`Failed to load '${direction}' stylesheet`)) + updatedLink.onerror = () => + reject(new Error(`Failed to load '${direction}' stylesheet`)) }) } diff --git a/ui/app/helpers/utils/token-util.js b/ui/app/helpers/utils/token-util.js index 700f5f9c8..fa581f910 100644 --- a/ui/app/helpers/utils/token-util.js +++ b/ui/app/helpers/utils/token-util.js @@ -15,36 +15,42 @@ const casedContractMap = Object.keys(contractMap).reduce((acc, base) => { const DEFAULT_SYMBOL = '' const DEFAULT_DECIMALS = '0' -async function getSymbolFromContract (tokenAddress) { +async function getSymbolFromContract(tokenAddress) { const token = util.getContractAtAddress(tokenAddress) try { const result = await token.symbol() return result[0] } catch (error) { - log.warn(`symbol() call for token at address ${tokenAddress} resulted in error:`, error) + log.warn( + `symbol() call for token at address ${tokenAddress} resulted in error:`, + error, + ) return undefined } } -async function getDecimalsFromContract (tokenAddress) { +async function getDecimalsFromContract(tokenAddress) { const token = util.getContractAtAddress(tokenAddress) try { const result = await token.decimals() const decimalsBN = result[0] - return decimalsBN && decimalsBN.toString() + return decimalsBN?.toString() } catch (error) { - log.warn(`decimals() call for token at address ${tokenAddress} resulted in error:`, error) + log.warn( + `decimals() call for token at address ${tokenAddress} resulted in error:`, + error, + ) return undefined } } -function getContractMetadata (tokenAddress) { +function getContractMetadata(tokenAddress) { return tokenAddress && casedContractMap[tokenAddress.toLowerCase()] } -async function getSymbol (tokenAddress) { +async function getSymbol(tokenAddress) { let symbol = await getSymbolFromContract(tokenAddress) if (!symbol) { @@ -58,7 +64,7 @@ async function getSymbol (tokenAddress) { return symbol } -async function getDecimals (tokenAddress) { +async function getDecimals(tokenAddress) { let decimals = await getDecimalsFromContract(tokenAddress) if (!decimals || decimals === '0') { @@ -72,14 +78,17 @@ async function getDecimals (tokenAddress) { return decimals } -export async function fetchSymbolAndDecimals (tokenAddress) { +export async function fetchSymbolAndDecimals(tokenAddress) { let symbol, decimals try { symbol = await getSymbol(tokenAddress) decimals = await getDecimals(tokenAddress) } catch (error) { - log.warn(`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`, error) + log.warn( + `symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`, + error, + ) } return { @@ -88,8 +97,10 @@ export async function fetchSymbolAndDecimals (tokenAddress) { } } -export async function getSymbolAndDecimals (tokenAddress, existingTokens = []) { - const existingToken = existingTokens.find(({ address }) => tokenAddress === address) +export async function getSymbolAndDecimals(tokenAddress, existingTokens = []) { + const existingToken = existingTokens.find( + ({ address }) => tokenAddress === address, + ) if (existingToken) { return { @@ -104,7 +115,10 @@ export async function getSymbolAndDecimals (tokenAddress, existingTokens = []) { symbol = await getSymbol(tokenAddress) decimals = await getDecimals(tokenAddress) } catch (error) { - log.warn(`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`, error) + log.warn( + `symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`, + error, + ) } return { @@ -113,7 +127,7 @@ export async function getSymbolAndDecimals (tokenAddress, existingTokens = []) { } } -export function tokenInfoGetter () { +export function tokenInfoGetter() { const tokens = {} return async (address) => { @@ -127,12 +141,12 @@ export function tokenInfoGetter () { } } -export function calcTokenAmount (value, decimals) { +export function calcTokenAmount(value, decimals) { const multiplier = Math.pow(10, Number(decimals || 0)) return new BigNumber(String(value)).div(multiplier) } -export function calcTokenValue (value, decimals) { +export function calcTokenValue(value, decimals) { const multiplier = Math.pow(10, Number(decimals || 0)) return new BigNumber(String(value)).times(multiplier) } @@ -147,7 +161,7 @@ export function calcTokenValue (value, decimals) { * @param {Object} tokenData - ethers Interface token data. * @returns {string | undefined} A lowercase address string. */ -export function getTokenAddressParam (tokenData = {}) { +export function getTokenAddressParam(tokenData = {}) { const value = tokenData?.args?._to || tokenData?.args?.[0] return value?.toString().toLowerCase() } @@ -159,11 +173,11 @@ export function getTokenAddressParam (tokenData = {}) { * @param {Object} tokenData - ethers Interface token data. * @returns {string | undefined} A decimal string value. */ -export function getTokenValueParam (tokenData = {}) { +export function getTokenValueParam(tokenData = {}) { return tokenData?.args?._value?.toString() } -export function getTokenValue (tokenParams = []) { +export function getTokenValue(tokenParams = []) { const valueData = tokenParams.find((param) => param.name === '_value') return valueData && valueData.value } @@ -180,7 +194,7 @@ export function getTokenValue (tokenParams = []) { * @param {boolean} [hideCurrencySymbol] - excludes the currency symbol in the result if true * @returns {string|undefined} The token amount in the user's chosen fiat currency, optionally formatted and localize */ -export function getTokenFiatAmount ( +export function getTokenFiatAmount( contractExchangeRate, conversionRate, currentCurrency, @@ -192,13 +206,21 @@ export function getTokenFiatAmount ( // If the conversionRate is 0 (i.e. unknown) or the contract exchange rate // is currently unknown, the fiat amount cannot be calculated so it is not // shown to the user - if (conversionRate <= 0 || !contractExchangeRate || tokenAmount === undefined) { + if ( + conversionRate <= 0 || + !contractExchangeRate || + tokenAmount === undefined + ) { return undefined } const currentTokenToFiatRate = multiplyCurrencies( contractExchangeRate, conversionRate, + { + multiplicandBase: 10, + multiplierBase: 10, + }, ) const currentTokenInFiat = conversionUtil(tokenAmount, { fromNumericBase: 'dec', @@ -211,7 +233,10 @@ export function getTokenFiatAmount ( if (hideCurrencySymbol) { result = formatCurrency(currentTokenInFiat, currentCurrency) } else if (formatted) { - result = `${formatCurrency(currentTokenInFiat, currentCurrency)} ${currentCurrency.toUpperCase()}` + result = `${formatCurrency( + currentTokenInFiat, + currentCurrency, + )} ${currentCurrency.toUpperCase()}` } else { result = currentTokenInFiat } diff --git a/ui/app/helpers/utils/transactions.util.js b/ui/app/helpers/utils/transactions.util.js index fbbbf214b..6dda958cc 100644 --- a/ui/app/helpers/utils/transactions.util.js +++ b/ui/app/helpers/utils/transactions.util.js @@ -1,18 +1,15 @@ -import ethUtil from 'ethereumjs-util' import MethodRegistry from 'eth-method-registry' import abi from 'human-standard-token-abi' import { ethers } from 'ethers' import log from 'loglevel' -import { - TRANSACTION_TYPE_CANCEL, - TRANSACTION_STATUS_CONFIRMED, -} from '../../../../app/scripts/controllers/transactions/enums' +import { addHexPrefix } from '../../../../app/scripts/lib/util' import { getEtherscanNetworkPrefix } from '../../../lib/etherscan-prefix-for-network' import { - TOKEN_METHOD_TRANSFER, - TOKEN_METHOD_APPROVE, - TOKEN_METHOD_TRANSFER_FROM, -} from '../constants/transactions' + TRANSACTION_CATEGORIES, + TRANSACTION_GROUP_STATUSES, + TRANSACTION_STATUSES, + TRANSACTION_TYPES, +} from '../../../../shared/constants/transaction' import fetchWithCache from './fetch-with-cache' import { addCurrencies } from './conversion-util' @@ -35,7 +32,7 @@ const hstInterface = new ethers.utils.Interface(abi) /** * @returns {EthersContractCall | undefined} */ -export function getTokenData (data) { +export function getTokenData(data) { try { return hstInterface.parseTransaction({ data }) } catch (error) { @@ -44,13 +41,16 @@ export function getTokenData (data) { } } -async function getMethodFrom4Byte (fourBytePrefix) { - const fourByteResponse = (await fetchWithCache(`https://www.4byte.directory/api/v1/signatures/?hex_signature=${fourBytePrefix}`, { - referrerPolicy: 'no-referrer-when-downgrade', - body: null, - method: 'GET', - mode: 'cors', - })) +async function getMethodFrom4Byte(fourBytePrefix) { + const fourByteResponse = await fetchWithCache( + `https://www.4byte.directory/api/v1/signatures/?hex_signature=${fourBytePrefix}`, + { + referrerPolicy: 'no-referrer-when-downgrade', + body: null, + method: 'GET', + mode: 'cors', + }, + ) if (fourByteResponse.count === 1) { return fourByteResponse.results[0].text_signature @@ -64,7 +64,7 @@ let registry * @param {string} fourBytePrefix - The prefix from the method code associated with the data * @returns {Object} */ -export async function getMethodDataAsync (fourBytePrefix) { +export async function getMethodDataAsync(fourBytePrefix) { try { const fourByteSig = getMethodFrom4Byte(fourBytePrefix).catch((e) => { log.error(e) @@ -101,29 +101,32 @@ export async function getMethodDataAsync (fourBytePrefix) { * Returns four-byte method signature from data * * @param {string} data - The hex data (@code txParams.data) of a transaction - * @returns {string} - The four-byte method signature + * @returns {string} The four-byte method signature */ -export function getFourBytePrefix (data = '') { - const prefixedData = ethUtil.addHexPrefix(data) +export function getFourBytePrefix(data = '') { + const prefixedData = addHexPrefix(data) const fourBytePrefix = prefixedData.slice(0, 10) return fourBytePrefix } /** - * Given an transaction category, returns a boolean which indicates whether the transaction is calling an erc20 token method - * - * @param {string} transactionCategory - The category of transaction being evaluated - * @returns {boolean} - whether the transaction is calling an erc20 token method - */ -export function isTokenMethodAction (transactionCategory) { + * Given an transaction category, returns a boolean which indicates whether the transaction is calling an erc20 token method + * + * @param {string} transactionCategory - The category of transaction being evaluated + * @returns {boolean} whether the transaction is calling an erc20 token method + */ +export function isTokenMethodAction(transactionCategory) { return [ - TOKEN_METHOD_TRANSFER, - TOKEN_METHOD_APPROVE, - TOKEN_METHOD_TRANSFER_FROM, + TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, + TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE, + TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM, ].includes(transactionCategory) } -export function getLatestSubmittedTxWithNonce (transactions = [], nonce = '0x0') { +export function getLatestSubmittedTxWithNonce( + transactions = [], + nonce = '0x0', +) { if (!transactions.length) { return {} } @@ -141,21 +144,23 @@ export function getLatestSubmittedTxWithNonce (transactions = [], nonce = '0x0') }, {}) } -export async function isSmartContractAddress (address) { +export async function isSmartContractAddress(address) { const code = await global.eth.getCode(address) // Geth will return '0x', and ganache-core v2.2.1 will return '0x0' const codeIsEmpty = !code || code === '0x' || code === '0x0' return !codeIsEmpty } -export function sumHexes (...args) { - const total = args.reduce((acc, base) => { - return addCurrencies(acc, base, { +export function sumHexes(...args) { + const total = args.reduce((acc, hexAmount) => { + return addCurrencies(acc, hexAmount, { toNumericBase: 'hex', + aBase: 16, + bBase: 16, }) }) - return ethUtil.addHexPrefix(total) + return addHexPrefix(total) } /** @@ -165,16 +170,23 @@ export function sumHexes (...args) { * @param {Object} transaction.txReceipt - The transaction receipt. * @returns {string} */ -export function getStatusKey (transaction) { - const { txReceipt: { status: receiptStatus } = {}, type, status } = transaction +export function getStatusKey(transaction) { + const { + txReceipt: { status: receiptStatus } = {}, + type, + status, + } = transaction // There was an on-chain failure if (receiptStatus === '0x0') { - return 'failed' + return TRANSACTION_STATUSES.FAILED } - if (status === TRANSACTION_STATUS_CONFIRMED && type === TRANSACTION_TYPE_CANCEL) { - return 'cancelled' + if ( + status === TRANSACTION_STATUSES.CONFIRMED && + type === TRANSACTION_TYPES.CANCEL + ) { + return TRANSACTION_GROUP_STATUSES.CANCELLED } return transaction.status @@ -186,7 +198,7 @@ export function getStatusKey (transaction) { * @param {string} hash * @param {Object} rpcPrefs */ -export function getBlockExplorerUrlForTx (networkId, hash, rpcPrefs = {}) { +export function getBlockExplorerUrlForTx(networkId, hash, rpcPrefs = {}) { if (rpcPrefs.blockExplorerUrl) { return `${rpcPrefs.blockExplorerUrl.replace(/\/+$/u, '')}/tx/${hash}` } diff --git a/ui/app/helpers/utils/transactions.util.test.js b/ui/app/helpers/utils/transactions.util.test.js index 503d6a641..80de38846 100644 --- a/ui/app/helpers/utils/transactions.util.test.js +++ b/ui/app/helpers/utils/transactions.util.test.js @@ -1,13 +1,20 @@ import assert from 'assert' +import { + TRANSACTION_CATEGORIES, + TRANSACTION_GROUP_STATUSES, + TRANSACTION_STATUSES, +} from '../../../../shared/constants/transaction' import * as utils from './transactions.util' describe('Transactions utils', function () { describe('getTokenData', function () { it('should return token data', function () { - const tokenData = utils.getTokenData('0xa9059cbb00000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000004e20') + const tokenData = utils.getTokenData( + '0xa9059cbb00000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000004e20', + ) assert.ok(tokenData) const { name, args } = tokenData - assert.equal(name, 'transfer') + assert.equal(name, TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER) const to = args._to const value = args._value.toString() assert.equal(to, '0x50A9D56C2B8BA9A5c7f2C08C3d26E0499F23a706') @@ -24,27 +31,27 @@ describe('Transactions utils', function () { const tests = [ { transaction: { - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, txReceipt: { status: '0x0', }, }, - expected: 'failed', + expected: TRANSACTION_STATUSES.FAILED, }, { transaction: { - status: 'confirmed', + status: TRANSACTION_STATUSES.CONFIRMED, txReceipt: { status: '0x1', }, }, - expected: 'confirmed', + expected: TRANSACTION_STATUSES.CONFIRMED, }, { transaction: { - status: 'pending', + status: TRANSACTION_GROUP_STATUSES.PENDING, }, - expected: 'pending', + expected: TRANSACTION_GROUP_STATUSES.PENDING, }, ] @@ -89,7 +96,10 @@ describe('Transactions utils', function () { ] tests.forEach(({ expected, networkId, hash, rpcPrefs }) => { - assert.equal(utils.getBlockExplorerUrlForTx(networkId, hash, rpcPrefs), expected) + assert.equal( + utils.getBlockExplorerUrlForTx(networkId, hash, rpcPrefs), + expected, + ) }) }) }) diff --git a/ui/app/helpers/utils/util.js b/ui/app/helpers/utils/util.js index 8cf64947d..020ed9325 100644 --- a/ui/app/helpers/utils/util.js +++ b/ui/app/helpers/utils/util.js @@ -3,16 +3,23 @@ import abi from 'human-standard-token-abi' import BigNumber from 'bignumber.js' import ethUtil from 'ethereumjs-util' import { DateTime } from 'luxon' +import { addHexPrefix } from '../../../../app/scripts/lib/util' // formatData :: ( date: ) -> String -export function formatDate (date, format = 'M/d/y \'at\' T') { +export function formatDate(date, format = "M/d/y 'at' T") { return DateTime.fromMillis(date).toFormat(format) } -export function formatDateWithYearContext (date, formatThisYear = 'MMM d', fallback = 'MMM d, y') { +export function formatDateWithYearContext( + date, + formatThisYear = 'MMM d', + fallback = 'MMM d, y', +) { const dateTime = DateTime.fromMillis(date) const now = DateTime.local() - return dateTime.toFormat(now.year === dateTime.year ? formatThisYear : fallback) + return dateTime.toFormat( + now.year === dateTime.year ? formatThisYear : fallback, + ) } const valueTable = { @@ -33,25 +40,36 @@ Object.keys(valueTable).forEach((currency) => { bnTable[currency] = new ethUtil.BN(valueTable[currency], 10) }) -export function isEthNetwork (netId) { - if (!netId || netId === '1' || netId === '3' || netId === '4' || netId === '42' || netId === '1337') { +export function isEthNetwork(netId) { + if ( + !netId || + netId === '1' || + netId === '3' || + netId === '4' || + netId === '42' || + netId === '1337' + ) { return true } return false } -export function valuesFor (obj) { +export function valuesFor(obj) { if (!obj) { return [] } - return Object.keys(obj) - .map(function (key) { - return obj[key] - }) + return Object.keys(obj).map(function (key) { + return obj[key] + }) } -export function addressSummary (address, firstSegLength = 10, lastSegLength = 4, includeHex = true) { +export function addressSummary( + address, + firstSegLength = 10, + lastSegLength = 4, + includeHex = true, +) { if (!address) { return '' } @@ -59,28 +77,38 @@ export function addressSummary (address, firstSegLength = 10, lastSegLength = 4, if (!includeHex) { checked = ethUtil.stripHexPrefix(checked) } - return checked ? `${checked.slice(0, firstSegLength)}...${checked.slice(checked.length - lastSegLength)}` : '...' + return checked + ? `${checked.slice(0, firstSegLength)}...${checked.slice( + checked.length - lastSegLength, + )}` + : '...' } -export function isValidAddress (address) { +export function isValidAddress(address) { if (!address || address === '0x0000000000000000000000000000000000000000') { return false } - const prefixed = address.startsWith('0X') ? address : ethUtil.addHexPrefix(address) - return (isAllOneCase(prefixed.slice(2)) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) + const prefixed = addHexPrefix(address) + return ( + (isAllOneCase(prefixed.slice(2)) && ethUtil.isValidAddress(prefixed)) || + ethUtil.isValidChecksumAddress(prefixed) + ) } -export function isValidDomainName (address) { - const match = punycode.toASCII(address) +export function isValidDomainName(address) { + const match = punycode + .toASCII(address) .toLowerCase() // Checks that the domain consists of at least one valid domain pieces separated by periods, followed by a tld // Each piece of domain name has only the characters a-z, 0-9, and a hyphen (but not at the start or end of chunk) // A chunk has minimum length of 1, but minimum tld is set to 2 for now (no 1-character tlds exist yet) - .match(/^(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)+[a-z0-9][-a-z0-9]*[a-z0-9]$/u) + .match( + /^(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)+[a-z0-9][-a-z0-9]*[a-z0-9]$/u, + ) return match !== null } -export function isAllOneCase (address) { +export function isAllOneCase(address) { if (!address) { return true } @@ -90,7 +118,7 @@ export function isAllOneCase (address) { } // Takes wei Hex, returns wei BN, even if input is null -export function numericBalance (balance) { +export function numericBalance(balance) { if (!balance) { return new ethUtil.BN(0, 16) } @@ -99,14 +127,17 @@ export function numericBalance (balance) { } // Takes hex, returns [beforeDecimal, afterDecimal] -export function parseBalance (balance) { +export function parseBalance(balance) { let afterDecimal const wei = numericBalance(balance) const weiString = wei.toString() const trailingZeros = /0+$/u - const beforeDecimal = weiString.length > 18 ? weiString.slice(0, weiString.length - 18) : '0' - afterDecimal = (`000000000000000000${wei}`).slice(-18).replace(trailingZeros, '') + const beforeDecimal = + weiString.length > 18 ? weiString.slice(0, weiString.length - 18) : '0' + afterDecimal = `000000000000000000${wei}` + .slice(-18) + .replace(trailingZeros, '') if (afterDecimal === '') { afterDecimal = '0' } @@ -115,7 +146,12 @@ export function parseBalance (balance) { // Takes wei hex, returns an object with three properties. // Its "formatted" property is what we generally use to render values. -export function formatBalance (balance, decimalsToKeep, needsParse = true, ticker = 'ETH') { +export function formatBalance( + balance, + decimalsToKeep, + needsParse = true, + ticker = 'ETH', +) { const parsed = needsParse ? parseBalance(balance) : balance.split('.') const beforeDecimal = parsed[0] let afterDecimal = parsed[1] @@ -134,12 +170,15 @@ export function formatBalance (balance, decimalsToKeep, needsParse = true, ticke } } else { afterDecimal += Array(decimalsToKeep).join('0') - formatted = `${beforeDecimal}.${afterDecimal.slice(0, decimalsToKeep)} ${ticker}` + formatted = `${beforeDecimal}.${afterDecimal.slice( + 0, + decimalsToKeep, + )} ${ticker}` } return formatted } -export function generateBalanceObject (formattedBalance, decimalsToKeep = 1) { +export function generateBalanceObject(formattedBalance, decimalsToKeep = 1) { let balance = formattedBalance.split(' ')[0] const label = formattedBalance.split(' ')[1] const beforeDecimal = balance.split('.')[0] @@ -160,7 +199,7 @@ export function generateBalanceObject (formattedBalance, decimalsToKeep = 1) { return { balance, label, shortBalance } } -export function shortenBalance (balance, decimalsToKeep = 1) { +export function shortenBalance(balance, decimalsToKeep = 1) { let truncatedValue const convertedBalance = parseFloat(balance) if (convertedBalance > 1000000) { @@ -185,7 +224,7 @@ export function shortenBalance (balance, decimalsToKeep = 1) { // Takes a BN and an ethereum currency name, // returns a BN in wei -export function normalizeToWei (amount, currency) { +export function normalizeToWei(amount, currency) { try { return amount.mul(bnTable.wei).div(bnTable[currency]) } catch (e) { @@ -193,7 +232,7 @@ export function normalizeToWei (amount, currency) { } } -export function normalizeEthStringToWei (str) { +export function normalizeEthStringToWei(str) { const parts = str.split('.') let eth = new ethUtil.BN(parts[0], 10).mul(bnTable.wei) if (parts[1]) { @@ -211,26 +250,26 @@ export function normalizeEthStringToWei (str) { } const multiple = new ethUtil.BN('10000', 10) -export function normalizeNumberToWei (n, currency) { +export function normalizeNumberToWei(n, currency) { const enlarged = n * 10000 const amount = new ethUtil.BN(String(enlarged), 10) return normalizeToWei(amount, currency).div(multiple) } -export function isHex (str) { +export function isHex(str) { return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/u)) } -export function getContractAtAddress (tokenAddress) { +export function getContractAtAddress(tokenAddress) { return global.eth.contract(abi).at(tokenAddress) } -export function getRandomFileName () { +export function getRandomFileName() { let fileName = '' const charBank = [ ...'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', ] - const fileNameLength = Math.floor((Math.random() * 7) + 6) + const fileNameLength = Math.floor(Math.random() * 7 + 6) for (let i = 0; i < fileNameLength; i++) { fileName += charBank[Math.floor(Math.random() * charBank.length)] @@ -239,7 +278,7 @@ export function getRandomFileName () { return fileName } -export function exportAsFile (filename, data, type = 'text/csv') { +export function exportAsFile(filename, data, type = 'text/csv') { // eslint-disable-next-line no-param-reassign filename = filename || getRandomFileName() // source: https://stackoverflow.com/a/33542499 by Ludovic Feltz @@ -261,10 +300,10 @@ export function exportAsFile (filename, data, type = 'text/csv') { * Safely checksumms a potentially-null address * * @param {string} [address] - address to checksum - * @returns {string} - checksummed address + * @returns {string} checksummed address * */ -export function checksumAddress (address) { +export function checksumAddress(address) { const checksummed = address ? ethUtil.toChecksumAddress(address) : '' return checksummed } @@ -280,7 +319,7 @@ export function checksumAddress (address) { * @returns {string} The shortened address, or the original if it was no longer * than 10 characters. */ -export function shortenAddress (address = '') { +export function shortenAddress(address = '') { if (address.length < 11) { return address } @@ -288,14 +327,14 @@ export function shortenAddress (address = '') { return `${address.slice(0, 6)}...${address.slice(-4)}` } -export function isValidAddressHead (address) { +export function isValidAddressHead(address) { const addressLengthIsLessThanFull = address.length < 42 const addressIsHex = isHex(address) return addressLengthIsLessThanFull && addressIsHex } -export function getAccountByAddress (accounts = [], targetAddress) { +export function getAccountByAddress(accounts = [], targetAddress) { return accounts.find(({ address }) => address === targetAddress) } @@ -307,7 +346,7 @@ export function getAccountByAddress (accounts = [], targetAddress) { * @param {string} urlString - The URL string to strip the scheme from. * @returns {string} The URL string, without the scheme, if it was stripped. */ -export function stripHttpSchemes (urlString) { +export function stripHttpSchemes(urlString) { return urlString.replace(/^https?:\/\//u, '') } @@ -317,8 +356,7 @@ export function stripHttpSchemes (urlString) { * @param {string | URL | object} urlLike - The URL-like value to test. * @returns {boolean} Whether the URL-like value is an extension URL. */ -export function isExtensionUrl (urlLike) { - +export function isExtensionUrl(urlLike) { const EXT_PROTOCOLS = ['chrome-extension:', 'moz-extension:'] if (typeof urlLike === 'string') { @@ -340,10 +378,10 @@ export function isExtensionUrl (urlLike) { * lowercased version of the addresses. * * @param {string} address - The hex address to check - * @param {array} list - The array of objects to check + * @param {Array} list - The array of objects to check * @returns {boolean} Whether or not the address is in the list */ -export function checkExistingAddresses (address, list = []) { +export function checkExistingAddresses(address, list = []) { if (!address) { return false } @@ -364,19 +402,19 @@ export function checkExistingAddresses (address, list = []) { * @param {number} precision - The maximum number of significant digits in the return value * @returns {string} The number in decimal form, with <= precision significant digits and no decimal trailing zeros */ -export function toPrecisionWithoutTrailingZeros (n, precision) { - return (new BigNumber(n)) +export function toPrecisionWithoutTrailingZeros(n, precision) { + return new BigNumber(n) .toPrecision(precision) .replace(/(\.[0-9]*[1-9])0*|(\.0*)/u, '$1') } /** - * Given and object where all values are strings, returns the same object with all values - * now prefixed with '0x' - */ -export function addHexPrefixToObjectValues (obj) { + * Given and object where all values are strings, returns the same object with all values + * now prefixed with '0x' + */ +export function addHexPrefixToObjectValues(obj) { return Object.keys(obj).reduce((newObj, key) => { - return { ...newObj, [key]: ethUtil.addHexPrefix(obj[key]) } + return { ...newObj, [key]: addHexPrefix(obj[key]) } }, {}) } @@ -390,9 +428,17 @@ export function addHexPrefixToObjectValues (obj) { * @param {string} from - A hex address of the tx sender address * @param {string} gas - A hex representation of the gas value for the transaction * @param {string} gasPrice - A hex representation of the gas price for the transaction - * @returns {object} An object ready for submission to the blockchain, with all values appropriately hex prefixed + * @returns {Object} An object ready for submission to the blockchain, with all values appropriately hex prefixed */ -export function constructTxParams ({ sendToken, data, to, amount, from, gas, gasPrice }) { +export function constructTxParams({ + sendToken, + data, + to, + amount, + from, + gas, + gasPrice, +}) { const txParams = { data, from, @@ -417,20 +463,33 @@ export function constructTxParams ({ sendToken, data, to, amount, from, gas, gas * @returns {Promise} Returns the result of the RPC method call, * or throws an error in case of failure. */ -export async function jsonRpcRequest (rpcUrl, rpcMethod, rpcParams = []) { - const jsonRpcResponse = await window.fetch(rpcUrl, { - method: 'POST', - body: JSON.stringify({ - id: Date.now().toString(), - jsonrpc: '2.0', - method: rpcMethod, - params: rpcParams, - }), - headers: { - 'Content-Type': 'application/json', - }, - cache: 'default', - }) +export async function jsonRpcRequest(rpcUrl, rpcMethod, rpcParams = []) { + let fetchUrl = rpcUrl + const headers = { + 'Content-Type': 'application/json', + } + // Convert basic auth URL component to Authorization header + const { origin, pathname, username, password, search } = new URL(rpcUrl) + // URLs containing username and password needs special processing + if (username && password) { + const encodedAuth = Buffer.from(`${username}:${password}`).toString( + 'base64', + ) + headers.Authorization = `Basic ${encodedAuth}` + fetchUrl = `${origin}${pathname}${search}` + } + const jsonRpcResponse = await window + .fetch(fetchUrl, { + method: 'POST', + body: JSON.stringify({ + id: Date.now().toString(), + jsonrpc: '2.0', + method: rpcMethod, + params: rpcParams, + }), + headers, + cache: 'default', + }) .then((httpResponse) => httpResponse.json()) if ( diff --git a/ui/app/helpers/utils/util.test.js b/ui/app/helpers/utils/util.test.js index 21b3aa9ed..15c8e722f 100644 --- a/ui/app/helpers/utils/util.test.js +++ b/ui/app/helpers/utils/util.test.js @@ -182,7 +182,9 @@ describe('util', function () { }) it('should return 0.500 ETH', function () { - const input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() + const input = new ethUtil.BN(ethInWei, 10) + .div(new ethUtil.BN('2', 10)) + .toJSON() const result = util.formatBalance(input, 3) assert.equal(result, '0.500 ETH') }) @@ -233,7 +235,13 @@ describe('util', function () { Object.keys(valueTable).forEach((currency) => { const value = new ethUtil.BN(valueTable[currency], 10) const output = util.normalizeToWei(value, currency) - assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`) + assert.equal( + output.toString(10), + valueTable.wei, + `value of ${output.toString( + 10, + )} ${currency} should convert to ${oneEthBn}`, + ) }) }) }) @@ -279,27 +287,39 @@ describe('util', function () { it('should convert a ether number to the appropriate equivalent wei', function () { const result = util.normalizeNumberToWei(1.111, 'ether') - assert.equal(result.toString(10), '1111000000000000000', 'accepts decimals') + assert.equal( + result.toString(10), + '1111000000000000000', + 'accepts decimals', + ) }) }) describe('#isHex', function () { it('should return true when given a hex string', function () { - const result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') + const result = util.isHex( + 'c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2', + ) assert(result) }) it('should return false when given a non-hex string', function () { - const result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal') + const result = util.isHex( + 'c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal', + ) assert(!result) }) it('should return false when given a string containing a non letter/number character', function () { - const result = util.isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal') + const result = util.isHex( + 'c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal', + ) assert(!result) }) it('should return true when given a hex string with hex-prefix', function () { - const result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') + const result = util.isHex( + '0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2', + ) assert(result) }) }) @@ -307,7 +327,7 @@ describe('util', function () { describe('#getRandomFileName', function () { it('should only return a string containing alphanumeric characters', function () { const result = util.getRandomFileName() - assert(result.match(/^[a-zA-Z0-9]*$/ug)) + assert(result.match(/^[a-zA-Z0-9]*$/gu)) }) // 50 samples diff --git a/ui/app/hooks/tests/useCancelTransaction.test.js b/ui/app/hooks/tests/useCancelTransaction.test.js index a355ee5e5..903e861d0 100644 --- a/ui/app/hooks/tests/useCancelTransaction.test.js +++ b/ui/app/hooks/tests/useCancelTransaction.test.js @@ -35,17 +35,26 @@ describe('useCancelTransaction', function () { }) }) transactions.forEach((transactionGroup) => { - const originalGasPrice = transactionGroup.primaryTransaction.txParams?.gasPrice - const gasPrice = originalGasPrice && increaseLastGasPrice(originalGasPrice) + const originalGasPrice = + transactionGroup.primaryTransaction.txParams?.gasPrice + const gasPrice = + originalGasPrice && increaseLastGasPrice(originalGasPrice) const transactionId = transactionGroup.initialTransaction.id it(`should indicate account has insufficient funds to cover ${gasPrice} gas price`, function () { - const { result } = renderHook(() => useCancelTransaction(transactionGroup)) + const { result } = renderHook(() => + useCancelTransaction(transactionGroup), + ) assert.equal(result.current[0], false) }) it(`should return a function that kicks off cancellation for id ${transactionId}`, function () { - const { result } = renderHook(() => useCancelTransaction(transactionGroup)) + const { result } = renderHook(() => + useCancelTransaction(transactionGroup), + ) assert.equal(typeof result.current[1], 'function') - result.current[1]({ preventDefault: () => undefined, stopPropagation: () => undefined }) + result.current[1]({ + preventDefault: () => undefined, + stopPropagation: () => undefined, + }) assert.equal( dispatch.calledWith( showModal({ @@ -78,17 +87,26 @@ describe('useCancelTransaction', function () { }) }) transactions.forEach((transactionGroup) => { - const originalGasPrice = transactionGroup.primaryTransaction.txParams?.gasPrice - const gasPrice = originalGasPrice && increaseLastGasPrice(originalGasPrice) + const originalGasPrice = + transactionGroup.primaryTransaction.txParams?.gasPrice + const gasPrice = + originalGasPrice && increaseLastGasPrice(originalGasPrice) const transactionId = transactionGroup.initialTransaction.id it(`should indicate account has funds to cover ${gasPrice} gas price`, function () { - const { result } = renderHook(() => useCancelTransaction(transactionGroup)) + const { result } = renderHook(() => + useCancelTransaction(transactionGroup), + ) assert.equal(result.current[0], true) }) it(`should return a function that kicks off cancellation for id ${transactionId}`, function () { - const { result } = renderHook(() => useCancelTransaction(transactionGroup)) + const { result } = renderHook(() => + useCancelTransaction(transactionGroup), + ) assert.equal(typeof result.current[1], 'function') - result.current[1]({ preventDefault: () => undefined, stopPropagation: () => undefined }) + result.current[1]({ + preventDefault: () => undefined, + stopPropagation: () => undefined, + }) assert.equal( dispatch.calledWith( showModal({ diff --git a/ui/app/hooks/tests/useCurrencyDisplay.test.js b/ui/app/hooks/tests/useCurrencyDisplay.test.js index 8b3f6910a..bacaa6ff0 100644 --- a/ui/app/hooks/tests/useCurrencyDisplay.test.js +++ b/ui/app/hooks/tests/useCurrencyDisplay.test.js @@ -3,7 +3,11 @@ import { renderHook } from '@testing-library/react-hooks' import * as reactRedux from 'react-redux' import sinon from 'sinon' import { useCurrencyDisplay } from '../useCurrencyDisplay' -import { getCurrentCurrency, getNativeCurrency, getConversionRate } from '../../selectors' +import { + getCurrentCurrency, + getNativeCurrency, + getConversionRate, +} from '../../selectors' const tests = [ { diff --git a/ui/app/hooks/tests/useRetryTransaction.test.js b/ui/app/hooks/tests/useRetryTransaction.test.js index b922bd92c..d1e1fb541 100644 --- a/ui/app/hooks/tests/useRetryTransaction.test.js +++ b/ui/app/hooks/tests/useRetryTransaction.test.js @@ -12,7 +12,10 @@ describe('useRetryTransaction', function () { describe('when transaction meets retry enabled criteria', function () { const dispatch = sinon.spy(() => Promise.resolve({ blockTime: 0 })) const trackEvent = sinon.spy() - const event = { preventDefault: () => undefined, stopPropagation: () => undefined } + const event = { + preventDefault: () => undefined, + stopPropagation: () => undefined, + } before(function () { sinon.stub(reactRedux, 'useDispatch').returns(dispatch) @@ -35,14 +38,18 @@ describe('useRetryTransaction', function () { } it('retryTransaction function should track metrics', function () { - const { result } = renderHook(() => useRetryTransaction(retryEnabledTransaction, true)) + const { result } = renderHook(() => + useRetryTransaction(retryEnabledTransaction, true), + ) const retry = result.current retry(event) assert.equal(trackEvent.calledOnce, true) }) it('retryTransaction function should show retry sidebar', async function () { - const { result } = renderHook(() => useRetryTransaction(retryEnabledTransaction, true)) + const { result } = renderHook(() => + useRetryTransaction(retryEnabledTransaction, true), + ) const retry = result.current await retry(event) const calls = dispatch.getCalls() diff --git a/ui/app/hooks/tests/useTokenData.test.js b/ui/app/hooks/tests/useTokenData.test.js index f745ba23b..373c84eb5 100644 --- a/ui/app/hooks/tests/useTokenData.test.js +++ b/ui/app/hooks/tests/useTokenData.test.js @@ -2,33 +2,37 @@ import assert from 'assert' import { ethers } from 'ethers' import { renderHook } from '@testing-library/react-hooks' import { useTokenData } from '../useTokenData' +import { TRANSACTION_CATEGORIES } from '../../../../shared/constants/transaction' const tests = [ { - data: '0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a970000000000000000000000000000000000000000000000000000000000003a98', + data: + '0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a970000000000000000000000000000000000000000000000000000000000003a98', tokenData: { - 'name': 'transfer', - 'args': [ + name: TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, + args: [ '0xffe5bc4e8f1f969934d773fa67da095d2e491a97', ethers.BigNumber.from(15000), ], }, }, { - data: '0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a9700000000000000000000000000000000000000000000000000000000000061a8', + data: + '0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a9700000000000000000000000000000000000000000000000000000000000061a8', tokenData: { - 'name': 'transfer', - 'args': [ + name: TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, + args: [ '0xffe5bc4e8f1f969934d773fa67da095d2e491a97', ethers.BigNumber.from(25000), ], }, }, { - data: '0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a970000000000000000000000000000000000000000000000000000000000002710', + data: + '0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a970000000000000000000000000000000000000000000000000000000000002710', tokenData: { - 'name': 'transfer', - 'args': [ + name: TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, + args: [ '0xffe5bc4e8f1f969934d773fa67da095d2e491a97', ethers.BigNumber.from(10000), ], @@ -42,14 +46,18 @@ const tests = [ describe('useTokenData', function () { tests.forEach((test) => { - const testTitle = test.tokenData === null - ? `should return null when no data provided` - : `should return properly decoded data with _value ${test.tokenData.args[1].value}` + const testTitle = + test.tokenData === null + ? `should return null when no data provided` + : `should return properly decoded data with _value ${test.tokenData.args[1].value}` it(testTitle, function () { const { result } = renderHook(() => useTokenData(test.data)) if (test.tokenData) { assert.equal(result.current.name, test.tokenData.name) - assert.equal(result.current.args[0].toLowerCase(), test.tokenData.args[0]) + assert.equal( + result.current.args[0].toLowerCase(), + test.tokenData.args[0], + ) assert.ok(test.tokenData.args[1].eq(result.current.args[1])) } else { assert.equal(result.current, test.tokenData) diff --git a/ui/app/hooks/tests/useTokenDisplayValue.test.js b/ui/app/hooks/tests/useTokenDisplayValue.test.js index bcb028957..e9ba328bd 100644 --- a/ui/app/hooks/tests/useTokenDisplayValue.test.js +++ b/ui/app/hooks/tests/useTokenDisplayValue.test.js @@ -126,7 +126,9 @@ describe('useTokenDisplayValue', function () { const getTokenDataStub = sinon.stub(txUtil, 'getTokenData') getTokenDataStub.callsFake(() => test.tokenData) getTokenValueStub.callsFake(() => test.tokenValue) - const { result } = renderHook(() => useTokenDisplayValue(`${idx}-fakestring`, test.token)) + const { result } = renderHook(() => + useTokenDisplayValue(`${idx}-fakestring`, test.token), + ) sinon.restore() assert.equal(result.current, test.displayValue) }) diff --git a/ui/app/hooks/tests/useTransactionDisplayData.test.js b/ui/app/hooks/tests/useTransactionDisplayData.test.js index d44eb77ae..cb7784620 100644 --- a/ui/app/hooks/tests/useTransactionDisplayData.test.js +++ b/ui/app/hooks/tests/useTransactionDisplayData.test.js @@ -7,17 +7,27 @@ import { MemoryRouter } from 'react-router-dom' import transactions from '../../../../test/data/transaction-data.json' import { useTransactionDisplayData } from '../useTransactionDisplayData' import * as useTokenFiatAmountHooks from '../useTokenFiatAmount' -import { getPreferences, getShouldShowFiat, getNativeCurrency, getCurrentCurrency } from '../../selectors' +import { + getPreferences, + getShouldShowFiat, + getNativeCurrency, + getCurrentCurrency, +} from '../../selectors' import { getTokens } from '../../ducks/metamask/metamask' import * as i18nhooks from '../useI18nContext' import { getMessage } from '../../helpers/utils/i18n-helper' import messages from '../../../../app/_locales/en/messages.json' import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../helpers/constants/routes' +import { + TRANSACTION_CATEGORIES, + TRANSACTION_GROUP_CATEGORIES, + TRANSACTION_STATUSES, +} from '../../../../shared/constants/transaction' const expectedResults = [ { title: 'Send ETH', - category: 'send', + category: TRANSACTION_GROUP_CATEGORIES.SEND, subtitle: 'To: 0xffe5...1a97', subtitleContainsOrigin: false, date: 'May 12', @@ -26,12 +36,12 @@ const expectedResults = [ recipientAddress: '0xffe5bc4e8f1f969934d773fa67da095d2e491a97', secondaryCurrency: '-1 ETH', isPending: false, - displayedStatusKey: 'confirmed', + displayedStatusKey: TRANSACTION_STATUSES.CONFIRMED, isSubmitted: false, }, { title: 'Send ETH', - category: 'send', + category: TRANSACTION_GROUP_CATEGORIES.SEND, subtitle: 'To: 0x0ccc...8848', subtitleContainsOrigin: false, date: 'May 12', @@ -40,11 +50,11 @@ const expectedResults = [ recipientAddress: '0x0ccc8aeeaf5ce790f3b448325981a143fdef8848', secondaryCurrency: '-2 ETH', isPending: false, - displayedStatusKey: 'confirmed', + displayedStatusKey: TRANSACTION_STATUSES.CONFIRMED, }, { title: 'Send ETH', - category: 'send', + category: TRANSACTION_GROUP_CATEGORIES.SEND, subtitle: 'To: 0xffe5...1a97', subtitleContainsOrigin: false, date: 'May 12', @@ -53,11 +63,11 @@ const expectedResults = [ recipientAddress: '0xffe5bc4e8f1f969934d773fa67da095d2e491a97', secondaryCurrency: '-2 ETH', isPending: false, - displayedStatusKey: 'confirmed', + displayedStatusKey: TRANSACTION_STATUSES.CONFIRMED, }, { title: 'Receive', - category: 'receive', + category: TRANSACTION_GROUP_CATEGORIES.RECEIVE, subtitle: 'From: 0x31b9...4523', subtitleContainsOrigin: false, date: 'May 12', @@ -66,11 +76,11 @@ const expectedResults = [ recipientAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149', secondaryCurrency: '18.75 ETH', isPending: false, - displayedStatusKey: 'confirmed', + displayedStatusKey: TRANSACTION_STATUSES.CONFIRMED, }, { title: 'Receive', - category: 'receive', + category: TRANSACTION_GROUP_CATEGORIES.RECEIVE, subtitle: 'From: 0x9eca...a149', subtitleContainsOrigin: false, date: 'May 8', @@ -79,11 +89,11 @@ const expectedResults = [ recipientAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149', secondaryCurrency: '0 ETH', isPending: false, - displayedStatusKey: 'confirmed', + displayedStatusKey: TRANSACTION_STATUSES.CONFIRMED, }, { title: 'Receive', - category: 'receive', + category: TRANSACTION_GROUP_CATEGORIES.RECEIVE, subtitle: 'From: 0xee01...febb', subtitleContainsOrigin: false, date: 'May 24', @@ -92,11 +102,11 @@ const expectedResults = [ recipientAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149', secondaryCurrency: '1 ETH', isPending: false, - displayedStatusKey: 'confirmed', + displayedStatusKey: TRANSACTION_STATUSES.CONFIRMED, }, { title: 'Swap ETH to ABC', - category: 'swap', + category: TRANSACTION_CATEGORIES.SWAP, subtitle: '', subtitleContainsOrigin: false, date: 'May 12', @@ -105,31 +115,45 @@ const expectedResults = [ recipientAddress: '0xabca64466f257793eaa52fcfff5066894b76a149', secondaryCurrency: undefined, isPending: false, - displayedStatusKey: 'confirmed', + displayedStatusKey: TRANSACTION_STATUSES.CONFIRMED, }, ] let useSelector, useI18nContext, useTokenFiatAmount const renderHookWithRouter = (cb, tokenAddress) => { - const initialEntries = [tokenAddress ? `${ASSET_ROUTE}/${tokenAddress}` : DEFAULT_ROUTE] - // eslint-disable-next-line - const wrapper = ({ children }) => {children} + const initialEntries = [ + tokenAddress ? `${ASSET_ROUTE}/${tokenAddress}` : DEFAULT_ROUTE, + ] + const wrapper = ({ children }) => ( + {children} + ) return renderHook(cb, { wrapper }) } describe('useTransactionDisplayData', function () { before(function () { useSelector = sinon.stub(reactRedux, 'useSelector') - useTokenFiatAmount = sinon.stub(useTokenFiatAmountHooks, 'useTokenFiatAmount') + useTokenFiatAmount = sinon.stub( + useTokenFiatAmountHooks, + 'useTokenFiatAmount', + ) useTokenFiatAmount.returns((tokenAddress) => { return tokenAddress ? '1 TST' : undefined }) useI18nContext = sinon.stub(i18nhooks, 'useI18nContext') - useI18nContext.returns((key, variables) => getMessage('en', messages, key, variables)) + useI18nContext.returns((key, variables) => + getMessage('en', messages, key, variables), + ) useSelector.callsFake((selector) => { if (selector === getTokens) { - return [{ address: '0xabca64466f257793eaa52fcfff5066894b76a149', symbol: 'ABC', decimals: 18 }] + return [ + { + address: '0xabca64466f257793eaa52fcfff5066894b76a149', + symbol: 'ABC', + decimals: 18, + }, + ] } else if (selector === getPreferences) { return { useNativeCurrencyAsPrimaryCurrency: true, @@ -147,43 +171,76 @@ describe('useTransactionDisplayData', function () { transactions.forEach((transactionGroup, idx) => { describe(`when called with group containing primaryTransaction id ${transactionGroup.primaryTransaction.id}`, function () { const expected = expectedResults[idx] - const tokenAddress = transactionGroup.primaryTransaction?.destinationTokenAddress + const tokenAddress = + transactionGroup.primaryTransaction?.destinationTokenAddress it(`should return a title of ${expected.title}`, function () { - const { result } = renderHookWithRouter(() => useTransactionDisplayData(transactionGroup), tokenAddress) + const { result } = renderHookWithRouter( + () => useTransactionDisplayData(transactionGroup), + tokenAddress, + ) assert.equal(result.current.title, expected.title) }) it(`should return a subtitle of ${expected.subtitle}`, function () { - const { result } = renderHookWithRouter(() => useTransactionDisplayData(transactionGroup), tokenAddress) + const { result } = renderHookWithRouter( + () => useTransactionDisplayData(transactionGroup), + tokenAddress, + ) assert.equal(result.current.subtitle, expected.subtitle) }) it(`should return a category of ${expected.category}`, function () { - const { result } = renderHookWithRouter(() => useTransactionDisplayData(transactionGroup), tokenAddress) + const { result } = renderHookWithRouter( + () => useTransactionDisplayData(transactionGroup), + tokenAddress, + ) assert.equal(result.current.category, expected.category) }) it(`should return a primaryCurrency of ${expected.primaryCurrency}`, function () { - const { result } = renderHookWithRouter(() => useTransactionDisplayData(transactionGroup), tokenAddress) + const { result } = renderHookWithRouter( + () => useTransactionDisplayData(transactionGroup), + tokenAddress, + ) assert.equal(result.current.primaryCurrency, expected.primaryCurrency) }) it(`should return a secondaryCurrency of ${expected.secondaryCurrency}`, function () { - const { result } = renderHookWithRouter(() => useTransactionDisplayData(transactionGroup), tokenAddress) - assert.equal(result.current.secondaryCurrency, expected.secondaryCurrency) + const { result } = renderHookWithRouter( + () => useTransactionDisplayData(transactionGroup), + tokenAddress, + ) + assert.equal( + result.current.secondaryCurrency, + expected.secondaryCurrency, + ) }) it(`should return a displayedStatusKey of ${expected.displayedStatusKey}`, function () { - const { result } = renderHookWithRouter(() => useTransactionDisplayData(transactionGroup), tokenAddress) - assert.equal(result.current.displayedStatusKey, expected.displayedStatusKey) + const { result } = renderHookWithRouter( + () => useTransactionDisplayData(transactionGroup), + tokenAddress, + ) + assert.equal( + result.current.displayedStatusKey, + expected.displayedStatusKey, + ) }) it(`should return a recipientAddress of ${expected.recipientAddress}`, function () { - const { result } = renderHookWithRouter(() => useTransactionDisplayData(transactionGroup), tokenAddress) + const { result } = renderHookWithRouter( + () => useTransactionDisplayData(transactionGroup), + tokenAddress, + ) assert.equal(result.current.recipientAddress, expected.recipientAddress) }) it(`should return a senderAddress of ${expected.senderAddress}`, function () { - const { result } = renderHookWithRouter(() => useTransactionDisplayData(transactionGroup), tokenAddress) + const { result } = renderHookWithRouter( + () => useTransactionDisplayData(transactionGroup), + tokenAddress, + ) assert.equal(result.current.senderAddress, expected.senderAddress) }) }) }) it('should return an appropriate object', function () { - const { result } = renderHookWithRouter(() => useTransactionDisplayData(transactions[0])) + const { result } = renderHookWithRouter(() => + useTransactionDisplayData(transactions[0]), + ) assert.deepEqual(result.current, expectedResults[0]) }) after(function () { diff --git a/ui/app/hooks/tests/useUserPreferencedCurrency.test.js b/ui/app/hooks/tests/useUserPreferencedCurrency.test.js index b5d7b47e9..0b5534fdb 100644 --- a/ui/app/hooks/tests/useUserPreferencedCurrency.test.js +++ b/ui/app/hooks/tests/useUserPreferencedCurrency.test.js @@ -111,7 +111,7 @@ const tests = [ }, ] -function getFakeUseSelector (state) { +function getFakeUseSelector(state) { return (selector) => { if (selector === getPreferences) { return state @@ -128,13 +128,22 @@ describe('useUserPreferencedCurrency', function () { const stub = sinon.stub(reactRedux, 'useSelector') stub.callsFake(getFakeUseSelector(state)) - const { result: hookResult } = renderHook(() => useUserPreferencedCurrency(type, otherParams)) + const { result: hookResult } = renderHook(() => + useUserPreferencedCurrency(type, otherParams), + ) stub.restore() - it(`should return currency as ${result.currency || 'not modified by user preferences'}`, function () { + it(`should return currency as ${ + result.currency || 'not modified by user preferences' + }`, function () { assert.equal(hookResult.current.currency, result.currency) }) - it(`should return decimals as ${result.numberOfDecimals || 'not modified by user preferences'}`, function () { - assert.equal(hookResult.current.numberOfDecimals, result.numberOfDecimals) + it(`should return decimals as ${ + result.numberOfDecimals || 'not modified by user preferences' + }`, function () { + assert.equal( + hookResult.current.numberOfDecimals, + result.numberOfDecimals, + ) }) }) }) diff --git a/ui/app/hooks/useCancelTransaction.js b/ui/app/hooks/useCancelTransaction.js index 547697360..3d478e410 100644 --- a/ui/app/hooks/useCancelTransaction.js +++ b/ui/app/hooks/useCancelTransaction.js @@ -2,7 +2,10 @@ import { useDispatch, useSelector } from 'react-redux' import { useCallback } from 'react' import { showModal } from '../store/actions' import { isBalanceSufficient } from '../pages/send/send.utils' -import { getHexGasTotal, increaseLastGasPrice } from '../helpers/utils/confirm-tx.util' +import { + getHexGasTotal, + increaseLastGasPrice, +} from '../helpers/utils/confirm-tx.util' import { getConversionRate, getSelectedAccount } from '../selectors' /** @@ -15,28 +18,41 @@ import { getConversionRate, getSelectedAccount } from '../selectors' * @param {Object} transactionGroup * @return {[boolean, Function]} */ -export function useCancelTransaction (transactionGroup) { +export function useCancelTransaction(transactionGroup) { const { primaryTransaction, initialTransaction } = transactionGroup - const gasPrice = primaryTransaction.txParams?.gasPrice + const gasPrice = primaryTransaction.txParams?.gasPrice?.startsWith('-') + ? '0x0' + : primaryTransaction.txParams?.gasPrice const { id } = initialTransaction const dispatch = useDispatch() const selectedAccount = useSelector(getSelectedAccount) const conversionRate = useSelector(getConversionRate) - const cancelTransaction = useCallback((event) => { - event.stopPropagation() + const cancelTransaction = useCallback( + (event) => { + event.stopPropagation() - return dispatch(showModal({ name: 'CANCEL_TRANSACTION', transactionId: id, originalGasPrice: gasPrice })) - }, [dispatch, id, gasPrice]) + return dispatch( + showModal({ + name: 'CANCEL_TRANSACTION', + transactionId: id, + originalGasPrice: gasPrice, + }), + ) + }, + [dispatch, id, gasPrice], + ) - const hasEnoughCancelGas = primaryTransaction.txParams && isBalanceSufficient({ - amount: '0x0', - gasTotal: getHexGasTotal({ - gasPrice: increaseLastGasPrice(gasPrice), - gasLimit: primaryTransaction.txParams.gas, - }), - balance: selectedAccount.balance, - conversionRate, - }) + const hasEnoughCancelGas = + primaryTransaction.txParams && + isBalanceSufficient({ + amount: '0x0', + gasTotal: getHexGasTotal({ + gasPrice: increaseLastGasPrice(gasPrice), + gasLimit: primaryTransaction.txParams.gas, + }), + balance: selectedAccount.balance, + conversionRate, + }) return [hasEnoughCancelGas, cancelTransaction] } diff --git a/ui/app/hooks/useCopyToClipboard.js b/ui/app/hooks/useCopyToClipboard.js index 8d870d1b8..542496e15 100644 --- a/ui/app/hooks/useCopyToClipboard.js +++ b/ui/app/hooks/useCopyToClipboard.js @@ -5,13 +5,13 @@ import { useTimeout } from './useTimeout' /** * useCopyToClipboard * - * @param {number} [delay=3000] - delay in ms + * @param {number} [delay=3000] - delay in ms * * @return {[boolean, Function]} */ const DEFAULT_DELAY = 3000 -export function useCopyToClipboard (delay = DEFAULT_DELAY) { +export function useCopyToClipboard(delay = DEFAULT_DELAY) { const [copied, setCopied] = useState(false) const startTimeout = useTimeout(() => setCopied(false), delay, false) diff --git a/ui/app/hooks/useCurrencyDisplay.js b/ui/app/hooks/useCurrencyDisplay.js index 4e2c99e3f..14e274d2f 100644 --- a/ui/app/hooks/useCurrencyDisplay.js +++ b/ui/app/hooks/useCurrencyDisplay.js @@ -1,7 +1,14 @@ import { useMemo } from 'react' import { useSelector } from 'react-redux' -import { formatCurrency, getValueFromWeiHex } from '../helpers/utils/confirm-tx.util' -import { getCurrentCurrency, getConversionRate, getNativeCurrency } from '../selectors' +import { + formatCurrency, + getValueFromWeiHex, +} from '../helpers/utils/confirm-tx.util' +import { + getCurrentCurrency, + getConversionRate, + getNativeCurrency, +} from '../selectors' /** * Defines the shape of the options parameter for useCurrencyDisplay @@ -31,7 +38,10 @@ import { getCurrentCurrency, getConversionRate, getNativeCurrency } from '../sel * @param {UseCurrencyOptions} opts - An object for options to format the inputValue * @return {[string, CurrencyDisplayParts]} */ -export function useCurrencyDisplay (inputValue, { displayValue, prefix, numberOfDecimals, denomination, currency, ...opts }) { +export function useCurrencyDisplay( + inputValue, + { displayValue, prefix, numberOfDecimals, denomination, currency, ...opts }, +) { const currentCurrency = useSelector(getCurrentCurrency) const nativeCurrency = useSelector(getNativeCurrency) const conversionRate = useSelector(getConversionRate) @@ -53,7 +63,15 @@ export function useCurrencyDisplay (inputValue, { displayValue, prefix, numberOf }), toCurrency, ) - }, [inputValue, nativeCurrency, conversionRate, displayValue, numberOfDecimals, denomination, toCurrency]) + }, [ + inputValue, + nativeCurrency, + conversionRate, + displayValue, + numberOfDecimals, + denomination, + toCurrency, + ]) let suffix @@ -61,5 +79,8 @@ export function useCurrencyDisplay (inputValue, { displayValue, prefix, numberOf suffix = opts.suffix || toCurrency.toUpperCase() } - return [`${prefix || ''}${value}${suffix ? ` ${suffix}` : ''}`, { prefix, value, suffix }] + return [ + `${prefix || ''}${value}${suffix ? ` ${suffix}` : ''}`, + { prefix, value, suffix }, + ] } diff --git a/ui/app/hooks/useCurrentAsset.js b/ui/app/hooks/useCurrentAsset.js index 7a5b14ec4..80e905a94 100644 --- a/ui/app/hooks/useCurrentAsset.js +++ b/ui/app/hooks/useCurrentAsset.js @@ -10,15 +10,18 @@ import { ETH_SWAPS_TOKEN_OBJECT } from '../helpers/constants/swaps' * the primary, unfiltered, activity list or the ETH asset page. * @returns {import('./useTokenDisplayValue').Token} */ -export function useCurrentAsset () { +export function useCurrentAsset() { // To determine which primary currency to display for swaps transactions we need to be aware // of which asset, if any, we are viewing at present - const match = useRouteMatch({ path: `${ASSET_ROUTE}/:asset`, exact: true, strict: true }) + const match = useRouteMatch({ + path: `${ASSET_ROUTE}/:asset`, + exact: true, + strict: true, + }) const tokenAddress = match?.params?.asset const knownTokens = useSelector(getTokens) - const token = tokenAddress && knownTokens.find( - ({ address }) => address === tokenAddress, - ) + const token = + tokenAddress && knownTokens.find(({ address }) => address === tokenAddress) return token ?? ETH_SWAPS_TOKEN_OBJECT } diff --git a/ui/app/hooks/useEqualityCheck.js b/ui/app/hooks/useEqualityCheck.js index ee4ca1e0a..4212beeab 100644 --- a/ui/app/hooks/useEqualityCheck.js +++ b/ui/app/hooks/useEqualityCheck.js @@ -10,11 +10,11 @@ import { isEqual } from 'lodash' * infrequently changes it's value. By default, uses isEqual from * lodash. This is typically only useful with objects and arrays. * - * @param {T} value - any value to check equality of + * @param {T} value - any value to check equality of * @param {(T, T) => boolean} equalityFn - A function to determine equality * @returns {T} */ -export function useEqualityCheck (value, equalityFn = isEqual) { +export function useEqualityCheck(value, equalityFn = isEqual) { const [computedValue, setComputedValue] = useState(value) useLayoutEffect(() => { diff --git a/ui/app/hooks/useEthFiatAmount.js b/ui/app/hooks/useEthFiatAmount.js index 2e12a272d..7e89e3ad3 100644 --- a/ui/app/hooks/useEthFiatAmount.js +++ b/ui/app/hooks/useEthFiatAmount.js @@ -1,6 +1,10 @@ import { useMemo } from 'react' import { useSelector } from 'react-redux' -import { getConversionRate, getCurrentCurrency, getShouldShowFiat } from '../selectors' +import { + getConversionRate, + getCurrentCurrency, + getShouldShowFiat, +} from '../selectors' import { decEthToConvertedCurrency } from '../helpers/utils/conversions.util' import { formatCurrency } from '../helpers/utils/confirm-tx.util' @@ -8,13 +12,17 @@ import { formatCurrency } from '../helpers/utils/confirm-tx.util' * Get an Eth amount converted to fiat and formatted for display * * @param {string} [tokenAmount] - The eth amount to convert - * @param {object} [overrides] - A configuration object that allows the called to explicitly + * @param {Object} [overrides] - A configuration object that allows the called to explicitly * ensure fiat is shown even if the property is not set in state. * @param {boolean} [overrides.showFiat] - If truthy, ensures the fiat value is shown even if the showFiat value from state is falsey * @param {boolean} hideCurrencySymbol Indicates whether the returned formatted amount should include the trailing currency symbol * @return {string} - The formatted token amount in the user's chosen fiat currency */ -export function useEthFiatAmount (ethAmount, overrides = {}, hideCurrencySymbol) { +export function useEthFiatAmount( + ethAmount, + overrides = {}, + hideCurrencySymbol, +) { const conversionRate = useSelector(getConversionRate) const currentCurrency = useSelector(getCurrentCurrency) const userPrefersShownFiat = useSelector(getShouldShowFiat) @@ -24,11 +32,19 @@ export function useEthFiatAmount (ethAmount, overrides = {}, hideCurrencySymbol) [conversionRate, currentCurrency, ethAmount], ) - if (!showFiat || currentCurrency.toUpperCase() === 'ETH' || conversionRate <= 0 || ethAmount === undefined) { + if ( + !showFiat || + currentCurrency.toUpperCase() === 'ETH' || + conversionRate <= 0 || + ethAmount === undefined + ) { return undefined } return hideCurrencySymbol ? formatCurrency(formattedFiat, currentCurrency) - : `${formatCurrency(formattedFiat, currentCurrency)} ${currentCurrency.toUpperCase()}` + : `${formatCurrency( + formattedFiat, + currentCurrency, + )} ${currentCurrency.toUpperCase()}` } diff --git a/ui/app/hooks/useI18nContext.js b/ui/app/hooks/useI18nContext.js index 819a85c7c..d2fb087c3 100644 --- a/ui/app/hooks/useI18nContext.js +++ b/ui/app/hooks/useI18nContext.js @@ -8,6 +8,6 @@ import { I18nContext } from '../contexts/i18n' * different places. * @return {Function} I18n function from contexts/I18n.js */ -export function useI18nContext () { +export function useI18nContext() { return useContext(I18nContext) } diff --git a/ui/app/hooks/useMethodData.js b/ui/app/hooks/useMethodData.js index c98060f6b..124e130b1 100644 --- a/ui/app/hooks/useMethodData.js +++ b/ui/app/hooks/useMethodData.js @@ -13,13 +13,18 @@ import { getKnownMethodData } from '../selectors/selectors' * if the data is in the store and returning it directly. While using this hook * in multiple places in a tree for the same data will create extra event ticks and * hit the action more frequently, it should only ever result in a single store update - * @param {string} data the transaction data to find method data for + * @param {string} data - the transaction data to find method data for * @return {Object} contract method data */ -export function useMethodData (data) { +export function useMethodData(data) { const dispatch = useDispatch() - const knownMethodData = useSelector((state) => getKnownMethodData(state, data)) - const getContractMethodData = useCallback((methodData) => dispatch(getContractMethodDataAction(methodData)), [dispatch]) + const knownMethodData = useSelector((state) => + getKnownMethodData(state, data), + ) + const getContractMethodData = useCallback( + (methodData) => dispatch(getContractMethodDataAction(methodData)), + [dispatch], + ) useEffect(() => { if (data) { diff --git a/ui/app/hooks/useMetricEvent.js b/ui/app/hooks/useMetricEvent.js index 790bc5e28..352f95cc4 100644 --- a/ui/app/hooks/useMetricEvent.js +++ b/ui/app/hooks/useMetricEvent.js @@ -3,28 +3,35 @@ import { MetaMetricsContext } from '../contexts/metametrics' import { MetaMetricsContext as NewMetaMetricsContext } from '../contexts/metametrics.new' import { useEqualityCheck } from './useEqualityCheck' -export function useMetricEvent (config = {}, overrides = {}) { +export function useMetricEvent(config = {}, overrides = {}) { const metricsEvent = useContext(MetaMetricsContext) - const trackEvent = useCallback(() => metricsEvent(config, overrides), [config, metricsEvent, overrides]) + const trackEvent = useCallback(() => metricsEvent(config, overrides), [ + config, + metricsEvent, + overrides, + ]) return trackEvent } /** - * track a metametrics event using segment - * e.g metricsEvent({ event: 'Unlocked MetaMask', category: 'Navigation' }) - * - * @param {object} config - configuration object for the event to track - * @param {string} config.event - event name to track - * @param {string} config.category - category to associate event to - * @param {boolean} [config.isOptIn] - happened during opt in/out workflow - * @param {object} [config.properties] - object of custom values to track, snake_case - * @param {number} [config.revenue] - amount of currency that event creates in revenue for MetaMask - * @param {string} [config.currency] - ISO 4127 format currency for events with revenue, defaults to US dollars - * @param {number} [config.value] - Abstract "value" that this event has for MetaMask. - * @return {() => undefined} function to execute the tracking event - */ -export function useNewMetricEvent (config) { + * track a metametrics event using segment + * e.g metricsEvent({ event: 'Unlocked MetaMask', category: 'Navigation' }) + * + * @param {Object} config - configuration object for the event to track + * @param {string} config.event - event name to track + * @param {string} config.category - category to associate event to + * @param {boolean} [config.isOptIn] - happened during opt in/out workflow + * @param {Object} [config.properties] - object of custom values to track, snake_case + * @param {number} [config.revenue] - amount of currency that event creates in revenue for MetaMask + * @param {string} [config.currency] - ISO 4127 format currency for events with revenue, defaults to US dollars + * @param {number} [config.value] - Abstract "value" that this event has for MetaMask. + * @return {() => undefined} function to execute the tracking event + */ +export function useNewMetricEvent(config) { const memoizedConfig = useEqualityCheck(config) const metricsEvent = useContext(NewMetaMetricsContext) - return useCallback(() => metricsEvent(memoizedConfig), [metricsEvent, memoizedConfig]) + return useCallback(() => metricsEvent(memoizedConfig), [ + metricsEvent, + memoizedConfig, + ]) } diff --git a/ui/app/hooks/usePrevious.js b/ui/app/hooks/usePrevious.js index 8a3721fe6..64ff105f1 100644 --- a/ui/app/hooks/usePrevious.js +++ b/ui/app/hooks/usePrevious.js @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react' -export function usePrevious (value) { +export function usePrevious(value) { const ref = useRef() useEffect(() => { ref.current = value diff --git a/ui/app/hooks/useRetryTransaction.js b/ui/app/hooks/useRetryTransaction.js index 1f3ffe701..c12000266 100644 --- a/ui/app/hooks/useRetryTransaction.js +++ b/ui/app/hooks/useRetryTransaction.js @@ -16,35 +16,44 @@ import { useMetricEvent } from './useMetricEvent' * @param {Object} transactionGroup - the transaction group * @return {Function} */ -export function useRetryTransaction (transactionGroup) { +export function useRetryTransaction(transactionGroup) { const { primaryTransaction, initialTransaction } = transactionGroup // Signature requests do not have a txParams, but this hook is called indiscriminately const gasPrice = primaryTransaction.txParams?.gasPrice - const trackMetricsEvent = useMetricEvent(({ + const trackMetricsEvent = useMetricEvent({ eventOpts: { category: 'Navigation', action: 'Activity Log', name: 'Clicked "Speed Up"', }, - })) + }) const dispatch = useDispatch() - const retryTransaction = useCallback(async (event) => { - event.stopPropagation() + const retryTransaction = useCallback( + async (event) => { + event.stopPropagation() - trackMetricsEvent() - const basicEstimates = await dispatch(fetchBasicGasAndTimeEstimates) - await dispatch(fetchGasEstimates(basicEstimates.blockTime)) - const transaction = initialTransaction - const increasedGasPrice = increaseLastGasPrice(gasPrice) - dispatch(setCustomGasPriceForRetry(increasedGasPrice || transaction.txParams.gasPrice)) - dispatch(setCustomGasLimit(transaction.txParams.gas)) - dispatch(showSidebar({ - transitionName: 'sidebar-left', - type: 'customize-gas', - props: { transaction }, - })) - }, [dispatch, trackMetricsEvent, initialTransaction, gasPrice]) + trackMetricsEvent() + const basicEstimates = await dispatch(fetchBasicGasAndTimeEstimates) + await dispatch(fetchGasEstimates(basicEstimates.blockTime)) + const transaction = initialTransaction + const increasedGasPrice = increaseLastGasPrice(gasPrice) + dispatch( + setCustomGasPriceForRetry( + increasedGasPrice || transaction.txParams.gasPrice, + ), + ) + dispatch(setCustomGasLimit(transaction.txParams.gas)) + dispatch( + showSidebar({ + transitionName: 'sidebar-left', + type: 'customize-gas', + props: { transaction }, + }), + ) + }, + [dispatch, trackMetricsEvent, initialTransaction, gasPrice], + ) return retryTransaction } diff --git a/ui/app/hooks/useShouldShowSpeedUp.js b/ui/app/hooks/useShouldShowSpeedUp.js index 437c6cba2..97c79044f 100644 --- a/ui/app/hooks/useShouldShowSpeedUp.js +++ b/ui/app/hooks/useShouldShowSpeedUp.js @@ -7,7 +7,7 @@ import { useEffect, useState } from 'react' * @param {Object} transactionGroup - the transaction group to check against * @param {boolean} isEarliestNonce - Whether this group is currently the earliest nonce */ -export function useShouldShowSpeedUp (transactionGroup, isEarliestNonce) { +export function useShouldShowSpeedUp(transactionGroup, isEarliestNonce) { const { transactions, hasRetried } = transactionGroup const [earliestTransaction = {}] = transactions const { submittedTime } = earliestTransaction diff --git a/ui/app/hooks/useSwappedTokenValue.js b/ui/app/hooks/useSwappedTokenValue.js index 8357d8749..952260c9b 100644 --- a/ui/app/hooks/useSwappedTokenValue.js +++ b/ui/app/hooks/useSwappedTokenValue.js @@ -1,5 +1,5 @@ +import { TRANSACTION_CATEGORIES } from '../../../shared/constants/transaction' import { ETH_SWAPS_TOKEN_ADDRESS } from '../helpers/constants/swaps' -import { SWAP } from '../helpers/constants/transactions' import { getSwapsTokensReceivedFromTxMeta } from '../pages/swaps/swaps.util' import { useTokenFiatAmount } from './useTokenFiatAmount' @@ -12,7 +12,7 @@ import { useTokenFiatAmount } from './useTokenFiatAmount' */ /** - * A SWAP transaction group's primaryTransaction contains details of the swap, + * A Swap transaction group's primaryTransaction contains details of the swap, * including the source (from) and destination (to) token type (ETH, DAI, etc..) * When viewing a non ETH asset page, we need to determine if that asset is the * token that was received (destination) from the swap. In that circumstance we @@ -22,40 +22,46 @@ import { useTokenFiatAmount } from './useTokenFiatAmount' * @param {import('./useTokenDisplayValue').Token} currentAsset - The current asset the user is looking at * @returns {SwappedTokenValue} */ -export function useSwappedTokenValue (transactionGroup, currentAsset) { +export function useSwappedTokenValue(transactionGroup, currentAsset) { const { symbol, decimals, address } = currentAsset const { primaryTransaction, initialTransaction } = transactionGroup const { transactionCategory } = initialTransaction const { from: senderAddress } = initialTransaction.txParams || {} - const isViewingReceivedTokenFromSwap = ( - (currentAsset?.symbol === primaryTransaction.destinationTokenSymbol) || ( - currentAsset.address === ETH_SWAPS_TOKEN_ADDRESS && - primaryTransaction.destinationTokenSymbol === 'ETH' - ) - ) + const isViewingReceivedTokenFromSwap = + currentAsset?.symbol === primaryTransaction.destinationTokenSymbol || + (currentAsset.address === ETH_SWAPS_TOKEN_ADDRESS && + primaryTransaction.destinationTokenSymbol === 'ETH') - const swapTokenValue = transactionCategory === SWAP && isViewingReceivedTokenFromSwap - ? getSwapsTokensReceivedFromTxMeta( - primaryTransaction.destinationTokenSymbol, - initialTransaction, - address, - senderAddress, - decimals, - ) - : transactionCategory === SWAP && primaryTransaction.swapTokenValue + const swapTokenValue = + transactionCategory === TRANSACTION_CATEGORIES.SWAP && + isViewingReceivedTokenFromSwap + ? getSwapsTokensReceivedFromTxMeta( + primaryTransaction.destinationTokenSymbol, + initialTransaction, + address, + senderAddress, + decimals, + ) + : transactionCategory === TRANSACTION_CATEGORIES.SWAP && + primaryTransaction.swapTokenValue - const isNegative = typeof swapTokenValue === 'string' - ? Math.sign(swapTokenValue) === -1 - : false + const isNegative = + typeof swapTokenValue === 'string' + ? Math.sign(swapTokenValue) === -1 + : false const _swapTokenFiatAmount = useTokenFiatAmount( address, swapTokenValue || '', symbol, ) - const swapTokenFiatAmount = ( + const swapTokenFiatAmount = swapTokenValue && isViewingReceivedTokenFromSwap && _swapTokenFiatAmount - ) - return { swapTokenValue, swapTokenFiatAmount, isViewingReceivedTokenFromSwap, isNegative } + return { + swapTokenValue, + swapTokenFiatAmount, + isViewingReceivedTokenFromSwap, + isNegative, + } } diff --git a/ui/app/hooks/useSwapsEthToken.js b/ui/app/hooks/useSwapsEthToken.js index 398668cf5..cd531f6e3 100644 --- a/ui/app/hooks/useSwapsEthToken.js +++ b/ui/app/hooks/useSwapsEthToken.js @@ -1,7 +1,10 @@ import { useSelector } from 'react-redux' import { getSelectedAccount } from '../selectors' import { ETH_SWAPS_TOKEN_OBJECT } from '../helpers/constants/swaps' -import { getValueFromWeiHex, hexToDecimal } from '../helpers/utils/conversions.util' +import { + getValueFromWeiHex, + hexToDecimal, +} from '../helpers/utils/conversions.util' /** * @typedef {Object} SwapsEthToken @@ -34,7 +37,7 @@ import { getValueFromWeiHex, hexToDecimal } from '../helpers/utils/conversions.u * @returns {SwapsEthToken} The token object representation of the currently * selected account's ETH balance, as expected by the Swaps API. */ -export function useSwapsEthToken () { +export function useSwapsEthToken() { const selectedAccount = useSelector(getSelectedAccount) const { balance } = selectedAccount diff --git a/ui/app/hooks/useTimeout.js b/ui/app/hooks/useTimeout.js index 244e9a34c..dc0a71434 100644 --- a/ui/app/hooks/useTimeout.js +++ b/ui/app/hooks/useTimeout.js @@ -3,13 +3,13 @@ import { useState, useEffect, useRef, useCallback } from 'react' /** * useTimeout * - * @param {Function} cb - callback function inside setTimeout - * @param {number} delay - delay in ms - * @param {boolean} [immediate] - determines whether the timeout is invoked immediately + * @param {Function} cb - callback function inside setTimeout + * @param {number} delay - delay in ms + * @param {boolean} [immediate] - determines whether the timeout is invoked immediately * * @return {Function|undefined} */ -export function useTimeout (cb, delay, immediate = true) { +export function useTimeout(cb, delay, immediate = true) { const saveCb = useRef() const [timeoutId, setTimeoutId] = useState(null) diff --git a/ui/app/hooks/useTokenData.js b/ui/app/hooks/useTokenData.js index 932a8752f..c9602f084 100644 --- a/ui/app/hooks/useTokenData.js +++ b/ui/app/hooks/useTokenData.js @@ -5,7 +5,7 @@ import { getTokenData } from '../helpers/utils/transactions.util' * useTokenData * Given the data string from txParams return a decoded object of the details of the * transaction data. - * @param {string} [transactionData] - Raw data string from token transaction + * @param {string} [transactionData] - Raw data string from token transaction * @param {boolean} [isTokenTransaction] - Due to the nature of hooks, it isn't possible * to conditionally call this hook. This flag will * force this hook to return null if it set as false @@ -13,7 +13,7 @@ import { getTokenData } from '../helpers/utils/transactions.util' * with a token. * @return {Object} - Decoded token data */ -export function useTokenData (transactionData, isTokenTransaction = true) { +export function useTokenData(transactionData, isTokenTransaction = true) { return useMemo(() => { if (!isTokenTransaction || !transactionData) { return null diff --git a/ui/app/hooks/useTokenDisplayValue.js b/ui/app/hooks/useTokenDisplayValue.js index 347c0819c..331b53dfb 100644 --- a/ui/app/hooks/useTokenDisplayValue.js +++ b/ui/app/hooks/useTokenDisplayValue.js @@ -1,5 +1,8 @@ import { useMemo } from 'react' -import { getTokenValueParam, calcTokenAmount } from '../helpers/utils/token-util' +import { + getTokenValueParam, + calcTokenAmount, +} from '../helpers/utils/token-util' import { useTokenData } from './useTokenData' /** @@ -24,17 +27,21 @@ import { useTokenData } from './useTokenData' * with a token. * @return {string} - The computed displayValue of the provided transactionData and token */ -export function useTokenDisplayValue (transactionData, token, isTokenTransaction = true) { +export function useTokenDisplayValue( + transactionData, + token, + isTokenTransaction = true, +) { const tokenData = useTokenData(transactionData, isTokenTransaction) const shouldCalculateTokenValue = Boolean( // If we are currently processing a token transaction isTokenTransaction && - // and raw transaction data string is provided - transactionData && - // and a token object has been provided - token && - // and we are able to parse the token details from the raw data - tokenData?.args?.length, + // and raw transaction data string is provided + transactionData && + // and a token object has been provided + token && + // and we are able to parse the token details from the raw data + tokenData?.args?.length, ) const displayValue = useMemo(() => { diff --git a/ui/app/hooks/useTokenFiatAmount.js b/ui/app/hooks/useTokenFiatAmount.js index 65e423805..6f393c861 100644 --- a/ui/app/hooks/useTokenFiatAmount.js +++ b/ui/app/hooks/useTokenFiatAmount.js @@ -1,6 +1,11 @@ import { useMemo } from 'react' import { useSelector } from 'react-redux' -import { getTokenExchangeRates, getConversionRate, getCurrentCurrency, getShouldShowFiat } from '../selectors' +import { + getTokenExchangeRates, + getConversionRate, + getCurrentCurrency, + getShouldShowFiat, +} from '../selectors' import { getTokenFiatAmount } from '../helpers/utils/token-util' /** @@ -9,31 +14,46 @@ import { getTokenFiatAmount } from '../helpers/utils/token-util' * @param {string} [tokenAddress] - The token address * @param {string} [tokenAmount] - The token balance * @param {string} [tokenSymbol] - The token symbol - * @param {object} [overrides] - A configuration object that allows the caller to explicitly pass an exchange rate or + * @param {Object} [overrides] - A configuration object that allows the caller to explicitly pass an exchange rate or * ensure fiat is shown even if the property is not set in state. * @param {number} [overrides.exchangeRate] - An exhchange rate to use instead of the one selected from state * @param {boolean} [overrides.showFiat] - If truthy, ensures the fiat value is shown even if the showFiat value from state is falsey * @param {boolean} hideCurrencySymbol Indicates whether the returned formatted amount should include the trailing currency symbol * @return {string} - The formatted token amount in the user's chosen fiat currency */ -export function useTokenFiatAmount (tokenAddress, tokenAmount, tokenSymbol, overrides = {}, hideCurrencySymbol) { +export function useTokenFiatAmount( + tokenAddress, + tokenAmount, + tokenSymbol, + overrides = {}, + hideCurrencySymbol, +) { const contractExchangeRates = useSelector(getTokenExchangeRates) const conversionRate = useSelector(getConversionRate) const currentCurrency = useSelector(getCurrentCurrency) const userPrefersShownFiat = useSelector(getShouldShowFiat) const showFiat = overrides.showFiat ?? userPrefersShownFiat - const tokenExchangeRate = overrides.exchangeRate ?? contractExchangeRates[tokenAddress] + const tokenExchangeRate = + overrides.exchangeRate ?? contractExchangeRates[tokenAddress] const formattedFiat = useMemo( - () => getTokenFiatAmount( + () => + getTokenFiatAmount( + tokenExchangeRate, + conversionRate, + currentCurrency, + tokenAmount, + tokenSymbol, + true, + hideCurrencySymbol, + ), + [ tokenExchangeRate, conversionRate, currentCurrency, tokenAmount, tokenSymbol, - true, hideCurrencySymbol, - ), - [tokenExchangeRate, conversionRate, currentCurrency, tokenAmount, tokenSymbol, hideCurrencySymbol], + ], ) if (!showFiat || currentCurrency.toUpperCase() === tokenSymbol) { diff --git a/ui/app/hooks/useTokenTracker.js b/ui/app/hooks/useTokenTracker.js index 65671cd00..5e15af03d 100644 --- a/ui/app/hooks/useTokenTracker.js +++ b/ui/app/hooks/useTokenTracker.js @@ -4,7 +4,7 @@ import { useSelector } from 'react-redux' import { getCurrentNetwork, getSelectedAddress } from '../selectors' import { useEqualityCheck } from './useEqualityCheck' -export function useTokenTracker (tokens) { +export function useTokenTracker(tokens) { const network = useSelector(getCurrentNetwork) const userAddress = useSelector(getSelectedAddress) @@ -34,20 +34,23 @@ export function useTokenTracker (tokens) { } }, []) - const buildTracker = useCallback((address, tokenList) => { - // clear out previous tracker, if it exists. - teardownTracker() - tokenTracker.current = new TokenTracker({ - userAddress: address, - provider: global.ethereumProvider, - tokens: tokenList, - pollingInterval: 8000, - }) + const buildTracker = useCallback( + (address, tokenList) => { + // clear out previous tracker, if it exists. + teardownTracker() + tokenTracker.current = new TokenTracker({ + userAddress: address, + provider: global.ethereumProvider, + tokens: tokenList, + pollingInterval: 8000, + }) - tokenTracker.current.on('update', updateBalances) - tokenTracker.current.on('error', showError) - tokenTracker.current.updateBalances() - }, [updateBalances, showError, teardownTracker]) + tokenTracker.current.on('update', updateBalances) + tokenTracker.current.on('error', showError) + tokenTracker.current.updateBalances() + }, + [updateBalances, showError, teardownTracker], + ) // Effect to remove the tracker when the component is removed from DOM // Do not overload this effect with additional dependencies. teardownTracker @@ -82,7 +85,14 @@ export function useTokenTracker (tokens) { } buildTracker(userAddress, memoizedTokens) - }, [userAddress, teardownTracker, network, memoizedTokens, updateBalances, buildTracker]) + }, [ + userAddress, + teardownTracker, + network, + memoizedTokens, + updateBalances, + buildTracker, + ]) return { loading, tokensWithBalances, error } } diff --git a/ui/app/hooks/useTokensToSearch.js b/ui/app/hooks/useTokensToSearch.js index 5b0b05521..0d9485e1b 100644 --- a/ui/app/hooks/useTokensToSearch.js +++ b/ui/app/hooks/useTokensToSearch.js @@ -5,40 +5,60 @@ import BigNumber from 'bignumber.js' import { isEqual, shuffle } from 'lodash' import { checksumAddress } from '../helpers/utils/util' import { getTokenFiatAmount } from '../helpers/utils/token-util' -import { getTokenExchangeRates, getConversionRate, getCurrentCurrency } from '../selectors' +import { + getTokenExchangeRates, + getConversionRate, + getCurrentCurrency, +} from '../selectors' import { getSwapsTokens } from '../ducks/swaps/swaps' import { useSwapsEthToken } from './useSwapsEthToken' import { useEqualityCheck } from './useEqualityCheck' -const tokenList = shuffle(Object.entries(contractMap) - .map(([address, tokenData]) => ({ ...tokenData, address: address.toLowerCase() })) - .filter((tokenData) => Boolean(tokenData.erc20))) +const tokenList = shuffle( + Object.entries(contractMap) + .map(([address, tokenData]) => ({ + ...tokenData, + address: address.toLowerCase(), + })) + .filter((tokenData) => Boolean(tokenData.erc20)), +) -export function getRenderableTokenData (token, contractExchangeRates, conversionRate, currentCurrency) { +export function getRenderableTokenData( + token, + contractExchangeRates, + conversionRate, + currentCurrency, +) { const { symbol, name, address, iconUrl, string, balance, decimals } = token - const formattedFiat = getTokenFiatAmount( - symbol === 'ETH' ? 1 : contractExchangeRates[address], - conversionRate, - currentCurrency, - string, - symbol, - true, - ) || '' - const rawFiat = getTokenFiatAmount( - symbol === 'ETH' ? 1 : contractExchangeRates[address], - conversionRate, - currentCurrency, - string, - symbol, - false, - ) || '' - const usedIconUrl = iconUrl || (contractMap[checksumAddress(address)] && `images/contract/${contractMap[checksumAddress(address)].logo}`) + const formattedFiat = + getTokenFiatAmount( + symbol === 'ETH' ? 1 : contractExchangeRates[address], + conversionRate, + currentCurrency, + string, + symbol, + true, + ) || '' + const rawFiat = + getTokenFiatAmount( + symbol === 'ETH' ? 1 : contractExchangeRates[address], + conversionRate, + currentCurrency, + string, + symbol, + false, + ) || '' + const usedIconUrl = + iconUrl || + (contractMap[checksumAddress(address)] && + `images/contract/${contractMap[checksumAddress(address)].logo}`) return { ...token, primaryLabel: symbol, secondaryLabel: name || contractMap[checksumAddress(address)]?.name, - rightPrimaryLabel: string && `${(new BigNumber(string)).round(6).toString()} ${symbol}`, + rightPrimaryLabel: + string && `${new BigNumber(string).round(6).toString()} ${symbol}`, rightSecondaryLabel: formattedFiat, iconUrl: usedIconUrl, identiconAddress: usedIconUrl ? null : address, @@ -49,7 +69,7 @@ export function getRenderableTokenData (token, contractExchangeRates, conversion } } -export function useTokensToSearch ({ +export function useTokensToSearch({ providedTokens, usersTokens = [], topTokens = {}, @@ -87,8 +107,10 @@ export function useTokensToSearch ({ } const memoizedTokensToSearch = useEqualityCheck(tokensToSearch) return useMemo(() => { - - const usersTokensAddressMap = memoizedUsersToken.reduce((acc, token) => ({ ...acc, [token.address]: token }), {}) + const usersTokensAddressMap = memoizedUsersToken.reduce( + (acc, token) => ({ ...acc, [token.address]: token }), + {}, + ) const tokensToSearchBuckets = { owned: singleToken ? [] : [memoizedEthToken], @@ -97,24 +119,48 @@ export function useTokensToSearch ({ } memoizedTokensToSearch.forEach((token) => { - const renderableDataToken = getRenderableTokenData({ ...usersTokensAddressMap[token.address], ...token }, tokenConversionRates, conversionRate, currentCurrency) - if (usersTokensAddressMap[token.address] && ((renderableDataToken.symbol === 'ETH') || Number(renderableDataToken.balance ?? 0) !== 0)) { + const renderableDataToken = getRenderableTokenData( + { ...usersTokensAddressMap[token.address], ...token }, + tokenConversionRates, + conversionRate, + currentCurrency, + ) + if ( + usersTokensAddressMap[token.address] && + (renderableDataToken.symbol === 'ETH' || + Number(renderableDataToken.balance ?? 0) !== 0) + ) { tokensToSearchBuckets.owned.push(renderableDataToken) } else if (memoizedTopTokens[token.address]) { - tokensToSearchBuckets.top[memoizedTopTokens[token.address].index] = renderableDataToken + tokensToSearchBuckets.top[ + memoizedTopTokens[token.address].index + ] = renderableDataToken } else { tokensToSearchBuckets.others.push(renderableDataToken) } }) - tokensToSearchBuckets.owned = tokensToSearchBuckets.owned.sort(({ rawFiat }, { rawFiat: secondRawFiat }) => { - return ((new BigNumber(rawFiat || 0)).gt(secondRawFiat || 0) ? -1 : 1) - }) - tokensToSearchBuckets.top = tokensToSearchBuckets.top.filter((token) => token) + tokensToSearchBuckets.owned = tokensToSearchBuckets.owned.sort( + ({ rawFiat }, { rawFiat: secondRawFiat }) => { + return new BigNumber(rawFiat || 0).gt(secondRawFiat || 0) ? -1 : 1 + }, + ) + tokensToSearchBuckets.top = tokensToSearchBuckets.top.filter( + (token) => token, + ) return [ ...tokensToSearchBuckets.owned, ...tokensToSearchBuckets.top, ...tokensToSearchBuckets.others, ] - }, [memoizedTokensToSearch, memoizedUsersToken, tokenConversionRates, conversionRate, currentCurrency, memoizedTopTokens, memoizedEthToken, singleToken]) + }, [ + memoizedTokensToSearch, + memoizedUsersToken, + tokenConversionRates, + conversionRate, + currentCurrency, + memoizedTopTokens, + memoizedEthToken, + singleToken, + ]) } diff --git a/ui/app/hooks/useTransactionDisplayData.js b/ui/app/hooks/useTransactionDisplayData.js index c41fcaf99..2fe8ae691 100644 --- a/ui/app/hooks/useTransactionDisplayData.js +++ b/ui/app/hooks/useTransactionDisplayData.js @@ -4,28 +4,21 @@ import { getStatusKey } from '../helpers/utils/transactions.util' import { camelCaseToCapitalize } from '../helpers/utils/common.util' import { PRIMARY, SECONDARY } from '../helpers/constants/common' import { getTokenAddressParam } from '../helpers/utils/token-util' -import { formatDateWithYearContext, shortenAddress, stripHttpSchemes } from '../helpers/utils/util' import { - CONTRACT_INTERACTION_KEY, - DEPLOY_CONTRACT_ACTION_KEY, - INCOMING_TRANSACTION, - TOKEN_METHOD_TRANSFER, - TOKEN_METHOD_TRANSFER_FROM, - SEND_ETHER_ACTION_KEY, - TRANSACTION_CATEGORY_APPROVAL, - TRANSACTION_CATEGORY_INTERACTION, - TRANSACTION_CATEGORY_RECEIVE, - TRANSACTION_CATEGORY_SEND, - TRANSACTION_CATEGORY_SIGNATURE_REQUEST, - TRANSACTION_CATEGORY_SWAP, - TOKEN_METHOD_APPROVE, + formatDateWithYearContext, + shortenAddress, + stripHttpSchemes, +} from '../helpers/utils/util' +import { PENDING_STATUS_HASH, TOKEN_CATEGORY_HASH, - SWAP, - SWAP_APPROVAL, - SUBMITTED_STATUS, } from '../helpers/constants/transactions' import { getTokens } from '../ducks/metamask/metamask' +import { + TRANSACTION_CATEGORIES, + TRANSACTION_GROUP_CATEGORIES, + TRANSACTION_STATUSES, +} from '../../../shared/constants/transaction' import { useI18nContext } from './useI18nContext' import { useTokenFiatAmount } from './useTokenFiatAmount' import { useUserPreferencedCurrency } from './useUserPreferencedCurrency' @@ -55,10 +48,10 @@ import { useCurrentAsset } from './useCurrentAsset' * state access required to take a transactionGroup and derive from it a shape * of data that can power all views related to a transaction. Presently the main * case is for shared logic between transaction-list-item and transaction-detail-view - * @param {Object} transactionGroup group of transactions + * @param {Object} transactionGroup - group of transactions * @return {TransactionDisplayData} */ -export function useTransactionDisplayData (transactionGroup) { +export function useTransactionDisplayData(transactionGroup) { // To determine which primary currency to display for swaps transactions we need to be aware // of which asset, if any, we are viewing at present const currentAsset = useCurrentAsset() @@ -71,11 +64,14 @@ export function useTransactionDisplayData (transactionGroup) { const { from: senderAddress, to } = initialTransaction.txParams || {} // for smart contract interactions, methodData can be used to derive the name of the action being taken - const methodData = useSelector((state) => getKnownMethodData(state, initialTransaction?.txParams?.data)) || {} + const methodData = + useSelector((state) => + getKnownMethodData(state, initialTransaction?.txParams?.data), + ) || {} const displayedStatusKey = getStatusKey(primaryTransaction) const isPending = displayedStatusKey in PENDING_STATUS_HASH - const isSubmitted = displayedStatusKey === SUBMITTED_STATUS + const isSubmitted = displayedStatusKey === TRANSACTION_STATUSES.SUBMITTED const primaryValue = primaryTransaction.txParams?.value let prefix = '-' @@ -94,12 +90,27 @@ export function useTransactionDisplayData (transactionGroup) { // transfers, we pass an additional argument to these hooks that will be // false for non-token transactions. This additional argument forces the // hook to return null - const token = isTokenCategory && knownTokens.find(({ address }) => address === recipientAddress) - const tokenData = useTokenData(initialTransaction?.txParams?.data, isTokenCategory) - const tokenDisplayValue = useTokenDisplayValue(initialTransaction?.txParams?.data, token, isTokenCategory) - const tokenFiatAmount = useTokenFiatAmount(token?.address, tokenDisplayValue, token?.symbol) + const token = + isTokenCategory && + knownTokens.find(({ address }) => address === recipientAddress) + const tokenData = useTokenData( + initialTransaction?.txParams?.data, + isTokenCategory, + ) + const tokenDisplayValue = useTokenDisplayValue( + initialTransaction?.txParams?.data, + token, + isTokenCategory, + ) + const tokenFiatAmount = useTokenFiatAmount( + token?.address, + tokenDisplayValue, + token?.symbol, + ) - const origin = stripHttpSchemes(initialTransaction.origin || initialTransaction.msgParams?.origin || '') + const origin = stripHttpSchemes( + initialTransaction.origin || initialTransaction.msgParams?.origin || '', + ) // used to append to the primary display value. initialized to either token.symbol or undefined // but can later be modified if dealing with a swap @@ -115,7 +126,12 @@ export function useTransactionDisplayData (transactionGroup) { // The primary title of the Tx that will be displayed in the activity list let title - const { swapTokenValue, isNegative, swapTokenFiatAmount, isViewingReceivedTokenFromSwap } = useSwappedTokenValue(transactionGroup, currentAsset) + const { + swapTokenValue, + isNegative, + swapTokenFiatAmount, + isViewingReceivedTokenFromSwap, + } = useSwappedTokenValue(transactionGroup, currentAsset) // There are seven types of transaction entries that are currently differentiated in the design // 1. Signature request @@ -127,13 +143,12 @@ export function useTransactionDisplayData (transactionGroup) { // 7. Swap Approval if (transactionCategory === null || transactionCategory === undefined) { - category = TRANSACTION_CATEGORY_SIGNATURE_REQUEST + category = TRANSACTION_GROUP_CATEGORIES.SIGNATURE_REQUEST title = t('signatureRequest') subtitle = origin subtitleContainsOrigin = true - - } else if (transactionCategory === SWAP) { - category = TRANSACTION_CATEGORY_SWAP + } else if (transactionCategory === TRANSACTION_CATEGORIES.SWAP) { + category = TRANSACTION_GROUP_CATEGORIES.SWAP title = t('swapTokenToToken', [ initialTransaction.sourceTokenSymbol, initialTransaction.destinationTokenSymbol, @@ -152,38 +167,45 @@ export function useTransactionDisplayData (transactionGroup) { } else { prefix = '-' } - } else if (transactionCategory === SWAP_APPROVAL) { - category = TRANSACTION_CATEGORY_APPROVAL + } else if (transactionCategory === TRANSACTION_CATEGORIES.SWAP_APPROVAL) { + category = TRANSACTION_GROUP_CATEGORIES.APPROVAL title = t('swapApproval', [primaryTransaction.sourceTokenSymbol]) subtitle = origin subtitleContainsOrigin = true primarySuffix = primaryTransaction.sourceTokenSymbol - } else if (transactionCategory === TOKEN_METHOD_APPROVE) { - category = TRANSACTION_CATEGORY_APPROVAL + } else if ( + transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE + ) { + category = TRANSACTION_GROUP_CATEGORIES.APPROVAL prefix = '' title = t('approveSpendLimit', [token?.symbol || t('token')]) subtitle = origin subtitleContainsOrigin = true - } else if (transactionCategory === DEPLOY_CONTRACT_ACTION_KEY || transactionCategory === CONTRACT_INTERACTION_KEY) { - category = TRANSACTION_CATEGORY_INTERACTION - title = (methodData?.name && camelCaseToCapitalize(methodData.name)) || t(transactionCategory) + } else if ( + transactionCategory === TRANSACTION_CATEGORIES.DEPLOY_CONTRACT || + transactionCategory === TRANSACTION_CATEGORIES.CONTRACT_INTERACTION + ) { + category = TRANSACTION_GROUP_CATEGORIES.INTERACTION + title = + (methodData?.name && camelCaseToCapitalize(methodData.name)) || + t(transactionCategory) subtitle = origin subtitleContainsOrigin = true - - } else if (transactionCategory === INCOMING_TRANSACTION) { - category = TRANSACTION_CATEGORY_RECEIVE + } else if (transactionCategory === TRANSACTION_CATEGORIES.INCOMING) { + category = TRANSACTION_GROUP_CATEGORIES.RECEIVE title = t('receive') prefix = '' subtitle = t('fromAddress', [shortenAddress(senderAddress)]) - - } else if (transactionCategory === TOKEN_METHOD_TRANSFER_FROM || transactionCategory === TOKEN_METHOD_TRANSFER) { - category = TRANSACTION_CATEGORY_SEND + } else if ( + transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM || + transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER + ) { + category = TRANSACTION_GROUP_CATEGORIES.SEND title = t('sendSpecifiedTokens', [token?.symbol || t('token')]) recipientAddress = getTokenAddressParam(tokenData) subtitle = t('toAddress', [shortenAddress(recipientAddress)]) - - } else if (transactionCategory === SEND_ETHER_ACTION_KEY) { - category = TRANSACTION_CATEGORY_SEND + } else if (transactionCategory === TRANSACTION_CATEGORIES.SENT_ETHER) { + category = TRANSACTION_GROUP_CATEGORIES.SEND title = t('sendETH') subtitle = t('toAddress', [shortenAddress(recipientAddress)]) } @@ -211,13 +233,18 @@ export function useTransactionDisplayData (transactionGroup) { date, subtitle, subtitleContainsOrigin, - primaryCurrency: transactionCategory === SWAP && isPending ? '' : primaryCurrency, + primaryCurrency: + transactionCategory === TRANSACTION_CATEGORIES.SWAP && isPending + ? '' + : primaryCurrency, senderAddress, recipientAddress, - secondaryCurrency: ( + secondaryCurrency: (isTokenCategory && !tokenFiatAmount) || - (transactionCategory === SWAP && !swapTokenFiatAmount) - ) ? undefined : secondaryCurrency, + (transactionCategory === TRANSACTION_CATEGORIES.SWAP && + !swapTokenFiatAmount) + ? undefined + : secondaryCurrency, displayedStatusKey, isPending, isSubmitted, diff --git a/ui/app/hooks/useTransactionTimeRemaining.js b/ui/app/hooks/useTransactionTimeRemaining.js index 50f413d05..96deadf07 100644 --- a/ui/app/hooks/useTransactionTimeRemaining.js +++ b/ui/app/hooks/useTransactionTimeRemaining.js @@ -3,7 +3,12 @@ import { useRef, useEffect, useState, useMemo } from 'react' import { isEqual } from 'lodash' import { captureException } from '@sentry/browser' import { hexWEIToDecGWEI } from '../helpers/utils/conversions.util' -import { getEstimatedGasPrices, getEstimatedGasTimes, getFeatureFlags, getIsMainnet } from '../selectors' +import { + getEstimatedGasPrices, + getEstimatedGasTimes, + getFeatureFlags, + getIsMainnet, +} from '../selectors' import { getRawTimeEstimateData } from '../helpers/utils/gas-time-estimates.util' import { getCurrentLocale } from '../ducks/metamask/metamask' @@ -13,12 +18,15 @@ import { getCurrentLocale } from '../ducks/metamask/metamask' * @param {number} submittedTime - timestamp of when the tx was submitted * @return {number} minutes remaining */ -function calcTransactionTimeRemaining (initialTimeEstimate, submittedTime) { - const currentTime = (new Date()).getTime() +function calcTransactionTimeRemaining(initialTimeEstimate, submittedTime) { + const currentTime = new Date().getTime() const timeElapsedSinceSubmission = (currentTime - submittedTime) / 1000 - const timeRemainingOnEstimate = initialTimeEstimate - timeElapsedSinceSubmission + const timeRemainingOnEstimate = + initialTimeEstimate - timeElapsedSinceSubmission - const renderingTimeRemainingEstimate = Math.round(timeRemainingOnEstimate / 60) + const renderingTimeRemainingEstimate = Math.round( + timeRemainingOnEstimate / 60, + ) return renderingTimeRemainingEstimate } @@ -33,7 +41,7 @@ function calcTransactionTimeRemaining (initialTimeEstimate, submittedTime) { * @param {boolean} dontFormat - Whether the result should be be formatted, or just a number of minutes * @returns {string | undefined} i18n formatted string if applicable */ -export function useTransactionTimeRemaining ( +export function useTransactionTimeRemaining( isSubmitted, isEarliestNonce, submittedTime, @@ -53,15 +61,20 @@ export function useTransactionTimeRemaining ( const featureFlags = useSelector(getFeatureFlags) const transactionTimeFeatureActive = featureFlags?.transactionTime - const rtf = new Intl.RelativeTimeFormat(locale.replace('_', '-'), { numeric: 'auto', style: 'narrow' }) + const rtf = new Intl.RelativeTimeFormat(locale.replace('_', '-'), { + numeric: 'auto', + style: 'narrow', + }) // Memoize this value so it can be used as a dependency in the effect below const initialTimeEstimate = useMemo(() => { const customGasPrice = Number(hexWEIToDecGWEI(currentGasPrice)) try { - const { - newTimeEstimate, - } = getRawTimeEstimateData(customGasPrice, gasPrices, estimatedTimes) + const { newTimeEstimate } = getRawTimeEstimateData( + customGasPrice, + gasPrices, + estimatedTimes, + ) return newTimeEstimate } catch (error) { captureException(error) @@ -71,8 +84,8 @@ export function useTransactionTimeRemaining ( useEffect(() => { if ( - (isMainNet && - (transactionTimeFeatureActive || forceAllow)) && + isMainNet && + (transactionTimeFeatureActive || forceAllow) && isSubmitted && isEarliestNonce && !isNaN(initialTimeEstimate) diff --git a/ui/app/hooks/useUserPreferencedCurrency.js b/ui/app/hooks/useUserPreferencedCurrency.js index 986ef6dd7..1764fa24e 100644 --- a/ui/app/hooks/useUserPreferencedCurrency.js +++ b/ui/app/hooks/useUserPreferencedCurrency.js @@ -1,5 +1,9 @@ import { useSelector } from 'react-redux' -import { getPreferences, getShouldShowFiat, getNativeCurrency } from '../selectors' +import { + getPreferences, + getShouldShowFiat, + getNativeCurrency, +} from '../selectors' import { PRIMARY, SECONDARY, ETH } from '../helpers/constants/common' /** @@ -29,21 +33,24 @@ import { PRIMARY, SECONDARY, ETH } from '../helpers/constants/common' * @param {UseUserPreferencedCurrencyOptions} opts - options to override default values * @return {UserPreferredCurrency} */ -export function useUserPreferencedCurrency (type, opts = {}) { +export function useUserPreferencedCurrency(type, opts = {}) { const nativeCurrency = useSelector(getNativeCurrency) - const { - useNativeCurrencyAsPrimaryCurrency, - } = useSelector(getPreferences) + const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences) const showFiat = useSelector(getShouldShowFiat) let currency, numberOfDecimals - if (!showFiat || (type === PRIMARY && useNativeCurrencyAsPrimaryCurrency) || - (type === SECONDARY && !useNativeCurrencyAsPrimaryCurrency)) { + if ( + !showFiat || + (type === PRIMARY && useNativeCurrencyAsPrimaryCurrency) || + (type === SECONDARY && !useNativeCurrencyAsPrimaryCurrency) + ) { // Display ETH currency = nativeCurrency || ETH numberOfDecimals = opts.numberOfDecimals || opts.ethNumberOfDecimals || 6 - } else if ((type === SECONDARY && useNativeCurrencyAsPrimaryCurrency) || - (type === PRIMARY && !useNativeCurrencyAsPrimaryCurrency)) { + } else if ( + (type === SECONDARY && useNativeCurrencyAsPrimaryCurrency) || + (type === PRIMARY && !useNativeCurrencyAsPrimaryCurrency) + ) { // Display Fiat numberOfDecimals = opts.numberOfDecimals || opts.fiatNumberOfDecimals || 2 } diff --git a/ui/app/pages/add-token/add-token.component.js b/ui/app/pages/add-token/add-token.component.js index 00f17d271..20d673f73 100644 --- a/ui/app/pages/add-token/add-token.component.js +++ b/ui/app/pages/add-token/add-token.component.js @@ -7,6 +7,7 @@ import { CONFIRM_ADD_TOKEN_ROUTE } from '../../helpers/constants/routes' import TextField from '../../components/ui/text-field' import PageContainer from '../../components/ui/page-container' import { Tabs, Tab } from '../../components/ui/tabs' +import { addHexPrefix } from '../../../../app/scripts/lib/util' import TokenList from './token-list' import TokenSearch from './token-search' @@ -41,7 +42,7 @@ class AddToken extends Component { forceEditSymbol: false, } - componentDidMount () { + componentDidMount() { this.tokenInfoGetter = tokenInfoGetter() const { pendingTokens = {} } = this.props const pendingTokenKeys = Object.keys(pendingTokens) @@ -67,11 +68,16 @@ class AddToken extends Component { decimals: customDecimals = 0, } = customToken - this.setState({ selectedTokens, customAddress, customSymbol, customDecimals }) + this.setState({ + selectedTokens, + customAddress, + customSymbol, + customDecimals, + }) } } - handleToggleToken (token) { + handleToggleToken(token) { const { address } = token const { selectedTokens = {} } = this.state const selectedTokensCopy = { ...selectedTokens } @@ -88,7 +94,7 @@ class AddToken extends Component { }) } - hasError () { + hasError() { const { tokenSelectorError, customAddressError, @@ -96,15 +102,20 @@ class AddToken extends Component { customDecimalsError, } = this.state - return tokenSelectorError || customAddressError || customSymbolError || customDecimalsError + return ( + tokenSelectorError || + customAddressError || + customSymbolError || + customDecimalsError + ) } - hasSelected () { + hasSelected() { const { customAddress = '', selectedTokens = {} } = this.state return customAddress || Object.keys(selectedTokens).length > 0 } - handleNext () { + handleNext() { if (this.hasError()) { return } @@ -132,7 +143,7 @@ class AddToken extends Component { history.push(CONFIRM_ADD_TOKEN_ROUTE) } - async attemptToAutoFillTokenParams (address) { + async attemptToAutoFillTokenParams(address) { const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(address) const autoFilled = Boolean(symbol && decimals) @@ -141,7 +152,7 @@ class AddToken extends Component { this.handleCustomDecimalsChange(decimals) } - handleCustomAddressChange (value) { + handleCustomAddressChange(value) { const customAddress = value.trim() this.setState({ customAddress, @@ -151,7 +162,7 @@ class AddToken extends Component { }) const isValidAddress = ethUtil.isValidAddress(customAddress) - const standardAddress = ethUtil.addHexPrefix(customAddress).toLowerCase() + const standardAddress = addHexPrefix(customAddress).toLowerCase() switch (true) { case !isValidAddress: @@ -183,7 +194,7 @@ class AddToken extends Component { } } - handleCustomSymbolChange (value) { + handleCustomSymbolChange(value) { const customSymbol = value.trim() const symbolLength = customSymbol.length let customSymbolError = null @@ -195,9 +206,10 @@ class AddToken extends Component { this.setState({ customSymbol, customSymbolError }) } - handleCustomDecimalsChange (value) { + handleCustomDecimalsChange(value) { const customDecimals = value.trim() - const validDecimals = customDecimals !== null && + const validDecimals = + customDecimals !== null && customDecimals !== '' && customDecimals >= 0 && customDecimals <= 36 @@ -210,7 +222,7 @@ class AddToken extends Component { this.setState({ customDecimals, customDecimalsError }) } - renderCustomTokenForm () { + renderCustomTokenForm() { const { customAddress, customSymbol, @@ -237,12 +249,12 @@ class AddToken extends Component { /> {this.context.t('tokenSymbol')} - {(autoFilled && !forceEditSymbol) && ( + {autoFilled && !forceEditSymbol && (
    this.setState({ forceEditSymbol: true })} @@ -251,7 +263,7 @@ class AddToken extends Component {
    )}
    - )} + } type="text" value={customSymbol} onChange={(e) => this.handleCustomSymbolChange(e.target.value)} @@ -275,13 +287,15 @@ class AddToken extends Component { ) } - renderSearchToken () { + renderSearchToken() { const { tokenSelectorError, selectedTokens, searchResults } = this.state return (
    this.setState({ searchResults: results })} + onSearch={({ results = [] }) => + this.setState({ searchResults: results }) + } error={tokenSelectorError} />
    @@ -295,20 +309,18 @@ class AddToken extends Component { ) } - renderTabs () { + renderTabs() { return ( - - { this.renderSearchToken() } - + {this.renderSearchToken()} - { this.renderCustomTokenForm() } + {this.renderCustomTokenForm()} ) } - render () { + render() { const { history, clearPendingTokens, mostRecentOverviewPage } = this.props return ( diff --git a/ui/app/pages/add-token/add-token.container.js b/ui/app/pages/add-token/add-token.container.js index 9f3f6d21e..80059a689 100644 --- a/ui/app/pages/add-token/add-token.container.js +++ b/ui/app/pages/add-token/add-token.container.js @@ -5,7 +5,9 @@ import { getMostRecentOverviewPage } from '../../ducks/history/history' import AddToken from './add-token.component' const mapStateToProps = (state) => { - const { metamask: { identities, tokens, pendingTokens } } = state + const { + metamask: { identities, tokens, pendingTokens }, + } = state return { identities, mostRecentOverviewPage: getMostRecentOverviewPage(state), diff --git a/ui/app/pages/add-token/tests/add-token.test.js b/ui/app/pages/add-token/tests/add-token.test.js index 8138845e7..300a6b019 100644 --- a/ui/app/pages/add-token/tests/add-token.test.js +++ b/ui/app/pages/add-token/tests/add-token.test.js @@ -33,7 +33,8 @@ describe('Add Token', function () { wrapper = mountWithRouter( - , store, + , + store, ) wrapper.find({ name: 'customToken' }).simulate('click') @@ -44,7 +45,9 @@ describe('Add Token', function () { }) it('next button is disabled when no fields are populated', function () { - const nextButton = wrapper.find('.button.btn-secondary.page-container__footer-button') + const nextButton = wrapper.find( + '.button.btn-secondary.page-container__footer-button', + ) assert.equal(nextButton.props().disabled, true) }) @@ -55,7 +58,10 @@ describe('Add Token', function () { const customAddress = wrapper.find('input#custom-address') customAddress.simulate('change', event) - assert.equal(wrapper.find('AddToken').instance().state.customAddress, tokenAddress) + assert.equal( + wrapper.find('AddToken').instance().state.customAddress, + tokenAddress, + ) }) it('edits token symbol', function () { @@ -64,7 +70,10 @@ describe('Add Token', function () { const customAddress = wrapper.find('#custom-symbol') customAddress.last().simulate('change', event) - assert.equal(wrapper.find('AddToken').instance().state.customSymbol, tokenSymbol) + assert.equal( + wrapper.find('AddToken').instance().state.customSymbol, + tokenSymbol, + ) }) it('edits token decimal precision', function () { @@ -73,11 +82,16 @@ describe('Add Token', function () { const customAddress = wrapper.find('#custom-decimals') customAddress.last().simulate('change', event) - assert.equal(wrapper.find('AddToken').instance().state.customDecimals, tokenPrecision) + assert.equal( + wrapper.find('AddToken').instance().state.customDecimals, + tokenPrecision, + ) }) it('next', function () { - const nextButton = wrapper.find('.button.btn-secondary.page-container__footer-button') + const nextButton = wrapper.find( + '.button.btn-secondary.page-container__footer-button', + ) nextButton.simulate('click') assert(props.setPendingTokens.calledOnce) @@ -86,7 +100,9 @@ describe('Add Token', function () { }) it('cancels', function () { - const cancelButton = wrapper.find('button.btn-default.page-container__footer-button') + const cancelButton = wrapper.find( + 'button.btn-default.page-container__footer-button', + ) cancelButton.simulate('click') assert(props.clearPendingTokens.calledOnce) diff --git a/ui/app/pages/add-token/token-list/index.scss b/ui/app/pages/add-token/token-list/index.scss index 165b175f3..f9f296098 100644 --- a/ui/app/pages/add-token/token-list/index.scss +++ b/ui/app/pages/add-token/token-list/index.scss @@ -2,7 +2,7 @@ .token-list { &__title { - font-size: 0.75rem; + @include H7; } &__tokens-container { @@ -20,15 +20,16 @@ box-sizing: border-box; border-radius: 10px; cursor: pointer; - border: 2px solid transparent; + border: 1px solid transparent; position: relative; - &:hover { - border: 2px solid rgba($malibu-blue, 0.5); + &:hover, + &:focus { + border-color: rgba($malibu-blue, 0.5); } &--selected { - border: 2px solid $malibu-blue !important; + border-color: $malibu-blue !important; } &--disabled { diff --git a/ui/app/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js b/ui/app/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js index ae14581e8..bc47745cc 100644 --- a/ui/app/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js +++ b/ui/app/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js @@ -7,12 +7,12 @@ export default class TokenListPlaceholder extends Component { t: PropTypes.func, } - render () { + render() { return (
    - { this.context.t('addAcquiredTokens') } + {this.context.t('addAcquiredTokens')}
    ) diff --git a/ui/app/pages/add-token/token-list/token-list.component.js b/ui/app/pages/add-token/token-list/token-list.component.js index 633ffea9c..83e9c352d 100644 --- a/ui/app/pages/add-token/token-list/token-list.component.js +++ b/ui/app/pages/add-token/token-list/token-list.component.js @@ -16,46 +16,57 @@ export default class InfoBox extends Component { onToggleToken: PropTypes.func, } - render () { - const { results = [], selectedTokens = {}, onToggleToken, tokens = [] } = this.props + render() { + const { + results = [], + selectedTokens = {}, + onToggleToken, + tokens = [], + } = this.props - return results.length === 0 - ? - : ( -
    -
    - { this.context.t('searchResults') } -
    -
    - { - Array(6).fill(undefined) - .map((_, i) => { - const { logo, symbol, name, address } = results[i] || {} - const tokenAlreadyAdded = checkExistingAddresses(address, tokens) - - return Boolean(logo || symbol || name) && ( -
    !tokenAlreadyAdded && onToggleToken(results[i])} - key={i} - > -
    -
    -
    - { `${name} (${symbol})` } -
    -
    - ) - }) - } -
    + return results.length === 0 ? ( + + ) : ( +
    +
    + {this.context.t('searchResults')}
    - ) +
    + {Array(6) + .fill(undefined) + .map((_, i) => { + const { logo, symbol, name, address } = results[i] || {} + const tokenAlreadyAdded = checkExistingAddresses(address, tokens) + const onClick = () => + !tokenAlreadyAdded && onToggleToken(results[i]) + + return ( + Boolean(logo || symbol || name) && ( +
    event.key === 'Enter' && onClick()} + key={i} + tabIndex="0" + > +
    +
    + {`${name} (${symbol})`} +
    +
    + ) + ) + })} +
    +
    + ) } } diff --git a/ui/app/pages/add-token/token-search/token-search.component.js b/ui/app/pages/add-token/token-search/token-search.component.js index 2cca26e84..904fdcefb 100644 --- a/ui/app/pages/add-token/token-search/token-search.component.js +++ b/ui/app/pages/add-token/token-search/token-search.component.js @@ -40,7 +40,7 @@ export default class TokenSearch extends Component { searchQuery: '', } - handleSearch (searchQuery) { + handleSearch(searchQuery) { this.setState({ searchQuery }) const fuseSearchResult = fuse.search(searchQuery) const addressSearchResult = contractList.filter((token) => { @@ -50,18 +50,15 @@ export default class TokenSearch extends Component { this.props.onSearch({ searchQuery, results }) } - renderAdornment () { + renderAdornment() { return ( - + ) } - render () { + render() { const { error } = this.props const { searchQuery } = this.state diff --git a/ui/app/pages/asset/asset.js b/ui/app/pages/asset/asset.js index fa9479eb0..22baaf68a 100644 --- a/ui/app/pages/asset/asset.js +++ b/ui/app/pages/asset/asset.js @@ -22,11 +22,7 @@ const Asset = () => { } else { content = } - return ( -
    - { content } -
    - ) + return
    {content}
    } export default Asset diff --git a/ui/app/pages/asset/asset.scss b/ui/app/pages/asset/asset.scss index 4445cb67e..e265e4943 100644 --- a/ui/app/pages/asset/asset.scss +++ b/ui/app/pages/asset/asset.scss @@ -17,13 +17,14 @@ } .asset-breadcrumb { - font-size: 14px; + @include H6; + color: $Black-100; background-color: inherit; &__chevron { + font-size: $font-size-paragraph; padding: 0 10px 0 2px; - font-size: 16px; } &__asset { @@ -33,13 +34,13 @@ .token-options { &__button { - font-size: 20px; + font-size: $font-size-paragraph; color: $Black-100; background-color: inherit; padding: 2px 8px; } &__icon { - font-size: 16px; + @include Paragraph; } } diff --git a/ui/app/pages/asset/components/asset-breadcrumb.js b/ui/app/pages/asset/components/asset-breadcrumb.js index 136fec6b0..a081e5b36 100644 --- a/ui/app/pages/asset/components/asset-breadcrumb.js +++ b/ui/app/pages/asset/components/asset-breadcrumb.js @@ -3,15 +3,14 @@ import PropTypes from 'prop-types' const AssetBreadcrumb = ({ accountName, assetName, onBack }) => { return ( - ) } diff --git a/ui/app/pages/asset/components/asset-navigation.js b/ui/app/pages/asset/components/asset-navigation.js index 5423b1094..2af0fac82 100644 --- a/ui/app/pages/asset/components/asset-navigation.js +++ b/ui/app/pages/asset/components/asset-navigation.js @@ -6,8 +6,12 @@ import AssetBreadcrumb from './asset-breadcrumb' const AssetNavigation = ({ accountName, assetName, onBack, optionsButton }) => { return (
    - - { optionsButton } + + {optionsButton}
    ) } diff --git a/ui/app/pages/asset/components/native-asset.js b/ui/app/pages/asset/components/native-asset.js index 3bda491b8..cb7f85750 100644 --- a/ui/app/pages/asset/components/native-asset.js +++ b/ui/app/pages/asset/components/native-asset.js @@ -10,8 +10,10 @@ import { DEFAULT_ROUTE } from '../../../helpers/constants/routes' import AssetNavigation from './asset-navigation' -export default function NativeAsset ({ nativeCurrency }) { - const selectedAccountName = useSelector((state) => getSelectedIdentity(state).name) +export default function NativeAsset({ nativeCurrency }) { + const selectedAccountName = useSelector( + (state) => getSelectedIdentity(state).name, + ) const history = useHistory() return ( diff --git a/ui/app/pages/asset/components/token-asset.js b/ui/app/pages/asset/components/token-asset.js index 84c7eee37..81198ee5d 100644 --- a/ui/app/pages/asset/components/token-asset.js +++ b/ui/app/pages/asset/components/token-asset.js @@ -6,17 +6,22 @@ import { createAccountLink } from '@metamask/etherscan-link' import TransactionList from '../../../components/app/transaction-list' import { TokenOverview } from '../../../components/app/wallet-overview' -import { getCurrentNetworkId, getSelectedIdentity } from '../../../selectors/selectors' +import { + getCurrentNetworkId, + getSelectedIdentity, +} from '../../../selectors/selectors' import { DEFAULT_ROUTE } from '../../../helpers/constants/routes' import { showModal } from '../../../store/actions' import AssetNavigation from './asset-navigation' import TokenOptions from './token-options' -export default function TokenAsset ({ token }) { +export default function TokenAsset({ token }) { const dispatch = useDispatch() const network = useSelector(getCurrentNetworkId) - const selectedAccountName = useSelector((state) => getSelectedIdentity(state).name) + const selectedAccountName = useSelector( + (state) => getSelectedIdentity(state).name, + ) const history = useHistory() return ( @@ -25,16 +30,18 @@ export default function TokenAsset ({ token }) { accountName={selectedAccountName} assetName={token.symbol} onBack={() => history.push(DEFAULT_ROUTE)} - optionsButton={( + optionsButton={ dispatch(showModal({ name: 'HIDE_TOKEN_CONFIRMATION', token }))} + onRemove={() => + dispatch(showModal({ name: 'HIDE_TOKEN_CONFIRMATION', token })) + } onViewEtherscan={() => { const url = createAccountLink(token.address, network) global.platform.openTab({ url }) }} tokenSymbol={token.symbol} /> - )} + } /> diff --git a/ui/app/pages/asset/components/token-options.js b/ui/app/pages/asset/components/token-options.js index eaf142d34..613455f57 100644 --- a/ui/app/pages/asset/components/token-options.js +++ b/ui/app/pages/asset/components/token-options.js @@ -6,7 +6,9 @@ import { Menu, MenuItem } from '../../../components/ui/menu' const TokenOptions = ({ onRemove, onViewEtherscan, tokenSymbol }) => { const t = useContext(I18nContext) - const [tokenOptionsButtonElement, setTokenOptionsButtonElement] = useState(null) + const [tokenOptionsButtonElement, setTokenOptionsButtonElement] = useState( + null, + ) const [tokenOptionsOpen, setTokenOptionsOpen] = useState(false) return ( @@ -18,34 +20,33 @@ const TokenOptions = ({ onRemove, onViewEtherscan, tokenSymbol }) => { ref={setTokenOptionsButtonElement} title={t('tokenOptions')} /> - { - tokenOptionsOpen - ? ( - setTokenOptionsOpen(false)} > - { - setTokenOptionsOpen(false) - onViewEtherscan() - }} - > - { t('viewOnEtherscan') } - - { - setTokenOptionsOpen(false) - onRemove() - }} - > - { t('hideTokenSymbol', [tokenSymbol]) } - - - ) - : null - } + {tokenOptionsOpen ? ( + setTokenOptionsOpen(false)} + > + { + setTokenOptionsOpen(false) + onViewEtherscan() + }} + > + {t('viewOnEtherscan')} + + { + setTokenOptionsOpen(false) + onRemove() + }} + > + {t('hideTokenSymbol', [tokenSymbol])} + + + ) : null} ) } diff --git a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js index 28b04ba40..8243a10bb 100644 --- a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js +++ b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js @@ -20,15 +20,15 @@ export default class ConfirmAddSuggestedToken extends Component { tokens: PropTypes.array, } - componentDidMount () { + componentDidMount() { this._checkPendingTokens() } - componentDidUpdate () { + componentDidUpdate() { this._checkPendingTokens() } - _checkPendingTokens () { + _checkPendingTokens() { const { mostRecentOverviewPage, pendingTokens = {}, history } = this.props if (Object.keys(pendingTokens).length > 0) { @@ -42,14 +42,19 @@ export default class ConfirmAddSuggestedToken extends Component { } } - getTokenName (name, symbol) { - return typeof name === 'undefined' - ? symbol - : `${name} (${symbol})` + getTokenName(name, symbol) { + return typeof name === 'undefined' ? symbol : `${name} (${symbol})` } - render () { - const { addToken, pendingTokens, tokens, removeSuggestedTokens, history, mostRecentOverviewPage } = this.props + render() { + const { + addToken, + pendingTokens, + tokens, + removeSuggestedTokens, + history, + mostRecentOverviewPage, + } = this.props const pendingTokenKey = Object.keys(pendingTokens)[0] const pendingToken = pendingTokens[pendingTokenKey] const hasTokenDuplicates = this.checkTokenDuplicates(pendingTokens, tokens) @@ -59,65 +64,56 @@ export default class ConfirmAddSuggestedToken extends Component {
    - { this.context.t('addSuggestedTokens') } + {this.context.t('addSuggestedTokens')}
    - { this.context.t('likeToAddTokens') } + {this.context.t('likeToAddTokens')}
    - { hasTokenDuplicates ? - ( -
    - { this.context.t('knownTokenWarning') } -
    - ) : null - } - { reusesName ? - ( -
    - { this.context.t('reusedTokenNameWarning') } -
    - ) : null - } + {hasTokenDuplicates ? ( +
    {this.context.t('knownTokenWarning')}
    + ) : null} + {reusesName ? ( +
    + {this.context.t('reusedTokenNameWarning')} +
    + ) : null}
    - { this.context.t('token') } + {this.context.t('token')}
    - { this.context.t('balance') } + {this.context.t('balance')}
    - { - Object.entries(pendingTokens) - .map(([address, token]) => { - const { name, symbol, image } = token + {Object.entries(pendingTokens).map(([address, token]) => { + const { name, symbol, image } = token - return ( -
    -
    - -
    - { this.getTokenName(name, symbol) } -
    -
    -
    - -
    + return ( +
    +
    + +
    + {this.getTokenName(name, symbol)}
    - ) - }) - } +
    +
    + +
    +
    + ) + })}
    @@ -128,11 +124,12 @@ export default class ConfirmAddSuggestedToken extends Component { large className="page-container__footer-button" onClick={() => { - removeSuggestedTokens() - .then(() => history.push(mostRecentOverviewPage)) + removeSuggestedTokens().then(() => + history.push(mostRecentOverviewPage), + ) }} > - { this.context.t('cancel') } + {this.context.t('cancel')}
    @@ -153,7 +150,7 @@ export default class ConfirmAddSuggestedToken extends Component { ) } - checkTokenDuplicates (pendingTokens, tokens) { + checkTokenDuplicates(pendingTokens, tokens) { const pending = Object.keys(pendingTokens) const existing = tokens.map((token) => token.address) const dupes = pending.filter((proposed) => { @@ -169,15 +166,15 @@ export default class ConfirmAddSuggestedToken extends Component { * - Does not share an address with that same `tokens` member. * This should be flagged as possibly deceptive or confusing. */ - checkNameReuse (pendingTokens, tokens) { + checkNameReuse(pendingTokens, tokens) { const duplicates = Object.keys(pendingTokens) .map((addr) => pendingTokens[addr]) .filter((token) => { - const dupes = tokens.filter((old) => old.symbol === token.symbol) + const dupes = tokens + .filter((old) => old.symbol === token.symbol) .filter((old) => old.address !== token.address) return dupes.length > 0 }) return duplicates.length > 0 } - } diff --git a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js index 319d1ed82..51a29a555 100644 --- a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js +++ b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js @@ -6,7 +6,9 @@ import { getMostRecentOverviewPage } from '../../ducks/history/history' import ConfirmAddSuggestedToken from './confirm-add-suggested-token.component' const mapStateToProps = (state) => { - const { metamask: { pendingTokens, suggestedTokens, tokens } } = state + const { + metamask: { pendingTokens, suggestedTokens, tokens }, + } = state const params = { ...pendingTokens, ...suggestedTokens } return { @@ -18,7 +20,8 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return { - addToken: ({ address, symbol, decimals, image }) => dispatch(addToken(address, symbol, Number(decimals), image)), + addToken: ({ address, symbol, decimals, image }) => + dispatch(addToken(address, symbol, Number(decimals), image)), removeSuggestedTokens: () => dispatch(removeSuggestedTokens()), } } diff --git a/ui/app/pages/confirm-add-token/confirm-add-token.component.js b/ui/app/pages/confirm-add-token/confirm-add-token.component.js index f79d962eb..3a0b0b3d0 100644 --- a/ui/app/pages/confirm-add-token/confirm-add-token.component.js +++ b/ui/app/pages/confirm-add-token/confirm-add-token.component.js @@ -18,7 +18,7 @@ export default class ConfirmAddToken extends Component { pendingTokens: PropTypes.object, } - componentDidMount () { + componentDidMount() { const { mostRecentOverviewPage, pendingTokens = {}, history } = this.props if (Object.keys(pendingTokens).length === 0) { @@ -26,63 +26,64 @@ export default class ConfirmAddToken extends Component { } } - getTokenName (name, symbol) { - return typeof name === 'undefined' - ? symbol - : `${name} (${symbol})` + getTokenName(name, symbol) { + return typeof name === 'undefined' ? symbol : `${name} (${symbol})` } - render () { - const { history, addTokens, clearPendingTokens, mostRecentOverviewPage, pendingTokens } = this.props + render() { + const { + history, + addTokens, + clearPendingTokens, + mostRecentOverviewPage, + pendingTokens, + } = this.props return (
    - { this.context.t('addTokens') } + {this.context.t('addTokens')}
    - { this.context.t('likeToAddTokens') } + {this.context.t('likeToAddTokens')}
    - { this.context.t('token') } + {this.context.t('token')}
    - { this.context.t('balance') } + {this.context.t('balance')}
    - { - Object.entries(pendingTokens) - .map(([address, token]) => { - const { name, symbol } = token + {Object.entries(pendingTokens).map(([address, token]) => { + const { name, symbol } = token - return ( -
    -
    - -
    - { this.getTokenName(name, symbol) } -
    -
    -
    - -
    + return ( +
    +
    + +
    + {this.getTokenName(name, symbol)}
    - ) - }) - } +
    +
    + +
    +
    + ) + })}
    @@ -94,26 +95,27 @@ export default class ConfirmAddToken extends Component { className="page-container__footer-button" onClick={() => history.push(ADD_TOKEN_ROUTE)} > - { this.context.t('back') } + {this.context.t('back')}
    diff --git a/ui/app/pages/confirm-add-token/confirm-add-token.container.js b/ui/app/pages/confirm-add-token/confirm-add-token.container.js index 8c09ad837..08a01163c 100644 --- a/ui/app/pages/confirm-add-token/confirm-add-token.container.js +++ b/ui/app/pages/confirm-add-token/confirm-add-token.container.js @@ -5,7 +5,9 @@ import { getMostRecentOverviewPage } from '../../ducks/history/history' import ConfirmAddToken from './confirm-add-token.component' const mapStateToProps = (state) => { - const { metamask: { pendingTokens } } = state + const { + metamask: { pendingTokens }, + } = state return { mostRecentOverviewPage: getMostRecentOverviewPage(state), pendingTokens, diff --git a/ui/app/pages/confirm-add-token/index.scss b/ui/app/pages/confirm-add-token/index.scss index f1f021ddb..a049e9b24 100644 --- a/ui/app/pages/confirm-add-token/index.scss +++ b/ui/app/pages/confirm-add-token/index.scss @@ -2,7 +2,8 @@ padding: 16px; &__header { - font-size: 0.75rem; + @include H7; + display: flex; } @@ -26,17 +27,16 @@ align-items: flex-start; &__amount { + @include H1; + color: $scorpion; - font-size: 43px; - line-height: 43px; margin-right: 8px; } &__symbol { + @include H5; + color: $scorpion; - font-size: 16px; - font-weight: 400; - line-height: 24px; } } } diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js index 8c7394e6f..6cea504e0 100644 --- a/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js +++ b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js @@ -2,9 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' import Identicon from '../../../components/ui/identicon' -import { - addressSummary, -} from '../../../helpers/utils/util' +import { addressSummary } from '../../../helpers/utils/util' import { formatCurrency } from '../../../helpers/utils/confirm-tx.util' export default class ConfirmApproveContent extends Component { @@ -34,7 +32,7 @@ export default class ConfirmApproveContent extends Component { showFullTxDetails: false, } - renderApproveContentCard ({ + renderApproveContentCard({ symbol, title, showEdit, @@ -51,8 +49,12 @@ export default class ConfirmApproveContent extends Component { })} >
    -
    { symbol }
    -
    { title }
    +
    + {symbol} +
    +
    + {title} +
    {showEdit && (
    )}
    -
    - { content } -
    - { footer } +
    {content}
    + {footer}
    ) } // TODO: Add "Learn Why" with link to the feeAssociatedRequest text - renderTransactionDetailsContent () { + renderTransactionDetailsContent() { const { t } = this.context const { currentCurrency, @@ -81,51 +81,71 @@ export default class ConfirmApproveContent extends Component { return (
    - { t('feeAssociatedRequest') } + {t('feeAssociatedRequest')}
    - { formatCurrency(fiatTransactionTotal, currentCurrency) } + {formatCurrency(fiatTransactionTotal, currentCurrency)}
    - { `${ethTransactionTotal} ETH` } + {`${ethTransactionTotal} ETH`}
    ) } - renderPermissionContent () { + renderPermissionContent() { const { t } = this.context - const { customTokenAmount, tokenAmount, tokenSymbol, origin, toAddress } = this.props + const { + customTokenAmount, + tokenAmount, + tokenSymbol, + origin, + toAddress, + } = this.props return (
    -
    { t('accessAndSpendNotice', [origin]) }
    -
    -
    { t('amountWithColon') }
    -
    { `${Number(customTokenAmount || tokenAmount)} ${tokenSymbol}` }
    +
    + {t('accessAndSpendNotice', [origin])}
    -
    { t('toWithColon') }
    -
    { addressSummary(toAddress) }
    +
    + {t('amountWithColon')} +
    +
    + {`${Number(customTokenAmount || tokenAmount)} ${tokenSymbol}`} +
    +
    +
    +
    + {t('toWithColon')} +
    +
    + {addressSummary(toAddress)} +
    ) } - renderDataContent () { + renderDataContent() { const { t } = this.context const { data } = this.props return (
    -
    { t('functionApprove') }
    -
    { data }
    +
    + {t('functionApprove')} +
    +
    + {data} +
    ) } - render () { + render() { const { t } = this.context const { decimals, @@ -156,27 +176,27 @@ export default class ConfirmApproveContent extends Component { />
    - { t('allowOriginSpendToken', [origin, tokenSymbol]) } + {t('allowOriginSpendToken', [origin, tokenSymbol])}
    - { t('trustSiteApprovePermission', [origin, tokenSymbol]) } + {t('trustSiteApprovePermission', [origin, tokenSymbol])}
    -
    +
    showEditApprovalPermissionModal({ - customTokenAmount, - decimals, - origin, - setCustomAmount, - tokenAmount, - tokenSymbol, - tokenBalance, - })} + onClick={() => + showEditApprovalPermissionModal({ + customTokenAmount, + decimals, + origin, + setCustomAmount, + tokenAmount, + tokenSymbol, + tokenBalance, + }) + } > - { t('editPermission') } + {t('editPermission')}
    @@ -190,7 +210,11 @@ export default class ConfirmApproveContent extends Component { footer: (
    this.setState({ showFullTxDetails: !this.state.showFullTxDetails })} + onClick={() => + this.setState({ + showFullTxDetails: !this.state.showFullTxDetails, + }) + } >
    @@ -208,39 +232,36 @@ export default class ConfirmApproveContent extends Component { })}
    - { - showFullTxDetails - ? ( -
    -
    - {this.renderApproveContentCard({ - symbol: , - title: 'Permission', - content: this.renderPermissionContent(), - showEdit: true, - onEditClick: () => showEditApprovalPermissionModal({ - customTokenAmount, - decimals, - origin, - setCustomAmount, - tokenAmount, - tokenSymbol, - tokenBalance, - }), - })} -
    -
    - {this.renderApproveContentCard({ - symbol: , - title: 'Data', - content: this.renderDataContent(), - noBorder: true, - })} -
    -
    - ) - : null - } + {showFullTxDetails ? ( +
    +
    + {this.renderApproveContentCard({ + symbol: , + title: 'Permission', + content: this.renderPermissionContent(), + showEdit: true, + onEditClick: () => + showEditApprovalPermissionModal({ + customTokenAmount, + decimals, + origin, + setCustomAmount, + tokenAmount, + tokenSymbol, + tokenBalance, + }), + })} +
    +
    + {this.renderApproveContentCard({ + symbol: , + title: 'Data', + content: this.renderDataContent(), + noBorder: true, + })} +
    +
    + ) : null}
    ) } diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/index.scss b/ui/app/pages/confirm-approve/confirm-approve-content/index.scss index 459e5b184..e912f6675 100644 --- a/ui/app/pages/confirm-approve/confirm-approve-content/index.scss +++ b/ui/app/pages/confirm-approve/confirm-approve-content/index.scss @@ -28,9 +28,8 @@ } &__title { - font-weight: normal; - font-size: 24px; - line-height: 34px; + @include H3; + width: 100%; display: flex; justify-content: center; @@ -41,9 +40,8 @@ } &__description { - font-weight: normal; - font-size: 14px; - line-height: 20px; + @include H6; + margin-top: 16px; margin-bottom: 16px; color: #6a737d; @@ -62,15 +60,14 @@ padding-right: 24px; &__bold-text { + @include H6; + font-weight: bold; - font-size: 14px; - line-height: 20px; } &__thin-text { - font-weight: normal; - font-size: 12px; - line-height: 17px; + @include H7; + color: #6a737d; } } @@ -96,9 +93,9 @@ &__title, &__title-value { + @include H6; + font-weight: bold; - font-size: 14px; - line-height: 20px; } &__title { @@ -177,16 +174,16 @@ } &__primary-fee { + @include H4; + font-weight: bold; - font-size: 18px; - line-height: 25px; color: #000; } &__secondary-fee { + @include H6; + font-weight: normal; - font-size: 14px; - line-height: 20px; color: #8c8e94; } } @@ -223,23 +220,22 @@ } &__large-text { - font-size: 18px; - line-height: 25px; + @include H4; + color: #24292e; } &__medium-link-text { - font-size: 14px; - line-height: 20px; + @include H6; + font-weight: 500; color: $primary-blue; } &__medium-text, &__label { - font-weight: normal; - font-size: 14px; - line-height: 20px; + @include H6; + color: #24292e; } @@ -251,9 +247,8 @@ &__small-text, &__small-blue-text, &__info-row { - font-weight: normal; - font-size: 12px; - line-height: 17px; + @include H7; + color: #6a737d; } diff --git a/ui/app/pages/confirm-approve/confirm-approve.js b/ui/app/pages/confirm-approve/confirm-approve.js index 0c036a06b..e1109c473 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.js +++ b/ui/app/pages/confirm-approve/confirm-approve.js @@ -3,9 +3,7 @@ import { useDispatch, useSelector } from 'react-redux' import { useParams } from 'react-router-dom' import ConfirmTransactionBase from '../confirm-transaction-base' import { showModal } from '../../store/actions' -import { - getTokenData, -} from '../../helpers/utils/transactions.util' +import { getTokenData } from '../../helpers/utils/transactions.util' import { calcTokenAmount, getTokenAddressParam, @@ -17,20 +15,20 @@ import { transactionFeeSelector, txDataSelector, } from '../../selectors/confirm-transaction' -import { getCurrentCurrency, getDomainMetadata } from '../../selectors/selectors' +import { + getCurrentCurrency, + getDomainMetadata, +} from '../../selectors/selectors' import { currentNetworkTxListSelector } from '../../selectors/transactions' import { getCustomTxParamsData } from './confirm-approve.util' import ConfirmApproveContent from './confirm-approve-content' -export default function ConfirmApprove () { +export default function ConfirmApprove() { const dispatch = useDispatch() const { id: paramsTransactionId } = useParams() const { id: transactionId, - txParams: { - to: tokenAddress, - data, - } = {}, + txParams: { to: tokenAddress, data } = {}, } = useSelector(txDataSelector) const currentCurrency = useSelector(getCurrentCurrency) @@ -38,15 +36,18 @@ export default function ConfirmApprove () { const domainMetadata = useSelector(getDomainMetadata) const tokens = useSelector(getTokens) - const transaction = ( - currentNetworkTxList.find(({ id }) => id === (Number(paramsTransactionId) || transactionId)) || {} + const transaction = + currentNetworkTxList.find( + ({ id }) => id === (Number(paramsTransactionId) || transactionId), + ) || {} + const { ethTransactionTotal, fiatTransactionTotal } = useSelector((state) => + transactionFeeSelector(state, transaction), ) - const { - ethTransactionTotal, - fiatTransactionTotal, - } = useSelector((state) => transactionFeeSelector(state, transaction)) - const currentToken = (tokens && tokens.find(({ address }) => tokenAddress === address)) || { address: tokenAddress } + const currentToken = (tokens && + tokens.find(({ address }) => tokenAddress === address)) || { + address: tokenAddress, + } const { tokensWithBalances } = useTokenTracker([currentToken]) const tokenTrackerBalance = tokensWithBalances[0]?.balance || '' @@ -56,21 +57,19 @@ export default function ConfirmApprove () { const tokenData = getTokenData(data) const tokenValue = getTokenValueParam(tokenData) const toAddress = getTokenAddressParam(tokenData) - const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toString(10) + const tokenAmount = + tokenData && calcTokenAmount(tokenValue, decimals).toString(10) const [customPermissionAmount, setCustomPermissionAmount] = useState('') const previousTokenAmount = useRef(tokenAmount) - useEffect( - () => { - if (customPermissionAmount && previousTokenAmount.current !== tokenAmount) { - setCustomPermissionAmount(tokenAmount) - } - previousTokenAmount.current = tokenAmount - }, - [customPermissionAmount, tokenAmount], - ) + useEffect(() => { + if (customPermissionAmount && previousTokenAmount.current !== tokenAmount) { + setCustomPermissionAmount(tokenAmount) + } + previousTokenAmount.current = tokenAmount + }, [customPermissionAmount, tokenAmount]) const { origin } = transaction const formattedOrigin = origin @@ -94,7 +93,7 @@ export default function ConfirmApprove () { identiconAddress={tokenAddress} showAccountInHeader title={tokensText} - contentComponent={( + contentComponent={ dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData }))} - showEditApprovalPermissionModal={ - ({ - /* eslint-disable no-shadow */ - customTokenAmount, - decimals, - origin, - setCustomAmount, - tokenAmount, - tokenBalance, - tokenSymbol, - /* eslint-enable no-shadow */ - }) => dispatch( + showCustomizeGasModal={() => + dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData })) + } + showEditApprovalPermissionModal={({ + /* eslint-disable no-shadow */ + customTokenAmount, + decimals, + origin, + setCustomAmount, + tokenAmount, + tokenBalance, + tokenSymbol, + /* eslint-enable no-shadow */ + }) => + dispatch( showModal({ name: 'EDIT_APPROVAL_PERMISSION', customTokenAmount, @@ -135,7 +136,7 @@ export default function ConfirmApprove () { ethTransactionTotal={ethTransactionTotal} fiatTransactionTotal={fiatTransactionTotal} /> - )} + } hideSenderToRecipient customTxParamsData={customData} /> diff --git a/ui/app/pages/confirm-approve/confirm-approve.util.js b/ui/app/pages/confirm-approve/confirm-approve.util.js index afa53352a..b652256a1 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.util.js +++ b/ui/app/pages/confirm-approve/confirm-approve.util.js @@ -1,14 +1,23 @@ +import { TRANSACTION_CATEGORIES } from '../../../../shared/constants/transaction' import { decimalToHex } from '../../helpers/utils/conversions.util' -import { calcTokenValue, getTokenAddressParam } from '../../helpers/utils/token-util' +import { + calcTokenValue, + getTokenAddressParam, +} from '../../helpers/utils/token-util' import { getTokenData } from '../../helpers/utils/transactions.util' -export function getCustomTxParamsData (data, { customPermissionAmount, decimals }) { +export function getCustomTxParamsData( + data, + { customPermissionAmount, decimals }, +) { const tokenData = getTokenData(data) if (!tokenData) { throw new Error('Invalid data') - } else if (tokenData.name !== 'approve') { - throw new Error(`Invalid data; should be 'approve' method, but instead is '${tokenData.name}'`) + } else if (tokenData.name !== TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE) { + throw new Error( + `Invalid data; should be 'approve' method, but instead is '${tokenData.name}'`, + ) } let spender = getTokenAddressParam(tokenData) if (spender.startsWith('0x')) { @@ -19,10 +28,14 @@ export function getCustomTxParamsData (data, { customPermissionAmount, decimals if (!signature || !tokenValue) { throw new Error('Invalid data') } else if (tokenValue.length !== 64) { - throw new Error('Invalid token value; should be exactly 64 hex digits long (u256)') + throw new Error( + 'Invalid token value; should be exactly 64 hex digits long (u256)', + ) } - let customPermissionValue = decimalToHex(calcTokenValue(customPermissionAmount, decimals)) + let customPermissionValue = decimalToHex( + calcTokenValue(customPermissionAmount, decimals), + ) if (customPermissionValue.length > 64) { throw new Error('Custom value is larger than u256') } diff --git a/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js b/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js index 57310d7b2..9f036cb80 100644 --- a/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js +++ b/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js @@ -44,7 +44,9 @@ export default class ConfirmDecryptMessage extends Component { } componentDidMount = () => { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + if ( + getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION + ) { window.addEventListener('beforeunload', this._beforeUnload) } } @@ -54,11 +56,7 @@ export default class ConfirmDecryptMessage extends Component { } _beforeUnload = async (event) => { - const { - clearConfirmTransaction, - cancelDecryptMessage, - txData, - } = this.props + const { clearConfirmTransaction, cancelDecryptMessage, txData } = this.props const { metricsEvent } = this.context await cancelDecryptMessage(txData, event) metricsEvent({ @@ -72,7 +70,9 @@ export default class ConfirmDecryptMessage extends Component { } _removeBeforeUnload = () => { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + if ( + getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION + ) { window.removeEventListener('beforeunload', this._beforeUnload) } } @@ -96,7 +96,7 @@ export default class ConfirmDecryptMessage extends Component {
    - { this.context.t('decryptRequest') } + {this.context.t('decryptRequest')}
    @@ -113,13 +113,11 @@ export default class ConfirmDecryptMessage extends Component { return (
    - { `${t('account')}:` } + {`${t('account')}:`}
    - +
    ) @@ -127,7 +125,9 @@ export default class ConfirmDecryptMessage extends Component { renderBalance = () => { const { conversionRate } = this.props - const { fromAccount: { balance } } = this.state + const { + fromAccount: { balance }, + } = this.state const { t } = this.context const balanceInEther = conversionUtil(balance, { @@ -141,10 +141,10 @@ export default class ConfirmDecryptMessage extends Component { return (
    - { `${t('balance')}:` } + {`${t('balance')}:`}
    - { `${balanceInEther} ETH` } + {`${balanceInEther} ETH`}
    ) @@ -155,10 +155,7 @@ export default class ConfirmDecryptMessage extends Component { return (
    - +
    ) } @@ -166,9 +163,9 @@ export default class ConfirmDecryptMessage extends Component { renderAccountInfo = () => { return (
    - { this.renderAccount() } - { this.renderRequestIcon() } - { this.renderBalance() } + {this.renderAccount()} + {this.renderRequestIcon()} + {this.renderBalance()}
    ) } @@ -191,10 +188,8 @@ export default class ConfirmDecryptMessage extends Component { return (
    - { this.renderAccountInfo() } -
    + {this.renderAccountInfo()} +
    {origin.icon ? ( )} -
    - { notice } -
    +
    {notice}
    -
    -
    - { !hasDecrypted && !hasError ? txData.msgParams.data : rawMessage } - { hasError ? errorMessage : '' } +
    +
    + {!hasDecrypted && !hasError ? txData.msgParams.data : rawMessage} + {hasError ? errorMessage : ''}
    -
    + />
    { decryptMessageInline(txData, event).then((result) => { if (result.error) { - this.setState({ hasError: true, errorMessage: this.context.t('decryptInlineError', [result.error]) }) + this.setState({ + hasError: true, + errorMessage: this.context.t('decryptInlineError', [ + result.error, + ]), + }) } else { - this.setState({ hasDecrypted: true, rawMessage: result.rawData }) + this.setState({ + hasDecrypted: true, + rawMessage: result.rawData, + }) } }) }} > -
    +
    {t('decryptMetamask')}
    - { hasDecrypted ? - ( -
    this.copyMessage()} - onMouseDown={() => this.setState({ copyToClipboardPressed: true })} - onMouseUp={() => this.setState({ copyToClipboardPressed: false })} + {hasDecrypted ? ( +
    this.copyMessage()} + onMouseDown={() => this.setState({ copyToClipboardPressed: true })} + onMouseUp={() => this.setState({ copyToClipboardPressed: false })} + > + - -
    - {t('decryptCopy')} -
    - -
    -
    - ) - : +
    + {t('decryptCopy')} +
    + + +
    + ) : (
    - } + )}
    ) } @@ -316,7 +306,7 @@ export default class ConfirmDecryptMessage extends Component { history.push(mostRecentOverviewPage) }} > - { t('cancel') } + {t('cancel')}
    ) @@ -345,9 +335,9 @@ export default class ConfirmDecryptMessage extends Component { render = () => { return (
    - { this.renderHeader() } - { this.renderBody() } - { this.renderFooter() } + {this.renderHeader()} + {this.renderBody()} + {this.renderFooter()}
    ) } diff --git a/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.container.js b/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.container.js index 57d00021a..6be21e2ea 100644 --- a/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.container.js +++ b/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.container.js @@ -16,17 +16,17 @@ import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm import { getMostRecentOverviewPage } from '../../ducks/history/history' import ConfirmDecryptMessage from './confirm-decrypt-message.component' -function mapStateToProps (state) { +function mapStateToProps(state) { const { confirmTransaction, metamask: { domainMetadata = {} }, } = state - const { - txData = {}, - } = confirmTransaction + const { txData = {} } = confirmTransaction - const { msgParams: { from } } = txData + const { + msgParams: { from }, + } = txData const fromAccount = getTargetAccountWithSendEtherInfo(state, from) @@ -41,7 +41,7 @@ function mapStateToProps (state) { } } -function mapDispatchToProps (dispatch) { +function mapDispatchToProps(dispatch) { return { goHome: () => dispatch(goHome()), clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), diff --git a/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.scss b/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.scss index fd2ee53a8..fb7e6ebca 100644 --- a/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.scss +++ b/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.scss @@ -61,9 +61,9 @@ } &__header__text { + @include H3; + color: #5b5d67; - font-size: 22px; - line-height: 29px; z-index: 3; text-align: center; } @@ -97,14 +97,14 @@ } &__account-text { - font-size: 14px; + @include H6; } &__account-item { + @include H7; + height: 22px; background-color: $white; - line-height: 16px; - font-size: 12px; width: 124px; .account-list-item { @@ -130,8 +130,9 @@ } &__balance-text { + @include H6; + text-align: right; - font-size: 14px; } &__balance-value { @@ -153,8 +154,8 @@ } &__notice { - font-size: 14px; - line-height: 19px; + @include H6; + text-align: center; margin-top: 15px; margin-bottom: 11px; @@ -171,7 +172,8 @@ position: relative; &-text { - font-size: 0.7em; + @include H7; + height: 115px; } @@ -206,20 +208,21 @@ } &-lock-text { + @include H7; + width: 200px; - font-size: 0.75em; position: absolute; top: calc(50% + 5px); text-align: center; left: calc(50% - 100px); background-color: white; - line-height: 1em; border-radius: 3px; } &-copy { + @include H7; + justify-content: space-evenly; - font-size: 0.75em; margin-left: 20px; margin-right: 20px; display: flex; @@ -237,11 +240,12 @@ } &__footer { + @include H4; + width: 100%; display: flex; align-items: center; justify-content: center; - font-size: 22px; position: relative; flex: 0 0 auto; border-top: 1px solid $geyser; diff --git a/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js b/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js index 2bb2b8156..d799aa28d 100644 --- a/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js +++ b/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js @@ -12,48 +12,35 @@ export default class ConfirmDeployContract extends Component { txData: PropTypes.object, } - renderData () { + renderData() { const { t } = this.context - const { - txData: { - origin, - txParams: { - data, - } = {}, - } = {}, - } = this.props + const { txData: { origin, txParams: { data } = {} } = {} } = this.props return (
    - { `${t('origin')}:` } -
    -
    - { origin } + {`${t('origin')}:`}
    +
    {origin}
    - { `${t('bytes')}:` } -
    -
    - { ethUtil.toBuffer(data).length } + {`${t('bytes')}:`}
    +
    {ethUtil.toBuffer(data).length}
    - { `${t('hexData')}:` } -
    -
    - { data } + {`${t('hexData')}:`}
    +
    {data}
    ) } - render () { + render() { return ( { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + if ( + getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION + ) { window.addEventListener('beforeunload', this._beforeUnload) } } @@ -65,7 +67,9 @@ export default class ConfirmEncryptionPublicKey extends Component { } _removeBeforeUnload = () => { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + if ( + getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION + ) { window.removeEventListener('beforeunload', this._beforeUnload) } } @@ -76,7 +80,7 @@ export default class ConfirmEncryptionPublicKey extends Component {
    - { this.context.t('encryptionPublicKeyRequest') } + {this.context.t('encryptionPublicKeyRequest')}
    @@ -93,13 +97,11 @@ export default class ConfirmEncryptionPublicKey extends Component { return (
    - { `${t('account')}:` } + {`${t('account')}:`}
    - +
    ) @@ -108,7 +110,9 @@ export default class ConfirmEncryptionPublicKey extends Component { renderBalance = () => { const { conversionRate } = this.props const { t } = this.context - const { fromAccount: { balance } } = this.state + const { + fromAccount: { balance }, + } = this.state const balanceInEther = conversionUtil(balance, { fromNumericBase: 'hex', @@ -121,10 +125,10 @@ export default class ConfirmEncryptionPublicKey extends Component { return (
    - { `${t('balance')}:` } + {`${t('balance')}:`}
    - { `${balanceInEther} ETH` } + {`${balanceInEther} ETH`}
    ) @@ -135,10 +139,7 @@ export default class ConfirmEncryptionPublicKey extends Component { return (
    - +
    ) } @@ -146,9 +147,9 @@ export default class ConfirmEncryptionPublicKey extends Component { renderAccountInfo = () => { return (
    - { this.renderAccount() } - { this.renderRequestIcon() } - { this.renderBalance() } + {this.renderAccount()} + {this.renderRequestIcon()} + {this.renderBalance()}
    ) } @@ -162,10 +163,8 @@ export default class ConfirmEncryptionPublicKey extends Component { return (
    - { this.renderAccountInfo() } -
    + {this.renderAccountInfo()} +
    {origin.icon ? ( )} -
    - { notice } +
    + {notice}
    @@ -219,7 +216,7 @@ export default class ConfirmEncryptionPublicKey extends Component { history.push(mostRecentOverviewPage) }} > - { this.context.t('cancel') } + {this.context.t('cancel')}
    ) @@ -248,9 +245,9 @@ export default class ConfirmEncryptionPublicKey extends Component { render = () => { return (
    - { this.renderHeader() } - { this.renderBody() } - { this.renderFooter() } + {this.renderHeader()} + {this.renderBody()} + {this.renderFooter()}
    ) } diff --git a/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.container.js b/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.container.js index 7a0b2c37f..ea4c156e7 100644 --- a/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.container.js +++ b/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.container.js @@ -17,15 +17,13 @@ import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm import { getMostRecentOverviewPage } from '../../ducks/history/history' import ConfirmEncryptionPublicKey from './confirm-encryption-public-key.component' -function mapStateToProps (state) { +function mapStateToProps(state) { const { confirmTransaction, metamask: { domainMetadata = {} }, } = state - const { - txData = {}, - } = confirmTransaction + const { txData = {} } = confirmTransaction const { msgParams: from } = txData @@ -42,7 +40,7 @@ function mapStateToProps (state) { } } -function mapDispatchToProps (dispatch) { +function mapDispatchToProps(dispatch) { return { goHome: () => dispatch(goHome()), clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), diff --git a/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.scss b/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.scss index 8860ca5d8..25c336936 100644 --- a/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.scss +++ b/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.scss @@ -61,9 +61,9 @@ } &__header__text { + @include H3; + color: #5b5d67; - font-size: 22px; - line-height: 29px; z-index: 3; text-align: center; } @@ -97,14 +97,14 @@ } &__account-text { - font-size: 14px; + @include H6; } &__account-item { + @include H7; + height: 22px; background-color: $white; - line-height: 16px; - font-size: 12px; width: 124px; .account-list-item { @@ -130,8 +130,9 @@ } &__balance-text { + @include H6; + text-align: right; - font-size: 14px; } &__balance-value { @@ -153,8 +154,8 @@ } &__notice { - font-size: 14px; - line-height: 19px; + @include H6; + text-align: center; margin-top: 41px; margin-bottom: 11px; @@ -165,11 +166,12 @@ } &__footer { + @include H3; + width: 100%; display: flex; align-items: center; justify-content: center; - font-size: 22px; position: relative; flex: 0 0 auto; border-top: 1px solid $geyser; diff --git a/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js b/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js index 2fd23f45f..6cff21550 100644 --- a/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js +++ b/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js @@ -14,25 +14,27 @@ export default class ConfirmSendEther extends Component { txParams: PropTypes.object, } - handleEdit ({ txData }) { + handleEdit({ txData }) { const { editTransaction, history } = this.props editTransaction(txData) history.push(SEND_ROUTE) } - shouldHideData () { + shouldHideData() { const { txParams = {} } = this.props return !txParams.data } - render () { + render() { const hideData = this.shouldHideData() return ( this.handleEdit(confirmTransactionData)} + onEdit={(confirmTransactionData) => + this.handleEdit(confirmTransactionData) + } /> ) } diff --git a/ui/app/pages/confirm-send-ether/confirm-send-ether.container.js b/ui/app/pages/confirm-send-ether/confirm-send-ether.container.js index 03268571e..8eca1803f 100644 --- a/ui/app/pages/confirm-send-ether/confirm-send-ether.container.js +++ b/ui/app/pages/confirm-send-ether/confirm-send-ether.container.js @@ -6,7 +6,9 @@ import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm import ConfirmSendEther from './confirm-send-ether.component' const mapStateToProps = (state) => { - const { confirmTransaction: { txData: { txParams } = {} } } = state + const { + confirmTransaction: { txData: { txParams } = {} }, + } = state return { txParams, @@ -17,24 +19,20 @@ const mapDispatchToProps = (dispatch) => { return { editTransaction: (txData) => { const { id, txParams } = txData - const { - from, - gas: gasLimit, - gasPrice, - to, - value: amount, - } = txParams + const { from, gas: gasLimit, gasPrice, to, value: amount } = txParams - dispatch(updateSend({ - from, - gasLimit, - gasPrice, - gasTotal: null, - to, - amount, - errors: { to: null, amount: null }, - editingTransactionId: id && id.toString(), - })) + dispatch( + updateSend({ + from, + gasLimit, + gasPrice, + gasTotal: null, + to, + amount, + errors: { to: null, amount: null }, + editingTransactionId: id?.toString(), + }), + ) dispatch(clearConfirmTransaction()) }, diff --git a/ui/app/pages/confirm-send-token/confirm-send-token.component.js b/ui/app/pages/confirm-send-token/confirm-send-token.component.js index ae87808dc..3c68e564b 100644 --- a/ui/app/pages/confirm-send-token/confirm-send-token.component.js +++ b/ui/app/pages/confirm-send-token/confirm-send-token.component.js @@ -10,18 +10,20 @@ export default class ConfirmSendToken extends Component { tokenAmount: PropTypes.string, } - handleEdit (confirmTransactionData) { + handleEdit(confirmTransactionData) { const { editTransaction, history } = this.props editTransaction(confirmTransactionData) history.push(SEND_ROUTE) } - render () { + render() { const { tokenAmount } = this.props return ( this.handleEdit(confirmTransactionData)} + onEdit={(confirmTransactionData) => + this.handleEdit(confirmTransactionData) + } tokenAmount={tokenAmount} /> ) diff --git a/ui/app/pages/confirm-send-token/confirm-send-token.container.js b/ui/app/pages/confirm-send-token/confirm-send-token.container.js index 266bb03c9..04bb1f7b2 100644 --- a/ui/app/pages/confirm-send-token/confirm-send-token.container.js +++ b/ui/app/pages/confirm-send-token/confirm-send-token.container.js @@ -4,7 +4,10 @@ import { withRouter } from 'react-router-dom' import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck' import { updateSend, showSendTokenPage } from '../../store/actions' import { conversionUtil } from '../../helpers/utils/conversion-util' -import { getTokenValueParam, getTokenAddressParam } from '../../helpers/utils/token-util' +import { + getTokenValueParam, + getTokenAddressParam, +} from '../../helpers/utils/token-util' import { sendTokenTokenAmountAndToAddressSelector } from '../../selectors' import ConfirmSendToken from './confirm-send-token.component' @@ -19,15 +22,9 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return { editTransaction: ({ txData, tokenData, tokenProps }) => { - const { id, - txParams: { - from, - to: tokenAddress, - gas: gasLimit, - gasPrice, - } = {}, + txParams: { from, to: tokenAddress, gas: gasLimit, gasPrice } = {}, } = txData const to = getTokenValueParam(tokenData) @@ -38,20 +35,22 @@ const mapDispatchToProps = (dispatch) => { toNumericBase: 'hex', }) - dispatch(updateSend({ - from, - gasLimit, - gasPrice, - gasTotal: null, - to, - amount: tokenAmountInHex, - errors: { to: null, amount: null }, - editingTransactionId: id && id.toString(), - token: { - ...tokenProps, - address: tokenAddress, - }, - })) + dispatch( + updateSend({ + from, + gasLimit, + gasPrice, + gasTotal: null, + to, + amount: tokenAmountInHex, + errors: { to: null, amount: null }, + editingTransactionId: id?.toString(), + token: { + ...tokenProps, + address: tokenAddress, + }, + }), + ) dispatch(clearConfirmTransaction()) dispatch(showSendTokenPage()) }, diff --git a/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js b/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js index 1bbdde3b1..e60ec0ec2 100644 --- a/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js +++ b/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js @@ -13,7 +13,7 @@ import { import { getWeiHexFromDecimalValue } from '../../helpers/utils/conversions.util' import { ETH, PRIMARY } from '../../helpers/constants/common' -export default function ConfirmTokenTransactionBase ({ +export default function ConfirmTokenTransactionBase({ toAddress, tokenAddress, tokenAmount = '0', @@ -31,9 +31,9 @@ export default function ConfirmTokenTransactionBase ({ return '0' } - const decimalEthValue = ( - (new BigNumber(tokenAmount)).times(new BigNumber(contractExchangeRate)) - ).toFixed() + const decimalEthValue = new BigNumber(tokenAmount) + .times(new BigNumber(contractExchangeRate)) + .toFixed() return getWeiHexFromDecimalValue({ value: decimalEthValue, @@ -56,8 +56,7 @@ export default function ConfirmTokenTransactionBase ({ const fiatTotal = addFiat(fiatTransactionAmount, fiatTransactionTotal) const roundedFiatTotal = roundExponential(fiatTotal) return formatCurrency(roundedFiatTotal, currentCurrency) - }, - [ + }, [ currentCurrency, conversionRate, contractExchangeRate, @@ -73,30 +72,24 @@ export default function ConfirmTokenTransactionBase ({ identiconAddress={tokenAddress} title={tokensText} subtitleComponent={ - contractExchangeRate === undefined - ? ( - - { t('noConversionRateAvailable') } - - ) : ( - - ) - } - primaryTotalTextOverride={( -
    - { `${tokensText} + ` } - {t('noConversionRateAvailable')} + ) : ( + - { ethTransactionTotal } + ) + } + primaryTotalTextOverride={ +
    + {`${tokensText} + `} + + {ethTransactionTotal}
    - )} + } secondaryTotalTextOverride={secondaryTotalTextOverride} /> ) diff --git a/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js b/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js index d835a884e..5c01aa9bd 100644 --- a/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js +++ b/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js @@ -6,9 +6,7 @@ import { transactionFeeSelector, } from '../../selectors' import { getTokens } from '../../ducks/metamask/metamask' -import { - getTokenData, -} from '../../helpers/utils/transactions.util' +import { getTokenData } from '../../helpers/utils/transactions.util' import { calcTokenAmount, getTokenAddressParam, @@ -17,7 +15,9 @@ import { import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component' const mapStateToProps = (state, ownProps) => { - const { match: { params = {} } } = ownProps + const { + match: { params = {} }, + } = ownProps const { id: paramsTransactionId } = params const { confirmTransaction, @@ -25,26 +25,30 @@ const mapStateToProps = (state, ownProps) => { } = state const { - txData: { id: transactionId, txParams: { to: tokenAddress, data } = {} } = {}, + txData: { + id: transactionId, + txParams: { to: tokenAddress, data } = {}, + } = {}, } = confirmTransaction - const transaction = ( - currentNetworkTxList.find(({ id }) => id === (Number(paramsTransactionId) || - transactionId)) || {} - ) + const transaction = + currentNetworkTxList.find( + ({ id }) => id === (Number(paramsTransactionId) || transactionId), + ) || {} - const { - ethTransactionTotal, - fiatTransactionTotal, - } = transactionFeeSelector(state, transaction) + const { ethTransactionTotal, fiatTransactionTotal } = transactionFeeSelector( + state, + transaction, + ) const tokens = getTokens(state) - const currentToken = tokens && tokens.find(({ address }) => tokenAddress === address) + const currentToken = tokens?.find(({ address }) => tokenAddress === address) const { decimals, symbol: tokenSymbol } = currentToken || {} const tokenData = getTokenData(data) const tokenValue = getTokenValueParam(tokenData) const toAddress = getTokenAddressParam(tokenData) - const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toFixed() + const tokenAmount = + tokenData && calcTokenAmount(tokenValue, decimals).toFixed() const contractExchangeRate = contractExchangeRateSelector(state) return { diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js index 4c9ab94e1..f25380795 100644 --- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -3,7 +3,9 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums' import { getEnvironmentType } from '../../../../app/scripts/lib/util' -import ConfirmPageContainer, { ConfirmDetailRow } from '../../components/app/confirm-page-container' +import ConfirmPageContainer, { + ConfirmDetailRow, +} from '../../components/app/confirm-page-container' import { isBalanceSufficient } from '../send/send.utils' import { CONFIRM_TRANSACTION_ROUTE } from '../../helpers/constants/routes' import { @@ -11,12 +13,15 @@ import { TRANSACTION_ERROR_KEY, GAS_LIMIT_TOO_LOW_ERROR_KEY, } from '../../helpers/constants/error-keys' -import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../helpers/constants/transactions' import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display' import { PRIMARY, SECONDARY } from '../../helpers/constants/common' import { hexToDecimal } from '../../helpers/utils/conversions.util' import AdvancedGasInputs from '../../components/app/gas-customization/advanced-gas-inputs' import TextField from '../../components/ui/text-field' +import { + TRANSACTION_CATEGORIES, + TRANSACTION_STATUSES, +} from '../../../../shared/constants/transaction' export default class ConfirmTransactionBase extends Component { static contextTypes = { @@ -68,7 +73,10 @@ export default class ConfirmTransactionBase extends Component { detailsComponent: PropTypes.node, errorKey: PropTypes.string, errorMessage: PropTypes.string, - primaryTotalTextOverride: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + primaryTotalTextOverride: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.node, + ]), secondaryTotalTextOverride: PropTypes.string, hideData: PropTypes.bool, hideDetails: PropTypes.bool, @@ -106,7 +114,7 @@ export default class ConfirmTransactionBase extends Component { submitWarning: '', } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { const { transactionStatus, showTransactionConfirmedModal, @@ -125,11 +133,18 @@ export default class ConfirmTransactionBase extends Component { transactionStatus: prevTxStatus, } = prevProps const statusUpdated = transactionStatus !== prevTxStatus - const txDroppedOrConfirmed = transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS + const txDroppedOrConfirmed = + transactionStatus === TRANSACTION_STATUSES.DROPPED || + transactionStatus === TRANSACTION_STATUSES.CONFIRMED - if (nextNonce !== prevNextNonce || customNonceValue !== prevCustomNonceValue) { + if ( + nextNonce !== prevNextNonce || + customNonceValue !== prevCustomNonceValue + ) { if (customNonceValue > nextNonce) { - this.setState({ submitWarning: this.context.t('nextNonceWarning', [nextNonce]) }) + this.setState({ + submitWarning: this.context.t('nextNonceWarning', [nextNonce]), + }) } else { this.setState({ submitWarning: '' }) } @@ -149,26 +164,23 @@ export default class ConfirmTransactionBase extends Component { } } - getErrorKey () { + getErrorKey() { const { balance, conversionRate, hexTransactionFee, - txData: { - simulationFails, - txParams: { - value: amount, - } = {}, - } = {}, + txData: { simulationFails, txParams: { value: amount } = {} } = {}, customGas, } = this.props - const insufficientBalance = balance && !isBalanceSufficient({ - amount, - gasTotal: hexTransactionFee || '0x0', - balance, - conversionRate, - }) + const insufficientBalance = + balance && + !isBalanceSufficient({ + amount, + gasTotal: hexTransactionFee || '0x0', + balance, + conversionRate, + }) if (insufficientBalance) { return { @@ -187,7 +199,9 @@ export default class ConfirmTransactionBase extends Component { if (simulationFails) { return { valid: true, - errorKey: simulationFails.errorKey ? simulationFails.errorKey : TRANSACTION_ERROR_KEY, + errorKey: simulationFails.errorKey + ? simulationFails.errorKey + : TRANSACTION_ERROR_KEY, } } @@ -196,8 +210,14 @@ export default class ConfirmTransactionBase extends Component { } } - handleEditGas () { - const { onEditGas, showCustomizeGasModal, actionKey, txData: { origin }, methodData = {} } = this.props + handleEditGas() { + const { + onEditGas, + showCustomizeGasModal, + actionKey, + txData: { origin }, + methodData = {}, + } = this.props this.context.metricsEvent({ eventOpts: { @@ -207,7 +227,10 @@ export default class ConfirmTransactionBase extends Component { }, customVariables: { recipientKnown: null, - functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction', + functionType: + actionKey || + getMethodName(methodData.name) || + TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, origin, }, }) @@ -219,7 +242,7 @@ export default class ConfirmTransactionBase extends Component { } } - renderDetails () { + renderDetails() { const { detailsComponent, primaryTotalTextOverride, @@ -254,31 +277,48 @@ export default class ConfirmTransactionBase extends Component { label="Gas Fee" value={hexTransactionFee} headerText={notMainnetOrTest ? '' : 'Edit'} - headerTextClassName={notMainnetOrTest ? '' : 'confirm-detail-row__header-text--edit'} - onHeaderClick={notMainnetOrTest ? null : () => this.handleEditGas()} - secondaryText={hideFiatConversion ? this.context.t('noConversionRateAvailable') : ''} + headerTextClassName={ + notMainnetOrTest ? '' : 'confirm-detail-row__header-text--edit' + } + onHeaderClick={ + notMainnetOrTest ? null : () => this.handleEditGas() + } + secondaryText={ + hideFiatConversion + ? this.context.t('noConversionRateAvailable') + : '' + } /> - {advancedInlineGasShown || notMainnetOrTest - ? ( - updateGasAndCalculate({ ...customGas, gasPrice: newGasPrice })} - updateCustomGasLimit={(newGasLimit) => updateGasAndCalculate({ ...customGas, gasLimit: newGasLimit })} - customGasPrice={customGas.gasPrice} - customGasLimit={customGas.gasLimit} - insufficientBalance={insufficientBalance} - customPriceIsSafe - isSpeedUp={false} - /> - ) - : null - } + {advancedInlineGasShown || notMainnetOrTest ? ( + + updateGasAndCalculate({ ...customGas, gasPrice: newGasPrice }) + } + updateCustomGasLimit={(newGasLimit) => + updateGasAndCalculate({ ...customGas, gasLimit: newGasLimit }) + } + customGasPrice={customGas.gasPrice} + customGasLimit={customGas.gasLimit} + insufficientBalance={insufficientBalance} + customPriceIsSafe + isSpeedUp={false} + /> + ) : null}
    -
    +
    - { this.context.t('nonceFieldHeading') } + {this.context.t('nonceFieldHeading')}
    { if (!value.length || Number(value) < 0) { updateCustomNonce('') @@ -305,7 +349,7 @@ export default class ConfirmTransactionBase extends Component { }} fullWidth margin="dense" - value={ customNonceValue || '' } + value={customNonceValue || ''} />
    @@ -316,17 +360,11 @@ export default class ConfirmTransactionBase extends Component { ) } - renderData (functionType) { + renderData(functionType) { const { t } = this.context const { - txData: { - txParams: { - data, - } = {}, - } = {}, - methodData: { - params, - } = {}, + txData: { txParams: { data } = {} } = {}, + methodData: { params } = {}, hideData, dataComponent, } = this.props @@ -335,38 +373,44 @@ export default class ConfirmTransactionBase extends Component { return null } - return dataComponent || ( -
    -
    - {`${t('functionType')}:`} - - { functionType } - -
    - { - params && ( + return ( + dataComponent || ( +
    +
    + {`${t('functionType')}:`} + + {functionType} + +
    + {params && (
    - { `${t('parameters')}:` } + {`${t('parameters')}:`}
    -
    { JSON.stringify(params, null, 2) }
    +
    {JSON.stringify(params, null, 2)}
    - ) - } -
    - {`${t('hexData')}: ${ethUtil.toBuffer(data).length} bytes`} + )} +
    + {`${t('hexData')}: ${ethUtil.toBuffer(data).length} bytes`} +
    +
    {data}
    -
    - { data } -
    -
    + ) ) } - handleEdit () { - const { txData, tokenData, tokenProps, onEdit, actionKey, txData: { origin }, methodData = {} } = this.props + handleEdit() { + const { + txData, + tokenData, + tokenProps, + onEdit, + actionKey, + txData: { origin }, + methodData = {}, + } = this.props this.context.metricsEvent({ eventOpts: { @@ -376,7 +420,10 @@ export default class ConfirmTransactionBase extends Component { }, customVariables: { recipientKnown: null, - functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction', + functionType: + actionKey || + getMethodName(methodData.name) || + TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, origin, }, }) @@ -384,7 +431,7 @@ export default class ConfirmTransactionBase extends Component { onEdit({ txData, tokenData, tokenProps }) } - handleCancelAll () { + handleCancelAll() { const { cancelAllTransactions, clearConfirmTransaction, @@ -405,7 +452,7 @@ export default class ConfirmTransactionBase extends Component { }) } - handleCancel () { + handleCancel() { const { metricsEvent } = this.context const { onCancel, @@ -429,7 +476,10 @@ export default class ConfirmTransactionBase extends Component { }, customVariables: { recipientKnown: null, - functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction', + functionType: + actionKey || + getMethodName(methodData.name) || + TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, origin, }, }) @@ -437,15 +487,14 @@ export default class ConfirmTransactionBase extends Component { if (onCancel) { onCancel(txData) } else { - cancelTransaction(txData) - .then(() => { - clearConfirmTransaction() - history.push(mostRecentOverviewPage) - }) + cancelTransaction(txData).then(() => { + clearConfirmTransaction() + history.push(mostRecentOverviewPage) + }) } } - handleSubmit () { + handleSubmit() { const { metricsEvent } = this.context const { txData: { origin }, @@ -467,44 +516,50 @@ export default class ConfirmTransactionBase extends Component { return } - this.setState({ - submitting: true, - submitError: null, - }, () => { - this._removeBeforeUnload() - metricsEvent({ - eventOpts: { - category: 'Transactions', - action: 'Confirm Screen', - name: 'Transaction Completed', - }, - customVariables: { - recipientKnown: null, - functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction', - origin, - }, - }) + this.setState( + { + submitting: true, + submitError: null, + }, + () => { + this._removeBeforeUnload() + metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Confirm Screen', + name: 'Transaction Completed', + }, + customVariables: { + recipientKnown: null, + functionType: + actionKey || + getMethodName(methodData.name) || + TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, + origin, + }, + }) - setMetaMetricsSendCount(metaMetricsSendCount + 1) - .then(() => { + setMetaMetricsSendCount(metaMetricsSendCount + 1).then(() => { if (onSubmit) { - Promise.resolve(onSubmit(txData)) - .then(() => { - this.setState({ - submitting: false, - }) - updateCustomNonce('') + Promise.resolve(onSubmit(txData)).then(() => { + this.setState({ + submitting: false, }) + updateCustomNonce('') + }) } else { sendTransaction(txData) .then(() => { clearConfirmTransaction() - this.setState({ - submitting: false, - }, () => { - history.push(mostRecentOverviewPage) - updateCustomNonce('') - }) + this.setState( + { + submitting: false, + }, + () => { + history.push(mostRecentOverviewPage) + updateCustomNonce('') + }, + ) }) .catch((error) => { this.setState({ @@ -515,10 +570,11 @@ export default class ConfirmTransactionBase extends Component { }) } }) - }) + }, + ) } - renderTitleComponent () { + renderTitleComponent() { const { title, titleComponent, hexTransactionAmount } = this.props // Title string passed in by props takes priority @@ -526,18 +582,20 @@ export default class ConfirmTransactionBase extends Component { return null } - return titleComponent || ( - + return ( + titleComponent || ( + + ) ) } - renderSubtitleComponent () { + renderSubtitleComponent() { const { subtitle, subtitleComponent, hexTransactionAmount } = this.props // Subtitle string passed in by props takes priority @@ -545,17 +603,19 @@ export default class ConfirmTransactionBase extends Component { return null } - return subtitleComponent || ( - + return ( + subtitleComponent || ( + + ) ) } - handleNextTx (txId) { + handleNextTx(txId) { const { history, clearConfirmTransaction } = this.props if (txId) { @@ -564,7 +624,7 @@ export default class ConfirmTransactionBase extends Component { } } - getNavigateTxData () { + getNavigateTxData() { const { currentNetworkUnapprovedTxs, txData: { id } = {} } = this.props const enumUnapprovedTxs = Object.keys(currentNetworkUnapprovedTxs) const currentPosition = enumUnapprovedTxs.indexOf(id ? id.toString() : '') @@ -604,8 +664,13 @@ export default class ConfirmTransactionBase extends Component { } } - componentDidMount () { - const { toAddress, txData: { origin } = {}, getNextNonce, tryReverseResolveAddress } = this.props + componentDidMount() { + const { + toAddress, + txData: { origin } = {}, + getNextNonce, + tryReverseResolveAddress, + } = this.props const { metricsEvent } = this.context metricsEvent({ eventOpts: { @@ -628,11 +693,11 @@ export default class ConfirmTransactionBase extends Component { } } - componentWillUnmount () { + componentWillUnmount() { this._removeBeforeUnload() } - render () { + render() { const { t } = this.context const { isTxReprice, @@ -666,7 +731,17 @@ export default class ConfirmTransactionBase extends Component { const { name } = methodData const { valid, errorKey } = this.getErrorKey() - const { totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = this.getNavigateTxData() + const { + totalTx, + positionOfCurrentTx, + nextTxId, + prevTxId, + showNavigation, + firstTx, + lastTx, + ofText, + requestsWaitingText, + } = this.getNavigateTxData() let functionType = getMethodName(name) if (!functionType) { @@ -725,13 +800,13 @@ export default class ConfirmTransactionBase extends Component { } } -export function getMethodName (camelCase) { +export function getMethodName(camelCase) { if (!camelCase || typeof camelCase !== 'string') { return '' } return camelCase - .replace(/([a-z])([A-Z])/ug, '$1 $2') - .replace(/([A-Z])([a-z])/ug, ' $1$2') - .replace(/ +/ug, ' ') + .replace(/([a-z])([A-Z])/gu, '$1 $2') + .replace(/([A-Z])([a-z])/gu, ' $1$2') + .replace(/ +/gu, ' ') } diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js index 08b0f4581..245df89ec 100644 --- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js @@ -2,9 +2,7 @@ import { connect } from 'react-redux' import { compose } from 'redux' import { withRouter } from 'react-router-dom' import contractMap from 'eth-contract-metadata' -import { - clearConfirmTransaction, -} from '../../ducks/confirm-transaction/confirm-transaction.duck' +import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck' import { updateCustomNonce, @@ -25,7 +23,11 @@ import { getHexGasTotal } from '../../helpers/utils/confirm-tx.util' import { isBalanceSufficient, calcGasTotal } from '../send/send.utils' import { conversionGreaterThan } from '../../helpers/utils/conversion-util' import { MIN_GAS_LIMIT_DEC } from '../send/send.constants' -import { checksumAddress, shortenAddress, valuesFor } from '../../helpers/utils/util' +import { + checksumAddress, + shortenAddress, + valuesFor, +} from '../../helpers/utils/util' import { getAdvancedInlineGasShown, getCustomNonceValue, @@ -47,13 +49,20 @@ const casedContractMap = Object.keys(contractMap).reduce((acc, base) => { }, {}) let customNonceValue = '' -const customNonceMerge = (txData) => (customNonceValue ? ({ - ...txData, - customNonceValue, -}) : txData) +const customNonceMerge = (txData) => + customNonceValue + ? { + ...txData, + customNonceValue, + } + : txData const mapStateToProps = (state, ownProps) => { - const { toAddress: propsToAddress, customTxParamsData, match: { params = {} } } = ownProps + const { + toAddress: propsToAddress, + customTxParamsData, + match: { params = {} }, + } = ownProps const { id: paramsTransactionId } = params const { showFiatInTestnets } = getPreferences(state) const isMainnet = getIsMainnet(state) @@ -69,16 +78,17 @@ const mapStateToProps = (state, ownProps) => { metaMetricsSendCount, nextNonce, } = metamask + const { tokenData, txData, tokenProps, nonce } = confirmTransaction const { - tokenData, - txData, - tokenProps, - nonce, - } = confirmTransaction - const { txParams = {}, lastGasPrice, id: transactionId, transactionCategory } = txData - const transaction = Object.values(unapprovedTxs).find( - ({ id }) => id === (transactionId || Number(paramsTransactionId)), - ) || {} + txParams = {}, + lastGasPrice, + id: transactionId, + transactionCategory, + } = txData + const transaction = + Object.values(unapprovedTxs).find( + ({ id }) => id === (transactionId || Number(paramsTransactionId)), + ) || {} const { from: fromAddress, to: txParamsToAddress, @@ -94,7 +104,8 @@ const mapStateToProps = (state, ownProps) => { const { name: fromName } = identities[fromAddress] const toAddress = propsToAddress || txParamsToAddress - const toName = identities[toAddress]?.name || + const toName = + identities[toAddress]?.name || casedContractMap[toAddress]?.name || shortenAddress(checksumAddress(toAddress)) @@ -171,8 +182,8 @@ const mapStateToProps = (state, ownProps) => { useNonceField: getUseNonceField(state), customNonceValue: getCustomNonceValue(state), insufficientBalance, - hideSubtitle: (!isMainnet && !showFiatInTestnets), - hideFiatConversion: (!isMainnet && !showFiatInTestnets), + hideSubtitle: !isMainnet && !showFiatInTestnets, + hideFiatConversion: !isMainnet && !showFiatInTestnets, metaMetricsSendCount, transactionCategory, nextNonce, @@ -195,17 +206,25 @@ export const mapDispatchToProps = (dispatch) => { return dispatch(showModal({ name: 'TRANSACTION_CONFIRMED', onSubmit })) }, showCustomizeGasModal: ({ txData, onSubmit, validate }) => { - return dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData, onSubmit, validate })) + return dispatch( + showModal({ name: 'CUSTOMIZE_GAS', txData, onSubmit, validate }), + ) }, updateGasAndCalculate: (updatedTx) => { return dispatch(updateTransaction(updatedTx)) }, - showRejectTransactionsConfirmationModal: ({ onSubmit, unapprovedTxCount }) => { - return dispatch(showModal({ name: 'REJECT_TRANSACTIONS', onSubmit, unapprovedTxCount })) + showRejectTransactionsConfirmationModal: ({ + onSubmit, + unapprovedTxCount, + }) => { + return dispatch( + showModal({ name: 'REJECT_TRANSACTIONS', onSubmit, unapprovedTxCount }), + ) }, cancelTransaction: ({ id }) => dispatch(cancelTx({ id })), cancelAllTransactions: (txList) => dispatch(cancelTxs(txList)), - sendTransaction: (txData) => dispatch(updateAndApproveTx(customNonceMerge(txData))), + sendTransaction: (txData) => + dispatch(updateAndApproveTx(customNonceMerge(txData))), setMetaMetricsSendCount: (val) => dispatch(setMetaMetricsSendCount(val)), getNextNonce: () => dispatch(getNextNonce()), } @@ -230,17 +249,19 @@ const getValidateEditGas = ({ balance, conversionRate, txData }) => { } } - const gasLimitTooLow = gasLimit && conversionGreaterThan( - { - value: MIN_GAS_LIMIT_DEC, - fromNumericBase: 'dec', - conversionRate, - }, - { - value: gasLimit, - fromNumericBase: 'hex', - }, - ) + const gasLimitTooLow = + gasLimit && + conversionGreaterThan( + { + value: MIN_GAS_LIMIT_DEC, + fromNumericBase: 'dec', + conversionRate, + }, + { + value: gasLimit, + fromNumericBase: 'hex', + }, + ) if (gasLimitTooLow) { return { @@ -264,18 +285,24 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { ...otherDispatchProps } = dispatchProps - const validateEditGas = getValidateEditGas({ balance, conversionRate, txData }) + const validateEditGas = getValidateEditGas({ + balance, + conversionRate, + txData, + }) return { ...stateProps, ...otherDispatchProps, ...ownProps, - showCustomizeGasModal: () => dispatchShowCustomizeGasModal({ - txData, - onSubmit: (customGas) => dispatchUpdateGasAndCalculate(customGas), - validate: validateEditGas, - }), - cancelAllTransactions: () => dispatchCancelAllTransactions(valuesFor(unapprovedTxs)), + showCustomizeGasModal: () => + dispatchShowCustomizeGasModal({ + txData, + onSubmit: (customGas) => dispatchUpdateGasAndCalculate(customGas), + validate: validateEditGas, + }), + cancelAllTransactions: () => + dispatchCancelAllTransactions(valuesFor(unapprovedTxs)), updateGasAndCalculate: ({ gasLimit, gasPrice }) => { const updatedTx = { ...txData, diff --git a/ui/app/pages/confirm-transaction-base/tests/confirm-transaction-base.component.test.js b/ui/app/pages/confirm-transaction-base/tests/confirm-transaction-base.component.test.js index 92ac0c66b..3add429b6 100644 --- a/ui/app/pages/confirm-transaction-base/tests/confirm-transaction-base.component.test.js +++ b/ui/app/pages/confirm-transaction-base/tests/confirm-transaction-base.component.test.js @@ -8,7 +8,10 @@ describe('ConfirmTransactionBase Component', function () { assert.equal(getMethodName({}), '') assert.equal(getMethodName('confirm'), 'confirm') assert.equal(getMethodName('balanceOf'), 'balance Of') - assert.equal(getMethodName('ethToTokenSwapInput'), 'eth To Token Swap Input') + assert.equal( + getMethodName('ethToTokenSwapInput'), + 'eth To Token Swap Input', + ) }) }) }) diff --git a/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.component.js index 02014ee37..88dd10084 100644 --- a/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.component.js +++ b/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.component.js @@ -14,47 +14,39 @@ import { DECRYPT_MESSAGE_REQUEST_PATH, ENCRYPTION_PUBLIC_KEY_REQUEST_PATH, } from '../../helpers/constants/routes' -import { - TOKEN_METHOD_TRANSFER, - TOKEN_METHOD_APPROVE, - TOKEN_METHOD_TRANSFER_FROM, - DEPLOY_CONTRACT_ACTION_KEY, - SEND_ETHER_ACTION_KEY, -} from '../../helpers/constants/transactions' import { MESSAGE_TYPE } from '../../../../app/scripts/lib/enums' +import { TRANSACTION_CATEGORIES } from '../../../../shared/constants/transaction' export default class ConfirmTransactionSwitch extends Component { static propTypes = { txData: PropTypes.object, } - redirectToTransaction () { - const { - txData, - } = this.props + redirectToTransaction() { + const { txData } = this.props const { id, txParams: { data } = {}, transactionCategory } = txData - if (transactionCategory === DEPLOY_CONTRACT_ACTION_KEY) { + if (transactionCategory === TRANSACTION_CATEGORIES.DEPLOY_CONTRACT) { const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}` return } - if (transactionCategory === SEND_ETHER_ACTION_KEY) { + if (transactionCategory === TRANSACTION_CATEGORIES.SENT_ETHER) { const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}` return } if (data) { switch (transactionCategory) { - case TOKEN_METHOD_TRANSFER: { + case TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER: { const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_TOKEN_PATH}` return } - case TOKEN_METHOD_APPROVE: { + case TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE: { const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_APPROVE_PATH}` return } - case TOKEN_METHOD_TRANSFER_FROM: { + case TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM: { const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TRANSFER_FROM_PATH}` return } @@ -69,7 +61,7 @@ export default class ConfirmTransactionSwitch extends Component { return } - render () { + render() { const { txData } = this.props if (txData.txParams) { return this.redirectToTransaction() diff --git a/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js b/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js index dd6970d87..59d3f9555 100644 --- a/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js +++ b/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js @@ -3,9 +3,13 @@ import { unconfirmedTransactionsListSelector } from '../../selectors' import ConfirmTransactionSwitch from './confirm-transaction-switch.component' const mapStateToProps = (state, ownProps) => { - const { metamask: { unapprovedTxs } } = state - const { match: { params = {}, url } } = ownProps - const urlId = url && url.match(/\d+/u) && url.match(/\d+/u)[0] + const { + metamask: { unapprovedTxs }, + } = state + const { + match: { params = {}, url }, + } = ownProps + const urlId = url?.match(/\d+/u) && url?.match(/\d+/u)[0] const { id: paramsId } = params const transactionId = paramsId || urlId diff --git a/ui/app/pages/confirm-transaction/conf-tx.js b/ui/app/pages/confirm-transaction/conf-tx.js index 3e21a8eb0..cbf700548 100644 --- a/ui/app/pages/confirm-transaction/conf-tx.js +++ b/ui/app/pages/confirm-transaction/conf-tx.js @@ -11,17 +11,16 @@ import SignatureRequestOriginal from '../../components/app/signature-request-ori import Loading from '../../components/ui/loading-screen' import { getMostRecentOverviewPage } from '../../ducks/history/history' import { MESSAGE_TYPE } from '../../../../app/scripts/lib/enums' +import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction' -function mapStateToProps (state) { +function mapStateToProps(state) { const { metamask, appState } = state const { unapprovedMsgCount, unapprovedPersonalMsgCount, unapprovedTypedMessagesCount, } = metamask - const { - txId, - } = appState + const { txId } = appState return { identities: state.metamask.identities, @@ -72,17 +71,21 @@ class ConfirmTxScreen extends Component { }).isRequired, } - getUnapprovedMessagesTotal () { + getUnapprovedMessagesTotal() { const { unapprovedMsgCount = 0, unapprovedPersonalMsgCount = 0, unapprovedTypedMessagesCount = 0, } = this.props - return unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount + return ( + unapprovedTypedMessagesCount + + unapprovedMsgCount + + unapprovedPersonalMsgCount + ) } - getTxData () { + getTxData() { const { network, index, @@ -108,16 +111,19 @@ class ConfirmTxScreen extends Component { : unconfTxList[index] } - signatureSelect (type, version) { + signatureSelect(type, version) { // Temporarily direct only v3 and v4 requests to new code. - if (type === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA && (version === 'V3' || version === 'V4')) { + if ( + type === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA && + (version === 'V3' || version === 'V4') + ) { return SignatureRequest } return SignatureRequestOriginal } - signMessage (msgData, event) { + signMessage(msgData, event) { log.info('conf-tx.js: signing message') const params = msgData.msgParams params.metamaskId = msgData.id @@ -125,13 +131,13 @@ class ConfirmTxScreen extends Component { return this.props.dispatch(actions.signMsg(params)) } - stopPropagation (event) { + stopPropagation(event) { if (event.stopPropagation) { event.stopPropagation() } } - signPersonalMessage (msgData, event) { + signPersonalMessage(msgData, event) { log.info('conf-tx.js: signing personal message') const params = msgData.msgParams params.metamaskId = msgData.id @@ -139,7 +145,7 @@ class ConfirmTxScreen extends Component { return this.props.dispatch(actions.signPersonalMsg(params)) } - signTypedMessage (msgData, event) { + signTypedMessage(msgData, event) { log.info('conf-tx.js: signing typed message') const params = msgData.msgParams params.metamaskId = msgData.id @@ -147,25 +153,25 @@ class ConfirmTxScreen extends Component { return this.props.dispatch(actions.signTypedMsg(params)) } - cancelMessage (msgData, event) { + cancelMessage(msgData, event) { log.info('canceling message') this.stopPropagation(event) return this.props.dispatch(actions.cancelMsg(msgData)) } - cancelPersonalMessage (msgData, event) { + cancelPersonalMessage(msgData, event) { log.info('canceling personal message') this.stopPropagation(event) return this.props.dispatch(actions.cancelPersonalMsg(msgData)) } - cancelTypedMessage (msgData, event) { + cancelTypedMessage(msgData, event) { log.info('canceling typed message') this.stopPropagation(event) return this.props.dispatch(actions.cancelTypedMsg(msgData)) } - componentDidMount () { + componentDidMount() { const { unapprovedTxs = {}, history, @@ -175,12 +181,16 @@ class ConfirmTxScreen extends Component { } = this.props const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) - if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) { + if ( + unconfTxList.length === 0 && + !send.to && + this.getUnapprovedMessagesTotal() === 0 + ) { history.push(mostRecentOverviewPage) } } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { const { unapprovedTxs = {}, network, @@ -204,34 +214,39 @@ class ConfirmTxScreen extends Component { const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) - if (prevTx && prevTx.status === 'dropped') { - this.props.dispatch(actions.showModal({ - name: 'TRANSACTION_CONFIRMED', - onSubmit: () => history.push(mostRecentOverviewPage), - })) + if (prevTx && prevTx.status === TRANSACTION_STATUSES.DROPPED) { + this.props.dispatch( + actions.showModal({ + name: 'TRANSACTION_CONFIRMED', + onSubmit: () => history.push(mostRecentOverviewPage), + }), + ) return } - if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) { + if ( + unconfTxList.length === 0 && + !send.to && + this.getUnapprovedMessagesTotal() === 0 + ) { this.props.history.push(mostRecentOverviewPage) } } - render () { - const { - currentCurrency, - blockGasLimit, - } = this.props + render() { + const { currentCurrency, blockGasLimit } = this.props const txData = this.getTxData() || {} - const { msgParams, type, msgParams: { version } } = txData + const { + msgParams, + type, + msgParams: { version }, + } = txData log.debug('msgParams detected, rendering pending msg') if (!msgParams) { - return ( - - ) + return } const SigComponent = this.signatureSelect(type, version) @@ -253,7 +268,4 @@ class ConfirmTxScreen extends Component { } } -export default compose( - withRouter, - connect(mapStateToProps), -)(ConfirmTxScreen) +export default compose(withRouter, connect(mapStateToProps))(ConfirmTxScreen) diff --git a/ui/app/pages/confirm-transaction/confirm-transaction.component.js b/ui/app/pages/confirm-transaction/confirm-transaction.component.js index 087b8e8b4..92f073d9d 100644 --- a/ui/app/pages/confirm-transaction/confirm-transaction.component.js +++ b/ui/app/pages/confirm-transaction/confirm-transaction.component.js @@ -47,7 +47,7 @@ export default class ConfirmTransaction extends Component { isTokenMethodAction: PropTypes.bool, } - componentDidMount () { + componentDidMount() { const { totalUnapprovedCount = 0, send = {}, @@ -78,7 +78,7 @@ export default class ConfirmTransaction extends Component { } } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { const { setTransactionToConfirm, transaction: { txData: { txParams: { data } = {} } = {} }, @@ -91,73 +91,86 @@ export default class ConfirmTransaction extends Component { totalUnapprovedCount, } = this.props - if (paramsTransactionId && transactionId && prevProps.paramsTransactionId !== paramsTransactionId) { + if ( + paramsTransactionId && + transactionId && + prevProps.paramsTransactionId !== paramsTransactionId + ) { clearConfirmTransaction() getContractMethodData(data) setTransactionToConfirm(paramsTransactionId) - } else if (prevProps.transactionId && !transactionId && !totalUnapprovedCount) { + } else if ( + prevProps.transactionId && + !transactionId && + !totalUnapprovedCount + ) { history.replace(mostRecentOverviewPage) - } else if (prevProps.transactionId && transactionId && prevProps.transactionId !== transactionId) { + } else if ( + prevProps.transactionId && + transactionId && + prevProps.transactionId !== transactionId + ) { history.replace(mostRecentOverviewPage) } } - render () { + render() { const { transactionId, paramsTransactionId } = this.props // Show routes when state.confirmTransaction has been set and when either the ID in the params // isn't specified or is specified and matches the ID in state.confirmTransaction in order to // support URLs of /confirm-transaction or /confirm-transaction/ - return transactionId && (!paramsTransactionId || paramsTransactionId === transactionId) - ? ( - - - - - - - - - - - - - ) - : + return transactionId && + (!paramsTransactionId || paramsTransactionId === transactionId) ? ( + + + + + + + + + + + + + ) : ( + + ) } } diff --git a/ui/app/pages/confirm-transaction/confirm-transaction.container.js b/ui/app/pages/confirm-transaction/confirm-transaction.container.js index 6ee2579ff..61c4bc445 100644 --- a/ui/app/pages/confirm-transaction/confirm-transaction.container.js +++ b/ui/app/pages/confirm-transaction/confirm-transaction.container.js @@ -5,29 +5,21 @@ import { setTransactionToConfirm, clearConfirmTransaction, } from '../../ducks/confirm-transaction/confirm-transaction.duck' -import { - isTokenMethodAction, -} from '../../helpers/utils/transactions.util' -import { - fetchBasicGasAndTimeEstimates, -} from '../../ducks/gas/gas.duck' +import { isTokenMethodAction } from '../../helpers/utils/transactions.util' +import { fetchBasicGasAndTimeEstimates } from '../../ducks/gas/gas.duck' -import { - getContractMethodData, - getTokenParams, -} from '../../store/actions' +import { getContractMethodData, getTokenParams } from '../../store/actions' import { unconfirmedTransactionsListSelector } from '../../selectors' import { getMostRecentOverviewPage } from '../../ducks/history/history' import ConfirmTransaction from './confirm-transaction.component' const mapStateToProps = (state, ownProps) => { const { - metamask: { - send, - unapprovedTxs, - }, + metamask: { send, unapprovedTxs }, } = state - const { match: { params = {} } } = ownProps + const { + match: { params = {} }, + } = ownProps const { id } = params const unconfirmedTransactions = unconfirmedTransactionsListSelector(state) @@ -56,7 +48,8 @@ const mapDispatchToProps = (dispatch) => { dispatch(setTransactionToConfirm(transactionId)) }, clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), - fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()), + fetchBasicGasAndTimeEstimates: () => + dispatch(fetchBasicGasAndTimeEstimates()), getContractMethodData: (data) => dispatch(getContractMethodData(data)), getTokenParams: (tokenAddress) => dispatch(getTokenParams(tokenAddress)), } diff --git a/ui/app/pages/connected-accounts/connected-accounts.component.js b/ui/app/pages/connected-accounts/connected-accounts.component.js index 3fea60ed3..5a0a30109 100644 --- a/ui/app/pages/connected-accounts/connected-accounts.component.js +++ b/ui/app/pages/connected-accounts/connected-accounts.component.js @@ -28,7 +28,7 @@ export default class ConnectedAccounts extends PureComponent { history: PropTypes.object.isRequired, } - render () { + render() { const { accountToConnect, activeTabOrigin, @@ -44,14 +44,23 @@ export default class ConnectedAccounts extends PureComponent { } = this.props const { t } = this.context - const connectedAccountsDescription = connectedAccounts.length > 1 - ? t('connectedAccountsDescriptionPlural', [connectedAccounts.length]) - : t('connectedAccountsDescriptionSingular') + const connectedAccountsDescription = + connectedAccounts.length > 1 + ? t('connectedAccountsDescriptionPlural', [connectedAccounts.length]) + : t('connectedAccountsDescriptionSingular') return ( history.push(mostRecentOverviewPage)} footerClassName="connected-accounts__footer" footer={} diff --git a/ui/app/pages/connected-accounts/connected-accounts.container.js b/ui/app/pages/connected-accounts/connected-accounts.container.js index 0fe24583a..b1a1a2433 100644 --- a/ui/app/pages/connected-accounts/connected-accounts.container.js +++ b/ui/app/pages/connected-accounts/connected-accounts.container.js @@ -6,7 +6,11 @@ import { getSelectedAddress, } from '../../selectors' import { isExtensionUrl } from '../../helpers/utils/util' -import { addPermittedAccount, removePermittedAccount, setSelectedAddress } from '../../store/actions' +import { + addPermittedAccount, + removePermittedAccount, + setSelectedAddress, +} from '../../store/actions' import { getMostRecentOverviewPage } from '../../ducks/history/history' import ConnectedAccounts from './connected-accounts.component' @@ -31,8 +35,10 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return { - addPermittedAccount: (origin, address) => dispatch(addPermittedAccount(origin, address)), - removePermittedAccount: (origin, address) => dispatch(removePermittedAccount(origin, address)), + addPermittedAccount: (origin, address) => + dispatch(addPermittedAccount(origin, address)), + removePermittedAccount: (origin, address) => + dispatch(removePermittedAccount(origin, address)), setSelectedAddress: (address) => dispatch(setSelectedAddress(address)), } } @@ -44,9 +50,15 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { ...ownProps, ...stateProps, ...dispatchProps, - connectAccount: (address) => dispatchProps.addPermittedAccount(activeTabOrigin, address), - removePermittedAccount: (address) => dispatchProps.removePermittedAccount(activeTabOrigin, address), + connectAccount: (address) => + dispatchProps.addPermittedAccount(activeTabOrigin, address), + removePermittedAccount: (address) => + dispatchProps.removePermittedAccount(activeTabOrigin, address), } } -export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ConnectedAccounts) +export default connect( + mapStateToProps, + mapDispatchToProps, + mergeProps, +)(ConnectedAccounts) diff --git a/ui/app/pages/connected-accounts/index.scss b/ui/app/pages/connected-accounts/index.scss index a32c405f9..72117da3b 100644 --- a/ui/app/pages/connected-accounts/index.scss +++ b/ui/app/pages/connected-accounts/index.scss @@ -2,9 +2,10 @@ &__footer { a, a:hover { + @include H6; + color: $primary-blue; cursor: pointer; - font-size: 14px; } } } diff --git a/ui/app/pages/connected-sites/connected-sites.component.js b/ui/app/pages/connected-sites/connected-sites.component.js index 93a8d4e26..5c8f84e5f 100644 --- a/ui/app/pages/connected-sites/connected-sites.component.js +++ b/ui/app/pages/connected-sites/connected-sites.component.js @@ -32,7 +32,7 @@ export default class ConnectedSites extends Component { sitePendingDisconnect: null, } - componentDidMount () { + componentDidMount() { const { getOpenMetamaskTabsIds } = this.props getOpenMetamaskTabsIds() } @@ -67,7 +67,7 @@ export default class ConnectedSites extends Component { this.clearPendingDisconnect() } - renderConnectedSitesList () { + renderConnectedSitesList() { return ( - {t('connectManually')} - - ) - : null + tabToConnect ? ( + + {t('connectManually')} + + ) : null } footerClassName="connected-sites__add-site-manually" > @@ -115,11 +114,12 @@ export default class ConnectedSites extends Component { ) } - renderDisconnectPopover () { - + renderDisconnectPopover() { const { closePopover, permittedAccountsByOrigin } = this.props const { t } = this.context - const { sitePendingDisconnect: { domainKey } } = this.state + const { + sitePendingDisconnect: { domainKey }, + } = this.state const numPermittedAccounts = permittedAccountsByOrigin[domainKey].length @@ -129,46 +129,37 @@ export default class ConnectedSites extends Component { title={t('disconnectPrompt', [domainKey])} subtitle={t('disconnectAllAccountsConfirmationDescription')} onClose={closePopover} - footer={( + footer={ <>
    -
    - { - numPermittedAccounts > 1 - ? ( - - ) - : null - } + {numPermittedAccounts > 1 ? ( + + ) : null} - )} + } footerClassName="connected-sites__confirmation" /> ) } - render () { + render() { const { sitePendingDisconnect } = this.state - return ( - sitePendingDisconnect - ? this.renderDisconnectPopover() - : this.renderConnectedSitesPopover() - ) + return sitePendingDisconnect + ? this.renderDisconnectPopover() + : this.renderConnectedSitesPopover() } } diff --git a/ui/app/pages/connected-sites/connected-sites.container.js b/ui/app/pages/connected-sites/connected-sites.container.js index f34558741..ef5edd85b 100644 --- a/ui/app/pages/connected-sites/connected-sites.container.js +++ b/ui/app/pages/connected-sites/connected-sites.container.js @@ -26,9 +26,8 @@ const mapStateToProps = (state) => { const permittedAccountsByOrigin = getPermittedAccountsByOrigin(state) const selectedAddress = getSelectedAddress(state) - const currentTabHasNoAccounts = !permittedAccountsByOrigin[ - originOfCurrentTab - ]?.length + const currentTabHasNoAccounts = !permittedAccountsByOrigin[originOfCurrentTab] + ?.length let tabToConnect if (originOfCurrentTab && currentTabHasNoAccounts && !openMetaMaskTabs[id]) { @@ -56,12 +55,17 @@ const mapDispatchToProps = (dispatch) => { dispatch(removePermittedAccount(domainKey, address)) }, disconnectAllAccounts: (domainKey, domain) => { - const permissionMethodNames = domain.permissions.map(({ parentCapability }) => parentCapability) - dispatch(removePermissionsFor({ - [domainKey]: permissionMethodNames, - })) + const permissionMethodNames = domain.permissions.map( + ({ parentCapability }) => parentCapability, + ) + dispatch( + removePermissionsFor({ + [domainKey]: permissionMethodNames, + }), + ) }, - requestAccountsPermissionWithId: (origin) => dispatch(requestAccountsPermissionWithId(origin)), + requestAccountsPermissionWithId: (origin) => + dispatch(requestAccountsPermissionWithId(origin)), } } @@ -107,4 +111,8 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { } } -export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ConnectedSites) +export default connect( + mapStateToProps, + mapDispatchToProps, + mergeProps, +)(ConnectedSites) diff --git a/ui/app/pages/connected-sites/index.scss b/ui/app/pages/connected-sites/index.scss index 5b963bf42..9af0eaed5 100644 --- a/ui/app/pages/connected-sites/index.scss +++ b/ui/app/pages/connected-sites/index.scss @@ -26,8 +26,8 @@ a, a:hover { - font-size: 14px; - line-height: 20px; + @include H6; + color: $primary-blue; cursor: pointer; } diff --git a/ui/app/pages/create-account/connect-hardware/account-list.js b/ui/app/pages/create-account/connect-hardware/account-list.js index f1f54ddbc..b819c81be 100644 --- a/ui/app/pages/create-account/connect-hardware/account-list.js +++ b/ui/app/pages/create-account/connect-hardware/account-list.js @@ -5,7 +5,7 @@ import getAccountLink from '../../../../lib/account-link' import Button from '../../../components/ui/button' class AccountList extends Component { - getHdPaths () { + getHdPaths() { return [ { label: `Ledger Live`, @@ -18,167 +18,181 @@ class AccountList extends Component { ] } - goToNextPage = () => { - // If we have < 5 accounts, it's restricted by BIP-44 - if (this.props.accounts.length === 5) { - this.props.getPage(this.props.device, 1, this.props.selectedPath) - } else { - this.props.onAccountRestriction() - } + goToNextPage = () => { + // If we have < 5 accounts, it's restricted by BIP-44 + if (this.props.accounts.length === 5) { + this.props.getPage(this.props.device, 1, this.props.selectedPath) + } else { + this.props.onAccountRestriction() } + } - goToPreviousPage = () => { - this.props.getPage(this.props.device, -1, this.props.selectedPath) - } + goToPreviousPage = () => { + this.props.getPage(this.props.device, -1, this.props.selectedPath) + } - renderHdPathSelector () { - const { onPathChange, selectedPath } = this.props + renderHdPathSelector() { + const { onPathChange, selectedPath } = this.props - const options = this.getHdPaths() - return ( -
    -

    - {this.context.t('selectHdPath')} -

    -

    - {this.context.t('selectPathHelp')} -

    -
    - { + onPathChange(opt.value) + }} + />
    - ) - } +
    + ) + } - capitalizeDevice (device) { - return device.slice(0, 1).toUpperCase() + device.slice(1) - } + capitalizeDevice(device) { + return device.slice(0, 1).toUpperCase() + device.slice(1) + } - renderHeader () { - const { device } = this.props - return ( -
    -

    - {`${this.context.t('unlock')} ${this.capitalizeDevice(device)}`} -

    - {device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null} -

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

    -

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

    -
    - ) - } + renderHeader() { + const { device } = this.props + return ( +
    +

    + {`${this.context.t('unlock')} ${this.capitalizeDevice(device)}`} +

    + {device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null} +

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

    +

    + {this.context.t('selectAnAccountHelp')} +

    +
    + ) + } - renderAccounts () { - return ( -
    - {this.props.accounts.map((account, idx) => ( -
    -
    - this.props.onAccountChange(e.target.value)} - checked={this.props.selectedAccount === account.index.toString()} - /> - -
    - + {this.props.accounts.map((account, idx) => ( +
    +
    + this.props.onAccountChange(e.target.value)} + checked={ + this.props.selectedAccount === account.index.toString() + } + /> + + + {account.index + 1} + + {`${account.address.slice(0, 4)}...${account.address.slice( + -4, + )}`} + {`${account.balance}`} +
    - ))} -
    - ) + + + +
    + ))} +
    + ) + } + + renderPagination() { + return ( +
    + + +
    + ) + } + + renderButtons() { + const disabled = this.props.selectedAccount === null + const buttonProps = {} + if (disabled) { + buttonProps.disabled = true } - renderPagination () { - return ( -
    - - -
    - ) - } + return ( +
    + + +
    + ) + } - renderButtons () { - const disabled = this.props.selectedAccount === null - const buttonProps = {} - if (disabled) { - buttonProps.disabled = true - } - - return ( -
    - - -
    - ) - } - - renderForgetDevice () { - return ( - - ) - } - - render () { - return ( -
    - {this.renderHeader()} - {this.renderAccounts()} - {this.renderPagination()} - {this.renderButtons()} - {this.renderForgetDevice()} -
    - ) - } + renderForgetDevice() { + return ( + + ) + } + render() { + return ( +
    + {this.renderHeader()} + {this.renderAccounts()} + {this.renderPagination()} + {this.renderButtons()} + {this.renderForgetDevice()} +
    + ) + } } AccountList.propTypes = { diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js index 30bbfd44c..8e80d3775 100644 --- a/ui/app/pages/create-account/connect-hardware/index.js +++ b/ui/app/pages/create-account/connect-hardware/index.js @@ -8,6 +8,8 @@ import { getMostRecentOverviewPage } from '../../../ducks/history/history' import SelectHardware from './select-hardware' import AccountList from './account-list' +const U2F_ERROR = 'U2F' + class ConnectHardwareForm extends Component { state = { error: null, @@ -18,24 +20,29 @@ class ConnectHardwareForm extends Component { device: null, } - UNSAFE_componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { accounts } = nextProps const newAccounts = this.state.accounts.map((a) => { const normalizedAddress = a.address.toLowerCase() - const balanceValue = (accounts[normalizedAddress] && accounts[normalizedAddress].balance) || null + const balanceValue = + (accounts[normalizedAddress] && accounts[normalizedAddress].balance) || + null a.balance = balanceValue ? formatBalance(balanceValue, 6) : '...' return a }) this.setState({ accounts: newAccounts }) } - componentDidMount () { + componentDidMount() { this.checkIfUnlocked() } - async checkIfUnlocked () { + async checkIfUnlocked() { for (const device of ['trezor', 'ledger']) { - const unlocked = await this.props.checkHardwareStatus(device, this.props.defaultHdPaths[device]) + const unlocked = await this.props.checkHardwareStatus( + device, + this.props.defaultHdPaths[device], + ) if (unlocked) { this.setState({ unlocked: true }) this.getPage(device, 0, this.props.defaultHdPaths[device]) @@ -54,7 +61,10 @@ class ConnectHardwareForm extends Component { } onPathChange = (path) => { - this.props.setHardwareWalletDefaultHdPath({ device: this.state.device, path }) + this.props.setHardwareWalletDefaultHdPath({ + device: this.state.device, + path, + }) this.getPage(this.state.device, 0, path) } @@ -66,7 +76,7 @@ class ConnectHardwareForm extends Component { this.setState({ error: this.context.t('ledgerAccountRestriction') }) } - showTemporaryAlert () { + showTemporaryAlert() { this.props.showAlert(this.context.t('hardwareWalletConnected')) // Autohide the alert after 5 seconds setTimeout((_) => { @@ -79,7 +89,6 @@ class ConnectHardwareForm extends Component { .connectHardware(device, page, hdPath) .then((accounts) => { if (accounts.length) { - // If we just loaded the accounts for the first time // (device previously locked) show the global alert if (this.state.accounts.length === 0 && !this.state.unlocked) { @@ -94,16 +103,25 @@ class ConnectHardwareForm extends Component { newState.selectedAccount = a.index.toString() } }) - // If the page doesn't contain the selected account, let's deselect it - } else if (!accounts.filter((a) => a.index.toString() === this.state.selectedAccount).length) { + // If the page doesn't contain the selected account, let's deselect it + } else if ( + !accounts.filter( + (a) => a.index.toString() === this.state.selectedAccount, + ).length + ) { newState.selectedAccount = null } // Map accounts with balances newState.accounts = accounts.map((account) => { const normalizedAddress = account.address.toLowerCase() - const balanceValue = (this.props.accounts[normalizedAddress] && this.props.accounts[normalizedAddress].balance) || null - account.balance = balanceValue ? formatBalance(balanceValue, 6) : '...' + const balanceValue = + (this.props.accounts[normalizedAddress] && + this.props.accounts[normalizedAddress].balance) || + null + account.balance = balanceValue + ? formatBalance(balanceValue, 6) + : '...' return account }) @@ -114,16 +132,20 @@ class ConnectHardwareForm extends Component { const errorMessage = e.message if (errorMessage === 'Window blocked') { this.setState({ browserSupported: false, error: null }) - } else if (e.indexOf('U2F') > -1) { - this.setState({ error: 'U2F' }) - } else if (errorMessage !== 'Window closed' && errorMessage !== 'Popup closed') { + } else if (errorMessage.includes(U2F_ERROR)) { + this.setState({ error: U2F_ERROR }) + } else if ( + errorMessage !== 'Window closed' && + errorMessage !== 'Popup closed' + ) { this.setState({ error: errorMessage }) } }) } onForgetDevice = (device) => { - this.props.forgetDevice(device) + this.props + .forgetDevice(device) .then((_) => { this.setState({ error: null, @@ -131,13 +153,18 @@ class ConnectHardwareForm extends Component { accounts: [], unlocked: false, }) - }).catch((e) => { + }) + .catch((e) => { this.setState({ error: e.message }) }) } onUnlockAccount = (device) => { - const { history, mostRecentOverviewPage, unlockHardwareWalletAccount } = this.props + const { + history, + mostRecentOverviewPage, + unlockHardwareWalletAccount, + } = this.props if (this.state.selectedAccount === null) { this.setState({ error: this.context.t('accountSelectionRequired') }) @@ -153,7 +180,8 @@ class ConnectHardwareForm extends Component { }, }) history.push(mostRecentOverviewPage) - }).catch((e) => { + }) + .catch((e) => { this.context.metricsEvent({ eventOpts: { category: 'Accounts', @@ -173,13 +201,12 @@ class ConnectHardwareForm extends Component { history.push(mostRecentOverviewPage) } - renderError () { - if (this.state.error === 'U2F') { + renderError() { + if (this.state.error === U2F_ERROR) { return ( -

    - {this.context.t('troubleConnectingToWallet', [this.state.device, ( +

    + {this.context.t('troubleConnectingToWallet', [ + this.state.device, // eslint-disable-next-line react/jsx-key {this.context.t('walletConnectionGuide')} - - )])} + , + ])}

    ) } - return this.state.error - ? ( - - {this.state.error} - - ) - : null + return this.state.error ? ( + {this.state.error} + ) : null } - renderContent () { + renderContent() { if (!this.state.accounts.length) { return ( {this.renderError()} @@ -290,7 +313,9 @@ const mapDispatchToProps = (dispatch) => { return dispatch(actions.forgetDevice(deviceName)) }, unlockHardwareWalletAccount: (index, deviceName, hdPath) => { - return dispatch(actions.unlockHardwareWalletAccount(index, deviceName, hdPath)) + return dispatch( + actions.unlockHardwareWalletAccount(index, deviceName, hdPath), + ) }, showAlert: (msg) => dispatch(actions.showAlert(msg)), hideAlert: () => dispatch(actions.hideAlert()), @@ -302,6 +327,4 @@ ConnectHardwareForm.contextTypes = { metricsEvent: PropTypes.func, } -export default connect(mapStateToProps, mapDispatchToProps)( - ConnectHardwareForm, -) +export default connect(mapStateToProps, mapDispatchToProps)(ConnectHardwareForm) diff --git a/ui/app/pages/create-account/connect-hardware/index.scss b/ui/app/pages/create-account/connect-hardware/index.scss index c40e7b097..b9eb4c8f8 100644 --- a/ui/app/pages/create-account/connect-hardware/index.scss +++ b/ui/app/pages/create-account/connect-hardware/index.scss @@ -12,13 +12,15 @@ &__header { &__title { + @include H3; + margin-top: 5px; margin-bottom: 15px; - font-size: 22px; } &__msg { - font-size: 14px; + @include H6; + color: #9b9b9b; margin-top: 10px; margin-bottom: 20px; @@ -43,7 +45,7 @@ &__btn { background: #fbfbfb; - border: 2px solid #e5e5e5; + border: 1px solid #e5e5e5; height: 100px; width: 150px; flex: 1; @@ -59,7 +61,7 @@ } &__btn.selected { - border: 2px solid #00a8ee; + border-color: #00a8ee; width: 149px; } @@ -73,11 +75,12 @@ } &__hdPath { + @include H6; + display: flex; flex-direction: row; margin-top: 15px; margin-bottom: 30px; - font-size: 14px; &__title { display: flex; @@ -92,10 +95,10 @@ } &__learn-more { + @include H6; + margin-top: 15px; - font-size: 14px; color: #5b5d67; - line-height: 19px; text-align: center; cursor: pointer; @@ -109,20 +112,23 @@ } &__title { + @include H4; + padding-top: 10px; font-weight: 400; - font-size: 18px; } &__unlock-title { + @include H3; + padding-top: 10px; font-weight: 400; - font-size: 22px; margin-bottom: 15px; } &__msg { - font-size: 14px; + @include H6; + color: #9b9b9b; margin-top: 10px; margin-bottom: 15px; @@ -141,15 +147,17 @@ &__footer { &__title { + @include H4; + padding-top: 15px; padding-bottom: 12px; font-weight: 400; - font-size: 18px; text-align: center; } &__msg { - font-size: 14px; + @include H6; + color: #9b9b9b; margin-top: 12px; margin-bottom: 27px; @@ -170,12 +178,14 @@ padding-top: 10px; &__msg { - font-size: 14px; + @include H6; + color: #9b9b9b; } &__link { - font-size: 14px; + @include H4; + text-align: center; color: #2f9ae0; cursor: pointer; @@ -201,28 +211,28 @@ } &__title { + @include H5; + margin-bottom: 23px; align-self: flex-start; color: $scorpion; - font-size: 16px; - line-height: 21px; font-weight: bold; display: flex; flex: 1; } &__device { + @include Paragraph; + margin-bottom: 23px; align-self: flex-end; color: $scorpion; - font-size: 16px; - line-height: 21px; - font-weight: normal; display: flex; } &__item { - font-size: 15px; + @include Paragraph; + flex-direction: row; display: flex; padding-left: 10px; @@ -287,12 +297,12 @@ margin-top: 10px; &__button { + @include H6; + background: #fff; height: 19px; display: flex; color: #33a4e7; - font-size: 14px; - line-height: 19px; border: none; min-width: 46px; margin-right: 0; @@ -343,8 +353,9 @@ padding: 22px; a { + @include H6; + color: #2f9ae0; - font-size: 14px; cursor: pointer; } } diff --git a/ui/app/pages/create-account/connect-hardware/select-hardware.js b/ui/app/pages/create-account/connect-hardware/select-hardware.js index 02c26c95e..f8dd18211 100644 --- a/ui/app/pages/create-account/connect-hardware/select-hardware.js +++ b/ui/app/pages/create-account/connect-hardware/select-hardware.js @@ -24,11 +24,11 @@ export default class SelectHardware extends Component { return null } - renderConnectToTrezorButton () { + renderConnectToTrezorButton() { return ( @@ -98,23 +104,29 @@ export default class SelectHardware extends Component { ) } - renderHeader () { + renderHeader() { return (
    -

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

    -

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

    +

    + {this.context.t('hardwareWallets')} +

    +

    + {this.context.t('hardwareWalletsMsg')} +

    ) } - getAffiliateLinks () { + getAffiliateLinks() { const links = { trezor: `Trezor`, ledger: `Ledger`, } const text = this.context.t('orderOneHere') - const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger) + const response = text + .replace('Trezor', links.trezor) + .replace('Ledger', links.ledger) return (
    -

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

    +

    + {this.context.t('dontHaveAHardwareWallet')} +

    {this.getAffiliateLinks()}
    ) @@ -139,16 +153,20 @@ export default class SelectHardware extends Component { } } - renderLearnMore () { + renderLearnMore() { return (

    {this.context.t('learnMore')} - +

    ) } - renderTutorialSteps () { + renderTutorialSteps() { const steps = [ { asset: 'hardware-wallet-step-1', @@ -181,21 +199,33 @@ export default class SelectHardware extends Component {

    {step.title}

    {step.message}

    - +
    ))}
    ) } - renderFooter () { + renderFooter() { return (
    -

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

    +

    + {this.context.t('readyToConnect')} +

    {this.renderButtons()}

    {this.context.t('havingTroubleConnecting')} - + {this.context.t('getHelp')}

    @@ -203,7 +233,7 @@ export default class SelectHardware extends Component { ) } - renderConnectScreen () { + renderConnectScreen() { return (
    {this.renderHeader()} @@ -216,7 +246,7 @@ export default class SelectHardware extends Component { ) } - render () { + render() { if (this.props.browserSupported) { return this.renderConnectScreen() } diff --git a/ui/app/pages/create-account/create-account.component.js b/ui/app/pages/create-account/create-account.component.js index 2ff859144..1ec2e5258 100644 --- a/ui/app/pages/create-account/create-account.component.js +++ b/ui/app/pages/create-account/create-account.component.js @@ -12,35 +12,50 @@ import NewAccountImportForm from './import-account' import ConnectHardwareForm from './connect-hardware' export default class CreateAccountPage extends Component { - renderTabs () { - const { history, location: { pathname } } = this.props - const getClassNames = (path) => classnames('new-account__tabs__tab', { - 'new-account__tabs__selected': matchPath(pathname, { - path, - exact: true, - }), - }) + renderTabs() { + const { + history, + location: { pathname }, + } = this.props + const getClassNames = (path) => + classnames('new-account__tabs__tab', { + 'new-account__tabs__selected': matchPath(pathname, { + path, + exact: true, + }), + }) return (
    -
    history.push(NEW_ACCOUNT_ROUTE)}> +
    history.push(NEW_ACCOUNT_ROUTE)} + > {this.context.t('create')}
    -
    history.push(IMPORT_ACCOUNT_ROUTE)}> +
    history.push(IMPORT_ACCOUNT_ROUTE)} + > {this.context.t('import')}
    -
    history.push(CONNECT_HARDWARE_ROUTE)}> +
    history.push(CONNECT_HARDWARE_ROUTE)} + > {this.context.t('hardware')}
    ) } - render () { + render() { return (
    -
    +
    {this.renderTabs()}
    diff --git a/ui/app/pages/create-account/import-account/index.js b/ui/app/pages/create-account/import-account/index.js index dee390024..0b7e45cf9 100644 --- a/ui/app/pages/create-account/import-account/index.js +++ b/ui/app/pages/create-account/import-account/index.js @@ -13,14 +13,11 @@ export default class AccountImportSubview extends Component { state = {} - getMenuItemTexts () { - return [ - this.context.t('privateKey'), - this.context.t('jsonFile'), - ] + getMenuItemTexts() { + return [this.context.t('privateKey'), this.context.t('jsonFile')] } - renderImportView () { + renderImportView() { const { type } = this.state const menuItems = this.getMenuItemTexts() const current = type || menuItems[0] @@ -35,7 +32,7 @@ export default class AccountImportSubview extends Component { } } - render () { + render() { const menuItems = this.getMenuItemTexts() const { type } = this.state @@ -50,7 +47,8 @@ export default class AccountImportSubview extends Component { }} onClick={() => { global.platform.openTab({ - url: 'https://metamask.zendesk.com/hc/en-us/articles/360015289932', + url: + 'https://metamask.zendesk.com/hc/en-us/articles/360015289932', }) }} > diff --git a/ui/app/pages/create-account/import-account/index.scss b/ui/app/pages/create-account/import-account/index.scss index 8ad30cc13..6749e0e76 100644 --- a/ui/app/pages/create-account/import-account/index.scss +++ b/ui/app/pages/create-account/import-account/index.scss @@ -1,11 +1,11 @@ .new-account-import-disclaimer { + @include H7; + width: 120%; background-color: #f4f9fc; display: inline-block; align-items: center; padding: 20px 30px 20px; - font-size: 12px; - line-height: 1.5; } .new-account-import-form { @@ -23,9 +23,9 @@ } &__select-label { + @include Paragraph; + color: $scorpion; - font-size: 16px; - line-height: 21px; } &__select { @@ -58,9 +58,9 @@ } &__instruction { + @include Paragraph; + color: $scorpion; - font-size: 16px; - line-height: 21px; align-self: flex-start; } @@ -72,6 +72,8 @@ } &__input-password { + @include Paragraph; + height: 54px; width: 315px; border: 1px solid $geyser; @@ -79,7 +81,6 @@ background-color: $white; margin-top: 16px; color: $scorpion; - font-size: 16px; padding: 0 20px; } diff --git a/ui/app/pages/create-account/import-account/json.js b/ui/app/pages/create-account/import-account/json.js index 1edaed355..34eadaf84 100644 --- a/ui/app/pages/create-account/import-account/json.js +++ b/ui/app/pages/create-account/import-account/json.js @@ -9,7 +9,8 @@ import { getMetaMaskAccounts } from '../../../selectors' import Button from '../../../components/ui/button' import { getMostRecentOverviewPage } from '../../../ducks/history/history' -const HELP_LINK = 'https://metamask.zendesk.com/hc/en-us/articles/360015489331-Importing-an-Account' +const HELP_LINK = + 'https://metamask.zendesk.com/hc/en-us/articles/360015489331-Importing-an-Account' class JsonImportSubview extends Component { state = { @@ -19,14 +20,21 @@ class JsonImportSubview extends Component { inputRef = React.createRef() - render () { + render() { const { error, history, mostRecentOverviewPage } = this.props const enabled = !this.state.isEmpty && this.state.fileContents !== '' return ( - { - error - ? {error} - : null - } + {error ? {error} : null}
    ) } - onLoad (event) { + onLoad(event) { this.setState({ fileContents: event.target.result, }) } - createKeyringOnEnter (event) { + createKeyringOnEnter(event) { if (event.key === 'Enter') { event.preventDefault() this.createNewKeychain() } } - createNewKeychain () { + createNewKeychain() { const { firstAddress, displayWarning, @@ -134,7 +138,7 @@ class JsonImportSubview extends Component { .catch((err) => err && displayWarning(err.message || err)) } - checkInputEmpty () { + checkInputEmpty() { const password = this.inputRef.current.value let isEmpty = true if (password !== '') { @@ -165,8 +169,10 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return { displayWarning: (warning) => dispatch(actions.displayWarning(warning)), - importNewJsonAccount: (options) => dispatch(actions.importNewAccount('JSON File', options)), - setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)), + importNewJsonAccount: (options) => + dispatch(actions.importNewAccount('JSON File', options)), + setSelectedAddress: (address) => + dispatch(actions.setSelectedAddress(address)), } } diff --git a/ui/app/pages/create-account/import-account/private-key.js b/ui/app/pages/create-account/import-account/private-key.js index d3c4240f8..e2f78ada4 100644 --- a/ui/app/pages/create-account/import-account/private-key.js +++ b/ui/app/pages/create-account/import-account/private-key.js @@ -28,9 +28,16 @@ class PrivateKeyImportView extends Component { state = { isEmpty: true } - createNewKeychain () { + createNewKeychain() { const privateKey = this.inputRef.current.value - const { importNewAccount, history, displayWarning, mostRecentOverviewPage, setSelectedAddress, firstAddress } = this.props + const { + importNewAccount, + history, + displayWarning, + mostRecentOverviewPage, + setSelectedAddress, + firstAddress, + } = this.props importNewAccount('Private Key', [privateKey]) .then(({ selectedAddress }) => { @@ -66,7 +73,7 @@ class PrivateKeyImportView extends Component { } } - checkInputEmpty () { + checkInputEmpty() { const privateKey = this.inputRef.current.value let isEmpty = true if (privateKey !== '') { @@ -75,7 +82,7 @@ class PrivateKeyImportView extends Component { this.setState({ isEmpty }) } - render () { + render() { const { error, displayWarning } = this.props return ( @@ -117,11 +124,7 @@ class PrivateKeyImportView extends Component { {this.context.t('import')}
    - { - error - ? {error} - : null - } + {error ? {error} : null}
    ) } @@ -132,7 +135,7 @@ export default compose( connect(mapStateToProps, mapDispatchToProps), )(PrivateKeyImportView) -function mapStateToProps (state) { +function mapStateToProps(state) { return { error: state.appState.warning, firstAddress: Object.keys(getMetaMaskAccounts(state))[0], @@ -140,12 +143,14 @@ function mapStateToProps (state) { } } -function mapDispatchToProps (dispatch) { +function mapDispatchToProps(dispatch) { return { importNewAccount: (strategy, [privateKey]) => { return dispatch(actions.importNewAccount(strategy, [privateKey])) }, - displayWarning: (message) => dispatch(actions.displayWarning(message || null)), - setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)), + displayWarning: (message) => + dispatch(actions.displayWarning(message || null)), + setSelectedAddress: (address) => + dispatch(actions.setSelectedAddress(address)), } } diff --git a/ui/app/pages/create-account/index.scss b/ui/app/pages/create-account/index.scss index d6b1717e0..fa55a3efa 100644 --- a/ui/app/pages/create-account/index.scss +++ b/ui/app/pages/create-account/index.scss @@ -25,10 +25,10 @@ } &__title { + @include H2; + color: $tundora; - font-size: 32px; font-weight: 500; - line-height: 43px; margin-top: 22px; margin-left: 29px; } @@ -39,11 +39,11 @@ margin-top: 10px; &__tab { + @include H4; + height: 54px; padding: 15px 10px; color: $dusty-gray; - font-size: 18px; - line-height: 24px; text-align: center; cursor: pointer; } @@ -70,21 +70,21 @@ padding: 30px; &__input-label { + @include Paragraph; + color: $scorpion; - font-size: 16px; - line-height: 21px; align-self: flex-start; } &__input { + @include Paragraph; + height: 54px; width: 315.84px; border: 1px solid $geyser; border-radius: 4px; background-color: $white; color: $scorpion; - font-size: 16px; - line-height: 21px; margin-top: 15px; padding: 0 20px; } diff --git a/ui/app/pages/create-account/new-account.component.js b/ui/app/pages/create-account/new-account.component.js index 9995b28ab..92f832a23 100644 --- a/ui/app/pages/create-account/new-account.component.js +++ b/ui/app/pages/create-account/new-account.component.js @@ -14,7 +14,7 @@ export default class NewAccountCreateForm extends Component { ]), } - render () { + render() { const { newAccountName, defaultAccountName } = this.state const { history, createAccount, mostRecentOverviewPage } = this.props const createClick = (_) => { @@ -53,7 +53,9 @@ export default class NewAccountCreateForm extends Component { className="new-account-create-form__input" value={newAccountName} placeholder={defaultAccountName} - onChange={(event) => this.setState({ newAccountName: event.target.value })} + onChange={(event) => + this.setState({ newAccountName: event.target.value }) + } autoFocus />
    diff --git a/ui/app/pages/create-account/new-account.container.js b/ui/app/pages/create-account/new-account.container.js index 16fffe959..833d7143c 100644 --- a/ui/app/pages/create-account/new-account.container.js +++ b/ui/app/pages/create-account/new-account.container.js @@ -4,7 +4,9 @@ import { getMostRecentOverviewPage } from '../../ducks/history/history' import NewAccountCreateForm from './new-account.component' const mapStateToProps = (state) => { - const { metamask: { identities = {} } } = state + const { + metamask: { identities = {} }, + } = state const numberOfExistingAccounts = Object.keys(identities).length const newAccountNumber = numberOfExistingAccounts + 1 @@ -17,14 +19,16 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return { createAccount: (newAccountName) => { - return dispatch(actions.addNewAccount()) - .then((newAccountAddress) => { - if (newAccountName) { - dispatch(actions.setAccountLabel(newAccountAddress, newAccountName)) - } - }) + return dispatch(actions.addNewAccount()).then((newAccountAddress) => { + if (newAccountName) { + dispatch(actions.setAccountLabel(newAccountAddress, newAccountName)) + } + }) }, } } -export default connect(mapStateToProps, mapDispatchToProps)(NewAccountCreateForm) +export default connect( + mapStateToProps, + mapDispatchToProps, +)(NewAccountCreateForm) diff --git a/ui/app/pages/create-account/tests/create-account.test.js b/ui/app/pages/create-account/tests/create-account.test.js index f41aa2796..a99ffa47f 100644 --- a/ui/app/pages/create-account/tests/create-account.test.js +++ b/ui/app/pages/create-account/tests/create-account.test.js @@ -17,9 +17,7 @@ describe('Create Account Page', function () { } before(function () { - wrapper = mountWithRouter( - , - ) + wrapper = mountWithRouter() }) afterEach(function () { diff --git a/ui/app/pages/error/error.component.js b/ui/app/pages/error/error.component.js index 1da1ac609..d3fe6f756 100644 --- a/ui/app/pages/error/error.component.js +++ b/ui/app/pages/error/error.component.js @@ -12,30 +12,24 @@ class ErrorPage extends PureComponent { error: PropTypes.object.isRequired, } - renderErrorDetail (content) { + renderErrorDetail(content) { return (
  • -

    - {content} -

    +

    {content}

  • ) } - renderErrorStack (title, stack) { + renderErrorStack(title, stack) { return (
  • - - {title} - -
    -          {stack}
    -        
    + {title} +
    {stack}
  • ) } - render () { + render() { const { error } = this.props const { t } = this.context @@ -43,26 +37,26 @@ class ErrorPage extends PureComponent { return (
    -

    - {t('errorPageTitle')} -

    +

    {t('errorPageTitle')}

    - { - isPopup - ? t('errorPagePopupMessage') - : t('errorPageMessage') - } + {isPopup ? t('errorPagePopupMessage') : t('errorPageMessage')}

    - - {t('errorDetails')} - + {t('errorDetails')}
      - { error.message ? this.renderErrorDetail(t('errorMessage', [error.message])) : null } - { error.code ? this.renderErrorDetail(t('errorCode', [error.code])) : null } - { error.name ? this.renderErrorDetail(t('errorName', [error.name])) : null } - { error.stack ? this.renderErrorStack(t('errorStack'), error.stack) : null } + {error.message + ? this.renderErrorDetail(t('errorMessage', [error.message])) + : null} + {error.code + ? this.renderErrorDetail(t('errorCode', [error.code])) + : null} + {error.name + ? this.renderErrorDetail(t('errorName', [error.name])) + : null} + {error.stack + ? this.renderErrorStack(t('errorStack'), error.stack) + : null}
    diff --git a/ui/app/pages/error/index.scss b/ui/app/pages/error/index.scss index 14ec9da84..0edb6a630 100644 --- a/ui/app/pages/error/index.scss +++ b/ui/app/pages/error/index.scss @@ -8,15 +8,17 @@ height: 100%; &__header { + @include H1; + display: flex; justify-content: center; - font-size: 42px; padding: 10px 0; text-align: center; } &__subheader { - font-size: 19px; + @include H4; + padding: 10px 0; width: 100%; max-width: 720px; @@ -24,7 +26,8 @@ } &__details { - font-size: 18px; + @include H4; + overflow-y: auto; width: 100%; max-width: 720px; diff --git a/ui/app/pages/first-time-flow/create-password/create-password.component.js b/ui/app/pages/first-time-flow/create-password/create-password.component.js index a159601da..b249c3aba 100644 --- a/ui/app/pages/first-time-flow/create-password/create-password.component.js +++ b/ui/app/pages/first-time-flow/create-password/create-password.component.js @@ -18,7 +18,7 @@ export default class CreatePassword extends PureComponent { onCreateNewAccountFromSeed: PropTypes.func, } - componentDidMount () { + componentDidMount() { const { isInitialized, history } = this.props if (isInitialized) { @@ -26,7 +26,7 @@ export default class CreatePassword extends PureComponent { } } - render () { + render() { const { onCreateNewAccount, onCreateNewAccountFromSeed } = this.props return ( @@ -38,7 +38,7 @@ export default class CreatePassword extends PureComponent { path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE} render={(routeProps) => ( )} @@ -47,10 +47,7 @@ export default class CreatePassword extends PureComponent { exact path={INITIALIZE_CREATE_PASSWORD_ROUTE} render={(routeProps) => ( - + )} /> diff --git a/ui/app/pages/first-time-flow/create-password/create-password.container.js b/ui/app/pages/first-time-flow/create-password/create-password.container.js index 728764d6c..9e8d12870 100644 --- a/ui/app/pages/first-time-flow/create-password/create-password.container.js +++ b/ui/app/pages/first-time-flow/create-password/create-password.container.js @@ -2,7 +2,9 @@ import { connect } from 'react-redux' import CreatePassword from './create-password.component' const mapStateToProps = (state) => { - const { metamask: { isInitialized } } = state + const { + metamask: { isInitialized }, + } = state return { isInitialized, diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js index bc12fe782..b9de02575 100644 --- a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js @@ -21,7 +21,6 @@ export default class ImportWithSeedPhrase extends PureComponent { onSubmit: PropTypes.func.isRequired, setSeedPhraseBackedUp: PropTypes.func, initializeThreeBox: PropTypes.func, - completeOnboarding: PropTypes.func, } state = { @@ -35,28 +34,30 @@ export default class ImportWithSeedPhrase extends PureComponent { termsChecked: false, } - parseSeedPhrase = (seedPhrase) => (seedPhrase || '').trim().toLowerCase().match(/\w+/gu)?.join(' ') || '' + parseSeedPhrase = (seedPhrase) => + (seedPhrase || '').trim().toLowerCase().match(/\w+/gu)?.join(' ') || '' - UNSAFE_componentWillMount () { - this._onBeforeUnload = () => this.context.metricsEvent({ - eventOpts: { - category: 'Onboarding', - action: 'Import Seed Phrase', - name: 'Close window on import screen', - }, - customVariables: { - errorLabel: 'Seed Phrase Error', - errorMessage: this.state.seedPhraseError, - }, - }) + UNSAFE_componentWillMount() { + this._onBeforeUnload = () => + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Import Seed Phrase', + name: 'Close window on import screen', + }, + customVariables: { + errorLabel: 'Seed Phrase Error', + errorMessage: this.state.seedPhraseError, + }, + }) window.addEventListener('beforeunload', this._onBeforeUnload) } - componentWillUnmount () { + componentWillUnmount() { window.removeEventListener('beforeunload', this._onBeforeUnload) } - handleSeedPhraseChange (seedPhrase) { + handleSeedPhraseChange(seedPhrase) { let seedPhraseError = '' if (seedPhrase) { @@ -72,7 +73,7 @@ export default class ImportWithSeedPhrase extends PureComponent { this.setState({ seedPhrase, seedPhraseError }) } - handlePasswordChange (password) { + handlePasswordChange(password) { const { t } = this.context this.setState((state) => { @@ -96,7 +97,7 @@ export default class ImportWithSeedPhrase extends PureComponent { }) } - handleConfirmPasswordChange (confirmPassword) { + handleConfirmPasswordChange(confirmPassword) { const { t } = this.context this.setState((state) => { @@ -122,7 +123,12 @@ export default class ImportWithSeedPhrase extends PureComponent { } const { password, seedPhrase } = this.state - const { history, onSubmit, setSeedPhraseBackedUp, initializeThreeBox, completeOnboarding } = this.props + const { + history, + onSubmit, + setSeedPhraseBackedUp, + initializeThreeBox, + } = this.props try { await onSubmit(password, this.parseSeedPhrase(seedPhrase)) @@ -135,7 +141,6 @@ export default class ImportWithSeedPhrase extends PureComponent { }) setSeedPhraseBackedUp(true).then(async () => { - await completeOnboarding() initializeThreeBox() history.push(INITIALIZE_END_OF_FLOW_ROUTE) }) @@ -144,7 +149,7 @@ export default class ImportWithSeedPhrase extends PureComponent { } } - isValid () { + isValid() { const { seedPhrase, password, @@ -154,7 +159,12 @@ export default class ImportWithSeedPhrase extends PureComponent { seedPhraseError, } = this.state - if (!password || !confirmPassword || !seedPhrase || password !== confirmPassword) { + if ( + !password || + !confirmPassword || + !seedPhrase || + password !== confirmPassword + ) { return false } @@ -190,15 +200,18 @@ export default class ImportWithSeedPhrase extends PureComponent { })) } - render () { + render() { const { t } = this.context - const { seedPhraseError, showSeedPhrase, passwordError, confirmPasswordError, termsChecked } = this.state + const { + seedPhraseError, + showSeedPhrase, + passwordError, + confirmPasswordError, + termsChecked, + } = this.state return ( -
    +
    - { t('importAccountSeedPhrase') } -
    -
    - { t('secretPhrase') } + {t('importAccountSeedPhrase')}
    +
    {t('secretPhrase')}
    - + {showSeedPhrase ? (