diff --git a/.circleci/config.yml b/.circleci/config.yml index 183808490..852794659 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -837,6 +837,8 @@ jobs: - .nyc_output - coverage - jest-coverage + - store_test_results: + path: test/test-results/junit.xml test-unit-global: executor: node-browsers steps: diff --git a/.circleci/scripts/bundle-stats-commit.sh b/.circleci/scripts/bundle-stats-commit.sh index db5fe101b..31f0ca9d5 100755 --- a/.circleci/scripts/bundle-stats-commit.sh +++ b/.circleci/scripts/bundle-stats-commit.sh @@ -40,25 +40,21 @@ git config --global user.name "MetaMask Bot" git clone git@github.com:MetaMask/extension_bundlesize_stats.git temp -if [[ -f "temp/stats/bundle_size_stats-${CIRCLE_SHA1}.json" ]] -then - printf 'Bundle size of the commit is already recorded' - cd .. - rm -rf temp - exit 0 -fi +{ + echo " '${CIRCLE_SHA1}': "; + cat test-artifacts/chrome/mv3/bundle_size_stats.json; + echo ", "; +} >> temp/stats/bundle_size_data.temp.js -cp -R test-artifacts/chrome/mv3/bundle_size_stats.json temp/stats +cp temp/stats/bundle_size_data.temp.js temp/stats/bundle_size_data.js -echo " bundle_size_stats-${CIRCLE_SHA1}.json" >> temp/stats/fileList.txt - -mv temp/stats/bundle_size_stats.json "temp/stats/bundle_size_stats-${CIRCLE_SHA1}.json" +echo " }" >> temp/stats/bundle_size_data.js cd temp git add . -git commit --message "Bundle size at commit: ${CIRCLE_SHA1}" +git commit --message "Adding bundle size at commit: ${CIRCLE_SHA1}" repo_slug="$CIRCLE_PROJECT_USERNAME/extension_bundlesize_stats" git push "https://$GITHUB_TOKEN_USER:$GITHUB_TOKEN@github.com/$repo_slug" main diff --git a/.circleci/scripts/chrome-install.sh b/.circleci/scripts/chrome-install.sh index dcd122fb8..083b6bbaf 100755 --- a/.circleci/scripts/chrome-install.sh +++ b/.circleci/scripts/chrome-install.sh @@ -5,12 +5,12 @@ set -u set -o pipefail # To get the latest version, see -CHROME_VERSION='103.0.5060.53-1' +CHROME_VERSION='105.0.5195.102-1' CHROME_BINARY="google-chrome-stable_${CHROME_VERSION}_amd64.deb" CHROME_BINARY_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/${CHROME_BINARY}" # To retrieve this checksum, run the `wget` and `shasum` commands below -CHROME_BINARY_SHA512SUM='36f4e79f46cb71c1431dccf1489f5f8e89d35204a717a4618c7f6f638123ddc2b37bd5cbd00498be8f84c7713149f2faa447cb6da3518be1cb9703e99d110e1a' +CHROME_BINARY_SHA512SUM='3a1f2267ae009424ee8c623c3f78760d969dc1f3acb490e103e667d11e52cf0d955f201aeb3892dd41f33e68625af77ca5a20244b5be718f794eccb07a4c0413' wget -O "${CHROME_BINARY}" -t 5 "${CHROME_BINARY_URL}" diff --git a/.eslintrc.js b/.eslintrc.js index 31c60bd51..13fa5251c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,8 +1,11 @@ const path = require('path'); + const { version: reactVersion } = require('react/package.json'); module.exports = { root: true, + // Suggested addition from the storybook 6.5 update + extends: ['plugin:storybook/recommended'], // Ignore files which are also in .prettierignore ignorePatterns: [ 'app/vendor/**', @@ -22,7 +25,6 @@ module.exports = { * because we do not allow a file to use two different styles for specifying * imports and exports (however theoretically possible it may be). */ - { /** * Modules (CommonJS module syntax) @@ -128,11 +130,9 @@ module.exports = { 'import/namespace': 'off', 'import/default': 'off', 'import/no-named-as-default-member': 'off', - // Disabled due to incompatibility with Record. // See: '@typescript-eslint/consistent-type-definitions': 'off', - // Modified to include the 'ignoreRestSiblings' option. // TODO: Migrate this rule change back into `@metamask/eslint-config` '@typescript-eslint/no-unused-vars': [ @@ -165,7 +165,6 @@ module.exports = { sourceType: 'script', }, }, - /** * == Everything else == * @@ -198,7 +197,10 @@ module.exports = { 'react/jsx-boolean-value': 'error', 'react/jsx-curly-brace-presence': [ 'error', - { props: 'never', children: 'never' }, + { + props: 'never', + children: 'never', + }, ], 'react/no-deprecated': 'error', 'react/default-props-match-prop-types': 'error', @@ -256,16 +258,18 @@ module.exports = { files: [ '**/__snapshots__/*.snap', 'app/scripts/controllers/network/**/*.test.js', + 'app/scripts/controllers/network/provider-api-tests/*.js', 'app/scripts/controllers/permissions/**/*.test.js', 'app/scripts/lib/**/*.test.js', 'app/scripts/migrations/*.test.js', 'app/scripts/platforms/*.test.js', 'development/**/*.test.js', 'shared/**/*.test.js', - 'test/jest/*.js', 'test/helpers/*.js', + 'test/jest/*.js', 'ui/**/*.test.js', 'ui/__mocks__/*.js', + 'shared/lib/error-utils.test.js', ], extends: ['@metamask/eslint-config-jest'], parserOptions: { @@ -276,15 +280,20 @@ module.exports = { 'import/named': 'off', 'jest/no-large-snapshots': [ 'error', - { maxSize: 50, inlineMaxSize: 50 }, + { + maxSize: 50, + inlineMaxSize: 50, + }, ], 'jest/no-restricted-matchers': 'off', + /** * jest/prefer-to-be is a new rule that was disabled to reduce churn * when upgrading eslint. It should be considered for use and enabled * in a future PR if agreeable. */ 'jest/prefer-to-be': 'off', + /** * jest/lowercase-name was renamed to jest/prefer-lowercase-title this * change was made to essentially retain the same state as the original @@ -292,7 +301,12 @@ module.exports = { * two lines can be deleted. */ 'jest/lowercase-name': 'off', - 'jest/prefer-lowercase-title': ['error', { ignore: ['describe'] }], + 'jest/prefer-lowercase-title': [ + 'error', + { + ignore: ['describe'], + }, + ], }, }, /** @@ -301,7 +315,12 @@ module.exports = { { files: ['app/scripts/migrations/*.js', '**/*.stories.js'], rules: { - 'import/no-anonymous-default-export': ['error', { allowObject: true }], + 'import/no-anonymous-default-export': [ + 'error', + { + allowObject: true, + }, + ], }, }, /** @@ -347,7 +366,13 @@ module.exports = { { files: ['ui/pages/settings/*.js'], rules: { - 'sort-keys': ['error', 'asc', { natural: true }], + 'sort-keys': [ + 'error', + 'asc', + { + natural: true, + }, + ], }, }, ], diff --git a/.mocharc.js b/.mocharc.js index e3904c2b1..2db1510e4 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -7,6 +7,7 @@ module.exports = { './app/scripts/platforms/*.test.js', './app/scripts/controllers/network/**/*.test.js', './app/scripts/controllers/permissions/**/*.test.js', + './app/scripts/constants/error-utils.test.js', ], recursive: true, require: ['test/env.js', 'test/setup.js'], diff --git a/.storybook/__mocks__/webextension-polyfill.js b/.storybook/__mocks__/webextension-polyfill.js index c8e3dea17..011bdd867 100644 --- a/.storybook/__mocks__/webextension-polyfill.js +++ b/.storybook/__mocks__/webextension-polyfill.js @@ -1,4 +1,8 @@ module.exports = { - runtime: {}, - }; + runtime: { + getManifest: () => { + return { manifest_version: 2 }; + } + }, +}; \ No newline at end of file diff --git a/.storybook/main.js b/.storybook/main.js index e7b859b84..81387c7e6 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -2,6 +2,8 @@ const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); +const { generateIconNames } = require('../development/generate-icon-names'); + module.exports = { stories: [ '../ui/**/*.stories.js', @@ -16,14 +18,25 @@ module.exports = { './i18n-party-addon/register.js', 'storybook-dark-mode', ], + staticDirs: ['../app', './images'], // Uses babel.config.js settings and prevents "Missing class properties transform" error babel: async (options) => ({ overrides: options.overrides }), + // Sets env variables https://storybook.js.org/docs/react/configure/environment-variables/ + env: async (config) => { + return { + ...config, + // Creates the icon names environment variable for the component-library/icon/icon.js component + ICON_NAMES: await generateIconNames(), + }; + }, webpackFinal: async (config) => { config.context = process.cwd(); config.node = { __filename: true, }; - config.resolve.alias['webextension-polyfill'] = require.resolve('./__mocks__/webextension-polyfill.js') + config.resolve.alias['webextension-polyfill'] = require.resolve( + './__mocks__/webextension-polyfill.js', + ); config.module.strictExportPresence = true; config.module.rules.push({ test: /\.scss$/, diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e7dbb98..743ffb254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -689,7 +689,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#12230](https://github.com/MetaMask/metamask-extension/pull/12230): Fix gas control flicker on send screen when switching between EIP-1559 networks - [#12186](https://github.com/MetaMask/metamask-extension/pull/12186): Fix grammatical issue with "Not connected to this site" message - [#12381](https://github.com/MetaMask/metamask-extension/pull/12381): Fix width and padding of the hide token modal while in the popup view -- [#12339](https://github.com/MetaMask/metamask-extension/pull/11996): Fix 'BigNumber' app error when '0x' is supplied as the transaction value +- [#11996](https://github.com/MetaMask/metamask-extension/pull/11996): Fix 'BigNumber' app error when '0x' is supplied as the transaction value - [#12339](https://github.com/MetaMask/metamask-extension/pull/12339): Correctly notify the inpage provider of current selected account on "unlock" events - [#12405](https://github.com/MetaMask/metamask-extension/pull/12405): Fix allowance issue with WETH -> ETH Swaps @@ -1750,7 +1750,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [7.0.0] - 2019-08-07 ### Uncategorized - Capitalized speed up label to match rest of UI ([#6828](https://github.com/MetaMask/metamask-extension/pull/6828)) -- Allows skipping of seed phrase challenge during onboarding, and completing it at a later time ([#6874](https://github.com/MetaMask/metamask-extension/pull/6928)) +- Allows skipping of seed phrase challenge during onboarding, and completing it at a later time ([#6874](https://github.com/MetaMask/metamask-extension/pull/6874)) - Prevent opening of asset dropdown if no tokens in account ([#6900](https://github.com/MetaMask/metamask-extension/pull/6900)) - Set privacy mode as default ([#6904](https://github.com/MetaMask/metamask-extension/pull/6904)) - Adds Address Book feature ([#6914](https://github.com/MetaMask/metamask-extension/pull/6914)) @@ -1936,7 +1936,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [5.3.5] - 2019-02-04 ### Uncategorized -- Privacy mode fixes ([#6084](https://github.com/MetaMask/metamask-extension/pull/6087)) +- Privacy mode fixes ([#6087](https://github.com/MetaMask/metamask-extension/pull/6087)) ## [5.3.4] - 2019-01-31 ### Uncategorized diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index d78a3f854..2f6199786 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -135,7 +135,7 @@ "message": "Dadurch kann dieses Netzwerk innerhalb MetaMask verwendet werden." }, "addEthereumChainConfirmationRisks": { - "message": "MetaMaske überprüft keine benutzerdefinierten Netzwerke." + "message": "MetaMask überprüft keine benutzerdefinierten Netzwerke." }, "addEthereumChainConfirmationRisksLearnMore": { "message": "Erfahren Sie mehr über $1.", @@ -412,7 +412,7 @@ "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, "buyCryptoWithCoinbasePay": { - "message": "1 $ mit Coinbase Pay kaufen", + "message": "$1 mit Coinbase Pay kaufen", "description": "$1 represents the crypto symbol to be purchased" }, "buyCryptoWithCoinbasePayDescription": { @@ -467,7 +467,7 @@ "description": "$1 is string 'cancel' or 'speed up'" }, "cancelSwapForFee": { - "message": "Tausch für 1 $ abbrechen", + "message": "Tausch für $1 abbrechen", "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" }, "cancelSwapForFree": { @@ -1151,7 +1151,7 @@ "description": "Message displayed on generic error page in the popup UI, $1 is a clickable link with text defined by the 'here' key. The link will open to a form where users can file support tickets." }, "errorPageTitle": { - "message": "MetaMaske hat einen Fehler festgestellt", + "message": "MetaMask hat einen Fehler festgestellt", "description": "Title of generic error page" }, "errorStack": { @@ -1659,7 +1659,7 @@ "message": "Zuletzt verbunden" }, "learmMoreAboutGas": { - "message": "Wollen Sie 1 $ über Gas?" + "message": "Wollen Sie $1 über Gas?" }, "learnCancelSpeeedup": { "message": "Erfahren Sie, wie Sie $1", @@ -1860,21 +1860,21 @@ "message": "Erfassen Sie niemals Schlüssel, Adressen, Transaktionen, Salden, Hashes oder persönliche Informationen" }, "metametricsCommitmentsNeverCollectIP": { - "message": "$1 Erfassen Sie Ihre vollständige IP-Adresse", + "message": "$1 Ihre vollständige IP-Adresse erfassen", "description": "The $1 is the bolded word 'Never', from 'metametricsCommitmentsBoldNever'" }, "metametricsCommitmentsNeverCollectKeysEtc": { - "message": "$ erfassen Sie Schlüssel, Adressen, Transaktionen, Salden, Hashes oder persönliche Informationen", + "message": "$1 Schlüssel, Adressen, Transaktionen, Salden, Hashes oder persönliche Informationen erfassen", "description": "The $1 is the bolded word 'Never', from 'metametricsCommitmentsBoldNever'" }, "metametricsCommitmentsNeverIP": { - "message": "Erfassen Sie nie Ihre vollständige IP-Adresse" + "message": "Nie Ihre vollständige IP-Adresse erfassen" }, "metametricsCommitmentsNeverSell": { - "message": "Verkaufen Sie niemals Daten mit Gewinnabsicht. Niemals!" + "message": "Nie Daten mit Gewinnabsicht verkaufen. Niemals!" }, "metametricsCommitmentsNeverSellDataForProfit": { - "message": "1 $ Daten für Gewinn verkaufen. Immer!", + "message": "$1 Daten für Gewinn verkaufen. Niemals!", "description": "The $1 is the bolded word 'Never', from 'metametricsCommitmentsBoldNever'" }, "metametricsCommitmentsSendAnonymizedEvents": { @@ -1974,14 +1974,11 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, "networkSettingsChainIdDescription": { - "message": "Die Ketten-ID wird zur Unterzeichnung von Transaktionen verwendet. Sie muss mit der vom Netzwerk zurückgegebenen Ketten-ID übereinstimmen. Sie können eine Dezimalzahl oder '0x'-prefix Hexadezimalzahl eingeben, aber wir werden die Zahl dezimal anzeigen." + "message": "Die Chain-ID wird zur Unterzeichnung von Transaktionen verwendet. Sie muss mit der vom Netzwerk zurückgegebenen Ketten-ID übereinstimmen. Sie können eine Dezimalzahl oder '0x'-prefix Hexadezimalzahl eingeben, aber wir werden die Zahl dezimal anzeigen." }, "networkStatus": { "message": "Netzwerkstatus" @@ -2182,7 +2179,7 @@ "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." }, "notifications3Description": { - "message": "Bleiben Sie auf dem Laufenden bezüglich der bewährten Sicherheitsverfahren von MetaMask und erhalten Sie die neuesten Sicherheitstipps des offiziellen MetaMasken-Supports.", + "message": "Bleiben Sie auf dem Laufenden bezüglich der bewährten Sicherheitsverfahren von MetaMask und erhalten Sie die neuesten Sicherheitstipps des offiziellen MetaMask-Supports.", "description": "Description of a notification in the 'See What's New' popup. Describes the information they can get on security from the linked support page." }, "notifications3Title": { @@ -3233,9 +3230,6 @@ "swap": { "message": "Swap" }, - "swapAdvancedSlippageInfo": { - "message": "Wenn sich der Kurs zwischen der Aufgabe und der Bestätigung Ihres Auftrags ändert, nennt man das \"Slippage\". Ihr Swap wird automatisch storniert, wenn die Slippage Ihre Einstellung für die maximale Slippage überschreitet." - }, "swapAggregator": { "message": "Aggregator" }, @@ -3399,9 +3393,6 @@ "swapQuoteDetails": { "message": "Kursdetails" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Wenn sich der Kurs zwischen der Aufgabe und der Bestätigung Ihres Auftrags ändert, nennt man das \"Slippage\". Ihr Swap wird automatisch storniert, wenn die Slippage Ihre Einstellung für die \"Slippagetoleranz\" überschreitet." - }, "swapQuoteSource": { "message": "Kursquelle" }, @@ -3432,9 +3423,6 @@ "swapReviewSwap": { "message": "Swap überprüfen" }, - "swapSearchForAToken": { - "message": "Nach einem Token suchen" - }, "swapSelect": { "message": "Auswählen" }, @@ -3557,7 +3545,7 @@ "message": "Netzwerk wechseln" }, "switchToNetwork": { - "message": "Zu 1 $ wechseln", + "message": "Zu $1 wechseln", "description": "$1 represents the custom network that has previously been added" }, "switchToThisAccount": { @@ -4014,7 +4002,7 @@ "message": "Erkunden dezentraler Apps" }, "welcomeLoginDescription": { - "message": "Verwenden Sie Ihre MetaMaske, um sich bei dezentralen Apps anzumelden - keine Anmeldung erforderlich." + "message": "Verwenden Sie MetaMask, um sich bei dezentralen Apps anzumelden - keine Anmeldung erforderlich." }, "welcomeLoginTitle": { "message": "Sagen Sie Hallo zu Ihrer Wallet" @@ -4050,7 +4038,7 @@ "message": "Ja, versuchen wir es" }, "youHaveAddedAll": { - "message": "Sie haben alle beliebten Netzwerke hinzugefügt. Sie können weitere Netzwerke entdecken 1 $ oder Sie können 2 $", + "message": "Sie haben alle beliebten Netzwerke hinzugefügt. Sie können weitere Netzwerke entdecken $1 oder Sie können $2", "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'" }, "youNeedToAllowCameraAccess": { diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index e844d02b3..73f6abd7b 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Πολύγωνο" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "Ανταλλαγή" }, - "swapAdvancedSlippageInfo": { - "message": "Εάν η τιμή αλλάζει μεταξύ της ώρας που τοποθετείται η παραγγελία σας και της επιβεβαίωσης, αυτό ονομάζεται \"ολίσθηση\". Η ανταλλαγή σας θα ακυρωθεί αυτόματα αν η ολίσθηση υπερβαίνει τη ρύθμιση \"ανοχή ολίσθησης\"." - }, "swapAggregator": { "message": "Aggregator Ανταλλακτηρίων" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "Λεπτομέρειες προσφοράς" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Εάν η τιμή αλλάζει μεταξύ της ώρας που τοποθετείται η παραγγελία σας και επιβεβαιώνεται ονομάζεται \"ολίσθηση\". Η ανταλλαγή σας θα ακυρωθεί αυτόματα αν η ολίσθηση υπερβαίνει τη ρύθμιση \"ανοχή ολίσθησης\"." - }, "swapQuoteSource": { "message": "Πηγή προσφοράς" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "Επανεξέταση Ανταλλαγής" }, - "swapSearchForAToken": { - "message": "Αναζήτηση για ένα token" - }, "swapSelect": { "message": "Επιλογή" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f69cebb57..b8d64b675 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -609,7 +609,14 @@ "message": "Confirm" }, "confirmPageDialogSetApprovalForAll": { - "message": "By clicking approve, you are granting access to all of the NFTs you currently own on this contract and any NFTs on this contract you may acquire in the future until you revoke this approval. The party to whom you're granting approval will be able to transfer your NFTs from your wallet without further notice. Proceed with caution." + "message": "You're granting access to $1, including any you might own in the future. The party on the other end can transfer NFTs from your wallet at any time without asking you until you revoke this approval. $2", + "description": "$1 and $2 are bolded translations 'confirmPageDialogSetApprovalForAllPlaceholder1' and 'confirmPageDialogSetApprovalForAllPlaceholder2'" + }, + "confirmPageDialogSetApprovalForAllPlaceholder1": { + "message": "all the NFTs on this contract" + }, + "confirmPageDialogSetApprovalForAllPlaceholder2": { + "message": "Proceed with caution." }, "confirmPassword": { "message": "Confirm password" @@ -1528,7 +1535,7 @@ "message": "Get Ether" }, "getEtherFromFaucet": { - "message": "Get Ether from a faucet for the $1", + "message": "Get Ether from a faucet for the $1 network.", "description": "Displays network name for Ether faucet" }, "getStarted": { @@ -1695,6 +1702,9 @@ "initialTransactionConfirmed": { "message": "Your initial transaction was confirmed by the network. Click OK to go back." }, + "install": { + "message": "Install" + }, "insufficientBalance": { "message": "Insufficient balance." }, @@ -2141,12 +2151,12 @@ "networkNameEthereum": { "message": "Ethereum" }, + "networkNameGoerli": { + "message": "Goerli" + }, "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -2674,6 +2684,10 @@ "message": "Show notifications.", "description": "The description for the `snap_notify` permission" }, + "permission_transactionInsight": { + "message": "Fetch and display transaction insights.", + "description": "The description for the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Unknown permission: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -3229,6 +3243,10 @@ "message": "Added on $1 from $2", "description": "$1 represents the date the snap was installed, $2 represents which origin installed the snap." }, + "snapContent": { + "message": "This content is coming from $1", + "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." + }, "snapError": { "message": "Snap Error: '$1'. Error Code: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3259,6 +3277,12 @@ "snaps": { "message": "Snaps" }, + "snapsInsightLoading": { + "message": "Loading transaction insight..." + }, + "snapsNoInsight": { + "message": "The snap didn't return any insight" + }, "snapsSettingsDescription": { "message": "Manage your Snaps" }, @@ -3510,9 +3534,6 @@ "swap": { "message": "Swap" }, - "swapAdvancedSlippageInfo": { - "message": "If the price changes between the time your order is placed and confirmed it’s called “slippage”. Your swap will automatically cancel if slippage exceeds your “max slippage” setting." - }, "swapAggregator": { "message": "Aggregator" }, @@ -3676,9 +3697,6 @@ "swapQuoteDetails": { "message": "Quote details" }, - "swapQuoteDetailsSlippageInfo": { - "message": "If the price changes between the time your order is placed and confirmed it’s called \"slippage\". Your Swap will automatically cancel if slippage exceeds your \"slippage tolerance\" setting." - }, "swapQuoteSource": { "message": "Quote source" }, @@ -3709,8 +3727,8 @@ "swapReviewSwap": { "message": "Review swap" }, - "swapSearchForAToken": { - "message": "Search for a token" + "swapSearchNameOrAddress": { + "message": "Search name or paste address" }, "swapSelect": { "message": "Select" @@ -3731,6 +3749,9 @@ "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooltip": { + "message": "If the price changes between the time your order is placed and confirmed it’s called “slippage”. Your swap will automatically cancel if slippage exceeds your “slippage tolerance” setting." + }, "swapSource": { "message": "Liquidity source" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 541bbf579..0516a55f5 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Red de prueba" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "Canjear" }, - "swapAdvancedSlippageInfo": { - "message": "Si el precio cambia entre el momento en que hace el pedido y cuando se confirma, se denomina “desfase”. El canje se cancelará automáticamente si el desfase supera lo establecido en la configuración “max slippage” (desfase máximo)." - }, "swapAggregator": { "message": "Agregador" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "Detalles de cotización" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Si el precio cambia entre el momento en que hace el pedido y cuando se confirma, se denomina \"desfase\". El canje se cancelará automáticamente si el desfase supera lo establecido en la configuración \"tolerancia de desfase\"." - }, "swapQuoteSource": { "message": "Fuente de la cotización" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "Revisar canje" }, - "swapSearchForAToken": { - "message": "Buscar un token" - }, "swapSelect": { "message": "Seleccionar" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index e3a617fbe..9aca362ed 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -1703,9 +1703,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Red de prueba" }, @@ -2615,9 +2612,6 @@ "swap": { "message": "Canjear" }, - "swapAdvancedSlippageInfo": { - "message": "Si el precio cambia entre el momento en que hace el pedido y cuando se confirma, se denomina “desfase”. El canje se cancelará automáticamente si el desfase supera lo establecido en la configuración “max slippage” (desfase máximo)." - }, "swapAggregator": { "message": "Agregador" }, @@ -2773,9 +2767,6 @@ "swapQuoteDetails": { "message": "Detalles de cotización" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Si el precio cambia entre el momento en que hace el pedido y cuando se confirma, se denomina \"desfase\". El canje se cancelará automáticamente si el desfase supera lo establecido en la configuración de la \"tolerancia de desfase\"." - }, "swapQuoteSource": { "message": "Fuente de la cotización" }, @@ -2806,9 +2797,6 @@ "swapReviewSwap": { "message": "Revisar canje" }, - "swapSearchForAToken": { - "message": "Buscar un token" - }, "swapSelect": { "message": "Seleccionar" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 43c53f4d7..5844ab461 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -360,7 +360,7 @@ "message": "MetaMask est un portefeuille sécurisé utilisé par des millions de personnes qui rend l’univers du web3 accessible à toutes et à tous." }, "betaMetamaskDescriptionExplanation": { - "message": "Utilisez cette version pour tester les futures fonctionnalités avant leur lancement. Votre utilisation et vos commentaires nous aident à développer la meilleure version possible de MetaMask. Votre utilisation de MetaMask Beta est conditionnée par notre norme $1, ainsi que par notre $2. Il s’agit de la version bêta, présentant un risque de bogues plus élevé. En continuant, vous acceptez et reconnaissez ce risque, ainsi que ceux mentionnés dans nos Conditions d’utilisation et nos Conditions bêta.", + "message": "Utilisez cette version pour tester les futures fonctionnalités avant leur lancement. Votre utilisation et vos commentaires nous aident à développer la meilleure version possible de MetaMask. Votre utilisation de la version bêta de MetaMask est conditionnée par notre norme $1, ainsi que par notre $2. Il s’agit de la version bêta, présentant un risque de bogues plus élevé. En continuant, vous acceptez et reconnaissez ce risque, ainsi que ceux mentionnés dans nos Conditions d’utilisation et nos Conditions bêta.", "description": "$1 represents localization item betaMetamaskDescriptionExplanationTermsLinkText. $2 represents localization item betaMetamaskDescriptionExplanationBetaTermsLinkText" }, "betaMetamaskDescriptionExplanationBetaTermsLinkText": { @@ -370,10 +370,10 @@ "message": "Conditions d’utilisation" }, "betaMetamaskVersion": { - "message": "Version MetaMask Beta" + "message": "MetaMask version bêta" }, "betaWelcome": { - "message": "Bienvenue sur MetaMask Bêta" + "message": "Bienvenue sur MetaMask Beta" }, "blockExplorerAccountAction": { "message": "Compte", @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -2637,7 +2634,7 @@ "message": "Supprimer" }, "removeAccount": { - "message": "Suprimer le compte" + "message": "Supprimer le compte" }, "removeAccountDescription": { "message": "Ce compte va être supprimé de votre portefeuille. Veuillez vérifier que vous avez la phrase secrète de récupération originale de ce compte ou la clé privée pour ce compte importé avant de continuer. Vous pouvez importer ou créer à nouveau des comptes à partir du menu des comptes. " @@ -3289,9 +3286,6 @@ "swap": { "message": "Swap" }, - "swapAdvancedSlippageInfo": { - "message": "Si le prix fluctue entre le passage de votre ordre et sa confirmation, on parle alors d’un « effet de glissement » (slippage). Votre swap sera automatiquement annulé si ce phénomène dépasse votre paramètre de « glissement maximal »." - }, "swapAggregator": { "message": "Agrégateur" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "Détails de la cotation" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Si le prix fluctue entre le passage de votre ordre et sa confirmation, on parle alors d’un « effet de glissement » (slippage). Votre swap sera automatiquement annulé si ce phénomène dépasse votre paramètre de « tolérance de glissement »." - }, "swapQuoteSource": { "message": "Origine de la cotation" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "Vérifier le swap" }, - "swapSearchForAToken": { - "message": "Rechercher un jeton" - }, "swapSelect": { "message": "Sélectionner" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index dd68eaaf1..7124213a5 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "बहुभुज" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "स्वैप करें" }, - "swapAdvancedSlippageInfo": { - "message": "यदि आपके ऑर्डर किए जाने और पुष्टि किए जाने के समय के बीच मूल्य में परिवर्तन होता है, तो इसे “स्लिपेज” कहा जाता है। यदि आपका स्लिपेज आपकी “अधिकतम स्लिपेज” सेटिंग से अधिक हो जाता है, तो आपका स्वैप स्वतः रद्द हो जाएगा।" - }, "swapAggregator": { "message": "एग्रीगेटर" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "उद्धरण का विवरण" }, - "swapQuoteDetailsSlippageInfo": { - "message": "यदि आपके ऑर्डर किए जाने और पुष्टि किए जाने के समय के बीच मूल्य में परिवर्तन होता है, तो इसे \"स्लिपेज\" कहा जाता है। यदि स्लिपेज आपकी \"स्लिपेज टॉलरेंस\" सेटिंग से अधिक हो जाता है, तो आपका स्वैप स्वतः रद्द हो जाएगा।" - }, "swapQuoteSource": { "message": "उद्धरण का स्रोत" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "स्वैप की समीक्षा करें" }, - "swapSearchForAToken": { - "message": "एक टोकन की खोज करें" - }, "swapSelect": { "message": "चयन करें" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 8063b53d6..9c4eafd55 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "Pertukaran" }, - "swapAdvancedSlippageInfo": { - "message": "Jika harga berubah antara waktu penempatan dan konfirmasi order Anda, ini disebut “slippage”. Swap akan otomatis dibatalkan jika slippage melebihi pengaturan “slippage maks”." - }, "swapAggregator": { "message": "Agregator" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "Detail kuotasi" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Jika harga berubah antara waktu penempatan dan konfirmasi order Anda, ini disebut \"slippage\". Swap Anda akan otomatis dibatalkan jika slippage melebihi pengaturan \"toleransi slippage\"." - }, "swapQuoteSource": { "message": "Sumber kuotasi" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "Tinjau Swap" }, - "swapSearchForAToken": { - "message": "Cari token" - }, "swapSelect": { "message": "Pilih" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index c352034da..048ba414e 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -1355,9 +1355,6 @@ "swap": { "message": "Scambia" }, - "swapAdvancedSlippageInfo": { - "message": "Si chiama “slippage” la differenza tra il prezzo quando il tuo ordine viene inserito e quando viene confermato. Lo scambio sarà annullato automaticamente se lo slippage supera il “massimo slippage” impostato." - }, "swapAggregator": { "message": "Aggregatore" }, @@ -1445,9 +1442,6 @@ "swapQuoteDetails": { "message": "Dettagli quotazione" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Si chiama \"slippage\" la differenza tra il prezzo quando il tuo ordine viene inserito e quando viene confermato. Lo scambio sarà annullato automaticamente se lo slippage supera il \"massimo slippage\" impostato." - }, "swapQuoteSource": { "message": "Sorgente della quota" }, @@ -1478,9 +1472,6 @@ "swapReviewSwap": { "message": "Verifica Scambio" }, - "swapSearchForAToken": { - "message": "Cerca un token" - }, "swapSelect": { "message": "Selezione" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 0b9a735fb..3bc7420c8 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "テストネット" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "スワップ" }, - "swapAdvancedSlippageInfo": { - "message": "注文した時点と注文が承認された時点で価格が変わることを 「スリッページ」 と呼びます。スリッページが「最大スリッページ」設定を超える場合、スワップは自動的にキャンセルされます。" - }, "swapAggregator": { "message": "アグリゲーター" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "見積もりの詳細" }, - "swapQuoteDetailsSlippageInfo": { - "message": "注文した時点と注文が承認された時点で価格が変わることを「スリッページ」と呼びます。スリッページが「最大スリッページ」設定を超える場合、スワップは自動的にキャンセルされます。" - }, "swapQuoteSource": { "message": "見積もりのソース" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "スワップの確認" }, - "swapSearchForAToken": { - "message": "トークンの検索" - }, "swapSelect": { "message": "選択" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index f4d108491..cabef3736 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "테스트넷" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "스왑" }, - "swapAdvancedSlippageInfo": { - "message": "주문 시점과 확인 시점 사이에 가격이 변동되는 현상을 '슬리패지'라고 합니다. 슬리패지가 '최대 슬리패지' 설정을 초과하면 스왑이 자동으로 취소됩니다." - }, "swapAggregator": { "message": "애그리게이터" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "견적 세부 정보" }, - "swapQuoteDetailsSlippageInfo": { - "message": "주문 시점과 확인 시점 사이에 가격이 변동되는 현상을 \"슬리패지\"라고 합니다. 슬리패지가 \"최대 슬리패지\" 설정을 초과하면 스왑이 자동으로 취소됩니다." - }, "swapQuoteSource": { "message": "견적 소스" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "스왑 검토" }, - "swapSearchForAToken": { - "message": "토큰 검색" - }, "swapSelect": { "message": "선택" }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index af0a3c7a6..bb889e262 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -1707,9 +1707,6 @@ "swap": { "message": "I-swap" }, - "swapAdvancedSlippageInfo": { - "message": "Kung magbabago ang presyo sa pagitan ng oras ng pag-order mo at sa oras na nakumpirma ito, tinatawag itong “slippage.” Awtomatikong makakansela ang iyong pag-swap kung lalampas ang slippage sa iyong setting na “max slippage”." - }, "swapAggregator": { "message": "Aggregator" }, @@ -1833,9 +1830,6 @@ "swapQuoteDetails": { "message": "Mga detalye ng quote" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Kung magbabago ang presyo sa pagitan ng oras ng pag-order mo at sa oras na nakumpirma ito, tinatawag itong \"slippage\". Awtomatikong makakansela ang iyong Pag-swap kung lalampas ang slippage sa iyong setting na \"tolerance ng slippage.\"" - }, "swapQuoteSource": { "message": "Pinagkunan ng quote" }, @@ -1866,9 +1860,6 @@ "swapReviewSwap": { "message": "Suriin ang Pag-swap" }, - "swapSearchForAToken": { - "message": "Maghanap ng token" - }, "swapSelect": { "message": "Piliin" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 4e261ec78..81a04a144 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "Swap" }, - "swapAdvancedSlippageInfo": { - "message": "Se o preço varia entre o momento em que a sua ordem é efetuada e o momento em que é confirmada, isso recebe o nome de \"slippage\". Sua troca será automaticamente cancelada se o slippage exceder a sua configuração de \"slippage máximo\"." - }, "swapAggregator": { "message": "Agregador" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "Detalhes da cotação" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Se o preço varia entre o momento em que a sua ordem é efetuada e o momento em que é confirmada, isso recebe o nome de \"slippage\". Sua troca será automaticamente cancelada se o slippage for superior à configuração de \"tolerância a slippage\"." - }, "swapQuoteSource": { "message": "Fonte da cotação" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "Revisar troca" }, - "swapSearchForAToken": { - "message": "Pesquisar um token" - }, "swapSelect": { "message": "Selecione" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 5b2c10459..36ab85551 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -1687,9 +1687,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -2599,9 +2596,6 @@ "swap": { "message": "Trocar" }, - "swapAdvancedSlippageInfo": { - "message": "Se o preço varia entre o momento em que a sua ordem é efetuada e o momento em que é confirmada, isso recebe o nome de \"slippage\". Sua troca será automaticamente cancelada se o slippage exceder a sua configuração de \"slippage máximo\"." - }, "swapAggregator": { "message": "Agregador" }, @@ -2757,9 +2751,6 @@ "swapQuoteDetails": { "message": "Detalhes da cotação" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Se o preço varia entre o momento em que a sua ordem é efetuada e o momento em que é confirmada, isso recebe o nome de \"slippage\". Sua troca será automaticamente cancelada se o slippage for superior à configuração de \"tolerância a slippage\"." - }, "swapQuoteSource": { "message": "Fonte da cotação" }, @@ -2790,9 +2781,6 @@ "swapReviewSwap": { "message": "Revisar troca" }, - "swapSearchForAToken": { - "message": "Buscar um token" - }, "swapSelect": { "message": "Selecione" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index a4d0891ab..7066fa236 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Тестовая сеть" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "Обмен" }, - "swapAdvancedSlippageInfo": { - "message": "Изменение цены в период между размещением заказа и подтверждением называется проскальзыванием. Ваш обмен будет автоматически отменен, если проскальзывание превысит вашу настройку «максимального проскальзывания»." - }, "swapAggregator": { "message": "Агрегатор" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "Свдения о котировке" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Изменение цены в период между размещением заказа и подтверждением называется проскальзыванием. Обмен будет автоматически отменен, если фактическое проскальзывание превысит установленный «допуск проскальзывания»." - }, "swapQuoteSource": { "message": "Источник котировки" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "Проверить обмен" }, - "swapSearchForAToken": { - "message": "Поиск токена" - }, "swapSelect": { "message": "Выбрать" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 04c1f8983..bafd99635 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "I-swap" }, - "swapAdvancedSlippageInfo": { - "message": "Kung magbabago ang presyo sa pagitan ng oras ng pag-order mo at sa oras na nakumpirma ito, tinatawag itong “slippage”. Awtomatikong makakansela ang iyong pag-swap kung lalampas ang slippage sa iyong setting na “max slippage”." - }, "swapAggregator": { "message": "Aggregator" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "Mga detalye ng quote" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Kung magbabago ang presyo sa pagitan ng oras ng pag-order mo at sa oras na nakumpirma ito, tinatawag itong \"slippage\". Awtomatikong makakansela ang iyong Pag-swap kung lalampas ang slippage sa iyong setting na \"max slippage\"." - }, "swapQuoteSource": { "message": "Pinagkunan ng quote" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "I-review ang Pag-swap" }, - "swapSearchForAToken": { - "message": "Maghanap ng token" - }, "swapSelect": { "message": "Piliin" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index fa30acbdd..bf08426a3 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "Takas" }, - "swapAdvancedSlippageInfo": { - "message": "Emrinizin verildiği ve onaylandığı zamanlar arasında fiyat farkı oluşursa buna \"fark\" denir. Fark, \"maks. fark\" ayarınızı aşarsa takas işleminiz otomatik olarak iptal edilir." - }, "swapAggregator": { "message": "Toplayıcı" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "Teklif ayrıntıları" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Emrinizin verildiği ve onaylandığı zamanlar arasında fiyat farkı oluşursa buna \"fark\" denir. Fark, \"fark toleransı\" ayarınızı aşarsa Takas işleminiz otomatik olarak iptal edilir." - }, "swapQuoteSource": { "message": "Teklif kaynağı" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "Takası İncele" }, - "swapSearchForAToken": { - "message": "Bir token ara" - }, "swapSelect": { "message": "Seç" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 9adab7642..11a6f8dc9 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -2001,9 +2001,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Mạng thử nghiệm" }, @@ -3289,9 +3286,6 @@ "swap": { "message": "Hoán đổi" }, - "swapAdvancedSlippageInfo": { - "message": "Khi giá giữa thời điểm đặt lệnh và thời điểm xác nhận lệnh thay đổi, hiện tượng này được gọi là “trượt giá”. Giao dịch hoán đổi của bạn sẽ tự động hủy nếu mức trượt giá vượt quá “mức trượt giá tối đa” đã cài đặt." - }, "swapAggregator": { "message": "Trình tổng hợp" }, @@ -3455,9 +3449,6 @@ "swapQuoteDetails": { "message": "Chi tiết báo giá" }, - "swapQuoteDetailsSlippageInfo": { - "message": "Khi giá giữa thời điểm đặt lệnh và thời điểm xác nhận lệnh thay đổi, hiện tượng này được gọi là \"trượt giá\". Giao dịch hoán đổi của bạn sẽ tự động hủy nếu mức trượt giá vượt quá \"mức trượt giá cho phép\" đã đặt." - }, "swapQuoteSource": { "message": "Nguồn báo giá" }, @@ -3488,9 +3479,6 @@ "swapReviewSwap": { "message": "Xem lại giao dịch hoán đổi" }, - "swapSearchForAToken": { - "message": "Tìm kiếm token" - }, "swapSelect": { "message": "Chọn" }, diff --git a/app/_locales/zh/messages.json b/app/_locales/zh/messages.json index 37d0db906..70610a5bc 100644 --- a/app/_locales/zh/messages.json +++ b/app/_locales/zh/messages.json @@ -2004,9 +2004,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -3299,9 +3296,6 @@ "swap": { "message": "交换" }, - "swapAdvancedSlippageInfo": { - "message": "如果在您下订单和确认订单之间价格发生了变化,这就叫做“滑点”。如果滑点超过您的“最大滑点”设置,您的交换将自动取消。" - }, "swapAggregator": { "message": "聚合器" }, @@ -3465,9 +3459,6 @@ "swapQuoteDetails": { "message": "报价详情" }, - "swapQuoteDetailsSlippageInfo": { - "message": "如果在您下订单和确认订单之间价格发生了变化,这就叫做“滑点”。如果滑点超过您的“最大滑点”设置,您的交换将自动取消。" - }, "swapQuoteSource": { "message": "报价来源" }, @@ -3498,9 +3489,6 @@ "swapReviewSwap": { "message": "审查交换" }, - "swapSearchForAToken": { - "message": "搜索代币" - }, "swapSelect": { "message": "选择" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 147485e81..4058548aa 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -1654,9 +1654,6 @@ "networkNamePolygon": { "message": "Polygon" }, - "networkNameRinkeby": { - "message": "Rinkeby" - }, "networkNameTestnet": { "message": "Testnet" }, @@ -2563,9 +2560,6 @@ "swap": { "message": "兑换 Swap" }, - "swapAdvancedSlippageInfo": { - "message": "如果价格在您下单和确认之间发生变化,这就叫做“滑点”。如果滑点超过您的“最大滑点”设置,您的的兑换将自动取消。" - }, "swapAggregator": { "message": "聚合商" }, @@ -2721,9 +2715,6 @@ "swapQuoteDetails": { "message": "报价详情" }, - "swapQuoteDetailsSlippageInfo": { - "message": "如果在您下订单和确认订单之间的价格发生了变化,这就叫做\"滑点\"。如果滑点超过您的\"最大滑点\"设置,您的兑换将自动取消。" - }, "swapQuoteSource": { "message": "报价来源" }, @@ -2754,9 +2745,6 @@ "swapReviewSwap": { "message": "审查交换" }, - "swapSearchForAToken": { - "message": "搜索代币" - }, "swapSelect": { "message": "选择" }, diff --git a/app/images/icons/icon-add-outline.svg b/app/images/icons/icon-add-outline.svg new file mode 100644 index 000000000..74baee2f9 --- /dev/null +++ b/app/images/icons/icon-add-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-add-square-filled.svg b/app/images/icons/icon-add-square-filled.svg new file mode 100644 index 000000000..a4e9dea81 --- /dev/null +++ b/app/images/icons/icon-add-square-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-arrow-2-down.svg b/app/images/icons/icon-arrow-2-down.svg new file mode 100644 index 000000000..32c6cfca3 --- /dev/null +++ b/app/images/icons/icon-arrow-2-down.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-arrow-2-left.svg b/app/images/icons/icon-arrow-2-left.svg new file mode 100644 index 000000000..7baada3cb --- /dev/null +++ b/app/images/icons/icon-arrow-2-left.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-arrow-2-right.svg b/app/images/icons/icon-arrow-2-right.svg new file mode 100644 index 000000000..d36eb39e1 --- /dev/null +++ b/app/images/icons/icon-arrow-2-right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-arrow-2-up.svg b/app/images/icons/icon-arrow-2-up.svg new file mode 100644 index 000000000..255d2a419 --- /dev/null +++ b/app/images/icons/icon-arrow-2-up.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-arrow-down.svg b/app/images/icons/icon-arrow-down.svg new file mode 100644 index 000000000..c69193890 --- /dev/null +++ b/app/images/icons/icon-arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-arrow-left.svg b/app/images/icons/icon-arrow-left.svg new file mode 100644 index 000000000..4c27d4c94 --- /dev/null +++ b/app/images/icons/icon-arrow-left.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-arrow-right.svg b/app/images/icons/icon-arrow-right.svg new file mode 100644 index 000000000..35b692706 --- /dev/null +++ b/app/images/icons/icon-arrow-right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-arrow-up.svg b/app/images/icons/icon-arrow-up.svg new file mode 100644 index 000000000..eadbd3f45 --- /dev/null +++ b/app/images/icons/icon-arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-bank-filled.svg b/app/images/icons/icon-bank-filled.svg new file mode 100644 index 000000000..b76666c44 --- /dev/null +++ b/app/images/icons/icon-bank-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-bank-token-filled.svg b/app/images/icons/icon-bank-token-filled.svg new file mode 100644 index 000000000..99fd288cd --- /dev/null +++ b/app/images/icons/icon-bank-token-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-book-filled.svg b/app/images/icons/icon-book-filled.svg new file mode 100644 index 000000000..6e7386358 --- /dev/null +++ b/app/images/icons/icon-book-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-bookmark-filled.svg b/app/images/icons/icon-bookmark-filled.svg new file mode 100644 index 000000000..494baea6e --- /dev/null +++ b/app/images/icons/icon-bookmark-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-calculator-filled.svg b/app/images/icons/icon-calculator-filled.svg new file mode 100644 index 000000000..064209e03 --- /dev/null +++ b/app/images/icons/icon-calculator-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-card-filled.svg b/app/images/icons/icon-card-filled.svg new file mode 100644 index 000000000..2e20fa22c --- /dev/null +++ b/app/images/icons/icon-card-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-card-pos-filled.svg b/app/images/icons/icon-card-pos-filled.svg new file mode 100644 index 000000000..f0e0efaf0 --- /dev/null +++ b/app/images/icons/icon-card-pos-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-card-token-filled.svg b/app/images/icons/icon-card-token-filled.svg new file mode 100644 index 000000000..3a270944a --- /dev/null +++ b/app/images/icons/icon-card-token-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-category-filled.svg b/app/images/icons/icon-category-filled.svg new file mode 100644 index 000000000..8100a230f --- /dev/null +++ b/app/images/icons/icon-category-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-chart-filled.svg b/app/images/icons/icon-chart-filled.svg new file mode 100644 index 000000000..02c0f0c5d --- /dev/null +++ b/app/images/icons/icon-chart-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-check-box-off-outline.svg b/app/images/icons/icon-check-box-off-outline.svg new file mode 100644 index 000000000..629413aa4 --- /dev/null +++ b/app/images/icons/icon-check-box-off-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-check-box-on-filled.svg b/app/images/icons/icon-check-box-on-filled.svg new file mode 100644 index 000000000..40a7000cf --- /dev/null +++ b/app/images/icons/icon-check-box-on-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-check-circle-on-filled.svg b/app/images/icons/icon-check-circle-on-filled.svg new file mode 100644 index 000000000..7fce9feb8 --- /dev/null +++ b/app/images/icons/icon-check-circle-on-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-check-outline.svg b/app/images/icons/icon-check-outline.svg new file mode 100644 index 000000000..ba6edb151 --- /dev/null +++ b/app/images/icons/icon-check-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-clock-filled.svg b/app/images/icons/icon-clock-filled.svg new file mode 100644 index 000000000..797f78b63 --- /dev/null +++ b/app/images/icons/icon-clock-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-close-outline.svg b/app/images/icons/icon-close-outline.svg new file mode 100644 index 000000000..7bb91220b --- /dev/null +++ b/app/images/icons/icon-close-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-code-circle-filled.svg b/app/images/icons/icon-code-circle-filled.svg new file mode 100644 index 000000000..a9c166524 --- /dev/null +++ b/app/images/icons/icon-code-circle-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-coin-filled.svg b/app/images/icons/icon-coin-filled.svg new file mode 100644 index 000000000..994d75d02 --- /dev/null +++ b/app/images/icons/icon-coin-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-confirmation-filled.svg b/app/images/icons/icon-confirmation-filled.svg new file mode 100644 index 000000000..14d1a0a64 --- /dev/null +++ b/app/images/icons/icon-confirmation-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-connect-filled.svg b/app/images/icons/icon-connect-filled.svg new file mode 100644 index 000000000..b168a9bf9 --- /dev/null +++ b/app/images/icons/icon-connect-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-copy-filled.svg b/app/images/icons/icon-copy-filled.svg new file mode 100644 index 000000000..165a3821c --- /dev/null +++ b/app/images/icons/icon-copy-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-copy-success-filled.svg b/app/images/icons/icon-copy-success-filled.svg new file mode 100644 index 000000000..3e66bf870 --- /dev/null +++ b/app/images/icons/icon-copy-success-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-danger-filled.svg b/app/images/icons/icon-danger-filled.svg new file mode 100644 index 000000000..1c48c60e7 --- /dev/null +++ b/app/images/icons/icon-danger-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-dark-filled.svg b/app/images/icons/icon-dark-filled.svg new file mode 100644 index 000000000..9efd0e198 --- /dev/null +++ b/app/images/icons/icon-dark-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-data-filled.svg b/app/images/icons/icon-data-filled.svg new file mode 100644 index 000000000..1c8981364 --- /dev/null +++ b/app/images/icons/icon-data-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-diagram.svg b/app/images/icons/icon-diagram.svg new file mode 100644 index 000000000..beb5424ad --- /dev/null +++ b/app/images/icons/icon-diagram.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-document-code-filled.svg b/app/images/icons/icon-document-code-filled.svg new file mode 100644 index 000000000..e75e62899 --- /dev/null +++ b/app/images/icons/icon-document-code-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-edit-filled.svg b/app/images/icons/icon-edit-filled.svg new file mode 100644 index 000000000..2ac4730f9 --- /dev/null +++ b/app/images/icons/icon-edit-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-eraser-filled.svg b/app/images/icons/icon-eraser-filled.svg new file mode 100644 index 000000000..09d97823b --- /dev/null +++ b/app/images/icons/icon-eraser-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-expand-outline.svg b/app/images/icons/icon-expand-outline.svg new file mode 100644 index 000000000..04f08ed22 --- /dev/null +++ b/app/images/icons/icon-expand-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-explore-filled.svg b/app/images/icons/icon-explore-filled.svg new file mode 100644 index 000000000..1411dca10 --- /dev/null +++ b/app/images/icons/icon-explore-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-export.svg b/app/images/icons/icon-export.svg new file mode 100644 index 000000000..9782c1433 --- /dev/null +++ b/app/images/icons/icon-export.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-eye-filled.svg b/app/images/icons/icon-eye-filled.svg new file mode 100644 index 000000000..ee03c8c97 --- /dev/null +++ b/app/images/icons/icon-eye-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-eye-slash-filled.svg b/app/images/icons/icon-eye-slash-filled.svg new file mode 100644 index 000000000..438c00e87 --- /dev/null +++ b/app/images/icons/icon-eye-slash-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-filter-outline.svg b/app/images/icons/icon-filter-outline.svg new file mode 100644 index 000000000..066ace471 --- /dev/null +++ b/app/images/icons/icon-filter-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-flag-filled.svg b/app/images/icons/icon-flag-filled.svg new file mode 100644 index 000000000..90a01f496 --- /dev/null +++ b/app/images/icons/icon-flag-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-flash-filled.svg b/app/images/icons/icon-flash-filled.svg new file mode 100644 index 000000000..9d9456505 --- /dev/null +++ b/app/images/icons/icon-flash-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-flash-slash-filled.svg b/app/images/icons/icon-flash-slash-filled.svg new file mode 100644 index 000000000..8992d1ea9 --- /dev/null +++ b/app/images/icons/icon-flash-slash-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-full-circle-filled.svg b/app/images/icons/icon-full-circle-filled.svg new file mode 100644 index 000000000..c99354607 --- /dev/null +++ b/app/images/icons/icon-full-circle-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-gas-filled.svg b/app/images/icons/icon-gas-filled.svg new file mode 100644 index 000000000..1d56ad926 --- /dev/null +++ b/app/images/icons/icon-gas-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-global-filled.svg b/app/images/icons/icon-global-filled.svg new file mode 100644 index 000000000..1396a3626 --- /dev/null +++ b/app/images/icons/icon-global-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-global-search-filled.svg b/app/images/icons/icon-global-search-filled.svg new file mode 100644 index 000000000..fa6d31b61 --- /dev/null +++ b/app/images/icons/icon-global-search-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-graph-filled.svg b/app/images/icons/icon-graph-filled.svg new file mode 100644 index 000000000..d13f7b6b6 --- /dev/null +++ b/app/images/icons/icon-graph-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-heart-filled.svg b/app/images/icons/icon-heart-filled.svg new file mode 100644 index 000000000..326e81966 --- /dev/null +++ b/app/images/icons/icon-heart-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-hierarchy-filled.svg b/app/images/icons/icon-hierarchy-filled.svg new file mode 100644 index 000000000..3fe5af751 --- /dev/null +++ b/app/images/icons/icon-hierarchy-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-home-filled.svg b/app/images/icons/icon-home-filled.svg new file mode 100644 index 000000000..8f4daf83c --- /dev/null +++ b/app/images/icons/icon-home-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-import.svg b/app/images/icons/icon-import.svg new file mode 100644 index 000000000..2995ff1a7 --- /dev/null +++ b/app/images/icons/icon-import.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-info-filled.svg b/app/images/icons/icon-info-filled.svg new file mode 100644 index 000000000..3a8c9add0 --- /dev/null +++ b/app/images/icons/icon-info-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-key-filled.svg b/app/images/icons/icon-key-filled.svg new file mode 100644 index 000000000..4e7a6e9ce --- /dev/null +++ b/app/images/icons/icon-key-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-light-filled.svg b/app/images/icons/icon-light-filled.svg new file mode 100644 index 000000000..aae9ffb17 --- /dev/null +++ b/app/images/icons/icon-light-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-link-outline.svg b/app/images/icons/icon-link-outline.svg new file mode 100644 index 000000000..027977d20 --- /dev/null +++ b/app/images/icons/icon-link-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-loading-filled.svg b/app/images/icons/icon-loading-filled.svg new file mode 100644 index 000000000..8c66c5192 --- /dev/null +++ b/app/images/icons/icon-loading-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-lock-circle-filled.svg b/app/images/icons/icon-lock-circle-filled.svg new file mode 100644 index 000000000..90333666c --- /dev/null +++ b/app/images/icons/icon-lock-circle-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-lock-filled.svg b/app/images/icons/icon-lock-filled.svg new file mode 100644 index 000000000..9b1a3247a --- /dev/null +++ b/app/images/icons/icon-lock-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-lock-slash-filled.svg b/app/images/icons/icon-lock-slash-filled.svg new file mode 100644 index 000000000..9e21d6b6e --- /dev/null +++ b/app/images/icons/icon-lock-slash-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-login-outline.svg b/app/images/icons/icon-login-outline.svg new file mode 100644 index 000000000..378f0325e --- /dev/null +++ b/app/images/icons/icon-login-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-menu-outline.svg b/app/images/icons/icon-menu-outline.svg new file mode 100644 index 000000000..12005b088 --- /dev/null +++ b/app/images/icons/icon-menu-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-message-question-filled.svg b/app/images/icons/icon-message-question-filled.svg new file mode 100644 index 000000000..c32f1642a --- /dev/null +++ b/app/images/icons/icon-message-question-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-messages-filled.svg b/app/images/icons/icon-messages-filled.svg new file mode 100644 index 000000000..b8a2e820e --- /dev/null +++ b/app/images/icons/icon-messages-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-minus-outine.svg b/app/images/icons/icon-minus-outine.svg new file mode 100644 index 000000000..7688bbd7c --- /dev/null +++ b/app/images/icons/icon-minus-outine.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-minus-square-filled.svg b/app/images/icons/icon-minus-square-filled.svg new file mode 100644 index 000000000..40d8ba37d --- /dev/null +++ b/app/images/icons/icon-minus-square-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-mobile-filled.svg b/app/images/icons/icon-mobile-filled.svg new file mode 100644 index 000000000..bf0bd851f --- /dev/null +++ b/app/images/icons/icon-mobile-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-money-filled.svg b/app/images/icons/icon-money-filled.svg new file mode 100644 index 000000000..4b3fa8772 --- /dev/null +++ b/app/images/icons/icon-money-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-monitor-filled.svg b/app/images/icons/icon-monitor-filled.svg new file mode 100644 index 000000000..c7bf792f8 --- /dev/null +++ b/app/images/icons/icon-monitor-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-more-horizontal-outline.svg b/app/images/icons/icon-more-horizontal-outline.svg new file mode 100644 index 000000000..58eb2bc09 --- /dev/null +++ b/app/images/icons/icon-more-horizontal-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-more-vertical-outline.svg b/app/images/icons/icon-more-vertical-outline.svg new file mode 100644 index 000000000..4a867d550 --- /dev/null +++ b/app/images/icons/icon-more-vertical-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-notification-circle-filled.svg b/app/images/icons/icon-notification-circle-filled.svg new file mode 100644 index 000000000..c22cbffa7 --- /dev/null +++ b/app/images/icons/icon-notification-circle-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-notification-filled.svg b/app/images/icons/icon-notification-filled.svg new file mode 100644 index 000000000..8e2cbfa4f --- /dev/null +++ b/app/images/icons/icon-notification-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-password-check-filled.svg b/app/images/icons/icon-password-check-filled.svg new file mode 100644 index 000000000..01b532e7b --- /dev/null +++ b/app/images/icons/icon-password-check-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-people-filled.svg b/app/images/icons/icon-people-filled.svg new file mode 100644 index 000000000..5d353662f --- /dev/null +++ b/app/images/icons/icon-people-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-programming-arrows-filled.svg b/app/images/icons/icon-programming-arrows-filled.svg new file mode 100644 index 000000000..3da86b9ed --- /dev/null +++ b/app/images/icons/icon-programming-arrows-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-question-filled.svg b/app/images/icons/icon-question-filled.svg new file mode 100644 index 000000000..a47c9d7ee --- /dev/null +++ b/app/images/icons/icon-question-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-received-outline.svg b/app/images/icons/icon-received-outline.svg new file mode 100644 index 000000000..2b0b7d958 --- /dev/null +++ b/app/images/icons/icon-received-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-refresh.svg b/app/images/icons/icon-refresh.svg new file mode 100644 index 000000000..80585b1ea --- /dev/null +++ b/app/images/icons/icon-refresh.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-save.svg b/app/images/icons/icon-save.svg new file mode 100644 index 000000000..6dd6d94b2 --- /dev/null +++ b/app/images/icons/icon-save.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-scan-barcode-filled.svg b/app/images/icons/icon-scan-barcode-filled.svg new file mode 100644 index 000000000..c622b73f8 --- /dev/null +++ b/app/images/icons/icon-scan-barcode-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-scan-filled.svg b/app/images/icons/icon-scan-filled.svg new file mode 100644 index 000000000..9291a2f5e --- /dev/null +++ b/app/images/icons/icon-scan-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-scan-focus-outline.svg b/app/images/icons/icon-scan-focus-outline.svg new file mode 100644 index 000000000..1d9ba64de --- /dev/null +++ b/app/images/icons/icon-scan-focus-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-scroll-filled.svg b/app/images/icons/icon-scroll-filled.svg new file mode 100644 index 000000000..c8b0ad1f5 --- /dev/null +++ b/app/images/icons/icon-scroll-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-search-filled.svg b/app/images/icons/icon-search-filled.svg new file mode 100644 index 000000000..88b36fc08 --- /dev/null +++ b/app/images/icons/icon-search-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-security-card-filled.svg b/app/images/icons/icon-security-card-filled.svg new file mode 100644 index 000000000..daceaa640 --- /dev/null +++ b/app/images/icons/icon-security-card-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-security-cross-filled.svg b/app/images/icons/icon-security-cross-filled.svg new file mode 100644 index 000000000..89efa2d55 --- /dev/null +++ b/app/images/icons/icon-security-cross-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-security-key-filled.svg b/app/images/icons/icon-security-key-filled.svg new file mode 100644 index 000000000..6b38a6c04 --- /dev/null +++ b/app/images/icons/icon-security-key-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-security-search-filled.svg b/app/images/icons/icon-security-search-filled.svg new file mode 100644 index 000000000..e18f8cfcc --- /dev/null +++ b/app/images/icons/icon-security-search-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-security-slash-filled.svg b/app/images/icons/icon-security-slash-filled.svg new file mode 100644 index 000000000..4872ab7be --- /dev/null +++ b/app/images/icons/icon-security-slash-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-security-tick-filled.svg b/app/images/icons/icon-security-tick-filled.svg new file mode 100644 index 000000000..ef85785c2 --- /dev/null +++ b/app/images/icons/icon-security-tick-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-security-time-filled.svg b/app/images/icons/icon-security-time-filled.svg new file mode 100644 index 000000000..429e4ab75 --- /dev/null +++ b/app/images/icons/icon-security-time-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-security-user-filled.svg b/app/images/icons/icon-security-user-filled.svg new file mode 100644 index 000000000..8ef1ad213 --- /dev/null +++ b/app/images/icons/icon-security-user-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-send-1-filled.svg b/app/images/icons/icon-send-1-filled.svg new file mode 100644 index 000000000..db41be77f --- /dev/null +++ b/app/images/icons/icon-send-1-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-send-2-outline.svg b/app/images/icons/icon-send-2-outline.svg new file mode 100644 index 000000000..bfea4b2e1 --- /dev/null +++ b/app/images/icons/icon-send-2-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-setting-filled.svg b/app/images/icons/icon-setting-filled.svg new file mode 100644 index 000000000..c0c03dbed --- /dev/null +++ b/app/images/icons/icon-setting-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-slash-filled.svg b/app/images/icons/icon-slash-filled.svg new file mode 100644 index 000000000..30a11f325 --- /dev/null +++ b/app/images/icons/icon-slash-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-snaps-filled.svg b/app/images/icons/icon-snaps-filled.svg new file mode 100644 index 000000000..03ef5a1e3 --- /dev/null +++ b/app/images/icons/icon-snaps-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-snaps-mobile-filled.svg b/app/images/icons/icon-snaps-mobile-filled.svg new file mode 100644 index 000000000..49446ebd9 --- /dev/null +++ b/app/images/icons/icon-snaps-mobile-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-snaps-plus-filled.svg b/app/images/icons/icon-snaps-plus-filled.svg new file mode 100644 index 000000000..413e52fac --- /dev/null +++ b/app/images/icons/icon-snaps-plus-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-speedometer-filled.svg b/app/images/icons/icon-speedometer-filled.svg new file mode 100644 index 000000000..a14af2d6d --- /dev/null +++ b/app/images/icons/icon-speedometer-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-star.svg b/app/images/icons/icon-star.svg new file mode 100644 index 000000000..5e088fd8e --- /dev/null +++ b/app/images/icons/icon-star.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-student-filled.svg b/app/images/icons/icon-student-filled.svg new file mode 100644 index 000000000..fd7eb9168 --- /dev/null +++ b/app/images/icons/icon-student-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/icons/icon-swap-horizontal-outline.svg b/app/images/icons/icon-swap-horizontal-outline.svg new file mode 100644 index 000000000..61145d238 --- /dev/null +++ b/app/images/icons/icon-swap-horizontal-outline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-swap-vertical-filled.svg b/app/images/icons/icon-swap-vertical-filled.svg new file mode 100644 index 000000000..4c3836289 --- /dev/null +++ b/app/images/icons/icon-swap-vertical-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-tag-filled.svg b/app/images/icons/icon-tag-filled.svg new file mode 100644 index 000000000..47329195e --- /dev/null +++ b/app/images/icons/icon-tag-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-tilde.svg b/app/images/icons/icon-tilde.svg new file mode 100644 index 000000000..e199dd54c --- /dev/null +++ b/app/images/icons/icon-tilde.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-timer-filled.svg b/app/images/icons/icon-timer-filled.svg new file mode 100644 index 000000000..62dd112f0 --- /dev/null +++ b/app/images/icons/icon-timer-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-trash-filled.svg b/app/images/icons/icon-trash-filled.svg new file mode 100644 index 000000000..df8eb8aaf --- /dev/null +++ b/app/images/icons/icon-trash-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-trend-down-filled.svg b/app/images/icons/icon-trend-down-filled.svg new file mode 100644 index 000000000..edbfaff68 --- /dev/null +++ b/app/images/icons/icon-trend-down-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/images/icons/icon-trend-up-filled.svg b/app/images/icons/icon-trend-up-filled.svg new file mode 100644 index 000000000..695dc6771 --- /dev/null +++ b/app/images/icons/icon-trend-up-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-user-circle-add-filled.svg b/app/images/icons/icon-user-circle-add-filled.svg new file mode 100644 index 000000000..d73ea8ebf --- /dev/null +++ b/app/images/icons/icon-user-circle-add-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-user-circle-filled.svg b/app/images/icons/icon-user-circle-filled.svg new file mode 100644 index 000000000..611d25d08 --- /dev/null +++ b/app/images/icons/icon-user-circle-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-user-cirlce-add-filled.svg b/app/images/icons/icon-user-cirlce-add-filled.svg new file mode 100644 index 000000000..01ffc6c9c --- /dev/null +++ b/app/images/icons/icon-user-cirlce-add-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-user-filled.svg b/app/images/icons/icon-user-filled.svg new file mode 100644 index 000000000..981a05537 --- /dev/null +++ b/app/images/icons/icon-user-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-wallet-card-filled.svg b/app/images/icons/icon-wallet-card-filled.svg new file mode 100644 index 000000000..5ae651f1a --- /dev/null +++ b/app/images/icons/icon-wallet-card-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-wallet-filled.svg b/app/images/icons/icon-wallet-filled.svg new file mode 100644 index 000000000..bca151054 --- /dev/null +++ b/app/images/icons/icon-wallet-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-wallet-money-filled.svg b/app/images/icons/icon-wallet-money-filled.svg new file mode 100644 index 000000000..f834605c0 --- /dev/null +++ b/app/images/icons/icon-wallet-money-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/icons/icon-warning-filled.svg b/app/images/icons/icon-warning-filled.svg new file mode 100644 index 000000000..fea0948f9 --- /dev/null +++ b/app/images/icons/icon-warning-filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/manifest/v3/_base.json b/app/manifest/v3/_base.json index 7b28cd2ed..696a4d62e 100644 --- a/app/manifest/v3/_base.json +++ b/app/manifest/v3/_base.json @@ -47,7 +47,12 @@ ], "default_locale": "en", "description": "__MSG_appDescription__", - "host_permissions": ["file://*/*", "http://*/*", "https://*/*"], + "host_permissions": [ + "http://localhost:8545/", + "file://*/*", + "http://*/*", + "https://*/*" + ], "icons": { "16": "images/icon-16.png", "19": "images/icon-19.png", @@ -61,18 +66,13 @@ "manifest_version": 3, "name": "__MSG_appName__", "permissions": [ - "storage", - "scripting", - "unlimitedStorage", - "clipboardWrite", - "http://localhost:8545/", - "https://*.infura.io/", - "https://chainid.network/chains.json", - "https://lattice.gridplus.io/*", "activeTab", - "webRequest", - "*://*.eth/", - "notifications" + "clipboardWrite", + "notifications", + "scripting", + "storage", + "unlimitedStorage", + "webRequest" ], "short_name": "__MSG_appName__" } diff --git a/app/manifest/v3/chrome.json b/app/manifest/v3/chrome.json index 9e21aa058..45d7f699a 100644 --- a/app/manifest/v3/chrome.json +++ b/app/manifest/v3/chrome.json @@ -1,6 +1,6 @@ { "content_security_policy": { - "extension_pages": "default-src 'self'; frame-ancestors 'none'" + "extension_pages": "script-src 'self'; object-src 'self'; frame-ancestors 'none';" }, "externally_connectable": { "matches": ["https://metamask.io/*"], diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index a6fb3bcd1..1ae4167b3 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -36,19 +36,33 @@ const LEGACY_INPAGE = 'inpage'; const LEGACY_PROVIDER = 'provider'; const LEGACY_PUBLIC_CONFIG = 'publicConfig'; +let legacyExtMux, + legacyExtChannel, + legacyExtPublicConfigChannel, + legacyPageMux, + legacyPageMuxLegacyProviderChannel, + legacyPagePublicConfigChannel, + notificationTransformStream; + +const WORKER_KEEP_ALIVE_INTERVAL = 1000; +const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE'; + const phishingPageUrl = new URL(process.env.PHISHING_WARNING_PAGE_URL); -if ( - window.location.origin === phishingPageUrl.origin && - window.location.pathname === phishingPageUrl.pathname -) { - setupPhishingStream(); -} else if (shouldInjectProvider()) { - if (!isManifestV3) { - injectScript(inpageBundle); - } - setupStreams(); -} +let phishingExtChannel, + phishingExtMux, + phishingExtPort, + phishingExtStream, + phishingPageChannel, + phishingPageMux; + +let extensionMux, + extensionChannel, + extensionPort, + extensionPhishingStream, + extensionStream, + pageMux, + pageChannel; /** * Injects a script tag into the current document @@ -68,26 +82,41 @@ function injectScript(content) { } } -async function setupPhishingStream() { +/** + * PHISHING STREAM LOGIC + */ + +function setupPhishingPageStreams() { // the transport-specific streams for communication between inpage and background - const pageStream = new WindowPostMessageStream({ + const phishingPageStream = new WindowPostMessageStream({ name: CONTENT_SCRIPT, target: PHISHING_WARNING_PAGE, }); - const extensionPort = browser.runtime.connect({ name: CONTENT_SCRIPT }); - const extensionStream = new PortStream(extensionPort); // create and connect channel muxers // so we can handle the channels individually - const pageMux = new ObjectMultiplex(); - pageMux.setMaxListeners(25); - const extensionMux = new ObjectMultiplex(); - extensionMux.setMaxListeners(25); + phishingPageMux = new ObjectMultiplex(); + phishingPageMux.setMaxListeners(25); - pump(pageMux, pageStream, pageMux, (err) => + pump(phishingPageMux, phishingPageStream, phishingPageMux, (err) => logStreamDisconnectWarning('MetaMask Inpage Multiplex', err), ); - pump(extensionMux, extensionStream, extensionMux, (err) => { + + phishingPageChannel = phishingPageMux.createStream(PHISHING_SAFELIST); +} + +const setupPhishingExtStreams = () => { + phishingExtPort = browser.runtime.connect({ + name: CONTENT_SCRIPT, + }); + phishingExtStream = new PortStream(phishingExtPort); + + // create and connect channel muxers + // so we can handle the channels individually + phishingExtMux = new ObjectMultiplex(); + phishingExtMux.setMaxListeners(25); + + pump(phishingExtMux, phishingExtStream, phishingExtMux, (err) => { logStreamDisconnectWarning('MetaMask Background Multiplex', err); window.postMessage( { @@ -106,112 +135,230 @@ async function setupPhishingStream() { }); // forward communication across inpage-background for these channels only - forwardTrafficBetweenMuxes(PHISHING_SAFELIST, pageMux, extensionMux); -} + phishingExtChannel = phishingExtMux.createStream(PHISHING_SAFELIST); + pump(phishingPageChannel, phishingExtChannel, phishingPageChannel, (error) => + console.debug( + `MetaMask: Muxed traffic for channel "${PHISHING_SAFELIST}" failed.`, + error, + ), + ); +}; + +/** Destroys all of the phishing extension streams */ +const destroyPhishingExtStreams = () => { + phishingPageChannel.removeAllListeners(); + + phishingExtMux.removeAllListeners(); + phishingExtMux.destroy(); + + phishingExtChannel.removeAllListeners(); + phishingExtChannel.destroy(); +}; /** - * Sets up two-way communication streams between the - * browser extension and local per-page browser context. - * + * Resets the extension stream with new streams to channel with the phishing page streams, + * and creates a new event listener to the reestablished extension port. */ -async function setupStreams() { +const resetPhishingStreamAndListeners = () => { + phishingExtPort.onDisconnect.removeListener(resetPhishingStreamAndListeners); + + destroyPhishingExtStreams(); + setupPhishingExtStreams(); + + phishingExtPort.onDisconnect.addListener(resetPhishingStreamAndListeners); +}; + +/** + * Initializes two-way communication streams between the browser extension and + * the phishing page context. This function also creates an event listener to + * reset the streams if the service worker resets. + */ +const initPhishingStreams = () => { + setupPhishingPageStreams(); + setupPhishingExtStreams(); + + phishingExtPort.onDisconnect.addListener(resetPhishingStreamAndListeners); +}; + +/** + * INPAGE - EXTENSION STREAM LOGIC + */ + +const setupPageStreams = () => { // the transport-specific streams for communication between inpage and background const pageStream = new WindowPostMessageStream({ name: CONTENT_SCRIPT, target: INPAGE, }); - const extensionPort = browser.runtime.connect({ name: CONTENT_SCRIPT }); - const extensionStream = new PortStream(extensionPort); // create and connect channel muxers // so we can handle the channels individually - const pageMux = new ObjectMultiplex(); + pageMux = new ObjectMultiplex(); pageMux.setMaxListeners(25); - const extensionMux = new ObjectMultiplex(); - extensionMux.setMaxListeners(25); - extensionMux.ignoreStream(LEGACY_PUBLIC_CONFIG); // TODO:LegacyProvider: Delete pump(pageMux, pageStream, pageMux, (err) => logStreamDisconnectWarning('MetaMask Inpage Multiplex', err), ); + + pageChannel = pageMux.createStream(PROVIDER); +}; + +const setupExtensionStreams = () => { + extensionPort = browser.runtime.connect({ name: CONTENT_SCRIPT }); + extensionStream = new PortStream(extensionPort); + + // create and connect channel muxers + // so we can handle the channels individually + extensionMux = new ObjectMultiplex(); + extensionMux.setMaxListeners(25); + extensionMux.ignoreStream(LEGACY_PUBLIC_CONFIG); // TODO:LegacyProvider: Delete + pump(extensionMux, extensionStream, extensionMux, (err) => { logStreamDisconnectWarning('MetaMask Background Multiplex', err); notifyInpageOfStreamFailure(); }); // forward communication across inpage-background for these channels only - forwardTrafficBetweenMuxes(PROVIDER, pageMux, extensionMux); + extensionChannel = extensionMux.createStream(PROVIDER); + pump(pageChannel, extensionChannel, pageChannel, (error) => + console.debug( + `MetaMask: Muxed traffic for channel "${PROVIDER}" failed.`, + error, + ), + ); // connect "phishing" channel to warning system - const phishingStream = extensionMux.createStream('phishing'); - phishingStream.once('data', redirectToPhishingWarning); + extensionPhishingStream = extensionMux.createStream('phishing'); + extensionPhishingStream.once('data', redirectToPhishingWarning); +}; - // TODO:LegacyProvider: Delete - // handle legacy provider +/** Destroys all of the extension streams */ +const destroyExtensionStreams = () => { + pageChannel.removeAllListeners(); + + extensionMux.removeAllListeners(); + extensionMux.destroy(); + + extensionChannel.removeAllListeners(); + extensionChannel.destroy(); +}; + +/** + * LEGACY STREAM LOGIC + */ + +// TODO:LegacyProvider: Delete +const setupLegacyPageStreams = () => { const legacyPageStream = new WindowPostMessageStream({ name: LEGACY_CONTENT_SCRIPT, target: LEGACY_INPAGE, }); - const legacyPageMux = new ObjectMultiplex(); + legacyPageMux = new ObjectMultiplex(); legacyPageMux.setMaxListeners(25); - const legacyExtensionMux = new ObjectMultiplex(); - legacyExtensionMux.setMaxListeners(25); pump(legacyPageMux, legacyPageStream, legacyPageMux, (err) => logStreamDisconnectWarning('MetaMask Legacy Inpage Multiplex', err), ); + + legacyPageMuxLegacyProviderChannel = + legacyPageMux.createStream(LEGACY_PROVIDER); + legacyPagePublicConfigChannel = + legacyPageMux.createStream(LEGACY_PUBLIC_CONFIG); +}; + +// TODO:LegacyProvider: Delete +const setupLegacyExtensionStreams = () => { + legacyExtMux = new ObjectMultiplex(); + legacyExtMux.setMaxListeners(25); + + notificationTransformStream = getNotificationTransformStream(); pump( - legacyExtensionMux, + legacyExtMux, extensionStream, - getNotificationTransformStream(), - legacyExtensionMux, + notificationTransformStream, + legacyExtMux, (err) => { logStreamDisconnectWarning('MetaMask Background Legacy Multiplex', err); notifyInpageOfStreamFailure(); }, ); - forwardNamedTrafficBetweenMuxes( - LEGACY_PROVIDER, - PROVIDER, - legacyPageMux, - legacyExtensionMux, + legacyExtChannel = legacyExtMux.createStream(PROVIDER); + pump( + legacyPageMuxLegacyProviderChannel, + legacyExtChannel, + legacyPageMuxLegacyProviderChannel, + (error) => + console.debug( + `MetaMask: Muxed traffic between channels "${LEGACY_PROVIDER}" and "${PROVIDER}" failed.`, + error, + ), ); - forwardTrafficBetweenMuxes( - LEGACY_PUBLIC_CONFIG, - legacyPageMux, - legacyExtensionMux, - ); -} -function forwardTrafficBetweenMuxes(channelName, muxA, muxB) { - const channelA = muxA.createStream(channelName); - const channelB = muxB.createStream(channelName); - pump(channelA, channelB, channelA, (error) => - console.debug( - `MetaMask: Muxed traffic for channel "${channelName}" failed.`, - error, - ), + legacyExtPublicConfigChannel = + legacyExtMux.createStream(LEGACY_PUBLIC_CONFIG); + pump( + legacyPagePublicConfigChannel, + legacyExtPublicConfigChannel, + legacyPagePublicConfigChannel, + (error) => + console.debug( + `MetaMask: Muxed traffic for channel "${LEGACY_PUBLIC_CONFIG}" failed.`, + error, + ), ); -} +}; -// TODO:LegacyProvider: Delete -function forwardNamedTrafficBetweenMuxes( - channelAName, - channelBName, - muxA, - muxB, -) { - const channelA = muxA.createStream(channelAName); - const channelB = muxB.createStream(channelBName); - pump(channelA, channelB, channelA, (error) => - console.debug( - `MetaMask: Muxed traffic between channels "${channelAName}" and "${channelBName}" failed.`, - error, - ), - ); -} +/** + * Destroys all of the legacy extension streams + * TODO:LegacyProvider: Delete + */ +const destroyLegacyExtensionStreams = () => { + legacyPageMuxLegacyProviderChannel.removeAllListeners(); + legacyPagePublicConfigChannel.removeAllListeners(); + + legacyExtMux.removeAllListeners(); + legacyExtMux.destroy(); + + legacyExtChannel.removeAllListeners(); + legacyExtChannel.destroy(); + + legacyExtPublicConfigChannel.removeAllListeners(); + legacyExtPublicConfigChannel.destroy(); +}; + +/** + * Resets the extension stream with new streams to channel with the in page streams, + * and creates a new event listener to the reestablished extension port. + */ +const resetStreamAndListeners = () => { + extensionPort.onDisconnect.removeListener(resetStreamAndListeners); + + destroyExtensionStreams(); + setupExtensionStreams(); + + destroyLegacyExtensionStreams(); + setupLegacyExtensionStreams(); + + extensionPort.onDisconnect.addListener(resetStreamAndListeners); +}; + +/** + * Initializes two-way communication streams between the browser extension and + * the local per-page browser context. This function also creates an event listener to + * reset the streams if the service worker resets. + */ +const initStreams = () => { + setupPageStreams(); + setupExtensionStreams(); + + // TODO:LegacyProvider: Delete + setupLegacyPageStreams(); + setupLegacyExtensionStreams(); + + extensionPort.onDisconnect.addListener(resetStreamAndListeners); +}; // TODO:LegacyProvider: Delete function getNotificationTransformStream() { @@ -276,3 +423,31 @@ function redirectToPhishingWarning(data = {}) { const querystring = new URLSearchParams({ hostname, href, newIssueUrl }); window.location.href = `${baseUrl}#${querystring}`; } + +const initKeepWorkerAlive = () => { + setInterval(() => { + browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }); + }, WORKER_KEEP_ALIVE_INTERVAL); +}; + +const start = () => { + const isDetectedPhishingSite = + window.location.origin === phishingPageUrl.origin && + window.location.pathname === phishingPageUrl.pathname; + + if (isDetectedPhishingSite) { + initPhishingStreams(); + return; + } + + if (shouldInjectProvider()) { + if (isManifestV3) { + initKeepWorkerAlive(); + } else { + injectScript(inpageBundle); + } + initStreams(); + } +}; + +start(); diff --git a/app/scripts/controllers/cached-balances.test.js b/app/scripts/controllers/cached-balances.test.js index 5ef473110..5f507f076 100644 --- a/app/scripts/controllers/cached-balances.test.js +++ b/app/scripts/controllers/cached-balances.test.js @@ -1,13 +1,13 @@ import { strict as assert } from 'assert'; import sinon from 'sinon'; -import { KOVAN_CHAIN_ID } from '../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import CachedBalancesController from './cached-balances'; describe('CachedBalancesController', function () { describe('updateCachedBalances', function () { it('should update the cached balances', async function () { const controller = new CachedBalancesController({ - getCurrentChainId: () => KOVAN_CHAIN_ID, + getCurrentChainId: () => CHAIN_IDS.KOVAN, accountTracker: { store: { subscribe: () => undefined, @@ -27,7 +27,7 @@ describe('CachedBalancesController', function () { assert.equal(controller._generateBalancesToCache.callCount, 1); assert.deepEqual(controller._generateBalancesToCache.args[0], [ 'mockAccounts', - KOVAN_CHAIN_ID, + CHAIN_IDS.KOVAN, ]); assert.equal( controller.store.getState().cachedBalances, @@ -46,7 +46,7 @@ describe('CachedBalancesController', function () { }, initState: { cachedBalances: { - [KOVAN_CHAIN_ID]: { + [CHAIN_IDS.KOVAN]: { a: '0x1', b: '0x2', c: '0x3', @@ -66,11 +66,11 @@ describe('CachedBalancesController', function () { b: { balance: null }, c: { balance: '0x5' }, }, - KOVAN_CHAIN_ID, + CHAIN_IDS.KOVAN, ); assert.deepEqual(result, { - [KOVAN_CHAIN_ID]: { + [CHAIN_IDS.KOVAN]: { a: '0x4', b: '0x2', c: '0x5', @@ -92,7 +92,7 @@ describe('CachedBalancesController', function () { }, initState: { cachedBalances: { - [KOVAN_CHAIN_ID]: { + [CHAIN_IDS.KOVAN]: { a: '0x1', b: '0x2', c: '0x3', @@ -111,7 +111,7 @@ describe('CachedBalancesController', function () { ); assert.deepEqual(result, { - [KOVAN_CHAIN_ID]: { + [CHAIN_IDS.KOVAN]: { a: '0x1', b: '0x2', c: '0x3', @@ -128,7 +128,7 @@ describe('CachedBalancesController', function () { it('should subscribe to the account tracker with the updateCachedBalances method', async function () { const subscribeSpy = sinon.spy(); const controller = new CachedBalancesController({ - getCurrentChainId: () => KOVAN_CHAIN_ID, + getCurrentChainId: () => CHAIN_IDS.KOVAN, accountTracker: { store: { subscribe: subscribeSpy, diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index 0b8ffeede..389928bee 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -1,7 +1,6 @@ -import Web3 from 'web3'; import { warn } from 'loglevel'; import { MINUTE } from '../../../shared/constants/time'; -import { MAINNET_CHAIN_ID } from '../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import { STATIC_MAINNET_TOKEN_LIST } from '../../../shared/constants/tokens'; import { isTokenDetectionEnabledForNetwork } from '../../../shared/modules/network.utils'; import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; @@ -97,14 +96,14 @@ export default class DetectTokensController { } if ( !this.useTokenDetection && - this.getChainIdFromNetworkStore(this._network) !== MAINNET_CHAIN_ID + this.getChainIdFromNetworkStore(this._network) !== CHAIN_IDS.MAINNET ) { return; } const isTokenDetectionInactiveInMainnet = !this.useTokenDetection && - this.getChainIdFromNetworkStore(this._network) === MAINNET_CHAIN_ID; + this.getChainIdFromNetworkStore(this._network) === CHAIN_IDS.MAINNET; const { tokenList } = this._tokenList.state; const tokenListUsed = isTokenDetectionInactiveInMainnet @@ -112,7 +111,6 @@ export default class DetectTokensController { : tokenList; const tokensToDetect = []; - this.web3.setProvider(this._network._provider); for (const tokenAddress in tokenListUsed) { if ( !this.tokenAddresses.find(({ address }) => @@ -219,7 +217,6 @@ export default class DetectTokensController { return; } this._network = network; - this.web3 = new Web3(network._provider); this._network.store.subscribe(() => { if (this.chainId !== this.getChainIdFromNetworkStore(network)) { this.restartTokenDetection(); diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js index f0ec07954..2f06b4088 100644 --- a/app/scripts/controllers/detect-tokens.test.js +++ b/app/scripts/controllers/detect-tokens.test.js @@ -9,7 +9,7 @@ import { TokensController, AssetsContractController, } from '@metamask/controllers'; -import { MAINNET, ROPSTEN } from '../../../shared/constants/network'; +import { NETWORK_TYPES } from '../../../shared/constants/network'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; import DetectTokensController from './detect-tokens'; import NetworkController from './network'; @@ -169,7 +169,7 @@ describe('DetectTokensController', function () { it('should be called on every polling period', async function () { const clock = sandbox.useFakeTimers(); - network.setProviderType(MAINNET); + network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ preferences, network, @@ -195,7 +195,7 @@ describe('DetectTokensController', function () { it('should not check and add tokens while on unsupported networks', async function () { sandbox.useFakeTimers(); - network.setProviderType(ROPSTEN); + network.setProviderType(NETWORK_TYPES.ROPSTEN); const tokenListMessengerRopsten = new ControllerMessenger().getRestricted({ name: 'TokenListController', }); @@ -228,7 +228,7 @@ describe('DetectTokensController', function () { it('should skip adding tokens listed in ignoredTokens array', async function () { sandbox.useFakeTimers(); - network.setProviderType(MAINNET); + network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ preferences, network, @@ -279,7 +279,7 @@ describe('DetectTokensController', function () { it('should check and add tokens while on supported networks', async function () { sandbox.useFakeTimers(); - network.setProviderType(MAINNET); + network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ preferences, network, @@ -374,7 +374,7 @@ describe('DetectTokensController', function () { it('should not trigger detect new tokens when not unlocked', async function () { const clock = sandbox.useFakeTimers(); - network.setProviderType(MAINNET); + network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ preferences, network, @@ -395,7 +395,7 @@ describe('DetectTokensController', function () { it('should not trigger detect new tokens when not open', async function () { const clock = sandbox.useFakeTimers(); - network.setProviderType(MAINNET); + network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ preferences, network, diff --git a/app/scripts/controllers/incoming-transactions.js b/app/scripts/controllers/incoming-transactions.js index f1f81352b..a110c3c07 100644 --- a/app/scripts/controllers/incoming-transactions.js +++ b/app/scripts/controllers/incoming-transactions.js @@ -10,14 +10,9 @@ import { TRANSACTION_STATUSES, } from '../../../shared/constants/transaction'; import { + CHAIN_IDS, CHAIN_ID_TO_NETWORK_ID_MAP, CHAIN_ID_TO_TYPE_MAP, - GOERLI_CHAIN_ID, - KOVAN_CHAIN_ID, - MAINNET_CHAIN_ID, - RINKEBY_CHAIN_ID, - ROPSTEN_CHAIN_ID, - SEPOLIA_CHAIN_ID, } from '../../../shared/constants/network'; const fetchWithTimeout = getFetchWithTimeout(); @@ -54,12 +49,12 @@ const fetchWithTimeout = getFetchWithTimeout(); * 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, - SEPOLIA_CHAIN_ID, + CHAIN_IDS.GOERLI, + CHAIN_IDS.KOVAN, + CHAIN_IDS.MAINNET, + CHAIN_IDS.RINKEBY, + CHAIN_IDS.ROPSTEN, + CHAIN_IDS.SEPOLIA, ]; export default class IncomingTransactionsController { @@ -83,12 +78,12 @@ export default class IncomingTransactionsController { const initState = { incomingTransactions: {}, incomingTxLastFetchedBlockByChainId: { - [GOERLI_CHAIN_ID]: null, - [KOVAN_CHAIN_ID]: null, - [MAINNET_CHAIN_ID]: null, - [RINKEBY_CHAIN_ID]: null, - [ROPSTEN_CHAIN_ID]: null, - [SEPOLIA_CHAIN_ID]: null, + [CHAIN_IDS.GOERLI]: null, + [CHAIN_IDS.KOVAN]: null, + [CHAIN_IDS.MAINNET]: null, + [CHAIN_IDS.RINKEBY]: null, + [CHAIN_IDS.ROPSTEN]: null, + [CHAIN_IDS.SEPOLIA]: null, }, ...opts.initState, }; @@ -228,7 +223,7 @@ export default class IncomingTransactionsController { */ async _getNewIncomingTransactions(address, fromBlock, chainId) { const etherscanSubdomain = - chainId === MAINNET_CHAIN_ID + chainId === CHAIN_IDS.MAINNET ? 'api' : `api-${CHAIN_ID_TO_TYPE_MAP[chainId]}`; diff --git a/app/scripts/controllers/incoming-transactions.test.js b/app/scripts/controllers/incoming-transactions.test.js index 5d1d86129..100696ae6 100644 --- a/app/scripts/controllers/incoming-transactions.test.js +++ b/app/scripts/controllers/incoming-transactions.test.js @@ -7,14 +7,9 @@ import { cloneDeep } from 'lodash'; import waitUntilCalled from '../../../test/lib/wait-until-called'; import { CHAIN_ID_TO_TYPE_MAP, - GOERLI_CHAIN_ID, - KOVAN_CHAIN_ID, - MAINNET_CHAIN_ID, - RINKEBY_CHAIN_ID, - ROPSTEN_CHAIN_ID, - SEPOLIA_CHAIN_ID, - ROPSTEN_NETWORK_ID, - ROPSTEN, + CHAIN_IDS, + NETWORK_TYPES, + NETWORK_IDS, } from '../../../shared/constants/network'; import { TRANSACTION_TYPES, @@ -35,20 +30,20 @@ const PREPOPULATED_INCOMING_TXS_BY_HASH = { [EXISTING_INCOMING_TX.hash]: EXISTING_INCOMING_TX, }; const PREPOPULATED_BLOCKS_BY_NETWORK = { - [GOERLI_CHAIN_ID]: 1, - [KOVAN_CHAIN_ID]: 2, - [MAINNET_CHAIN_ID]: 3, - [RINKEBY_CHAIN_ID]: 5, - [ROPSTEN_CHAIN_ID]: 4, - [SEPOLIA_CHAIN_ID]: 6, + [CHAIN_IDS.GOERLI]: 1, + [CHAIN_IDS.KOVAN]: 2, + [CHAIN_IDS.MAINNET]: 3, + [CHAIN_IDS.RINKEBY]: 5, + [CHAIN_IDS.ROPSTEN]: 4, + [CHAIN_IDS.SEPOLIA]: 6, }; const EMPTY_BLOCKS_BY_NETWORK = { - [GOERLI_CHAIN_ID]: null, - [KOVAN_CHAIN_ID]: null, - [MAINNET_CHAIN_ID]: null, - [RINKEBY_CHAIN_ID]: null, - [ROPSTEN_CHAIN_ID]: null, - [SEPOLIA_CHAIN_ID]: null, + [CHAIN_IDS.GOERLI]: null, + [CHAIN_IDS.KOVAN]: null, + [CHAIN_IDS.MAINNET]: null, + [CHAIN_IDS.RINKEBY]: null, + [CHAIN_IDS.ROPSTEN]: null, + [CHAIN_IDS.SEPOLIA]: null, }; function getEmptyInitState() { @@ -151,17 +146,17 @@ const getFakeEtherscanTransaction = ({ function nockEtherscanApiForAllChains(mockResponse) { for (const chainId of [ - GOERLI_CHAIN_ID, - KOVAN_CHAIN_ID, - MAINNET_CHAIN_ID, - RINKEBY_CHAIN_ID, - ROPSTEN_CHAIN_ID, - SEPOLIA_CHAIN_ID, + CHAIN_IDS.GOERLI, + CHAIN_IDS.KOVAN, + CHAIN_IDS.MAINNET, + CHAIN_IDS.RINKEBY, + CHAIN_IDS.ROPSTEN, + CHAIN_IDS.SEPOLIA, 'undefined', ]) { nock( `https://api${ - chainId === MAINNET_CHAIN_ID ? '' : `-${CHAIN_ID_TO_TYPE_MAP[chainId]}` + chainId === CHAIN_IDS.MAINNET ? '' : `-${CHAIN_ID_TO_TYPE_MAP[chainId]}` }.etherscan.io`, ) .get(/api.+/u) @@ -251,14 +246,14 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, ); const startBlock = getNonEmptyInitState().incomingTxLastFetchedBlockByChainId[ - ROPSTEN_CHAIN_ID + CHAIN_IDS.ROPSTEN ]; nock('https://api-ropsten.etherscan.io') .get( @@ -310,8 +305,8 @@ describe('IncomingTransactionsController', function () { '0xfake': { blockNumber: '10', hash: '0xfake', - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, status: TRANSACTION_STATUSES.CONFIRMED, time: 16000000000000000, type: TRANSACTION_TYPES.INCOMING, @@ -327,8 +322,8 @@ describe('IncomingTransactionsController', function () { '0xfakeeip1559': { blockNumber: '10', hash: '0xfakeeip1559', - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, status: TRANSACTION_STATUSES.CONFIRMED, time: 16000000000000000, type: TRANSACTION_TYPES.INCOMING, @@ -345,7 +340,7 @@ describe('IncomingTransactionsController', function () { }, incomingTxLastFetchedBlockByChainId: { ...getNonEmptyInitState().incomingTxLastFetchedBlockByChainId, - [ROPSTEN_CHAIN_ID]: 11, + [CHAIN_IDS.ROPSTEN]: 11, }, }, 'State should have been updated after first block was received', @@ -453,7 +448,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -498,7 +493,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -545,7 +540,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -553,7 +548,7 @@ describe('IncomingTransactionsController', function () { const NEW_MOCK_SELECTED_ADDRESS = `${MOCK_SELECTED_ADDRESS}9`; const startBlock = getNonEmptyInitState().incomingTxLastFetchedBlockByChainId[ - ROPSTEN_CHAIN_ID + CHAIN_IDS.ROPSTEN ]; nock('https://api-ropsten.etherscan.io') .get( @@ -608,8 +603,8 @@ describe('IncomingTransactionsController', function () { '0xfake': { blockNumber: '10', hash: '0xfake', - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, status: TRANSACTION_STATUSES.CONFIRMED, time: 16000000000000000, type: TRANSACTION_TYPES.INCOMING, @@ -625,7 +620,7 @@ describe('IncomingTransactionsController', function () { }, incomingTxLastFetchedBlockByChainId: { ...getNonEmptyInitState().incomingTxLastFetchedBlockByChainId, - [ROPSTEN_CHAIN_ID]: 11, + [CHAIN_IDS.ROPSTEN]: 11, }, }, 'State should have been updated after first block was received', @@ -691,8 +686,9 @@ describe('IncomingTransactionsController', function () { }); it('should update when switching to a supported network', async function () { - const mockedNetworkMethods = - getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID); + const mockedNetworkMethods = getMockNetworkControllerMethods( + CHAIN_IDS.ROPSTEN, + ); const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), @@ -703,7 +699,7 @@ describe('IncomingTransactionsController', function () { ); const startBlock = getNonEmptyInitState().incomingTxLastFetchedBlockByChainId[ - ROPSTEN_CHAIN_ID + CHAIN_IDS.ROPSTEN ]; nock('https://api-ropsten.etherscan.io') .get( @@ -727,7 +723,7 @@ describe('IncomingTransactionsController', function () { const subscription = mockedNetworkMethods.onNetworkDidChange.getCall(0).args[0]; - await subscription(ROPSTEN_CHAIN_ID); + await subscription(CHAIN_IDS.ROPSTEN); await updateStateCalled(); const actualState = incomingTransactionsController.store.getState(); @@ -748,8 +744,8 @@ describe('IncomingTransactionsController', function () { '0xfake': { blockNumber: '10', hash: '0xfake', - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, status: TRANSACTION_STATUSES.CONFIRMED, time: 16000000000000000, type: TRANSACTION_TYPES.INCOMING, @@ -765,7 +761,7 @@ describe('IncomingTransactionsController', function () { }, incomingTxLastFetchedBlockByChainId: { ...getNonEmptyInitState().incomingTxLastFetchedBlockByChainId, - [ROPSTEN_CHAIN_ID]: 11, + [CHAIN_IDS.ROPSTEN]: 11, }, }, 'State should have been updated after first block was received', @@ -773,8 +769,9 @@ describe('IncomingTransactionsController', function () { }); it('should not update when switching to an unsupported network', async function () { - const mockedNetworkMethods = - getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID); + const mockedNetworkMethods = getMockNetworkControllerMethods( + CHAIN_IDS.ROPSTEN, + ); const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), @@ -832,10 +829,10 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController({ blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getEmptyInitState(), - getCurrentChainId: () => ROPSTEN_CHAIN_ID, + getCurrentChainId: () => CHAIN_IDS.ROPSTEN, }); sinon.spy(incomingTransactionsController.store, 'updateState'); @@ -850,14 +847,14 @@ describe('IncomingTransactionsController', function () { assert.deepStrictEqual( incomingTransactionsController._getNewIncomingTransactions.getCall(0) .args, - ['fakeAddress', 999, ROPSTEN_CHAIN_ID], + ['fakeAddress', 999, CHAIN_IDS.ROPSTEN], ); assert.deepStrictEqual( incomingTransactionsController.store.updateState.getCall(0).args[0], { incomingTxLastFetchedBlockByChainId: { ...EMPTY_BLOCKS_BY_NETWORK, - [ROPSTEN_CHAIN_ID]: 1000, + [CHAIN_IDS.ROPSTEN]: 1000, }, incomingTransactions: {}, }, @@ -868,10 +865,10 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController({ blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getEmptyInitState(), - getCurrentChainId: () => ROPSTEN_CHAIN_ID, + getCurrentChainId: () => CHAIN_IDS.ROPSTEN, }); const NEW_TRANSACTION_ONE = { @@ -897,7 +894,7 @@ describe('IncomingTransactionsController', function () { assert.deepStrictEqual( incomingTransactionsController._getNewIncomingTransactions.getCall(0) .args, - ['fakeAddress', 10, ROPSTEN_CHAIN_ID], + ['fakeAddress', 10, CHAIN_IDS.ROPSTEN], ); assert.deepStrictEqual( @@ -905,7 +902,7 @@ describe('IncomingTransactionsController', function () { { incomingTxLastFetchedBlockByChainId: { ...EMPTY_BLOCKS_BY_NETWORK, - [ROPSTEN_CHAIN_ID]: 445, + [CHAIN_IDS.ROPSTEN]: 445, }, incomingTransactions: { [NEW_TRANSACTION_ONE.hash]: NEW_TRANSACTION_ONE, @@ -921,10 +918,10 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController({ blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), - getCurrentChainId: () => ROPSTEN_CHAIN_ID, + getCurrentChainId: () => CHAIN_IDS.ROPSTEN, }); sinon.spy(incomingTransactionsController.store, 'updateState'); incomingTransactionsController._getNewIncomingTransactions = sinon @@ -940,7 +937,7 @@ describe('IncomingTransactionsController', function () { assert.deepStrictEqual( incomingTransactionsController._getNewIncomingTransactions.getCall(0) .args, - ['fakeAddress', 4, ROPSTEN_CHAIN_ID], + ['fakeAddress', 4, CHAIN_IDS.ROPSTEN], ); assert.deepStrictEqual( @@ -948,8 +945,8 @@ describe('IncomingTransactionsController', function () { { incomingTxLastFetchedBlockByChainId: { ...PREPOPULATED_BLOCKS_BY_NETWORK, - [ROPSTEN_CHAIN_ID]: - PREPOPULATED_BLOCKS_BY_NETWORK[ROPSTEN_CHAIN_ID] + 1, + [CHAIN_IDS.ROPSTEN]: + PREPOPULATED_BLOCKS_BY_NETWORK[CHAIN_IDS.ROPSTEN] + 1, }, incomingTransactions: PREPOPULATED_INCOMING_TXS_BY_HASH, }, @@ -961,10 +958,10 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), - getCurrentChainId: () => ROPSTEN_CHAIN_ID, + getCurrentChainId: () => CHAIN_IDS.ROPSTEN, }, ); @@ -991,7 +988,7 @@ describe('IncomingTransactionsController', function () { assert.deepStrictEqual( incomingTransactionsController._getNewIncomingTransactions.getCall(0) .args, - ['fakeAddress', 4, ROPSTEN_CHAIN_ID], + ['fakeAddress', 4, CHAIN_IDS.ROPSTEN], ); assert.deepStrictEqual( @@ -999,7 +996,7 @@ describe('IncomingTransactionsController', function () { { incomingTxLastFetchedBlockByChainId: { ...PREPOPULATED_BLOCKS_BY_NETWORK, - [ROPSTEN_CHAIN_ID]: 445, + [CHAIN_IDS.ROPSTEN]: 445, }, incomingTransactions: { ...PREPOPULATED_INCOMING_TXS_BY_HASH, @@ -1036,7 +1033,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -1045,13 +1042,13 @@ describe('IncomingTransactionsController', function () { await incomingTransactionsController._getNewIncomingTransactions( ADDRESS_TO_FETCH_FOR, '789', - ROPSTEN_CHAIN_ID, + CHAIN_IDS.ROPSTEN, ); assert(mockFetch.calledOnce); assert.strictEqual( mockFetch.getCall(0).args[0], - `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`, + `https://api-${NETWORK_TYPES.ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`, ); }); @@ -1059,7 +1056,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(MAINNET_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.MAINNET), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -1068,7 +1065,7 @@ describe('IncomingTransactionsController', function () { await incomingTransactionsController._getNewIncomingTransactions( ADDRESS_TO_FETCH_FOR, '789', - MAINNET_CHAIN_ID, + CHAIN_IDS.MAINNET, ); assert(mockFetch.calledOnce); @@ -1082,7 +1079,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -1091,13 +1088,13 @@ describe('IncomingTransactionsController', function () { await incomingTransactionsController._getNewIncomingTransactions( ADDRESS_TO_FETCH_FOR, null, - ROPSTEN_CHAIN_ID, + CHAIN_IDS.ROPSTEN, ); assert(mockFetch.calledOnce); assert.strictEqual( mockFetch.getCall(0).args[0], - `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`, + `https://api-${NETWORK_TYPES.ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`, ); }); @@ -1105,7 +1102,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -1115,14 +1112,14 @@ describe('IncomingTransactionsController', function () { await incomingTransactionsController._getNewIncomingTransactions( ADDRESS_TO_FETCH_FOR, '789', - ROPSTEN_CHAIN_ID, + CHAIN_IDS.ROPSTEN, ); assert(mockFetch.calledOnce); assert.deepStrictEqual(result, [ incomingTransactionsController._normalizeTxFromEtherscan( FETCHED_TX, - ROPSTEN_CHAIN_ID, + CHAIN_IDS.ROPSTEN, ), ]); }); @@ -1138,7 +1135,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -1148,7 +1145,7 @@ describe('IncomingTransactionsController', function () { await incomingTransactionsController._getNewIncomingTransactions( ADDRESS_TO_FETCH_FOR, '789', - ROPSTEN_CHAIN_ID, + CHAIN_IDS.ROPSTEN, ); assert.deepStrictEqual(result, []); window.fetch = tempFetchStatusZero; @@ -1166,7 +1163,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -1176,7 +1173,7 @@ describe('IncomingTransactionsController', function () { await incomingTransactionsController._getNewIncomingTransactions( ADDRESS_TO_FETCH_FOR, '789', - ROPSTEN_CHAIN_ID, + CHAIN_IDS.ROPSTEN, ); assert.deepStrictEqual(result, []); window.fetch = tempFetchEmptyResult; @@ -1189,7 +1186,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -1208,14 +1205,14 @@ describe('IncomingTransactionsController', function () { value: '15', hash: '0xg', }, - ROPSTEN_CHAIN_ID, + CHAIN_IDS.ROPSTEN, ); assert.deepStrictEqual(result, { blockNumber: 333, id: 54321, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, status: TRANSACTION_STATUSES.FAILED, time: 4444000, txParams: { @@ -1235,7 +1232,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -1254,14 +1251,14 @@ describe('IncomingTransactionsController', function () { value: '15', hash: '0xg', }, - ROPSTEN_CHAIN_ID, + CHAIN_IDS.ROPSTEN, ); assert.deepStrictEqual(result, { blockNumber: 333, id: 54321, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, status: TRANSACTION_STATUSES.CONFIRMED, time: 4444000, txParams: { @@ -1281,7 +1278,7 @@ describe('IncomingTransactionsController', function () { const incomingTransactionsController = new IncomingTransactionsController( { blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + ...getMockNetworkControllerMethods(CHAIN_IDS.ROPSTEN), preferencesController: getMockPreferencesController(), initState: getNonEmptyInitState(), }, @@ -1301,14 +1298,14 @@ describe('IncomingTransactionsController', function () { value: '15', hash: '0xg', }, - ROPSTEN_CHAIN_ID, + CHAIN_IDS.ROPSTEN, ); assert.deepStrictEqual(result, { blockNumber: 333, id: 54321, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, status: TRANSACTION_STATUSES.CONFIRMED, time: 4444000, txParams: { diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 744cc714c..d771c5319 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -188,6 +188,15 @@ export default class MetaMetricsController { }`, ); } + + const existingFragment = this.getExistingEventFragment( + options.actionId, + options.uniqueIdentifier, + ); + if (existingFragment) { + return existingFragment; + } + const { fragments } = this.store.getState(); const id = options.uniqueIdentifier ?? uuidv4(); @@ -236,6 +245,26 @@ export default class MetaMetricsController { return fragment; } + /** + * Returns the fragment stored in memory with provided id or undefined if it + * does not exist. + * + * @param {string} actionId - actionId passed from UI + * @param {string} uniqueIdentifier - uniqueIdentifier of the event + * @returns {[MetaMetricsEventFragment]} + */ + getExistingEventFragment(actionId, uniqueIdentifier) { + const { fragments } = this.store.getState(); + + const existingFragment = Object.values(fragments).find( + (fragment) => + fragment.actionId === actionId && + fragment.uniqueIdentifier === uniqueIdentifier, + ); + + return existingFragment; + } + /** * Updates an event fragment in state * diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index c5449b90e..1ad4e18d6 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -8,12 +8,7 @@ import { TRAITS, } from '../../../shared/constants/metametrics'; import waitUntilCalled from '../../../test/lib/wait-until-called'; -import { - ETH_SYMBOL, - MAINNET_CHAIN_ID, - ROPSTEN_CHAIN_ID, - TEST_ETH_SYMBOL, -} from '../../../shared/constants/network'; +import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../../shared/constants/network'; import MetaMetricsController from './metametrics'; import { NETWORK_EVENTS } from './network'; @@ -642,8 +637,8 @@ describe('MetaMetricsController', function () { const metaMetricsController = getMetaMetricsController(); const traits = metaMetricsController._buildUserTraitsObject({ addressBook: { - [MAINNET_CHAIN_ID]: [{ address: '0x' }], - [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + [CHAIN_IDS.MAINNET]: [{ address: '0x' }], + [CHAIN_IDS.ROPSTEN]: [{ address: '0x' }, { address: '0x0' }], }, allCollectibles: { '0xac706cE8A9BF27Afecf080fB298d0ee13cfb978A': { @@ -673,8 +668,8 @@ describe('MetaMetricsController', function () { }, allTokens: MOCK_ALL_TOKENS, frequentRpcListDetail: [ - { chainId: MAINNET_CHAIN_ID, ticker: ETH_SYMBOL }, - { chainId: ROPSTEN_CHAIN_ID, ticker: TEST_ETH_SYMBOL }, + { chainId: CHAIN_IDS.MAINNET, ticker: CURRENCY_SYMBOLS.ETH }, + { chainId: CHAIN_IDS.ROPSTEN, ticker: CURRENCY_SYMBOLS.TEST_ETH }, { chainId: '0xaf' }, ], identities: [{}, {}], @@ -690,7 +685,7 @@ describe('MetaMetricsController', function () { [TRAITS.ADDRESS_BOOK_ENTRIES]: 3, [TRAITS.INSTALL_DATE_EXT]: '', [TRAITS.LEDGER_CONNECTION_TYPE]: 'web-hid', - [TRAITS.NETWORKS_ADDED]: [MAINNET_CHAIN_ID, ROPSTEN_CHAIN_ID, '0xaf'], + [TRAITS.NETWORKS_ADDED]: [CHAIN_IDS.MAINNET, CHAIN_IDS.ROPSTEN, '0xaf'], [TRAITS.NETWORKS_WITHOUT_TICKER]: ['0xaf'], [TRAITS.NFT_AUTODETECTION_ENABLED]: false, [TRAITS.NUMBER_OF_ACCOUNTS]: 2, @@ -708,13 +703,13 @@ describe('MetaMetricsController', function () { const metaMetricsController = getMetaMetricsController(); metaMetricsController._buildUserTraitsObject({ addressBook: { - [MAINNET_CHAIN_ID]: [{ address: '0x' }], - [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + [CHAIN_IDS.MAINNET]: [{ address: '0x' }], + [CHAIN_IDS.ROPSTEN]: [{ address: '0x' }, { address: '0x0' }], }, allTokens: {}, frequentRpcListDetail: [ - { chainId: MAINNET_CHAIN_ID }, - { chainId: ROPSTEN_CHAIN_ID }, + { chainId: CHAIN_IDS.MAINNET }, + { chainId: CHAIN_IDS.ROPSTEN }, ], ledgerTransportType: 'web-hid', openSeaEnabled: true, @@ -727,15 +722,15 @@ describe('MetaMetricsController', function () { const updatedTraits = metaMetricsController._buildUserTraitsObject({ addressBook: { - [MAINNET_CHAIN_ID]: [{ address: '0x' }, { address: '0x1' }], - [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + [CHAIN_IDS.MAINNET]: [{ address: '0x' }, { address: '0x1' }], + [CHAIN_IDS.ROPSTEN]: [{ address: '0x' }, { address: '0x0' }], }, allTokens: { '0x1': { '0xabcde': [{ '0x12345': { address: '0xtestAddress' } }] }, }, frequentRpcListDetail: [ - { chainId: MAINNET_CHAIN_ID }, - { chainId: ROPSTEN_CHAIN_ID }, + { chainId: CHAIN_IDS.MAINNET }, + { chainId: CHAIN_IDS.ROPSTEN }, ], ledgerTransportType: 'web-hid', openSeaEnabled: false, @@ -758,13 +753,13 @@ describe('MetaMetricsController', function () { const metaMetricsController = getMetaMetricsController(); metaMetricsController._buildUserTraitsObject({ addressBook: { - [MAINNET_CHAIN_ID]: [{ address: '0x' }], - [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + [CHAIN_IDS.MAINNET]: [{ address: '0x' }], + [CHAIN_IDS.ROPSTEN]: [{ address: '0x' }, { address: '0x0' }], }, allTokens: {}, frequentRpcListDetail: [ - { chainId: MAINNET_CHAIN_ID }, - { chainId: ROPSTEN_CHAIN_ID }, + { chainId: CHAIN_IDS.MAINNET }, + { chainId: CHAIN_IDS.ROPSTEN }, ], ledgerTransportType: 'web-hid', openSeaEnabled: true, @@ -777,13 +772,13 @@ describe('MetaMetricsController', function () { const updatedTraits = metaMetricsController._buildUserTraitsObject({ addressBook: { - [MAINNET_CHAIN_ID]: [{ address: '0x' }], - [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + [CHAIN_IDS.MAINNET]: [{ address: '0x' }], + [CHAIN_IDS.ROPSTEN]: [{ address: '0x' }, { address: '0x0' }], }, allTokens: {}, frequentRpcListDetail: [ - { chainId: MAINNET_CHAIN_ID }, - { chainId: ROPSTEN_CHAIN_ID }, + { chainId: CHAIN_IDS.MAINNET }, + { chainId: CHAIN_IDS.ROPSTEN }, ], ledgerTransportType: 'web-hid', openSeaEnabled: true, diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index ff143bff8..111e742ce 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -8,10 +8,10 @@ import { providerFromMiddleware, } from 'eth-json-rpc-middleware'; -import createInfuraMiddleware from 'eth-json-rpc-infura'; +import { createInfuraMiddleware } from '@metamask/eth-json-rpc-infura'; import { PollingBlockTracker } from 'eth-block-tracker'; -import { NETWORK_TYPE_TO_ID_MAP } from '../../../../shared/constants/network'; +import { BUILT_IN_NETWORKS } from '../../../../shared/constants/network'; export default function createInfuraClient({ network, projectId }) { const infuraMiddleware = createInfuraMiddleware({ @@ -36,11 +36,11 @@ export default function createInfuraClient({ network, projectId }) { } function createNetworkAndChainIdMiddleware({ network }) { - if (!NETWORK_TYPE_TO_ID_MAP[network]) { + if (!BUILT_IN_NETWORKS[network]) { throw new Error(`createInfuraClient - unknown network "${network}"`); } - const { chainId, networkId } = NETWORK_TYPE_TO_ID_MAP[network]; + const { chainId, networkId } = BUILT_IN_NETWORKS[network]; return createScaffoldMiddleware({ eth_chainId: chainId, diff --git a/app/scripts/controllers/network/createInfuraClient.test.js b/app/scripts/controllers/network/createInfuraClient.test.js new file mode 100644 index 000000000..0d7ac9557 --- /dev/null +++ b/app/scripts/controllers/network/createInfuraClient.test.js @@ -0,0 +1,332 @@ +/** + * @jest-environment node + */ + +import { withInfuraClient } from './provider-api-tests/helpers'; +import { + testsForRpcMethodNotHandledByMiddleware, + testsForRpcMethodAssumingNoBlockParam, + testsForRpcMethodsThatCheckForBlockHashInResponse, + testsForRpcMethodSupportingBlockParam, +} from './provider-api-tests/shared-tests'; + +describe('createInfuraClient', () => { + // Infura documentation: + // Ethereum JSON-RPC spec: + + describe('RPC methods supported by Infura and listed in the JSON-RPC spec', () => { + describe('eth_accounts', () => { + testsForRpcMethodNotHandledByMiddleware('eth_accounts', { + numberOfParameters: 0, + }); + }); + + describe('eth_blockNumber', () => { + testsForRpcMethodAssumingNoBlockParam('eth_blockNumber'); + }); + + describe('eth_call', () => { + testsForRpcMethodSupportingBlockParam('eth_call', { + blockParamIndex: 1, + }); + }); + + describe('eth_chainId', () => { + it('does not hit Infura, instead returning the chain id that maps to the Infura network, as a hex string', async () => { + const chainId = await withInfuraClient( + { network: 'ropsten' }, + ({ makeRpcCall }) => { + return makeRpcCall({ + method: 'eth_chainId', + }); + }, + ); + + expect(chainId).toStrictEqual('0x3'); + }); + }); + + describe('eth_coinbase', () => { + testsForRpcMethodNotHandledByMiddleware('eth_coinbase', { + numberOfParameters: 0, + }); + }); + + describe('eth_estimateGas', () => { + testsForRpcMethodAssumingNoBlockParam('eth_estimateGas'); + }); + + describe('eth_feeHistory', () => { + testsForRpcMethodNotHandledByMiddleware('eth_feeHistory', { + numberOfParameters: 3, + }); + }); + + describe('eth_getBalance', () => { + testsForRpcMethodSupportingBlockParam('eth_getBalance', { + blockParamIndex: 1, + }); + }); + + describe('eth_gasPrice', () => { + testsForRpcMethodAssumingNoBlockParam('eth_gasPrice'); + }); + + describe('eth_getBlockByHash', () => { + testsForRpcMethodAssumingNoBlockParam('eth_getBlockByHash'); + }); + + describe('eth_getBlockByNumber', () => { + testsForRpcMethodSupportingBlockParam('eth_getBlockByNumber', { + blockParamIndex: 0, + }); + }); + + describe('eth_getBlockTransactionCountByHash', () => { + testsForRpcMethodAssumingNoBlockParam( + 'eth_getBlockTransactionCountByHash', + ); + }); + + describe('eth_getBlockTransactionCountByNumber', () => { + // NOTE: eth_getBlockTransactionCountByNumber does take a block param at + // the 0th index, but this is not handled by our cache middleware + // currently + testsForRpcMethodAssumingNoBlockParam( + 'eth_getBlockTransactionCountByNumber', + ); + }); + + describe('eth_getCode', () => { + testsForRpcMethodSupportingBlockParam('eth_getCode', { + blockParamIndex: 1, + }); + }); + + describe('eth_getFilterChanges', () => { + testsForRpcMethodNotHandledByMiddleware('eth_getFilterChanges', { + numberOfParameters: 1, + }); + }); + + describe('eth_getFilterLogs', () => { + testsForRpcMethodAssumingNoBlockParam('eth_getFilterLogs'); + }); + + describe('eth_getLogs', () => { + testsForRpcMethodNotHandledByMiddleware('eth_getLogs', { + numberOfParameters: 1, + }); + }); + + describe('eth_getStorageAt', () => { + testsForRpcMethodSupportingBlockParam('eth_getStorageAt', { + blockParamIndex: 2, + }); + }); + + describe('eth_getTransactionByBlockHashAndIndex', () => { + testsForRpcMethodAssumingNoBlockParam( + 'eth_getTransactionByBlockHashAndIndex', + ); + }); + + describe('eth_getTransactionByBlockNumberAndIndex', () => { + // NOTE: eth_getTransactionByBlockNumberAndIndex does take a block param + // at the 0th index, but this is not handled by our cache middleware + // currently + testsForRpcMethodAssumingNoBlockParam( + 'eth_getTransactionByBlockNumberAndIndex', + ); + }); + + describe('eth_getTransactionByHash', () => { + testsForRpcMethodsThatCheckForBlockHashInResponse( + 'eth_getTransactionByHash', + ); + }); + + describe('eth_getTransactionCount', () => { + testsForRpcMethodSupportingBlockParam('eth_getTransactionCount', { + blockParamIndex: 1, + }); + }); + + describe('eth_getTransactionReceipt', () => { + testsForRpcMethodsThatCheckForBlockHashInResponse( + 'eth_getTransactionReceipt', + ); + }); + + describe('eth_getUncleByBlockHashAndIndex', () => { + testsForRpcMethodAssumingNoBlockParam('eth_getUncleByBlockHashAndIndex'); + }); + + describe('eth_getUncleByBlockNumberAndIndex', () => { + // NOTE: eth_getUncleByBlockNumberAndIndex does take a block param at the + // 0th index, but this is not handled by our cache middleware currently + testsForRpcMethodAssumingNoBlockParam( + 'eth_getUncleByBlockNumberAndIndex', + ); + }); + + describe('eth_getUncleCountByBlockHash', () => { + testsForRpcMethodAssumingNoBlockParam('eth_getUncleCountByBlockHash'); + }); + + describe('eth_getUncleCountByBlockNumber', () => { + // NOTE: eth_getUncleCountByBlockNumber does take a block param at the 0th + // index, but this is not handled by our cache middleware currently + testsForRpcMethodAssumingNoBlockParam('eth_getUncleCountByBlockNumber'); + }); + + describe('eth_getWork', () => { + testsForRpcMethodNotHandledByMiddleware('eth_getWork', { + numberOfParameters: 0, + }); + }); + + describe('eth_hashrate', () => { + testsForRpcMethodNotHandledByMiddleware('eth_hashrate', { + numberOfParameters: 0, + }); + }); + + describe('eth_mining', () => { + testsForRpcMethodNotHandledByMiddleware('eth_mining', { + numberOfParameters: 0, + }); + }); + + describe('eth_newBlockFilter', () => { + testsForRpcMethodNotHandledByMiddleware('eth_newBlockFilter', { + numberOfParameters: 0, + }); + }); + + describe('eth_newFilter', () => { + testsForRpcMethodNotHandledByMiddleware('eth_newFilter', { + numberOfParameters: 1, + }); + }); + + describe('eth_newPendingTransactionFilter', () => { + testsForRpcMethodNotHandledByMiddleware( + 'eth_newPendingTransactionFilter', + { numberOfParameters: 0 }, + ); + }); + + describe('eth_protocolVersion', () => { + testsForRpcMethodAssumingNoBlockParam('eth_protocolVersion'); + }); + + describe('eth_sendRawTransaction', () => { + testsForRpcMethodNotHandledByMiddleware('eth_sendRawTransaction', { + numberOfParameters: 1, + }); + }); + + describe('eth_sendTransaction', () => { + testsForRpcMethodNotHandledByMiddleware('eth_sendTransaction', { + numberOfParameters: 1, + }); + }); + + describe('eth_sign', () => { + testsForRpcMethodNotHandledByMiddleware('eth_sign', { + numberOfParameters: 2, + }); + }); + + describe('eth_submitWork', () => { + testsForRpcMethodNotHandledByMiddleware('eth_submitWork', { + numberOfParameters: 3, + }); + }); + + describe('eth_syncing', () => { + testsForRpcMethodNotHandledByMiddleware('eth_syncing', { + numberOfParameters: 0, + }); + }); + + describe('eth_uninstallFilter', () => { + testsForRpcMethodNotHandledByMiddleware('eth_uninstallFilter', { + numberOfParameters: 1, + }); + }); + }); + + describe('RPC methods supported by Infura but not listed in the JSON-RPC spec', () => { + describe('eth_subscribe', () => { + testsForRpcMethodNotHandledByMiddleware('eth_subscribe', { + numberOfParameters: 1, + }); + }); + + describe('eth_unsubscribe', () => { + testsForRpcMethodNotHandledByMiddleware('eth_unsubscribe', { + numberOfParameters: 1, + }); + }); + + describe('net_listening', () => { + testsForRpcMethodNotHandledByMiddleware('net_listening', { + numberOfParameters: 0, + }); + }); + + describe('net_peerCount', () => { + testsForRpcMethodNotHandledByMiddleware('net_peerCount', { + numberOfParameters: 0, + }); + }); + + describe('net_version', () => { + it('does not hit Infura, instead returning the chain id that maps to the Infura network, as a decimal string', async () => { + const chainId = await withInfuraClient( + { network: 'ropsten' }, + ({ makeRpcCall }) => { + return makeRpcCall({ + method: 'net_version', + }); + }, + ); + + expect(chainId).toStrictEqual('3'); + }); + }); + + describe('parity_nextNonce', () => { + testsForRpcMethodNotHandledByMiddleware('parity_nextNonce', { + numberOfParameters: 1, + }); + }); + + describe('web3_clientVersion', () => { + testsForRpcMethodAssumingNoBlockParam('web3_clientVersion'); + }); + }); + + // NOTE: The following methods are omitted because although they are listed in + // the Ethereum spec, they do not seem to be supported by Infura: + // + // - debug_getBadBlocks + // - debug_getRawBlock + // - debug_getRawHeader + // - debug_getRawReceipts + // - eth_createAccessList + // - eth_compileLLL + // - eth_compileSerpent + // - eth_compileSolidity + // - eth_getCompilers + // - eth_getProof + // - eth_maxPriorityFeePerGas + // - eth_submitHashrate + // - web3_sha3 + + testsForRpcMethodNotHandledByMiddleware('custom_rpc_method', { + numberOfParameters: 1, + }); +}); diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index a079307ab..8214da894 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -10,15 +10,12 @@ import { } from 'swappable-obj-proxy'; import EthQuery from 'eth-query'; import { - RINKEBY, - MAINNET, INFURA_PROVIDER_TYPES, - NETWORK_TYPE_RPC, - NETWORK_TYPE_TO_ID_MAP, - MAINNET_CHAIN_ID, - RINKEBY_CHAIN_ID, + BUILT_IN_NETWORKS, INFURA_BLOCKED_KEY, TEST_NETWORK_TICKER_MAP, + CHAIN_IDS, + NETWORK_TYPES, } from '../../../../shared/constants/network'; import { isPrefixedFormattedHexString, @@ -35,19 +32,22 @@ const fetchWithTimeout = getFetchWithTimeout(); let defaultProviderConfigOpts; if (process.env.IN_TEST) { defaultProviderConfigOpts = { - type: NETWORK_TYPE_RPC, + type: NETWORK_TYPES.RPC, rpcUrl: 'http://localhost:8545', chainId: '0x539', nickname: 'Localhost 8545', }; } else if (process.env.METAMASK_DEBUG || env === 'test') { defaultProviderConfigOpts = { - type: RINKEBY, - chainId: RINKEBY_CHAIN_ID, + type: NETWORK_TYPES.RINKEBY, + chainId: CHAIN_IDS.RINKEBY, ticker: TEST_NETWORK_TICKER_MAP.rinkeby, }; } else { - defaultProviderConfigOpts = { type: MAINNET, chainId: MAINNET_CHAIN_ID }; + defaultProviderConfigOpts = { + type: NETWORK_TYPES.MAINNET, + chainId: CHAIN_IDS.MAINNET, + }; } const defaultProviderConfig = { @@ -268,7 +268,7 @@ export default class NetworkController extends EventEmitter { getCurrentChainId() { const { type, chainId: configChainId } = this.getProviderConfig(); - return NETWORK_TYPE_TO_ID_MAP[type]?.chainId || configChainId; + return BUILT_IN_NETWORKS[type]?.chainId || configChainId; } getCurrentRpcUrl() { @@ -286,7 +286,7 @@ export default class NetworkController extends EventEmitter { `Invalid chain ID "${chainId}": numerical value greater than max safe value.`, ); this.setProviderConfig({ - type: NETWORK_TYPE_RPC, + type: NETWORK_TYPES.RPC, rpcUrl, chainId, ticker, @@ -298,14 +298,14 @@ export default class NetworkController extends EventEmitter { async setProviderType(type) { assert.notStrictEqual( type, - NETWORK_TYPE_RPC, - `NetworkController - cannot call "setProviderType" with type "${NETWORK_TYPE_RPC}". Use "setRpcTarget"`, + NETWORK_TYPES.RPC, + `NetworkController - cannot call "setProviderType" with type "${NETWORK_TYPES.RPC}". Use "setRpcTarget"`, ); assert.ok( INFURA_PROVIDER_TYPES.includes(type), `Unknown Infura provider type "${type}".`, ); - const { chainId, ticker } = NETWORK_TYPE_TO_ID_MAP[type]; + const { chainId, ticker } = BUILT_IN_NETWORKS[type]; this.setProviderConfig({ type, rpcUrl: '', @@ -342,7 +342,9 @@ export default class NetworkController extends EventEmitter { getNetworkIdentifier() { const provider = this.providerStore.getState(); - return provider.type === NETWORK_TYPE_RPC ? provider.rpcUrl : provider.type; + return provider.type === NETWORK_TYPES.RPC + ? provider.rpcUrl + : provider.type; } // @@ -407,7 +409,7 @@ export default class NetworkController extends EventEmitter { if (isInfura) { this._configureInfuraProvider(type, this._infuraProjectId); // url-based rpc endpoints - } else if (type === NETWORK_TYPE_RPC) { + } else if (type === NETWORK_TYPES.RPC) { this._configureStandardProvider(rpcUrl, chainId); } else { throw new Error( diff --git a/app/scripts/controllers/network/provider-api-tests/helpers.js b/app/scripts/controllers/network/provider-api-tests/helpers.js new file mode 100644 index 000000000..9b4c22460 --- /dev/null +++ b/app/scripts/controllers/network/provider-api-tests/helpers.js @@ -0,0 +1,333 @@ +import nock from 'nock'; +import sinon from 'sinon'; +import { JsonRpcEngine } from 'json-rpc-engine'; +import { providerFromEngine } from 'eth-json-rpc-middleware'; +import EthQuery from 'eth-query'; +import createInfuraClient from '../createInfuraClient'; + +/** + * @typedef {import('nock').Scope} NockScope + * + * A object returned by `nock(...)` for mocking requests to a particular base + * URL. + */ + +/** + * @typedef {{makeRpcCall: (request: Partial) => Promise, makeRpcCallsInSeries: (requests: Partial[]) => Promise}} InfuraClient + * + * Provides methods to interact with the suite of middleware that + * `createInfuraClient` exposes. + */ + +/** + * @typedef {{network: string}} WithInfuraClientOptions + * + * The options bag that `withInfuraClient` takes. + */ + +/** + * @typedef {(client: InfuraClient) => Promise} WithInfuraClientCallback + * + * The callback that `withInfuraClient` takes. + */ + +/** + * @typedef {[WithInfuraClientOptions, WithInfuraClientCallback] | [WithInfuraClientCallback]} WithInfuraClientArgs + * + * The arguments to `withInfuraClient`. + */ + +/** + * @typedef {{ nockScope: NockScope, blockNumber: string }} MockNextBlockTrackerRequestOptions + * + * The options to `mockNextBlockTrackerRequest`. + */ + +/** + * @typedef {{ nockScope: NockScope, request: object, response: object, delay?: number }} MockSuccessfulInfuraRpcCallOptions + * + * The options to `mockSuccessfulInfuraRpcCall`. + */ + +/** + * @typedef {{mockNextBlockTrackerRequest: (options: Omit) => void, mockSuccessfulInfuraRpcCall: (options: Omit) => NockScope}} InfuraCommunications + * + * Provides methods to mock different kinds of requests to Infura. + */ + +/** + * @typedef {{network: string}} MockingInfuraCommunicationsOptions + * + * The options bag that `mockingInfuraCommunications` takes. + */ + +/** + * @typedef {(comms: InfuraCommunications) => Promise} MockingInfuraCommunicationsCallback + * + * The callback that `mockingInfuraCommunications` takes. + */ + +/** + * @typedef {[MockingInfuraCommunicationsOptions, MockingInfuraCommunicationsCallback] | [MockingInfuraCommunicationsCallback]} MockingInfuraCommunicationsArgs + * + * The arguments to `mockingInfuraCommunications`. + */ + +const INFURA_PROJECT_ID = 'abc123'; +const DEFAULT_LATEST_BLOCK_NUMBER = '0x42'; + +/** + * If you're having trouble writing a test and you're wondering why the test + * keeps failing, you can set `process.env.DEBUG_PROVIDER_TESTS` to `1`. This + * will turn on some extra logging. + * + * @param {any[]} args - The arguments that `console.log` takes. + */ +function debug(...args) { + if (process.env.DEBUG_PROVIDER_TESTS === '1') { + console.log(...args); + } +} + +/** + * Builds a Nock scope object for mocking requests to a particular network that + * Infura supports. + * + * @param {object} options - The options. + * @param {string} options.network - The Infura network you're testing with + * (default: "mainnet"). + * @returns {NockScope} The nock scope. + */ +function buildScopeForMockingInfuraRequests({ network = 'mainnet' } = {}) { + return nock(`https://${network}.infura.io`).filteringRequestBody((body) => { + const copyOfBody = JSON.parse(body); + // some ids are random, so remove them entirely from the request to + // make it possible to mock these requests + delete copyOfBody.id; + return JSON.stringify(copyOfBody); + }); +} + +/** + * Mocks the next request for the latest block that the block tracker will make. + * + * @param {MockNextBlockTrackerRequestOptions} args - The arguments. + * @param {NockScope} args.nockScope - A nock scope (a set of mocked requests + * scoped to a certain base URL). + * @param {string} args.blockNumber - The block number that the block tracker + * should report, as a 0x-prefixed hex string. + */ +async function mockNextBlockTrackerRequest({ + nockScope, + blockNumber = DEFAULT_LATEST_BLOCK_NUMBER, +}) { + await mockSuccessfulInfuraRpcCall({ + nockScope, + request: { method: 'eth_blockNumber', params: [] }, + response: { result: blockNumber }, + }); +} + +/** + * Mocks a JSON-RPC request sent to Infura with the given response. + * + * @param {MockSuccessfulInfuraRpcCallOptions} args - The arguments. + * @param {NockScope} args.nockScope - A nock scope (a set of mocked requests + * scoped to a certain base URL). + * @param {object} args.request - The request data. + * @param {object} args.response - The response that the request should have. + * @param {number} args.delay - The amount of time that should pass before the + * request resolves with the response. + * @returns {NockScope} The nock scope. + */ +function mockSuccessfulInfuraRpcCall({ nockScope, request, response, delay }) { + // eth-query always passes `params`, so even if we don't supply this property + // for consistency with makeRpcCall, assume that the `body` contains it + const { method, params = [], ...rest } = request; + const completeResponse = { + id: 1, + jsonrpc: '2.0', + ...response, + }; + const nockRequest = nockScope.post(`/v3/${INFURA_PROJECT_ID}`, { + jsonrpc: '2.0', + method, + params, + ...rest, + }); + + if (delay !== undefined) { + nockRequest.delay(delay); + } + + return nockRequest.reply(200, completeResponse); +} + +/** + * Makes a JSON-RPC call through the given eth-query object. + * + * @param {any} ethQuery - The eth-query object. + * @param {object} request - The request data. + * @returns {Promise} A promise that either resolves with the result from + * the JSON-RPC response if it is successful or rejects with the error from the + * JSON-RPC response otherwise. + */ +function makeRpcCall(ethQuery, request) { + return new Promise((resolve, reject) => { + debug('[makeRpcCall] making request', request); + ethQuery.sendAsync(request, (error, result) => { + debug('[makeRpcCall > ethQuery handler] error', error, 'result', result); + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); +} + +/** + * Sets up request mocks for requests to Infura. + * + * @param {MockingInfuraCommunicationsArgs} args - Either an options bag + a + * function, or just a function. The options bag, at the moment, may contain + * `network` (that is, the Infura network; defaults to "mainnet"). The function + * is called with an object that allows you to mock different kinds of requests. + * @returns {Promise} The return value of the given function. + */ +export async function withMockedInfuraCommunications(...args) { + const [options, fn] = args.length === 2 ? args : [{}, args[0]]; + const { network = 'mainnet' } = options; + + const nockScope = buildScopeForMockingInfuraRequests({ network }); + const curriedMockNextBlockTrackerRequest = (localOptions) => + mockNextBlockTrackerRequest({ nockScope, ...localOptions }); + const curriedMockSuccessfulInfuraRpcCall = (localOptions) => + mockSuccessfulInfuraRpcCall({ nockScope, ...localOptions }); + const comms = { + mockNextBlockTrackerRequest: curriedMockNextBlockTrackerRequest, + mockSuccessfulInfuraRpcCall: curriedMockSuccessfulInfuraRpcCall, + }; + + try { + return await fn(comms); + } finally { + nock.isDone(); + nock.cleanAll(); + } +} + +/** + * Builds a provider from the Infura middleware along with a block tracker, runs + * the given function with those two things, and then ensures the block tracker + * is stopped at the end. + * + * @param {WithInfuraClientArgs} args - Either an options bag + a function, or + * just a function. The options bag, at the moment, may contain `network` (that + * is, the Infura network; defaults to "mainnet"). The function is called with + * an object that allows you to interact with the client via a couple of methods + * on that object. + * @returns {Promise} The return value of the given function. + */ +export async function withInfuraClient(...args) { + const [options, fn] = args.length === 2 ? args : [{}, args[0]]; + const { network = 'mainnet' } = options; + + const { networkMiddleware, blockTracker } = createInfuraClient({ + network, + projectId: INFURA_PROJECT_ID, + }); + + const engine = new JsonRpcEngine(); + engine.push(networkMiddleware); + const provider = providerFromEngine(engine); + const ethQuery = new EthQuery(provider); + + const curriedMakeRpcCall = (request) => makeRpcCall(ethQuery, request); + const makeRpcCallsInSeries = async (requests) => { + const responses = []; + for (const request of requests) { + responses.push(await curriedMakeRpcCall(request)); + } + return responses; + }; + // Faking timers ends up doing two things: + // 1. Halting the block tracker (which depends on `setTimeout` to periodically + // request the latest block) set up in `eth-json-rpc-middleware` + // 2. Halting the retry logic in `@metamask/eth-json-rpc-infura` (which also + // depends on `setTimeout`) + const clock = sinon.useFakeTimers(); + const client = { + makeRpcCall: curriedMakeRpcCall, + makeRpcCallsInSeries, + clock, + }; + + try { + return await fn(client); + } finally { + await blockTracker.destroy(); + + clock.restore(); + } +} + +/** + * Some JSON-RPC endpoints take a "block" param (example: `eth_blockNumber`) + * which can optionally be left out. Additionally, the endpoint may support some + * number of arguments, although the "block" param will always be last, even if + * it is optional. Given this, this function builds a mock `params` array for + * such an endpoint, filling it with arbitrary values, but with the "block" + * param missing. + * + * @param {number} index - The index within the `params` array where the "block" + * param *would* appear. + * @returns {string[]} The mock params. + */ +export function buildMockParamsWithoutBlockParamAt(index) { + const params = []; + for (let i = 0; i < index; i++) { + params.push('some value'); + } + return params; +} + +/** + * Some JSON-RPC endpoints take a "block" param (example: `eth_blockNumber`) + * which can optionally be left out. Additionally, the endpoint may support some + * number of arguments, although the "block" param will always be last, even if + * it is optional. Given this, this function builds a `params` array for such an + * endpoint with the given "block" param added at the end. + * + * @param {number} index - The index within the `params` array to add the + * "block" param. + * @param {any} blockParam - The desired "block" param to add. + * @returns {any[]} The mock params. + */ +export function buildMockParamsWithBlockParamAt(index, blockParam) { + const params = buildMockParamsWithoutBlockParamAt(index); + params.push(blockParam); + return params; +} + +/** + * Returns a partial JSON-RPC request object, with the "block" param replaced + * with the given value. + * + * @param {object} request - The request object. + * @param {string} request.method - The request method. + * @param {params} [request.params] - The request params. + * @param {number} blockParamIndex - The index within the `params` array of the + * block param. + * @param {any} blockParam - The desired block param value. + * @returns {object} The updated request object. + */ +export function buildRequestWithReplacedBlockParam( + { method, params = [] }, + blockParamIndex, + blockParam, +) { + const updatedParams = params.slice(); + updatedParams[blockParamIndex] = blockParam; + return { method, params: updatedParams }; +} diff --git a/app/scripts/controllers/network/provider-api-tests/shared-tests.js b/app/scripts/controllers/network/provider-api-tests/shared-tests.js new file mode 100644 index 000000000..aafa2155a --- /dev/null +++ b/app/scripts/controllers/network/provider-api-tests/shared-tests.js @@ -0,0 +1,708 @@ +/* eslint-disable jest/require-top-level-describe, jest/no-export, jest/no-identical-title */ + +import { fill } from 'lodash'; +import { + withMockedInfuraCommunications, + withInfuraClient, + buildMockParamsWithoutBlockParamAt, + buildMockParamsWithBlockParamAt, + buildRequestWithReplacedBlockParam, +} from './helpers'; + +export function testsForRpcMethodNotHandledByMiddleware( + method, + { numberOfParameters }, +) { + it('attempts to pass the request off to Infura', async () => { + const request = { + method, + params: fill(Array(numberOfParameters), 'some value'), + }; + const expectedResult = 'the result'; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request, + response: { result: expectedResult }, + }); + const actualResult = await withInfuraClient(({ makeRpcCall }) => + makeRpcCall(request), + ); + + expect(actualResult).toStrictEqual(expectedResult); + }); + }); +} + +/** + * Defines tests which exercise the behavior exhibited by an RPC method which is + * assumed to not take a block parameter. Even if it does, the value of this + * parameter will not be used in determining how to cache the method. + * + * @param method - The name of the RPC method under test. + */ +export function testsForRpcMethodAssumingNoBlockParam(method) { + it('does not hit Infura more than once for identical requests', async () => { + const requests = [{ method }, { method }]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + + it('hits Infura and does not reuse the result of a previous request if the latest block number was updated since', async () => { + const requests = [{ method }, { method }]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // Note that we have to mock these requests in a specific order. The + // first block tracker request occurs because of the first RPC request. + // The second block tracker request, however, does not occur because of + // the second RPC request, but rather because we call `clock.runAll()` + // below. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(async (client) => { + const firstResult = await client.makeRpcCall(requests[0]); + // Proceed to the next iteration of the block tracker so that a new + // block is fetched and the current block is updated. + client.clock.runAll(); + const secondResult = await client.makeRpcCall(requests[1]); + return [firstResult, secondResult]; + }); + + expect(results).toStrictEqual(mockResults); + }); + }); + + it.each([null, undefined, '\u003cnil\u003e'])( + 'does not reuse the result of a previous request if it was `%s`', + async (emptyValue) => { + const requests = [{ method }, { method }]; + const mockResults = [emptyValue, 'some result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }, + ); +} + +/** + * Defines tests which exercise the behavior exhibited by an RPC method that + * use `blockHash` in the response data to determine whether the response is + * cacheable. + * + * @param method - The name of the RPC method under test. + */ +export function testsForRpcMethodsThatCheckForBlockHashInResponse(method) { + it('does not hit Infura more than once for identical requests and it has a valid blockHash', async () => { + const requests = [{ method }, { method }]; + const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }]; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + + it('hits Infura and does not reuse the result of a previous request if the latest block number was updated since', async () => { + const requests = [{ method }, { method }]; + const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }]; + + await withMockedInfuraCommunications(async (comms) => { + // Note that we have to mock these requests in a specific order. The + // first block tracker request occurs because of the first RPC + // request. The second block tracker request, however, does not occur + // because of the second RPC request, but rather because we call + // `clock.runAll()` below. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(async (client) => { + const firstResult = await client.makeRpcCall(requests[0]); + // Proceed to the next iteration of the block tracker so that a new + // block is fetched and the current block is updated. + client.clock.runAll(); + const secondResult = await client.makeRpcCall(requests[1]); + return [firstResult, secondResult]; + }); + + expect(results).toStrictEqual(mockResults); + }); + }); + + it.each([null, undefined, '\u003cnil\u003e'])( + 'does not reuse the result of a previous request if it was `%s`', + async (emptyValue) => { + const requests = [{ method }, { method }]; + const mockResults = [emptyValue, { blockHash: '0x100' }]; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }, + ); + + it('does not reuse the result of a previous request if result.blockHash was null', async () => { + const requests = [{ method }, { method }]; + const mockResults = [ + { blockHash: null, extra: 'some value' }, + { blockHash: '0x100', extra: 'some other value' }, + ]; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }); + + it('does not reuse the result of a previous request if result.blockHash was undefined', async () => { + const requests = [{ method }, { method }]; + const mockResults = [ + { extra: 'some value' }, + { blockHash: '0x100', extra: 'some other value' }, + ]; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }); + + it('does not reuse the result of a previous request if result.blockHash was "0x0000000000000000000000000000000000000000000000000000000000000000"', async () => { + const requests = [{ method }, { method }]; + const mockResults = [ + { + blockHash: + '0x0000000000000000000000000000000000000000000000000000000000000000', + extra: 'some value', + }, + { blockHash: '0x100', extra: 'some other value' }, + ]; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }); +} + +/** + * Defines tests which exercise the behavior exhibited by an RPC method that + * takes a block parameter. The value of this parameter can be either a block + * number or a block tag ("latest", "earliest", or "pending") and affects how + * the method is cached. + */ +/* eslint-disable-next-line jest/no-export */ +export function testsForRpcMethodSupportingBlockParam( + method, + { blockParamIndex }, +) { + describe.each([ + ['given no block tag', 'none'], + ['given a block tag of "latest"', 'latest', 'latest'], + ])('%s', (_desc, blockParamType, blockParam) => { + const params = + blockParamType === 'none' + ? buildMockParamsWithoutBlockParamAt(blockParamIndex) + : buildMockParamsWithBlockParamAt(blockParamIndex, blockParam); + + it('does not hit Infura more than once for identical requests', async () => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the block-cache + // middleware will request the latest block number through the block + // tracker to determine the cache key. Later, the block-ref + // middleware will request the latest block number again to resolve + // the value of "latest", but the block number is cached once made, + // so we only need to mock the request once. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); + // The block-ref middleware will make the request as specified + // except that the block param is replaced with the latest block + // number. + comms.mockSuccessfulInfuraRpcCall({ + request: buildRequestWithReplacedBlockParam( + requests[0], + blockParamIndex, + '0x100', + ), + response: { result: mockResults[0] }, + }); + // Note that the block-ref middleware will still allow the original + // request to go through. + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + + it('hits Infura and does not reuse the result of a previous request if the latest block number was updated since', async () => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // Note that we have to mock these requests in a specific order. + // The first block tracker request occurs because of the first RPC + // request. The second block tracker request, however, does not + // occur because of the second RPC request, but rather because we + // call `clock.runAll()` below. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); + // The block-ref middleware will make the request as specified + // except that the block param is replaced with the latest block + // number. + comms.mockSuccessfulInfuraRpcCall({ + request: buildRequestWithReplacedBlockParam( + requests[0], + blockParamIndex, + '0x100', + ), + response: { result: mockResults[0] }, + }); + // Note that the block-ref middleware will still allow the original + // request to go through. + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockNextBlockTrackerRequest({ blockNumber: '0x200' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + // The previous two requests will happen again, with a different block + // number, in the same order. + comms.mockSuccessfulInfuraRpcCall({ + request: buildRequestWithReplacedBlockParam( + requests[0], + blockParamIndex, + '0x200', + ), + response: { result: mockResults[1] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(async (client) => { + const firstResult = await client.makeRpcCall(requests[0]); + // Proceed to the next iteration of the block tracker so that a + // new block is fetched and the current block is updated. + client.clock.runAll(); + const secondResult = await client.makeRpcCall(requests[1]); + return [firstResult, secondResult]; + }); + + expect(results).toStrictEqual(mockResults); + }); + }); + + it.each([null, undefined, '\u003cnil\u003e'])( + 'does not reuse the result of a previous request if it was `%s`', + async (emptyValue) => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = [emptyValue, 'some result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the + // block-cache middleware will request the latest block number + // through the block tracker to determine the cache key. Later, + // the block-ref middleware will request the latest block number + // again to resolve the value of "latest", but the block number is + // cached once made, so we only need to mock the request once. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); + // The block-ref middleware will make the request as specified + // except that the block param is replaced with the latest block + // number. + comms.mockSuccessfulInfuraRpcCall({ + request: buildRequestWithReplacedBlockParam( + requests[0], + blockParamIndex, + '0x100', + ), + response: { result: mockResults[0] }, + }); + // Note that the block-ref middleware will still allow the original + // request to go through. + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + // The previous two requests will happen again, in the same order. + comms.mockSuccessfulInfuraRpcCall({ + request: buildRequestWithReplacedBlockParam( + requests[0], + blockParamIndex, + '0x100', + ), + response: { result: mockResults[1] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }, + ); + }); + + describe.each([ + ['given a block tag of "earliest"', 'earliest', 'earliest'], + ['given a block number', 'block number', '0x100'], + ])('%s', (_desc, blockParamType, blockParam) => { + const params = buildMockParamsWithBlockParamAt(blockParamIndex, blockParam); + + it('does not hit Infura more than once for identical requests', async () => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the block-cache + // middleware will request the latest block number through the block + // tracker to determine the cache key. This block number doesn't + // matter. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + + it('reuses the result of a previous request even if the latest block number was updated since', async () => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // Note that we have to mock these requests in a specific order. The + // first block tracker request occurs because of the first RPC + // request. The second block tracker request, however, does not + // occur because of the second RPC request, but rather because we + // call `clock.runAll()` below. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(async (client) => { + const firstResult = await client.makeRpcCall(requests[0]); + // Proceed to the next iteration of the block tracker so that a + // new block is fetched and the current block is updated. + client.clock.runAll(); + const secondResult = await client.makeRpcCall(requests[1]); + return [firstResult, secondResult]; + }); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + + it.each([null, undefined, '\u003cnil\u003e'])( + 'does not reuse the result of a previous request if it was `%s`', + async (emptyValue) => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = [emptyValue, 'some result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }, + ); + + if (blockParamType === 'earliest') { + it('treats "0x00" as a synonym for "earliest"', async () => { + const requests = [ + { + method, + params: buildMockParamsWithBlockParamAt( + blockParamIndex, + blockParam, + ), + }, + { + method, + params: buildMockParamsWithBlockParamAt(blockParamIndex, '0x00'), + }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest + // block number is retrieved through the block tracker first. It + // doesn't matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + } + + if (blockParamType === 'block number') { + it('does not reuse the result of a previous request if it was made with different arguments than this one', async () => { + await withMockedInfuraCommunications(async (comms) => { + const requests = [ + { + method, + params: buildMockParamsWithBlockParamAt(blockParamIndex, '0x100'), + }, + { + method, + params: buildMockParamsWithBlockParamAt(blockParamIndex, '0x200'), + }, + ]; + + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: 'first result' }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: 'second result' }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(['first result', 'second result']); + }); + }); + } + }); + + describe('given a block tag of "pending"', () => { + const params = buildMockParamsWithBlockParamAt(blockParamIndex, 'pending'); + + it('hits Infura on all calls and does not cache anything', async () => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest + // block number is retrieved through the block tracker first. It + // doesn't matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }); + }); +} diff --git a/app/scripts/controllers/permissions/background-api.js b/app/scripts/controllers/permissions/background-api.js index 0ed2354d1..1d1b19c67 100644 --- a/app/scripts/controllers/permissions/background-api.js +++ b/app/scripts/controllers/permissions/background-api.js @@ -14,9 +14,7 @@ export function getPermissionBackgroundApiMethods(permissionController) { ); if (existing.value.includes(account)) { - throw new Error( - `eth_accounts permission for origin "${origin}" already permits account "${account}".`, - ); + return; } permissionController.updateCaveat( @@ -35,9 +33,7 @@ export function getPermissionBackgroundApiMethods(permissionController) { ); if (!existing.value.includes(account)) { - throw new Error( - `eth_accounts permission for origin "${origin}" already does not permit account "${account}".`, - ); + return; } const remainingAccounts = existing.value.filter( diff --git a/app/scripts/controllers/permissions/background-api.test.js b/app/scripts/controllers/permissions/background-api.test.js index 8857835b4..a7f433eb1 100644 --- a/app/scripts/controllers/permissions/background-api.test.js +++ b/app/scripts/controllers/permissions/background-api.test.js @@ -34,7 +34,7 @@ describe('permission background API methods', () => { ); }); - it('throws if the specified account is already permitted', () => { + it('does not add a permitted account', () => { const permissionController = { getCaveat: jest.fn().mockImplementationOnce(() => { return { type: CaveatTypes.restrictReturnedAccounts, value: ['0x1'] }; @@ -42,14 +42,9 @@ describe('permission background API methods', () => { updateCaveat: jest.fn(), }; - expect(() => - getPermissionBackgroundApiMethods( - permissionController, - ).addPermittedAccount('foo.com', '0x1'), - ).toThrow( - `eth_accounts permission for origin "foo.com" already permits account "0x1".`, - ); - + getPermissionBackgroundApiMethods( + permissionController, + ).addPermittedAccount('foo.com', '0x1'); expect(permissionController.getCaveat).toHaveBeenCalledTimes(1); expect(permissionController.getCaveat).toHaveBeenCalledWith( 'foo.com', @@ -128,7 +123,7 @@ describe('permission background API methods', () => { expect(permissionController.updateCaveat).not.toHaveBeenCalled(); }); - it('throws if the specified account is not permitted', () => { + it('does not call permissionController.updateCaveat if the specified account is not permitted', () => { const permissionController = { getCaveat: jest.fn().mockImplementationOnce(() => { return { type: CaveatTypes.restrictReturnedAccounts, value: ['0x1'] }; @@ -137,14 +132,9 @@ describe('permission background API methods', () => { updateCaveat: jest.fn(), }; - expect(() => - getPermissionBackgroundApiMethods( - permissionController, - ).removePermittedAccount('foo.com', '0x2'), - ).toThrow( - `eth_accounts permission for origin "foo.com" already does not permit account "0x2".`, - ); - + getPermissionBackgroundApiMethods( + permissionController, + ).removePermittedAccount('foo.com', '0x2'); expect(permissionController.getCaveat).toHaveBeenCalledTimes(1); expect(permissionController.getCaveat).toHaveBeenCalledWith( 'foo.com', diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 18ac909a7..949c8de8d 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -5,7 +5,7 @@ import { ethers } from 'ethers'; import log from 'loglevel'; import { IPFS_DEFAULT_GATEWAY_URL, - NETWORK_TYPE_TO_ID_MAP, + BUILT_IN_NETWORKS, } from '../../../shared/constants/network'; import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils'; import { LEDGER_TRANSPORT_TYPES } from '../../../shared/constants/hardware-wallets'; @@ -424,9 +424,9 @@ 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 builtInProviderNetworkIds = Object.values(BUILT_IN_NETWORKS).map( + (ids) => ids.networkId, + ); const otherRpcEntries = rpcList.filter( (entry) => entry.rpcUrl !== newRpcDetails.rpcUrl, ); diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index 8696f7997..ee19e216f 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -4,7 +4,7 @@ import { ControllerMessenger, TokenListController, } from '@metamask/controllers'; -import { MAINNET_CHAIN_ID } from '../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import PreferencesController from './preferences'; import NetworkController from './network'; @@ -18,7 +18,7 @@ describe('preferences controller', function () { beforeEach(function () { const sandbox = sinon.createSandbox(); - currentChainId = MAINNET_CHAIN_ID; + currentChainId = CHAIN_IDS.MAINNET; const networkControllerProviderConfig = { getAccounts: () => undefined, }; diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index e49eff3c5..e1baff4e1 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -4,8 +4,6 @@ import BigNumber from 'bignumber.js'; import { ObservableStore } from '@metamask/obs-store'; import { mapValues, cloneDeep } from 'lodash'; import abi from 'human-standard-token-abi'; -import { calcTokenAmount } from '../../../ui/helpers/utils/token-util'; -import { calcGasTotal } from '../../../ui/pages/send/send.utils'; import { conversionUtil, decGWEIToHexWEI, @@ -30,10 +28,15 @@ import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils' import { fetchTradesInfo as defaultFetchTradesInfo, getBaseApi, -} from '../../../ui/pages/swaps/swaps.util'; -import fetchWithCache from '../../../ui/helpers/utils/fetch-with-cache'; +} from '../../../shared/lib/swaps-utils'; +import fetchWithCache from '../../../shared/lib/fetch-with-cache'; import { MINUTE, SECOND } from '../../../shared/constants/time'; import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; +import { + calcGasTotal, + calcTokenAmount, +} from '../../../shared/lib/transactions-controller-utils'; + import { NETWORK_EVENTS } from './network'; // The MAX_GAS_LIMIT is a number that is higher than the maximum gas costs we have observed on any aggregator diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps.test.js index e34dff016..ea3be085a 100644 --- a/app/scripts/controllers/swaps.test.js +++ b/app/scripts/controllers/swaps.test.js @@ -4,11 +4,7 @@ import sinon from 'sinon'; import { ethers } from 'ethers'; import { mapValues } from 'lodash'; import BigNumber from 'bignumber.js'; -import { - ROPSTEN_NETWORK_ID, - MAINNET_NETWORK_ID, - MAINNET_CHAIN_ID, -} from '../../../shared/constants/network'; +import { CHAIN_IDS, NETWORK_IDS } from '../../../shared/constants/network'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps'; import { createTestProviderTools } from '../../../test/stub/provider'; import { SECOND } from '../../../shared/constants/time'; @@ -82,7 +78,7 @@ const MOCK_FETCH_METADATA = { symbol: 'FOO', decimals: 18, }, - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, }; const MOCK_TOKEN_RATES_STORE = () => ({ @@ -104,7 +100,7 @@ function getMockNetworkController() { store: { getState: () => { return { - network: ROPSTEN_NETWORK_ID, + network: NETWORK_IDS.ROPSTEN, }; }, }, @@ -149,7 +145,7 @@ const EMPTY_INIT_STATE = { const sandbox = sinon.createSandbox(); const fetchTradesInfoStub = sandbox.stub(); const getCurrentChainIdStub = sandbox.stub(); -getCurrentChainIdStub.returns(MAINNET_CHAIN_ID); +getCurrentChainIdStub.returns(CHAIN_IDS.MAINNET); const getEIP1559GasFeeEstimatesStub = sandbox.stub(() => { return { gasFeeEstimates: { @@ -225,7 +221,7 @@ describe('SwapsController', function () { const currentEthersInstance = swapsController.ethersProvider; const onNetworkDidChange = networkController.on.getCall(0).args[1]; - onNetworkDidChange(MAINNET_NETWORK_ID); + onNetworkDidChange(NETWORK_IDS.MAINNET); const newEthersInstance = swapsController.ethersProvider; assert.notStrictEqual( @@ -273,7 +269,7 @@ describe('SwapsController', function () { const currentEthersInstance = swapsController.ethersProvider; const onNetworkDidChange = networkController.on.getCall(0).args[1]; - onNetworkDidChange(ROPSTEN_NETWORK_ID); + onNetworkDidChange(NETWORK_IDS.ROPSTEN); const newEthersInstance = swapsController.ethersProvider; assert.strictEqual( @@ -726,7 +722,7 @@ describe('SwapsController', function () { allowanceStub.calledOnceWithExactly( MOCK_FETCH_PARAMS.sourceToken, MOCK_FETCH_PARAMS.fromAddress, - MAINNET_CHAIN_ID, + CHAIN_IDS.MAINNET, ), true, ); diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 8e7a2d62f..c34b5816d 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -17,13 +17,6 @@ import { addHexPrefix, getChainType, } from '../../lib/util'; -import { calcGasTotal } from '../../../../ui/pages/send/send.utils'; -import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/pages/swaps/swaps.util'; -import { - hexWEIToDecGWEI, - decimalToHex, - hexWEIToDecETH, -} from '../../../../ui/helpers/utils/conversions.util'; import { TRANSACTION_STATUSES, TRANSACTION_TYPES, @@ -32,7 +25,6 @@ import { TRANSACTION_ENVELOPE_TYPES, TRANSACTION_EVENTS, } from '../../../../shared/constants/transaction'; -import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../ui/helpers/constants/transactions'; import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; import { GAS_LIMITS, @@ -46,10 +38,8 @@ import { isSwapsDefaultTokenAddress } from '../../../../shared/modules/swaps.uti import { EVENT } from '../../../../shared/constants/metametrics'; import { HARDFORKS, - MAINNET, - SEPOLIA, - NETWORK_TYPE_RPC, CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP, + NETWORK_TYPES, } from '../../../../shared/constants/network'; import { determineTransactionAssetType, @@ -58,6 +48,14 @@ import { isEIP1559Transaction, } from '../../../../shared/modules/transaction.utils'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; +import { + calcGasTotal, + decimalToHex, + getSwapsTokensReceivedFromTxMeta, + hexWEIToDecETH, + hexWEIToDecGWEI, + TRANSACTION_ENVELOPE_TYPE_NAMES, +} from '../../../../shared/lib/transactions-controller-utils'; import TransactionStateManager from './tx-state-manager'; import TxGasUtil from './tx-gas-utils'; import PendingTransactionTracker from './pending-tx-tracker'; @@ -258,7 +256,7 @@ export default class TransactionController extends EventEmitter { // type will be one of our default network names or 'rpc'. the default // network names are sufficient configuration, simply pass the name as the // chain argument in the constructor. - if (type !== NETWORK_TYPE_RPC && type !== SEPOLIA) { + if (type !== NETWORK_TYPES.RPC && type !== NETWORK_TYPES.SEPOLIA) { return new Common({ chain: type, hardfork, @@ -287,7 +285,11 @@ export default class TransactionController extends EventEmitter { networkId: networkId === 'loading' ? 0 : parseInt(networkId, 10), }; - return Common.forCustomChain(MAINNET, customChainParams, hardfork); + return Common.forCustomChain( + NETWORK_TYPES.MAINNET, + customChainParams, + hardfork, + ); } /** @@ -299,7 +301,11 @@ export default class TransactionController extends EventEmitter { addTransaction(txMeta) { this.txStateManager.addTransaction(txMeta); this.emit(`${txMeta.id}:unapproved`, txMeta); - this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.ADDED); + this._trackTransactionMetricsEvent( + txMeta, + TRANSACTION_EVENTS.ADDED, + txMeta.actionId, + ); } /** @@ -773,7 +779,7 @@ export default class TransactionController extends EventEmitter { ); } - // In transaction is found for same action id, do not create a new transaction. + // If a transaction is found with the same actionId, do not create a new speed-up transaction. if (actionId) { let existingTxMeta = this.txStateManager.getTransactionWithActionId(actionId); @@ -1162,13 +1168,23 @@ export default class TransactionController extends EventEmitter { * params instead of allowing this method to generate them * @param options * @param options.estimatedBaseFee + * @param options.actionId * @returns {txMeta} */ async createCancelTransaction( originalTxId, customGasSettings, - { estimatedBaseFee } = {}, + { estimatedBaseFee, actionId } = {}, ) { + // If transaction is found for same action id, do not create a new cancel transaction. + if (actionId) { + const existingTxMeta = + this.txStateManager.getTransactionWithActionId(actionId); + if (existingTxMeta) { + return existingTxMeta; + } + } + const originalTxMeta = this.txStateManager.getTransaction(originalTxId); const { txParams } = originalTxMeta; const { from, nonce } = txParams; @@ -1196,6 +1212,7 @@ export default class TransactionController extends EventEmitter { loadingDefaults: false, status: TRANSACTION_STATUSES.APPROVED, type: TRANSACTION_TYPES.CANCEL, + actionId, }); if (estimatedBaseFee) { @@ -1203,7 +1220,7 @@ export default class TransactionController extends EventEmitter { } this.addTransaction(newTxMeta); - await this.approveTransaction(newTxMeta.id); + await this.approveTransaction(newTxMeta.id, actionId); return newTxMeta; } @@ -1218,13 +1235,23 @@ export default class TransactionController extends EventEmitter { * params instead of allowing this method to generate them * @param options * @param options.estimatedBaseFee + * @param options.actionId * @returns {txMeta} */ async createSpeedUpTransaction( originalTxId, customGasSettings, - { estimatedBaseFee } = {}, + { estimatedBaseFee, actionId } = {}, ) { + // If transaction is found for same action id, do not create a new speed-up transaction. + if (actionId) { + const existingTxMeta = + this.txStateManager.getTransactionWithActionId(actionId); + if (existingTxMeta) { + return existingTxMeta; + } + } + const originalTxMeta = this.txStateManager.getTransaction(originalTxId); const { txParams } = originalTxMeta; @@ -1243,6 +1270,7 @@ export default class TransactionController extends EventEmitter { status: TRANSACTION_STATUSES.APPROVED, type: TRANSACTION_TYPES.RETRY, originalType: originalTxMeta.type, + actionId, }); if (estimatedBaseFee) { @@ -1250,7 +1278,7 @@ export default class TransactionController extends EventEmitter { } this.addTransaction(newTxMeta); - await this.approveTransaction(newTxMeta.id); + await this.approveTransaction(newTxMeta.id, actionId); return newTxMeta; } @@ -1270,13 +1298,14 @@ export default class TransactionController extends EventEmitter { * updates and approves the transaction * * @param {object} txMeta + * @param {string} actionId */ - async updateAndApproveTransaction(txMeta) { + async updateAndApproveTransaction(txMeta, actionId) { this.txStateManager.updateTransaction( txMeta, 'confTx: user approved transaction', ); - await this.approveTransaction(txMeta.id); + await this.approveTransaction(txMeta.id, actionId); } /** @@ -1287,13 +1316,15 @@ 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 + * @param {string} actionId - actionId passed from UI */ - async approveTransaction(txId) { + async approveTransaction(txId, actionId) { // TODO: Move this safety out of this function. // Since this transaction is async, // we need to keep track of what is currently being signed, // So that we do not increment nonce + resubmit something // that is already being incremented & signed. + const txMeta = this.txStateManager.getTransaction(txId); if (this.inProcessOfSigning.has(txId)) { return; } @@ -1303,8 +1334,6 @@ export default class TransactionController extends EventEmitter { // approve this.txStateManager.setTxStatusApproved(txId); // get next nonce - const txMeta = this.txStateManager.getTransaction(txId); - const fromAddress = txMeta.txParams.from; // wait for a nonce let { customNonceValue } = txMeta; @@ -1331,14 +1360,18 @@ export default class TransactionController extends EventEmitter { ); // sign transaction const rawTx = await this.signTransaction(txId); - await this.publishTransaction(txId, rawTx); - this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.APPROVED); + await this.publishTransaction(txId, rawTx, actionId); + this._trackTransactionMetricsEvent( + txMeta, + TRANSACTION_EVENTS.APPROVED, + actionId, + ); // must set transaction to submitted/failed before releasing lock nonceLock.releaseLock(); } catch (err) { // this is try-catch wrapped so that we can guarantee that the nonceLock is released try { - this._failTransaction(txId, err); + this._failTransaction(txId, err, actionId); } catch (err2) { log.error(err2); } @@ -1467,8 +1500,9 @@ export default class TransactionController extends EventEmitter { * @param {number} txId - the tx's Id * @param {string} rawTx - the hex string of the serialized signed transaction * @returns {Promise} + * @param {number} actionId - actionId passed from UI */ - async publishTransaction(txId, rawTx) { + async publishTransaction(txId, rawTx, actionId) { const txMeta = this.txStateManager.getTransaction(txId); txMeta.rawTx = rawTx; if (txMeta.type === TRANSACTION_TYPES.SWAP) { @@ -1494,7 +1528,11 @@ export default class TransactionController extends EventEmitter { this.txStateManager.setTxStatusSubmitted(txId); - this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.SUBMITTED); + this._trackTransactionMetricsEvent( + txMeta, + TRANSACTION_EVENTS.SUBMITTED, + actionId, + ); } async updatePostTxBalance({ txMeta, txId, numberOfAttempts = 6 }) { @@ -1583,6 +1621,7 @@ export default class TransactionController extends EventEmitter { this._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.FINALIZED, + undefined, metricsParams, ); @@ -1643,6 +1682,7 @@ export default class TransactionController extends EventEmitter { this._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.FINALIZED, + undefined, metricsParams, ); @@ -1666,12 +1706,17 @@ export default class TransactionController extends EventEmitter { * Convenience method for the ui thats sets the transaction to rejected * * @param {number} txId - the tx's Id + * @param {string} actionId - actionId passed from UI * @returns {Promise} */ - async cancelTransaction(txId) { + async cancelTransaction(txId, actionId) { const txMeta = this.txStateManager.getTransaction(txId); this.txStateManager.setTxStatusRejected(txId); - this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.REJECTED); + this._trackTransactionMetricsEvent( + txMeta, + TRANSACTION_EVENTS.REJECTED, + actionId, + ); } /** @@ -1694,8 +1739,9 @@ export default class TransactionController extends EventEmitter { * @param {number} transactionId - The transaction id to create the event * fragment for * @param {valueOf} event - event type to create + * @param {string} actionId - actionId passed from UI */ - async createTransactionEventFragment(transactionId, event) { + async createTransactionEventFragment(transactionId, event, actionId) { const txMeta = this.txStateManager.getTransaction(transactionId); const { properties, sensitiveProperties } = await this._buildEventFragmentProperties(txMeta); @@ -1704,6 +1750,7 @@ export default class TransactionController extends EventEmitter { event, properties, sensitiveProperties, + actionId, ); } @@ -1794,10 +1841,9 @@ export default class TransactionController extends EventEmitter { }, }) .forEach((txMeta) => { - const txSignError = new Error( - 'Transaction found as "approved" during boot - possibly stuck during signing', - ); - this._failTransaction(txMeta.id, txSignError); + // Line below will try to publish transaction which is in + // APPROVED state at the time of controller bootup + this.approveTransaction(txMeta); }); } @@ -2307,6 +2353,7 @@ export default class TransactionController extends EventEmitter { * triggered fragment creation * @param {object} properties - properties to include in the fragment * @param {object} [sensitiveProperties] - sensitive properties to include in + * @param {object} [actionId] - actionId passed from UI * the fragment */ _createTransactionEventFragment( @@ -2314,6 +2361,7 @@ export default class TransactionController extends EventEmitter { event, properties, sensitiveProperties, + actionId, ) { const isSubmitted = [ TRANSACTION_EVENTS.FINALIZED, @@ -2348,6 +2396,7 @@ export default class TransactionController extends EventEmitter { sensitiveProperties, persist: true, uniqueIdentifier, + actionId, }); break; // If for some reason an approval or rejection occurs without the added @@ -2368,6 +2417,7 @@ export default class TransactionController extends EventEmitter { sensitiveProperties, persist: true, uniqueIdentifier, + actionId, }); break; // When a transaction is submitted it will always result in updating @@ -2389,6 +2439,7 @@ export default class TransactionController extends EventEmitter { sensitiveProperties, persist: true, uniqueIdentifier, + actionId, }); break; // If for some reason a transaction is finalized without the submitted @@ -2407,6 +2458,7 @@ export default class TransactionController extends EventEmitter { sensitiveProperties, persist: true, uniqueIdentifier, + actionId, }); break; default: @@ -2421,9 +2473,15 @@ export default class TransactionController extends EventEmitter { * * @param {object} txMeta - the txMeta object * @param {TransactionMetaMetricsEventString} event - the name of the transaction event + * @param {string} actionId - actionId passed from UI * @param {object} extraParams - optional props and values to include in sensitiveProperties */ - async _trackTransactionMetricsEvent(txMeta, event, extraParams = {}) { + async _trackTransactionMetricsEvent( + txMeta, + event, + actionId, + extraParams = {}, + ) { if (!txMeta) { return; } @@ -2437,6 +2495,7 @@ export default class TransactionController extends EventEmitter { event, properties, sensitiveProperties, + actionId, ); let id; @@ -2486,19 +2545,29 @@ export default class TransactionController extends EventEmitter { return gasValuesInGwei; } - _failTransaction(txId, error) { + _failTransaction(txId, error, actionId) { this.txStateManager.setTxStatusFailed(txId, error); const txMeta = this.txStateManager.getTransaction(txId); - this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.FINALIZED, { - error: error.message, - }); + this._trackTransactionMetricsEvent( + txMeta, + TRANSACTION_EVENTS.FINALIZED, + actionId, + { + error: error.message, + }, + ); } _dropTransaction(txId) { this.txStateManager.setTxStatusDropped(txId); const txMeta = this.txStateManager.getTransaction(txId); - this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.FINALIZED, { - dropped: true, - }); + this._trackTransactionMetricsEvent( + txMeta, + TRANSACTION_EVENTS.FINALIZED, + undefined, + { + dropped: true, + }, + ); } } diff --git a/app/scripts/controllers/transactions/index.test.js b/app/scripts/controllers/transactions/index.test.js index 4bbb3046d..4bd5da250 100644 --- a/app/scripts/controllers/transactions/index.test.js +++ b/app/scripts/controllers/transactions/index.test.js @@ -25,9 +25,9 @@ import { GAS_ESTIMATE_TYPES, GAS_RECOMMENDATIONS, } from '../../../../shared/constants/gas'; -import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../ui/helpers/constants/transactions'; import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; +import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils'; import TransactionController from '.'; const noop = () => true; @@ -36,7 +36,7 @@ const currentChainId = '0x2a'; const providerConfig = { type: 'kovan', }; - +const actionId = 'DUMMY_ACTION_ID'; const VALID_ADDRESS = '0x0000000000000000000000000000000000000000'; const VALID_ADDRESS_TWO = '0x0000000000000000000000000000000000000001'; @@ -467,6 +467,105 @@ describe('Transaction Controller', function () { }); }); + describe('#createCancelTransaction', function () { + const selectedAddress = '0x1678a085c290ebd122dc42cba69373b5953b831d'; + const recipientAddress = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'; + + let getSelectedAddress, + getPermittedAccounts, + getDefaultGasFees, + getDefaultGasLimit; + beforeEach(function () { + const hash = + '0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8'; + providerResultStub.eth_sendRawTransaction = hash; + + getSelectedAddress = sinon + .stub(txController, 'getSelectedAddress') + .returns(selectedAddress); + getDefaultGasFees = sinon + .stub(txController, '_getDefaultGasFees') + .returns({}); + getDefaultGasLimit = sinon + .stub(txController, '_getDefaultGasLimit') + .returns({}); + getPermittedAccounts = sinon + .stub(txController, 'getPermittedAccounts') + .returns([selectedAddress]); + }); + + afterEach(function () { + getSelectedAddress.restore(); + getPermittedAccounts.restore(); + getDefaultGasFees.restore(); + getDefaultGasLimit.restore(); + }); + + it('should add an cancel transaction and return a valid txMeta', async function () { + const txMeta = await txController.addUnapprovedTransaction({ + from: selectedAddress, + to: recipientAddress, + }); + await txController.approveTransaction(txMeta.id); + const cancelTxMeta = await txController.createCancelTransaction( + txMeta.id, + {}, + { actionId: 12345 }, + ); + assert.equal(cancelTxMeta.type, TRANSACTION_TYPES.CANCEL); + const memTxMeta = txController.txStateManager.getTransaction( + cancelTxMeta.id, + ); + assert.deepEqual(cancelTxMeta, memTxMeta); + }); + + it('should add only 1 cancel transaction when called twice with same actionId', async function () { + const txMeta = await txController.addUnapprovedTransaction({ + from: selectedAddress, + to: recipientAddress, + }); + await txController.approveTransaction(txMeta.id); + await txController.createCancelTransaction( + txMeta.id, + {}, + { actionId: 12345 }, + ); + const transactionCount1 = + txController.txStateManager.getTransactions().length; + await txController.createCancelTransaction( + txMeta.id, + {}, + { actionId: 12345 }, + ); + const transactionCount2 = + txController.txStateManager.getTransactions().length; + assert.equal(transactionCount1, transactionCount2); + }); + + it('should add multiple transactions when called with different actionId', async function () { + const txMeta = await txController.addUnapprovedTransaction({ + from: selectedAddress, + to: recipientAddress, + }); + await txController.approveTransaction(txMeta.id); + await txController.createCancelTransaction( + txMeta.id, + {}, + { actionId: 12345 }, + ); + const transactionCount1 = + txController.txStateManager.getTransactions().length; + await txController.createCancelTransaction( + txMeta.id, + {}, + { actionId: 11111 }, + ); + const transactionCount2 = + txController.txStateManager.getTransactions().length; + assert.equal(transactionCount1 + 1, transactionCount2); + }); + }); + describe('#addTxGasDefaults', function () { it('should add the tx defaults if their are none', async function () { txController.txStateManager._addTransactionsToState([ @@ -1071,11 +1170,35 @@ describe('Transaction Controller', function () { let approveTransactionSpy; let txParams; let expectedTxParams; + const selectedAddress = '0x1678a085c290ebd122dc42cba69373b5953b831d'; + const recipientAddress = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'; + + let getSelectedAddress, + getPermittedAccounts, + getDefaultGasFees, + getDefaultGasLimit; beforeEach(function () { addTransactionSpy = sinon.spy(txController, 'addTransaction'); approveTransactionSpy = sinon.spy(txController, 'approveTransaction'); + const hash = + '0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8'; + providerResultStub.eth_sendRawTransaction = hash; + + getSelectedAddress = sinon + .stub(txController, 'getSelectedAddress') + .returns(selectedAddress); + getDefaultGasFees = sinon + .stub(txController, '_getDefaultGasFees') + .returns({}); + getDefaultGasLimit = sinon + .stub(txController, '_getDefaultGasLimit') + .returns({}); + getPermittedAccounts = sinon + .stub(txController, 'getPermittedAccounts') + .returns([selectedAddress]); + txParams = { nonce: '0x00', from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4', @@ -1101,6 +1224,10 @@ describe('Transaction Controller', function () { afterEach(function () { addTransactionSpy.restore(); approveTransactionSpy.restore(); + getSelectedAddress.restore(); + getPermittedAccounts.restore(); + getDefaultGasFees.restore(); + getDefaultGasLimit.restore(); }); it('should call this.addTransaction and this.approveTransaction with the expected args', async function () { @@ -1142,6 +1269,52 @@ describe('Transaction Controller', function () { }, ); }); + + it('should add only 1 speedup transaction when called twice with same actionId', async function () { + const txMeta = await txController.addUnapprovedTransaction({ + from: selectedAddress, + to: recipientAddress, + }); + await txController.approveTransaction(txMeta.id); + await txController.createSpeedUpTransaction( + txMeta.id, + {}, + { actionId: 12345 }, + ); + const transactionCount1 = + txController.txStateManager.getTransactions().length; + await txController.createSpeedUpTransaction( + txMeta.id, + {}, + { actionId: 12345 }, + ); + const transactionCount2 = + txController.txStateManager.getTransactions().length; + assert.equal(transactionCount1, transactionCount2); + }); + + it('should add multiple transactions when called with different actionId', async function () { + const txMeta = await txController.addUnapprovedTransaction({ + from: selectedAddress, + to: recipientAddress, + }); + await txController.approveTransaction(txMeta.id); + await txController.createSpeedUpTransaction( + txMeta.id, + {}, + { actionId: 12345 }, + ); + const transactionCount1 = + txController.txStateManager.getTransactions().length; + await txController.createSpeedUpTransaction( + txMeta.id, + {}, + { actionId: 11111 }, + ); + const transactionCount2 = + txController.txStateManager.getTransactions().length; + assert.equal(transactionCount1 + 1, transactionCount2); + }); }); describe('#signTransaction', function () { @@ -1492,6 +1665,7 @@ describe('Transaction Controller', function () { describe('On transaction created by the user', function () { let txMeta; + before(function () { txMeta = { id: 1, @@ -1517,6 +1691,7 @@ describe('Transaction Controller', function () { it('should create an event fragment when transaction added', async function () { const expectedPayload = { + actionId, initialEvent: 'Transaction Added', successEvent: 'Transaction Approved', failureEvent: 'Transaction Rejected', @@ -1554,6 +1729,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.ADDED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 1); assert.equal(finalizeEventFragmentSpy.callCount, 0); @@ -1568,6 +1744,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.REJECTED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 0); assert.equal(finalizeEventFragmentSpy.callCount, 1); @@ -1585,6 +1762,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.APPROVED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 0); assert.equal(finalizeEventFragmentSpy.callCount, 1); @@ -1600,6 +1778,7 @@ describe('Transaction Controller', function () { it('should create an event fragment when transaction is submitted', async function () { const expectedPayload = { + actionId, initialEvent: 'Transaction Submitted', successEvent: 'Transaction Finalized', uniqueIdentifier: 'transaction-submitted-1', @@ -1636,6 +1815,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.SUBMITTED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 1); assert.equal(finalizeEventFragmentSpy.callCount, 0); @@ -1650,6 +1830,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.FINALIZED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 0); assert.equal(finalizeEventFragmentSpy.callCount, 1); @@ -1691,6 +1872,7 @@ describe('Transaction Controller', function () { it('should create an event fragment when transaction added', async function () { const expectedPayload = { + actionId, initialEvent: 'Transaction Added', successEvent: 'Transaction Approved', failureEvent: 'Transaction Rejected', @@ -1728,6 +1910,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.ADDED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 1); assert.equal(finalizeEventFragmentSpy.callCount, 0); @@ -1743,6 +1926,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.REJECTED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 0); assert.equal(finalizeEventFragmentSpy.callCount, 1); @@ -1761,6 +1945,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.APPROVED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 0); assert.equal(finalizeEventFragmentSpy.callCount, 1); @@ -1776,6 +1961,7 @@ describe('Transaction Controller', function () { it('should create an event fragment when transaction is submitted', async function () { const expectedPayload = { + actionId, initialEvent: 'Transaction Submitted', successEvent: 'Transaction Finalized', uniqueIdentifier: 'transaction-submitted-1', @@ -1812,6 +1998,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.SUBMITTED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 1); assert.equal(finalizeEventFragmentSpy.callCount, 0); @@ -1827,6 +2014,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.FINALIZED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 0); assert.equal(finalizeEventFragmentSpy.callCount, 1); @@ -1860,6 +2048,7 @@ describe('Transaction Controller', function () { }; const expectedPayload = { + actionId, successEvent: 'Transaction Approved', failureEvent: 'Transaction Rejected', uniqueIdentifier: 'transaction-added-1', @@ -1893,6 +2082,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.APPROVED, + actionId, ); assert.equal(createEventFragmentSpy.callCount, 1); assert.deepEqual( @@ -1925,6 +2115,7 @@ describe('Transaction Controller', function () { metamaskNetworkId: currentNetworkId, }; const expectedPayload = { + actionId, initialEvent: 'Transaction Added', successEvent: 'Transaction Approved', failureEvent: 'Transaction Rejected', @@ -1962,6 +2153,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.ADDED, + actionId, { baz: 3.0, foo: 'bar', @@ -2001,6 +2193,7 @@ describe('Transaction Controller', function () { }, }; const expectedPayload = { + actionId, initialEvent: 'Transaction Added', successEvent: 'Transaction Approved', failureEvent: 'Transaction Rejected', @@ -2044,6 +2237,7 @@ describe('Transaction Controller', function () { await txController._trackTransactionMetricsEvent( txMeta, TRANSACTION_EVENTS.ADDED, + actionId, { baz: 3.0, foo: 'bar', diff --git a/app/scripts/controllers/transactions/tx-state-manager.test.js b/app/scripts/controllers/transactions/tx-state-manager.test.js index 7fbe6ec1e..2e5646a14 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.test.js +++ b/app/scripts/controllers/transactions/tx-state-manager.test.js @@ -4,12 +4,7 @@ import { TRANSACTION_STATUSES, TRANSACTION_TYPES, } from '../../../../shared/constants/transaction'; -import { - KOVAN_CHAIN_ID, - MAINNET_CHAIN_ID, - RINKEBY_CHAIN_ID, - KOVAN_NETWORK_ID, -} from '../../../../shared/constants/network'; +import { CHAIN_IDS, NETWORK_IDS } from '../../../../shared/constants/network'; import { GAS_LIMITS } from '../../../../shared/constants/gas'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import TxStateManager, { ERROR_SUBMITTING } from './tx-state-manager'; @@ -49,8 +44,8 @@ function generateTransactions( } describe('TransactionStateManager', function () { let txStateManager; - const currentNetworkId = KOVAN_NETWORK_ID; - const currentChainId = KOVAN_CHAIN_ID; + const currentNetworkId = NETWORK_IDS.KOVAN; + const currentChainId = CHAIN_IDS.MAINNET; const otherNetworkId = '2'; beforeEach(function () { @@ -681,9 +676,9 @@ describe('TransactionStateManager', function () { const txs = generateTransactions(limit + 5, { chainId: (i) => { if (i === 0 || i === 1) { - return MAINNET_CHAIN_ID; + return CHAIN_IDS.MAINNET; } else if (i === 4 || i === 5) { - return RINKEBY_CHAIN_ID; + return CHAIN_IDS.RINKEBY; } return currentChainId; }, @@ -713,7 +708,7 @@ describe('TransactionStateManager', function () { assert.equal( result.some( (tx) => - tx.chainId === MAINNET_CHAIN_ID && tx.txParams.nonce === '0x0', + tx.chainId === CHAIN_IDS.MAINNET && tx.txParams.nonce === '0x0', ), false, 'the mainnet transactions with nonce 0x0 should not be present in the result', diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 95fce4fc7..92ae8d286 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -36,6 +36,10 @@ import { WindowPostMessageStream } from '@metamask/post-message-stream'; import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider'; import shouldInjectProvider from '../../shared/modules/provider-injection'; +// contexts +const CONTENT_SCRIPT = 'metamask-contentscript'; +const INPAGE = 'metamask-inpage'; + restoreContextAfterImports(); log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn'); @@ -47,8 +51,8 @@ log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn'); if (shouldInjectProvider()) { // setup background connection const metamaskStream = new WindowPostMessageStream({ - name: 'metamask-inpage', - target: 'metamask-contentscript', + name: INPAGE, + target: CONTENT_SCRIPT, }); initializeProvider({ diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index e7a420a14..38efc9dd5 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -12,22 +12,9 @@ import EthQuery from 'eth-query'; import { ObservableStore } from '@metamask/obs-store'; import log from 'loglevel'; import pify from 'pify'; -import Web3 from 'web3'; +import { ethers } from 'ethers'; import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi'; -import { - MAINNET_CHAIN_ID, - RINKEBY_CHAIN_ID, - ROPSTEN_CHAIN_ID, - SEPOLIA_CHAIN_ID, - KOVAN_CHAIN_ID, - GOERLI_CHAIN_ID, - BSC_CHAIN_ID, - OPTIMISM_CHAIN_ID, - POLYGON_CHAIN_ID, - AVALANCHE_CHAIN_ID, - FANTOM_CHAIN_ID, - ARBITRUM_CHAIN_ID, -} from '../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import { SINGLE_CALL_BALANCES_ADDRESS, @@ -43,7 +30,6 @@ import { SINGLE_CALL_BALANCES_ADDRESS_FANTOM, SINGLE_CALL_BALANCES_ADDRESS_ARBITRUM, } from '../constants/contracts'; -import { bnToHex } from './util'; /** * This module is responsible for tracking any number of accounts and caching their current balances & transaction @@ -87,7 +73,7 @@ export default class AccountTracker { this._updateForBlock = this._updateForBlock.bind(this); this.getCurrentChainId = opts.getCurrentChainId; - this.web3 = new Web3(this._provider); + this.ethersProvider = new ethers.providers.Web3Provider(this._provider); } start() { @@ -218,84 +204,84 @@ export default class AccountTracker { const chainId = this.getCurrentChainId(); switch (chainId) { - case MAINNET_CHAIN_ID: + case CHAIN_IDS.MAINNET: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS, ); break; - case RINKEBY_CHAIN_ID: + case CHAIN_IDS.RINKEBY: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY, ); break; - case ROPSTEN_CHAIN_ID: + case CHAIN_IDS.ROPSTEN: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN, ); break; - case KOVAN_CHAIN_ID: + case CHAIN_IDS.KOVAN: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_KOVAN, ); break; - case GOERLI_CHAIN_ID: + case CHAIN_IDS.GOERLI: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_GOERLI, ); break; - case SEPOLIA_CHAIN_ID: + case CHAIN_IDS.SEPOLIA: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_SEPOLIA, ); break; - case BSC_CHAIN_ID: + case CHAIN_IDS.BSC: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_BSC, ); break; - case OPTIMISM_CHAIN_ID: + case CHAIN_IDS.OPTIMISM: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_OPTIMISM, ); break; - case POLYGON_CHAIN_ID: + case CHAIN_IDS.POLYGON: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_POLYGON, ); break; - case AVALANCHE_CHAIN_ID: + case CHAIN_IDS.AVALANCHE: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_AVALANCHE, ); break; - case FANTOM_CHAIN_ID: + case CHAIN_IDS.FANTOM: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_FANTOM, ); break; - case ARBITRUM_CHAIN_ID: + case CHAIN_IDS.ARBITRUM: await this._updateAccountsViaBalanceChecker( addresses, SINGLE_CALL_BALANCES_ADDRESS_ARBITRUM, @@ -345,26 +331,29 @@ export default class AccountTracker { */ 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 ethBalance = ['0x0']; + this.ethersProvider = new ethers.providers.Web3Provider(this._provider); + + const ethContract = await new ethers.Contract( + deployedContractAddress, + SINGLE_CALL_BALANCES_ABI, + this.ethersProvider, + ); + const ethBalance = ['0x0000000000000000000000000000000000000000']; + + try { + const balances = await ethContract.balances(addresses, ethBalance); - ethContract.balances(addresses, ethBalance, (error, result) => { - if (error) { - log.warn( - `MetaMask - Account Tracker single call balance fetch failed`, - error, - ); - Promise.all(addresses.map(this._updateAccount.bind(this))); - return; - } addresses.forEach((address, index) => { - const balance = result[index] ? bnToHex(result[index]) : '0x0'; + const balance = balances[index] ? balances[index].toHexString() : '0x0'; accounts[address] = { address, balance }; }); this.store.updateState({ accounts }); - }); + } catch (error) { + log.warn( + `MetaMask - Account Tracker single call balance fetch failed`, + error, + ); + Promise.all(addresses.map(this._updateAccount.bind(this))); + } } } diff --git a/app/scripts/lib/buy-url.js b/app/scripts/lib/buy-url.js index 47b7163ba..5a4f2dadf 100644 --- a/app/scripts/lib/buy-url.js +++ b/app/scripts/lib/buy-url.js @@ -2,13 +2,8 @@ import log from 'loglevel'; import { SWAPS_API_V2_BASE_URL } from '../../../shared/constants/swaps'; import { - GOERLI_CHAIN_ID, - KOVAN_CHAIN_ID, - MAINNET_CHAIN_ID, - RINKEBY_CHAIN_ID, - ROPSTEN_CHAIN_ID, - SEPOLIA_CHAIN_ID, BUYABLE_CHAINS_MAP, + CHAIN_IDS, } from '../../../shared/constants/network'; import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'; import { @@ -58,16 +53,16 @@ const createWyrePurchaseUrl = async (walletAddress, chainId) => { * * @param {string} walletAddress - Ethereum destination address * @param {string} chainId - Current chain ID + * @param {string|undefined} symbol - Token symbol to buy * @returns String */ -const createTransakUrl = (walletAddress, chainId) => { - const { transakCurrencies, network } = BUYABLE_CHAINS_MAP[chainId]; +const createTransakUrl = (walletAddress, chainId, symbol) => { + const { nativeCurrency, network } = BUYABLE_CHAINS_MAP[chainId]; const queryParams = new URLSearchParams({ apiKey: TRANSAK_API_KEY, hostURL: 'https://metamask.io', - cryptoCurrencyList: transakCurrencies.join(','), - defaultCryptoCurrency: transakCurrencies[0], + defaultCryptoCurrency: symbol || nativeCurrency, networks: network, walletAddress, }); @@ -120,17 +115,20 @@ const createMoonPayUrl = async (walletAddress, chainId) => { * * @param {string} walletAddress - Ethereum destination address * @param {string} chainId - Current chain ID + * @param {string|undefined} symbol - Token symbol to buy * @returns String */ -const createCoinbasePayUrl = (walletAddress, chainId) => { - const { coinbasePayCurrencies } = BUYABLE_CHAINS_MAP[chainId]; +const createCoinbasePayUrl = (walletAddress, chainId, symbol) => { + // since coinbasePayCurrencies is going to be extended to include all tokens supported + // we now default to nativeCurrency instead of the 2 previous tokens + eth that we had before + const { nativeCurrency } = BUYABLE_CHAINS_MAP[chainId]; const queryParams = new URLSearchParams({ appId: COINBASEPAY_API_KEY, attribution: 'extension', destinationWallets: JSON.stringify([ { address: walletAddress, - assets: coinbasePayCurrencies, + assets: symbol ? [symbol] : [nativeCurrency], }, ]), }); @@ -144,10 +142,11 @@ const createCoinbasePayUrl = (walletAddress, chainId) => { * @param {string} opts.chainId - The chainId for which to return a url * @param {string} opts.address - The address the bought ETH should be sent to. Only relevant if chainId === '0x1'. * @param opts.service + * @param {string|undefined} opts.symbol - The symbol of the token to buy. Only relevant if buying a token. * @returns {string|undefined} The url at which the user can access ETH, while in the given chain. If the passed * chainId does not match any of the specified cases, or if no chainId is given, returns undefined. */ -export default async function getBuyUrl({ chainId, address, service }) { +export default async function getBuyUrl({ chainId, address, service, symbol }) { // default service by network if not specified if (!service) { // eslint-disable-next-line no-param-reassign @@ -158,11 +157,11 @@ export default async function getBuyUrl({ chainId, address, service }) { case 'wyre': return await createWyrePurchaseUrl(address, chainId); case 'transak': - return createTransakUrl(address, chainId); + return createTransakUrl(address, chainId, symbol); case 'moonpay': return createMoonPayUrl(address, chainId); case 'coinbase': - return createCoinbasePayUrl(address, chainId); + return createCoinbasePayUrl(address, chainId, symbol); case 'metamask-faucet': return 'https://faucet.metamask.io/'; case 'rinkeby-faucet': @@ -182,17 +181,17 @@ export default async function getBuyUrl({ chainId, address, service }) { function getDefaultServiceForChain(chainId) { switch (chainId) { - case MAINNET_CHAIN_ID: + case CHAIN_IDS.MAINNET: return 'wyre'; - case ROPSTEN_CHAIN_ID: + case CHAIN_IDS.ROPSTEN: return 'metamask-faucet'; - case RINKEBY_CHAIN_ID: + case CHAIN_IDS.RINKEBY: return 'rinkeby-faucet'; - case KOVAN_CHAIN_ID: + case CHAIN_IDS.KOVAN: return 'kovan-faucet'; - case GOERLI_CHAIN_ID: + case CHAIN_IDS.GOERLI: return 'goerli-faucet'; - case SEPOLIA_CHAIN_ID: + case CHAIN_IDS.SEPOLIA: return 'sepolia-faucet'; default: throw new Error( diff --git a/app/scripts/lib/buy-url.test.js b/app/scripts/lib/buy-url.test.js index 21c3a3498..188153f6c 100644 --- a/app/scripts/lib/buy-url.test.js +++ b/app/scripts/lib/buy-url.test.js @@ -1,13 +1,8 @@ import nock from 'nock'; import { - KOVAN_CHAIN_ID, - MAINNET_CHAIN_ID, - RINKEBY_CHAIN_ID, - ROPSTEN_CHAIN_ID, - BSC_CHAIN_ID, - POLYGON_CHAIN_ID, - ETH_SYMBOL, BUYABLE_CHAINS_MAP, + CHAIN_IDS, + CURRENCY_SYMBOLS, } from '../../../shared/constants/network'; import { TRANSAK_API_KEY, MOONPAY_API_KEY } from '../constants/on-ramp'; import { SWAPS_API_V2_BASE_URL } from '../../../shared/constants/swaps'; @@ -16,26 +11,26 @@ import getBuyUrl from './buy-url'; const WYRE_ACCOUNT_ID = 'AC-7AG3W4XH4N2'; const ETH_ADDRESS = '0x0dcd5d886577d5581b0c524242ef2ee70be3e7bc'; const MAINNET = { - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, amount: 5, address: ETH_ADDRESS, }; const ROPSTEN = { - chainId: ROPSTEN_CHAIN_ID, + chainId: CHAIN_IDS.ROPSTEN, }; const RINKEBY = { - chainId: RINKEBY_CHAIN_ID, + chainId: CHAIN_IDS.RINKEBY, }; const KOVAN = { - chainId: KOVAN_CHAIN_ID, + chainId: CHAIN_IDS.KOVAN, }; const BSC = { - chainId: BSC_CHAIN_ID, + chainId: CHAIN_IDS.BSC, amount: 5, address: ETH_ADDRESS, }; const POLYGON = { - chainId: POLYGON_CHAIN_ID, + chainId: CHAIN_IDS.POLYGON, amount: 5, address: ETH_ADDRESS, }; @@ -47,11 +42,11 @@ describe('buy-url', () => { `/networks/1/fiatOnRampUrl?serviceName=wyre&destinationAddress=${ETH_ADDRESS}`, ) .reply(200, { - url: `https://pay.sendwyre.com/purchase?accountId=${WYRE_ACCOUNT_ID}&utm_campaign=${WYRE_ACCOUNT_ID}&destCurrency=${ETH_SYMBOL}&utm_medium=widget&paymentMethod=debit-card&reservation=MLZVUF8FMXZUMARJC23B&dest=ethereum%3A${ETH_ADDRESS}&utm_source=checkout`, + url: `https://pay.sendwyre.com/purchase?accountId=${WYRE_ACCOUNT_ID}&utm_campaign=${WYRE_ACCOUNT_ID}&destCurrency=${CURRENCY_SYMBOLS.ETH}&utm_medium=widget&paymentMethod=debit-card&reservation=MLZVUF8FMXZUMARJC23B&dest=ethereum%3A${ETH_ADDRESS}&utm_source=checkout`, }); const wyreUrl = await getBuyUrl(MAINNET); expect(wyreUrl).toStrictEqual( - `https://pay.sendwyre.com/purchase?accountId=${WYRE_ACCOUNT_ID}&utm_campaign=${WYRE_ACCOUNT_ID}&destCurrency=${ETH_SYMBOL}&utm_medium=widget&paymentMethod=debit-card&reservation=MLZVUF8FMXZUMARJC23B&dest=ethereum%3A${ETH_ADDRESS}&utm_source=checkout`, + `https://pay.sendwyre.com/purchase?accountId=${WYRE_ACCOUNT_ID}&utm_campaign=${WYRE_ACCOUNT_ID}&destCurrency=${CURRENCY_SYMBOLS.ETH}&utm_medium=widget&paymentMethod=debit-card&reservation=MLZVUF8FMXZUMARJC23B&dest=ethereum%3A${ETH_ADDRESS}&utm_source=checkout`, ); nock.cleanAll(); }); @@ -60,43 +55,34 @@ describe('buy-url', () => { const wyreUrl = await getBuyUrl(MAINNET); expect(wyreUrl).toStrictEqual( - `https://pay.sendwyre.com/purchase?dest=ethereum:${ETH_ADDRESS}&destCurrency=${ETH_SYMBOL}&accountId=${WYRE_ACCOUNT_ID}&paymentMethod=debit-card`, + `https://pay.sendwyre.com/purchase?dest=ethereum:${ETH_ADDRESS}&destCurrency=${CURRENCY_SYMBOLS.ETH}&accountId=${WYRE_ACCOUNT_ID}&paymentMethod=debit-card`, ); }); it('returns Transak url with an ETH address for Ethereum mainnet', async () => { const transakUrl = await getBuyUrl({ ...MAINNET, service: 'transak' }); const buyableChain = BUYABLE_CHAINS_MAP[MAINNET.chainId]; - const buyableCurrencies = encodeURIComponent( - buyableChain.transakCurrencies.join(','), - ); expect(transakUrl).toStrictEqual( - `https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&cryptoCurrencyList=${buyableCurrencies}&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`, + `https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`, ); }); it('returns Transak url with an BNB address for Binance Smart Chain', async () => { const transakUrl = await getBuyUrl({ ...BSC, service: 'transak' }); const buyableChain = BUYABLE_CHAINS_MAP[BSC.chainId]; - const buyableCurrencies = encodeURIComponent( - buyableChain.transakCurrencies.join(','), - ); expect(transakUrl).toStrictEqual( - `https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&cryptoCurrencyList=${buyableCurrencies}&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`, + `https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`, ); }); it('returns Transak url with an MATIC address for Polygon', async () => { const transakUrl = await getBuyUrl({ ...POLYGON, service: 'transak' }); const buyableChain = BUYABLE_CHAINS_MAP[POLYGON.chainId]; - const buyableCurrencies = encodeURIComponent( - buyableChain.transakCurrencies.join(','), - ); expect(transakUrl).toStrictEqual( - `https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&cryptoCurrencyList=${buyableCurrencies}&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`, + `https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`, ); }); diff --git a/app/scripts/lib/metaRPCClientFactory.js b/app/scripts/lib/metaRPCClientFactory.js index c69651c01..c09f7e38d 100644 --- a/app/scripts/lib/metaRPCClientFactory.js +++ b/app/scripts/lib/metaRPCClientFactory.js @@ -1,7 +1,7 @@ import { EthereumRpcError } from 'eth-rpc-errors'; import SafeEventEmitter from 'safe-event-emitter'; import createRandomId from '../../../shared/modules/random-id'; -import { TEN_SECONDS_IN_MILLISECONDS } from '../../../ui/helpers/constants/critical-error'; +import { TEN_SECONDS_IN_MILLISECONDS } from '../../../shared/lib/transactions-controller-utils'; class DisconnectError extends Error {} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js index c2d73f16b..c32dc4620 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js @@ -2,10 +2,10 @@ import { ethErrors } from 'eth-rpc-errors'; import { omit } from 'lodash'; import { MESSAGE_TYPE } from '../../../../../shared/constants/app'; import { - ETH_SYMBOL, CHAIN_ID_TO_TYPE_MAP, NETWORK_TO_NAME_MAP, CHAIN_ID_TO_RPC_URL_MAP, + CURRENCY_SYMBOLS, } from '../../../../../shared/constants/network'; import { isPrefixedFormattedHexString, @@ -29,7 +29,7 @@ function findExistingNetwork(chainId, findCustomRpcBy) { if (chainId in CHAIN_ID_TO_TYPE_MAP) { return { chainId, - ticker: ETH_SYMBOL, + ticker: CURRENCY_SYMBOLS.ETH, nickname: NETWORK_TO_NAME_MAP[chainId], rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[chainId], type: CHAIN_ID_TO_TYPE_MAP[chainId], diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 72b543e25..5ef0e68b4 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -9,7 +9,9 @@ import extractEthjsErrorMessage from './extractEthjsErrorMessage'; // Destructuring breaks the inlining of the environment variables const METAMASK_DEBUG = process.env.METAMASK_DEBUG; const METAMASK_ENVIRONMENT = process.env.METAMASK_ENVIRONMENT; -const SENTRY_DSN_DEV = process.env.SENTRY_DSN_DEV; +const SENTRY_DSN_DEV = + process.env.SENTRY_DSN_DEV || + 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'; const METAMASK_BUILD_TYPE = process.env.METAMASK_BUILD_TYPE; const IN_TEST = process.env.IN_TEST; /* eslint-enable prefer-destructuring */ diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index edb1aca70..11027123d 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -3,10 +3,7 @@ import browser from 'webextension-polyfill'; import { stripHexPrefix } from 'ethereumjs-util'; import BN from 'bn.js'; import { memoize } from 'lodash'; -import { - MAINNET_CHAIN_ID, - TEST_CHAINS, -} from '../../../shared/constants/network'; +import { CHAIN_IDS, TEST_CHAINS } from '../../../shared/constants/network'; import { ENVIRONMENT_TYPE_POPUP, @@ -148,7 +145,7 @@ function bnToHex(inputBn) { } function getChainType(chainId) { - if (chainId === MAINNET_CHAIN_ID) { + if (chainId === CHAIN_IDS.MAINNET) { return 'mainnet'; } else if (TEST_CHAINS.includes(chainId)) { return 'testnet'; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 453f6cd1e..1befeb496 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -65,7 +65,7 @@ import { GAS_DEV_API_BASE_URL, SWAPS_CLIENT_ID, } from '../../shared/constants/swaps'; -import { MAINNET_CHAIN_ID } from '../../shared/constants/network'; +import { CHAIN_IDS } from '../../shared/constants/network'; import { DEVICE_NAMES, KEYRING_TYPES, @@ -90,14 +90,14 @@ import { } from '../../shared/constants/app'; import { EVENT, EVENT_NAMES } from '../../shared/constants/metametrics'; -import { hexToDecimal } from '../../ui/helpers/utils/conversions.util'; -import { - getTokenIdParam, - getTokenValueParam, -} from '../../ui/helpers/utils/token-util'; +import { getTokenIdParam } from '../../ui/helpers/utils/token-util'; import { isEqualCaseInsensitive } from '../../shared/modules/string-utils'; import { parseStandardTokenTransactionData } from '../../shared/modules/transaction.utils'; import { STATIC_MAINNET_TOKEN_LIST } from '../../shared/constants/tokens'; +import { + getTokenValueParam, + hexToDecimal, +} from '../../shared/lib/metamask-controller-utils'; import { onMessageReceived, checkForMultipleVersionsRunning, @@ -431,11 +431,11 @@ export default class MetamaskController extends EventEmitter { EIP1559APIEndpoint: `${gasApiBaseUrl}/networks//suggestedGasFees`, getCurrentNetworkLegacyGasAPICompatibility: () => { const chainId = this.networkController.getCurrentChainId(); - return process.env.IN_TEST || chainId === MAINNET_CHAIN_ID; + return process.env.IN_TEST || chainId === CHAIN_IDS.MAINNET; }, getChainId: () => { return process.env.IN_TEST - ? MAINNET_CHAIN_ID + ? CHAIN_IDS.MAINNET : this.networkController.getCurrentChainId(); }, }); @@ -1867,6 +1867,10 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapController:remove', ), + handleSnapRequest: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:handleRequest', + ), dismissNotifications: this.dismissNotifications.bind(this), markNotificationsAsRead: this.markNotificationsAsRead.bind(this), ///: END:ONLY_INCLUDE_IN @@ -2262,7 +2266,7 @@ export default class MetamaskController extends EventEmitter { const isTokenDetectionInactiveInMainnet = !useTokenDetection && this.networkController.store.getState().provider.chainId === - MAINNET_CHAIN_ID; + CHAIN_IDS.MAINNET; const { tokenList } = this.tokenListController.state; const caseInSensitiveTokenList = isTokenDetectionInactiveInMainnet ? STATIC_MAINNET_TOKEN_LIST @@ -3280,18 +3284,14 @@ export default class MetamaskController extends EventEmitter { * './controllers/transactions' * ).CustomGasSettings} [customGasSettings] - overrides to use for gas params * instead of allowing this method to generate them - * @param newTxMetaProps + * @param options * @returns {object} MetaMask state */ - async createCancelTransaction( - originalTxId, - customGasSettings, - newTxMetaProps, - ) { + async createCancelTransaction(originalTxId, customGasSettings, options) { await this.txController.createCancelTransaction( originalTxId, customGasSettings, - newTxMetaProps, + options, ); const state = await this.getState(); return state; @@ -3307,18 +3307,14 @@ export default class MetamaskController extends EventEmitter { * './controllers/transactions' * ).CustomGasSettings} [customGasSettings] - overrides to use for gas params * instead of allowing this method to generate them - * @param newTxMetaProps + * @param options * @returns {object} MetaMask state */ - async createSpeedUpTransaction( - originalTxId, - customGasSettings, - newTxMetaProps, - ) { + async createSpeedUpTransaction(originalTxId, customGasSettings, options) { await this.txController.createSpeedUpTransaction( originalTxId, customGasSettings, - newTxMetaProps, + options, ); const state = await this.getState(); return state; diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 21b697fd2..51bfdc77c 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -9,7 +9,7 @@ import proxyquire from 'proxyquire'; import browser from 'webextension-polyfill'; import { TRANSACTION_STATUSES } from '../../shared/constants/transaction'; import createTxMeta from '../../test/lib/createTxMeta'; -import { NETWORK_TYPE_RPC } from '../../shared/constants/network'; +import { NETWORK_TYPES } from '../../shared/constants/network'; import { KEYRING_TYPES, DEVICE_NAMES, @@ -24,7 +24,7 @@ const firstTimeState = { config: {}, NetworkController: { provider: { - type: NETWORK_TYPE_RPC, + type: NETWORK_TYPES.RPC, rpcUrl: 'http://localhost:8545', chainId: '0x539', }, diff --git a/app/scripts/migrations/051.js b/app/scripts/migrations/051.js index c429fa635..c2d975e47 100644 --- a/app/scripts/migrations/051.js +++ b/app/scripts/migrations/051.js @@ -1,5 +1,5 @@ import { cloneDeep } from 'lodash'; -import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network'; +import { BUILT_IN_NETWORKS } from '../../../shared/constants/network'; const version = 51; @@ -19,7 +19,7 @@ export default { function transformState(state) { const { chainId, type } = state?.NetworkController?.provider || {}; - const enumChainId = NETWORK_TYPE_TO_ID_MAP[type]?.chainId; + const enumChainId = BUILT_IN_NETWORKS[type]?.chainId; if (enumChainId && chainId !== enumChainId) { state.NetworkController.provider.chainId = enumChainId; diff --git a/app/scripts/migrations/051.test.js b/app/scripts/migrations/051.test.js index 6dab2c0e3..407a3eae6 100644 --- a/app/scripts/migrations/051.test.js +++ b/app/scripts/migrations/051.test.js @@ -1,6 +1,6 @@ import { INFURA_PROVIDER_TYPES, - NETWORK_TYPE_TO_ID_MAP, + BUILT_IN_NETWORKS, } from '../../../shared/constants/network'; import migration51 from './051'; @@ -44,7 +44,7 @@ describe('migration #51', () => { }, provider: { type, - chainId: NETWORK_TYPE_TO_ID_MAP[type].chainId, + chainId: BUILT_IN_NETWORKS[type].chainId, }, }, foo: 'bar', @@ -75,7 +75,7 @@ describe('migration #51', () => { }, provider: { type, - chainId: NETWORK_TYPE_TO_ID_MAP[type].chainId, + chainId: BUILT_IN_NETWORKS[type].chainId, }, }, foo: 'bar', diff --git a/app/scripts/migrations/052.js b/app/scripts/migrations/052.js index f2469106e..e18040e47 100644 --- a/app/scripts/migrations/052.js +++ b/app/scripts/migrations/052.js @@ -1,17 +1,5 @@ import { cloneDeep } from 'lodash'; -import { - GOERLI, - GOERLI_CHAIN_ID, - KOVAN, - KOVAN_CHAIN_ID, - MAINNET, - MAINNET_CHAIN_ID, - NETWORK_TYPE_RPC, - RINKEBY, - RINKEBY_CHAIN_ID, - ROPSTEN, - ROPSTEN_CHAIN_ID, -} from '../../../shared/constants/network'; +import { CHAIN_IDS, NETWORK_TYPES } from '../../../shared/constants/network'; const version = 52; @@ -43,33 +31,33 @@ function transformState(state = {}) { if (accountTokens && Object.keys(accountTokens).length > 0) { for (const address of Object.keys(accountTokens)) { newAccountTokens[address] = {}; - if (accountTokens[address][NETWORK_TYPE_RPC]) { + if (accountTokens[address][NETWORK_TYPES.RPC]) { frequentRpcListDetail.forEach((detail) => { newAccountTokens[address][detail.chainId] = - accountTokens[address][NETWORK_TYPE_RPC]; + accountTokens[address][NETWORK_TYPES.RPC]; }); } for (const providerType of Object.keys(accountTokens[address])) { switch (providerType) { - case MAINNET: - newAccountTokens[address][MAINNET_CHAIN_ID] = - accountTokens[address][MAINNET]; + case NETWORK_TYPES.MAINNET: + newAccountTokens[address][CHAIN_IDS.MAINNET] = + accountTokens[address][NETWORK_TYPES.MAINNET]; break; - case ROPSTEN: - newAccountTokens[address][ROPSTEN_CHAIN_ID] = - accountTokens[address][ROPSTEN]; + case NETWORK_TYPES.ROPSTEN: + newAccountTokens[address][CHAIN_IDS.ROPSTEN] = + accountTokens[address][NETWORK_TYPES.ROPSTEN]; break; - case RINKEBY: - newAccountTokens[address][RINKEBY_CHAIN_ID] = - accountTokens[address][RINKEBY]; + case NETWORK_TYPES.RINKEBY: + newAccountTokens[address][CHAIN_IDS.RINKEBY] = + accountTokens[address][NETWORK_TYPES.RINKEBY]; break; - case GOERLI: - newAccountTokens[address][GOERLI_CHAIN_ID] = - accountTokens[address][GOERLI]; + case NETWORK_TYPES.GOERLI: + newAccountTokens[address][CHAIN_IDS.GOERLI] = + accountTokens[address][NETWORK_TYPES.GOERLI]; break; - case KOVAN: - newAccountTokens[address][KOVAN_CHAIN_ID] = - accountTokens[address][KOVAN]; + case NETWORK_TYPES.KOVAN: + newAccountTokens[address][CHAIN_IDS.KOVAN] = + accountTokens[address][NETWORK_TYPES.KOVAN]; break; default: break; @@ -82,33 +70,33 @@ function transformState(state = {}) { if (accountHiddenTokens && Object.keys(accountHiddenTokens).length > 0) { for (const address of Object.keys(accountHiddenTokens)) { newAccountHiddenTokens[address] = {}; - if (accountHiddenTokens[address][NETWORK_TYPE_RPC]) { + if (accountHiddenTokens[address][NETWORK_TYPES.RPC]) { frequentRpcListDetail.forEach((detail) => { newAccountHiddenTokens[address][detail.chainId] = - accountHiddenTokens[address][NETWORK_TYPE_RPC]; + accountHiddenTokens[address][NETWORK_TYPES.RPC]; }); } for (const providerType of Object.keys(accountHiddenTokens[address])) { switch (providerType) { - case MAINNET: - newAccountHiddenTokens[address][MAINNET_CHAIN_ID] = - accountHiddenTokens[address][MAINNET]; + case NETWORK_TYPES.MAINNET: + newAccountHiddenTokens[address][CHAIN_IDS.MAINNET] = + accountHiddenTokens[address][NETWORK_TYPES.MAINNET]; break; - case ROPSTEN: - newAccountHiddenTokens[address][ROPSTEN_CHAIN_ID] = - accountHiddenTokens[address][ROPSTEN]; + case NETWORK_TYPES.ROPSTEN: + newAccountHiddenTokens[address][CHAIN_IDS.ROPSTEN] = + accountHiddenTokens[address][NETWORK_TYPES.ROPSTEN]; break; - case RINKEBY: - newAccountHiddenTokens[address][RINKEBY_CHAIN_ID] = - accountHiddenTokens[address][RINKEBY]; + case NETWORK_TYPES.RINKEBY: + newAccountHiddenTokens[address][CHAIN_IDS.RINKEBY] = + accountHiddenTokens[address][NETWORK_TYPES.RINKEBY]; break; - case GOERLI: - newAccountHiddenTokens[address][GOERLI_CHAIN_ID] = - accountHiddenTokens[address][GOERLI]; + case NETWORK_TYPES.GOERLI: + newAccountHiddenTokens[address][CHAIN_IDS.GOERLI] = + accountHiddenTokens[address][NETWORK_TYPES.GOERLI]; break; - case KOVAN: - newAccountHiddenTokens[address][KOVAN_CHAIN_ID] = - accountHiddenTokens[address][KOVAN]; + case NETWORK_TYPES.KOVAN: + newAccountHiddenTokens[address][CHAIN_IDS.KOVAN] = + accountHiddenTokens[address][NETWORK_TYPES.KOVAN]; break; default: break; diff --git a/app/scripts/migrations/052.test.js b/app/scripts/migrations/052.test.js index 1a78374cb..37c325066 100644 --- a/app/scripts/migrations/052.test.js +++ b/app/scripts/migrations/052.test.js @@ -1,16 +1,4 @@ -import { - GOERLI, - GOERLI_CHAIN_ID, - KOVAN, - KOVAN_CHAIN_ID, - MAINNET, - MAINNET_CHAIN_ID, - NETWORK_TYPE_RPC, - RINKEBY, - RINKEBY_CHAIN_ID, - ROPSTEN, - ROPSTEN_CHAIN_ID, -} from '../../../shared/constants/network'; +import { CHAIN_IDS, NETWORK_TYPES } from '../../../shared/constants/network'; import migration52 from './052'; const TOKEN1 = { symbol: 'TST', address: '0x10', decimals: 18 }; @@ -33,25 +21,25 @@ describe('migration #52', () => { }); }); - it(`should move ${MAINNET} tokens and hidden tokens to be keyed by ${MAINNET_CHAIN_ID} for each address`, async () => { + it(`should move ${NETWORK_TYPES.MAINNET} tokens and hidden tokens to be keyed by ${CHAIN_IDS.MAINNET} for each address`, async () => { const oldStorage = { meta: {}, data: { PreferencesController: { accountHiddenTokens: { '0x1111': { - [MAINNET]: [TOKEN1], + [NETWORK_TYPES.MAINNET]: [TOKEN1], }, '0x1112': { - [MAINNET]: [TOKEN3], + [NETWORK_TYPES.MAINNET]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [MAINNET]: [TOKEN1, TOKEN2], + [NETWORK_TYPES.MAINNET]: [TOKEN1, TOKEN2], }, '0x1112': { - [MAINNET]: [TOKEN1, TOKEN3], + [NETWORK_TYPES.MAINNET]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -65,18 +53,18 @@ describe('migration #52', () => { PreferencesController: { accountHiddenTokens: { '0x1111': { - [MAINNET_CHAIN_ID]: [TOKEN1], + [CHAIN_IDS.MAINNET]: [TOKEN1], }, '0x1112': { - [MAINNET_CHAIN_ID]: [TOKEN3], + [CHAIN_IDS.MAINNET]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [MAINNET_CHAIN_ID]: [TOKEN1, TOKEN2], + [CHAIN_IDS.MAINNET]: [TOKEN1, TOKEN2], }, '0x1112': { - [MAINNET_CHAIN_ID]: [TOKEN1, TOKEN3], + [CHAIN_IDS.MAINNET]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -85,25 +73,25 @@ describe('migration #52', () => { }); }); - it(`should move ${RINKEBY} tokens and hidden tokens to be keyed by ${RINKEBY_CHAIN_ID} for each address`, async () => { + it(`should move ${NETWORK_TYPES.RINKEBY} tokens and hidden tokens to be keyed by ${CHAIN_IDS.RINKEBY} for each address`, async () => { const oldStorage = { meta: {}, data: { PreferencesController: { accountHiddenTokens: { '0x1111': { - [RINKEBY]: [TOKEN1], + [NETWORK_TYPES.RINKEBY]: [TOKEN1], }, '0x1112': { - [RINKEBY]: [TOKEN3], + [NETWORK_TYPES.RINKEBY]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [RINKEBY]: [TOKEN1, TOKEN2], + [NETWORK_TYPES.RINKEBY]: [TOKEN1, TOKEN2], }, '0x1112': { - [RINKEBY]: [TOKEN1, TOKEN3], + [NETWORK_TYPES.RINKEBY]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -117,18 +105,18 @@ describe('migration #52', () => { PreferencesController: { accountHiddenTokens: { '0x1111': { - [RINKEBY_CHAIN_ID]: [TOKEN1], + [CHAIN_IDS.RINKEBY]: [TOKEN1], }, '0x1112': { - [RINKEBY_CHAIN_ID]: [TOKEN3], + [CHAIN_IDS.RINKEBY]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [RINKEBY_CHAIN_ID]: [TOKEN1, TOKEN2], + [CHAIN_IDS.RINKEBY]: [TOKEN1, TOKEN2], }, '0x1112': { - [RINKEBY_CHAIN_ID]: [TOKEN1, TOKEN3], + [CHAIN_IDS.RINKEBY]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -137,25 +125,25 @@ describe('migration #52', () => { }); }); - it(`should move ${KOVAN} tokens and hidden tokens to be keyed by ${KOVAN_CHAIN_ID} for each address`, async () => { + it(`should move ${NETWORK_TYPES.KOVAN} tokens and hidden tokens to be keyed by ${CHAIN_IDS.KOVAN} for each address`, async () => { const oldStorage = { meta: {}, data: { PreferencesController: { accountHiddenTokens: { '0x1111': { - [KOVAN]: [TOKEN1], + [NETWORK_TYPES.KOVAN]: [TOKEN1], }, '0x1112': { - [KOVAN]: [TOKEN3], + [NETWORK_TYPES.KOVAN]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [KOVAN]: [TOKEN1, TOKEN2], + [NETWORK_TYPES.KOVAN]: [TOKEN1, TOKEN2], }, '0x1112': { - [KOVAN]: [TOKEN1, TOKEN3], + [NETWORK_TYPES.KOVAN]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -169,18 +157,18 @@ describe('migration #52', () => { PreferencesController: { accountHiddenTokens: { '0x1111': { - [KOVAN_CHAIN_ID]: [TOKEN1], + [CHAIN_IDS.KOVAN]: [TOKEN1], }, '0x1112': { - [KOVAN_CHAIN_ID]: [TOKEN3], + [CHAIN_IDS.KOVAN]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [KOVAN_CHAIN_ID]: [TOKEN1, TOKEN2], + [CHAIN_IDS.KOVAN]: [TOKEN1, TOKEN2], }, '0x1112': { - [KOVAN_CHAIN_ID]: [TOKEN1, TOKEN3], + [CHAIN_IDS.KOVAN]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -189,25 +177,25 @@ describe('migration #52', () => { }); }); - it(`should move ${GOERLI} tokens and hidden tokens to be keyed by ${GOERLI_CHAIN_ID} for each address`, async () => { + it(`should move ${NETWORK_TYPES.GOERLI} tokens and hidden tokens to be keyed by ${CHAIN_IDS.GOERLI} for each address`, async () => { const oldStorage = { meta: {}, data: { PreferencesController: { accountHiddenTokens: { '0x1111': { - [GOERLI]: [TOKEN1], + [NETWORK_TYPES.GOERLI]: [TOKEN1], }, '0x1112': { - [GOERLI]: [TOKEN3], + [NETWORK_TYPES.GOERLI]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [GOERLI]: [TOKEN1, TOKEN2], + [NETWORK_TYPES.GOERLI]: [TOKEN1, TOKEN2], }, '0x1112': { - [GOERLI]: [TOKEN1, TOKEN3], + [NETWORK_TYPES.GOERLI]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -221,18 +209,18 @@ describe('migration #52', () => { PreferencesController: { accountHiddenTokens: { '0x1111': { - [GOERLI_CHAIN_ID]: [TOKEN1], + [CHAIN_IDS.GOERLI]: [TOKEN1], }, '0x1112': { - [GOERLI_CHAIN_ID]: [TOKEN3], + [CHAIN_IDS.GOERLI]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [GOERLI_CHAIN_ID]: [TOKEN1, TOKEN2], + [CHAIN_IDS.GOERLI]: [TOKEN1, TOKEN2], }, '0x1112': { - [GOERLI_CHAIN_ID]: [TOKEN1, TOKEN3], + [CHAIN_IDS.GOERLI]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -241,25 +229,25 @@ describe('migration #52', () => { }); }); - it(`should move ${ROPSTEN} tokens and hidden tokens to be keyed by ${ROPSTEN_CHAIN_ID} for each address`, async () => { + it(`should move ${NETWORK_TYPES.ROPSTEN} tokens and hidden tokens to be keyed by ${CHAIN_IDS.ROPSTEN} for each address`, async () => { const oldStorage = { meta: {}, data: { PreferencesController: { accountHiddenTokens: { '0x1111': { - [ROPSTEN]: [TOKEN1], + [NETWORK_TYPES.ROPSTEN]: [TOKEN1], }, '0x1112': { - [ROPSTEN]: [TOKEN3], + [NETWORK_TYPES.ROPSTEN]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [ROPSTEN]: [TOKEN1, TOKEN2], + [NETWORK_TYPES.ROPSTEN]: [TOKEN1, TOKEN2], }, '0x1112': { - [ROPSTEN]: [TOKEN1, TOKEN3], + [NETWORK_TYPES.ROPSTEN]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -273,18 +261,18 @@ describe('migration #52', () => { PreferencesController: { accountHiddenTokens: { '0x1111': { - [ROPSTEN_CHAIN_ID]: [TOKEN1], + [CHAIN_IDS.ROPSTEN]: [TOKEN1], }, '0x1112': { - [ROPSTEN_CHAIN_ID]: [TOKEN3], + [CHAIN_IDS.ROPSTEN]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [ROPSTEN_CHAIN_ID]: [TOKEN1, TOKEN2], + [CHAIN_IDS.ROPSTEN]: [TOKEN1, TOKEN2], }, '0x1112': { - [ROPSTEN_CHAIN_ID]: [TOKEN1, TOKEN3], + [CHAIN_IDS.ROPSTEN]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -293,7 +281,7 @@ describe('migration #52', () => { }); }); - it(`should duplicate ${NETWORK_TYPE_RPC} tokens and hidden tokens to all custom networks for each address`, async () => { + it(`should duplicate ${NETWORK_TYPES.RPC} tokens and hidden tokens to all custom networks for each address`, async () => { const oldStorage = { meta: {}, data: { @@ -305,18 +293,18 @@ describe('migration #52', () => { ], accountHiddenTokens: { '0x1111': { - [NETWORK_TYPE_RPC]: [TOKEN1], + [NETWORK_TYPES.RPC]: [TOKEN1], }, '0x1112': { - [NETWORK_TYPE_RPC]: [TOKEN3], + [NETWORK_TYPES.RPC]: [TOKEN3], }, }, accountTokens: { '0x1111': { - [NETWORK_TYPE_RPC]: [TOKEN1, TOKEN2], + [NETWORK_TYPES.RPC]: [TOKEN1, TOKEN2], }, '0x1112': { - [NETWORK_TYPE_RPC]: [TOKEN1, TOKEN3], + [NETWORK_TYPES.RPC]: [TOKEN1, TOKEN3], }, }, bar: 'baz', @@ -363,7 +351,7 @@ describe('migration #52', () => { }); }); - it(`should overwrite ${NETWORK_TYPE_RPC} tokens with built in networks if chainIds match`, async () => { + it(`should overwrite ${NETWORK_TYPES.RPC} tokens with built in networks if chainIds match`, async () => { const oldStorage = { meta: {}, data: { @@ -371,14 +359,14 @@ describe('migration #52', () => { frequentRpcListDetail: [{ chainId: '0x1' }], accountHiddenTokens: { '0x1111': { - [NETWORK_TYPE_RPC]: [TOKEN3], - [MAINNET]: [TOKEN1], + [NETWORK_TYPES.RPC]: [TOKEN3], + [NETWORK_TYPES.MAINNET]: [TOKEN1], }, }, accountTokens: { '0x1111': { - [NETWORK_TYPE_RPC]: [TOKEN1, TOKEN2], - [MAINNET]: [TOKEN3, TOKEN4], + [NETWORK_TYPES.RPC]: [TOKEN1, TOKEN2], + [NETWORK_TYPES.MAINNET]: [TOKEN3, TOKEN4], }, }, bar: 'baz', diff --git a/app/scripts/migrations/054.test.js b/app/scripts/migrations/054.test.js index 0e7cde9ae..8ac809b3f 100644 --- a/app/scripts/migrations/054.test.js +++ b/app/scripts/migrations/054.test.js @@ -1,7 +1,4 @@ -import { - MAINNET_CHAIN_ID, - ROPSTEN_CHAIN_ID, -} from '../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import migration54 from './054'; describe('migration #54', () => { @@ -150,7 +147,7 @@ describe('migration #54', () => { PreferencesController: { accountTokens: { '0x1111': { - [MAINNET_CHAIN_ID]: [ + [CHAIN_IDS.MAINNET]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: '0', @@ -174,7 +171,7 @@ describe('migration #54', () => { ], }, '0x1112': { - [ROPSTEN_CHAIN_ID]: [ + [CHAIN_IDS.ROPSTEN]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: '0', @@ -208,7 +205,7 @@ describe('migration #54', () => { PreferencesController: { accountTokens: { '0x1111': { - [MAINNET_CHAIN_ID]: [ + [CHAIN_IDS.MAINNET]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: 0, @@ -232,7 +229,7 @@ describe('migration #54', () => { ], }, '0x1112': { - [ROPSTEN_CHAIN_ID]: [ + [CHAIN_IDS.ROPSTEN]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: 0, @@ -268,7 +265,7 @@ describe('migration #54', () => { PreferencesController: { accountTokens: { '0x1111': { - [MAINNET_CHAIN_ID]: [ + [CHAIN_IDS.MAINNET]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: 0, @@ -292,7 +289,7 @@ describe('migration #54', () => { ], }, '0x1112': { - [ROPSTEN_CHAIN_ID]: [ + [CHAIN_IDS.ROPSTEN]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: 0, @@ -326,7 +323,7 @@ describe('migration #54', () => { PreferencesController: { accountTokens: { '0x1111': { - [MAINNET_CHAIN_ID]: [ + [CHAIN_IDS.MAINNET]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: 0, @@ -350,7 +347,7 @@ describe('migration #54', () => { ], }, '0x1112': { - [ROPSTEN_CHAIN_ID]: [ + [CHAIN_IDS.ROPSTEN]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: 0, @@ -386,7 +383,7 @@ describe('migration #54', () => { PreferencesController: { accountTokens: { '0x1111': { - [MAINNET_CHAIN_ID]: [ + [CHAIN_IDS.MAINNET]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: '0', @@ -410,7 +407,7 @@ describe('migration #54', () => { ], }, '0x1112': { - [ROPSTEN_CHAIN_ID]: [ + [CHAIN_IDS.ROPSTEN]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: '0', @@ -465,7 +462,7 @@ describe('migration #54', () => { PreferencesController: { accountTokens: { '0x1111': { - [MAINNET_CHAIN_ID]: [ + [CHAIN_IDS.MAINNET]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: 0, @@ -489,7 +486,7 @@ describe('migration #54', () => { ], }, '0x1112': { - [ROPSTEN_CHAIN_ID]: [ + [CHAIN_IDS.ROPSTEN]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: 0, @@ -546,7 +543,7 @@ describe('migration #54', () => { PreferencesController: { accountTokens: { '0x1111': { - [MAINNET_CHAIN_ID]: [ + [CHAIN_IDS.MAINNET]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: '', @@ -570,7 +567,7 @@ describe('migration #54', () => { ], }, '0x1112': { - [ROPSTEN_CHAIN_ID]: [ + [CHAIN_IDS.ROPSTEN]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: '0', @@ -625,7 +622,7 @@ describe('migration #54', () => { PreferencesController: { accountTokens: { '0x1111': { - [MAINNET_CHAIN_ID]: [ + [CHAIN_IDS.MAINNET]: [ { address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', decimals: 18, @@ -644,7 +641,7 @@ describe('migration #54', () => { ], }, '0x1112': { - [ROPSTEN_CHAIN_ID]: [ + [CHAIN_IDS.ROPSTEN]: [ { address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', decimals: 0, diff --git a/app/scripts/migrations/055.js b/app/scripts/migrations/055.js index b0ab2a844..ca79f447e 100644 --- a/app/scripts/migrations/055.js +++ b/app/scripts/migrations/055.js @@ -1,5 +1,5 @@ import { cloneDeep, mapKeys } from 'lodash'; -import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network'; +import { BUILT_IN_NETWORKS } from '../../../shared/constants/network'; const version = 55; @@ -30,8 +30,7 @@ function transformState(state) { // using optional chaining in case user's state has fetched blocks for // RPC network types (which don't map to a single chainId). This should // not be possible, but it's safer - (_, key) => - NETWORK_TYPE_TO_ID_MAP[key]?.chainId ?? UNKNOWN_CHAIN_ID_KEY, + (_, key) => BUILT_IN_NETWORKS[key]?.chainId ?? UNKNOWN_CHAIN_ID_KEY, ); // Now that mainnet and test net last fetched blocks are keyed by their // respective chainIds, we can safely delete anything we had for custom diff --git a/app/scripts/migrations/055.test.js b/app/scripts/migrations/055.test.js index a481579b4..faff99bdd 100644 --- a/app/scripts/migrations/055.test.js +++ b/app/scripts/migrations/055.test.js @@ -1,15 +1,4 @@ -import { - GOERLI, - GOERLI_CHAIN_ID, - KOVAN, - KOVAN_CHAIN_ID, - MAINNET, - MAINNET_CHAIN_ID, - RINKEBY, - RINKEBY_CHAIN_ID, - ROPSTEN, - ROPSTEN_CHAIN_ID, -} from '../../../shared/constants/network'; +import { CHAIN_IDS, NETWORK_TYPES } from '../../../shared/constants/network'; import migration55 from './055'; describe('migration #55', () => { @@ -41,11 +30,11 @@ describe('migration #55', () => { }, }, incomingTxLastFetchedBlocksByNetwork: { - [MAINNET]: 1, - [ROPSTEN]: 2, - [RINKEBY]: 3, - [GOERLI]: 4, - [KOVAN]: 5, + [NETWORK_TYPES.MAINNET]: 1, + [NETWORK_TYPES.ROPSTEN]: 2, + [NETWORK_TYPES.RINKEBY]: 3, + [NETWORK_TYPES.GOERLI]: 4, + [NETWORK_TYPES.KOVAN]: 5, }, }, foo: 'bar', @@ -58,11 +47,11 @@ describe('migration #55', () => { incomingTransactions: oldStorage.data.IncomingTransactionsController.incomingTransactions, incomingTxLastFetchedBlockByChainId: { - [MAINNET_CHAIN_ID]: 1, - [ROPSTEN_CHAIN_ID]: 2, - [RINKEBY_CHAIN_ID]: 3, - [GOERLI_CHAIN_ID]: 4, - [KOVAN_CHAIN_ID]: 5, + [CHAIN_IDS.MAINNET]: 1, + [CHAIN_IDS.ROPSTEN]: 2, + [CHAIN_IDS.RINKEBY]: 3, + [CHAIN_IDS.GOERLI]: 4, + [CHAIN_IDS.KOVAN]: 5, }, }, foo: 'bar', diff --git a/app/scripts/migrations/059.test.js b/app/scripts/migrations/059.test.js index dff53fcd1..271a37b9e 100644 --- a/app/scripts/migrations/059.test.js +++ b/app/scripts/migrations/059.test.js @@ -1,10 +1,5 @@ import { cloneDeep } from 'lodash'; -import { - KOVAN_CHAIN_ID, - MAINNET_CHAIN_ID, - RINKEBY_CHAIN_ID, - GOERLI_CHAIN_ID, -} from '../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import { TRANSACTION_TYPES, TRANSACTION_STATUSES, @@ -17,7 +12,7 @@ const ERRONEOUS_TRANSACTION_STATE = { 0: { type: TRANSACTION_TYPES.CANCEL, id: 0, - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, txParams: { nonce: '0x0', }, @@ -25,7 +20,7 @@ const ERRONEOUS_TRANSACTION_STATE = { 1: { type: SENT_ETHER, id: 1, - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, txParams: { nonce: '0x1', }, @@ -33,7 +28,7 @@ const ERRONEOUS_TRANSACTION_STATE = { 2: { type: SENT_ETHER, id: 2, - chainId: KOVAN_CHAIN_ID, + chainId: CHAIN_IDS.KOVAN, txParams: { nonce: '0x2', }, @@ -41,7 +36,7 @@ const ERRONEOUS_TRANSACTION_STATE = { 3: { type: SENT_ETHER, id: 3, - chainId: RINKEBY_CHAIN_ID, + chainId: CHAIN_IDS.RINKEBY, txParams: { nonce: '0x3', }, @@ -49,7 +44,7 @@ const ERRONEOUS_TRANSACTION_STATE = { 4: { type: SENT_ETHER, id: 4, - chainId: RINKEBY_CHAIN_ID, + chainId: CHAIN_IDS.RINKEBY, txParams: { nonce: '0x4', }, @@ -57,7 +52,7 @@ const ERRONEOUS_TRANSACTION_STATE = { 5: { type: SENT_ETHER, id: 5, - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, txParams: { nonce: '0x5', }, @@ -65,7 +60,7 @@ const ERRONEOUS_TRANSACTION_STATE = { 6: { type: SENT_ETHER, id: 6, - chainId: KOVAN_CHAIN_ID, + chainId: CHAIN_IDS.KOVAN, txParams: { nonce: '0x6', }, @@ -73,7 +68,7 @@ const ERRONEOUS_TRANSACTION_STATE = { 7: { type: SENT_ETHER, id: 7, - chainId: RINKEBY_CHAIN_ID, + chainId: CHAIN_IDS.RINKEBY, txParams: { nonce: '0x7', }, @@ -81,7 +76,7 @@ const ERRONEOUS_TRANSACTION_STATE = { 8: { type: SENT_ETHER, id: 8, - chainId: RINKEBY_CHAIN_ID, + chainId: CHAIN_IDS.RINKEBY, txParams: { nonce: '0x8', }, @@ -89,7 +84,7 @@ const ERRONEOUS_TRANSACTION_STATE = { 9: { type: SENT_ETHER, id: 9, - chainId: RINKEBY_CHAIN_ID, + chainId: CHAIN_IDS.RINKEBY, status: TRANSACTION_STATUSES.UNAPPROVED, }, }; @@ -107,7 +102,7 @@ const ERRONEOUS_TRANSACTION_STATE_MIXED = { 10: { type: TRANSACTION_TYPES.RETRY, id: 10, - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, txParams: { nonce: '0xa', }, @@ -115,7 +110,7 @@ const ERRONEOUS_TRANSACTION_STATE_MIXED = { 11: { type: TRANSACTION_TYPES.RETRY, id: 11, - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, txParams: { nonce: '0xb', }, @@ -169,7 +164,7 @@ describe('migration #59', () => { 11: { ...ERRONEOUS_TRANSACTION_STATE['0'], id: 11, - chainId: GOERLI_CHAIN_ID, + chainId: CHAIN_IDS.GOERLI, type: SENT_ETHER, }, }, @@ -250,7 +245,7 @@ describe('migration #59', () => { 11: { ...ERRONEOUS_TRANSACTION_STATE_RETRY['0'], id: 11, - chainId: GOERLI_CHAIN_ID, + chainId: CHAIN_IDS.GOERLI, type: SENT_ETHER, }, }, diff --git a/app/scripts/migrations/064.test.js b/app/scripts/migrations/064.test.js index 227d4d11d..ec8360695 100644 --- a/app/scripts/migrations/064.test.js +++ b/app/scripts/migrations/064.test.js @@ -1,5 +1,5 @@ +import { CHAIN_IDS } from '../../../shared/constants/network'; import { TRANSACTION_TYPES } from '../../../shared/constants/transaction'; -import { MAINNET_CHAIN_ID } from '../../../shared/constants/network'; import migration64 from './064'; const SENT_ETHER = 'sentEther'; // the legacy transaction type being replaced in this migration with TRANSACTION_TYPES.SIMPLE_SEND @@ -84,7 +84,7 @@ describe('migration #64', () => { 1462177651588364: { type: TRANSACTION_TYPES.CANCEL, id: 0, - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, txParams: { nonce: '0x0', }, @@ -100,7 +100,7 @@ describe('migration #64', () => { 1: { type: SENT_ETHER, id: 1, - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, txParams: { nonce: '0x1', }, @@ -263,7 +263,7 @@ describe('migration #64', () => { 1462177651588364: { type: TRANSACTION_TYPES.CANCEL, id: 0, - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, txParams: { nonce: '0x0', }, @@ -279,7 +279,7 @@ describe('migration #64', () => { 1: { type: TRANSACTION_TYPES.SIMPLE_SEND, id: 1, - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, txParams: { nonce: '0x1', }, diff --git a/app/scripts/ui.js b/app/scripts/ui.js index de074300e..deb12f6c1 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -17,8 +17,8 @@ import { ENVIRONMENT_TYPE_POPUP, } from '../../shared/constants/app'; import { isManifestV3 } from '../../shared/modules/mv3.utils'; -import { SUPPORT_LINK } from '../../ui/helpers/constants/common'; -import { getErrorHtml } from '../../ui/helpers/utils/error-utils'; +import { SUPPORT_LINK } from '../../shared/lib/ui-utils'; +import { getErrorHtml } from '../../shared/lib/error-utils'; import ExtensionPlatform from './platforms/extension'; import { setupMultiplex } from './lib/stream-utils'; import { getEnvironmentType } from './lib/util'; diff --git a/development/build/config.js b/development/build/config.js index 386bb7554..be3777695 100644 --- a/development/build/config.js +++ b/development/build/config.js @@ -3,6 +3,33 @@ const { readFile } = require('fs/promises'); const ini = require('ini'); const { BuildType } = require('../lib/build-type'); +const commonConfigurationPropertyNames = ['PUBNUB_PUB_KEY', 'PUBNUB_SUB_KEY']; + +const configurationPropertyNames = [ + ...commonConfigurationPropertyNames, + 'COLLECTIBLES_V1', + 'INFURA_PROJECT_ID', + 'ONBOARDING_V2', + 'PHISHING_WARNING_PAGE_URL', + 'PORTFOLIO_URL', + 'SEGMENT_HOST', + 'SEGMENT_WRITE_KEY', + 'SENTRY_DSN_DEV', + 'SIWE_V1', + 'SWAPS_USE_DEV_APIS', +]; + +const productionConfigurationPropertyNames = [ + ...commonConfigurationPropertyNames, + 'INFURA_BETA_PROJECT_ID', + 'INFURA_FLASK_PROJECT_ID', + 'INFURA_PROD_PROJECT_ID', + 'SEGMENT_BETA_WRITE_KEY', + 'SEGMENT_FLASK_WRITE_KEY', + 'SEGMENT_PROD_WRITE_KEY', + 'SENTRY_DSN', +]; + /** * Get configuration for non-production builds. * @@ -20,22 +47,17 @@ async function getConfig() { throw error; } } + + const environmentVariables = {}; + for (const propertyName of configurationPropertyNames) { + if (process.env[propertyName]) { + environmentVariables[propertyName] = process.env[propertyName]; + } + } + return { - COLLECTIBLES_V1: process.env.COLLECTIBLES_V1, - INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID, - ONBOARDING_V2: process.env.ONBOARDING_V2, - PHISHING_WARNING_PAGE_URL: process.env.PHISHING_WARNING_PAGE_URL, - PORTFOLIO_URL: process.env.PORTFOLIO_URL, - PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY, - PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY, - SEGMENT_HOST: process.env.SEGMENT_HOST, - SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY, - SENTRY_DSN_DEV: - process.env.SENTRY_DSN_DEV ?? - 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496', - SIWE_V1: process.env.SIWE_V1, - SWAPS_USE_DEV_APIS: process.env.SWAPS_USE_DEV_APIS, ...ini.parse(configContents), + ...environmentVariables, }; } @@ -61,17 +83,17 @@ async function getProductionConfig(buildType) { throw error; } } + + const environmentVariables = {}; + for (const propertyName of productionConfigurationPropertyNames) { + if (process.env[propertyName]) { + environmentVariables[propertyName] = process.env[propertyName]; + } + } + const prodConfig = { - INFURA_BETA_PROJECT_ID: process.env.INFURA_BETA_PROJECT_ID, - INFURA_FLASK_PROJECT_ID: process.env.INFURA_FLASK_PROJECT_ID, - INFURA_PROD_PROJECT_ID: process.env.INFURA_PROD_PROJECT_ID, - PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY, - PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY, - SEGMENT_BETA_WRITE_KEY: process.env.SEGMENT_BETA_WRITE_KEY, - SEGMENT_FLASK_WRITE_KEY: process.env.SEGMENT_FLASK_WRITE_KEY, - SEGMENT_PROD_WRITE_KEY: process.env.SEGMENT_PROD_WRITE_KEY, - SENTRY_DSN: process.env.SENTRY_DSN, ...ini.parse(prodConfigContents), + ...environmentVariables, }; const requiredEnvironmentVariables = { diff --git a/development/build/scripts.js b/development/build/scripts.js index 5b89b372a..de5fd260c 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -29,6 +29,7 @@ const bifyModuleGroups = require('bify-module-groups'); const { streamFlatMap } = require('../stream-flat-map'); const { BuildType } = require('../lib/build-type'); +const { generateIconNames } = require('../generate-icon-names'); const { BUILD_TARGETS, ENVIRONMENT } = require('./constants'); const { getConfig, getProductionConfig } = require('./config'); const { @@ -1013,7 +1014,9 @@ async function getEnvironmentVariables({ buildTarget, buildType, version }) { const devMode = isDevBuild(buildTarget); const testing = isTestBuild(buildTarget); + const iconNames = await generateIconNames(); return { + ICON_NAMES: iconNames, COLLECTIBLES_V1: config.COLLECTIBLES_V1 === '1', CONF: devMode ? config : {}, IN_TEST: testing, diff --git a/development/generate-icon-names.js b/development/generate-icon-names.js new file mode 100644 index 000000000..bbe49f653 --- /dev/null +++ b/development/generate-icon-names.js @@ -0,0 +1,47 @@ +/** + * Generate icon names + * + * Reads all the icon svg files in app/images/icons + * and returns an object of icon name key value pairs + * stored in the environment variable ICON_NAMES + * Used with the Icon component in ./ui/component-library/icon + */ +const fs = require('fs'); +const path = require('path'); + +const SVG_ICONS_FOLDER = './app/images/icons'; +const ASSET_EXT = '.svg'; + +const getIconNameKebabCase = (fileName) => + path.basename(fileName, ASSET_EXT).replace('icon-', ''); + +const getIconNameInSnakeCase = (fileName) => + path + .basename(fileName, ASSET_EXT) + .replace('icon-', '') + .replace(/-/gu, '_') + .toUpperCase(); + +const generateIconNames = async () => { + const iconNames = {}; + + const svgIconsFolderPath = path.join(__dirname, `../${SVG_ICONS_FOLDER}`); + + const fileList = await fs.promises.readdir(svgIconsFolderPath); + + const svgIconsFileList = fileList.filter( + (fileName) => path.extname(fileName) === ASSET_EXT, + ); + + svgIconsFileList.forEach( + (fileName) => + (iconNames[getIconNameInSnakeCase(fileName)] = + getIconNameKebabCase(fileName)), + ); + + console.log('ICON_NAMES env var successfully generated!'); + + return iconNames; +}; + +module.exports = { generateIconNames }; diff --git a/development/ts-migration-dashboard/files-to-convert.json b/development/ts-migration-dashboard/files-to-convert.json index e8cc2681f..3a184644d 100644 --- a/development/ts-migration-dashboard/files-to-convert.json +++ b/development/ts-migration-dashboard/files-to-convert.json @@ -459,6 +459,9 @@ "ui/components/app/currency-input/currency-input.stories.js", "ui/components/app/currency-input/currency-input.test.js", "ui/components/app/currency-input/index.js", + "ui/components/app/deposit-popover/on-ramp-item.js", + "ui/components/app/deposit-popover/deposit-popover.js", + "ui/components/app/deposit-popover/index.js", "ui/components/app/detected-token/detected-token-address/detected-token-address.js", "ui/components/app/detected-token/detected-token-address/detected-token-address.stories.js", "ui/components/app/detected-token/detected-token-address/detected-token-address.test.js", @@ -624,9 +627,6 @@ "ui/components/app/modals/convert-token-to-nft-modal/convert-token-to-nft-modal.js", "ui/components/app/modals/customize-nonce/customize-nonce.component.js", "ui/components/app/modals/customize-nonce/index.js", - "ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js", - "ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js", - "ui/components/app/modals/deposit-ether-modal/index.js", "ui/components/app/modals/edit-approval-permission/edit-approval-permission.component.js", "ui/components/app/modals/edit-approval-permission/edit-approval-permission.container.js", "ui/components/app/modals/edit-approval-permission/index.js", diff --git a/development/verify-locale-strings.js b/development/verify-locale-strings.js index 26b01ad89..d9f27810a 100755 --- a/development/verify-locale-strings.js +++ b/development/verify-locale-strings.js @@ -177,9 +177,12 @@ async function verifyEnglishLocale() { 'ui/pages/confirmation/templates/*.js', ]; const testGlob = '**/*.test.js'; - const javascriptFiles = await glob(['ui/**/*.js', 'shared/**/*.js'], { - ignore: [...globsToStrictSearch, testGlob], - }); + const javascriptFiles = await glob( + ['ui/**/*.js', 'shared/**/*.js', 'app/scripts/constants/**/*.js'], + { + ignore: [...globsToStrictSearch, testGlob], + }, + ); const javascriptFilesToStrictSearch = await glob(globsToStrictSearch, { ignore: [testGlob], }); diff --git a/jest.config.js b/jest.config.js index 06d275fb8..c65632dce 100644 --- a/jest.config.js +++ b/jest.config.js @@ -28,6 +28,16 @@ module.exports = { statements: 100, }, }, + reporters: [ + 'default', + [ + 'jest-junit', + { + outputDirectory: 'test/test-results/', + outputName: 'junit.xml', + }, + ], + ], // TODO: enable resetMocks // resetMocks: true, restoreMocks: true, @@ -42,6 +52,7 @@ module.exports = { 'app/scripts/controllers/network/**/*.test.js', '/app/scripts/controllers/permissions/**/*.test.js', '/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js', + '/app/scripts/constants/error-utils.test.js', ], testTimeout: 2500, // We have to specify the environment we are running in, which is jsdom. The diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 033fd9e34..b9fa80382 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -764,7 +764,7 @@ "3box>ipfs>ipld-raw>multihashing-async": true, "3box>ipfs>multicodec": true, "3box>ipfs>protons": true, - "@storybook/client-api>stable": true, + "@storybook/react>@storybook/store>stable": true, "browserify>assert": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true @@ -2635,12 +2635,14 @@ "@metamask/contract-metadata": true, "@metamask/controllers>abort-controller": true, "@metamask/controllers>async-mutex": true, + "@metamask/controllers>eth-json-rpc-infura": true, "@metamask/controllers>eth-method-registry": true, "@metamask/controllers>eth-phishing-detect": true, "@metamask/controllers>ethereumjs-wallet": true, "@metamask/controllers>isomorphic-fetch": true, "@metamask/controllers>multiformats": true, "@metamask/controllers>nanoid": true, + "@metamask/controllers>web3": true, "@metamask/controllers>web3-provider-engine": true, "@metamask/metamask-eth-abis": true, "browserify>buffer": true, @@ -2648,7 +2650,6 @@ "deep-freeze-strict": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, - "eth-json-rpc-infura": true, "eth-keyring-controller": true, "eth-query": true, "eth-rpc-errors": true, @@ -2661,8 +2662,7 @@ "jsonschema": true, "punycode": true, "single-call-balance-checker-abi": true, - "uuid": true, - "web3": true + "uuid": true } }, "@metamask/controllers>abort-controller": { @@ -2683,6 +2683,33 @@ "define": true } }, + "@metamask/controllers>eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/controllers>eth-json-rpc-infura>eth-json-rpc-middleware": true, + "@metamask/controllers>eth-json-rpc-infura>eth-rpc-errors": true, + "@metamask/controllers>eth-json-rpc-infura>json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/controllers>eth-json-rpc-infura>eth-json-rpc-middleware": { + "packages": { + "safe-event-emitter": true + } + }, + "@metamask/controllers>eth-json-rpc-infura>eth-rpc-errors": { + "packages": { + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/controllers>eth-json-rpc-infura>json-rpc-engine": { + "packages": { + "@metamask/controllers>eth-json-rpc-infura>eth-rpc-errors": true, + "safe-event-emitter": true + } + }, "@metamask/controllers>eth-method-registry": { "packages": { "@metamask/controllers>eth-method-registry>ethjs": true @@ -2805,6 +2832,22 @@ "crypto.getRandomValues": true } }, + "@metamask/controllers>web3": { + "globals": { + "Web3": "write", + "XMLHttpRequest": true, + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/controllers>web3>bignumber.js": true, + "@metamask/controllers>web3>crypto-js": true, + "@metamask/controllers>web3>utf8": true, + "@metamask/controllers>web3>xhr2-cookies": true, + "browserify>buffer": true + } + }, "@metamask/controllers>web3-provider-engine": { "globals": { "WebSocket": true, @@ -2813,6 +2856,7 @@ }, "packages": { "@ethereumjs/tx": true, + "@metamask/controllers>eth-json-rpc-infura": true, "@metamask/controllers>web3-provider-engine>backoff": true, "@metamask/controllers>web3-provider-engine>eth-block-tracker": true, "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware": true, @@ -2824,7 +2868,6 @@ "browserify>events": true, "browserify>util": true, "eth-json-rpc-filters": true, - "eth-json-rpc-infura": true, "gh-pages>async": true, "lavamoat>json-stable-stringify": true, "watchify>xtend": true @@ -2943,6 +2986,121 @@ "browserify>process": true } }, + "@metamask/controllers>web3>bignumber.js": { + "globals": { + "define": true + }, + "packages": { + "browserify>crypto-browserify": true + } + }, + "@metamask/controllers>web3>crypto-js": { + "globals": { + "define": true + } + }, + "@metamask/controllers>web3>utf8": { + "globals": { + "define": true + } + }, + "@metamask/controllers>web3>xhr2-cookies": { + "globals": { + "console.warn": true + }, + "packages": { + "browserify>buffer": true, + "browserify>https-browserify": true, + "browserify>os-browserify": true, + "browserify>process": true, + "browserify>stream-http": true, + "browserify>url": true, + "pubnub>superagent>cookiejar": true + } + }, + "@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/eth-json-rpc-infura>@metamask/utils": { + "packages": { + "@metamask/eth-json-rpc-infura>@metamask/utils>superstruct": true, + "eslint>fast-deep-equal": true, + "madge>debug": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "json-rpc-engine>@metamask/safe-event-emitter": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": true, + "bn.js": true, + "browserify>buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, "@metamask/eth-ledger-bridge-keyring": { "globals": { "addEventListener": true, @@ -3366,7 +3524,7 @@ "localStorage": true } }, - "@storybook/client-api>stable": { + "@storybook/react>@storybook/store>stable": { "globals": { "define": true } @@ -4201,11 +4359,19 @@ "setTimeout": true }, "packages": { + "eth-block-tracker>@metamask/utils": true, "eth-block-tracker>pify": true, "eth-query>json-rpc-random-id": true, "json-rpc-engine>@metamask/safe-event-emitter": true } }, + "eth-block-tracker>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-block-tracker>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-ens-namehash": { "globals": { "name": "write" @@ -4249,33 +4415,6 @@ "eth-json-rpc-filters>json-rpc-engine": true } }, - "eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-json-rpc-infura>eth-rpc-errors": true, - "eth-json-rpc-infura>json-rpc-engine": true, - "node-fetch": true - } - }, - "eth-json-rpc-infura>eth-json-rpc-middleware": { - "packages": { - "safe-event-emitter": true - } - }, - "eth-json-rpc-infura>eth-rpc-errors": { - "packages": { - "eth-rpc-errors>fast-safe-stringify": true - } - }, - "eth-json-rpc-infura>json-rpc-engine": { - "packages": { - "eth-json-rpc-infura>eth-rpc-errors": true, - "safe-event-emitter": true - } - }, "eth-json-rpc-middleware": { "globals": { "URL": true, @@ -4286,6 +4425,7 @@ }, "packages": { "browserify>browser-resolve": true, + "eth-json-rpc-middleware>@metamask/utils": true, "eth-json-rpc-middleware>eth-sig-util": true, "eth-json-rpc-middleware>pify": true, "eth-rpc-errors": true, @@ -4295,6 +4435,13 @@ "vinyl>clone": true } }, + "eth-json-rpc-middleware>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-json-rpc-middleware>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-json-rpc-middleware>eth-sig-util": { "packages": { "eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, @@ -4601,6 +4748,7 @@ "eth-query": { "packages": { "eth-query>json-rpc-random-id": true, + "madge>debug": true, "watchify>xtend": true } }, @@ -5754,6 +5902,11 @@ "browserify>buffer": true } }, + "pubnub>superagent>cookiejar": { + "globals": { + "console.warn": true + } + }, "pump": { "packages": { "browserify>browser-resolve": true, @@ -6064,9 +6217,9 @@ "react-router-dom>react-router>mini-create-react-context": { "packages": { "@babel/runtime": true, - "@storybook/api>@reach/router>create-react-context>gud": true, "prop-types": true, "react": true, + "react-router-dom>react-router>mini-create-react-context>gud": true, "react-router-dom>tiny-warning": true } }, @@ -6308,18 +6461,7 @@ }, "web3": { "globals": { - "Web3": "write", - "XMLHttpRequest": true, - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "browserify>buffer": true, - "web3>bignumber.js": true, - "web3>crypto-js": true, - "web3>utf8": true, - "web3>xhr2-cookies": true + "XMLHttpRequest": true } }, "web3-stream-provider": { @@ -6338,43 +6480,6 @@ "msCrypto": true } }, - "web3>bignumber.js": { - "globals": { - "define": true - }, - "packages": { - "browserify>crypto-browserify": true - } - }, - "web3>crypto-js": { - "globals": { - "define": true - } - }, - "web3>utf8": { - "globals": { - "define": true - } - }, - "web3>xhr2-cookies": { - "globals": { - "console.warn": true - }, - "packages": { - "browserify>buffer": true, - "browserify>https-browserify": true, - "browserify>os-browserify": true, - "browserify>process": true, - "browserify>stream-http": true, - "browserify>url": true, - "web3>xhr2-cookies>cookiejar": true - } - }, - "web3>xhr2-cookies>cookiejar": { - "globals": { - "console.warn": true - } - }, "webextension-polyfill": { "globals": { "browser": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index ef9075582..ecfe34552 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -764,7 +764,7 @@ "3box>ipfs>ipld-raw>multihashing-async": true, "3box>ipfs>multicodec": true, "3box>ipfs>protons": true, - "@storybook/client-api>stable": true, + "@storybook/react>@storybook/store>stable": true, "browserify>assert": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true @@ -2228,7 +2228,7 @@ }, "packages": { "@babel/core>@ampproject/remapping>@jridgewell/gen-mapping": true, - "@babel/core>@ampproject/remapping>@jridgewell/trace-mapping": true + "terser>@jridgewell/source-map>@jridgewell/trace-mapping": true } }, "@babel/core>@ampproject/remapping>@jridgewell/gen-mapping": { @@ -2236,22 +2236,8 @@ "define": true }, "packages": { - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/set-array": true, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true - } - }, - "@babel/core>@ampproject/remapping>@jridgewell/trace-mapping": { - "globals": { - "define": true - }, - "packages": { - "@babel/core>@ampproject/remapping>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true - } - }, - "@babel/core>@ampproject/remapping>@jridgewell/trace-mapping>@jridgewell/resolve-uri": { - "globals": { - "define": true + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": true, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true } }, "@babel/core>@babel/generator": { @@ -2259,34 +2245,10 @@ "console.error": true }, "packages": { - "@babel/core>@babel/generator>@jridgewell/gen-mapping": true, "@babel/core>@babel/generator>jsesc": true, "@babel/core>@babel/types": true, - "browserify>buffer": true - } - }, - "@babel/core>@babel/generator>@jridgewell/gen-mapping": { - "globals": { - "define": true - }, - "packages": { - "@babel/core>@ampproject/remapping>@jridgewell/trace-mapping": true, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/set-array": true, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true - } - }, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/set-array": { - "globals": { - "define": true - } - }, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": { - "globals": { - "TextDecoder": true, - "define": true - }, - "packages": { - "browserify>buffer": true + "browserify>buffer": true, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true } }, "@babel/core>@babel/generator>jsesc": { @@ -2818,12 +2780,14 @@ "@metamask/contract-metadata": true, "@metamask/controllers>abort-controller": true, "@metamask/controllers>async-mutex": true, + "@metamask/controllers>eth-json-rpc-infura": true, "@metamask/controllers>eth-method-registry": true, "@metamask/controllers>eth-phishing-detect": true, "@metamask/controllers>ethereumjs-wallet": true, "@metamask/controllers>isomorphic-fetch": true, "@metamask/controllers>multiformats": true, "@metamask/controllers>nanoid": true, + "@metamask/controllers>web3": true, "@metamask/controllers>web3-provider-engine": true, "@metamask/metamask-eth-abis": true, "browserify>buffer": true, @@ -2831,7 +2795,6 @@ "deep-freeze-strict": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, - "eth-json-rpc-infura": true, "eth-keyring-controller": true, "eth-query": true, "eth-rpc-errors": true, @@ -2844,8 +2807,7 @@ "jsonschema": true, "punycode": true, "single-call-balance-checker-abi": true, - "uuid": true, - "web3": true + "uuid": true } }, "@metamask/controllers>abort-controller": { @@ -2866,6 +2828,33 @@ "define": true } }, + "@metamask/controllers>eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/controllers>eth-json-rpc-infura>eth-json-rpc-middleware": true, + "@metamask/controllers>eth-json-rpc-infura>eth-rpc-errors": true, + "@metamask/controllers>eth-json-rpc-infura>json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/controllers>eth-json-rpc-infura>eth-json-rpc-middleware": { + "packages": { + "safe-event-emitter": true + } + }, + "@metamask/controllers>eth-json-rpc-infura>eth-rpc-errors": { + "packages": { + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/controllers>eth-json-rpc-infura>json-rpc-engine": { + "packages": { + "@metamask/controllers>eth-json-rpc-infura>eth-rpc-errors": true, + "safe-event-emitter": true + } + }, "@metamask/controllers>eth-method-registry": { "packages": { "@metamask/controllers>eth-method-registry>ethjs": true @@ -2988,6 +2977,22 @@ "crypto.getRandomValues": true } }, + "@metamask/controllers>web3": { + "globals": { + "Web3": "write", + "XMLHttpRequest": true, + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/controllers>web3>bignumber.js": true, + "@metamask/controllers>web3>crypto-js": true, + "@metamask/controllers>web3>utf8": true, + "@metamask/controllers>web3>xhr2-cookies": true, + "browserify>buffer": true + } + }, "@metamask/controllers>web3-provider-engine": { "globals": { "WebSocket": true, @@ -2996,6 +3001,7 @@ }, "packages": { "@ethereumjs/tx": true, + "@metamask/controllers>eth-json-rpc-infura": true, "@metamask/controllers>web3-provider-engine>backoff": true, "@metamask/controllers>web3-provider-engine>eth-block-tracker": true, "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware": true, @@ -3007,7 +3013,6 @@ "browserify>events": true, "browserify>util": true, "eth-json-rpc-filters": true, - "eth-json-rpc-infura": true, "gh-pages>async": true, "lavamoat>json-stable-stringify": true, "watchify>xtend": true @@ -3126,6 +3131,121 @@ "browserify>process": true } }, + "@metamask/controllers>web3>bignumber.js": { + "globals": { + "define": true + }, + "packages": { + "browserify>crypto-browserify": true + } + }, + "@metamask/controllers>web3>crypto-js": { + "globals": { + "define": true + } + }, + "@metamask/controllers>web3>utf8": { + "globals": { + "define": true + } + }, + "@metamask/controllers>web3>xhr2-cookies": { + "globals": { + "console.warn": true + }, + "packages": { + "browserify>buffer": true, + "browserify>https-browserify": true, + "browserify>os-browserify": true, + "browserify>process": true, + "browserify>stream-http": true, + "browserify>url": true, + "pubnub>superagent>cookiejar": true + } + }, + "@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/eth-json-rpc-infura>@metamask/utils": { + "packages": { + "@metamask/eth-json-rpc-infura>@metamask/utils>superstruct": true, + "eslint>fast-deep-equal": true, + "madge>debug": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "json-rpc-engine>@metamask/safe-event-emitter": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": true, + "bn.js": true, + "browserify>buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, "@metamask/eth-ledger-bridge-keyring": { "globals": { "addEventListener": true, @@ -3912,7 +4032,7 @@ "localStorage": true } }, - "@storybook/client-api>stable": { + "@storybook/react>@storybook/store>stable": { "globals": { "define": true } @@ -4781,11 +4901,19 @@ "setTimeout": true }, "packages": { + "eth-block-tracker>@metamask/utils": true, "eth-block-tracker>pify": true, "eth-query>json-rpc-random-id": true, "json-rpc-engine>@metamask/safe-event-emitter": true } }, + "eth-block-tracker>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-block-tracker>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-ens-namehash": { "globals": { "name": "write" @@ -4829,33 +4957,6 @@ "eth-json-rpc-filters>json-rpc-engine": true } }, - "eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-json-rpc-infura>eth-rpc-errors": true, - "eth-json-rpc-infura>json-rpc-engine": true, - "node-fetch": true - } - }, - "eth-json-rpc-infura>eth-json-rpc-middleware": { - "packages": { - "safe-event-emitter": true - } - }, - "eth-json-rpc-infura>eth-rpc-errors": { - "packages": { - "eth-rpc-errors>fast-safe-stringify": true - } - }, - "eth-json-rpc-infura>json-rpc-engine": { - "packages": { - "eth-json-rpc-infura>eth-rpc-errors": true, - "safe-event-emitter": true - } - }, "eth-json-rpc-middleware": { "globals": { "URL": true, @@ -4866,6 +4967,7 @@ }, "packages": { "browserify>browser-resolve": true, + "eth-json-rpc-middleware>@metamask/utils": true, "eth-json-rpc-middleware>eth-sig-util": true, "eth-json-rpc-middleware>pify": true, "eth-rpc-errors": true, @@ -4875,6 +4977,13 @@ "vinyl>clone": true } }, + "eth-json-rpc-middleware>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-json-rpc-middleware>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-json-rpc-middleware>eth-sig-util": { "packages": { "eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, @@ -5181,6 +5290,7 @@ "eth-query": { "packages": { "eth-query>json-rpc-random-id": true, + "madge>debug": true, "watchify>xtend": true } }, @@ -6366,6 +6476,11 @@ "browserify>buffer": true } }, + "pubnub>superagent>cookiejar": { + "globals": { + "console.warn": true + } + }, "pump": { "packages": { "browserify>browser-resolve": true, @@ -6676,9 +6791,9 @@ "react-router-dom>react-router>mini-create-react-context": { "packages": { "@babel/runtime": true, - "@storybook/api>@reach/router>create-react-context>gud": true, "prop-types": true, "react": true, + "react-router-dom>react-router>mini-create-react-context>gud": true, "react-router-dom>tiny-warning": true } }, @@ -6905,6 +7020,44 @@ "jsdom>request>is-typedarray": true } }, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping": { + "globals": { + "define": true + }, + "packages": { + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": true, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true, + "terser>@jridgewell/source-map>@jridgewell/trace-mapping": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": { + "globals": { + "define": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": { + "globals": { + "TextDecoder": true, + "define": true + }, + "packages": { + "browserify>buffer": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/trace-mapping": { + "globals": { + "define": true + }, + "packages": { + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true, + "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/resolve-uri": { + "globals": { + "define": true + } + }, "terser>source-map-support>buffer-from": { "packages": { "browserify>buffer": true @@ -6939,18 +7092,7 @@ }, "web3": { "globals": { - "Web3": "write", - "XMLHttpRequest": true, - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "browserify>buffer": true, - "web3>bignumber.js": true, - "web3>crypto-js": true, - "web3>utf8": true, - "web3>xhr2-cookies": true + "XMLHttpRequest": true } }, "web3-stream-provider": { @@ -6969,43 +7111,6 @@ "msCrypto": true } }, - "web3>bignumber.js": { - "globals": { - "define": true - }, - "packages": { - "browserify>crypto-browserify": true - } - }, - "web3>crypto-js": { - "globals": { - "define": true - } - }, - "web3>utf8": { - "globals": { - "define": true - } - }, - "web3>xhr2-cookies": { - "globals": { - "console.warn": true - }, - "packages": { - "browserify>buffer": true, - "browserify>https-browserify": true, - "browserify>os-browserify": true, - "browserify>process": true, - "browserify>stream-http": true, - "browserify>url": true, - "web3>xhr2-cookies>cookiejar": true - } - }, - "web3>xhr2-cookies>cookiejar": { - "globals": { - "console.warn": true - } - }, "webextension-polyfill": { "globals": { "browser": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 033fd9e34..b9fa80382 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -764,7 +764,7 @@ "3box>ipfs>ipld-raw>multihashing-async": true, "3box>ipfs>multicodec": true, "3box>ipfs>protons": true, - "@storybook/client-api>stable": true, + "@storybook/react>@storybook/store>stable": true, "browserify>assert": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true @@ -2635,12 +2635,14 @@ "@metamask/contract-metadata": true, "@metamask/controllers>abort-controller": true, "@metamask/controllers>async-mutex": true, + "@metamask/controllers>eth-json-rpc-infura": true, "@metamask/controllers>eth-method-registry": true, "@metamask/controllers>eth-phishing-detect": true, "@metamask/controllers>ethereumjs-wallet": true, "@metamask/controllers>isomorphic-fetch": true, "@metamask/controllers>multiformats": true, "@metamask/controllers>nanoid": true, + "@metamask/controllers>web3": true, "@metamask/controllers>web3-provider-engine": true, "@metamask/metamask-eth-abis": true, "browserify>buffer": true, @@ -2648,7 +2650,6 @@ "deep-freeze-strict": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, - "eth-json-rpc-infura": true, "eth-keyring-controller": true, "eth-query": true, "eth-rpc-errors": true, @@ -2661,8 +2662,7 @@ "jsonschema": true, "punycode": true, "single-call-balance-checker-abi": true, - "uuid": true, - "web3": true + "uuid": true } }, "@metamask/controllers>abort-controller": { @@ -2683,6 +2683,33 @@ "define": true } }, + "@metamask/controllers>eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/controllers>eth-json-rpc-infura>eth-json-rpc-middleware": true, + "@metamask/controllers>eth-json-rpc-infura>eth-rpc-errors": true, + "@metamask/controllers>eth-json-rpc-infura>json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/controllers>eth-json-rpc-infura>eth-json-rpc-middleware": { + "packages": { + "safe-event-emitter": true + } + }, + "@metamask/controllers>eth-json-rpc-infura>eth-rpc-errors": { + "packages": { + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/controllers>eth-json-rpc-infura>json-rpc-engine": { + "packages": { + "@metamask/controllers>eth-json-rpc-infura>eth-rpc-errors": true, + "safe-event-emitter": true + } + }, "@metamask/controllers>eth-method-registry": { "packages": { "@metamask/controllers>eth-method-registry>ethjs": true @@ -2805,6 +2832,22 @@ "crypto.getRandomValues": true } }, + "@metamask/controllers>web3": { + "globals": { + "Web3": "write", + "XMLHttpRequest": true, + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/controllers>web3>bignumber.js": true, + "@metamask/controllers>web3>crypto-js": true, + "@metamask/controllers>web3>utf8": true, + "@metamask/controllers>web3>xhr2-cookies": true, + "browserify>buffer": true + } + }, "@metamask/controllers>web3-provider-engine": { "globals": { "WebSocket": true, @@ -2813,6 +2856,7 @@ }, "packages": { "@ethereumjs/tx": true, + "@metamask/controllers>eth-json-rpc-infura": true, "@metamask/controllers>web3-provider-engine>backoff": true, "@metamask/controllers>web3-provider-engine>eth-block-tracker": true, "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware": true, @@ -2824,7 +2868,6 @@ "browserify>events": true, "browserify>util": true, "eth-json-rpc-filters": true, - "eth-json-rpc-infura": true, "gh-pages>async": true, "lavamoat>json-stable-stringify": true, "watchify>xtend": true @@ -2943,6 +2986,121 @@ "browserify>process": true } }, + "@metamask/controllers>web3>bignumber.js": { + "globals": { + "define": true + }, + "packages": { + "browserify>crypto-browserify": true + } + }, + "@metamask/controllers>web3>crypto-js": { + "globals": { + "define": true + } + }, + "@metamask/controllers>web3>utf8": { + "globals": { + "define": true + } + }, + "@metamask/controllers>web3>xhr2-cookies": { + "globals": { + "console.warn": true + }, + "packages": { + "browserify>buffer": true, + "browserify>https-browserify": true, + "browserify>os-browserify": true, + "browserify>process": true, + "browserify>stream-http": true, + "browserify>url": true, + "pubnub>superagent>cookiejar": true + } + }, + "@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/eth-json-rpc-infura>@metamask/utils": { + "packages": { + "@metamask/eth-json-rpc-infura>@metamask/utils>superstruct": true, + "eslint>fast-deep-equal": true, + "madge>debug": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "json-rpc-engine>@metamask/safe-event-emitter": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": true, + "bn.js": true, + "browserify>buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, "@metamask/eth-ledger-bridge-keyring": { "globals": { "addEventListener": true, @@ -3366,7 +3524,7 @@ "localStorage": true } }, - "@storybook/client-api>stable": { + "@storybook/react>@storybook/store>stable": { "globals": { "define": true } @@ -4201,11 +4359,19 @@ "setTimeout": true }, "packages": { + "eth-block-tracker>@metamask/utils": true, "eth-block-tracker>pify": true, "eth-query>json-rpc-random-id": true, "json-rpc-engine>@metamask/safe-event-emitter": true } }, + "eth-block-tracker>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-block-tracker>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-ens-namehash": { "globals": { "name": "write" @@ -4249,33 +4415,6 @@ "eth-json-rpc-filters>json-rpc-engine": true } }, - "eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-json-rpc-infura>eth-rpc-errors": true, - "eth-json-rpc-infura>json-rpc-engine": true, - "node-fetch": true - } - }, - "eth-json-rpc-infura>eth-json-rpc-middleware": { - "packages": { - "safe-event-emitter": true - } - }, - "eth-json-rpc-infura>eth-rpc-errors": { - "packages": { - "eth-rpc-errors>fast-safe-stringify": true - } - }, - "eth-json-rpc-infura>json-rpc-engine": { - "packages": { - "eth-json-rpc-infura>eth-rpc-errors": true, - "safe-event-emitter": true - } - }, "eth-json-rpc-middleware": { "globals": { "URL": true, @@ -4286,6 +4425,7 @@ }, "packages": { "browserify>browser-resolve": true, + "eth-json-rpc-middleware>@metamask/utils": true, "eth-json-rpc-middleware>eth-sig-util": true, "eth-json-rpc-middleware>pify": true, "eth-rpc-errors": true, @@ -4295,6 +4435,13 @@ "vinyl>clone": true } }, + "eth-json-rpc-middleware>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-json-rpc-middleware>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-json-rpc-middleware>eth-sig-util": { "packages": { "eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, @@ -4601,6 +4748,7 @@ "eth-query": { "packages": { "eth-query>json-rpc-random-id": true, + "madge>debug": true, "watchify>xtend": true } }, @@ -5754,6 +5902,11 @@ "browserify>buffer": true } }, + "pubnub>superagent>cookiejar": { + "globals": { + "console.warn": true + } + }, "pump": { "packages": { "browserify>browser-resolve": true, @@ -6064,9 +6217,9 @@ "react-router-dom>react-router>mini-create-react-context": { "packages": { "@babel/runtime": true, - "@storybook/api>@reach/router>create-react-context>gud": true, "prop-types": true, "react": true, + "react-router-dom>react-router>mini-create-react-context>gud": true, "react-router-dom>tiny-warning": true } }, @@ -6308,18 +6461,7 @@ }, "web3": { "globals": { - "Web3": "write", - "XMLHttpRequest": true, - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "browserify>buffer": true, - "web3>bignumber.js": true, - "web3>crypto-js": true, - "web3>utf8": true, - "web3>xhr2-cookies": true + "XMLHttpRequest": true } }, "web3-stream-provider": { @@ -6338,43 +6480,6 @@ "msCrypto": true } }, - "web3>bignumber.js": { - "globals": { - "define": true - }, - "packages": { - "browserify>crypto-browserify": true - } - }, - "web3>crypto-js": { - "globals": { - "define": true - } - }, - "web3>utf8": { - "globals": { - "define": true - } - }, - "web3>xhr2-cookies": { - "globals": { - "console.warn": true - }, - "packages": { - "browserify>buffer": true, - "browserify>https-browserify": true, - "browserify>os-browserify": true, - "browserify>process": true, - "browserify>stream-http": true, - "browserify>url": true, - "web3>xhr2-cookies>cookiejar": true - } - }, - "web3>xhr2-cookies>cookiejar": { - "globals": { - "console.warn": true - } - }, "webextension-polyfill": { "globals": { "browser": true, diff --git a/lavamoat/browserify/policy-override.json b/lavamoat/browserify/policy-override.json index 96504e814..4089a7536 100644 --- a/lavamoat/browserify/policy-override.json +++ b/lavamoat/browserify/policy-override.json @@ -58,6 +58,11 @@ "XMLHttpRequest": true } }, + "@metamask/controllers>web3": { + "globals": { + "XMLHttpRequest": true + } + }, "react-dom": { "globals": { "HTMLIFrameElement": true diff --git a/lavamoat/build-system/policy-override.json b/lavamoat/build-system/policy-override.json index 84098a0b4..499e683e2 100644 --- a/lavamoat/build-system/policy-override.json +++ b/lavamoat/build-system/policy-override.json @@ -91,6 +91,8 @@ "packages": { "eslint-plugin-jest>@typescript-eslint/experimental-utils>@typescript-eslint/types": true, "eslint-plugin-jest>@typescript-eslint/utils>eslint-utils": true, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/types": true, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager": true, "@typescript-eslint/parser>@typescript-eslint/types": true, "eslint": true, "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, @@ -99,6 +101,36 @@ "eslint>eslint-utils": true } }, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager>@typescript-eslint/visitor-keys": { + "builtin": { + "path": true + }, + "packages": { + "eslint>eslint-visitor-keys": true + } + }, + "@typescript-eslint/eslint-plugin>@typescript-eslint/utils": { + "builtin": { + "path": true + }, + "packages": { + "eslint>eslint-utils": true, + "@typescript-eslint/parser>@typescript-eslint/types": true, + "eslint": true, + "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, + "@babel/eslint-parser>eslint-scope": true, + "@typescript-eslint/utils": true + } + }, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager": { + "builtin": { + "path": true + }, + "packages": { + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager>@typescript-eslint/visitor-keys": true, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/types": true + } + }, "eslint-plugin-jest>@typescript-eslint/experimental-utils": { "builtin": { "path": true @@ -121,6 +153,7 @@ "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils": { "packages": { "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils>debug": true, + "@typescript-eslint/eslint-plugin>@typescript-eslint/utils": true, "@typescript-eslint/eslint-plugin>tsutils": true, "eslint-plugin-jest>@typescript-eslint/utils": true, "eslint>debug": true, @@ -142,6 +175,7 @@ "typescript": true, "eslint-plugin-jest>@typescript-eslint/utils": true, "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils": true, + "@typescript-eslint/eslint-plugin>@typescript-eslint/utils": true, "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, "@typescript-eslint/eslint-plugin>tsutils": true, "eslint>debug": true, diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 0bb487573..3b27e82c2 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -62,7 +62,7 @@ }, "packages": { "@babel/core>@ampproject/remapping>@jridgewell/gen-mapping": true, - "@babel/core>@ampproject/remapping>@jridgewell/trace-mapping": true + "terser>@jridgewell/source-map>@jridgewell/trace-mapping": true } }, "@babel/core>@ampproject/remapping>@jridgewell/gen-mapping": { @@ -70,22 +70,8 @@ "define": true }, "packages": { - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/set-array": true, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true - } - }, - "@babel/core>@ampproject/remapping>@jridgewell/trace-mapping": { - "globals": { - "define": true - }, - "packages": { - "@babel/core>@ampproject/remapping>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true - } - }, - "@babel/core>@ampproject/remapping>@jridgewell/trace-mapping>@jridgewell/resolve-uri": { - "globals": { - "define": true + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": true, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true } }, "@babel/core>@babel/generator": { @@ -93,31 +79,9 @@ "console.error": true }, "packages": { - "@babel/core>@babel/generator>@jridgewell/gen-mapping": true, "@babel/core>@babel/generator>jsesc": true, - "@babel/core>@babel/types": true - } - }, - "@babel/core>@babel/generator>@jridgewell/gen-mapping": { - "globals": { - "define": true - }, - "packages": { - "@babel/core>@ampproject/remapping>@jridgewell/trace-mapping": true, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/set-array": true, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true - } - }, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/set-array": { - "globals": { - "define": true - } - }, - "@babel/core>@babel/generator>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": { - "globals": { - "Buffer": true, - "TextDecoder": true, - "define": true + "@babel/core>@babel/types": true, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true } }, "@babel/core>@babel/generator>jsesc": { @@ -1092,11 +1056,6 @@ "define": true } }, - "@storybook/addon-essentials>@storybook/addon-docs>acorn-walk": { - "globals": { - "define": true - } - }, "@storybook/addon-essentials>@storybook/addon-docs>remark-slug>unist-util-visit": { "packages": { "@storybook/addon-essentials>@storybook/addon-docs>remark-slug>unist-util-visit>unist-util-visit-parents": true @@ -1117,47 +1076,20 @@ "util.deprecate": true } }, - "@storybook/components>react-syntax-highlighter>refractor>parse-entities": { - "packages": { - "@storybook/components>react-syntax-highlighter>refractor>parse-entities>character-entities": true, - "@storybook/components>react-syntax-highlighter>refractor>parse-entities>character-entities-legacy": true, - "@storybook/components>react-syntax-highlighter>refractor>parse-entities>character-reference-invalid": true, - "@storybook/components>react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": true, - "@storybook/components>react-syntax-highlighter>refractor>parse-entities>is-hexadecimal": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true + "@storybook/react>acorn-walk": { + "globals": { + "define": true } }, - "@storybook/components>react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": { - "packages": { - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-alphabetical": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true - } - }, - "@storybook/react>@storybook/core-common>glob-base": { - "builtin": { - "path.dirname": true - }, - "packages": { - "@storybook/react>@storybook/core-common>glob-base>glob-parent": true, - "@storybook/react>@storybook/core-common>glob-base>is-glob": true - } - }, - "@storybook/react>@storybook/core-common>glob-base>glob-parent": { - "builtin": { - "path.dirname": true - }, - "packages": { - "@storybook/react>@storybook/core-common>glob-base>is-glob": true - } - }, - "@storybook/react>@storybook/core-common>glob-base>is-glob": { - "packages": { - "gulp-watch>anymatch>micromatch>is-extglob": true + "@storybook/react>webpack>json-parse-even-better-errors": { + "globals": { + "Buffer.isBuffer": true } }, "@typescript-eslint/eslint-plugin": { "packages": { "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils": true, + "@typescript-eslint/eslint-plugin>@typescript-eslint/utils": true, "@typescript-eslint/eslint-plugin>tsutils": true, "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, "eslint": true, @@ -1172,6 +1104,7 @@ "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils": { "packages": { "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils>debug": true, + "@typescript-eslint/eslint-plugin>@typescript-eslint/utils": true, "@typescript-eslint/eslint-plugin>tsutils": true, "eslint-plugin-jest>@typescript-eslint/utils": true, "eslint>debug": true, @@ -1188,6 +1121,19 @@ "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils>debug>ms": true } }, + "@typescript-eslint/eslint-plugin>@typescript-eslint/utils": { + "builtin": { + "path": true + }, + "packages": { + "@babel/eslint-parser>eslint-scope": true, + "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, + "@typescript-eslint/parser>@typescript-eslint/types": true, + "@typescript-eslint/utils": true, + "eslint": true, + "eslint>eslint-utils": true + } + }, "@typescript-eslint/eslint-plugin>tsutils": { "packages": { "@sentry/utils>tslib": true, @@ -1386,7 +1332,7 @@ }, "brfs>static-module>acorn-node": { "packages": { - "@storybook/addon-essentials>@storybook/addon-docs>acorn-walk": true, + "@storybook/react>acorn-walk": true, "brfs>static-module>acorn-node>acorn": true, "watchify>xtend": true } @@ -2073,8 +2019,8 @@ "depcheck>cosmiconfig>parse-json": { "packages": { "@babel/code-frame": true, + "@storybook/react>webpack>json-parse-even-better-errors": true, "depcheck>cosmiconfig>parse-json>error-ex": true, - "depcheck>cosmiconfig>parse-json>json-parse-even-better-errors": true, "depcheck>cosmiconfig>parse-json>lines-and-columns": true } }, @@ -2086,11 +2032,6 @@ "depcheck>cosmiconfig>parse-json>error-ex>is-arrayish": true } }, - "depcheck>cosmiconfig>parse-json>json-parse-even-better-errors": { - "globals": { - "Buffer.isBuffer": true - } - }, "depcheck>cosmiconfig>yaml": { "globals": { "Buffer": true, @@ -2557,11 +2498,30 @@ "@typescript-eslint/parser>@typescript-eslint/types": true, "eslint": true, "eslint-plugin-jest>@typescript-eslint/experimental-utils>@typescript-eslint/types": true, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager": true, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/types": true, "eslint-plugin-jest>@typescript-eslint/utils>eslint-utils": true, "eslint>eslint-scope": true, "eslint>eslint-utils": true } }, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager": { + "builtin": { + "path": true + }, + "packages": { + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager>@typescript-eslint/visitor-keys": true, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/types": true + } + }, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager>@typescript-eslint/visitor-keys": { + "builtin": { + "path": true + }, + "packages": { + "eslint>eslint-visitor-keys": true + } + }, "eslint-plugin-jest>@typescript-eslint/utils>eslint-utils": { "packages": { "eslint-plugin-jest>@typescript-eslint/utils>eslint-utils>eslint-visitor-keys": true @@ -2625,10 +2585,15 @@ }, "eslint-plugin-node>eslint-plugin-es": { "packages": { - "eslint-plugin-node>eslint-utils": true, + "eslint-plugin-node>eslint-plugin-es>eslint-utils": true, "eslint>regexpp": true } }, + "eslint-plugin-node>eslint-plugin-es>eslint-utils": { + "packages": { + "eslint-plugin-node>eslint-plugin-es>eslint-utils>eslint-visitor-keys": true + } + }, "eslint-plugin-node>eslint-utils": { "packages": { "eslint-plugin-node>eslint-utils>eslint-visitor-keys": true @@ -2857,7 +2822,7 @@ "packages": { "eslint>eslint-visitor-keys": true, "eslint>espree>acorn-jsx": true, - "ts-node>acorn": true + "terser>acorn": true } }, "eslint>espree>acorn-jsx": { @@ -4182,12 +4147,34 @@ }, "gulp-watch>anymatch>micromatch>parse-glob": { "packages": { - "@storybook/react>@storybook/core-common>glob-base": true, "gulp-watch>anymatch>micromatch>is-extglob": true, + "gulp-watch>anymatch>micromatch>parse-glob>glob-base": true, "gulp-watch>anymatch>micromatch>parse-glob>is-dotfile": true, "gulp-watch>anymatch>micromatch>parse-glob>is-glob": true } }, + "gulp-watch>anymatch>micromatch>parse-glob>glob-base": { + "builtin": { + "path.dirname": true + }, + "packages": { + "gulp-watch>anymatch>micromatch>parse-glob>glob-base>glob-parent": true, + "gulp-watch>anymatch>micromatch>parse-glob>glob-base>is-glob": true + } + }, + "gulp-watch>anymatch>micromatch>parse-glob>glob-base>glob-parent": { + "builtin": { + "path.dirname": true + }, + "packages": { + "gulp-watch>anymatch>micromatch>parse-glob>glob-base>is-glob": true + } + }, + "gulp-watch>anymatch>micromatch>parse-glob>glob-base>is-glob": { + "packages": { + "gulp-watch>anymatch>micromatch>is-extglob": true + } + }, "gulp-watch>anymatch>micromatch>parse-glob>is-glob": { "packages": { "gulp-watch>anymatch>micromatch>is-extglob": true @@ -6335,7 +6322,6 @@ }, "stylelint>@stylelint/postcss-markdown>remark>remark-parse": { "packages": { - "@storybook/components>react-syntax-highlighter>refractor>parse-entities": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>ccount": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>collapse-white-space": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-alphabetical": true, @@ -6343,6 +6329,7 @@ "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-whitespace-character": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-word-character": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>markdown-escapes": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>state-toggle": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>trim": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>trim-trailing-lines": true, @@ -6353,6 +6340,22 @@ "webpack>micromatch>braces>fill-range>repeat-string": true } }, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities": { + "packages": { + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities>character-entities": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities>character-entities-legacy": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities>character-reference-invalid": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities>is-alphanumerical": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities>is-hexadecimal": true + } + }, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities>is-alphanumerical": { + "packages": { + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-alphabetical": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true + } + }, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unherit": { "packages": { "pumpify>inherits": true, @@ -6366,11 +6369,11 @@ }, "stylelint>@stylelint/postcss-markdown>remark>remark-stringify": { "packages": { - "@storybook/components>react-syntax-highlighter>refractor>parse-entities": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>ccount": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-whitespace-character": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>markdown-escapes": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>state-toggle": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unherit": true, "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>is-alphanumeric": true, @@ -6394,10 +6397,10 @@ }, "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>stringify-entities": { "packages": { - "@storybook/components>react-syntax-highlighter>refractor>parse-entities>character-entities-legacy": true, - "@storybook/components>react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": true, - "@storybook/components>react-syntax-highlighter>refractor>parse-entities>is-hexadecimal": true, "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities>character-entities-legacy": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities>is-alphanumerical": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>parse-entities>is-hexadecimal": true, "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>stringify-entities>character-entities-html4": true } }, @@ -6867,17 +6870,60 @@ "console.log": true, "console.warn": true, "define": true, - "process.argv": true, - "process.exit": true, - "process.platform": true, - "process.stderr.write": true, - "process.stdin.on": true, - "process.stdin.resume": true, - "process.stdin.setEncoding": true + "process": true }, "packages": { - "source-map": true, - "ts-node>acorn": true + "terser>@jridgewell/source-map": true, + "terser>acorn": true + } + }, + "terser>@jridgewell/source-map": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "define": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping": { + "globals": { + "define": true + }, + "packages": { + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": true, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true, + "terser>@jridgewell/source-map>@jridgewell/trace-mapping": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": { + "globals": { + "define": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "define": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/trace-mapping": { + "globals": { + "define": true + }, + "packages": { + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true, + "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/resolve-uri": { + "globals": { + "define": true + } + }, + "terser>acorn": { + "globals": { + "console": true, + "define": true } }, "terser>source-map-support": { @@ -6925,12 +6971,6 @@ "pumpify>inherits": true } }, - "ts-node>acorn": { - "globals": { - "console": true, - "define": true - } - }, "tsutils": { "packages": { "tslib": true, diff --git a/package.json b/package.json index 65868d4cf..a7e5e0dfc 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dist": "yarn build dist", "build": "yarn lavamoat:build", "build:dev": "node development/build/index.js", + "build:icons": "node ui/components/component-library/icon/scripts/generate-icon-names.js && node ui/components/component-library/icon/scripts/generate-icon-scss.js", "start:test": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build testDev", "benchmark:chrome": "SELENIUM_BROWSER=chrome node test/e2e/benchmark.js", "mv3:stats:chrome": "SELENIUM_BROWSER=chrome ENABLE_MV3=true node test/e2e/mv3-perf-stats/index.js", @@ -64,9 +65,9 @@ "devtools:redux": "remotedev --hostname=localhost --port=8000", "start:dev": "concurrently -k -n build,react,redux yarn:start yarn:devtools:react yarn:devtools:redux", "announce": "node development/announcer.js", - "storybook": "start-storybook -p 6006 -c .storybook -s ./app,./.storybook/images", + "storybook": "start-storybook -p 6006 -c .storybook", "storybook:test": "jest --config=./jest.stories.config.js", - "storybook:build": "build-storybook -c .storybook -o storybook-build -s ./app,./.storybook/images", + "storybook:build": "build-storybook -c .storybook -o storybook-build", "storybook:deploy": "storybook-to-ghpages --existing-output-dir storybook-build --remote storybook --branch master", "update-changelog": "auto-changelog update", "generate:migration": "./development/generate-migration.sh", @@ -104,7 +105,6 @@ "netmask": "^2.0.1", "pubnub/superagent-proxy": "^3.0.0", "pull-ws": "^3.3.2", - "ws": "^7.4.6", "json-schema": "^0.4.0", "simple-get": "^4.0.1" }, @@ -125,6 +125,7 @@ "@metamask/controllers": "^30.3.0", "@metamask/design-tokens": "^1.9.0", "@metamask/eth-ledger-bridge-keyring": "^0.13.0", + "@metamask/eth-json-rpc-infura": "^7.0.0", "@metamask/eth-token-tracker": "^4.0.0", "@metamask/etherscan-link": "^2.2.0", "@metamask/jazzicon": "^2.0.0", @@ -162,11 +163,10 @@ "debounce-stream": "^2.0.0", "deep-freeze-strict": "1.1.1", "end-of-stream": "^1.4.4", - "eth-block-tracker": "^5.0.1", + "eth-block-tracker": "^6.0.0", "eth-ens-namehash": "^2.0.8", "eth-json-rpc-filters": "^4.2.1", - "eth-json-rpc-infura": "^5.1.0", - "eth-json-rpc-middleware": "^8.0.0", + "eth-json-rpc-middleware": "^9.0.0", "eth-keyring-controller": "^7.0.2", "eth-lattice-keyring": "^0.12.0", "eth-method-registry": "^2.0.0", @@ -188,6 +188,7 @@ "globalthis": "^1.0.1", "human-standard-token-abi": "^2.0.0", "immer": "^9.0.6", + "jest-junit": "^14.0.1", "json-rpc-engine": "^6.1.0", "json-rpc-middleware-stream": "^2.1.1", "jsonschema": "^1.2.4", @@ -233,7 +234,6 @@ "unicode-confusables": "^0.1.1", "uuid": "^8.3.2", "valid-url": "^1.0.9", - "web3": "^0.20.7", "web3-stream-provider": "^4.0.0", "zxcvbn": "^4.4.2" }, @@ -258,19 +258,19 @@ "@metamask/phishing-warning": "^1.2.1", "@metamask/test-dapp": "^5.2.0", "@sentry/cli": "^1.58.0", - "@storybook/addon-a11y": "^6.3.12", - "@storybook/addon-actions": "^6.3.12", - "@storybook/addon-essentials": "^6.3.12", - "@storybook/addon-knobs": "^6.3.1", - "@storybook/addons": "^6.3.12", - "@storybook/api": "^6.3.12", - "@storybook/client-api": "^6.3.12", - "@storybook/components": "^6.3.12", - "@storybook/core": "^6.3.12", - "@storybook/core-events": "^6.3.0", - "@storybook/react": "^6.3.12", - "@storybook/storybook-deployer": "^2.8.10", - "@storybook/theming": "^6.3.0", + "@storybook/addon-a11y": "^6.5.10", + "@storybook/addon-actions": "^6.5.10", + "@storybook/addon-essentials": "^6.5.10", + "@storybook/addon-knobs": "^6.4.0", + "@storybook/addons": "^6.5.10", + "@storybook/api": "^6.5.10", + "@storybook/client-api": "^6.5.10", + "@storybook/components": "^6.5.10", + "@storybook/core": "^6.5.10", + "@storybook/core-events": "^6.5.10", + "@storybook/react": "^6.5.10", + "@storybook/storybook-deployer": "^2.8.12", + "@storybook/theming": "^6.5.10", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^10.4.8", "@testing-library/react-hooks": "^3.2.1", @@ -302,7 +302,7 @@ "browserify": "^16.5.1", "chalk": "^3.0.0", "chokidar": "^3.5.3", - "chromedriver": "^103.0.0", + "chromedriver": "^105.0.0", "concurrently": "^5.2.0", "copy-webpack-plugin": "^6.0.3", "cross-spawn": "^7.0.3", @@ -327,6 +327,7 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.23.1", "eslint-plugin-react-hooks": "^4.2.0", + "eslint-plugin-storybook": "^0.6.4", "fancy-log": "^1.3.3", "fast-glob": "^3.2.2", "fs-extra": "^8.1.0", @@ -386,7 +387,7 @@ "source-map": "^0.7.2", "source-map-explorer": "^2.4.2", "squirrelly": "^8.0.8", - "storybook-dark-mode": "^1.0.9", + "storybook-dark-mode": "^1.1.0", "string.prototype.matchall": "^4.0.2", "style-loader": "^0.21.0", "stylelint": "^13.6.1", @@ -445,8 +446,6 @@ "@storybook/core>@storybook/core-client>@storybook/ui>core-js-pure": false, "eth-json-rpc-filters>eth-json-rpc-middleware>ethereumjs-util>keccak": false, "eth-json-rpc-filters>eth-json-rpc-middleware>ethereumjs-util>secp256k1": false, - "eth-json-rpc-infura>eth-json-rpc-middleware>ethereumjs-util>keccak": false, - "eth-json-rpc-infura>eth-json-rpc-middleware>ethereumjs-util>secp256k1": false, "eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>keccak": false, "eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>secp256k1": false, "eth-lattice-keyring>gridplus-sdk": false, @@ -471,7 +470,15 @@ "@keystonehq/bc-ur-registry-eth>hdkey>secp256k1": false, "@metamask/rpc-methods>@metamask/key-tree>secp256k1": false, "eth-lattice-keyring>gridplus-sdk>secp256k1": false, - "eth-lattice-keyring>secp256k1": false + "eth-lattice-keyring>secp256k1": false, + "@storybook/react>@pmmmwh/react-refresh-webpack-plugin>core-js-pure": false, + "@testing-library/jest-dom>aria-query>@babel/runtime-corejs3>core-js-pure": false, + "web3": false, + "web3>web3-bzz": false, + "web3>web3-core>web3-core-requestmanager>web3-providers-ws>websocket>bufferutil": false, + "web3>web3-core>web3-core-requestmanager>web3-providers-ws>websocket>es5-ext": false, + "web3>web3-core>web3-core-requestmanager>web3-providers-ws>websocket>utf-8-validate": false, + "web3>web3-shh": false } } } diff --git a/patches/@metamask+snap-controllers+0.20.0.patch b/patches/@metamask+snap-controllers+0.20.0.patch new file mode 100644 index 000000000..6e1df558c --- /dev/null +++ b/patches/@metamask+snap-controllers+0.20.0.patch @@ -0,0 +1,62 @@ +diff --git a/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js b/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js +index ad84417..158e8e6 100644 +--- a/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js ++++ b/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js +@@ -30,6 +30,7 @@ const RequestQueue_1 = require("./RequestQueue"); + const utils_3 = require("./utils"); + const Timer_1 = require("./Timer"); + exports.controllerName = 'SnapController'; ++exports.SNAP_APPROVAL_INSTALL = 'wallet_installSnap'; + exports.SNAP_APPROVAL_UPDATE = 'wallet_updateSnap'; + const TRUNCATED_SNAP_PROPERTIES = new Set([ + 'initialPermissions', +@@ -738,7 +739,7 @@ class SnapController extends controllers_1.BaseControllerV2 { + id: snapId, + versionRange, + }); +- await this.authorize(snapId); ++ await this.authorize(origin, snapId); + await this._startSnap({ + snapId, + sourceCode, +@@ -1073,18 +1074,34 @@ class SnapController extends controllers_1.BaseControllerV2 { + * @param snapId - The id of the Snap. + * @returns The snap's approvedPermissions. + */ +- async authorize(snapId) { ++ async authorize(origin, snapId) { + console.info(`Authorizing snap: ${snapId}`); + const snapsState = this.state.snaps; + const snap = snapsState[snapId]; + const { initialPermissions } = snap; + try { +- if ((0, utils_1.isNonEmptyArray)(Object.keys(initialPermissions))) { +- const processedPermissions = this.processSnapPermissions(initialPermissions); +- const [approvedPermissions] = await this.messagingSystem.call('PermissionController:requestPermissions', { origin: snapId }, processedPermissions); +- return Object.values(approvedPermissions).map((perm) => perm.parentCapability); ++ const processedPermissions = this.processSnapPermissions(initialPermissions); ++ const id = (0, nanoid_1.nanoid)(); ++ const isApproved = await this.messagingSystem.call('ApprovalController:addRequest', { ++ origin, ++ id, ++ type: exports.SNAP_APPROVAL_INSTALL, ++ requestData: { ++ // Mirror previous installation metadata ++ metadata: { id, origin: snapId, dappOrigin: origin }, ++ permissions: processedPermissions, ++ snapId, ++ }, ++ }, true); ++ if (!isApproved) { ++ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest(); ++ } ++ if ((0, utils_1.isNonEmptyArray)(Object.keys(processedPermissions))) { ++ await this.messagingSystem.call('PermissionController:grantPermissions', { ++ approvedPermissions: processedPermissions, ++ subject: { origin: snapId }, ++ }); + } +- return []; + } + finally { + const runtime = this.getRuntimeExpect(snapId); diff --git a/patches/eth-query+2.1.2.patch b/patches/eth-query+2.1.2.patch new file mode 100644 index 000000000..ea405d5f8 --- /dev/null +++ b/patches/eth-query+2.1.2.patch @@ -0,0 +1,22 @@ +diff --git a/node_modules/eth-query/index.js b/node_modules/eth-query/index.js +index 13e9f3c..303d703 100644 +--- a/node_modules/eth-query/index.js ++++ b/node_modules/eth-query/index.js +@@ -1,5 +1,6 @@ + const extend = require('xtend') + const createRandomId = require('json-rpc-random-id')() ++const debug = require('debug')('eth-query'); + + module.exports = EthQuery + +@@ -63,7 +64,9 @@ EthQuery.prototype.submitHashrate = generateFnFor('eth_subm + + EthQuery.prototype.sendAsync = function(opts, cb){ + const self = this +- self.currentProvider.sendAsync(createPayload(opts), function(err, response){ ++ const payload = createPayload(opts) ++ debug('making request %o', payload) ++ self.currentProvider.sendAsync(payload, function(err, response){ + if (!err && response.error) err = new Error('EthQuery - RPC Error - '+response.error.message) + if (err) return cb(err) + cb(null, response.result) diff --git a/shared/constants/network.js b/shared/constants/network.js deleted file mode 100644 index 2f60d19a2..000000000 --- a/shared/constants/network.js +++ /dev/null @@ -1,455 +0,0 @@ -import { capitalize } from 'lodash'; - -export const ROPSTEN = 'ropsten'; -export const RINKEBY = 'rinkeby'; -export const KOVAN = 'kovan'; -export const MAINNET = 'mainnet'; -export const GOERLI = 'goerli'; -export const LOCALHOST = 'localhost'; -export const NETWORK_TYPE_RPC = 'rpc'; -export const HOMESTEAD = 'homestead'; -export const SEPOLIA = 'sepolia'; - -export const MAINNET_NETWORK_ID = '1'; -export const ROPSTEN_NETWORK_ID = '3'; -export const RINKEBY_NETWORK_ID = '4'; -export const GOERLI_NETWORK_ID = '5'; -export const KOVAN_NETWORK_ID = '42'; -export const SEPOLIA_NETWORK_ID = '11155111'; -export const LOCALHOST_NETWORK_ID = '1337'; - -export const MAINNET_CHAIN_ID = '0x1'; -export const ROPSTEN_CHAIN_ID = '0x3'; -export const RINKEBY_CHAIN_ID = '0x4'; -export const GOERLI_CHAIN_ID = '0x5'; -export const KOVAN_CHAIN_ID = '0x2a'; -export const SEPOLIA_CHAIN_ID = '0xaa36a7'; -export const LOCALHOST_CHAIN_ID = '0x539'; -export const BSC_CHAIN_ID = '0x38'; -export const OPTIMISM_CHAIN_ID = '0xa'; -export const OPTIMISM_TESTNET_CHAIN_ID = '0x1a4'; -export const POLYGON_CHAIN_ID = '0x89'; -export const AVALANCHE_CHAIN_ID = '0xa86a'; -export const FANTOM_CHAIN_ID = '0xfa'; -export const CELO_CHAIN_ID = '0xa4ec'; -export const ARBITRUM_CHAIN_ID = '0xa4b1'; -export const HARMONY_CHAIN_ID = '0x63564c40'; -export const PALM_CHAIN_ID = '0x2a15c308d'; - -/** - * The largest possible chain ID we can handle. - * Explanation: https://gist.github.com/rekmarks/a47bd5f2525936c4b8eee31a16345553 - */ -export const MAX_SAFE_CHAIN_ID = 4503599627370476; - -export const ROPSTEN_DISPLAY_NAME = 'Ropsten'; -export const RINKEBY_DISPLAY_NAME = 'Rinkeby'; -export const KOVAN_DISPLAY_NAME = 'Kovan'; -export const MAINNET_DISPLAY_NAME = 'Ethereum Mainnet'; -export const GOERLI_DISPLAY_NAME = 'Goerli'; -export const SEPOLIA_DISPLAY_NAME = 'Sepolia'; -export const LOCALHOST_DISPLAY_NAME = 'Localhost 8545'; -export const BSC_DISPLAY_NAME = 'Binance Smart Chain'; -export const POLYGON_DISPLAY_NAME = 'Polygon'; -export const AVALANCHE_DISPLAY_NAME = 'Avalanche Network C-Chain'; -export const ARBITRUM_DISPLAY_NAME = 'Arbitrum One'; -export const BNB_DISPLAY_NAME = - 'BNB Smart Chain (previously Binance Smart Chain Mainnet)'; -export const OPTIMISM_DISPLAY_NAME = 'Optimism'; -export const FANTOM_DISPLAY_NAME = 'Fantom Opera'; -export const HARMONY_DISPLAY_NAME = 'Harmony Mainnet Shard 0'; -export const PALM_DISPLAY_NAME = 'Palm'; - -export const infuraProjectId = process.env.INFURA_PROJECT_ID; -export const getRpcUrl = ({ network, excludeProjectId = false }) => - `https://${network}.infura.io/v3/${excludeProjectId ? '' : infuraProjectId}`; - -export const ROPSTEN_RPC_URL = getRpcUrl({ network: ROPSTEN }); -export const RINKEBY_RPC_URL = getRpcUrl({ network: RINKEBY }); -export const KOVAN_RPC_URL = getRpcUrl({ network: KOVAN }); -export const MAINNET_RPC_URL = getRpcUrl({ network: MAINNET }); -export const GOERLI_RPC_URL = getRpcUrl({ network: GOERLI }); -export const SEPOLIA_RPC_URL = getRpcUrl({ network: SEPOLIA }); -export const LOCALHOST_RPC_URL = 'http://localhost:8545'; - -export const ETH_SYMBOL = 'ETH'; -export const WETH_SYMBOL = 'WETH'; -export const TEST_ETH_SYMBOL = 'TESTETH'; -export const BNB_SYMBOL = 'BNB'; -export const MATIC_SYMBOL = 'MATIC'; -export const AVALANCHE_SYMBOL = 'AVAX'; -export const FANTOM_SYMBOL = 'FTM'; -export const CELO_SYMBOL = 'CELO'; -export const ARBITRUM_SYMBOL = 'AETH'; -export const HARMONY_SYMBOL = 'ONE'; -export const PALM_SYMBOL = 'PALM'; - -export const ETH_TOKEN_IMAGE_URL = './images/eth_logo.svg'; -export const TEST_ETH_TOKEN_IMAGE_URL = './images/black-eth-logo.svg'; -export const BNB_TOKEN_IMAGE_URL = './images/bnb.png'; -export const MATIC_TOKEN_IMAGE_URL = './images/matic-token.png'; -export const AVAX_TOKEN_IMAGE_URL = './images/avax-token.png'; -export const AETH_TOKEN_IMAGE_URL = './images/arbitrum.svg'; -export const FTM_TOKEN_IMAGE_URL = './images/fantom-opera.svg'; -export const HARMONY_ONE_TOKEN_IMAGE_URL = './images/harmony-one.svg'; -export const OPTIMISM_TOKEN_IMAGE_URL = './images/optimism.svg'; -export const PALM_TOKEN_IMAGE_URL = './images/palm.svg'; - -export const INFURA_PROVIDER_TYPES = [ - ROPSTEN, - RINKEBY, - KOVAN, - MAINNET, - GOERLI, - SEPOLIA, -]; - -export const TEST_CHAINS = [ - ROPSTEN_CHAIN_ID, - RINKEBY_CHAIN_ID, - GOERLI_CHAIN_ID, - KOVAN_CHAIN_ID, - SEPOLIA_RPC_URL, - LOCALHOST_CHAIN_ID, - SEPOLIA_CHAIN_ID, -]; - -export const TEST_NETWORK_TICKER_MAP = { - [ROPSTEN]: `${capitalize(ROPSTEN)}${ETH_SYMBOL}`, - [RINKEBY]: `${capitalize(RINKEBY)}${ETH_SYMBOL}`, - [KOVAN]: `${capitalize(KOVAN)}${ETH_SYMBOL}`, - [GOERLI]: `${capitalize(GOERLI)}${ETH_SYMBOL}`, - [SEPOLIA]: `${capitalize(SEPOLIA)}${ETH_SYMBOL}`, -}; - -/** - * Map of all build-in Infura networks to their network, ticker and chain IDs. - */ -export const NETWORK_TYPE_TO_ID_MAP = { - [ROPSTEN]: { - networkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, - ticker: TEST_NETWORK_TICKER_MAP[ROPSTEN], - }, - [RINKEBY]: { - networkId: RINKEBY_NETWORK_ID, - chainId: RINKEBY_CHAIN_ID, - ticker: TEST_NETWORK_TICKER_MAP[RINKEBY], - }, - [KOVAN]: { - networkId: KOVAN_NETWORK_ID, - chainId: KOVAN_CHAIN_ID, - ticker: TEST_NETWORK_TICKER_MAP[KOVAN], - }, - [GOERLI]: { - networkId: GOERLI_NETWORK_ID, - chainId: GOERLI_CHAIN_ID, - ticker: TEST_NETWORK_TICKER_MAP[GOERLI], - }, - [SEPOLIA]: { - networkId: SEPOLIA_NETWORK_ID, - chainId: SEPOLIA_CHAIN_ID, - ticker: TEST_NETWORK_TICKER_MAP[SEPOLIA], - }, - [MAINNET]: { - networkId: MAINNET_NETWORK_ID, - chainId: MAINNET_CHAIN_ID, - }, - [LOCALHOST]: { - networkId: LOCALHOST_NETWORK_ID, - chainId: LOCALHOST_CHAIN_ID, - }, -}; - -export const NETWORK_TO_NAME_MAP = { - [ROPSTEN]: ROPSTEN_DISPLAY_NAME, - [RINKEBY]: RINKEBY_DISPLAY_NAME, - [KOVAN]: KOVAN_DISPLAY_NAME, - [MAINNET]: MAINNET_DISPLAY_NAME, - [GOERLI]: GOERLI_DISPLAY_NAME, - [SEPOLIA]: SEPOLIA_DISPLAY_NAME, - [LOCALHOST]: LOCALHOST_DISPLAY_NAME, - - [ROPSTEN_NETWORK_ID]: ROPSTEN_DISPLAY_NAME, - [RINKEBY_NETWORK_ID]: RINKEBY_DISPLAY_NAME, - [KOVAN_NETWORK_ID]: KOVAN_DISPLAY_NAME, - [GOERLI_NETWORK_ID]: GOERLI_DISPLAY_NAME, - [SEPOLIA_NETWORK_ID]: SEPOLIA_DISPLAY_NAME, - [MAINNET_NETWORK_ID]: MAINNET_DISPLAY_NAME, - [LOCALHOST_NETWORK_ID]: LOCALHOST_DISPLAY_NAME, - - [ROPSTEN_CHAIN_ID]: ROPSTEN_DISPLAY_NAME, - [RINKEBY_CHAIN_ID]: RINKEBY_DISPLAY_NAME, - [KOVAN_CHAIN_ID]: KOVAN_DISPLAY_NAME, - [GOERLI_CHAIN_ID]: GOERLI_DISPLAY_NAME, - [SEPOLIA_CHAIN_ID]: SEPOLIA_DISPLAY_NAME, - [MAINNET_CHAIN_ID]: MAINNET_DISPLAY_NAME, - [LOCALHOST_CHAIN_ID]: LOCALHOST_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_RPC_URL_MAP = { - [ROPSTEN_CHAIN_ID]: ROPSTEN_RPC_URL, - [RINKEBY_CHAIN_ID]: RINKEBY_RPC_URL, - [KOVAN_CHAIN_ID]: KOVAN_RPC_URL, - [GOERLI_CHAIN_ID]: GOERLI_RPC_URL, - [SEPOLIA_CHAIN_ID]: SEPOLIA_RPC_URL, - [MAINNET_CHAIN_ID]: MAINNET_RPC_URL, - [LOCALHOST_CHAIN_ID]: LOCALHOST_RPC_URL, -}; - -export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { - [MAINNET_CHAIN_ID]: ETH_TOKEN_IMAGE_URL, - [AVALANCHE_CHAIN_ID]: AVAX_TOKEN_IMAGE_URL, - [BSC_CHAIN_ID]: BNB_TOKEN_IMAGE_URL, - [POLYGON_CHAIN_ID]: MATIC_TOKEN_IMAGE_URL, - [ARBITRUM_CHAIN_ID]: AETH_TOKEN_IMAGE_URL, - [BSC_CHAIN_ID]: BNB_TOKEN_IMAGE_URL, - [FANTOM_CHAIN_ID]: FTM_TOKEN_IMAGE_URL, - [HARMONY_CHAIN_ID]: HARMONY_ONE_TOKEN_IMAGE_URL, - [OPTIMISM_CHAIN_ID]: OPTIMISM_TOKEN_IMAGE_URL, - [PALM_CHAIN_ID]: PALM_TOKEN_IMAGE_URL, -}; - -export const NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP = { - [ROPSTEN_NETWORK_ID]: ROPSTEN, - [RINKEBY_NETWORK_ID]: RINKEBY, - [GOERLI_NETWORK_ID]: GOERLI, - [SEPOLIA_NETWORK_ID]: SEPOLIA, - [MAINNET_NETWORK_ID]: HOMESTEAD, -}; - -export const CHAIN_ID_TO_NETWORK_ID_MAP = Object.values( - NETWORK_TYPE_TO_ID_MAP, -).reduce((chainIdToNetworkIdMap, { chainId, networkId }) => { - chainIdToNetworkIdMap[chainId] = networkId; - return chainIdToNetworkIdMap; -}, {}); - -export const NATIVE_CURRENCY_TOKEN_IMAGE_MAP = { - [ETH_SYMBOL]: ETH_TOKEN_IMAGE_URL, - [TEST_ETH_SYMBOL]: TEST_ETH_TOKEN_IMAGE_URL, - [BNB_SYMBOL]: BNB_TOKEN_IMAGE_URL, - [MATIC_SYMBOL]: MATIC_TOKEN_IMAGE_URL, - [AVALANCHE_SYMBOL]: AVAX_TOKEN_IMAGE_URL, -}; - -export const INFURA_BLOCKED_KEY = 'countryBlocked'; - -/** - * Hardforks are points in the chain where logic is changed significantly - * enough where there is a fork and the new fork becomes the active chain. - * These constants are presented in chronological order starting with BERLIN - * because when we first needed to track the hardfork we had launched support - * for EIP-2718 (where transactions can have types and different shapes) and - * EIP-2930 (optional access lists), which were included in BERLIN. - * - * BERLIN - forked at block number 12,244,000, included typed transactions and - * optional access lists - * LONDON - future, upcoming fork that introduces the baseFeePerGas, an amount - * of the ETH transaction fees that will be burned instead of given to the - * miner. This change necessitated the third type of transaction envelope to - * specify maxFeePerGas and maxPriorityFeePerGas moving the fee bidding system - * to a second price auction model. - */ -export const HARDFORKS = { - BERLIN: 'berlin', - LONDON: 'london', -}; - -export const CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP = { - [OPTIMISM_CHAIN_ID]: 1, - [OPTIMISM_TESTNET_CHAIN_ID]: 1, -}; - -/** - * Ethereum JSON-RPC methods that are known to exist but that we intentionally - * do not support. - */ -export const UNSUPPORTED_RPC_METHODS = new Set([ - // This is implemented later in our middleware stack – specifically, in - // eth-json-rpc-middleware – but our UI does not support it. - 'eth_signTransaction', -]); - -export const IPFS_DEFAULT_GATEWAY_URL = 'dweb.link'; - -// The first item in transakCurrencies must be the -// default crypto currency for the network -const BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME = 'ethereum'; - -export const BUYABLE_CHAINS_MAP = { - [MAINNET_CHAIN_ID]: { - nativeCurrency: ETH_SYMBOL, - network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, - transakCurrencies: [ETH_SYMBOL, 'USDT', 'USDC', 'DAI'], - moonPay: { - defaultCurrencyCode: 'eth', - showOnlyCurrencies: 'eth,usdt,usdc,dai', - }, - wyre: { - srn: 'ethereum', - currencyCode: ETH_SYMBOL, - }, - coinbasePayCurrencies: [ETH_SYMBOL, 'USDC', 'DAI'], - }, - [ROPSTEN_CHAIN_ID]: { - nativeCurrency: TEST_NETWORK_TICKER_MAP[ROPSTEN], - network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, - }, - [RINKEBY_CHAIN_ID]: { - nativeCurrency: TEST_NETWORK_TICKER_MAP[RINKEBY], - network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, - }, - [GOERLI_CHAIN_ID]: { - nativeCurrency: TEST_NETWORK_TICKER_MAP[GOERLI], - network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, - }, - [SEPOLIA_CHAIN_ID]: { - nativeCurrency: TEST_NETWORK_TICKER_MAP[SEPOLIA], - network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, - }, - [KOVAN_CHAIN_ID]: { - nativeCurrency: TEST_NETWORK_TICKER_MAP[KOVAN], - network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, - }, - [BSC_CHAIN_ID]: { - nativeCurrency: BNB_SYMBOL, - network: 'bsc', - transakCurrencies: [BNB_SYMBOL, 'BUSD'], - moonPay: { - defaultCurrencyCode: 'bnb_bsc', - showOnlyCurrencies: 'bnb_bsc,busd_bsc', - }, - }, - [POLYGON_CHAIN_ID]: { - nativeCurrency: MATIC_SYMBOL, - network: 'polygon', - transakCurrencies: [MATIC_SYMBOL, 'USDT', 'USDC', 'DAI'], - moonPay: { - defaultCurrencyCode: 'matic_polygon', - showOnlyCurrencies: 'matic_polygon,usdc_polygon', - }, - wyre: { - srn: 'matic', - currencyCode: MATIC_SYMBOL, - }, - }, - [AVALANCHE_CHAIN_ID]: { - nativeCurrency: AVALANCHE_SYMBOL, - network: 'avaxcchain', - transakCurrencies: [AVALANCHE_SYMBOL], - moonPay: { - defaultCurrencyCode: 'avax_cchain', - showOnlyCurrencies: 'avax_cchain', - }, - wyre: { - srn: 'avalanche', - currencyCode: AVALANCHE_SYMBOL, - }, - coinbasePayCurrencies: [AVALANCHE_SYMBOL], - }, - [FANTOM_CHAIN_ID]: { - nativeCurrency: FANTOM_SYMBOL, - network: 'fantom', - transakCurrencies: [FANTOM_SYMBOL], - }, - [CELO_CHAIN_ID]: { - nativeCurrency: CELO_SYMBOL, - network: 'celo', - transakCurrencies: [CELO_SYMBOL], - moonPay: { - defaultCurrencyCode: 'celo', - showOnlyCurrencies: 'celo', - }, - }, -}; - -export const FEATURED_RPCS = [ - { - chainId: ARBITRUM_CHAIN_ID, - nickname: ARBITRUM_DISPLAY_NAME, - rpcUrl: `https://arbitrum-mainnet.infura.io/v3/${infuraProjectId}`, - ticker: ARBITRUM_SYMBOL, - rpcPrefs: { - blockExplorerUrl: 'https://explorer.arbitrum.io', - imageUrl: AETH_TOKEN_IMAGE_URL, - }, - }, - { - chainId: AVALANCHE_CHAIN_ID, - nickname: AVALANCHE_DISPLAY_NAME, - rpcUrl: `https://avalanche-mainnet.infura.io/v3/${infuraProjectId}`, - ticker: AVALANCHE_SYMBOL, - rpcPrefs: { - blockExplorerUrl: 'https://snowtrace.io/', - imageUrl: AVAX_TOKEN_IMAGE_URL, - }, - }, - { - chainId: BSC_CHAIN_ID, - nickname: BNB_DISPLAY_NAME, - rpcUrl: 'https://bsc-dataseed.binance.org/', - ticker: BNB_SYMBOL, - rpcPrefs: { - blockExplorerUrl: 'https://bscscan.com/', - imageUrl: BNB_TOKEN_IMAGE_URL, - }, - }, - { - chainId: FANTOM_CHAIN_ID, - nickname: FANTOM_DISPLAY_NAME, - rpcUrl: 'https://rpc.ftm.tools/', - ticker: FANTOM_SYMBOL, - rpcPrefs: { - blockExplorerUrl: 'https://ftmscan.com/', - imageUrl: FTM_TOKEN_IMAGE_URL, - }, - }, - { - chainId: HARMONY_CHAIN_ID, - nickname: HARMONY_DISPLAY_NAME, - rpcUrl: 'https://api.harmony.one/', - ticker: HARMONY_SYMBOL, - rpcPrefs: { - blockExplorerUrl: 'https://explorer.harmony.one/', - imageUrl: HARMONY_ONE_TOKEN_IMAGE_URL, - }, - }, - { - chainId: OPTIMISM_CHAIN_ID, - nickname: OPTIMISM_DISPLAY_NAME, - rpcUrl: `https://optimism-mainnet.infura.io/v3/${infuraProjectId}`, - ticker: ETH_SYMBOL, - rpcPrefs: { - blockExplorerUrl: 'https://optimistic.etherscan.io/', - imageUrl: OPTIMISM_TOKEN_IMAGE_URL, - }, - }, - { - chainId: PALM_CHAIN_ID, - nickname: PALM_DISPLAY_NAME, - rpcUrl: `https://palm-mainnet.infura.io/v3/${infuraProjectId}`, - ticker: PALM_SYMBOL, - rpcPrefs: { - blockExplorerUrl: 'https://explorer.palm.io/', - imageUrl: PALM_TOKEN_IMAGE_URL, - }, - }, - { - chainId: POLYGON_CHAIN_ID, - nickname: `${POLYGON_DISPLAY_NAME} ${capitalize(MAINNET)}`, - rpcUrl: `https://polygon-mainnet.infura.io/v3/${infuraProjectId}`, - ticker: MATIC_SYMBOL, - rpcPrefs: { - blockExplorerUrl: 'https://polygonscan.com/', - imageUrl: MATIC_TOKEN_IMAGE_URL, - }, - }, -]; diff --git a/shared/constants/network.ts b/shared/constants/network.ts new file mode 100644 index 000000000..45a7d7d03 --- /dev/null +++ b/shared/constants/network.ts @@ -0,0 +1,957 @@ +import { capitalize } from 'lodash'; +/** + * A type representing any valid value for 'type' for setProviderType and other + * methods that add or manipulate networks in MetaMask state. + */ +export type NetworkType = typeof NETWORK_TYPES[keyof typeof NETWORK_TYPES]; + +/** + * A union type of all possible hard-coded chain ids. This type is not + * exhaustive and cannot be used for typing chainId in areas where the user or + * dapp may specify any chainId. + */ +export type ChainId = typeof CHAIN_IDS[keyof typeof CHAIN_IDS]; + +/** + * A type that is a union type of all possible hardcoded currency symbols. + * This type is non-exhaustive, and cannot be used for areas where the user + * or dapp may supply their own symbol. + */ +type CurrencySymbol = typeof CURRENCY_SYMBOLS[keyof typeof CURRENCY_SYMBOLS]; +/** + * A type that is a union type for the supported symbols on different onramp providers. + */ +type SupportedCurrencySymbol = + typeof SUPPORTED_CURRENCY_SYMBOLS[keyof typeof SUPPORTED_CURRENCY_SYMBOLS]; +/** + * For certain specific situations we need the above type, but with all symbols + * in lowercase format. + */ +type LowercaseCurrencySymbol = Lowercase; +/** + * Test networks have special symbols that combine the network name and 'ETH' + * so that they are distinct from mainnet and other networks that use 'ETH'. + */ +export type TestNetworkCurrencySymbol = + typeof TEST_NETWORK_TICKER_MAP[keyof typeof TEST_NETWORK_TICKER_MAP]; + +/** + * MoonPay is a fiat onramp provider, and there are some special strings that + * inform the MoonPay API which network the user is attempting to onramp into. + * This type reflects those possible values. + */ +type MoonPayNetworkAbbreviation = 'bsc' | 'cchain' | 'polygon'; + +/** + * MoonPay requires some settings that are configured per network that it is + * enabled on. This type describes those settings. + */ +type MoonPayChainSettings = { + /** + * What should the default onramp currency be, for example 'eth' on 'mainnet' + * This type matches a single LowercaseCurrencySymbol or a + * LowercaseCurrencySymbol and a MoonPayNetworkAbbreviation joined by a '_'. + */ + defaultCurrencyCode: + | LowercaseCurrencySymbol + | `${LowercaseCurrencySymbol}_${MoonPayNetworkAbbreviation}`; + /** + * We must also configure all possible onramp currencies we wish to support. + * This type matches 1 to 3 LowercaseCurrencySymbols, joined by ','. It also + * matches 1 or 2 LowercaseCurrencySymbols with a + * MoonPayNetworkAbbreviation joined by a '_', and concatenated with ','. + */ + showOnlyCurrencies: + | `${LowercaseCurrencySymbol}` + | `${LowercaseCurrencySymbol},${LowercaseCurrencySymbol}` + | `${LowercaseCurrencySymbol},${LowercaseCurrencySymbol},${LowercaseCurrencySymbol}` + | `${LowercaseCurrencySymbol},${LowercaseCurrencySymbol},${LowercaseCurrencySymbol},${LowercaseCurrencySymbol}` + | `${LowercaseCurrencySymbol}_${MoonPayNetworkAbbreviation}` + | `${LowercaseCurrencySymbol}_${MoonPayNetworkAbbreviation},${LowercaseCurrencySymbol}_${MoonPayNetworkAbbreviation}`; +}; + +/** + * An object containing preferences for an RPC definition + */ +type RPCPreferences = { + /** + * A URL for the block explorer for the RPC's network + */ + blockExplorerUrl: `https://${string}`; + /** + * A image reflecting the asset symbol for the network + */ + imageUrl: string; +}; + +/** + * An object that describes a network to be used inside of MetaMask + */ +type RPCDefinition = { + /** + * The hex encoded ChainId for the network + */ + chainId: ChainId; + /** + * The nickname for the network + */ + nickname: string; + /** + * The URL for the client to send network requests to + */ + rpcUrl: `https://${string}`; + /** + * The Currency Symbol for the network + */ + ticker: string; + /** + * Additional preferences for the network, such as blockExplorerUrl + */ + rpcPrefs: RPCPreferences; +}; + +/** + * Wyre is a fiat onramp provider. We must provide some settings for networks + * that support Wyre. + */ +type WyreChainSettings = { + /** + * The network name + */ + srn: string; + /** + * The native currency for the network + */ + currencyCode: CurrencySymbol; +}; + +/** + * For each chain that we support fiat onramps for, we provide a set of + * configuration options that help for initializing the connectiong to the + * onramp providers. + */ +type BuyableChainSettings = { + /** + * The native currency for the given chain + */ + nativeCurrency: CurrencySymbol | TestNetworkCurrencySymbol; + /** + * The network name or identifier + */ + network: string; + /** + * The list of supported currencies for the Transak onramp provider + */ + transakCurrencies?: SupportedCurrencySymbol[]; + /** + * A configuration object for the MoonPay onramp provider + */ + moonPay?: MoonPayChainSettings; + /** + * A configuration object for the Wyre onramp provider + */ + wyre?: WyreChainSettings; + /** + * The list of supported currencies for the CoinbasePay onramp provider + */ + coinbasePayCurrencies?: SupportedCurrencySymbol[]; +}; + +/** + * Throughout the extension we set the current provider by referencing its + * "type", which can be any of the values in the below object. These values + * represent the built-in networks of MetaMask, including test nets, as well + * as "rpc" which is the "type" of a custom network added by the user or via + * wallet_addEthereumChain. + */ +export const NETWORK_TYPES = { + GOERLI: 'goerli', + KOVAN: 'kovan', + LOCALHOST: 'localhost', + MAINNET: 'mainnet', + RINKEBY: 'rinkeby', + ROPSTEN: 'ropsten', + RPC: 'rpc', + SEPOLIA: 'sepolia', +} as const; + +/** + * An object containing shortcut names for any non-builtin network. We need + * this to be able to differentiate between networks that require custom + * sections of code for our various features, such as swaps or token lists. + */ +export const NETWORK_NAMES = { + HOMESTEAD: 'homestead', +}; + +/** + * The Network ID for our builtin networks. This is the decimal equivalent of + * the chain id for the network, but is expresssed as a string. Many moons ago + * the decision was made on the extension team to expressly use chainId with + * hex encoding over network id. Consider that when accessing this object. Note + * for cross product purposes: alignment with mobile on this matter has not + * been fully achieved, thus it is possible for some dependencies to still + * ask for or require network id. + */ +export const NETWORK_IDS = { + MAINNET: '1', + ROPSTEN: '3', + RINKEBY: '4', + GOERLI: '5', + KOVAN: '42', + LOCALHOST: '1337', + SEPOLIA: '11155111', +} as const; + +/** + * An object containing all of the chain ids for networks both built in and + * those that we have added custom code to support our feature set. + */ +export const CHAIN_IDS = { + MAINNET: '0x1', + ROPSTEN: '0x3', + RINKEBY: '0x4', + GOERLI: '0x5', + KOVAN: '0x2a', + LOCALHOST: '0x539', + BSC: '0x38', + OPTIMISM: '0xa', + OPTIMISM_TESTNET: '0x1a4', + POLYGON: '0x89', + AVALANCHE: '0xa86a', + FANTOM: '0xfa', + CELO: '0xa4ec', + ARBITRUM: '0xa4b1', + HARMONY: '0x63564c40', + PALM: '0x2a15c308d', + SEPOLIA: '0xaa36a7', +} as const; + +/** + * The largest possible chain ID we can handle. + * Explanation: https://gist.github.com/rekmarks/a47bd5f2525936c4b8eee31a16345553 + */ +export const MAX_SAFE_CHAIN_ID = 4503599627370476; + +export const ROPSTEN_DISPLAY_NAME = 'Ropsten'; +export const RINKEBY_DISPLAY_NAME = 'Rinkeby'; +export const KOVAN_DISPLAY_NAME = 'Kovan'; +export const MAINNET_DISPLAY_NAME = 'Ethereum Mainnet'; +export const GOERLI_DISPLAY_NAME = 'Goerli'; +export const SEPOLIA_DISPLAY_NAME = 'Sepolia'; +export const LOCALHOST_DISPLAY_NAME = 'Localhost 8545'; +export const BSC_DISPLAY_NAME = 'Binance Smart Chain'; +export const POLYGON_DISPLAY_NAME = 'Polygon'; +export const AVALANCHE_DISPLAY_NAME = 'Avalanche Network C-Chain'; +export const ARBITRUM_DISPLAY_NAME = 'Arbitrum One'; +export const BNB_DISPLAY_NAME = + 'BNB Smart Chain (previously Binance Smart Chain Mainnet)'; +export const OPTIMISM_DISPLAY_NAME = 'Optimism'; +export const FANTOM_DISPLAY_NAME = 'Fantom Opera'; +export const HARMONY_DISPLAY_NAME = 'Harmony Mainnet Shard 0'; +export const PALM_DISPLAY_NAME = 'Palm'; + +export const infuraProjectId = process.env.INFURA_PROJECT_ID; +export const getRpcUrl = ({ + network, + excludeProjectId = false, +}: { + network: NetworkType; + excludeProjectId?: boolean; +}) => + `https://${network}.infura.io/v3/${excludeProjectId ? '' : infuraProjectId}`; + +export const ROPSTEN_RPC_URL = getRpcUrl({ + network: NETWORK_TYPES.ROPSTEN, +}); +export const RINKEBY_RPC_URL = getRpcUrl({ + network: NETWORK_TYPES.RINKEBY, +}); +export const KOVAN_RPC_URL = getRpcUrl({ network: NETWORK_TYPES.KOVAN }); +export const MAINNET_RPC_URL = getRpcUrl({ + network: NETWORK_TYPES.MAINNET, +}); +export const GOERLI_RPC_URL = getRpcUrl({ network: NETWORK_TYPES.GOERLI }); +export const SEPOLIA_RPC_URL = getRpcUrl({ network: NETWORK_TYPES.SEPOLIA }); +export const LOCALHOST_RPC_URL = 'http://localhost:8545'; + +/** + * An object containing the token symbols for various tokens that are either + * native currencies or those that have been special cased by the extension + * for supporting our feature set. + */ +export const CURRENCY_SYMBOLS = { + ARBITRUM: 'ETH', + AVALANCHE: 'AVAX', + BNB: 'BNB', + BUSD: 'BUSD', + CELO: 'CELO', + DAI: 'DAI', + ETH: 'ETH', + FANTOM: 'FTM', + HARMONY: 'ONE', + PALM: 'PALM', + MATIC: 'MATIC', + TEST_ETH: 'TESTETH', + USDC: 'USDC', + USDT: 'USDT', + WETH: 'WETH', +} as const; + +/** + * An object containing the token symbols for various tokens that are supported + * on different on ramp providers. This object is meant for internal consumption, + * hence why it is not exported. + */ +const SUPPORTED_CURRENCY_SYMBOLS = { + ...CURRENCY_SYMBOLS, + '1INCH': '1INCH', + AAVE: 'AAVE', + ABT: 'ABT', + ACH: 'ACH', + AGEUR: 'AGEUR', + AGLD: 'AGLD', + AMP: 'AMP', + ANKR: 'ANKR', + APE: 'APE', + ARPA: 'ARPA', + ASM: 'ASM', + AUCTION: 'AUCTION', + AXS: 'AXS', + BADGER: 'BADGER', + BAL: 'BAL', + BAND: 'BAND', + BAT: 'BAT', + BNT: 'BNT', + BOBA: 'BOBA', + BOND: 'BOND', + BTRST: 'BTRST', + CHAIN: 'CHAIN', + CHZ: 'CHZ', + CLV: 'CLV', + COMP: 'COMP', + COTI: 'COTI', + CRO: 'CRO', + CRV: 'CRV', + CTSI: 'CTSI', + CVC: 'CVC', + DAO: 'DAO', + DDX: 'DDX', + DNT: 'DNT', + ENJ: 'ENJ', + ENS: 'ENS', + EURT: 'EURT', + FARM: 'FARM', + FET: 'FET', + FORTH: 'FORTH', + FX: 'FX', + GNO: 'GNO', + GRT: 'GRT', + GTC: 'GTC', + GTH: 'GTH', + HEX: 'HEX', + IOTX: 'IOTX', + JASMY: 'JASMY', + KEEP: 'KEEP', + KNC: 'KNC', + KRL: 'KRL', + LCX: 'LCX', + LINK: 'LINK', + LPT: 'LPT', + LRC: 'LRC', + MANA: 'MANA', + MASK: 'MASK', + MINDS: 'MINDS', + MIR: 'MIR', + MKR: 'MKR', + MLN: 'MLN', + MTL: 'MTL', + NKN: 'NKN', + NMR: 'NMR', + NU: 'NU', + OGN: 'OGN', + OMG: 'OMG', + OXT: 'OXT', + PAX: 'PAX', + PERP: 'PERP', + PLA: 'PLA', + POLS: 'POLS', + POLY: 'POLY', + QNT: 'QNT', + QUICK: 'QUICK', + RAD: 'RAD', + RAI: 'RAI', + RARI: 'RARI', + REN: 'REN', + REP: 'REP', + REQ: 'REQ', + RLC: 'RLC', + RLY: 'RLY', + SAND: 'SAND', + SHIB: 'SHIB', + SKL: 'SKL', + SNX: 'SNX', + STETH: 'STETH', + STORJ: 'STORJ', + SUKU: 'SUKU', + SUSHI: 'SUSHI', + SWAP: 'SWAP', + SWFTC: 'SWFTC', + TRAC: 'TRAC', + TRB: 'TRB', + TRIBE: 'TRIBE', + TRU: 'TRU', + TXL: 'TXL', + UMA: 'UMA', + UNI: 'UNI', + VRA: 'VRA', + WBTC: 'WBTC', + WCFG: 'WCFG', + XYO: 'XYO', + YFII: 'YFII', + YLD: 'YLD', + ZRX: 'ZRX', +} as const; + +export const ETH_TOKEN_IMAGE_URL = './images/eth_logo.svg'; +export const TEST_ETH_TOKEN_IMAGE_URL = './images/black-eth-logo.svg'; +export const BNB_TOKEN_IMAGE_URL = './images/bnb.png'; +export const MATIC_TOKEN_IMAGE_URL = './images/matic-token.png'; +export const AVAX_TOKEN_IMAGE_URL = './images/avax-token.png'; +export const AETH_TOKEN_IMAGE_URL = './images/arbitrum.svg'; +export const FTM_TOKEN_IMAGE_URL = './images/fantom-opera.svg'; +export const HARMONY_ONE_TOKEN_IMAGE_URL = './images/harmony-one.svg'; +export const OPTIMISM_TOKEN_IMAGE_URL = './images/optimism.svg'; +export const PALM_TOKEN_IMAGE_URL = './images/palm.svg'; + +export const INFURA_PROVIDER_TYPES = [ + NETWORK_TYPES.ROPSTEN, + NETWORK_TYPES.RINKEBY, + NETWORK_TYPES.KOVAN, + NETWORK_TYPES.MAINNET, + NETWORK_TYPES.GOERLI, + NETWORK_TYPES.SEPOLIA, +]; + +export const TEST_CHAINS = [ + CHAIN_IDS.ROPSTEN, + CHAIN_IDS.RINKEBY, + CHAIN_IDS.GOERLI, + CHAIN_IDS.KOVAN, + CHAIN_IDS.SEPOLIA, + CHAIN_IDS.LOCALHOST, +]; + +const typedCapitalize = (k: K): Capitalize => + capitalize(k) as Capitalize; + +export const TEST_NETWORK_TICKER_MAP: { + [K in Exclude< + NetworkType, + 'localhost' | 'mainnet' | 'rpc' + >]: `${Capitalize}${typeof CURRENCY_SYMBOLS.ETH}`; +} = { + [NETWORK_TYPES.ROPSTEN]: `${typedCapitalize(NETWORK_TYPES.ROPSTEN)}${ + CURRENCY_SYMBOLS.ETH + }`, + [NETWORK_TYPES.RINKEBY]: `${typedCapitalize(NETWORK_TYPES.RINKEBY)}${ + CURRENCY_SYMBOLS.ETH + }`, + [NETWORK_TYPES.KOVAN]: `${typedCapitalize(NETWORK_TYPES.KOVAN)}${ + CURRENCY_SYMBOLS.ETH + }`, + [NETWORK_TYPES.GOERLI]: `${typedCapitalize(NETWORK_TYPES.GOERLI)}${ + CURRENCY_SYMBOLS.ETH + }`, + [NETWORK_TYPES.SEPOLIA]: `${typedCapitalize(NETWORK_TYPES.SEPOLIA)}${ + CURRENCY_SYMBOLS.ETH + }`, +}; + +/** + * Map of all build-in Infura networks to their network, ticker and chain IDs. + */ +export const BUILT_IN_NETWORKS = { + [NETWORK_TYPES.ROPSTEN]: { + networkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.ROPSTEN], + }, + [NETWORK_TYPES.RINKEBY]: { + networkId: NETWORK_IDS.RINKEBY, + chainId: CHAIN_IDS.RINKEBY, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.RINKEBY], + }, + [NETWORK_TYPES.KOVAN]: { + networkId: NETWORK_IDS.KOVAN, + chainId: CHAIN_IDS.KOVAN, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.KOVAN], + }, + [NETWORK_TYPES.GOERLI]: { + networkId: NETWORK_IDS.GOERLI, + chainId: CHAIN_IDS.GOERLI, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], + }, + [NETWORK_TYPES.SEPOLIA]: { + networkId: NETWORK_IDS.SEPOLIA, + chainId: CHAIN_IDS.SEPOLIA, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA], + }, + [NETWORK_TYPES.MAINNET]: { + networkId: NETWORK_IDS.MAINNET, + chainId: CHAIN_IDS.MAINNET, + }, + [NETWORK_TYPES.LOCALHOST]: { + networkId: NETWORK_IDS.LOCALHOST, + chainId: CHAIN_IDS.LOCALHOST, + }, +} as const; + +export const NETWORK_TO_NAME_MAP = { + [NETWORK_TYPES.ROPSTEN]: ROPSTEN_DISPLAY_NAME, + [NETWORK_TYPES.RINKEBY]: RINKEBY_DISPLAY_NAME, + [NETWORK_TYPES.KOVAN]: KOVAN_DISPLAY_NAME, + [NETWORK_TYPES.MAINNET]: MAINNET_DISPLAY_NAME, + [NETWORK_TYPES.GOERLI]: GOERLI_DISPLAY_NAME, + [NETWORK_TYPES.SEPOLIA]: SEPOLIA_DISPLAY_NAME, + [NETWORK_TYPES.LOCALHOST]: LOCALHOST_DISPLAY_NAME, + + [NETWORK_IDS.ROPSTEN]: ROPSTEN_DISPLAY_NAME, + [NETWORK_IDS.RINKEBY]: RINKEBY_DISPLAY_NAME, + [NETWORK_IDS.KOVAN]: KOVAN_DISPLAY_NAME, + [NETWORK_IDS.GOERLI]: GOERLI_DISPLAY_NAME, + [NETWORK_IDS.SEPOLIA]: SEPOLIA_DISPLAY_NAME, + [NETWORK_IDS.MAINNET]: MAINNET_DISPLAY_NAME, + [NETWORK_IDS.LOCALHOST]: LOCALHOST_DISPLAY_NAME, + + [CHAIN_IDS.ROPSTEN]: ROPSTEN_DISPLAY_NAME, + [CHAIN_IDS.RINKEBY]: RINKEBY_DISPLAY_NAME, + [CHAIN_IDS.KOVAN]: KOVAN_DISPLAY_NAME, + [CHAIN_IDS.GOERLI]: GOERLI_DISPLAY_NAME, + [CHAIN_IDS.SEPOLIA]: SEPOLIA_DISPLAY_NAME, + [CHAIN_IDS.MAINNET]: MAINNET_DISPLAY_NAME, + [CHAIN_IDS.LOCALHOST]: LOCALHOST_DISPLAY_NAME, +} as const; + +export const CHAIN_ID_TO_TYPE_MAP = { + [CHAIN_IDS.MAINNET]: NETWORK_TYPES.MAINNET, + [CHAIN_IDS.GOERLI]: NETWORK_TYPES.GOERLI, + [CHAIN_IDS.SEPOLIA]: NETWORK_TYPES.SEPOLIA, + [CHAIN_IDS.KOVAN]: NETWORK_TYPES.KOVAN, + [CHAIN_IDS.LOCALHOST]: NETWORK_TYPES.LOCALHOST, + [CHAIN_IDS.RINKEBY]: NETWORK_TYPES.RINKEBY, + [CHAIN_IDS.ROPSTEN]: NETWORK_TYPES.ROPSTEN, +} as const; + +export const CHAIN_ID_TO_RPC_URL_MAP = { + [CHAIN_IDS.ROPSTEN]: ROPSTEN_RPC_URL, + [CHAIN_IDS.RINKEBY]: RINKEBY_RPC_URL, + [CHAIN_IDS.KOVAN]: KOVAN_RPC_URL, + [CHAIN_IDS.GOERLI]: GOERLI_RPC_URL, + [CHAIN_IDS.SEPOLIA]: SEPOLIA_RPC_URL, + [CHAIN_IDS.MAINNET]: MAINNET_RPC_URL, + [CHAIN_IDS.LOCALHOST]: LOCALHOST_RPC_URL, +} as const; + +export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { + [CHAIN_IDS.MAINNET]: ETH_TOKEN_IMAGE_URL, + [CHAIN_IDS.AVALANCHE]: AVAX_TOKEN_IMAGE_URL, + [CHAIN_IDS.BSC]: BNB_TOKEN_IMAGE_URL, + [CHAIN_IDS.POLYGON]: MATIC_TOKEN_IMAGE_URL, + [CHAIN_IDS.ARBITRUM]: AETH_TOKEN_IMAGE_URL, + [CHAIN_IDS.FANTOM]: FTM_TOKEN_IMAGE_URL, + [CHAIN_IDS.HARMONY]: HARMONY_ONE_TOKEN_IMAGE_URL, + [CHAIN_IDS.OPTIMISM]: OPTIMISM_TOKEN_IMAGE_URL, + [CHAIN_IDS.PALM]: PALM_TOKEN_IMAGE_URL, +} as const; + +export const NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP = { + [NETWORK_IDS.ROPSTEN]: NETWORK_TYPES.ROPSTEN, + [NETWORK_IDS.RINKEBY]: NETWORK_TYPES.RINKEBY, + [NETWORK_IDS.GOERLI]: NETWORK_TYPES.GOERLI, + [NETWORK_IDS.SEPOLIA]: NETWORK_TYPES.SEPOLIA, + [NETWORK_IDS.MAINNET]: NETWORK_NAMES.HOMESTEAD, +} as const; + +export const CHAIN_ID_TO_NETWORK_ID_MAP = { + [CHAIN_IDS.MAINNET]: NETWORK_IDS.MAINNET, + [CHAIN_IDS.GOERLI]: NETWORK_IDS.GOERLI, + [CHAIN_IDS.SEPOLIA]: NETWORK_IDS.SEPOLIA, + [CHAIN_IDS.KOVAN]: NETWORK_IDS.KOVAN, + [CHAIN_IDS.LOCALHOST]: NETWORK_IDS.LOCALHOST, + [CHAIN_IDS.RINKEBY]: NETWORK_IDS.RINKEBY, + [CHAIN_IDS.ROPSTEN]: NETWORK_IDS.ROPSTEN, +} as const; + +export const NATIVE_CURRENCY_TOKEN_IMAGE_MAP = { + [CURRENCY_SYMBOLS.ETH]: ETH_TOKEN_IMAGE_URL, + [CURRENCY_SYMBOLS.TEST_ETH]: TEST_ETH_TOKEN_IMAGE_URL, + [CURRENCY_SYMBOLS.BNB]: BNB_TOKEN_IMAGE_URL, + [CURRENCY_SYMBOLS.MATIC]: MATIC_TOKEN_IMAGE_URL, + [CURRENCY_SYMBOLS.AVALANCHE]: AVAX_TOKEN_IMAGE_URL, +} as const; + +export const INFURA_BLOCKED_KEY = 'countryBlocked'; + +/** + * Hardforks are points in the chain where logic is changed significantly + * enough where there is a fork and the new fork becomes the active chain. + * These constants are presented in chronological order starting with BERLIN + * because when we first needed to track the hardfork we had launched support + * for EIP-2718 (where transactions can have types and different shapes) and + * EIP-2930 (optional access lists), which were included in BERLIN. + * + * BERLIN - forked at block number 12,244,000, included typed transactions and + * optional access lists + * LONDON - future, upcoming fork that introduces the baseFeePerGas, an amount + * of the ETH transaction fees that will be burned instead of given to the + * miner. This change necessitated the third type of transaction envelope to + * specify maxFeePerGas and maxPriorityFeePerGas moving the fee bidding system + * to a second price auction model. + */ +export const HARDFORKS = { + BERLIN: 'berlin', + LONDON: 'london', +} as const; + +export const CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP = { + [CHAIN_IDS.OPTIMISM]: 1, + [CHAIN_IDS.OPTIMISM_TESTNET]: 1, +}; + +/** + * Ethereum JSON-RPC methods that are known to exist but that we intentionally + * do not support. + */ +export const UNSUPPORTED_RPC_METHODS = new Set([ + // This is implemented later in our middleware stack – specifically, in + // eth-json-rpc-middleware – but our UI does not support it. + 'eth_signTransaction' as const, +]); + +export const IPFS_DEFAULT_GATEWAY_URL = 'dweb.link'; + +// The first item in transakCurrencies must be the +// default crypto currency for the network +const BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME = 'ethereum'; + +export const BUYABLE_CHAINS_MAP: { + [K in Exclude< + ChainId, + | typeof CHAIN_IDS.LOCALHOST + | typeof CHAIN_IDS.PALM + | typeof CHAIN_IDS.HARMONY + | typeof CHAIN_IDS.OPTIMISM + | typeof CHAIN_IDS.OPTIMISM_TESTNET + | typeof CHAIN_IDS.ARBITRUM + >]: BuyableChainSettings; +} = { + [CHAIN_IDS.MAINNET]: { + nativeCurrency: CURRENCY_SYMBOLS.ETH, + network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, + transakCurrencies: [ + SUPPORTED_CURRENCY_SYMBOLS.ETH, + SUPPORTED_CURRENCY_SYMBOLS['1INCH'], + SUPPORTED_CURRENCY_SYMBOLS.AAVE, + SUPPORTED_CURRENCY_SYMBOLS.AGEUR, + SUPPORTED_CURRENCY_SYMBOLS.BUSD, + SUPPORTED_CURRENCY_SYMBOLS.CHAIN, + SUPPORTED_CURRENCY_SYMBOLS.CLV, + SUPPORTED_CURRENCY_SYMBOLS.COMP, + SUPPORTED_CURRENCY_SYMBOLS.CTSI, + SUPPORTED_CURRENCY_SYMBOLS.DAI, + SUPPORTED_CURRENCY_SYMBOLS.DAO, + SUPPORTED_CURRENCY_SYMBOLS.ENJ, + SUPPORTED_CURRENCY_SYMBOLS.EURT, + SUPPORTED_CURRENCY_SYMBOLS.GTH, + SUPPORTED_CURRENCY_SYMBOLS.HEX, + SUPPORTED_CURRENCY_SYMBOLS.LINK, + SUPPORTED_CURRENCY_SYMBOLS.MANA, + SUPPORTED_CURRENCY_SYMBOLS.MASK, + SUPPORTED_CURRENCY_SYMBOLS.MINDS, + SUPPORTED_CURRENCY_SYMBOLS.MKR, + SUPPORTED_CURRENCY_SYMBOLS.PLA, + SUPPORTED_CURRENCY_SYMBOLS.POLS, + SUPPORTED_CURRENCY_SYMBOLS.SAND, + SUPPORTED_CURRENCY_SYMBOLS.STETH, + SUPPORTED_CURRENCY_SYMBOLS.SUSHI, + SUPPORTED_CURRENCY_SYMBOLS.SWAP, + SUPPORTED_CURRENCY_SYMBOLS.TXL, + SUPPORTED_CURRENCY_SYMBOLS.UNI, + SUPPORTED_CURRENCY_SYMBOLS.USDC, + SUPPORTED_CURRENCY_SYMBOLS.USDT, + SUPPORTED_CURRENCY_SYMBOLS.VRA, + SUPPORTED_CURRENCY_SYMBOLS.WBTC, + SUPPORTED_CURRENCY_SYMBOLS.YLD, + ], + moonPay: { + defaultCurrencyCode: 'eth', + showOnlyCurrencies: 'eth,usdt,usdc,dai', + }, + wyre: { + srn: 'ethereum', + currencyCode: CURRENCY_SYMBOLS.ETH, + }, + coinbasePayCurrencies: [ + SUPPORTED_CURRENCY_SYMBOLS.ETH, + SUPPORTED_CURRENCY_SYMBOLS['1INCH'], + SUPPORTED_CURRENCY_SYMBOLS.AAVE, + SUPPORTED_CURRENCY_SYMBOLS.ABT, + SUPPORTED_CURRENCY_SYMBOLS.ACH, + SUPPORTED_CURRENCY_SYMBOLS.AGLD, + SUPPORTED_CURRENCY_SYMBOLS.AMP, + SUPPORTED_CURRENCY_SYMBOLS.ANKR, + SUPPORTED_CURRENCY_SYMBOLS.APE, + SUPPORTED_CURRENCY_SYMBOLS.ARPA, + SUPPORTED_CURRENCY_SYMBOLS.ASM, + SUPPORTED_CURRENCY_SYMBOLS.AUCTION, + SUPPORTED_CURRENCY_SYMBOLS.AXS, + SUPPORTED_CURRENCY_SYMBOLS.BADGER, + SUPPORTED_CURRENCY_SYMBOLS.BAL, + SUPPORTED_CURRENCY_SYMBOLS.BAND, + SUPPORTED_CURRENCY_SYMBOLS.BAT, + SUPPORTED_CURRENCY_SYMBOLS.BNT, + SUPPORTED_CURRENCY_SYMBOLS.BOBA, + SUPPORTED_CURRENCY_SYMBOLS.BOND, + SUPPORTED_CURRENCY_SYMBOLS.BTRST, + SUPPORTED_CURRENCY_SYMBOLS.CHZ, + SUPPORTED_CURRENCY_SYMBOLS.CLV, + SUPPORTED_CURRENCY_SYMBOLS.COMP, + SUPPORTED_CURRENCY_SYMBOLS.COTI, + SUPPORTED_CURRENCY_SYMBOLS.CRO, + SUPPORTED_CURRENCY_SYMBOLS.CRV, + SUPPORTED_CURRENCY_SYMBOLS.CTSI, + SUPPORTED_CURRENCY_SYMBOLS.CVC, + SUPPORTED_CURRENCY_SYMBOLS.DAI, + SUPPORTED_CURRENCY_SYMBOLS.DDX, + SUPPORTED_CURRENCY_SYMBOLS.DNT, + SUPPORTED_CURRENCY_SYMBOLS.ENJ, + SUPPORTED_CURRENCY_SYMBOLS.ENS, + SUPPORTED_CURRENCY_SYMBOLS.FARM, + SUPPORTED_CURRENCY_SYMBOLS.FET, + SUPPORTED_CURRENCY_SYMBOLS.FORTH, + SUPPORTED_CURRENCY_SYMBOLS.FX, + SUPPORTED_CURRENCY_SYMBOLS.GNO, + SUPPORTED_CURRENCY_SYMBOLS.GRT, + SUPPORTED_CURRENCY_SYMBOLS.GTC, + SUPPORTED_CURRENCY_SYMBOLS.IOTX, + SUPPORTED_CURRENCY_SYMBOLS.JASMY, + SUPPORTED_CURRENCY_SYMBOLS.KEEP, + SUPPORTED_CURRENCY_SYMBOLS.KNC, + SUPPORTED_CURRENCY_SYMBOLS.KRL, + SUPPORTED_CURRENCY_SYMBOLS.LCX, + SUPPORTED_CURRENCY_SYMBOLS.LINK, + SUPPORTED_CURRENCY_SYMBOLS.LPT, + SUPPORTED_CURRENCY_SYMBOLS.LRC, + SUPPORTED_CURRENCY_SYMBOLS.MANA, + SUPPORTED_CURRENCY_SYMBOLS.MASK, + SUPPORTED_CURRENCY_SYMBOLS.MATIC, + SUPPORTED_CURRENCY_SYMBOLS.MIR, + SUPPORTED_CURRENCY_SYMBOLS.MKR, + SUPPORTED_CURRENCY_SYMBOLS.MLN, + SUPPORTED_CURRENCY_SYMBOLS.MTL, + SUPPORTED_CURRENCY_SYMBOLS.NKN, + SUPPORTED_CURRENCY_SYMBOLS.NMR, + SUPPORTED_CURRENCY_SYMBOLS.NU, + SUPPORTED_CURRENCY_SYMBOLS.OGN, + SUPPORTED_CURRENCY_SYMBOLS.OMG, + SUPPORTED_CURRENCY_SYMBOLS.OXT, + SUPPORTED_CURRENCY_SYMBOLS.PAX, + SUPPORTED_CURRENCY_SYMBOLS.PERP, + SUPPORTED_CURRENCY_SYMBOLS.PLA, + SUPPORTED_CURRENCY_SYMBOLS.POLY, + SUPPORTED_CURRENCY_SYMBOLS.QNT, + SUPPORTED_CURRENCY_SYMBOLS.QUICK, + SUPPORTED_CURRENCY_SYMBOLS.RAD, + SUPPORTED_CURRENCY_SYMBOLS.RAI, + SUPPORTED_CURRENCY_SYMBOLS.RARI, + SUPPORTED_CURRENCY_SYMBOLS.REN, + SUPPORTED_CURRENCY_SYMBOLS.REP, + SUPPORTED_CURRENCY_SYMBOLS.REQ, + SUPPORTED_CURRENCY_SYMBOLS.RLC, + SUPPORTED_CURRENCY_SYMBOLS.RLY, + SUPPORTED_CURRENCY_SYMBOLS.SAND, + SUPPORTED_CURRENCY_SYMBOLS.SHIB, + SUPPORTED_CURRENCY_SYMBOLS.SKL, + SUPPORTED_CURRENCY_SYMBOLS.SNX, + SUPPORTED_CURRENCY_SYMBOLS.STORJ, + SUPPORTED_CURRENCY_SYMBOLS.SUKU, + SUPPORTED_CURRENCY_SYMBOLS.SUSHI, + SUPPORTED_CURRENCY_SYMBOLS.SWFTC, + SUPPORTED_CURRENCY_SYMBOLS.TRAC, + SUPPORTED_CURRENCY_SYMBOLS.TRB, + SUPPORTED_CURRENCY_SYMBOLS.TRIBE, + SUPPORTED_CURRENCY_SYMBOLS.TRU, + SUPPORTED_CURRENCY_SYMBOLS.UMA, + SUPPORTED_CURRENCY_SYMBOLS.UNI, + SUPPORTED_CURRENCY_SYMBOLS.USDC, + SUPPORTED_CURRENCY_SYMBOLS.USDT, + SUPPORTED_CURRENCY_SYMBOLS.WBTC, + SUPPORTED_CURRENCY_SYMBOLS.WCFG, + SUPPORTED_CURRENCY_SYMBOLS.XYO, + SUPPORTED_CURRENCY_SYMBOLS.YFII, + SUPPORTED_CURRENCY_SYMBOLS.ZRX, + ], + }, + [CHAIN_IDS.ROPSTEN]: { + nativeCurrency: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.ROPSTEN], + network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, + }, + [CHAIN_IDS.RINKEBY]: { + nativeCurrency: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.RINKEBY], + network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, + }, + [CHAIN_IDS.GOERLI]: { + nativeCurrency: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], + network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, + }, + [CHAIN_IDS.SEPOLIA]: { + nativeCurrency: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA], + network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, + }, + [CHAIN_IDS.KOVAN]: { + nativeCurrency: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.KOVAN], + network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, + }, + [CHAIN_IDS.BSC]: { + nativeCurrency: CURRENCY_SYMBOLS.BNB, + network: 'bsc', + transakCurrencies: [ + SUPPORTED_CURRENCY_SYMBOLS.BNB, + SUPPORTED_CURRENCY_SYMBOLS.BUSD, + ], + moonPay: { + defaultCurrencyCode: 'bnb_bsc', + showOnlyCurrencies: 'bnb_bsc,busd_bsc', + }, + }, + [CHAIN_IDS.POLYGON]: { + nativeCurrency: CURRENCY_SYMBOLS.MATIC, + network: 'polygon', + transakCurrencies: [ + SUPPORTED_CURRENCY_SYMBOLS.MATIC, + SUPPORTED_CURRENCY_SYMBOLS.USDT, + SUPPORTED_CURRENCY_SYMBOLS.USDC, + SUPPORTED_CURRENCY_SYMBOLS.DAI, + ], + moonPay: { + defaultCurrencyCode: 'matic_polygon', + showOnlyCurrencies: 'matic_polygon,usdc_polygon', + }, + wyre: { + srn: 'matic', + currencyCode: CURRENCY_SYMBOLS.MATIC, + }, + }, + [CHAIN_IDS.AVALANCHE]: { + nativeCurrency: CURRENCY_SYMBOLS.AVALANCHE, + network: 'avaxcchain', + transakCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.AVALANCHE], + moonPay: { + defaultCurrencyCode: 'avax_cchain', + showOnlyCurrencies: 'avax_cchain', + }, + wyre: { + srn: 'avalanche', + currencyCode: CURRENCY_SYMBOLS.AVALANCHE, + }, + coinbasePayCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.AVALANCHE], + }, + [CHAIN_IDS.FANTOM]: { + nativeCurrency: CURRENCY_SYMBOLS.FANTOM, + network: 'fantom', + transakCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.FANTOM], + }, + [CHAIN_IDS.CELO]: { + nativeCurrency: CURRENCY_SYMBOLS.CELO, + network: 'celo', + transakCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.CELO], + moonPay: { + defaultCurrencyCode: 'celo', + showOnlyCurrencies: 'celo', + }, + }, +}; + +export const FEATURED_RPCS: RPCDefinition[] = [ + { + chainId: CHAIN_IDS.ARBITRUM, + nickname: ARBITRUM_DISPLAY_NAME, + rpcUrl: `https://arbitrum-mainnet.infura.io/v3/${infuraProjectId}`, + ticker: CURRENCY_SYMBOLS.ARBITRUM, + rpcPrefs: { + blockExplorerUrl: 'https://explorer.arbitrum.io', + imageUrl: AETH_TOKEN_IMAGE_URL, + }, + }, + { + chainId: CHAIN_IDS.AVALANCHE, + nickname: AVALANCHE_DISPLAY_NAME, + rpcUrl: `https://avalanche-mainnet.infura.io/v3/${infuraProjectId}`, + ticker: CURRENCY_SYMBOLS.AVALANCHE, + rpcPrefs: { + blockExplorerUrl: 'https://snowtrace.io/', + imageUrl: AVAX_TOKEN_IMAGE_URL, + }, + }, + { + chainId: CHAIN_IDS.BSC, + nickname: BNB_DISPLAY_NAME, + rpcUrl: 'https://bsc-dataseed.binance.org/', + ticker: CURRENCY_SYMBOLS.BNB, + rpcPrefs: { + blockExplorerUrl: 'https://bscscan.com/', + imageUrl: BNB_TOKEN_IMAGE_URL, + }, + }, + { + chainId: CHAIN_IDS.FANTOM, + nickname: FANTOM_DISPLAY_NAME, + rpcUrl: 'https://rpc.ftm.tools/', + ticker: CURRENCY_SYMBOLS.FANTOM, + rpcPrefs: { + blockExplorerUrl: 'https://ftmscan.com/', + imageUrl: FTM_TOKEN_IMAGE_URL, + }, + }, + { + chainId: CHAIN_IDS.HARMONY, + nickname: HARMONY_DISPLAY_NAME, + rpcUrl: 'https://api.harmony.one/', + ticker: CURRENCY_SYMBOLS.HARMONY, + rpcPrefs: { + blockExplorerUrl: 'https://explorer.harmony.one/', + imageUrl: HARMONY_ONE_TOKEN_IMAGE_URL, + }, + }, + { + chainId: CHAIN_IDS.OPTIMISM, + nickname: OPTIMISM_DISPLAY_NAME, + rpcUrl: `https://optimism-mainnet.infura.io/v3/${infuraProjectId}`, + ticker: CURRENCY_SYMBOLS.ETH, + rpcPrefs: { + blockExplorerUrl: 'https://optimistic.etherscan.io/', + imageUrl: OPTIMISM_TOKEN_IMAGE_URL, + }, + }, + { + chainId: CHAIN_IDS.PALM, + nickname: PALM_DISPLAY_NAME, + rpcUrl: `https://palm-mainnet.infura.io/v3/${infuraProjectId}`, + ticker: CURRENCY_SYMBOLS.PALM, + rpcPrefs: { + blockExplorerUrl: 'https://explorer.palm.io/', + imageUrl: PALM_TOKEN_IMAGE_URL, + }, + }, + { + chainId: CHAIN_IDS.POLYGON, + nickname: `${POLYGON_DISPLAY_NAME} ${capitalize(NETWORK_TYPES.MAINNET)}`, + rpcUrl: `https://polygon-mainnet.infura.io/v3/${infuraProjectId}`, + ticker: CURRENCY_SYMBOLS.MATIC, + rpcPrefs: { + blockExplorerUrl: 'https://polygonscan.com/', + imageUrl: MATIC_TOKEN_IMAGE_URL, + }, + }, +]; diff --git a/shared/constants/swaps.js b/shared/constants/swaps.js index 8482709ca..22629bc1a 100644 --- a/shared/constants/swaps.js +++ b/shared/constants/swaps.js @@ -1,18 +1,10 @@ import { - MAINNET_CHAIN_ID, - ETH_SYMBOL, - TEST_ETH_SYMBOL, - BNB_SYMBOL, TEST_ETH_TOKEN_IMAGE_URL, BNB_TOKEN_IMAGE_URL, - BSC_CHAIN_ID, - POLYGON_CHAIN_ID, - MATIC_SYMBOL, MATIC_TOKEN_IMAGE_URL, - RINKEBY_CHAIN_ID, - AVALANCHE_CHAIN_ID, - AVALANCHE_SYMBOL, AVAX_TOKEN_IMAGE_URL, + CURRENCY_SYMBOLS, + CHAIN_IDS, } from './network'; export const QUOTES_EXPIRED_ERROR = 'quotes-expired'; @@ -27,7 +19,7 @@ export const SWAPS_FETCH_ORDER_CONFLICT = 'swaps-fetch-order-conflict'; const DEFAULT_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000'; export const ETH_SWAPS_TOKEN_OBJECT = { - symbol: ETH_SYMBOL, + symbol: CURRENCY_SYMBOLS.ETH, name: 'Ether', address: DEFAULT_TOKEN_ADDRESS, decimals: 18, @@ -35,7 +27,7 @@ export const ETH_SWAPS_TOKEN_OBJECT = { }; export const BNB_SWAPS_TOKEN_OBJECT = { - symbol: BNB_SYMBOL, + symbol: CURRENCY_SYMBOLS.BNB, name: 'Binance Coin', address: DEFAULT_TOKEN_ADDRESS, decimals: 18, @@ -43,7 +35,7 @@ export const BNB_SWAPS_TOKEN_OBJECT = { }; export const MATIC_SWAPS_TOKEN_OBJECT = { - symbol: MATIC_SYMBOL, + symbol: CURRENCY_SYMBOLS.MATIC, name: 'Matic', address: DEFAULT_TOKEN_ADDRESS, decimals: 18, @@ -51,7 +43,7 @@ export const MATIC_SWAPS_TOKEN_OBJECT = { }; export const AVAX_SWAPS_TOKEN_OBJECT = { - symbol: AVALANCHE_SYMBOL, + symbol: CURRENCY_SYMBOLS.AVALANCHE, name: 'Avalanche', address: DEFAULT_TOKEN_ADDRESS, decimals: 18, @@ -59,15 +51,15 @@ export const AVAX_SWAPS_TOKEN_OBJECT = { }; export const TEST_ETH_SWAPS_TOKEN_OBJECT = { - symbol: TEST_ETH_SYMBOL, + symbol: CURRENCY_SYMBOLS.TEST_ETH, name: 'Test Ether', address: DEFAULT_TOKEN_ADDRESS, decimals: 18, iconUrl: TEST_ETH_TOKEN_IMAGE_URL, }; -export const RINKEBY_SWAPS_TOKEN_OBJECT = { - symbol: ETH_SYMBOL, +export const GOERLI_SWAPS_TOKEN_OBJECT = { + symbol: CURRENCY_SYMBOLS.ETH, name: 'Ether', address: DEFAULT_TOKEN_ADDRESS, decimals: 18, @@ -90,8 +82,8 @@ const AVALANCHE_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31'; export const WETH_CONTRACT_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; -export const WETH_RINKEBY_CONTRACT_ADDRESS = - '0xc778417e063141139fce010982780140aa0cd5ab'; +export const WETH_GOERLI_CONTRACT_ADDRESS = + '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6'; export const WBNB_CONTRACT_ADDRESS = '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c'; export const WMATIC_CONTRACT_ADDRESS = @@ -110,94 +102,94 @@ export const GAS_DEV_API_BASE_URL = const BSC_DEFAULT_BLOCK_EXPLORER_URL = 'https://bscscan.com/'; const MAINNET_DEFAULT_BLOCK_EXPLORER_URL = 'https://etherscan.io/'; -const RINKEBY_DEFAULT_BLOCK_EXPLORER_URL = 'https://rinkeby.etherscan.io/'; +const GOERLI_DEFAULT_BLOCK_EXPLORER_URL = 'https://goerli.etherscan.io/'; const POLYGON_DEFAULT_BLOCK_EXPLORER_URL = 'https://polygonscan.com/'; const AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL = 'https://snowtrace.io/'; export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [ - MAINNET_CHAIN_ID, + CHAIN_IDS.MAINNET, SWAPS_TESTNET_CHAIN_ID, - BSC_CHAIN_ID, - POLYGON_CHAIN_ID, - AVALANCHE_CHAIN_ID, + CHAIN_IDS.BSC, + CHAIN_IDS.POLYGON, + CHAIN_IDS.AVALANCHE, ]; export const ALLOWED_DEV_SWAPS_CHAIN_IDS = [ ...ALLOWED_PROD_SWAPS_CHAIN_IDS, - RINKEBY_CHAIN_ID, + CHAIN_IDS.GOERLI, ]; export const ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS = [ - MAINNET_CHAIN_ID, - RINKEBY_CHAIN_ID, + CHAIN_IDS.MAINNET, + CHAIN_IDS.GOERLI, ]; export const SWAPS_CHAINID_CONTRACT_ADDRESS_MAP = { - [MAINNET_CHAIN_ID]: MAINNET_CONTRACT_ADDRESS, + [CHAIN_IDS.MAINNET]: MAINNET_CONTRACT_ADDRESS, [SWAPS_TESTNET_CHAIN_ID]: TESTNET_CONTRACT_ADDRESS, - [BSC_CHAIN_ID]: BSC_CONTRACT_ADDRESS, - [POLYGON_CHAIN_ID]: POLYGON_CONTRACT_ADDRESS, - [RINKEBY_CHAIN_ID]: TESTNET_CONTRACT_ADDRESS, - [AVALANCHE_CHAIN_ID]: AVALANCHE_CONTRACT_ADDRESS, + [CHAIN_IDS.BSC]: BSC_CONTRACT_ADDRESS, + [CHAIN_IDS.POLYGON]: POLYGON_CONTRACT_ADDRESS, + [CHAIN_IDS.GOERLI]: TESTNET_CONTRACT_ADDRESS, + [CHAIN_IDS.AVALANCHE]: AVALANCHE_CONTRACT_ADDRESS, }; export const SWAPS_WRAPPED_TOKENS_ADDRESSES = { - [MAINNET_CHAIN_ID]: WETH_CONTRACT_ADDRESS, + [CHAIN_IDS.MAINNET]: WETH_CONTRACT_ADDRESS, [SWAPS_TESTNET_CHAIN_ID]: WETH_CONTRACT_ADDRESS, - [BSC_CHAIN_ID]: WBNB_CONTRACT_ADDRESS, - [POLYGON_CHAIN_ID]: WMATIC_CONTRACT_ADDRESS, - [RINKEBY_CHAIN_ID]: WETH_RINKEBY_CONTRACT_ADDRESS, - [AVALANCHE_CHAIN_ID]: WAVAX_CONTRACT_ADDRESS, + [CHAIN_IDS.BSC]: WBNB_CONTRACT_ADDRESS, + [CHAIN_IDS.POLYGON]: WMATIC_CONTRACT_ADDRESS, + [CHAIN_IDS.GOERLI]: WETH_GOERLI_CONTRACT_ADDRESS, + [CHAIN_IDS.AVALANCHE]: WAVAX_CONTRACT_ADDRESS, }; export const ALLOWED_CONTRACT_ADDRESSES = { - [MAINNET_CHAIN_ID]: [ - SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[MAINNET_CHAIN_ID], - SWAPS_WRAPPED_TOKENS_ADDRESSES[MAINNET_CHAIN_ID], + [CHAIN_IDS.MAINNET]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.MAINNET], + SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.MAINNET], ], [SWAPS_TESTNET_CHAIN_ID]: [ SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[SWAPS_TESTNET_CHAIN_ID], SWAPS_WRAPPED_TOKENS_ADDRESSES[SWAPS_TESTNET_CHAIN_ID], ], - [RINKEBY_CHAIN_ID]: [ - SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[RINKEBY_CHAIN_ID], - SWAPS_WRAPPED_TOKENS_ADDRESSES[RINKEBY_CHAIN_ID], + [CHAIN_IDS.GOERLI]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.GOERLI], + SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.GOERLI], ], - [BSC_CHAIN_ID]: [ - SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[BSC_CHAIN_ID], - SWAPS_WRAPPED_TOKENS_ADDRESSES[BSC_CHAIN_ID], + [CHAIN_IDS.BSC]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.BSC], + SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.BSC], ], - [POLYGON_CHAIN_ID]: [ - SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[POLYGON_CHAIN_ID], - SWAPS_WRAPPED_TOKENS_ADDRESSES[POLYGON_CHAIN_ID], + [CHAIN_IDS.POLYGON]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.POLYGON], + SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.POLYGON], ], - [AVALANCHE_CHAIN_ID]: [ - SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[AVALANCHE_CHAIN_ID], - SWAPS_WRAPPED_TOKENS_ADDRESSES[AVALANCHE_CHAIN_ID], + [CHAIN_IDS.AVALANCHE]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.AVALANCHE], + SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.AVALANCHE], ], }; export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = { - [MAINNET_CHAIN_ID]: ETH_SWAPS_TOKEN_OBJECT, + [CHAIN_IDS.MAINNET]: ETH_SWAPS_TOKEN_OBJECT, [SWAPS_TESTNET_CHAIN_ID]: TEST_ETH_SWAPS_TOKEN_OBJECT, - [BSC_CHAIN_ID]: BNB_SWAPS_TOKEN_OBJECT, - [POLYGON_CHAIN_ID]: MATIC_SWAPS_TOKEN_OBJECT, - [RINKEBY_CHAIN_ID]: RINKEBY_SWAPS_TOKEN_OBJECT, - [AVALANCHE_CHAIN_ID]: AVAX_SWAPS_TOKEN_OBJECT, + [CHAIN_IDS.BSC]: BNB_SWAPS_TOKEN_OBJECT, + [CHAIN_IDS.POLYGON]: MATIC_SWAPS_TOKEN_OBJECT, + [CHAIN_IDS.GOERLI]: GOERLI_SWAPS_TOKEN_OBJECT, + [CHAIN_IDS.AVALANCHE]: AVAX_SWAPS_TOKEN_OBJECT, }; export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = { - [BSC_CHAIN_ID]: BSC_DEFAULT_BLOCK_EXPLORER_URL, - [MAINNET_CHAIN_ID]: MAINNET_DEFAULT_BLOCK_EXPLORER_URL, - [POLYGON_CHAIN_ID]: POLYGON_DEFAULT_BLOCK_EXPLORER_URL, - [RINKEBY_CHAIN_ID]: RINKEBY_DEFAULT_BLOCK_EXPLORER_URL, - [AVALANCHE_CHAIN_ID]: AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.BSC]: BSC_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.MAINNET]: MAINNET_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.POLYGON]: POLYGON_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.GOERLI]: GOERLI_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.AVALANCHE]: AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL, }; export const ETHEREUM = 'ethereum'; export const POLYGON = 'polygon'; export const BSC = 'bsc'; -export const RINKEBY = 'rinkeby'; +export const GOERLI = 'goerli'; export const AVALANCHE = 'avalanche'; export const SWAPS_CLIENT_ID = 'extension'; diff --git a/ui/helpers/utils/error-utils.js b/shared/lib/error-utils.js similarity index 70% rename from ui/helpers/utils/error-utils.js rename to shared/lib/error-utils.js index a33a056fb..c0baad2c1 100644 --- a/ui/helpers/utils/error-utils.js +++ b/shared/lib/error-utils.js @@ -1,7 +1,27 @@ -import getFirstPreferredLangCode from '../../../app/scripts/lib/get-first-preferred-lang-code'; -import { setupLocale } from '../..'; +import { memoize } from 'lodash'; +import getFirstPreferredLangCode from '../../app/scripts/lib/get-first-preferred-lang-code'; +import { + fetchLocale, + loadRelativeTimeFormatLocaleData, +} from '../../ui/helpers/utils/i18n-helper'; import switchDirection from './switch-direction'; +const _setupLocale = async (currentLocale) => { + const currentLocaleMessages = currentLocale + ? await fetchLocale(currentLocale) + : {}; + const enLocaleMessages = await fetchLocale('en'); + + await loadRelativeTimeFormatLocaleData('en'); + if (currentLocale) { + await loadRelativeTimeFormatLocaleData(currentLocale); + } + + return { currentLocaleMessages, enLocaleMessages }; +}; + +export const setupLocale = memoize(_setupLocale); + const getLocaleContext = (currentLocaleMessages, enLocaleMessages) => { return (key) => { let message = currentLocaleMessages[key]?.message; diff --git a/ui/helpers/utils/error-utils.test.js b/shared/lib/error-utils.test.js similarity index 84% rename from ui/helpers/utils/error-utils.test.js rename to shared/lib/error-utils.test.js index 84aa05508..e1a121d7f 100644 --- a/ui/helpers/utils/error-utils.test.js +++ b/shared/lib/error-utils.test.js @@ -1,14 +1,14 @@ -import { SUPPORT_LINK } from '../constants/common'; +import { fetchLocale } from '../../ui/helpers/utils/i18n-helper'; +import { SUPPORT_LINK } from './ui-utils'; import { getErrorHtml } from './error-utils'; -import { fetchLocale } from './i18n-helper'; -jest.mock('./i18n-helper', () => ({ +jest.mock('../../ui/helpers/utils/i18n-helper', () => ({ fetchLocale: jest.fn(), loadRelativeTimeFormatLocaleData: jest.fn(), })); -describe('Error utils Tests', () => { - it('should get error html', async () => { +describe('Error utils Tests', function () { + it('should get error html', async function () { const mockStore = { localeMessages: { current: { diff --git a/ui/helpers/utils/fetch-with-cache.js b/shared/lib/fetch-with-cache.js similarity index 92% rename from ui/helpers/utils/fetch-with-cache.js rename to shared/lib/fetch-with-cache.js index 377c3d51f..a92f4fb2e 100644 --- a/ui/helpers/utils/fetch-with-cache.js +++ b/shared/lib/fetch-with-cache.js @@ -1,5 +1,5 @@ -import { MINUTE, SECOND } from '../../../shared/constants/time'; -import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'; +import { MINUTE, SECOND } from '../constants/time'; +import getFetchWithTimeout from '../modules/fetch-with-timeout'; import { getStorageItem, setStorageItem } from './storage-helpers'; const fetchWithCache = async ( diff --git a/ui/helpers/utils/fetch-with-cache.test.js b/shared/lib/fetch-with-cache.test.js similarity index 99% rename from ui/helpers/utils/fetch-with-cache.test.js rename to shared/lib/fetch-with-cache.test.js index 76e7ebd01..0b6f3f933 100644 --- a/ui/helpers/utils/fetch-with-cache.test.js +++ b/shared/lib/fetch-with-cache.test.js @@ -3,7 +3,7 @@ import sinon from 'sinon'; import { getStorageItem, setStorageItem } from './storage-helpers'; -jest.mock('./storage-helpers.js', () => ({ +jest.mock('./storage-helpers', () => ({ getStorageItem: jest.fn(), setStorageItem: jest.fn(), })); diff --git a/shared/lib/metamask-controller-utils.js b/shared/lib/metamask-controller-utils.js new file mode 100644 index 000000000..77b171fe5 --- /dev/null +++ b/shared/lib/metamask-controller-utils.js @@ -0,0 +1,12 @@ +import { conversionUtil } from '../modules/conversion.utils'; + +export function hexToDecimal(hexValue) { + return conversionUtil(hexValue, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + }); +} + +export function getTokenValueParam(tokenData = {}) { + return tokenData?.args?._value?.toString(); +} diff --git a/ui/helpers/utils/storage-helpers.js b/shared/lib/storage-helpers.js similarity index 100% rename from ui/helpers/utils/storage-helpers.js rename to shared/lib/storage-helpers.js diff --git a/shared/lib/swaps-utils.js b/shared/lib/swaps-utils.js new file mode 100644 index 000000000..6981ef255 --- /dev/null +++ b/shared/lib/swaps-utils.js @@ -0,0 +1,320 @@ +import BigNumber from 'bignumber.js'; +import log from 'loglevel'; +import { CHAIN_IDS } from '../constants/network'; +import { + GAS_API_BASE_URL, + GAS_DEV_API_BASE_URL, + SWAPS_API_V2_BASE_URL, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP, + SWAPS_CLIENT_ID, + SWAPS_DEV_API_V2_BASE_URL, + SWAPS_WRAPPED_TOKENS_ADDRESSES, +} from '../constants/swaps'; +import { SECOND } from '../constants/time'; +import { isValidHexAddress } from '../modules/hexstring-utils'; +import { addHexPrefix } from '../../app/scripts/lib/util'; +import fetchWithCache from './fetch-with-cache'; +import { decimalToHex } from './transactions-controller-utils'; + +const TEST_CHAIN_IDS = [CHAIN_IDS.GOERLI, CHAIN_IDS.LOCALHOST]; + +const clientIdHeader = { 'X-Client-Id': SWAPS_CLIENT_ID }; + +export const validHex = (string) => Boolean(string?.match(/^0x[a-f0-9]+$/u)); +export const truthyString = (string) => Boolean(string?.length); +export const truthyDigitString = (string) => + truthyString(string) && Boolean(string.match(/^\d+$/u)); + +export function validateData(validators, object, urlUsed, logError = true) { + return validators.every(({ property, type, validator }) => { + const types = type.split('|'); + + const valid = + types.some((_type) => typeof object[property] === _type) && + (!validator || validator(object[property])); + if (!valid && logError) { + log.error( + `response to GET ${urlUsed} invalid for property ${property}; value was:`, + object[property], + '| type was: ', + typeof object[property], + ); + } + return valid; + }); +} + +export const QUOTE_VALIDATORS = [ + { + property: 'trade', + type: 'object', + validator: (trade) => + trade && + validHex(trade.data) && + isValidHexAddress(trade.to, { allowNonPrefixed: false }) && + isValidHexAddress(trade.from, { allowNonPrefixed: false }) && + truthyString(trade.value), + }, + { + property: 'approvalNeeded', + type: 'object', + validator: (approvalTx) => + approvalTx === null || + (approvalTx && + validHex(approvalTx.data) && + isValidHexAddress(approvalTx.to, { allowNonPrefixed: false }) && + isValidHexAddress(approvalTx.from, { allowNonPrefixed: false })), + }, + { + property: 'sourceAmount', + type: 'string', + validator: truthyDigitString, + }, + { + property: 'destinationAmount', + type: 'string', + validator: truthyDigitString, + }, + { + property: 'sourceToken', + type: 'string', + validator: (input) => isValidHexAddress(input, { allowNonPrefixed: false }), + }, + { + property: 'destinationToken', + type: 'string', + validator: (input) => isValidHexAddress(input, { allowNonPrefixed: false }), + }, + { + property: 'aggregator', + type: 'string', + validator: truthyString, + }, + { + property: 'aggType', + type: 'string', + validator: truthyString, + }, + { + property: 'error', + type: 'object', + validator: (error) => error === null || typeof error === 'object', + }, + { + property: 'averageGas', + type: 'number', + }, + { + property: 'maxGas', + type: 'number', + }, + { + property: 'gasEstimate', + type: 'number|undefined', + validator: (gasEstimate) => gasEstimate === undefined || gasEstimate > 0, + }, + { + property: 'fee', + type: 'number', + }, +]; + +/** + * @param {string} type - Type of an API call, e.g. "tokens" + * @param {string} chainId + * @returns string + */ +const getBaseUrlForNewSwapsApi = (type, chainId) => { + const useDevApis = process.env.SWAPS_USE_DEV_APIS; + const v2ApiBaseUrl = useDevApis + ? SWAPS_DEV_API_V2_BASE_URL + : SWAPS_API_V2_BASE_URL; + const gasApiBaseUrl = useDevApis ? GAS_DEV_API_BASE_URL : GAS_API_BASE_URL; + const noNetworkSpecificTypes = ['refreshTime']; // These types don't need network info in the URL. + if (noNetworkSpecificTypes.includes(type)) { + return v2ApiBaseUrl; + } + const chainIdDecimal = chainId && parseInt(chainId, 16); + const gasApiTypes = ['gasPrices']; + if (gasApiTypes.includes(type)) { + return `${gasApiBaseUrl}/networks/${chainIdDecimal}`; // Gas calculations are in its own repo. + } + return `${v2ApiBaseUrl}/networks/${chainIdDecimal}`; +}; + +export const getBaseApi = function (type, chainId = CHAIN_IDS.MAINNET) { + // eslint-disable-next-line no-param-reassign + chainId = TEST_CHAIN_IDS.includes(chainId) ? CHAIN_IDS.MAINNET : chainId; + const baseUrl = getBaseUrlForNewSwapsApi(type, chainId); + const chainIdDecimal = chainId && parseInt(chainId, 16); + if (!baseUrl) { + throw new Error(`Swaps API calls are disabled for chainId: ${chainId}`); + } + switch (type) { + case 'trade': + return `${baseUrl}/trades?`; + case 'tokens': + return `${baseUrl}/tokens`; + case 'token': + return `${baseUrl}/token`; + case 'topAssets': + return `${baseUrl}/topAssets`; + case 'aggregatorMetadata': + return `${baseUrl}/aggregatorMetadata`; + case 'gasPrices': + return `${baseUrl}/gasPrices`; + case 'network': + // Only use v2 for this endpoint. + return `${SWAPS_API_V2_BASE_URL}/networks/${chainIdDecimal}`; + default: + throw new Error('getBaseApi requires an api call type'); + } +}; + +export function calcTokenValue(value, decimals) { + const multiplier = Math.pow(10, Number(decimals || 0)); + return new BigNumber(String(value)).times(multiplier); +} + +export const shouldEnableDirectWrapping = ( + chainId, + sourceToken, + destinationToken, +) => { + if (!sourceToken || !destinationToken) { + return false; + } + const wrappedToken = SWAPS_WRAPPED_TOKENS_ADDRESSES[chainId]; + const nativeToken = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId]?.address; + const sourceTokenLowerCase = sourceToken.toLowerCase(); + const destinationTokenLowerCase = destinationToken.toLowerCase(); + return ( + (sourceTokenLowerCase === wrappedToken && + destinationTokenLowerCase === nativeToken) || + (sourceTokenLowerCase === nativeToken && + destinationTokenLowerCase === wrappedToken) + ); +}; + +/** + * Given and object where all values are strings, returns the same object with all values + * now prefixed with '0x' + * + * @param obj + */ +export function addHexPrefixToObjectValues(obj) { + return Object.keys(obj).reduce((newObj, key) => { + return { ...newObj, [key]: addHexPrefix(obj[key]) }; + }, {}); +} + +/** + * Given the standard set of information about a transaction, returns a transaction properly formatted for + * publishing via JSON RPC and web3 + * + * @param {object} options + * @param {boolean} [options.sendToken] - Indicates whether or not the transaciton is a token transaction + * @param {string} options.data - A hex string containing the data to include in the transaction + * @param {string} options.to - A hex address of the tx recipient address + * @param options.amount + * @param {string} options.from - A hex address of the tx sender address + * @param {string} options.gas - A hex representation of the gas value for the transaction + * @param {string} options.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 + */ +export function constructTxParams({ + sendToken, + data, + to, + amount, + from, + gas, + gasPrice, +}) { + const txParams = { + data, + from, + value: '0', + gas, + gasPrice, + }; + + if (!sendToken) { + txParams.value = amount; + txParams.to = to; + } + return addHexPrefixToObjectValues(txParams); +} + +export async function fetchTradesInfo( + { + slippage, + sourceToken, + sourceDecimals, + destinationToken, + value, + fromAddress, + exchangeList, + }, + { chainId }, +) { + const urlParams = { + destinationToken, + sourceToken, + sourceAmount: calcTokenValue(value, sourceDecimals).toString(10), + slippage, + timeout: SECOND * 10, + walletAddress: fromAddress, + }; + + if (exchangeList) { + urlParams.exchangeList = exchangeList; + } + if (shouldEnableDirectWrapping(chainId, sourceToken, destinationToken)) { + urlParams.enableDirectWrapping = true; + } + + const queryString = new URLSearchParams(urlParams).toString(); + const tradeURL = `${getBaseApi('trade', chainId)}${queryString}`; + const tradesResponse = await fetchWithCache( + tradeURL, + { method: 'GET', headers: clientIdHeader }, + { cacheRefreshTime: 0, timeout: SECOND * 15 }, + ); + const newQuotes = tradesResponse.reduce((aggIdTradeMap, quote) => { + if ( + quote.trade && + !quote.error && + validateData(QUOTE_VALIDATORS, quote, tradeURL) + ) { + const constructedTrade = constructTxParams({ + to: quote.trade.to, + from: quote.trade.from, + data: quote.trade.data, + amount: decimalToHex(quote.trade.value), + gas: decimalToHex(quote.maxGas), + }); + + let { approvalNeeded } = quote; + + if (approvalNeeded) { + approvalNeeded = constructTxParams({ + ...approvalNeeded, + }); + } + + return { + ...aggIdTradeMap, + [quote.aggregator]: { + ...quote, + slippage, + trade: constructedTrade, + approvalNeeded, + }, + }; + } + return aggIdTradeMap; + }, {}); + + return newQuotes; +} diff --git a/ui/helpers/utils/switch-direction.js b/shared/lib/switch-direction.js similarity index 100% rename from ui/helpers/utils/switch-direction.js rename to shared/lib/switch-direction.js diff --git a/shared/lib/token-util.js b/shared/lib/token-util.js new file mode 100644 index 000000000..d8c2927e8 --- /dev/null +++ b/shared/lib/token-util.js @@ -0,0 +1,20 @@ +/** + * Gets the '_value' parameter of the given token transaction data + * (i.e function call) per the Human Standard Token ABI, if present. + * + * @param {object} tokenData - ethers Interface token data. + * @returns {string | undefined} A decimal string value. + */ +/** + * Gets either the '_tokenId' parameter or the 'id' param of the passed token transaction data., + * These are the parsed tokenId values returned by `parseStandardTokenTransactionData` as defined + * in the ERC721 and ERC1155 ABIs from metamask-eth-abis (https://github.com/MetaMask/metamask-eth-abis/tree/main/src/abis) + * + * @param {object} tokenData - ethers Interface token data. + * @returns {string | undefined} A decimal string value. + */ +export function getTokenIdParam(tokenData = {}) { + return ( + tokenData?.args?._tokenId?.toString() ?? tokenData?.args?.id?.toString() + ); +} diff --git a/shared/lib/transactions-controller-utils.js b/shared/lib/transactions-controller-utils.js new file mode 100644 index 000000000..bc6cec5ad --- /dev/null +++ b/shared/lib/transactions-controller-utils.js @@ -0,0 +1,172 @@ +import BigNumber from 'bignumber.js'; +import { TRANSACTION_ENVELOPE_TYPES } from '../constants/transaction'; +import { + conversionUtil, + multiplyCurrencies, + subtractCurrencies, +} from '../modules/conversion.utils'; +import { isSwapsDefaultTokenSymbol } from '../modules/swaps.utils'; + +const TOKEN_TRANSFER_LOG_TOPIC_HASH = + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; + +export const TRANSACTION_NO_CONTRACT_ERROR_KEY = 'transactionErrorNoContract'; + +export const TEN_SECONDS_IN_MILLISECONDS = 10_000; + +export function calcGasTotal(gasLimit = '0', gasPrice = '0') { + return multiplyCurrencies(gasLimit, gasPrice, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 16, + }); +} + +/** + * Given a number and specified precision, returns that number in base 10 with a maximum of precision + * significant digits, but without any trailing zeros after the decimal point To be used when wishing + * to display only as much digits to the user as necessary + * + * @param {string | number | BigNumber} n - The number to format + * @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) + .toPrecision(precision) + .replace(/(\.[0-9]*[1-9])0*|(\.0*)/u, '$1'); +} + +export function calcTokenAmount(value, decimals) { + const multiplier = Math.pow(10, Number(decimals || 0)); + return new BigNumber(String(value)).div(multiplier); +} + +export function getSwapsTokensReceivedFromTxMeta( + tokenSymbol, + txMeta, + tokenAddress, + accountAddress, + tokenDecimals, + approvalTxMeta, + chainId, +) { + const txReceipt = txMeta?.txReceipt; + const networkAndAccountSupports1559 = + txMeta?.txReceipt?.type === TRANSACTION_ENVELOPE_TYPES.FEE_MARKET; + if (isSwapsDefaultTokenSymbol(tokenSymbol, chainId)) { + if ( + !txReceipt || + !txMeta || + !txMeta.postTxBalance || + !txMeta.preTxBalance + ) { + return null; + } + + if (txMeta.swapMetaData && txMeta.preTxBalance === txMeta.postTxBalance) { + // If preTxBalance and postTxBalance are equal, postTxBalance hasn't been updated on time + // because of the RPC provider delay, so we return an estimated receiving amount instead. + return txMeta.swapMetaData.token_to_amount; + } + + let approvalTxGasCost = '0x0'; + if (approvalTxMeta && approvalTxMeta.txReceipt) { + approvalTxGasCost = calcGasTotal( + approvalTxMeta.txReceipt.gasUsed, + networkAndAccountSupports1559 + ? approvalTxMeta.txReceipt.effectiveGasPrice // Base fee + priority fee. + : approvalTxMeta.txParams.gasPrice, + ); + } + + const gasCost = calcGasTotal( + txReceipt.gasUsed, + networkAndAccountSupports1559 + ? txReceipt.effectiveGasPrice + : txMeta.txParams.gasPrice, + ); + const totalGasCost = new BigNumber(gasCost, 16) + .plus(approvalTxGasCost, 16) + .toString(16); + + const preTxBalanceLessGasCost = subtractCurrencies( + txMeta.preTxBalance, + totalGasCost, + { + aBase: 16, + bBase: 16, + toNumericBase: 'hex', + }, + ); + + const ethReceived = subtractCurrencies( + txMeta.postTxBalance, + preTxBalanceLessGasCost, + { + aBase: 16, + bBase: 16, + fromDenomination: 'WEI', + toDenomination: 'ETH', + toNumericBase: 'dec', + numberOfDecimals: 6, + }, + ); + return ethReceived; + } + const txReceiptLogs = txReceipt?.logs; + if (txReceiptLogs && txReceipt?.status !== '0x0') { + const tokenTransferLog = txReceiptLogs.find((txReceiptLog) => { + const isTokenTransfer = + txReceiptLog.topics && + txReceiptLog.topics[0] === TOKEN_TRANSFER_LOG_TOPIC_HASH; + const isTransferFromGivenToken = txReceiptLog.address === tokenAddress; + const isTransferFromGivenAddress = + txReceiptLog.topics && + txReceiptLog.topics[2] && + txReceiptLog.topics[2].match(accountAddress.slice(2)); + return ( + isTokenTransfer && + isTransferFromGivenToken && + isTransferFromGivenAddress + ); + }); + return tokenTransferLog + ? toPrecisionWithoutTrailingZeros( + calcTokenAmount(tokenTransferLog.data, tokenDecimals).toString(10), + 6, + ) + : ''; + } + return null; +} + +export const TRANSACTION_ENVELOPE_TYPE_NAMES = { + FEE_MARKET: 'fee-market', + LEGACY: 'legacy', +}; + +export function hexWEIToDecGWEI(decGWEI) { + return conversionUtil(decGWEI, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromDenomination: 'WEI', + toDenomination: 'GWEI', + }); +} + +export function decimalToHex(decimal) { + return conversionUtil(decimal, { + fromNumericBase: 'dec', + toNumericBase: 'hex', + }); +} + +export function hexWEIToDecETH(hexWEI) { + return conversionUtil(hexWEI, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromDenomination: 'WEI', + toDenomination: 'ETH', + }); +} diff --git a/shared/lib/ui-utils.js b/shared/lib/ui-utils.js new file mode 100644 index 000000000..e03ce61e7 --- /dev/null +++ b/shared/lib/ui-utils.js @@ -0,0 +1,7 @@ +let _supportLink = 'https://support.metamask.io'; + +///: BEGIN:ONLY_INCLUDE_IN(flask) +_supportLink = 'https://metamask-flask.zendesk.com/hc'; +///: END:ONLY_INCLUDE_IN + +export const SUPPORT_LINK = _supportLink; diff --git a/shared/modules/network.utils.js b/shared/modules/network.utils.js index eaa7148a3..05723b3ac 100644 --- a/shared/modules/network.utils.js +++ b/shared/modules/network.utils.js @@ -1,10 +1,4 @@ -import { - MAX_SAFE_CHAIN_ID, - BSC_CHAIN_ID, - POLYGON_CHAIN_ID, - AVALANCHE_CHAIN_ID, - MAINNET_CHAIN_ID, -} from '../constants/network'; +import { CHAIN_IDS, MAX_SAFE_CHAIN_ID } from '../constants/network'; /** * Checks whether the given number primitive chain ID is safe. @@ -43,10 +37,10 @@ export function isPrefixedFormattedHexString(value) { */ export function isTokenDetectionEnabledForNetwork(chainId) { switch (chainId) { - case MAINNET_CHAIN_ID: - case BSC_CHAIN_ID: - case POLYGON_CHAIN_ID: - case AVALANCHE_CHAIN_ID: + case CHAIN_IDS.MAINNET: + case CHAIN_IDS.BSC: + case CHAIN_IDS.POLYGON: + case CHAIN_IDS.AVALANCHE: return true; default: return false; diff --git a/test/e2e/mv3-perf-stats/bundle-size.js b/test/e2e/mv3-perf-stats/bundle-size.js index d7360ab9d..7ece2877a 100755 --- a/test/e2e/mv3-perf-stats/bundle-size.js +++ b/test/e2e/mv3-perf-stats/bundle-size.js @@ -53,14 +53,14 @@ async function main() { const distFolder = 'dist/chrome'; const backgroundFileList = []; const uiFileList = []; + const commonFileList = []; const files = await fs.readdir(distFolder); for (let i = 0; i < files.length; i++) { const file = files[i]; if (CommonFileRegex.test(file)) { const stats = await fs.stat(`${distFolder}/${file}`); - backgroundFileList.push({ name: file, size: stats.size }); - uiFileList.push({ name: file, size: stats.size }); + commonFileList.push({ name: file, size: stats.size }); } else if ( backgroundFiles.includes(file) || BackgroundFileRegex.test(file) @@ -83,6 +83,11 @@ async function main() { 0, ); + const commonBundleSize = commonFileList.reduce( + (result, file) => result + file.size, + 0, + ); + const result = { background: { name: 'background', @@ -94,6 +99,11 @@ async function main() { size: uiBundleSize, fileList: uiFileList, }, + common: { + name: 'common', + size: commonBundleSize, + fileList: commonFileList, + }, }; if (out) { @@ -115,6 +125,7 @@ async function main() { { background: backgroundBundleSize, ui: uiBundleSize, + common: commonBundleSize, timestamp: new Date().getTime(), }, null, diff --git a/test/e2e/snaps/test-snap-bip-44.spec.js b/test/e2e/snaps/test-snap-bip-44.spec.js index 5795fa777..1db055b3a 100644 --- a/test/e2e/snaps/test-snap-bip-44.spec.js +++ b/test/e2e/snaps/test-snap-bip-44.spec.js @@ -79,6 +79,7 @@ describe('Test Snap bip-44', function () { await driver.waitUntilXWindowHandles(1, 5000, 10000); windowHandles = await driver.getAllWindowHandles(); await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.delay(1000); await driver.clickElement('#sendBip44'); // check the results of the public key test diff --git a/test/e2e/snaps/test-snap-confirm.spec.js b/test/e2e/snaps/test-snap-confirm.spec.js index f4dcfa99d..c6a35657a 100644 --- a/test/e2e/snaps/test-snap-confirm.spec.js +++ b/test/e2e/snaps/test-snap-confirm.spec.js @@ -64,6 +64,7 @@ describe('Test Snap Confirm', function () { await driver.waitUntilXWindowHandles(1, 5000, 10000); windowHandles = await driver.getAllWindowHandles(); await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.delay(1000); await driver.clickElement('#sendConfirmButton'); // hit 'approve' on the custom confirm diff --git a/test/e2e/snaps/test-snap-error.spec.js b/test/e2e/snaps/test-snap-error.spec.js index bb4fc251a..177184727 100644 --- a/test/e2e/snaps/test-snap-error.spec.js +++ b/test/e2e/snaps/test-snap-error.spec.js @@ -64,6 +64,7 @@ describe('Test Snap Error', function () { await driver.waitUntilXWindowHandles(1, 5000, 10000); windowHandles = await driver.getAllWindowHandles(); await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.delay(1000); await driver.clickElement('#sendError'); await driver.navigate(PAGES.HOME); diff --git a/test/e2e/snaps/test-snap-managestate.spec.js b/test/e2e/snaps/test-snap-managestate.spec.js index 03e481e89..06fddda59 100644 --- a/test/e2e/snaps/test-snap-managestate.spec.js +++ b/test/e2e/snaps/test-snap-managestate.spec.js @@ -73,6 +73,7 @@ describe('Test Snap manageState', function () { windowHandles = await driver.getAllWindowHandles(); await driver.switchToWindowWithTitle('Test Snaps', windowHandles); await driver.fill('#dataManageState', '23'); + await driver.delay(1000); await driver.clickElement('#sendManageState'); // check the results of the public key test diff --git a/test/e2e/snaps/test-snap-notification.spec.js b/test/e2e/snaps/test-snap-notification.spec.js index 0a303bc9c..25f7cbe34 100644 --- a/test/e2e/snaps/test-snap-notification.spec.js +++ b/test/e2e/snaps/test-snap-notification.spec.js @@ -72,6 +72,7 @@ describe('Test Snap Notification', function () { await driver.waitUntilXWindowHandles(1, 5000, 10000); windowHandles = await driver.getAllWindowHandles(); await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.delay(1000); await driver.clickElement('#sendInAppNotification'); // try to go to the MM pages diff --git a/test/e2e/tests/send-hex-address.spec.js b/test/e2e/tests/send-hex-address.spec.js index af815c238..5c5bbf0a9 100644 --- a/test/e2e/tests/send-hex-address.spec.js +++ b/test/e2e/tests/send-hex-address.spec.js @@ -1,5 +1,6 @@ const { strict: assert } = require('assert'); const { convertToHexValue, withFixtures } = require('../helpers'); +const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); const hexPrefixedAddress = '0x2f318C334780961FB129D2a6c30D0763d9a5C970'; const nonHexPrefixedAddress = hexPrefixedAddress.substring(2); @@ -119,6 +120,7 @@ describe('Send ETH to a 40 character hexadecimal address', function () { }); describe('Send ERC20 to a 40 character hexadecimal address', function () { + const smartContract = SMART_CONTRACTS.HST; const ganacheOptions = { accounts: [ { @@ -134,39 +136,29 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { dapp: true, fixtures: 'connected-state', ganacheOptions, + smartContract, title: this.test.title, failOnConsoleError: false, }, - async ({ driver }) => { + async ({ driver, contractRegistry }) => { + const contractAddress = await contractRegistry.getContractAddress( + smartContract, + ); await driver.navigate(); await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - // Create TST - await driver.openNewPage('http://127.0.0.1:8080/'); - await driver.clickElement('#createToken'); - await driver.waitUntilXWindowHandles(3); + await driver.openNewPage( + `http://127.0.0.1:8080/?contract=${contractAddress}`, + ); let windowHandles = await driver.getAllWindowHandles(); const extension = windowHandles[0]; - const dapp = await driver.switchToWindowWithTitle( - 'E2E Test Dapp', - windowHandles, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindow(extension); - await driver.clickElement('[data-testid="home__activity-tab"]'); - await driver.waitForSelector( - '.transaction-list__completed-transactions .transaction-list-item:nth-of-type(1)', - { timeout: 10000 }, - ); + + // Using the line below to make wait time deterministic and avoid using a delay + // See more here https://github.com/MetaMask/metamask-extension/pull/15604/files#r949300551 + await driver.findClickableElement('#deployButton'); // Add token - await driver.switchToWindow(dapp); await driver.clickElement('#watchAsset'); await driver.waitUntilXWindowHandles(3); windowHandles = await driver.getAllWindowHandles(); @@ -209,7 +201,7 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement('[data-testid="home__activity-tab"]'); await driver.waitForSelector( - '.transaction-list__completed-transactions .transaction-list-item:nth-of-type(2)', + '.transaction-list__completed-transactions .transaction-list-item:nth-of-type(1)', { timeout: 10000 }, ); const sendTransactionListItem = await driver.waitForSelector( @@ -233,39 +225,28 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { dapp: true, fixtures: 'connected-state', ganacheOptions, + smartContract, title: this.test.title, failOnConsoleError: false, }, - async ({ driver }) => { + async ({ driver, contractRegistry }) => { + const contractAddress = await contractRegistry.getContractAddress( + smartContract, + ); await driver.navigate(); await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); // Create TST - await driver.openNewPage('http://127.0.0.1:8080/'); - await driver.clickElement('#createToken'); - await driver.waitUntilXWindowHandles(3); - let windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - const dapp = await driver.switchToWindowWithTitle( - 'E2E Test Dapp', - windowHandles, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindow(extension); - await driver.clickElement('[data-testid="home__activity-tab"]'); - await driver.waitForSelector( - '.transaction-list__completed-transactions .transaction-list-item:nth-of-type(1)', - { timeout: 10000 }, + await driver.openNewPage( + `http://127.0.0.1:8080/?contract=${contractAddress}`, ); + let windowHandles = await driver.getAllWindowHandles(); + const extension = windowHandles[0]; + // Add token - await driver.switchToWindow(dapp); + await driver.findClickableElement('#deployButton'); await driver.clickElement('#watchAsset'); await driver.waitUntilXWindowHandles(3); windowHandles = await driver.getAllWindowHandles(); @@ -308,7 +289,7 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement('[data-testid="home__activity-tab"]'); await driver.waitForSelector( - '.transaction-list__completed-transactions .transaction-list-item:nth-of-type(2)', + '.transaction-list__completed-transactions .transaction-list-item:nth-of-type(1)', { timeout: 10000 }, ); const sendTransactionListItem = await driver.waitForSelector( diff --git a/test/e2e/tests/swap-eth.spec.js b/test/e2e/tests/swap-eth.spec.js index e53ba0d6d..771a5e050 100644 --- a/test/e2e/tests/swap-eth.spec.js +++ b/test/e2e/tests/swap-eth.spec.js @@ -39,8 +39,13 @@ describe('Swap Eth for another Token', function () { await driver.clickElement( '[class="dropdown-search-list__closed-primary-label dropdown-search-list__select-default"]', ); - await driver.clickElement('[placeholder="Search for a token"]'); - await driver.fill('[placeholder="Search for a token"]', 'DAI'); + await driver.clickElement( + '[placeholder="Search name or paste address"]', + ); + await driver.fill( + '[placeholder="Search name or paste address"]', + 'DAI', + ); await driver.waitForSelector( '[class="searchable-item-list__primary-label"]', ); diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index d7bd58818..59416becb 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -382,6 +382,10 @@ class Driver { const artifactDir = `./test-artifacts/${this.browser}/${title}`; const filepathBase = `${artifactDir}/test-failure`; await fs.mkdir(artifactDir, { recursive: true }); + const isPageError = await this.isElementPresent('.error-page__details'); + if (isPageError) { + await this.clickElement('.error-page__details'); + } const screenshot = await this.driver.takeScreenshot(); await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64', diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js index 2595746c0..54345e831 100644 --- a/test/jest/mock-store.js +++ b/test/jest/mock-store.js @@ -1,4 +1,4 @@ -import { MAINNET_CHAIN_ID } from '../../shared/constants/network'; +import { CHAIN_IDS } from '../../shared/constants/network'; const createGetSmartTransactionFeesApiResponse = () => { return { @@ -128,10 +128,10 @@ export const createSwapsMockStore = () => { }, }, provider: { - chainId: MAINNET_CHAIN_ID, + chainId: CHAIN_IDS.MAINNET, }, cachedBalances: { - [MAINNET_CHAIN_ID]: 5, + [CHAIN_IDS.MAINNET]: 5, }, preferences: { showFiatInTestnets: true, @@ -408,7 +408,7 @@ export const createSwapsMockStore = () => { liveness: true, fees: createGetSmartTransactionFeesApiResponse(), smartTransactions: { - [MAINNET_CHAIN_ID]: [ + [CHAIN_IDS.MAINNET]: [ { uuid: 'uuid2', status: 'success', diff --git a/ui/components/app/account-menu/account-menu.component.js b/ui/components/app/account-menu/account-menu.component.js index fbd67f923..369927afc 100644 --- a/ui/components/app/account-menu/account-menu.component.js +++ b/ui/components/app/account-menu/account-menu.component.js @@ -16,7 +16,6 @@ import SiteIcon from '../../ui/site-icon'; import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'; import { PRIMARY, - SUPPORT_LINK, ///: BEGIN:ONLY_INCLUDE_IN(beta,flask) SUPPORT_REQUEST_LINK, ///: END:ONLY_INCLUDE_IN @@ -41,6 +40,7 @@ import IconImport from '../../ui/icon/icon-import'; import Button from '../../ui/button'; import SearchIcon from '../../ui/icon/search-icon'; +import { SUPPORT_LINK } from '../../../../shared/lib/ui-utils'; import KeyRingLabel from './keyring-label'; export function AccountMenuItem(props) { diff --git a/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.test.js b/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.test.js index cc5f64a9f..00edd64a5 100644 --- a/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.test.js +++ b/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.test.js @@ -10,7 +10,7 @@ import { tick } from '../../../../../test/lib/tick'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import * as actions from '../../../../store/actions'; -import { KOVAN_CHAIN_ID } from '../../../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../../../shared/constants/network'; import UnconnectedAccountAlert from '.'; describe('Unconnected Account Alert', () => { @@ -39,7 +39,7 @@ describe('Unconnected Account Alert', () => { }; const cachedBalances = { - [KOVAN_CHAIN_ID]: { + [CHAIN_IDS]: { '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': '0x0', '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b': '0x0', }, @@ -63,7 +63,7 @@ describe('Unconnected Account Alert', () => { cachedBalances, keyrings, provider: { - chainId: KOVAN_CHAIN_ID, + chainId: CHAIN_IDS, }, permissionHistory: { 'https://test.dapp': { diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 77574fb3e..dc85fc6a1 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -32,6 +32,7 @@ @import 'edit-gas-fee-popover/edit-gas-tooltip/index'; @import 'flask/experimental-area/index'; @import 'flask/snaps-authorship-pill/index'; +@import 'flask/snap-content-footer/index'; @import 'flask/snap-install-warning/index'; @import 'flask/snap-remove-warning/index'; @import 'flask/snap-settings-card/index'; diff --git a/ui/components/app/asset-list/asset-list.test.js b/ui/components/app/asset-list/asset-list.test.js new file mode 100644 index 000000000..b3925d6b6 --- /dev/null +++ b/ui/components/app/asset-list/asset-list.test.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import { renderWithProvider } from '../../../../test/jest'; +import configureStore from '../../../store/store'; +import mockState from '../../../../test/data/mock-state.json'; +import AssetList from './asset-list'; + +const render = () => { + const store = configureStore({ + metamask: { + ...mockState.metamask, + }, + }); + return renderWithProvider(, store); +}; + +describe('AssetList', () => { + it('renders AssetList component and shows Refresh List text', () => { + render(); + expect(screen.getByText('Refresh list')).toBeInTheDocument(); + }); +}); diff --git a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js index d800a32ef..3e8293804 100644 --- a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js +++ b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js @@ -11,12 +11,9 @@ import mockEstimates from '../../../../test/data/mock-estimates.json'; import mockState from '../../../../test/data/mock-state.json'; import { GasFeeContextProvider } from '../../../contexts/gasFee'; import configureStore from '../../../store/store'; -import { - hexWEIToDecETH, - decGWEIToHexWEI, -} from '../../../helpers/utils/conversions.util'; +import { decGWEIToHexWEI } from '../../../helpers/utils/conversions.util'; import InfoTooltip from '../../ui/info-tooltip'; - +import { hexWEIToDecETH } from '../../../../shared/lib/transactions-controller-utils'; import CancelSpeedupPopover from './cancel-speedup-popover'; const MAXFEEPERGAS_ABOVE_MOCK_MEDIUM_HEX = '0x174876e800'; diff --git a/ui/components/app/collectible-default-image/collectible-default-image.stories.js b/ui/components/app/collectible-default-image/collectible-default-image.stories.js index d4b7a2a69..8439f9ed6 100644 --- a/ui/components/app/collectible-default-image/collectible-default-image.stories.js +++ b/ui/components/app/collectible-default-image/collectible-default-image.stories.js @@ -30,13 +30,13 @@ export const DefaultStory = (args) => ( DefaultStory.storyName = 'Default'; -export const handleImageClick = (args) => ( +export const HandleImageClick = (args) => (
); -handleImageClick.args = { +HandleImageClick.args = { // eslint-disable-next-line no-alert handleImageClick: () => window.alert('CollectibleDefaultImage clicked!'), }; diff --git a/ui/components/app/collectible-details/collectible-details.js b/ui/components/app/collectible-details/collectible-details.js index 7ad4b2622..f824a3814 100644 --- a/ui/components/app/collectible-details/collectible-details.js +++ b/ui/components/app/collectible-details/collectible-details.js @@ -33,15 +33,7 @@ import { checkAndUpdateSingleCollectibleOwnershipStatus, removeAndIgnoreCollectible, } from '../../../store/actions'; -import { - GOERLI_CHAIN_ID, - KOVAN_CHAIN_ID, - MAINNET_CHAIN_ID, - POLYGON_CHAIN_ID, - RINKEBY_CHAIN_ID, - ROPSTEN_CHAIN_ID, - SEPOLIA_CHAIN_ID, -} from '../../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; import CollectibleOptions from '../collectible-options/collectible-options'; @@ -100,15 +92,15 @@ export default function CollectibleDetails({ collectible }) { const getOpenSeaLink = () => { switch (currentNetwork) { - case MAINNET_CHAIN_ID: + case CHAIN_IDS.MAINNET: return `https://opensea.io/assets/${address}/${tokenId}`; - case POLYGON_CHAIN_ID: + case CHAIN_IDS.POLYGON: return `https://opensea.io/assets/matic/${address}/${tokenId}`; - case GOERLI_CHAIN_ID: - case KOVAN_CHAIN_ID: - case ROPSTEN_CHAIN_ID: - case RINKEBY_CHAIN_ID: - case SEPOLIA_CHAIN_ID: + case CHAIN_IDS.GOERLI: + case CHAIN_IDS.KOVAN: + case CHAIN_IDS.ROPSTEN: + case CHAIN_IDS.RINKEBY: + case CHAIN_IDS.SEPOLIA: return `https://testnets.opensea.io/assets/${address}/${tokenId}`; default: return null; diff --git a/ui/components/app/confirm-page-container/confirm-page-container-container.test.js b/ui/components/app/confirm-page-container/confirm-page-container-container.test.js index 6ca95a83b..32a8a46ca 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-container.test.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-container.test.js @@ -1,14 +1,11 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import sinon from 'sinon'; -import { Provider } from 'react-redux'; -import SenderToRecipient from '../../ui/sender-to-recipient'; -import { mountWithRouter } from '../../../../test/lib/render-helpers'; -import Dialog from '../../ui/dialog'; -import ConfirmPageContainer, { - ConfirmPageContainerHeader, - ConfirmPageContainerNavigation, -} from '.'; +import { fireEvent, screen } from '@testing-library/react'; +import mockState from '../../../../test/data/mock-state.json'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import { shortenAddress } from '../../../helpers/utils/util'; +import ConfirmPageContainer from '.'; jest.mock('../../../store/actions', () => ({ disconnectGasFeeEstimatePoller: jest.fn(), @@ -19,35 +16,6 @@ jest.mock('../../../store/actions', () => ({ })); describe('Confirm Page Container Container Test', () => { - let wrapper; - - const mockStore = { - metamask: { - provider: { - type: 'test', - }, - preferences: { - useNativeCurrencyAsPrimaryCurrency: true, - }, - accounts: { - '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5': { - address: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', - balance: '0x03', - }, - }, - cachedBalances: {}, - selectedAddress: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', - addressBook: [], - chainId: 'test', - identities: [], - featureFlags: {}, - enableEIP1559V2NoticeDismissed: true, - tokenList: {}, - }, - }; - - const store = configureMockStore()(mockStore); - const props = { title: 'Title', fromAddress: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', @@ -60,119 +28,125 @@ describe('Confirm Page Container Container Test', () => { onSubmit: sinon.spy(), handleCloseEditGas: sinon.spy(), // Gas Popover - currentTransaction: {}, - contact: undefined, + currentTransaction: { + id: 8783053010106567, + time: 1656448479005, + status: 'unapproved', + metamaskNetworkId: '4', + originalGasEstimate: '0x5208', + userEditedGasLimit: false, + loadingDefaults: false, + dappSuggestedGasFees: null, + sendFlowHistory: [], + txParams: { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', + to: '0x7a1A4Ad9cc746a70ee58568466f7996dD0aCE4E8', + value: '0x0', + gas: '0x5208', + maxFeePerGas: '0x59682f0d', + maxPriorityFeePerGas: '0x59682f00', + }, + origin: 'testOrigin', + type: 'simpleSend', + userFeeLevel: 'medium', + defaultGasEstimates: { + estimateType: 'medium', + gas: '0x5208', + maxFeePerGas: '59682f0d', + maxPriorityFeePerGas: '59682f00', + }, + }, isOwnedAccount: false, + showAccountInHeader: true, + showEdit: true, + hideSenderToRecipient: false, + toName: '0x7a1...E4E8', }; + describe('Render and simulate button clicks', () => { + const store = configureMockStore()(mockState); + beforeEach(() => { + renderWithProvider(, store); + }); - beforeAll(() => { - wrapper = mountWithRouter( - - , - , - store, - ); + it('should render a confirm page container component', () => { + const pageContainer = screen.queryByTestId('page-container'); + expect(pageContainer).toBeInTheDocument(); + }); + + it('should render navigation', () => { + const navigationContainer = screen.queryByTestId('navigation-container'); + expect(navigationContainer).toBeInTheDocument(); + }); + + it('should render header', () => { + const headerContainer = screen.queryByTestId('header-container'); + expect(headerContainer).toBeInTheDocument(); + + const shortenedFromAddress = shortenAddress(props.fromAddress); + const headerAddress = screen.queryByTestId('header-address'); + expect(headerAddress).toHaveTextContent(shortenedFromAddress); + }); + + it('should render sender to recipient in header', () => { + const senderRecipient = screen.queryByTestId('sender-to-recipient'); + expect(senderRecipient).toBeInTheDocument(); + }); + it('should render recipient as address', () => { + const recipientName = screen.queryByText(shortenAddress(props.toAddress)); + expect(recipientName).toBeInTheDocument(); + }); + + it('should render add address to address book dialog', () => { + const newAccountDetectDialog = screen.queryByText( + /New address detected!/u, + ); + expect(newAccountDetectDialog).toBeInTheDocument(); + }); + + it('should simulate click reject button', () => { + const rejectButton = screen.getByTestId('page-container-footer-cancel'); + fireEvent.click(rejectButton); + expect(props.onCancel.calledOnce).toStrictEqual(true); + }); + + it('should simulate click submit button', () => { + const confirmButton = screen.getByTestId('page-container-footer-next'); + fireEvent.click(confirmButton); + expect(props.onSubmit.calledOnce).toStrictEqual(true); + }); }); - it('should render a confirm page container component', () => { - const pageContainer = wrapper.find('.page-container'); - expect(pageContainer).toHaveLength(1); - expect(pageContainer.getElements()[0].props.className).toStrictEqual( - 'page-container', - ); - }); + describe('Contact/AddressBook name should appear in recipient header', () => { + it('should not show add to address dialog if recipient is in contact list and should display contact name', () => { + const addressBookName = 'test save name'; - it('should render navigation', () => { - expect(wrapper.find(ConfirmPageContainerNavigation)).toHaveLength(1); - }); + const addressBook = { + '0x4': { + '0x7a1A4Ad9cc746a70ee58568466f7996dD0aCE4E8': { + address: '0x7a1A4Ad9cc746a70ee58568466f7996dD0aCE4E8', + chainId: '0x4', + isEns: false, + memo: '', + name: addressBookName, + }, + }, + }; - it('should render header', () => { - expect(wrapper.find(ConfirmPageContainerHeader)).toHaveLength(1); - expect( - wrapper.find(ConfirmPageContainerHeader).getElements()[0].props - .accountAddress, - ).toStrictEqual('0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5'); - }); + mockState.metamask.addressBook = addressBook; - it('should render sender to recipient in header', () => { - expect(wrapper.find(SenderToRecipient)).toHaveLength(1); - expect( - wrapper.find(SenderToRecipient).getElements()[0].props.senderAddress, - ).toStrictEqual('0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5'); - expect( - wrapper.find(SenderToRecipient).getElements()[0].props.recipientAddress, - ).toStrictEqual('0x7a1A4Ad9cc746a70ee58568466f7996dD0aCE4E8'); - }); + const store = configureMockStore()(mockState); - it('should render recipient as address', () => { - const recipientWithAddress = wrapper.find( - '.sender-to-recipient__party--recipient-with-address', - ); - expect(recipientWithAddress).toHaveLength(1); + renderWithProvider(, store); - expect(wrapper.find('.sender-to-recipient__name')).toHaveLength(2); - }); + // Does not display new address dialog banner + const newAccountDetectDialog = screen.queryByText( + /New address detected!/u, + ); + expect(newAccountDetectDialog).not.toBeInTheDocument(); - it('should render add address to address book dialog', () => { - expect(wrapper.find(Dialog)).toHaveLength(1); - expect(wrapper.find(Dialog).getElements()[0].props.children).toStrictEqual( - 'newAccountDetectedDialogMessage', - ); - }); - - it('should not show add to address dialog if contact is not undefined', () => { - props.contact = { - address: '0x7a1A4Ad9cc746a70ee58568466f7996dD0aCE4E8', - name: 'test saved name', - isEns: false, - chainId: 'test', - }; - - const wrapper2 = mountWithRouter( - - , - , - store, - ); - - expect(wrapper2.find(Dialog)).toHaveLength(0); - }); - - it('should render recipient as name', () => { - const wrapper2 = mountWithRouter( - - , - , - store, - ); - - const recipientWithAddress = wrapper2.find( - '.sender-to-recipient__party--recipient-with-address', - ); - expect(recipientWithAddress).toHaveLength(1); - - expect(wrapper.find('.sender-to-recipient__name')).toHaveLength(2); - }); - - it('should simulate click reject button', () => { - expect(wrapper.find('button.page-container__footer-button')).toHaveLength( - 2, - ); - wrapper - .find('button.page-container__footer-button') - .first() - .simulate('click'); - expect(props.onCancel.calledOnce).toStrictEqual(true); - }); - - it('should simulate click submit button', () => { - expect(wrapper.find('button.page-container__footer-button')).toHaveLength( - 2, - ); - wrapper - .find('button.page-container__footer-button') - .at(1) - .simulate('click'); - expect(props.onSubmit.calledOnce).toStrictEqual(true); + // Shows contact/addressbook name + const contactName = screen.queryByText(addressBookName); + expect(contactName).toBeInTheDocument(); + }); }); }); diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index b09b534ed..be48015a5 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -23,6 +23,9 @@ export default class ConfirmPageContainerContent extends Component { dataComponent: PropTypes.node, dataHexComponent: PropTypes.node, detailsComponent: PropTypes.node, + ///: BEGIN:ONLY_INCLUDE_IN(flask) + insightComponent: PropTypes.node, + ///: END:ONLY_INCLUDE_IN errorKey: PropTypes.string, errorMessage: PropTypes.string, hideSubtitle: PropTypes.bool, @@ -59,15 +62,37 @@ export default class ConfirmPageContainerContent extends Component { renderContent() { const { detailsComponent, dataComponent } = this.props; + ///: BEGIN:ONLY_INCLUDE_IN(flask) + const { insightComponent } = this.props; + + if (insightComponent && (detailsComponent || dataComponent)) { + return this.renderTabs(); + } + ///: END:ONLY_INCLUDE_IN + if (detailsComponent && dataComponent) { return this.renderTabs(); } - return detailsComponent || dataComponent; + + return ( + detailsComponent || + ///: BEGIN:ONLY_INCLUDE_IN(flask) + insightComponent || + ///: END:ONLY_INCLUDE_IN + dataComponent + ); } renderTabs() { const { t } = this.context; - const { detailsComponent, dataComponent, dataHexComponent } = this.props; + const { + detailsComponent, + dataComponent, + dataHexComponent, + ///: BEGIN:ONLY_INCLUDE_IN(flask) + insightComponent, + ///: END:ONLY_INCLUDE_IN + } = this.props; return ( @@ -88,6 +113,12 @@ export default class ConfirmPageContainerContent extends Component { {dataHexComponent} )} + + { + ///: BEGIN:ONLY_INCLUDE_IN(flask) + insightComponent + ///: END:ONLY_INCLUDE_IN + } ); } diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/index.scss b/ui/components/app/confirm-page-container/confirm-page-container-content/index.scss index 37d104c80..92395fbb1 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/index.scss +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/index.scss @@ -82,12 +82,28 @@ color: var(--color-text-alternative); text-transform: uppercase; - margin: 0 8px; & button { font-size: unset; color: var(--color-text-alternative); text-transform: uppercase; + max-width: 170px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + & .dropdown__select { + color: var(--color-text-alternative); + text-transform: uppercase; + + option { + text-transform: none; + } + } + + & .dropdown__icon-caret-down { + top: 40%; } } diff --git a/ui/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js b/ui/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js index eb33888e1..1477e309b 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js @@ -29,14 +29,20 @@ export default function ConfirmPageContainerHeader({ return children; } return ( -
+
{showAccountInHeader ? (
-
+
{shortenAddress(accountAddress)}
diff --git a/ui/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js b/ui/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js index 6c7bb981b..06b944a6e 100755 --- a/ui/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js @@ -24,6 +24,7 @@ const ConfirmPageContainerNavigation = (props) => { >
-
+
- {t('confirmPageDialogSetApprovalForAll')} + {/* + TODO: https://github.com/MetaMask/metamask-extension/issues/15745 + style={{ fontWeight: 'bold' }} because reset.scss removes font-weight from b. We should fix this. + */} + {t('confirmPageDialogSetApprovalForAll', [ + + {t('confirmPageDialogSetApprovalForAllPlaceholder1')} + , + + {t('confirmPageDialogSetApprovalForAllPlaceholder2')} + , + ])} )} {contentComponent && ( diff --git a/ui/components/app/confirm-page-container/flask/index.js b/ui/components/app/confirm-page-container/flask/index.js new file mode 100644 index 000000000..7fa665cf7 --- /dev/null +++ b/ui/components/app/confirm-page-container/flask/index.js @@ -0,0 +1 @@ +export { SnapInsight } from './snap-insight'; diff --git a/ui/components/app/confirm-page-container/flask/index.scss b/ui/components/app/confirm-page-container/flask/index.scss new file mode 100644 index 000000000..cd027f79c --- /dev/null +++ b/ui/components/app/confirm-page-container/flask/index.scss @@ -0,0 +1,3 @@ +.snap-insight { + word-wrap: break-word; +} diff --git a/ui/components/app/confirm-page-container/flask/snap-insight.js b/ui/components/app/confirm-page-container/flask/snap-insight.js new file mode 100644 index 000000000..1567763dd --- /dev/null +++ b/ui/components/app/confirm-page-container/flask/snap-insight.js @@ -0,0 +1,111 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import Preloader from '../../../ui/icon/preloader/preloader-icon.component'; +import Typography from '../../../ui/typography/typography'; +import { + ALIGN_ITEMS, + COLORS, + FLEX_DIRECTION, + JUSTIFY_CONTENT, + TEXT_ALIGN, + TYPOGRAPHY, +} from '../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { useTransactionInsightSnap } from '../../../../hooks/flask/useTransactionInsightSnap'; +import SnapContentFooter from '../../flask/snap-content-footer/snap-content-footer'; +import Box from '../../../ui/box/box'; + +export const SnapInsight = ({ transaction, chainId, selectedSnap }) => { + const t = useI18nContext(); + const response = useTransactionInsightSnap({ + transaction, + chainId, + snapId: selectedSnap.id, + }); + + const data = response?.insights; + + const hasNoData = !data || !Object.keys(data).length; + + return ( + + {data ? ( + + {Object.keys(data).length ? ( + <> + + {Object.keys(data).map((key, i) => ( +
+ + {key} + + {data[key]} +
+ ))} +
+ + + ) : ( + + {t('snapsNoInsight')} + + )} +
+ ) : ( + <> + + + {t('snapsInsightLoading')} + + + )} +
+ ); +}; + +SnapInsight.propTypes = { + /* + * The transaction data object + */ + transaction: PropTypes.object, + /* + * CAIP2 Chain ID + */ + chainId: PropTypes.string, + /* + * The insight snap selected + */ + selectedSnap: PropTypes.object, +}; diff --git a/ui/components/app/confirm-page-container/index.js b/ui/components/app/confirm-page-container/index.js index 955ef1bb8..b216aba05 100644 --- a/ui/components/app/confirm-page-container/index.js +++ b/ui/components/app/confirm-page-container/index.js @@ -7,3 +7,6 @@ export { default as ConfirmPageContainerContent, ConfirmPageContainerSummary, } from './confirm-page-container-content'; +///: BEGIN:ONLY_INCLUDE_IN(flask) +export { SnapInsight } from './flask/snap-insight'; +///: END:ONLY_INCLUDE_IN diff --git a/ui/components/app/confirm-page-container/index.scss b/ui/components/app/confirm-page-container/index.scss index ca3e12aa7..ca1ac9fb1 100644 --- a/ui/components/app/confirm-page-container/index.scss +++ b/ui/components/app/confirm-page-container/index.scss @@ -2,6 +2,9 @@ @import 'confirm-page-container-header/index'; @import 'confirm-detail-row/index'; @import 'confirm-page-container-navigation/index'; +///: BEGIN:ONLY_INCLUDE_IN(flask) +@import 'flask/index'; +///: END:ONLY_INCLUDE_IN .confirm-page-container { &__dialog { diff --git a/ui/components/app/create-new-vault/create-new-vault.test.js b/ui/components/app/create-new-vault/create-new-vault.test.js new file mode 100644 index 000000000..6f01804e0 --- /dev/null +++ b/ui/components/app/create-new-vault/create-new-vault.test.js @@ -0,0 +1,31 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import { renderWithProvider } from '../../../../test/jest'; +import configureStore from '../../../store/store'; +import mockState from '../../../../test/data/mock-state.json'; +import CreateNewVault from './create-new-vault'; + +const store = configureStore({ + metamask: { + ...mockState.metamask, + }, +}); + +describe('CreateNewVault', () => { + it('renders CreateNewVault component and shows Secret Recovery Phrase text', () => { + renderWithProvider(, store); + expect(screen.getByText('Secret Recovery Phrase')).toBeInTheDocument(); + }); + + it('renders CreateNewVault component and shows You can paste... text', () => { + renderWithProvider( + , + store, + ); + expect( + screen.getByText( + 'You can paste your entire secret recovery phrase into any field', + ), + ).toBeInTheDocument(); + }); +}); diff --git a/ui/components/app/deposit-popover/deposit-popover.js b/ui/components/app/deposit-popover/deposit-popover.js new file mode 100644 index 000000000..0a0a814ba --- /dev/null +++ b/ui/components/app/deposit-popover/deposit-popover.js @@ -0,0 +1,221 @@ +import PropTypes from 'prop-types'; +import React, { useContext } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; + +import { I18nContext } from '../../../contexts/i18n'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { + NETWORK_TO_NAME_MAP, + BUYABLE_CHAINS_MAP, +} from '../../../../shared/constants/network'; +import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics'; + +import LogoMoonPay from '../../ui/logo/logo-moonpay'; +import LogoWyre from '../../ui/logo/logo-wyre'; +import LogoTransak from '../../ui/logo/logo-transak'; +import LogoCoinbasePay from '../../ui/logo/logo-coinbasepay'; +import LogoDepositEth from '../../ui/logo/logo-deposit-eth'; +import Popover from '../../ui/popover'; + +import { buy, showModal, hideWarning } from '../../../store/actions'; +import { + getIsTestnet, + getCurrentChainId, + getSelectedAddress, + getIsBuyableTransakChain, + getIsBuyableMoonPayChain, + getIsBuyableWyreChain, + getIsBuyableCoinbasePayChain, + getIsBuyableCoinbasePayToken, + getIsBuyableTransakToken, +} from '../../../selectors/selectors'; + +import OnRampItem from './on-ramp-item'; + +const DepositPopover = ({ onClose, token }) => { + const isTokenDeposit = Boolean(token); + + const t = useContext(I18nContext); + const trackEvent = useContext(MetaMetricsContext); + const dispatch = useDispatch(); + + const chainId = useSelector(getCurrentChainId); + const isTestnet = useSelector(getIsTestnet); + const address = useSelector(getSelectedAddress); + const isBuyableTransakChain = useSelector(getIsBuyableTransakChain); + const isBuyableMoonPayChain = useSelector(getIsBuyableMoonPayChain); + const isBuyableWyreChain = useSelector(getIsBuyableWyreChain); + const isBuyableCoinbasePayChain = useSelector(getIsBuyableCoinbasePayChain); + + const isTokenBuyableCoinbasePay = useSelector((state) => + getIsBuyableCoinbasePayToken(state, token?.symbol), + ); + const isTokenBuyableTransak = useSelector((state) => + getIsBuyableTransakToken(state, token?.symbol), + ); + + const networkName = NETWORK_TO_NAME_MAP[chainId]; + const symbol = token + ? token.symbol + : BUYABLE_CHAINS_MAP[chainId].nativeCurrency; + + const showAccountDetailModal = () => { + dispatch(showModal({ name: 'ACCOUNT_DETAILS' })); + }; + const hideWarningMessage = () => { + dispatch(hideWarning()); + }; + + const toCoinbasePay = () => { + dispatch( + buy({ service: 'coinbase', address, chainId, symbol: token?.symbol }), + ); + }; + const toTransak = () => { + dispatch( + buy({ service: 'transak', address, chainId, symbol: token?.symbol }), + ); + }; + const toMoonPay = () => { + dispatch(buy({ service: 'moonpay', address, chainId })); + }; + const toWyre = () => { + dispatch(buy({ service: 'wyre', address, chainId })); + }; + const toFaucet = () => dispatch(buy({ chainId })); + + const goToAccountDetailsModal = () => { + hideWarningMessage(); + showAccountDetailModal(); + onClose(); + }; + + return ( + + } + title={t('buyCryptoWithCoinbasePay', [symbol])} + text={t('buyCryptoWithCoinbasePayDescription', [symbol])} + buttonLabel={t('continueToCoinbasePay')} + onButtonClick={() => { + trackEvent({ + category: EVENT.CATEGORIES.ACCOUNTS, + event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED, + properties: { + onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.COINBASE, + }, + }); + toCoinbasePay(); + }} + hide={ + isTokenDeposit + ? !isBuyableCoinbasePayChain || !isTokenBuyableCoinbasePay + : !isBuyableCoinbasePayChain + } + /> + } + title={t('buyCryptoWithTransak', [symbol])} + text={t('buyCryptoWithTransakDescription', [symbol])} + buttonLabel={t('continueToTransak')} + onButtonClick={() => { + trackEvent({ + category: EVENT.CATEGORIES.ACCOUNTS, + event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED, + properties: { + onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.TRANSAK, + }, + }); + toTransak(); + }} + hide={ + isTokenDeposit + ? !isBuyableTransakChain || !isTokenBuyableTransak + : !isBuyableTransakChain + } + /> + } + title={t('buyCryptoWithMoonPay', [symbol])} + text={t('buyCryptoWithMoonPayDescription', [symbol])} + buttonLabel={t('continueToMoonPay')} + onButtonClick={() => { + trackEvent({ + category: EVENT.CATEGORIES.ACCOUNTS, + event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED, + properties: { + onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.MOONPAY, + }, + }); + toMoonPay(); + }} + hide={isTokenDeposit || !isBuyableMoonPayChain} + /> + + } + title={t('buyWithWyre', [symbol])} + text={t('buyWithWyreDescription', [symbol])} + buttonLabel={t('continueToWyre')} + onButtonClick={() => { + trackEvent({ + category: EVENT.CATEGORIES.ACCOUNTS, + event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED, + properties: { + onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.WYRE, + }, + }); + toWyre(); + }} + hide={isTokenDeposit || !isBuyableWyreChain} + /> + + } + title={t('directDepositCrypto', [symbol])} + text={t('directDepositCryptoExplainer', [symbol])} + buttonLabel={t('viewAccount')} + onButtonClick={() => { + trackEvent({ + category: EVENT.CATEGORIES.ACCOUNTS, + event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED, + properties: { + onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.SELF_DEPOSIT, + }, + }); + goToAccountDetailsModal(); + }} + hide={isTokenDeposit || !isBuyableWyreChain} + /> + + {networkName && ( + } + title={t('testFaucet')} + text={t('getEtherFromFaucet', [networkName])} + buttonLabel={t('getEther')} + onButtonClick={() => toFaucet()} + hide={!isTestnet} + /> + )} + + ); +}; + +DepositPopover.propTypes = { + onClose: PropTypes.func.isRequired, + token: PropTypes.shape({ + address: PropTypes.string.isRequired, + decimals: PropTypes.number, + symbol: PropTypes.string, + image: PropTypes.string, + aggregators: PropTypes.array, + isERC721: PropTypes.bool, + }), +}; + +export default DepositPopover; diff --git a/ui/components/app/deposit-popover/index.js b/ui/components/app/deposit-popover/index.js new file mode 100644 index 000000000..63c24927f --- /dev/null +++ b/ui/components/app/deposit-popover/index.js @@ -0,0 +1 @@ +export { default } from './deposit-popover'; diff --git a/ui/components/app/deposit-popover/on-ramp-item.js b/ui/components/app/deposit-popover/on-ramp-item.js new file mode 100644 index 000000000..505253592 --- /dev/null +++ b/ui/components/app/deposit-popover/on-ramp-item.js @@ -0,0 +1,67 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Button from '../../ui/button'; +import Box from '../../ui/box'; +import Typography from '../../ui/typography'; +import { COLORS, FRACTIONS } from '../../../helpers/constants/design-system'; + +const OnRampItem = ({ + logo, + title, + text, + buttonLabel, + onButtonClick, + hide = false, +}) => { + if (hide) { + return null; + } + return ( + + + {logo} + + {title} + + + {text} + + + + + + + ); +}; + +OnRampItem.propTypes = { + logo: PropTypes.node.isRequired, + title: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + buttonLabel: PropTypes.string.isRequired, + onButtonClick: PropTypes.func.isRequired, + hide: PropTypes.bool, +}; + +export default OnRampItem; diff --git a/ui/components/app/dropdowns/network-dropdown.js b/ui/components/app/dropdowns/network-dropdown.js index db35c3009..abb1eca04 100644 --- a/ui/components/app/dropdowns/network-dropdown.js +++ b/ui/components/app/dropdowns/network-dropdown.js @@ -7,8 +7,8 @@ import Button from '../../ui/button'; import * as actions from '../../../store/actions'; import { openAlert as displayInvalidCustomNetworkAlert } from '../../../ducks/alerts/invalid-custom-network'; import { - NETWORK_TYPE_RPC, LOCALHOST_RPC_URL, + NETWORK_TYPES, } from '../../../../shared/constants/network'; import { isPrefixedFormattedHexString } from '../../../../shared/modules/network.utils'; @@ -152,7 +152,7 @@ class NetworkDropdown extends Component { return reversedRpcListDetail.map((entry) => { const { rpcUrl, chainId, ticker = 'ETH', nickname = '' } = entry; const isCurrentRpcTarget = - provider.type === NETWORK_TYPE_RPC && rpcUrl === provider.rpcUrl; + provider.type === NETWORK_TYPES.RPC && rpcUrl === provider.rpcUrl; return ( { diff --git a/ui/components/app/edit-gas-popover/edit-gas-popover.component.js b/ui/components/app/edit-gas-popover/edit-gas-popover.component.js index cf4c21301..92cdd1b82 100644 --- a/ui/components/app/edit-gas-popover/edit-gas-popover.component.js +++ b/ui/components/app/edit-gas-popover/edit-gas-popover.component.js @@ -11,11 +11,7 @@ import { CUSTOM_GAS_ESTIMATE, } from '../../../../shared/constants/gas'; -import { - decGWEIToHexWEI, - decimalToHex, - hexToDecimal, -} from '../../../helpers/utils/conversions.util'; +import { decGWEIToHexWEI } from '../../../helpers/utils/conversions.util'; import Popover from '../../ui/popover'; import Button from '../../ui/button'; @@ -37,6 +33,8 @@ import LoadingHeartBeat from '../../ui/loading-heartbeat'; import { checkNetworkAndAccountSupports1559 } from '../../../selectors'; import { useIncrementedGasFees } from '../../../hooks/useIncrementedGasFees'; import { isLegacyTransaction } from '../../../helpers/utils/transactions.util'; +import { hexToDecimal } from '../../../../shared/lib/metamask-controller-utils'; +import { decimalToHex } from '../../../../shared/lib/transactions-controller-utils'; export default function EditGasPopover({ popoverTitle = '', diff --git a/ui/components/app/flask/experimental-area/index.scss b/ui/components/app/flask/experimental-area/index.scss index 44a348240..34b4f84a5 100644 --- a/ui/components/app/flask/experimental-area/index.scss +++ b/ui/components/app/flask/experimental-area/index.scss @@ -28,10 +28,6 @@ padding: 16px; max-width: 670px; - - b { - font-weight: bold; - } } ul { diff --git a/ui/components/app/flask/snap-content-footer/index.js b/ui/components/app/flask/snap-content-footer/index.js new file mode 100644 index 000000000..9eccd24ee --- /dev/null +++ b/ui/components/app/flask/snap-content-footer/index.js @@ -0,0 +1 @@ +export { default } from './snap-content-footer'; diff --git a/ui/components/app/flask/snap-content-footer/index.scss b/ui/components/app/flask/snap-content-footer/index.scss new file mode 100644 index 000000000..972d3328e --- /dev/null +++ b/ui/components/app/flask/snap-content-footer/index.scss @@ -0,0 +1,13 @@ +.snap-content-footer { + i { + color: var(--color-icon-muted); + padding-right: 4px; + } + + .button { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100px; + } +} diff --git a/ui/components/app/flask/snap-content-footer/snap-content-footer.js b/ui/components/app/flask/snap-content-footer/snap-content-footer.js new file mode 100644 index 000000000..6b0371d9e --- /dev/null +++ b/ui/components/app/flask/snap-content-footer/snap-content-footer.js @@ -0,0 +1,56 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { useHistory } from 'react-router-dom'; + +import Typography from '../../../ui/typography/typography'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { SNAPS_VIEW_ROUTE } from '../../../../helpers/constants/routes'; +import { + COLORS, + TYPOGRAPHY, + JUSTIFY_CONTENT, + ALIGN_ITEMS, +} from '../../../../helpers/constants/design-system'; +import Button from '../../../ui/button'; +import Box from '../../../ui/box/box'; + +export default function SnapContentFooter({ snapName, snapId }) { + const t = useI18nContext(); + const history = useHistory(); + + const handleNameClick = (e) => { + e.stopPropagation(); + history.push(`${SNAPS_VIEW_ROUTE}/${encodeURIComponent(snapId)}`); + }; + // TODO: add truncation to the snap name, need to pick a character length at which to cut off + return ( + + + + {t('snapContent', [ + , + ])} + + + ); +} + +SnapContentFooter.propTypes = { + /** + * The name of the snap who's content is displayed + */ + snapName: PropTypes.string, + /** + * The id of the snap + */ + snapId: PropTypes.string, +}; diff --git a/ui/components/app/flask/snap-content-footer/snap-content-footer.stories.js b/ui/components/app/flask/snap-content-footer/snap-content-footer.stories.js new file mode 100644 index 000000000..dbc7a12bf --- /dev/null +++ b/ui/components/app/flask/snap-content-footer/snap-content-footer.stories.js @@ -0,0 +1,17 @@ +import React from 'react'; + +import SnapContentFooter from '.'; + +export default { + title: 'Components/App/Flask/SnapContentFooter', + id: __filename, + component: SnapContentFooter, + args: { + snapName: 'Test Snap', + snapId: 'local:test-snap', + }, +}; + +export const DefaultStory = (args) => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js b/ui/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js index eca645f55..a800eefb3 100644 --- a/ui/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js +++ b/ui/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js @@ -1,11 +1,11 @@ import { connect } from 'react-redux'; -import { - decGWEIToHexWEI, - decimalToHex, - hexWEIToDecGWEI, -} from '../../../../helpers/utils/conversions.util'; +import { decGWEIToHexWEI } from '../../../../helpers/utils/conversions.util'; import { getNetworkSupportsSettingGasFees } from '../../../../selectors/selectors'; import { MIN_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants'; +import { + decimalToHex, + hexWEIToDecGWEI, +} from '../../../../../shared/lib/transactions-controller-utils'; import AdvancedGasInputs from './advanced-gas-inputs.component'; function convertGasPriceForInputs(gasPriceInHexWEI) { diff --git a/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js index e7126b6d3..ba80837b6 100644 --- a/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js +++ b/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js @@ -43,15 +43,11 @@ import { import { addHexes, subtractHexWEIsToDec, - hexWEIToDecGWEI, getValueFromWeiHex, sumHexWEIsToRenderableFiat, } from '../../../../helpers/utils/conversions.util'; import { formatETHFee } from '../../../../helpers/utils/formatters'; -import { - calcGasTotal, - isBalanceSufficient, -} from '../../../../pages/send/send.utils'; +import { isBalanceSufficient } from '../../../../pages/send/send.utils'; import { MIN_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants'; import { ASSET_TYPES, @@ -59,6 +55,10 @@ import { } from '../../../../../shared/constants/transaction'; import { GAS_LIMITS } from '../../../../../shared/constants/gas'; import { updateGasFees } from '../../../../ducks/metamask/metamask'; +import { + calcGasTotal, + hexWEIToDecGWEI, +} from '../../../../../shared/lib/transactions-controller-utils'; import GasModalPageContainer from './gas-modal-page-container.component'; const mapStateToProps = (state, ownProps) => { diff --git a/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.test.js b/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.test.js index 1e858c1a6..809932567 100644 --- a/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.test.js +++ b/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { screen, waitFor } from '@testing-library/react'; -import { MAINNET_CHAIN_ID } from '../../../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../../../shared/constants/network'; import { GasFeeContextProvider } from '../../../../contexts/gasFee'; import { renderWithProvider } from '../../../../../test/jest'; import configureStore from '../../../../store/store'; @@ -20,7 +20,7 @@ jest.mock('../../../../store/actions', () => ({ const render = () => { const store = configureStore({ metamask: { - provider: { chainId: MAINNET_CHAIN_ID }, + provider: { chainId: CHAIN_IDS.MAINNET }, cachedBalances: {}, accounts: { '0xAddress': { diff --git a/ui/components/app/loading-network-screen/loading-network-screen.container.js b/ui/components/app/loading-network-screen/loading-network-screen.container.js index 2f1e711ce..e243c011e 100644 --- a/ui/components/app/loading-network-screen/loading-network-screen.container.js +++ b/ui/components/app/loading-network-screen/loading-network-screen.container.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { NETWORK_TYPE_RPC } from '../../../../shared/constants/network'; +import { NETWORK_TYPES } from '../../../../shared/constants/network'; import * as actions from '../../../store/actions'; import { getNetworkIdentifier, isNetworkLoading } from '../../../selectors'; import LoadingNetworkScreen from './loading-network-screen.component'; @@ -10,7 +10,7 @@ const mapStateToProps = (state) => { const { rpcUrl, chainId, ticker, nickname, type } = provider; const setProviderArgs = - type === NETWORK_TYPE_RPC + type === NETWORK_TYPES.RPC ? [rpcUrl, chainId, ticker, nickname] : [provider.type]; diff --git a/ui/components/app/menu-bar/menu-bar.test.js b/ui/components/app/menu-bar/menu-bar.test.js index 4a958407a..d3a5a39a1 100644 --- a/ui/components/app/menu-bar/menu-bar.test.js +++ b/ui/components/app/menu-bar/menu-bar.test.js @@ -2,14 +2,14 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import { fireEvent, screen, waitFor } from '@testing-library/react'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; -import { ROPSTEN_CHAIN_ID } from '../../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; import MenuBar from './menu-bar'; const initState = { activeTab: {}, metamask: { provider: { - chainId: ROPSTEN_CHAIN_ID, + chainId: CHAIN_IDS.ROPSTEN, }, selectedAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', identities: { diff --git a/ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js b/ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js deleted file mode 100644 index 435ebf2a1..000000000 --- a/ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js +++ /dev/null @@ -1,243 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { - NETWORK_TO_NAME_MAP, - BUYABLE_CHAINS_MAP, -} from '../../../../../shared/constants/network'; -import { - EVENT, - EVENT_NAMES, -} from '../../../../../shared/constants/metametrics'; -import Button from '../../../ui/button'; -import LogoMoonPay from '../../../ui/logo/logo-moonpay'; -import LogoWyre from '../../../ui/logo/logo-wyre'; -import LogoTransak from '../../../ui/logo/logo-transak'; -import LogoCoinbasePay from '../../../ui/logo/logo-coinbasepay'; -import LogoDepositEth from '../../../ui/logo/logo-deposit-eth'; - -export default class DepositEtherModal extends Component { - static contextTypes = { - t: PropTypes.func, - trackEvent: PropTypes.func.isRequired, - }; - - static propTypes = { - chainId: PropTypes.string.isRequired, - isTestnet: PropTypes.bool.isRequired, - isBuyableTransakChain: PropTypes.bool.isRequired, - isBuyableMoonPayChain: PropTypes.bool.isRequired, - isBuyableWyreChain: PropTypes.bool.isRequired, - isBuyableCoinbasePayChain: PropTypes.bool.isRequired, - toWyre: PropTypes.func.isRequired, - toTransak: PropTypes.func.isRequired, - toMoonPay: PropTypes.func.isRequired, - toCoinbasePay: PropTypes.func.isRequired, - address: PropTypes.string.isRequired, - toFaucet: PropTypes.func.isRequired, - hideWarning: PropTypes.func.isRequired, - hideModal: PropTypes.func.isRequired, - showAccountDetailModal: PropTypes.func.isRequired, - }; - - goToAccountDetailsModal = () => { - this.props.hideWarning(); - this.props.hideModal(); - this.props.showAccountDetailModal(); - }; - - renderRow({ - logo, - title, - text, - buttonLabel, - onButtonClick, - hide, - className, - hideButton, - hideTitle, - onBackClick, - showBackButton, - }) { - if (hide) { - return null; - } - - return ( -
- {onBackClick && showBackButton && ( -
- -
- )} -
- {logo} -
-
- {!hideTitle && ( -
- {title} -
- )} -
- {text} -
-
- {!hideButton && ( -
- -
- )} -
- ); - } - - render() { - const { - chainId, - toWyre, - toTransak, - toMoonPay, - toCoinbasePay, - address, - toFaucet, - isTestnet, - isBuyableTransakChain, - isBuyableMoonPayChain, - isBuyableWyreChain, - isBuyableCoinbasePayChain, - } = this.props; - const { t } = this.context; - const networkName = NETWORK_TO_NAME_MAP[chainId]; - const symbol = BUYABLE_CHAINS_MAP[chainId].nativeCurrency; - - return ( -
-
-
- {t('depositCrypto', [symbol])} -
-
- {t('needCryptoInWallet', [symbol])} -
-
{ - this.props.hideWarning(); - this.props.hideModal(); - }} - /> -
-
-
- {this.renderRow({ - logo: , - title: t('buyCryptoWithCoinbasePay', [symbol]), - text: t('buyCryptoWithCoinbasePayDescription', [symbol]), - buttonLabel: t('continueToCoinbasePay'), - onButtonClick: () => { - this.context.trackEvent({ - category: EVENT.CATEGORIES.ACCOUNTS, - event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED, - properties: { - onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.COINBASE, - }, - }); - toCoinbasePay(address, chainId); - }, - hide: !isBuyableCoinbasePayChain, - })} - {this.renderRow({ - logo: , - title: t('buyCryptoWithTransak', [symbol]), - text: t('buyCryptoWithTransakDescription', [symbol]), - buttonLabel: t('continueToTransak'), - onButtonClick: () => { - this.context.trackEvent({ - category: EVENT.CATEGORIES.ACCOUNTS, - event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED, - properties: { - onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.TRANSAK, - }, - }); - toTransak(address, chainId); - }, - hide: !isBuyableTransakChain, - })} - {this.renderRow({ - logo: , - title: t('buyCryptoWithMoonPay', [symbol]), - text: t('buyCryptoWithMoonPayDescription', [symbol]), - buttonLabel: t('continueToMoonPay'), - onButtonClick: () => { - this.context.trackEvent({ - category: EVENT.CATEGORIES.ACCOUNTS, - event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED, - properties: { - onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.MOONPAY, - }, - }); - toMoonPay(address, chainId); - }, - hide: !isBuyableMoonPayChain, - })} - {this.renderRow({ - logo: , - title: t('buyWithWyre', [symbol]), - text: t('buyWithWyreDescription', [symbol]), - buttonLabel: t('continueToWyre'), - onButtonClick: () => { - this.context.trackEvent({ - category: EVENT.CATEGORIES.ACCOUNTS, - event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED, - properties: { - onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.WYRE, - }, - }); - toWyre(address, chainId); - }, - hide: !isBuyableWyreChain, - })} - {this.renderRow({ - logo: ( - - ), - title: t('directDepositCrypto', [symbol]), - text: t('directDepositCryptoExplainer', [symbol]), - buttonLabel: t('viewAccount'), - onButtonClick: () => { - this.context.trackEvent({ - category: EVENT.CATEGORIES.ACCOUNTS, - event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED, - properties: { - onramp_provider_type: - EVENT.ONRAMP_PROVIDER_TYPES.SELF_DEPOSIT, - }, - }); - this.goToAccountDetailsModal(); - }, - })} - {networkName && - this.renderRow({ - logo: , - title: t('testFaucet'), - text: t('getEtherFromFaucet', [networkName]), - buttonLabel: t('getEther'), - onButtonClick: () => toFaucet(chainId), - hide: !isTestnet, - })} -
-
-
- ); - } -} diff --git a/ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js b/ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js deleted file mode 100644 index 3b71d5740..000000000 --- a/ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js +++ /dev/null @@ -1,60 +0,0 @@ -import { connect } from 'react-redux'; -import { - buyEth, - hideModal, - showModal, - hideWarning, -} from '../../../../store/actions'; -import { - getIsTestnet, - getIsMainnet, - getCurrentChainId, - getSelectedAddress, - getIsBuyableTransakChain, - getIsBuyableMoonPayChain, - getIsBuyableWyreChain, - getIsBuyableCoinbasePayChain, -} from '../../../../selectors/selectors'; -import DepositEtherModal from './deposit-ether-modal.component'; - -function mapStateToProps(state) { - return { - chainId: getCurrentChainId(state), - isTestnet: getIsTestnet(state), - isMainnet: getIsMainnet(state), - address: getSelectedAddress(state), - isBuyableTransakChain: getIsBuyableTransakChain(state), - isBuyableMoonPayChain: getIsBuyableMoonPayChain(state), - isBuyableWyreChain: getIsBuyableWyreChain(state), - isBuyableCoinbasePayChain: getIsBuyableCoinbasePayChain(state), - }; -} - -function mapDispatchToProps(dispatch) { - return { - toWyre: (address, chainId) => { - dispatch(buyEth({ service: 'wyre', address, chainId })); - }, - toTransak: (address, chainId) => { - dispatch(buyEth({ service: 'transak', address, chainId })); - }, - toMoonPay: (address, chainId) => { - dispatch(buyEth({ service: 'moonpay', address, chainId })); - }, - toCoinbasePay: (address, chainId) => { - dispatch(buyEth({ service: 'coinbase', address, chainId })); - }, - hideModal: () => { - dispatch(hideModal()); - }, - hideWarning: () => { - dispatch(hideWarning()); - }, - showAccountDetailModal: () => { - dispatch(showModal({ name: 'ACCOUNT_DETAILS' })); - }, - toFaucet: (chainId) => dispatch(buyEth({ chainId })), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal); diff --git a/ui/components/app/modals/deposit-ether-modal/index.js b/ui/components/app/modals/deposit-ether-modal/index.js deleted file mode 100644 index 02f355cb0..000000000 --- a/ui/components/app/modals/deposit-ether-modal/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './deposit-ether-modal.container'; diff --git a/ui/components/app/modals/deposit-ether-modal/index.scss b/ui/components/app/modals/deposit-ether-modal/index.scss deleted file mode 100644 index 9c366d0b8..000000000 --- a/ui/components/app/modals/deposit-ether-modal/index.scss +++ /dev/null @@ -1,118 +0,0 @@ -.deposit-ether-modal { - border-radius: 8px; - display: flex; - flex-flow: column; - height: 100%; - - &__buy-rows { - width: 100%; - padding: 0 30px; - display: flex; - flex-flow: column nowrap; - flex: 1; - align-items: center; - - @include screen-sm-max { - height: 0; - } - } - - &__logo { - max-height: 40px; - background-repeat: no-repeat; - background-size: contain; - background-position: center; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - - &--lg { - max-height: 60px; - } - - @include screen-sm-min { - height: 60px; - } - } - - &__buy-row { - border-bottom: 1px solid var(--color-border-default); - display: flex; - justify-content: space-between; - align-items: center; - flex: 1 0 auto; - padding: 30px 0 20px; - min-height: 170px; - - @include screen-sm-max { - min-height: 270px; - flex-flow: column; - justify-content: flex-start; - } - - &__back { - position: absolute; - top: 10px; - left: 0; - } - - &__logo-container { - display: flex; - justify-content: center; - flex: 0 0 auto; - padding: 0 20px; - - @include screen-sm-min { - width: 12rem; - } - - @include screen-sm-max { - width: 100%; - max-height: 6rem; - padding-bottom: 20px; - } - } - - &__right { - display: flex; - } - - &__description { - color: var(--color-text-alternative); - padding-bottom: 20px; - align-self: flex-start; - - @include screen-sm-min { - width: 15rem; - } - - &__title { - @include H4; - } - - &__text { - @include H6; - - margin-top: 7px; - } - } - - &__button { - display: flex; - justify-content: flex-end; - - @include screen-sm-min { - min-width: 300px; - } - } - } - - &__buy-row:last-of-type { - border-bottom: 0; - } - - &__deposit-button { - width: 257px !important; - } -} diff --git a/ui/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/components/app/modals/edit-approval-permission/edit-approval-permission.component.js index c95e9460a..bdc9acce5 100644 --- a/ui/components/app/modals/edit-approval-permission/edit-approval-permission.component.js +++ b/ui/components/app/modals/edit-approval-permission/edit-approval-permission.component.js @@ -6,7 +6,7 @@ import BigNumber from 'bignumber.js'; import Modal from '../../modal'; import Identicon from '../../../ui/identicon'; import TextField from '../../../ui/text-field'; -import { calcTokenAmount } from '../../../../helpers/utils/token-util'; +import { calcTokenAmount } from '../../../../../shared/lib/transactions-controller-utils'; const MAX_UNSIGNED_256_INT = new BigNumber(2).pow(256).minus(1).toString(10); diff --git a/ui/components/app/modals/index.scss b/ui/components/app/modals/index.scss index 49e0063ab..f4467b153 100644 --- a/ui/components/app/modals/index.scss +++ b/ui/components/app/modals/index.scss @@ -2,7 +2,6 @@ @import 'account-modal-container/index'; @import 'cancel-transaction/index'; @import 'confirm-remove-account/index'; -@import 'deposit-ether-modal/index'; @import 'edit-approval-permission/index'; @import 'export-private-key-modal/index'; @import 'hide-token-confirmation-modal/index'; diff --git a/ui/components/app/modals/modal.js b/ui/components/app/modals/modal.js index 4df937f2d..f8442e2f7 100644 --- a/ui/components/app/modals/modal.js +++ b/ui/components/app/modals/modal.js @@ -10,7 +10,6 @@ import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; // Modal Components import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container'; -import DepositEtherModal from './deposit-ether-modal'; import AccountDetailsModal from './account-details-modal'; import ExportPrivateKeyModal from './export-private-key-modal'; import HideTokenConfirmationModal from './hide-token-confirmation-modal'; @@ -79,39 +78,6 @@ const accountModalStyle = { }; const MODALS = { - DEPOSIT_ETHER: { - contents: , - onHide: (props) => props.hideWarning(), - mobileModalStyle: { - width: '100%', - height: '100%', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - boxShadow: 'var(--shadow-size-sm) var(--color-shadow-default)', - top: '0', - display: 'flex', - }, - laptopModalStyle: { - width: 'initial', - maxWidth: '850px', - top: 'calc(10% + 10px)', - left: '0', - right: '0', - margin: '0 auto', - boxShadow: 'var(--shadow-size-sm) var(--color-shadow-default)', - borderRadius: '7px', - transform: 'none', - height: 'calc(80% - 20px)', - overflowY: 'hidden', - }, - contentStyle: { - borderRadius: '7px', - height: '100%', - }, - }, - NEW_ACCOUNT: { contents: , mobileModalStyle: { diff --git a/ui/components/app/network-display/network-display.js b/ui/components/app/network-display/network-display.js index 63af71f90..47e59e4a6 100644 --- a/ui/components/app/network-display/network-display.js +++ b/ui/components/app/network-display/network-display.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import { useSelector } from 'react-redux'; import { - NETWORK_TYPE_RPC, - NETWORK_TYPE_TO_ID_MAP, + NETWORK_TYPES, + BUILT_IN_NETWORKS, } from '../../../../shared/constants/network'; import LoadingIndicator from '../../ui/loading-indicator'; @@ -48,12 +48,14 @@ export default function NetworkDisplay({ > { const targetNetworkArr = [ - ...Object.keys(NETWORK_TYPE_TO_ID_MAP), - NETWORK_TYPE_RPC, + ...Object.keys(BUILT_IN_NETWORKS), + NETWORK_TYPES.RPC, ]; return ( <> @@ -72,8 +72,8 @@ export const TargetNetwork = (args) => { export const DisplayOnly = (args) => { const targetNetworkArr = [ - ...Object.keys(NETWORK_TYPE_TO_ID_MAP), - NETWORK_TYPE_RPC, + ...Object.keys(BUILT_IN_NETWORKS), + NETWORK_TYPES.RPC, ]; return ( <> diff --git a/ui/components/app/transaction-activity-log/index.scss b/ui/components/app/transaction-activity-log/index.scss index caa925d4c..e5544829c 100644 --- a/ui/components/app/transaction-activity-log/index.scss +++ b/ui/components/app/transaction-activity-log/index.scss @@ -79,8 +79,4 @@ cursor: pointer; color: var(--color-primary-default); } - - b { - font-weight: 500; - } } diff --git a/ui/components/app/transaction-activity-log/transaction-activity-log.util.test.js b/ui/components/app/transaction-activity-log/transaction-activity-log.util.test.js index 2cf586a0e..a6fd624b2 100644 --- a/ui/components/app/transaction-activity-log/transaction-activity-log.util.test.js +++ b/ui/components/app/transaction-activity-log/transaction-activity-log.util.test.js @@ -1,8 +1,5 @@ import { GAS_LIMITS } from '../../../../shared/constants/gas'; -import { - ROPSTEN_CHAIN_ID, - ROPSTEN_NETWORK_ID, -} from '../../../../shared/constants/network'; +import { CHAIN_IDS, NETWORK_IDS } from '../../../../shared/constants/network'; import { TRANSACTION_STATUSES, TRANSACTION_TYPES, @@ -27,8 +24,8 @@ describe('TransactionActivityLog utils', () => { id: 6400627574331058, time: 1543958845581, status: TRANSACTION_STATUSES.UNAPPROVED, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, loadingDefaults: true, txParams: { from: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', @@ -75,8 +72,8 @@ describe('TransactionActivityLog utils', () => { ], id: 6400627574331058, loadingDefaults: false, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, status: TRANSACTION_STATUSES.DROPPED, submittedTime: 1543958848135, time: 1543958845581, @@ -97,8 +94,8 @@ describe('TransactionActivityLog utils', () => { id: 6400627574331060, time: 1543958857697, status: TRANSACTION_STATUSES.UNAPPROVED, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, loadingDefaults: false, txParams: { from: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', @@ -168,8 +165,8 @@ describe('TransactionActivityLog utils', () => { id: 6400627574331060, lastGasPrice: '0x4190ab00', loadingDefaults: false, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, status: TRANSACTION_STATUSES.CONFIRMED, submittedTime: 1543958860054, time: 1543958857697, @@ -191,8 +188,8 @@ describe('TransactionActivityLog utils', () => { const expected = [ { id: 6400627574331058, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, hash: '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3', eventKey: 'transactionCreated', timestamp: 1543958845581, @@ -200,8 +197,8 @@ describe('TransactionActivityLog utils', () => { }, { id: 6400627574331058, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, hash: '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3', eventKey: 'transactionSubmitted', timestamp: 1543958848147, @@ -209,8 +206,8 @@ describe('TransactionActivityLog utils', () => { }, { id: 6400627574331060, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, hash: '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', eventKey: 'transactionResubmitted', timestamp: 1543958860061, @@ -218,8 +215,8 @@ describe('TransactionActivityLog utils', () => { }, { id: 6400627574331060, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, hash: '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', eventKey: 'transactionConfirmed', timestamp: 1543958897165, @@ -256,8 +253,8 @@ describe('TransactionActivityLog utils', () => { { id: 5559712943815343, loadingDefaults: true, - metamaskNetworkId: ROPSTEN_NETWORK_ID, - chainId: ROPSTEN_CHAIN_ID, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, + chainId: CHAIN_IDS.ROPSTEN, status: TRANSACTION_STATUSES.UNAPPROVED, time: 1535507561452, txParams: { @@ -397,8 +394,8 @@ describe('TransactionActivityLog utils', () => { value: '0x2386f26fc10000', }, hash: '0xabc', - chainId: ROPSTEN_CHAIN_ID, - metamaskNetworkId: ROPSTEN_NETWORK_ID, + chainId: CHAIN_IDS.ROPSTEN, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, }; const expectedResult = [ @@ -408,8 +405,8 @@ describe('TransactionActivityLog utils', () => { value: '0x2386f26fc10000', id: 1, hash: '0xabc', - chainId: ROPSTEN_CHAIN_ID, - metamaskNetworkId: ROPSTEN_NETWORK_ID, + chainId: CHAIN_IDS.ROPSTEN, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, }, { eventKey: 'transactionSubmitted', @@ -417,8 +414,8 @@ describe('TransactionActivityLog utils', () => { value: '0x2632e314a000', id: 1, hash: '0xabc', - chainId: ROPSTEN_CHAIN_ID, - metamaskNetworkId: ROPSTEN_NETWORK_ID, + chainId: CHAIN_IDS.ROPSTEN, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, }, { eventKey: 'transactionConfirmed', @@ -426,8 +423,8 @@ describe('TransactionActivityLog utils', () => { value: '0x2632e314a000', id: 1, hash: '0xabc', - chainId: ROPSTEN_CHAIN_ID, - metamaskNetworkId: ROPSTEN_NETWORK_ID, + chainId: CHAIN_IDS.ROPSTEN, + metamaskNetworkId: NETWORK_IDS.ROPSTEN, }, ]; diff --git a/ui/components/app/transaction-decoding/transaction-decoding.component.js b/ui/components/app/transaction-decoding/transaction-decoding.component.js index d1a73ca9d..dcedad335 100644 --- a/ui/components/app/transaction-decoding/transaction-decoding.component.js +++ b/ui/components/app/transaction-decoding/transaction-decoding.component.js @@ -6,11 +6,11 @@ import { useSelector } from 'react-redux'; import * as Codec from '@truffle/codec'; import Spinner from '../../ui/spinner'; import ErrorMessage from '../../ui/error-message'; -import fetchWithCache from '../../../helpers/utils/fetch-with-cache'; +import fetchWithCache from '../../../../shared/lib/fetch-with-cache'; import { getSelectedAccount, getCurrentChainId } from '../../../selectors'; -import { hexToDecimal } from '../../../helpers/utils/conversions.util'; import { I18nContext } from '../../../contexts/i18n'; import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; +import { hexToDecimal } from '../../../../shared/lib/metamask-controller-utils'; import { transformTxDecoding } from './transaction-decoding.util'; import { FETCH_PROJECT_INFO_URI, diff --git a/ui/components/app/transaction-list/transaction-list.test.js b/ui/components/app/transaction-list/transaction-list.test.js new file mode 100644 index 000000000..0f79aede7 --- /dev/null +++ b/ui/components/app/transaction-list/transaction-list.test.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import { renderWithProvider } from '../../../../test/jest'; +import configureStore from '../../../store/store'; +import mockState from '../../../../test/data/mock-state.json'; +import TransactionList from './transaction-list.component'; + +const render = () => { + const store = configureStore({ + metamask: { + ...mockState.metamask, + }, + }); + return renderWithProvider(, store); +}; + +describe('TransactionList', () => { + it('renders TransactionList component and shows You have no transactions text', () => { + render(); + expect(screen.getByText('You have no transactions')).toBeInTheDocument(); + }); +}); diff --git a/ui/components/app/wallet-overview/eth-overview.js b/ui/components/app/wallet-overview/eth-overview.js index a9a018954..5a8775720 100644 --- a/ui/components/app/wallet-overview/eth-overview.js +++ b/ui/components/app/wallet-overview/eth-overview.js @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useContext, useState } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import classnames from 'classnames'; @@ -13,7 +13,6 @@ import { 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, getShouldShowFiat, @@ -35,6 +34,7 @@ import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics'; import Spinner from '../../ui/spinner'; import { startNewDraftTransaction } from '../../../ducks/send'; import { ASSET_TYPES } from '../../../../shared/constants/transaction'; +import DepositPopover from '../deposit-popover'; import WalletOverview from './wallet-overview'; const EthOverview = ({ className }) => { @@ -42,6 +42,7 @@ const EthOverview = ({ className }) => { const t = useContext(I18nContext); const trackEvent = useContext(MetaMetricsContext); const history = useHistory(); + const [showDepositPopover, setShowDepositPopover] = useState(false); const keyring = useSelector(getCurrentKeyring); const usingHardwareWallet = isHardwareKeyring(keyring?.type); const balanceIsCached = useSelector(isBalanceCached); @@ -53,133 +54,141 @@ const EthOverview = ({ className }) => { const defaultSwapsToken = useSelector(getSwapsDefaultToken); return ( - -
-
- {balance ? ( + <> + {showDepositPopover && ( + setShowDepositPopover(false)} /> + )} + +
+
+ {balance ? ( + + ) : ( + + )} + {balanceIsCached ? ( + * + ) : null} +
+ {showFiat && balance && ( - ) : ( - )} - {balanceIsCached ? ( - * - ) : null}
- {showFiat && balance && ( - - )} -
- - } - buttons={ - <> - { - trackEvent({ - event: EVENT_NAMES.NAV_BUY_BUTTON_CLICKED, - category: EVENT.CATEGORIES.NAVIGATION, - properties: { - location: 'Home', - text: 'Buy', - }, - }); - dispatch(showModal({ name: 'DEPOSIT_ETHER' })); - }} - /> - { - trackEvent({ - event: EVENT_NAMES.NAV_SEND_BUTTON_CLICKED, - category: EVENT.CATEGORIES.NAVIGATION, - properties: { - token_symbol: 'ETH', - location: 'Home', - text: 'Send', - }, - }); - dispatch( - startNewDraftTransaction({ type: ASSET_TYPES.NATIVE }), - ).then(() => { - history.push(SEND_ROUTE); - }); - }} - /> - { - if (isSwapsChain) { + + } + buttons={ + <> + { trackEvent({ - event: EVENT_NAMES.NAV_SWAP_BUTTON_CLICKED, - category: EVENT.CATEGORIES.SWAPS, + event: EVENT_NAMES.NAV_BUY_BUTTON_CLICKED, + category: EVENT.CATEGORIES.NAVIGATION, properties: { - token_symbol: 'ETH', - location: EVENT.SOURCE.SWAPS.MAIN_VIEW, - text: 'Swap', + location: 'Home', + text: 'Buy', }, }); - dispatch(setSwapsFromToken(defaultSwapsToken)); - if (usingHardwareWallet) { - global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE); - } else { - history.push(BUILD_QUOTE_ROUTE); + setShowDepositPopover(true); + }} + /> + { + trackEvent({ + event: EVENT_NAMES.NAV_SEND_BUTTON_CLICKED, + category: EVENT.CATEGORIES.NAVIGATION, + properties: { + token_symbol: 'ETH', + location: 'Home', + text: 'Send', + }, + }); + dispatch( + startNewDraftTransaction({ type: ASSET_TYPES.NATIVE }), + ).then(() => { + history.push(SEND_ROUTE); + }); + }} + /> + { + if (isSwapsChain) { + trackEvent({ + event: EVENT_NAMES.NAV_SWAP_BUTTON_CLICKED, + category: EVENT.CATEGORIES.SWAPS, + properties: { + token_symbol: 'ETH', + location: EVENT.SOURCE.SWAPS.MAIN_VIEW, + text: 'Swap', + }, + }); + dispatch(setSwapsFromToken(defaultSwapsToken)); + if (usingHardwareWallet) { + global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE); + } else { + history.push(BUILD_QUOTE_ROUTE); + } } + }} + label={t('swap')} + tooltipRender={ + isSwapsChain + ? null + : (contents) => ( + + {contents} + + ) } - }} - label={t('swap')} - tooltipRender={(contents) => ( - - {contents} - - )} - /> - - } - className={className} - icon={} - /> + /> + + } + className={className} + icon={} + /> + ); }; diff --git a/ui/components/app/wallet-overview/token-overview.js b/ui/components/app/wallet-overview/token-overview.js index 95f93e61b..5142c5e8c 100644 --- a/ui/components/app/wallet-overview/token-overview.js +++ b/ui/components/app/wallet-overview/token-overview.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; @@ -19,8 +19,11 @@ import { setSwapsFromToken } from '../../../ducks/swaps/swaps'; import { getCurrentKeyring, getIsSwapsChain, + getIsBuyableCoinbasePayToken, + getIsBuyableTransakToken, } from '../../../selectors/selectors'; +import BuyIcon from '../../ui/icon/overview-buy-icon.component'; import SwapIcon from '../../ui/icon/swap-icon.component'; import SendIcon from '../../ui/icon/overview-send-icon.component'; @@ -30,6 +33,7 @@ import { showModal } from '../../../store/actions'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics'; import { ASSET_TYPES } from '../../../../shared/constants/transaction'; +import DepositPopover from '../deposit-popover'; import WalletOverview from './wallet-overview'; const TokenOverview = ({ className, token }) => { @@ -37,6 +41,7 @@ const TokenOverview = ({ className, token }) => { const t = useContext(I18nContext); const trackEvent = useContext(MetaMetricsContext); const history = useHistory(); + const [showDepositPopover, setShowDepositPopover] = useState(false); const keyring = useSelector(getCurrentKeyring); const usingHardwareWallet = isHardwareKeyring(keyring.type); const { tokensWithBalances } = useTokenTracker([token]); @@ -48,6 +53,13 @@ const TokenOverview = ({ className, token }) => { token.symbol, ); const isSwapsChain = useSelector(getIsSwapsChain); + const isTokenBuyableCoinbasePay = useSelector((state) => + getIsBuyableCoinbasePayToken(state, token.symbol), + ); + const isTokenBuyableTransak = useSelector((state) => + getIsBuyableTransakToken(state, token.symbol), + ); + const isBuyable = isTokenBuyableCoinbasePay || isTokenBuyableTransak; useEffect(() => { if (token.isERC721 && process.env.COLLECTIBLES_V1) { @@ -61,105 +73,140 @@ const TokenOverview = ({ className, token }) => { }, [token.isERC721, token.address, dispatch]); return ( - - - {formattedFiatBalance ? ( + <> + {showDepositPopover && ( + setShowDepositPopover(false)} + token={token} + /> + )} + - ) : null} -
- } - buttons={ - <> - { - trackEvent({ - event: EVENT_NAMES.NAV_SEND_BUTTON_CLICKED, - category: EVENT.CATEGORIES.NAVIGATION, - properties: { - token_symbol: token.symbol, - location: EVENT.SOURCE.SWAPS.TOKEN_VIEW, - text: 'Send', - }, - }); - try { - await dispatch( - startNewDraftTransaction({ - type: ASSET_TYPES.TOKEN, - details: token, - }), - ); - history.push(SEND_ROUTE); - } catch (err) { - if (!err.message.includes(INVALID_ASSET_TYPE)) { - throw err; - } - } - }} - Icon={SendIcon} - label={t('send')} - data-testid="eth-overview-send" - disabled={token.isERC721} - /> - { - if (isSwapsChain) { + {formattedFiatBalance ? ( + + ) : null} +
+ } + buttons={ + <> + {isBuyable && ( + { + trackEvent({ + event: 'Clicked Deposit: Token', + category: EVENT.CATEGORIES.NAVIGATION, + properties: { + action: 'Home', + legacy_event: true, + }, + }); + setShowDepositPopover(true); + }} + disabled={token.isERC721} + /> + )} + { trackEvent({ - event: EVENT_NAMES.NAV_SWAP_BUTTON_CLICKED, - category: EVENT.CATEGORIES.SWAPS, + event: EVENT_NAMES.NAV_SEND_BUTTON_CLICKED, + category: EVENT.CATEGORIES.NAVIGATION, properties: { token_symbol: token.symbol, location: EVENT.SOURCE.SWAPS.TOKEN_VIEW, - text: 'Swap', + text: 'Send', }, }); - dispatch( - setSwapsFromToken({ - ...token, - address: token.address.toLowerCase(), - iconUrl: token.image, - balance, - string: balanceToRender, - }), - ); - if (usingHardwareWallet) { - global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE); - } else { - history.push(BUILD_QUOTE_ROUTE); + try { + await dispatch( + startNewDraftTransaction({ + type: ASSET_TYPES.TOKEN, + details: token, + }), + ); + history.push(SEND_ROUTE); + } catch (err) { + if (!err.message.includes(INVALID_ASSET_TYPE)) { + throw err; + } } + }} + Icon={SendIcon} + label={t('send')} + data-testid="eth-overview-send" + disabled={token.isERC721} + /> + { + if (isSwapsChain) { + trackEvent({ + event: EVENT_NAMES.NAV_SWAP_BUTTON_CLICKED, + category: EVENT.CATEGORIES.SWAPS, + properties: { + token_symbol: token.symbol, + location: EVENT.SOURCE.SWAPS.TOKEN_VIEW, + text: 'Swap', + }, + }); + dispatch( + setSwapsFromToken({ + ...token, + address: token.address.toLowerCase(), + iconUrl: token.image, + balance, + string: balanceToRender, + }), + ); + if (usingHardwareWallet) { + global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE); + } else { + history.push(BUILD_QUOTE_ROUTE); + } + } + }} + label={t('swap')} + tooltipRender={ + isSwapsChain + ? null + : (contents) => ( + + {contents} + + ) } - }} - label={t('swap')} - tooltipRender={(contents) => ( - - {contents} - - )} + /> + + } + className={className} + icon={ + - - } - className={className} - icon={ - - } - /> + } + /> + ); }; diff --git a/ui/components/app/whats-new-popup/whats-new-popup.test.js b/ui/components/app/whats-new-popup/whats-new-popup.test.js new file mode 100644 index 000000000..c92bdc600 --- /dev/null +++ b/ui/components/app/whats-new-popup/whats-new-popup.test.js @@ -0,0 +1,121 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import { renderWithProvider } from '../../../../test/jest'; +import configureStore from '../../../store/store'; +import mockState from '../../../../test/data/mock-state.json'; +import WhatsNewPopup from './whats-new-popup'; + +const render = () => { + const store = configureStore({ + metamask: { + ...mockState.metamask, + announcements: { + 1: { + date: '2021-03-17', + id: 1, + image: { + height: '230px', + placeImageBelowDescription: true, + src: 'images/mobile-link-qr.svg', + width: '230px', + }, + isShown: false, + }, + 3: { + date: '2021-03-08', + id: 3, + isShown: false, + }, + 4: { + date: '2021-05-11', + id: 4, + image: { + src: 'images/source-logos-bsc.svg', + width: '100%', + }, + isShown: false, + }, + 5: { + date: '2021-06-09', + id: 5, + isShown: false, + }, + 6: { + date: '2021-05-26', + id: 6, + isShown: false, + }, + 7: { + date: '2021-09-17', + id: 7, + isShown: false, + }, + 8: { + date: '2021-11-01', + id: 8, + isShown: false, + }, + 9: { + date: '2021-12-07', + id: 9, + image: { + src: 'images/txinsights.png', + width: '80%', + }, + isShown: false, + }, + 10: { + date: '2022-04-18', + id: 10, + image: { + src: 'images/token-detection.svg', + width: '100%', + }, + isShown: true, + }, + 11: { + date: '2022-04-18', + id: 11, + isShown: true, + }, + 12: { + date: '2022-05-18', + id: 12, + image: { + src: 'images/darkmode-banner.png', + width: '100%', + }, + isShown: false, + }, + 13: { + date: '2022-07-12', + id: 13, + isShown: true, + }, + }, + }, + }); + return renderWithProvider(, store); +}; + +describe('WhatsNewPopup', () => { + beforeEach(() => { + const mockIntersectionObserver = jest.fn(); + mockIntersectionObserver.mockReturnValue({ + observe: () => null, + unobserve: () => null, + disconnect: () => null, + }); + window.IntersectionObserver = mockIntersectionObserver; + }); + + it("renders WhatsNewPopup component and shows What's new text", () => { + render(); + expect(screen.getByText("What's new")).toBeInTheDocument(); + }); + + it('renders WhatsNewPopup component and shows close button', () => { + render(); + expect(screen.getByTestId('popover-close')).toBeInTheDocument(); + }); +}); diff --git a/ui/components/component-library/avatar-network/avatar-network.stories.js b/ui/components/component-library/avatar-network/avatar-network.stories.js index 81ca71c49..4546ec597 100644 --- a/ui/components/component-library/avatar-network/avatar-network.stories.js +++ b/ui/components/component-library/avatar-network/avatar-network.stories.js @@ -61,6 +61,7 @@ export default { const Template = (args) => { return ; }; + export const DefaultStory = Template.bind({}); DefaultStory.storyName = 'Default'; @@ -74,15 +75,15 @@ export const Size = (args) => ( ); -export const networkName = Template.bind({}); -networkName.args = { +export const NetworkName = Template.bind({}); +NetworkName.args = { networkImageUrl: '', }; -export const networkImageUrl = Template.bind({}); +export const NetworkImageUrl = Template.bind({}); -export const showHalo = Template.bind({}); -showHalo.args = { +export const ShowHalo = Template.bind({}); +ShowHalo.args = { showHalo: true, }; diff --git a/ui/components/component-library/avatar-token/avatar-token.stories.js b/ui/components/component-library/avatar-token/avatar-token.stories.js index 7b74e10ae..494d42ecc 100644 --- a/ui/components/component-library/avatar-token/avatar-token.stories.js +++ b/ui/components/component-library/avatar-token/avatar-token.stories.js @@ -75,15 +75,15 @@ export const Size = (args) => ( ); -export const tokenName = Template.bind({}); -tokenName.args = { +export const TokenName = Template.bind({}); +TokenName.args = { tokenImageUrl: '', }; -export const tokenImageUrl = Template.bind({}); +export const TokenImageUrl = Template.bind({}); -export const showHalo = Template.bind({}); -showHalo.args = { +export const ShowHalo = Template.bind({}); +ShowHalo.args = { showHalo: true, }; diff --git a/ui/components/component-library/base-icon/README.mdx b/ui/components/component-library/base-icon/README.mdx deleted file mode 100644 index 519ebb745..000000000 --- a/ui/components/component-library/base-icon/README.mdx +++ /dev/null @@ -1,77 +0,0 @@ -import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; - -import { BaseIcon } from './base-icon'; - -### This is a base component. It should not be used in your feature code directly but as a "base" for other UI components - -# BaseIcon - -The `BaseIcon` is the base component for all icons. It is used in conjunction with a script to create all icons it should not be used directly. - - - - - -## Props - -The `BaseIcon` accepts all props below as well as all [Box](/docs/ui-components-ui-box-box-stories-js--default-story#props) component props - - - -### Size - -Use the `size` prop and the `SIZES` object from `./ui/helpers/constants/design-system.js` to change the size of `BaseIcon`. Defaults to `SIZES.SM` - -Possible sizes include: - -- `SIZES.XXS` 10px -- `SIZES.XS` 12px -- `SIZES.SM` 16px -- `SIZES.MD` 20px -- `SIZES.LG` 24px -- `SIZES.XL` 32px - - - - - -```jsx -import { SIZES } from '../../../helpers/constants/design-system'; -import { BaseIcon } from '../ui/component-library'; - - - - - - - -``` - -### Color - -Use the `color` prop and the `COLORS` object from `./ui/helpers/constants/design-system.js` to change the color of `BaseIcon`. Defaults to `COLORS.INHERIT` which will use the text color of the parent element. This is useful for inline icons. - - - - - -```jsx -import { COLORS } from '../../../helpers/constants/design-system'; -import { BaseIcon } from '../ui/component-library'; - - - - - - - - - - - - - - - - -``` diff --git a/ui/components/component-library/base-icon/base-icon.js b/ui/components/component-library/base-icon/base-icon.js deleted file mode 100644 index 966371410..000000000 --- a/ui/components/component-library/base-icon/base-icon.js +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import Box from '../../ui/box/box'; - -import { - SIZES, - COLORS, - ICON_COLORS, -} from '../../../helpers/constants/design-system'; - -export const BaseIcon = ({ - className, - size = SIZES.MD, - color = COLORS.INHERIT, - children, - ...props -}) => { - return ( - - {children} - - ); -}; - -BaseIcon.propTypes = { - /** - * The size of the BaseIcon. - * Possible values could be 'SIZES.XXS', 'SIZES.XS', 'SIZES.SM', 'SIZES.MD', 'SIZES.LG', 'SIZES.XL', - * Default value is 'SIZES.MD'. - */ - size: PropTypes.oneOf(Object.values(SIZES)), - /** - * The color of the icon. - * Defaults to COLORS.INHERIT. - */ - color: PropTypes.oneOf(Object.values(ICON_COLORS)), - /** - * An additional className to apply to the icon. - */ - className: PropTypes.string, - /** - * The to the icon. - */ - children: PropTypes.node, - /** - * BaseIcon accepts all the props from Box - */ - ...Box.propTypes, -}; diff --git a/ui/components/component-library/base-icon/base-icon.stories.js b/ui/components/component-library/base-icon/base-icon.stories.js deleted file mode 100644 index 89c7ce245..000000000 --- a/ui/components/component-library/base-icon/base-icon.stories.js +++ /dev/null @@ -1,176 +0,0 @@ -import React from 'react'; -import { - SIZES, - ALIGN_ITEMS, - DISPLAY, - COLORS, - ICON_COLORS, -} from '../../../helpers/constants/design-system'; -import Box from '../../ui/box/box'; - -import { BaseIcon } from './base-icon'; -import README from './README.mdx'; - -const marginSizeControlOptions = [ - undefined, - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 'auto', -]; - -export default { - title: 'Components/ComponentLibrary/BaseIcon', - id: __filename, - component: BaseIcon, - parameters: { - docs: { - page: README, - }, - }, - argTypes: { - size: { - control: 'select', - options: Object.values(SIZES), - }, - color: { - control: 'select', - options: Object.values(ICON_COLORS), - }, - className: { - control: 'text', - }, - marginTop: { - options: marginSizeControlOptions, - control: 'select', - table: { category: 'box props' }, - }, - marginRight: { - options: marginSizeControlOptions, - control: 'select', - table: { category: 'box props' }, - }, - marginBottom: { - options: marginSizeControlOptions, - control: 'select', - table: { category: 'box props' }, - }, - marginLeft: { - options: marginSizeControlOptions, - control: 'select', - table: { category: 'box props' }, - }, - }, - args: { - color: COLORS.INHERIT, - size: SIZES.MD, - children: ( - - ), - }, -}; - -export const DefaultStory = (args) => ; - -DefaultStory.storyName = 'Default'; - -export const Size = (args) => ( - - - - - - - - -); - -export const Color = (args) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); diff --git a/ui/components/component-library/base-icon/base-icon.test.js b/ui/components/component-library/base-icon/base-icon.test.js deleted file mode 100644 index 6ecda4ec7..000000000 --- a/ui/components/component-library/base-icon/base-icon.test.js +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable jest/require-top-level-describe */ -import { render } from '@testing-library/react'; -import React from 'react'; -import { SIZES, COLORS } from '../../../helpers/constants/design-system'; -import { BaseIcon } from './base-icon'; - -describe('BaseIcon', () => { - it('should render correctly', () => { - const { getByTestId, container } = render( - , - ); - expect(getByTestId('base-icon')).toBeDefined(); - expect(container.querySelector('svg')).toBeDefined(); - }); - it('should render with different size classes', () => { - const { getByTestId } = render( - <> - - - - - - - , - ); - expect(getByTestId('base-icon-xxs')).toHaveClass('base-icon--size-xxs'); - expect(getByTestId('base-icon-xs')).toHaveClass('base-icon--size-xs'); - expect(getByTestId('base-icon-sm')).toHaveClass('base-icon--size-sm'); - expect(getByTestId('base-icon-md')).toHaveClass('base-icon--size-md'); - expect(getByTestId('base-icon-lg')).toHaveClass('base-icon--size-lg'); - expect(getByTestId('base-icon-xl')).toHaveClass('base-icon--size-xl'); - }); - it('should render with icon colors', () => { - const { getByTestId } = render( - <> - - - - - , - ); - expect(getByTestId('base-icon-color-inherit')).toHaveClass( - 'box--color-inherit', - ); - expect(getByTestId('base-icon-color-default')).toHaveClass( - 'box--color-icon-default', - ); - expect(getByTestId('base-icon-color-alternative')).toHaveClass( - 'box--color-icon-alternative', - ); - expect(getByTestId('base-icon-color-muted')).toHaveClass( - 'box--color-icon-muted', - ); - }); -}); diff --git a/ui/components/component-library/base-icon/index.js b/ui/components/component-library/base-icon/index.js deleted file mode 100644 index f1ceeadd2..000000000 --- a/ui/components/component-library/base-icon/index.js +++ /dev/null @@ -1 +0,0 @@ -export { BaseIcon } from './base-icon'; diff --git a/ui/components/component-library/component-library-components.scss b/ui/components/component-library/component-library-components.scss index 55b4f014b..09c0d7a28 100644 --- a/ui/components/component-library/component-library-components.scss +++ b/ui/components/component-library/component-library-components.scss @@ -2,5 +2,5 @@ @import 'avatar-network/avatar-network'; @import 'avatar-token/avatar-token'; @import 'base-avatar/base-avatar'; -@import 'base-icon/base-icon'; +@import 'icon/icon'; @import 'text/text'; diff --git a/ui/components/component-library/icon/README.mdx b/ui/components/component-library/icon/README.mdx new file mode 100644 index 000000000..c2e9d4411 --- /dev/null +++ b/ui/components/component-library/icon/README.mdx @@ -0,0 +1,122 @@ +import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; + +import { Icon } from './icon'; + +# Icon + +The `Icon` component in conjunction with `ICON_NAMES` can be used for all icons in the extension + + + + + +## Props + +The `Icon` accepts all props below as well as all [Box](/docs/ui-components-ui-box-box-stories-js--default-story#props) component props + + + +### Name + +Use the `name` prop and the `ICON_NAMES` object to change the icon. + +Use the [IconSearch](/ui-components-component-library-icon-icon-stories-js--name) story to find the icon you want to use. + +```jsx +import { Icon, ICON_NAMES } from '../ui/component-library'; + + + + + +``` + + + + + +### Size + +Use the `size` prop and the `SIZES` object from `./ui/helpers/constants/design-system.js` to change the size of `Icon`. Defaults to `SIZES.SM` + +Possible sizes include: + +- `SIZES.XXS` 10px +- `SIZES.XS` 12px +- `SIZES.SM` 16px +- `SIZES.MD` 20px +- `SIZES.LG` 24px +- `SIZES.XL` 32px + + + + + +```jsx +import { SIZES } from '../../../helpers/constants/design-system'; +import { Icon, ICON_NAMES } from '../ui/component-library'; + + + + + + + +``` + +### Color + +Use the `color` prop and the `COLORS` object from `./ui/helpers/constants/design-system.js` to change the color of `Icon`. Defaults to `COLORS.INHERIT` which will use the text color of the parent element. This is useful for inline icons. + + + + + +```jsx +import { COLORS } from '../../../helpers/constants/design-system'; +import { Icon, ICON_NAMES } from '../ui/component-library'; + + + + + + + + + + + + + + + + +``` + +### Adding a new icon + +To add a new icon the only thing you need to do is add the icon svg file to `app/images/icons`. To ensure that the icon is added correctly follow these steps: + +#### Step 1. + +Optimize the svg using [Fontastic](https://fontastic.me/). This will remove any unnecessary code from the svg. Your svg should only contain a single path. + +Example of a correctly optimized svg: + +``` + + + +``` + +If your svg **does not** contain a single path, you will need to get a designer to join all paths and outline strokes into a single path. + +#### Step 2. + +Add your optimized svg file to to `app/images/icons` and ensure the icon name starts with `icon-` e.g. `icon-add-square-filled.svg` + +#### Step 3. + +Run `yarn start` to generate the `ICON_NAMES` with your added icon. + +If you have any questions please reach out to the design system team in the [#metamask-design-system](https://consensys.slack.com/archives/C0354T27M5M) channel on slack. diff --git a/ui/components/component-library/icon/icon.constants.js b/ui/components/component-library/icon/icon.constants.js new file mode 100644 index 000000000..3ea0780c8 --- /dev/null +++ b/ui/components/component-library/icon/icon.constants.js @@ -0,0 +1,11 @@ +/** + * The ICON_NAMES object contains all the possible icon names. + * It is generated using the generateIconNames script in development/generate-icon-names.js + * and stored in the environment variable ICON_NAMES + * To add a new icon, add the icon svg file to app/images/icons + * Ensure the svg has been optimized, is kebab case and starts with "icon-" + * See "Adding a new icon" in ./README.md for more details + */ + +/* eslint-disable prefer-destructuring*/ // process.env is not a standard JavaScript object, so we are not able to use object destructuring +export const ICON_NAMES = process.env.ICON_NAMES; diff --git a/ui/components/component-library/icon/icon.js b/ui/components/component-library/icon/icon.js new file mode 100644 index 000000000..bbc7edbba --- /dev/null +++ b/ui/components/component-library/icon/icon.js @@ -0,0 +1,69 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +import Box from '../../ui/box/box'; + +import { + SIZES, + COLORS, + ICON_COLORS, +} from '../../../helpers/constants/design-system'; + +export const Icon = ({ + name, + size = SIZES.MD, + color = COLORS.INHERIT, + className, + style, + ...props +}) => { + return ( + + ); +}; + +Icon.propTypes = { + /** + * The name of the icon to display. Should be one of ICON_NAMES + */ + name: PropTypes.string.isRequired, // Can't set PropTypes.oneOf(ICON_NAMES) because ICON_NAMES is an environment variable + /** + * The size of the Icon. + * Possible values could be 'SIZES.XXS', 'SIZES.XS', 'SIZES.SM', 'SIZES.MD', 'SIZES.LG', 'SIZES.XL', + * Default value is 'SIZES.MD'. + */ + size: PropTypes.oneOf(Object.values(SIZES)), + /** + * The color of the icon. + * Defaults to COLORS.INHERIT. + */ + color: PropTypes.oneOf(Object.values(ICON_COLORS)), + /** + * An additional className to apply to the icon. + */ + className: PropTypes.string, + /** + * Addition style properties to apply to the icon. + * The Icon component uses inline styles to apply the icon's mask-image so be wary of overriding + */ + style: PropTypes.style, + /** + * Icon accepts all the props from Box + */ + ...Box.propTypes, +}; diff --git a/ui/components/component-library/base-icon/base-icon.scss b/ui/components/component-library/icon/icon.scss similarity index 55% rename from ui/components/component-library/base-icon/base-icon.scss rename to ui/components/component-library/icon/icon.scss index fd81cab65..05a53b772 100644 --- a/ui/components/component-library/base-icon/base-icon.scss +++ b/ui/components/component-library/icon/icon.scss @@ -1,6 +1,21 @@ -.base-icon { +.icon { --icon-size: var(--size, 16px); + font-size: var(--icon-size); + width: 1em; + height: 1em; + max-width: 1em; + flex: 0 0 1em; + display: inline-block; + background-color: currentColor; // inherits parent text color + mask-size: cover; + -webkit-mask-size: cover; + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-position: center; + -webkit-mask-position: center; + + // Size &--size-xxs { --size: 10px; } @@ -24,10 +39,4 @@ &--size-xl { --size: 32px; } - - font-size: var(--icon-size); - width: 1em; - height: 1em; - display: inline-block; - fill: currentColor; } diff --git a/ui/components/component-library/icon/icon.stories.js b/ui/components/component-library/icon/icon.stories.js new file mode 100644 index 000000000..ff2244efa --- /dev/null +++ b/ui/components/component-library/icon/icon.stories.js @@ -0,0 +1,291 @@ +import React, { useState } from 'react'; +import { + SIZES, + ALIGN_ITEMS, + DISPLAY, + COLORS, + ICON_COLORS, + FLEX_DIRECTION, + JUSTIFY_CONTENT, + TEXT, +} from '../../../helpers/constants/design-system'; +import Box from '../../ui/box/box'; +import { Text } from '../text'; + +import { Icon } from './icon'; +import { ICON_NAMES } from './icon.constants'; + +import README from './README.mdx'; + +const marginSizeControlOptions = [ + undefined, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 'auto', +]; + +export default { + title: 'Components/ComponentLibrary/Icon', + id: __filename, + component: Icon, + parameters: { + docs: { + page: README, + }, + }, + argTypes: { + name: { + control: 'select', + options: Object.values(ICON_NAMES), + }, + size: { + control: 'select', + options: Object.values(SIZES), + }, + color: { + control: 'select', + options: Object.values(ICON_COLORS), + }, + className: { + control: 'text', + }, + marginTop: { + options: marginSizeControlOptions, + control: 'select', + table: { category: 'box props' }, + }, + marginRight: { + options: marginSizeControlOptions, + control: 'select', + table: { category: 'box props' }, + }, + marginBottom: { + options: marginSizeControlOptions, + control: 'select', + table: { category: 'box props' }, + }, + marginLeft: { + options: marginSizeControlOptions, + control: 'select', + table: { category: 'box props' }, + }, + }, + args: { + name: ICON_NAMES.ADD_SQUARE_FILLED, + color: COLORS.INHERIT, + size: SIZES.MD, + }, +}; + +export const DefaultStory = (args) => ; + +DefaultStory.storyName = 'Default'; + +export const Name = (args) => { + const [search, setSearch] = useState(''); + const iconList = Object.keys(ICON_NAMES) + .filter( + (item) => + search === '' || + item.toLowerCase().includes(search.toLowerCase().replace(' ', '_')), + ) + .sort(); + + const handleSearch = (e) => { + setSearch(e.target.value); + }; + + return ( + <> + + Icon search + + + + + + + {iconList.length > 0 ? ( + <> + {iconList.map((item) => { + return ( + + + + + { + const tempEl = document.createElement('textarea'); + tempEl.value = item; + document.body.appendChild(tempEl); + tempEl.select(); + document.execCommand('copy'); + document.body.removeChild(tempEl); + }} + > + {item} + + + ); + })} + + ) : ( + + No matches. Please try again or ask in the{' '} + + #metamask-design-system + {' '} + channel on slack. + + )} + + + ); +}; + +export const Size = (args) => ( + + + + + + + + +); + +export const Color = (args) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/ui/components/component-library/icon/icon.test.js b/ui/components/component-library/icon/icon.test.js new file mode 100644 index 000000000..697b3a2b3 --- /dev/null +++ b/ui/components/component-library/icon/icon.test.js @@ -0,0 +1,130 @@ +/* eslint-disable jest/require-top-level-describe */ +import { render } from '@testing-library/react'; +import React from 'react'; +import { SIZES, COLORS } from '../../../helpers/constants/design-system'; +import { Icon } from './icon'; + +// Icon names are stored in the ICON_NAMES environment variable +// mocking the environment variable here +const MOCK_ICON_NAMES = { + ADD_SQUARE_FILLED: 'add-square-filled', + BANK_FILLED: 'bank-filled', + BOOKMARK_FILLED: 'bookmark-filled', + CALCULATOR_FILLED: 'calculator-filled', +}; + +describe('Icon', () => { + it('should render correctly', () => { + const { getByTestId, container } = render( + , + ); + expect(getByTestId('icon')).toBeDefined(); + expect(container.querySelector('svg')).toBeDefined(); + }); + it('should render with different icons using mask-image and image urls', () => { + const { getByTestId } = render( + <> + + + + + , + ); + expect( + window.getComputedStyle(getByTestId('icon-add-square-filled')).maskImage, + ).toBe(`url('/images/icons/icon-add-square-filled.svg`); + expect( + window.getComputedStyle(getByTestId('icon-bank-filled')).maskImage, + ).toBe(`url('/images/icons/icon-bank-filled.svg`); + expect( + window.getComputedStyle(getByTestId('icon-bookmark-filled')).maskImage, + ).toBe(`url('/images/icons/icon-bookmark-filled.svg`); + expect( + window.getComputedStyle(getByTestId('icon-calculator-filled')).maskImage, + ).toBe(`url('/images/icons/icon-calculator-filled.svg`); + }); + it('should render with different size classes', () => { + const { getByTestId } = render( + <> + + + + + + + , + ); + expect(getByTestId('icon-xxs')).toHaveClass('icon--size-xxs'); + expect(getByTestId('icon-xs')).toHaveClass('icon--size-xs'); + expect(getByTestId('icon-sm')).toHaveClass('icon--size-sm'); + expect(getByTestId('icon-md')).toHaveClass('icon--size-md'); + expect(getByTestId('icon-lg')).toHaveClass('icon--size-lg'); + expect(getByTestId('icon-xl')).toHaveClass('icon--size-xl'); + }); + it('should render with icon colors', () => { + const { getByTestId } = render( + <> + + + + , + ); + expect(getByTestId('icon-color-default')).toHaveClass( + 'box--color-icon-default', + ); + expect(getByTestId('icon-color-alternative')).toHaveClass( + 'box--color-icon-alternative', + ); + expect(getByTestId('icon-color-muted')).toHaveClass( + 'box--color-icon-muted', + ); + }); +}); diff --git a/ui/components/component-library/icon/index.js b/ui/components/component-library/icon/index.js new file mode 100644 index 000000000..97ac040b5 --- /dev/null +++ b/ui/components/component-library/icon/index.js @@ -0,0 +1,2 @@ +export { Icon } from './icon'; +export { ICON_NAMES } from './icon.constants'; diff --git a/ui/components/ui/account-list/account-list.test.js b/ui/components/ui/account-list/account-list.test.js new file mode 100644 index 000000000..00f02f11c --- /dev/null +++ b/ui/components/ui/account-list/account-list.test.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import { renderWithProvider } from '../../../../test/jest'; +import configureStore from '../../../store/store'; +import mockState from '../../../../test/data/mock-state.json'; +import AccountList from './account-list'; + +const render = () => { + const store = configureStore({ + metamask: { + ...mockState.metamask, + }, + }); + + const args = { + accounts: [ + { + address: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4', + addressLabel: 'Account 1', + lastConnectedDate: 'Feb-22-2022', + balance: '8.7a73149c048545a3fe58', + has: () => { + /** nothing to do */ + }, + }, + ], + selectedAccounts: { + address: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4', + addressLabel: 'Account 2', + lastConnectedDate: 'Feb-22-2022', + balance: '8.7a73149c048545a3fe58', + has: () => { + /** nothing to do */ + }, + }, + addressLastConnectedMap: { + '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4': 'Feb-22-2022', + }, + allAreSelected: () => true, + nativeCurrency: 'USD', + }; + return renderWithProvider(, store); +}; + +describe('AccountList', () => { + it('renders AccountList component and shows New account text', () => { + render(); + expect(screen.getByText('New account')).toBeInTheDocument(); + }); + + it('renders AccountList component and shows Account 1 text', () => { + render(); + expect(screen.getByText('Account 1')).toBeInTheDocument(); + }); + + it('renders AccountList component and shows ETH text', () => { + render(); + expect(screen.getByText('ETH')).toBeInTheDocument(); + }); +}); diff --git a/ui/components/ui/form-field/README.mdx b/ui/components/ui/form-field/README.mdx index 5881cef40..41511a33e 100644 --- a/ui/components/ui/form-field/README.mdx +++ b/ui/components/ui/form-field/README.mdx @@ -29,3 +29,13 @@ Show form fields with error state + +### Custom Components + +Use the custom component props `TitleTextCustomComponent`, `TitleUnitCustomComponent` and `TooltipCustomComponent` to replace the default components. +If these props exists they will replace their respective text props. The FormField is wrapped in a Box component that renders as a `