Merge pull request #10054 from MetaMask/Version-v9.0.0

feature/default_network_editable
Erik Marks 4 years ago committed by GitHub
commit 93068e2c2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 143
      .circleci/config.yml
  2. 5
      .circleci/scripts/collect-har-artifact.sh
  3. 8
      .circleci/scripts/deps-install.sh
  4. 2
      .circleci/scripts/firefox-install
  5. 1
      .gitignore
  6. 104
      CHANGELOG.md
  7. 2
      README.md
  8. 32
      app/_locales/en/messages.json
  9. 4
      app/_locales/fr/messages.json
  10. 52
      app/_locales/zh_CN/messages.json
  11. 3
      app/images/down-arrow-grey.svg
  12. 1
      app/images/icons/connect.svg
  13. 2
      app/manifest/_base.json
  14. 2
      app/manifest/chrome.json
  15. 16
      app/scripts/background.js
  16. 74
      app/scripts/contentscript.js
  17. 70
      app/scripts/controllers/alert.js
  18. 2
      app/scripts/controllers/app-state.js
  19. 2
      app/scripts/controllers/cached-balances.js
  20. 11
      app/scripts/controllers/detect-tokens.js
  21. 2
      app/scripts/controllers/ens/index.js
  22. 6
      app/scripts/controllers/incoming-transactions.js
  23. 2
      app/scripts/controllers/metametrics.js
  24. 33
      app/scripts/controllers/network/createInfuraClient.js
  25. 14
      app/scripts/controllers/network/network.js
  26. 2
      app/scripts/controllers/onboarding.js
  27. 12
      app/scripts/controllers/permissions/enums.js
  28. 123
      app/scripts/controllers/permissions/index.js
  29. 1
      app/scripts/controllers/permissions/permissionsLog.js
  30. 2
      app/scripts/controllers/permissions/permissionsMethodMiddleware.js
  31. 98
      app/scripts/controllers/preferences.js
  32. 2
      app/scripts/controllers/swaps.js
  33. 2
      app/scripts/controllers/threebox.js
  34. 2
      app/scripts/controllers/token-rates.js
  35. 2
      app/scripts/controllers/transactions/index.js
  36. 2
      app/scripts/controllers/transactions/tx-state-manager.js
  37. 28
      app/scripts/inpage.js
  38. 2
      app/scripts/lib/ComposableObservableStore.js
  39. 2
      app/scripts/lib/account-tracker.js
  40. 2
      app/scripts/lib/decrypt-message-manager.js
  41. 2
      app/scripts/lib/encryption-public-key-manager.js
  42. 3
      app/scripts/lib/enums.js
  43. 4
      app/scripts/lib/fetch-with-timeout.js
  44. 2
      app/scripts/lib/message-manager.js
  45. 2
      app/scripts/lib/personal-message-manager.js
  46. 1
      app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js
  47. 46
      app/scripts/lib/rpc-method-middleware/handlers/get-provider-state.js
  48. 5
      app/scripts/lib/rpc-method-middleware/handlers/index.js
  49. 57
      app/scripts/lib/rpc-method-middleware/handlers/log-web3-shim-usage.js
  50. 63
      app/scripts/lib/rpc-method-middleware/handlers/log-web3-usage.js
  51. 251
      app/scripts/lib/setupWeb3.js
  52. 2
      app/scripts/lib/typed-message-manager.js
  53. 101
      app/scripts/lib/web3-entities.json
  54. 205
      app/scripts/metamask-controller.js
  55. 28
      app/scripts/migrations/051.js
  56. 1
      app/scripts/migrations/index.js
  57. 2
      babel.config.js
  58. 1
      development/build/index.js
  59. 4
      development/metamaskbot-build-announce.js
  60. 12
      package.json
  61. 18
      shared/constants/alerts.js
  62. 4
      test/e2e/webdriver/chrome.js
  63. 36
      test/e2e/webdriver/firefox.js
  64. 3
      test/e2e/webdriver/index.js
  65. 2
      test/unit/app/ComposableObservableStore.js
  66. 106
      test/unit/app/controllers/detect-tokens-test.js
  67. 2
      test/unit/app/controllers/ens-controller-test.js
  68. 4
      test/unit/app/controllers/metamask-controller-test.js
  69. 31
      test/unit/app/controllers/permissions/mocks.js
  70. 156
      test/unit/app/controllers/permissions/permissions-controller-test.js
  71. 4
      test/unit/app/controllers/permissions/permissions-log-controller-test.js
  72. 130
      test/unit/app/controllers/permissions/permissions-middleware-test.js
  73. 2
      test/unit/app/controllers/preferences-controller-test.js
  74. 2
      test/unit/app/controllers/swaps-test.js
  75. 2
      test/unit/app/controllers/token-rates-controller.js
  76. 2
      test/unit/app/controllers/transactions/tx-controller-test.js
  77. 124
      test/unit/migrations/051-test.js
  78. 9
      ui/app/components/app/dropdowns/network-dropdown.js
  79. 2
      ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
  80. 1
      ui/app/components/app/gas-customization/gas-modal-page-container/index.scss
  81. 130
      ui/app/components/app/home-notification/home-notification.component.js
  82. 83
      ui/app/components/app/home-notification/index.scss
  83. 9
      ui/app/components/app/loading-network-screen/loading-network-screen.component.js
  84. 5
      ui/app/components/app/loading-network-screen/loading-network-screen.container.js
  85. 2
      ui/app/components/app/multiple-notifications/multiple-notifications.component.js
  86. 2
      ui/app/components/ui/page-container/page-container.component.js
  87. 2
      ui/app/components/ui/url-icon/index.scss
  88. 5
      ui/app/components/ui/url-icon/url-icon.js
  89. 2
      ui/app/ducks/alerts/invalid-custom-network.js
  90. 4
      ui/app/ducks/alerts/unconnected-account.js
  91. 10
      ui/app/ducks/app/app.js
  92. 2
      ui/app/ducks/index.js
  93. 8
      ui/app/ducks/metamask/metamask.js
  94. 2
      ui/app/ducks/swaps/swaps.js
  95. 2
      ui/app/helpers/utils/conversions.util.js
  96. 4
      ui/app/hooks/useTokensToSearch.js
  97. 2
      ui/app/pages/add-token/token-list/token-list.component.js
  98. 2
      ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
  99. 37
      ui/app/pages/home/home.component.js
  100. 28
      ui/app/pages/home/home.container.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,5 +1,19 @@
version: 2.1 version: 2.1
executors:
node-browsers:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
node-browsers-medium-plus:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
resource_class: medium+
environment:
NODE_OPTIONS: --max_old_space_size=2048
shellcheck:
docker:
- image: koalaman/shellcheck-alpine@sha256:35882cba254810c7de458528011e935ba2c4f3ebcb224275dfa7ebfa930ef294
workflows: workflows:
test_and_release: test_and_release:
jobs: jobs:
@ -92,14 +106,10 @@ workflows:
only: develop only: develop
requires: requires:
- prep-build-storybook - prep-build-storybook
- coveralls-upload:
requires:
- test-unit
jobs: jobs:
create_release_pull_request: create_release_pull_request:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -112,18 +122,20 @@ jobs:
.circleci/scripts/release-create-release-pr .circleci/scripts/release-create-release-pr
prep-deps: prep-deps:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- restore_cache:
key: dependency-cache-{{ checksum "yarn.lock" }}
- run: - run:
name: Install deps name: Install deps
command: | command: |
.circleci/scripts/deps-install.sh .circleci/scripts/deps-install.sh
- run: - save_cache:
name: Collect yarn install HAR logs key: dependency-cache-{{ checksum "yarn.lock" }}
command: | paths:
.circleci/scripts/collect-har-artifact.sh - node_modules/
- build-artifacts/yarn-install-har/
- persist_to_workspace: - persist_to_workspace:
root: . root: .
paths: paths:
@ -131,11 +143,7 @@ jobs:
- build-artifacts - build-artifacts
prep-build: prep-build:
docker: executor: node-browsers-medium-plus
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
resource_class: medium+
environment:
NODE_OPTIONS: --max_old_space_size=2048
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -153,11 +161,7 @@ jobs:
- builds - builds
prep-build-test: prep-build-test:
docker: executor: node-browsers-medium-plus
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
resource_class: medium+
environment:
NODE_OPTIONS: --max_old_space_size=2048
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -168,17 +172,17 @@ jobs:
- run: - run:
name: Move test build to 'dist-test' to avoid conflict with production build name: Move test build to 'dist-test' to avoid conflict with production build
command: mv ./dist ./dist-test command: mv ./dist ./dist-test
- run:
name: Move test zips to 'builds-test' to avoid conflict with production build
command: mv ./builds ./builds-test
- persist_to_workspace: - persist_to_workspace:
root: . root: .
paths: paths:
- dist-test - dist-test
- builds-test
prep-build-test-metrics: prep-build-test-metrics:
docker: executor: node-browsers-medium-plus
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
resource_class: medium+
environment:
NODE_OPTIONS: --max_old_space_size=2048
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -189,14 +193,17 @@ jobs:
- run: - run:
name: Move test build to 'dist-test-metrics' to avoid conflict with production build name: Move test build to 'dist-test-metrics' to avoid conflict with production build
command: mv ./dist ./dist-test-metrics command: mv ./dist ./dist-test-metrics
- run:
name: Move test zips to 'builds-test' to avoid conflict with production build
command: mv ./builds ./builds-test-metrics
- persist_to_workspace: - persist_to_workspace:
root: . root: .
paths: paths:
- dist-test-metrics - dist-test-metrics
- builds-test-metrics
prep-build-storybook: prep-build-storybook:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -210,8 +217,7 @@ jobs:
- .out - .out
test-lint: test-lint:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -224,8 +230,7 @@ jobs:
command: yarn verify-locales --quiet command: yarn verify-locales --quiet
test-lint-shellcheck: test-lint-shellcheck:
docker: executor: shellcheck
- image: koalaman/shellcheck-alpine@sha256:35882cba254810c7de458528011e935ba2c4f3ebcb224275dfa7ebfa930ef294
steps: steps:
- checkout - checkout
- run: apk add --no-cache bash jq yarn - run: apk add --no-cache bash jq yarn
@ -234,8 +239,7 @@ jobs:
command: ./development/shellcheck.sh command: ./development/shellcheck.sh
test-lint-lockfile: test-lint-lockfile:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -245,8 +249,7 @@ jobs:
command: yarn lint:lockfile command: yarn lint:lockfile
test-deps: test-deps:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -256,8 +259,7 @@ jobs:
command: .circleci/scripts/yarn-audit command: .circleci/scripts/yarn-audit
test-e2e-chrome: test-e2e-chrome:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -265,6 +267,9 @@ jobs:
- run: - run:
name: Move test build to dist name: Move test build to dist
command: mv ./dist-test ./dist command: mv ./dist-test ./dist
- run:
name: Move test zips to builds
command: mv ./builds-test ./builds
- run: - run:
name: test:e2e:chrome name: test:e2e:chrome
command: | command: |
@ -278,8 +283,7 @@ jobs:
destination: test-artifacts destination: test-artifacts
test-e2e-chrome-metrics: test-e2e-chrome-metrics:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -287,6 +291,9 @@ jobs:
- run: - run:
name: Move test build to dist name: Move test build to dist
command: mv ./dist-test-metrics ./dist command: mv ./dist-test-metrics ./dist
- run:
name: Move test zips to builds
command: mv ./builds-test-metrics ./builds
- run: - run:
name: test:e2e:chrome:metrics name: test:e2e:chrome:metrics
command: | command: |
@ -300,8 +307,7 @@ jobs:
destination: test-artifacts destination: test-artifacts
test-e2e-firefox: test-e2e-firefox:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- run: - run:
@ -312,6 +318,9 @@ jobs:
- run: - run:
name: Move test build to dist name: Move test build to dist
command: mv ./dist-test ./dist command: mv ./dist-test ./dist
- run:
name: Move test zips to builds
command: mv ./builds-test ./builds
- run: - run:
name: test:e2e:firefox name: test:e2e:firefox
command: | command: |
@ -325,8 +334,7 @@ jobs:
destination: test-artifacts destination: test-artifacts
test-e2e-firefox-metrics: test-e2e-firefox-metrics:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- run: - run:
@ -337,6 +345,9 @@ jobs:
- run: - run:
name: Move test build to dist name: Move test build to dist
command: mv ./dist-test-metrics ./dist command: mv ./dist-test-metrics ./dist
- run:
name: Move test zips to builds
command: mv ./builds-test-metrics ./builds
- run: - run:
name: test:e2e:firefox:metrics name: test:e2e:firefox:metrics
command: | command: |
@ -350,8 +361,7 @@ jobs:
destination: test-artifacts destination: test-artifacts
benchmark: benchmark:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -359,6 +369,9 @@ jobs:
- run: - run:
name: Move test build to dist name: Move test build to dist
command: mv ./dist-test ./dist command: mv ./dist-test ./dist
- run:
name: Move test zips to builds
command: mv ./builds-test ./builds
- run: - run:
name: Run page load benchmark name: Run page load benchmark
command: yarn benchmark:chrome --out test-artifacts/chrome/benchmark/pageload.json command: yarn benchmark:chrome --out test-artifacts/chrome/benchmark/pageload.json
@ -371,8 +384,7 @@ jobs:
- test-artifacts - test-artifacts
job-publish-prerelease: job-publish-prerelease:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -386,6 +398,9 @@ jobs:
- store_artifacts: - store_artifacts:
path: builds path: builds
destination: builds destination: builds
- store_artifacts:
path: coverage
destination: coverage
- store_artifacts: - store_artifacts:
path: test-artifacts path: test-artifacts
destination: test-artifacts destination: test-artifacts
@ -403,8 +418,7 @@ jobs:
command: ./development/metamaskbot-build-announce.js command: ./development/metamaskbot-build-announce.js
job-publish-release: job-publish-release:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -421,8 +435,7 @@ jobs:
command: .circleci/scripts/release-create-master-pr command: .circleci/scripts/release-create-master-pr
job-publish-storybook: job-publish-storybook:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- add_ssh_keys: - add_ssh_keys:
fingerprints: fingerprints:
@ -437,8 +450,7 @@ jobs:
yarn storybook:deploy yarn storybook:deploy
test-unit: test-unit:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -452,8 +464,7 @@ jobs:
- .nyc_output - .nyc_output
- coverage - coverage
test-unit-global: test-unit-global:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -463,8 +474,7 @@ jobs:
command: yarn test:unit:global command: yarn test:unit:global
validate-source-maps: validate-source-maps:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -474,8 +484,7 @@ jobs:
command: yarn validate-source-maps command: yarn validate-source-maps
test-mozilla-lint: test-mozilla-lint:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -485,20 +494,8 @@ jobs:
command: NODE_OPTIONS=--max_old_space_size=3072 yarn mozilla-lint command: NODE_OPTIONS=--max_old_space_size=3072 yarn mozilla-lint
all-tests-pass: all-tests-pass:
docker: executor: node-browsers
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps: steps:
- run: - run:
name: All Tests Passed name: All Tests Passed
command: echo 'weew - everything passed!' command: echo 'weew - everything passed!'
coveralls-upload:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Coveralls upload
command: yarn test:coveralls-upload

@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -x
mkdir -p build-artifacts/yarn-install-har
mv ./*.har build-artifacts/yarn-install-har/

@ -7,6 +7,14 @@ set -e
yarn --frozen-lockfile --ignore-scripts --har yarn --frozen-lockfile --ignore-scripts --har
# Move HAR file into directory with consistent name so that we can cache it
mkdir -p build-artifacts/yarn-install-har
har_files=(./*.har)
if [[ -f "${har_files[0]}" ]]
then
mv ./*.har build-artifacts/yarn-install-har/
fi
# run each in subshell so directory change does not persist # run each in subshell so directory change does not persist
# scripts can be any of: # scripts can be any of:
# preinstall # preinstall

@ -4,7 +4,7 @@ set -e
set -u set -u
set -o pipefail set -o pipefail
FIREFOX_VERSION='70.0' FIREFOX_VERSION='83.0'
FIREFOX_BINARY="firefox-${FIREFOX_VERSION}.tar.bz2" FIREFOX_BINARY="firefox-${FIREFOX_VERSION}.tar.bz2"
FIREFOX_BINARY_URL="https://ftp.mozilla.org/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/${FIREFOX_BINARY}" FIREFOX_BINARY_URL="https://ftp.mozilla.org/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/${FIREFOX_BINARY}"
FIREFOX_PATH='/opt/firefox' FIREFOX_PATH='/opt/firefox'

1
.gitignore vendored

@ -41,7 +41,6 @@ ui/app/css/output/
notes.txt notes.txt
.coveralls.yml
.nyc_output .nyc_output
.metamaskrc .metamaskrc

@ -2,10 +2,34 @@
## Current Develop Branch ## Current Develop Branch
## 9.0.0 Fri Jan 8 2021
- [#9156](https://github.com/MetaMask/metamask-extension/pull/9156): Remove window.web3 injection
- [#10039](https://github.com/MetaMask/metamask-extension/pull/10039): Add web3 shim usage notification
- [#8640](https://github.com/MetaMask/metamask-extension/pull/8640): Implement breaking window.ethereum API changes
- [#8629](https://github.com/MetaMask/metamask-extension/pull/8629): Fix `eth_chainId` return values for Infura networks
- [#10019](https://github.com/MetaMask/metamask-extension/pull/10019): Increase Chrome minimum version to v63
- [#10135](https://github.com/MetaMask/metamask-extension/pull/10135): Fix error where a swap only completed the token approval transaction
- [#10100](https://github.com/MetaMask/metamask-extension/pull/10100): Remove unnecessary swaps footer space when in dropdown mode
- [#9905](https://github.com/MetaMask/metamask-extension/pull/9905): Redesign view quote screens
- [#9320](https://github.com/MetaMask/metamask-extension/pull/9320): Prevent hidden tokens from reappearing
- [#10000](https://github.com/MetaMask/metamask-extension/pull/10000): Use consistent font size for modal top right Close links
- [#10046](https://github.com/MetaMask/metamask-extension/pull/10046): Improve home screen notification appearance
- [#10093](https://github.com/MetaMask/metamask-extension/pull/10093): Always roll back to the previously selected network when unable to connect to a newly selected network
- [#10117](https://github.com/MetaMask/metamask-extension/pull/10117): Fix network settings Kovan block explorer link
- [#10143](https://github.com/MetaMask/metamask-extension/pull/10143): Prevent malformed next nonce warning
- [#10142](https://github.com/MetaMask/metamask-extension/pull/10142): Update @metamask/contract-metadata from v1.20.0 to 1.21.0
- [#10160](https://github.com/MetaMask/metamask-extension/pull/10160): Fix French "Block Explorer URL" translations
- [#10157](https://github.com/MetaMask/metamask-extension/pull/10157): Automatically detect tokens on custom Mainnet RPC endpoints
- [#9772](https://github.com/MetaMask/metamask-extension/pull/9772): Improve zh_CN translation
- [#10170](https://github.com/MetaMask/metamask-extension/pull/10170): Fix bug where swaps button was disabled on Mainnet if the user hadn't switched networks in a long time
## 8.1.11 Thu Jan 07 2021 ## 8.1.11 Thu Jan 07 2021
- [#10155](https://github.com/MetaMask/metamask-extension/pull/10155): Disable swaps when the current network's chainId does not match the mainnet chain ID, instead of disabling based on network ID - [#10155](https://github.com/MetaMask/metamask-extension/pull/10155): Disable swaps when the current network's chainId does not match the mainnet chain ID, instead of disabling based on network ID
## 8.1.10 Fri Dec 18 2020 ## 8.1.10 Fri Dec 18 2020
- [#10084](https://github.com/MetaMask/metamask-extension/pull/10084): Set last provider when switching to a customRPC - [#10084](https://github.com/MetaMask/metamask-extension/pull/10084): Set last provider when switching to a customRPC
- [#10096](https://github.com/MetaMask/metamask-extension/pull/10096): Update `@metamask/controllers` to v5.1.0 - [#10096](https://github.com/MetaMask/metamask-extension/pull/10096): Update `@metamask/controllers` to v5.1.0
- [#10103](https://github.com/MetaMask/metamask-extension/pull/10103): Prevent stuck loading screen in some situations - [#10103](https://github.com/MetaMask/metamask-extension/pull/10103): Prevent stuck loading screen in some situations
@ -13,23 +37,28 @@
- [#10110](https://github.com/MetaMask/metamask-extension/pull/10110): Fix frozen loading screen on Firefox when strict Enhanced Tracking Protection is enabled - [#10110](https://github.com/MetaMask/metamask-extension/pull/10110): Fix frozen loading screen on Firefox when strict Enhanced Tracking Protection is enabled
## 8.1.9 Tue Dec 15 2020 ## 8.1.9 Tue Dec 15 2020
- [#10034](https://github.com/MetaMask/metamask-extension/pull/10034): Fix contentscript injection failure on Firefox 56 - [#10034](https://github.com/MetaMask/metamask-extension/pull/10034): Fix contentscript injection failure on Firefox 56
- [#10045](https://github.com/MetaMask/metamask-extension/pull/10045): Fix token validation in Send flow - [#10045](https://github.com/MetaMask/metamask-extension/pull/10045): Fix token validation in Send flow
- [#10048](https://github.com/MetaMask/metamask-extension/pull/10048): Display boolean values when signing typed data - [#10048](https://github.com/MetaMask/metamask-extension/pull/10048): Display boolean values when signing typed data
- [#10070](https://github.com/MetaMask/metamask-extension/pull/10070): Add eth_getProof - [#10070](https://github.com/MetaMask/metamask-extension/pull/10070): Add eth_getProof
- [#10043](https://github.com/MetaMask/metamask-extension/pull/10043): Improve swaps maximum gas estimation - [#10043](https://github.com/MetaMask/metamask-extension/pull/10043): Improve swaps maximum gas estimation
- [#10069](https://github.com/MetaMask/metamask-extension/pull/10069): Fetch swap quote refresh time from API - [#10069](https://github.com/MetaMask/metamask-extension/pull/10069): Fetch swap quote refresh time from API
- [#10040](https://github.com/MetaMask/metamask-extension/pull/10040): Disable console in contentscript to reduce noise
## 8.1.8 Wed Dec 09 2020 ## 8.1.8 Wed Dec 09 2020
- [#9992](https://github.com/MetaMask/metamask-extension/pull/9992): Improve transaction params validation - [#9992](https://github.com/MetaMask/metamask-extension/pull/9992): Improve transaction params validation
- [#9991](https://github.com/MetaMask/metamask-extension/pull/9991): Don't allow more than 15% slippage - [#9991](https://github.com/MetaMask/metamask-extension/pull/9991): Don't allow more than 15% slippage
- [#9994](https://github.com/MetaMask/metamask-extension/pull/9994): Prevent unwanted 'no quotes available' message when going back to build quote screen while having insufficient funds - [#9994](https://github.com/MetaMask/metamask-extension/pull/9994): Prevent unwanted 'no quotes available' message when going back to build quote screen while having insufficient funds
- [#9999](https://github.com/MetaMask/metamask-extension/pull/9999): Fix missing contacts upon restart - [#9999](https://github.com/MetaMask/metamask-extension/pull/9999): Fix missing contacts upon restart
## 8.1.7 Tue Dec 08 2020 ## 8.1.7 Tue Dec 08 2020
- Revert SES lockdown - Revert SES lockdown
## 8.1.6 Wed Dec 02 2020 ## 8.1.6 Wed Dec 02 2020
- [#9916](https://github.com/MetaMask/metamask-extension/pull/9916): Fix QR code scans interpretting payment requests as token addresses - [#9916](https://github.com/MetaMask/metamask-extension/pull/9916): Fix QR code scans interpretting payment requests as token addresses
- [#9847](https://github.com/MetaMask/metamask-extension/pull/9847): Add alt text for images in list items - [#9847](https://github.com/MetaMask/metamask-extension/pull/9847): Add alt text for images in list items
- [#9960](https://github.com/MetaMask/metamask-extension/pull/9960): Ensure watchAsset returns errors for invalid token symbols - [#9960](https://github.com/MetaMask/metamask-extension/pull/9960): Ensure watchAsset returns errors for invalid token symbols
@ -41,6 +70,7 @@
- [#9993](https://github.com/MetaMask/metamask-extension/pull/9993): Add 48x48 MetaMask icon for use by browsers - [#9993](https://github.com/MetaMask/metamask-extension/pull/9993): Add 48x48 MetaMask icon for use by browsers
## 8.1.5 Wed Nov 18 2020 ## 8.1.5 Wed Nov 18 2020
- [#9871](https://github.com/MetaMask/metamask-extension/pull/9871): Show send text upon hover in main asset list - [#9871](https://github.com/MetaMask/metamask-extension/pull/9871): Show send text upon hover in main asset list
- [#9855](https://github.com/MetaMask/metamask-extension/pull/9855): Make edit icon and account name in account details modal focusable - [#9855](https://github.com/MetaMask/metamask-extension/pull/9855): Make edit icon and account name in account details modal focusable
- [#9853](https://github.com/MetaMask/metamask-extension/pull/9853): Provide alternative text for images where appropriate - [#9853](https://github.com/MetaMask/metamask-extension/pull/9853): Provide alternative text for images where appropriate
@ -54,8 +84,8 @@
- [#9911](https://github.com/MetaMask/metamask-extension/pull/9911): Fix display of Ledger connection error - [#9911](https://github.com/MetaMask/metamask-extension/pull/9911): Fix display of Ledger connection error
- [#9918](https://github.com/MetaMask/metamask-extension/pull/9918): Fix missing icon in asset page dropdown and in advanced gas modal button group - [#9918](https://github.com/MetaMask/metamask-extension/pull/9918): Fix missing icon in asset page dropdown and in advanced gas modal button group
## 8.1.4 Tue Nov 10 2020 ## 8.1.4 Tue Nov 10 2020
- [#9687](https://github.com/MetaMask/metamask-extension/pull/9687): Allow speeding up of underpriced transactions - [#9687](https://github.com/MetaMask/metamask-extension/pull/9687): Allow speeding up of underpriced transactions
- [#9694](https://github.com/MetaMask/metamask-extension/pull/9694): normalize UI component font styles - [#9694](https://github.com/MetaMask/metamask-extension/pull/9694): normalize UI component font styles
- [#9695](https://github.com/MetaMask/metamask-extension/pull/9695): normalize app component font styles - [#9695](https://github.com/MetaMask/metamask-extension/pull/9695): normalize app component font styles
@ -90,6 +120,7 @@
- [#9880](https://github.com/MetaMask/metamask-extension/pull/9880): Properly detect U2F errors in hardware wallet - [#9880](https://github.com/MetaMask/metamask-extension/pull/9880): Properly detect U2F errors in hardware wallet
## 8.1.3 Mon Oct 26 2020 ## 8.1.3 Mon Oct 26 2020
- [#9642](https://github.com/MetaMask/metamask-extension/pull/9642) Prevent excessive overflow from swap dropdowns - [#9642](https://github.com/MetaMask/metamask-extension/pull/9642) Prevent excessive overflow from swap dropdowns
- [#9658](https://github.com/MetaMask/metamask-extension/pull/9658): Fix sorting Quote Source column of quote sort list - [#9658](https://github.com/MetaMask/metamask-extension/pull/9658): Fix sorting Quote Source column of quote sort list
- [#9667](https://github.com/MetaMask/metamask-extension/pull/9667): Fix adding contact with QR code - [#9667](https://github.com/MetaMask/metamask-extension/pull/9667): Fix adding contact with QR code
@ -108,6 +139,7 @@
- [#9715](https://github.com/MetaMask/metamask-extension/pull/9715): Focus on wallet address in buy workflow - [#9715](https://github.com/MetaMask/metamask-extension/pull/9715): Focus on wallet address in buy workflow
## 8.1.2 Mon Oct 19 2020 ## 8.1.2 Mon Oct 19 2020
- [#9608](https://github.com/MetaMask/metamask-extension/pull/9608): Ensure QR code scanner works - [#9608](https://github.com/MetaMask/metamask-extension/pull/9608): Ensure QR code scanner works
- [#9624](https://github.com/MetaMask/metamask-extension/pull/9624): Help users avoid insufficient gas prices in swaps - [#9624](https://github.com/MetaMask/metamask-extension/pull/9624): Help users avoid insufficient gas prices in swaps
- [#9614](https://github.com/MetaMask/metamask-extension/pull/9614): Update swaps network fee tooltip - [#9614](https://github.com/MetaMask/metamask-extension/pull/9614): Update swaps network fee tooltip
@ -116,6 +148,7 @@
- [#9633](https://github.com/MetaMask/metamask-extension/pull/9633): Update View Quote page to better represent the MetaMask fee - [#9633](https://github.com/MetaMask/metamask-extension/pull/9633): Update View Quote page to better represent the MetaMask fee
## 8.1.1 Tue Oct 13 2020 ## 8.1.1 Tue Oct 13 2020
- [#9586](https://github.com/MetaMask/metamask-extension/pull/9586): Prevent build quote crash when swapping from non-tracked token with balance (#9586) - [#9586](https://github.com/MetaMask/metamask-extension/pull/9586): Prevent build quote crash when swapping from non-tracked token with balance (#9586)
- [#9592](https://github.com/MetaMask/metamask-extension/pull/9592): Remove commitment to maintain a public metrics dashboard (#9592) - [#9592](https://github.com/MetaMask/metamask-extension/pull/9592): Remove commitment to maintain a public metrics dashboard (#9592)
- [#9596](https://github.com/MetaMask/metamask-extension/pull/9596): Fix TypeError when `signTypedData` throws (#9596) - [#9596](https://github.com/MetaMask/metamask-extension/pull/9596): Fix TypeError when `signTypedData` throws (#9596)
@ -128,6 +161,7 @@
- [#9609](https://github.com/MetaMask/metamask-extension/pull/9609): Ensure swaps customize gas modal values are set correctly (#9609) - [#9609](https://github.com/MetaMask/metamask-extension/pull/9609): Ensure swaps customize gas modal values are set correctly (#9609)
## 8.1.0 Tue Oct 13 2020 ## 8.1.0 Tue Oct 13 2020
- [#9565](https://github.com/MetaMask/metamask-extension/pull/9565): Ensure address book entries are shared between networks with the same chain ID - [#9565](https://github.com/MetaMask/metamask-extension/pull/9565): Ensure address book entries are shared between networks with the same chain ID
- [#9552](https://github.com/MetaMask/metamask-extension/pull/9552): Fix `eth_signTypedData_v4` chain ID validation for non-default networks - [#9552](https://github.com/MetaMask/metamask-extension/pull/9552): Fix `eth_signTypedData_v4` chain ID validation for non-default networks
- [#9551](https://github.com/MetaMask/metamask-extension/pull/9551): Allow the "Localhost 8545" network to be edited, and require a chain ID to be specified for it - [#9551](https://github.com/MetaMask/metamask-extension/pull/9551): Allow the "Localhost 8545" network to be edited, and require a chain ID to be specified for it
@ -160,21 +194,25 @@
- [#9073](https://github.com/MetaMask/metamask-extension/pull/9073): Use new Euclid font throughout MetaMask - [#9073](https://github.com/MetaMask/metamask-extension/pull/9073): Use new Euclid font throughout MetaMask
## 8.0.10 Wed Sep 16 2020 ## 8.0.10 Wed Sep 16 2020
- [#9423](https://github.com/MetaMask/metamask-extension/pull/9423): Update default phishing list - [#9423](https://github.com/MetaMask/metamask-extension/pull/9423): Update default phishing list
- [#9416](https://github.com/MetaMask/metamask-extension/pull/9416): Fix fetching a new phishing list on Firefox - [#9416](https://github.com/MetaMask/metamask-extension/pull/9416): Fix fetching a new phishing list on Firefox
## 8.0.9 Wed Aug 19 2020 ## 8.0.9 Wed Aug 19 2020
- [#9228](https://github.com/MetaMask/metamask-extension/pull/9228): Move transaction confirmation footer buttons to scrollable area - [#9228](https://github.com/MetaMask/metamask-extension/pull/9228): Move transaction confirmation footer buttons to scrollable area
- [#9256](https://github.com/MetaMask/metamask-extension/pull/9256): Handle non-String web3 property access - [#9256](https://github.com/MetaMask/metamask-extension/pull/9256): Handle non-String web3 property access
- [#9266](https://github.com/MetaMask/metamask-extension/pull/9266): Use @metamask/controllers@2.0.5 - [#9266](https://github.com/MetaMask/metamask-extension/pull/9266): Use @metamask/controllers@2.0.5
- [#9189](https://github.com/MetaMask/metamask-extension/pull/9189): Hide ETH Gas Station estimates on non-main network - [#9189](https://github.com/MetaMask/metamask-extension/pull/9189): Hide ETH Gas Station estimates on non-main network
## 8.0.8 Fri Aug 14 2020 ## 8.0.8 Fri Aug 14 2020
- [#9211](https://github.com/MetaMask/metamask-extension/pull/9211): Fix Etherscan redirect on notification click - [#9211](https://github.com/MetaMask/metamask-extension/pull/9211): Fix Etherscan redirect on notification click
- [#9237](https://github.com/MetaMask/metamask-extension/pull/9237): Reduce volume of web3 usage metrics - [#9237](https://github.com/MetaMask/metamask-extension/pull/9237): Reduce volume of web3 usage metrics
- [#9227](https://github.com/MetaMask/metamask-extension/pull/9227): Permit all-caps addresses - [#9227](https://github.com/MetaMask/metamask-extension/pull/9227): Permit all-caps addresses
## 8.0.7 Fri Aug 07 2020 ## 8.0.7 Fri Aug 07 2020
- [#9065](https://github.com/MetaMask/metamask-extension/pull/9065): Change title of "Reveal Seed Words" page to "Reveal Seed Phrase" - [#9065](https://github.com/MetaMask/metamask-extension/pull/9065): Change title of "Reveal Seed Words" page to "Reveal Seed Phrase"
- [#8974](https://github.com/MetaMask/metamask-extension/pull/8974): Add tooltip to copy button for contacts and seed phrase - [#8974](https://github.com/MetaMask/metamask-extension/pull/8974): Add tooltip to copy button for contacts and seed phrase
- [#9063](https://github.com/MetaMask/metamask-extension/pull/9063): Fix broken UI upon failed password validation - [#9063](https://github.com/MetaMask/metamask-extension/pull/9063): Fix broken UI upon failed password validation
@ -187,6 +225,7 @@
- [#9144](https://github.com/MetaMask/metamask-extension/pull/9144): Add web3 usage metrics and prepare for web3 removal - [#9144](https://github.com/MetaMask/metamask-extension/pull/9144): Add web3 usage metrics and prepare for web3 removal
## 8.0.6 Wed Jul 22 2020 ## 8.0.6 Wed Jul 22 2020
- [#9030](https://github.com/MetaMask/metamask-extension/pull/9030): Hide "delete" button when editing contact of wallet account - [#9030](https://github.com/MetaMask/metamask-extension/pull/9030): Hide "delete" button when editing contact of wallet account
- [#9031](https://github.com/MetaMask/metamask-extension/pull/9031): Fix crash upon removing contact - [#9031](https://github.com/MetaMask/metamask-extension/pull/9031): Fix crash upon removing contact
- [#9032](https://github.com/MetaMask/metamask-extension/pull/9032): Do not show spend limit for approvals - [#9032](https://github.com/MetaMask/metamask-extension/pull/9032): Do not show spend limit for approvals
@ -196,6 +235,7 @@
- [#9056](https://github.com/MetaMask/metamask-extension/pull/9056): Display at least one significant digit of small non-zero token balances - [#9056](https://github.com/MetaMask/metamask-extension/pull/9056): Display at least one significant digit of small non-zero token balances
## 8.0.5 Thu Jul 16 2020 ## 8.0.5 Thu Jul 16 2020
- [#8942](https://github.com/MetaMask/metamask-extension/pull/8942): Fix display of incoming transactions (#8942) - [#8942](https://github.com/MetaMask/metamask-extension/pull/8942): Fix display of incoming transactions (#8942)
- [#8998](https://github.com/MetaMask/metamask-extension/pull/8998): Fix `web3_clientVersion` method (#8998) - [#8998](https://github.com/MetaMask/metamask-extension/pull/8998): Fix `web3_clientVersion` method (#8998)
- [#9003](https://github.com/MetaMask/metamask-extension/pull/9003): @metamask/inpage-provider@6.0.1 (#9003) - [#9003](https://github.com/MetaMask/metamask-extension/pull/9003): @metamask/inpage-provider@6.0.1 (#9003)
@ -208,16 +248,19 @@
- [#9026](https://github.com/MetaMask/metamask-extension/pull/9026): Clear transactions on createNewVaultAndRestore (#9026) - [#9026](https://github.com/MetaMask/metamask-extension/pull/9026): Clear transactions on createNewVaultAndRestore (#9026)
## 8.0.4 Tue Jul 07 2020 ## 8.0.4 Tue Jul 07 2020
- [#8934](https://github.com/MetaMask/metamask-extension/pull/8934): Fix transaction activity on custom networks - [#8934](https://github.com/MetaMask/metamask-extension/pull/8934): Fix transaction activity on custom networks
- [#8936](https://github.com/MetaMask/metamask-extension/pull/8936): Fix account tracker optimization - [#8936](https://github.com/MetaMask/metamask-extension/pull/8936): Fix account tracker optimization
## 8.0.3 Mon Jul 06 2020 ## 8.0.3 Mon Jul 06 2020
- [#8921](https://github.com/MetaMask/metamask-extension/pull/8921): Restore missing 'data' provider event, and fix 'notification' event - [#8921](https://github.com/MetaMask/metamask-extension/pull/8921): Restore missing 'data' provider event, and fix 'notification' event
- [#8923](https://github.com/MetaMask/metamask-extension/pull/8923): Normalize the 'from' parameter for `eth_sendTransaction` - [#8923](https://github.com/MetaMask/metamask-extension/pull/8923): Normalize the 'from' parameter for `eth_sendTransaction`
- [#8924](https://github.com/MetaMask/metamask-extension/pull/8924): Fix handling of multiple `eth_requestAccount` messages from the same domain - [#8924](https://github.com/MetaMask/metamask-extension/pull/8924): Fix handling of multiple `eth_requestAccount` messages from the same domain
- [#8917](https://github.com/MetaMask/metamask-extension/pull/8917): Update Italian translations - [#8917](https://github.com/MetaMask/metamask-extension/pull/8917): Update Italian translations
## 8.0.2 Fri Jul 03 2020 ## 8.0.2 Fri Jul 03 2020
- [#8907](https://github.com/MetaMask/metamask-extension/pull/8907): Tolerate missing or falsey substitutions - [#8907](https://github.com/MetaMask/metamask-extension/pull/8907): Tolerate missing or falsey substitutions
- [#8908](https://github.com/MetaMask/metamask-extension/pull/8908): Fix activity log inline buttons - [#8908](https://github.com/MetaMask/metamask-extension/pull/8908): Fix activity log inline buttons
- [#8909](https://github.com/MetaMask/metamask-extension/pull/8909): Prevent confirming blank suggested token - [#8909](https://github.com/MetaMask/metamask-extension/pull/8909): Prevent confirming blank suggested token
@ -225,6 +268,7 @@
- [#8913](https://github.com/MetaMask/metamask-extension/pull/8913): Fix Kovan chain ID constant - [#8913](https://github.com/MetaMask/metamask-extension/pull/8913): Fix Kovan chain ID constant
## 8.0.1 Thu Jul 02 2020 ## 8.0.1 Thu Jul 02 2020
- [#8874](https://github.com/MetaMask/metamask-extension/pull/8874): Fx overflow behaviour of add token list - [#8874](https://github.com/MetaMask/metamask-extension/pull/8874): Fx overflow behaviour of add token list
- [#8885](https://github.com/MetaMask/metamask-extension/pull/8885): Show `origin` in connect flow rather than site name - [#8885](https://github.com/MetaMask/metamask-extension/pull/8885): Show `origin` in connect flow rather than site name
- [#8883](https://github.com/MetaMask/metamask-extension/pull/8883): Allow setting a custom nonce of zero - [#8883](https://github.com/MetaMask/metamask-extension/pull/8883): Allow setting a custom nonce of zero
@ -237,6 +281,7 @@
- [#8898](https://github.com/MetaMask/metamask-extension/pull/8898): Replace percentage opacity value - [#8898](https://github.com/MetaMask/metamask-extension/pull/8898): Replace percentage opacity value
## 8.0.0 Mon Jun 23 2020 ## 8.0.0 Mon Jun 23 2020
- [#7004](https://github.com/MetaMask/metamask-extension/pull/7004): Add permission system - [#7004](https://github.com/MetaMask/metamask-extension/pull/7004): Add permission system
- [#7261](https://github.com/MetaMask/metamask-extension/pull/7261): Search accounts by name - [#7261](https://github.com/MetaMask/metamask-extension/pull/7261): Search accounts by name
- [#7483](https://github.com/MetaMask/metamask-extension/pull/7483): Buffer 3 blocks before dropping a transaction - [#7483](https://github.com/MetaMask/metamask-extension/pull/7483): Buffer 3 blocks before dropping a transaction
@ -328,6 +373,7 @@
- [#8631](https://github.com/MetaMask/metamask-extension/pull/8631): Include imported accounts in mobile sync - [#8631](https://github.com/MetaMask/metamask-extension/pull/8631): Include imported accounts in mobile sync
## 7.7.9 Tue Apr 28 2020 ## 7.7.9 Tue Apr 28 2020
- [#8446](https://github.com/MetaMask/metamask-extension/pull/8446): Fix popup not opening - [#8446](https://github.com/MetaMask/metamask-extension/pull/8446): Fix popup not opening
- [#8449](https://github.com/MetaMask/metamask-extension/pull/8449): Skip adding history entry for empty txMeta diffs - [#8449](https://github.com/MetaMask/metamask-extension/pull/8449): Skip adding history entry for empty txMeta diffs
- [#8447](https://github.com/MetaMask/metamask-extension/pull/8447): Delete Dai/Sai migration notification - [#8447](https://github.com/MetaMask/metamask-extension/pull/8447): Delete Dai/Sai migration notification
@ -345,23 +391,28 @@
- [#8509](https://github.com/MetaMask/metamask-extension/pull/8509): Fix Tohen Typo - [#8509](https://github.com/MetaMask/metamask-extension/pull/8509): Fix Tohen Typo
## 7.7.8 Wed Mar 11 2020 ## 7.7.8 Wed Mar 11 2020
- [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked
- [#8178](https://github.com/MetaMask/metamask-extension/pull/8178): Use specified gas limit when speeding up a transaction - [#8178](https://github.com/MetaMask/metamask-extension/pull/8178): Use specified gas limit when speeding up a transaction
## 7.7.7 Wed Mar 04 2020 ## 7.7.7 Wed Mar 04 2020
- [#8162](https://github.com/MetaMask/metamask-extension/pull/8162): Remove invalid Ledger accounts - [#8162](https://github.com/MetaMask/metamask-extension/pull/8162): Remove invalid Ledger accounts
- [#8163](https://github.com/MetaMask/metamask-extension/pull/8163): Fix account index check - [#8163](https://github.com/MetaMask/metamask-extension/pull/8163): Fix account index check
## 7.7.6 Mon Mar 02 2020 ## 7.7.6 Mon Mar 02 2020
- [#8154](https://github.com/MetaMask/metamask-extension/pull/8154): Prevent signing from incorrect Ledger account - [#8154](https://github.com/MetaMask/metamask-extension/pull/8154): Prevent signing from incorrect Ledger account
## 7.7.5 Fri Feb 14 2020 ## 7.7.5 Fri Feb 14 2020
- [#8053](https://github.com/MetaMask/metamask-extension/pull/8053): Inline the source text not the binary encoding for inpage script - [#8053](https://github.com/MetaMask/metamask-extension/pull/8053): Inline the source text not the binary encoding for inpage script
- [#8049](https://github.com/MetaMask/metamask-extension/pull/8049): Add warning to watchAsset API when editing a known token - [#8049](https://github.com/MetaMask/metamask-extension/pull/8049): Add warning to watchAsset API when editing a known token
- [#8051](https://github.com/MetaMask/metamask-extension/pull/8051): Update Wyre ETH purchase url - [#8051](https://github.com/MetaMask/metamask-extension/pull/8051): Update Wyre ETH purchase url
- [#8059](https://github.com/MetaMask/metamask-extension/pull/8059): Attempt ENS resolution on any valid domain name - [#8059](https://github.com/MetaMask/metamask-extension/pull/8059): Attempt ENS resolution on any valid domain name
## 7.7.4 Wed Jan 29 2020 ## 7.7.4 Wed Jan 29 2020
- [#7918](https://github.com/MetaMask/metamask-extension/pull/7918): Update data on Approve screen after updating custom spend limit - [#7918](https://github.com/MetaMask/metamask-extension/pull/7918): Update data on Approve screen after updating custom spend limit
- [#7919](https://github.com/MetaMask/metamask-extension/pull/7919): Allow editing max spend limit - [#7919](https://github.com/MetaMask/metamask-extension/pull/7919): Allow editing max spend limit
- [#7920](https://github.com/MetaMask/metamask-extension/pull/7920): Validate custom spend limit - [#7920](https://github.com/MetaMask/metamask-extension/pull/7920): Validate custom spend limit
@ -369,15 +420,18 @@
- [#7954](https://github.com/MetaMask/metamask-extension/pull/7954): Update ENS registry addresses - [#7954](https://github.com/MetaMask/metamask-extension/pull/7954): Update ENS registry addresses
## 7.7.3 Fri Jan 24 2020 ## 7.7.3 Fri Jan 24 2020
- [#7894](https://github.com/MetaMask/metamask-extension/pull/7894): Update GABA dependency version - [#7894](https://github.com/MetaMask/metamask-extension/pull/7894): Update GABA dependency version
- [#7901](https://github.com/MetaMask/metamask-extension/pull/7901): Use eth-contract-metadata@1.12.1 - [#7901](https://github.com/MetaMask/metamask-extension/pull/7901): Use eth-contract-metadata@1.12.1
- [#7910](https://github.com/MetaMask/metamask-extension/pull/7910): Fixing broken JSON import help link - [#7910](https://github.com/MetaMask/metamask-extension/pull/7910): Fixing broken JSON import help link
## 7.7.2 Fri Jan 10 2020 ## 7.7.2 Fri Jan 10 2020
- [#7753](https://github.com/MetaMask/metamask-extension/pull/7753): Fix gas estimate for tokens - [#7753](https://github.com/MetaMask/metamask-extension/pull/7753): Fix gas estimate for tokens
- [#7473](https://github.com/MetaMask/metamask-extension/pull/7473): Fix transaction order on transaction confirmation screen - [#7473](https://github.com/MetaMask/metamask-extension/pull/7473): Fix transaction order on transaction confirmation screen
## 7.7.1 Wed Dec 04 2019 ## 7.7.1 Wed Dec 04 2019
- [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction - [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction
- [#7491](https://github.com/MetaMask/metamask-extension/pull/7491): Update gas when asset is changed on send screen - [#7491](https://github.com/MetaMask/metamask-extension/pull/7491): Update gas when asset is changed on send screen
- [#7500](https://github.com/MetaMask/metamask-extension/pull/7500): Remove unused onClick prop from Dropdown component - [#7500](https://github.com/MetaMask/metamask-extension/pull/7500): Remove unused onClick prop from Dropdown component
@ -392,6 +446,7 @@
- [#7558](https://github.com/MetaMask/metamask-extension/pull/7558): Use localized messages for NotificationModal buttons - [#7558](https://github.com/MetaMask/metamask-extension/pull/7558): Use localized messages for NotificationModal buttons
## 7.7.0 Thu Nov 28 2019 [WITHDRAWN] ## 7.7.0 Thu Nov 28 2019 [WITHDRAWN]
- [#7004](https://github.com/MetaMask/metamask-extension/pull/7004): Connect distinct accounts per site - [#7004](https://github.com/MetaMask/metamask-extension/pull/7004): Connect distinct accounts per site
- [#7480](https://github.com/MetaMask/metamask-extension/pull/7480): Fixed link on root README.md - [#7480](https://github.com/MetaMask/metamask-extension/pull/7480): Fixed link on root README.md
- [#7482](https://github.com/MetaMask/metamask-extension/pull/7482): Update Wyre ETH purchase url - [#7482](https://github.com/MetaMask/metamask-extension/pull/7482): Update Wyre ETH purchase url
@ -405,16 +460,19 @@
- [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction - [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction
## 7.6.1 Tue Nov 19 2019 ## 7.6.1 Tue Nov 19 2019
- [#7475](https://github.com/MetaMask/metamask-extension/pull/7475): Add 'Remind Me Later' to the Maker notification - [#7475](https://github.com/MetaMask/metamask-extension/pull/7475): Add 'Remind Me Later' to the Maker notification
- [#7436](https://github.com/MetaMask/metamask-extension/pull/7436): Add additional rpcUrl verification - [#7436](https://github.com/MetaMask/metamask-extension/pull/7436): Add additional rpcUrl verification
- [#7468](https://github.com/MetaMask/metamask-extension/pull/7468): Show transaction fee units on approve screen - [#7468](https://github.com/MetaMask/metamask-extension/pull/7468): Show transaction fee units on approve screen
## 7.6.0 Mon Nov 18 2019 ## 7.6.0 Mon Nov 18 2019
- [#7450](https://github.com/MetaMask/metamask-extension/pull/7450): Add migration notification for users with non-zero Sai - [#7450](https://github.com/MetaMask/metamask-extension/pull/7450): Add migration notification for users with non-zero Sai
- [#7461](https://github.com/MetaMask/metamask-extension/pull/7461): Import styles for showing multiple notifications - [#7461](https://github.com/MetaMask/metamask-extension/pull/7461): Import styles for showing multiple notifications
- [#7451](https://github.com/MetaMask/metamask-extension/pull/7451): Add button disabled when password is empty - [#7451](https://github.com/MetaMask/metamask-extension/pull/7451): Add button disabled when password is empty
## 7.5.3 Fri Nov 15 2019 ## 7.5.3 Fri Nov 15 2019
- [#7412](https://github.com/MetaMask/metamask-extension/pull/7412): lock eth-contract-metadata (#7412) - [#7412](https://github.com/MetaMask/metamask-extension/pull/7412): lock eth-contract-metadata (#7412)
- [#7416](https://github.com/MetaMask/metamask-extension/pull/7416): Add eslint import plugin to help detect unresolved paths - [#7416](https://github.com/MetaMask/metamask-extension/pull/7416): Add eslint import plugin to help detect unresolved paths
- [#7414](https://github.com/MetaMask/metamask-extension/pull/7414): Ensure SignatureRequestOriginal 'beforeunload' handler is bound (#7414) - [#7414](https://github.com/MetaMask/metamask-extension/pull/7414): Ensure SignatureRequestOriginal 'beforeunload' handler is bound (#7414)
@ -429,14 +487,17 @@
- [#7419](https://github.com/MetaMask/metamask-extension/pull/7419): Added webRequest.RequestFilter to filter main_frame .eth requests (#7419) - [#7419](https://github.com/MetaMask/metamask-extension/pull/7419): Added webRequest.RequestFilter to filter main_frame .eth requests (#7419)
## 7.5.2 Thu Nov 14 2019 ## 7.5.2 Thu Nov 14 2019
- [#7414](https://github.com/MetaMask/metamask-extension/pull/7414): Ensure SignatureRequestOriginal 'beforeunload' handler is bound - [#7414](https://github.com/MetaMask/metamask-extension/pull/7414): Ensure SignatureRequestOriginal 'beforeunload' handler is bound
## 7.5.1 Tuesday Nov 13 2019 ## 7.5.1 Tuesday Nov 13 2019
- [#7402](https://github.com/MetaMask/metamask-extension/pull/7402): Fix regression for signed types data screens - [#7402](https://github.com/MetaMask/metamask-extension/pull/7402): Fix regression for signed types data screens
- [#7390](https://github.com/MetaMask/metamask-extension/pull/7390): Update json-rpc-engine - [#7390](https://github.com/MetaMask/metamask-extension/pull/7390): Update json-rpc-engine
- [#7401](https://github.com/MetaMask/metamask-extension/pull/7401): Reject connection request on window close - [#7401](https://github.com/MetaMask/metamask-extension/pull/7401): Reject connection request on window close
## 7.5.0 Mon Nov 04 2019 ## 7.5.0 Mon Nov 04 2019
- [#7328](https://github.com/MetaMask/metamask-extension/pull/7328): ignore known transactions on first broadcast and continue with normal flow - [#7328](https://github.com/MetaMask/metamask-extension/pull/7328): ignore known transactions on first broadcast and continue with normal flow
- [#7327](https://github.com/MetaMask/metamask-extension/pull/7327): eth_getTransactionByHash will now check metamask's local history for pending transactions - [#7327](https://github.com/MetaMask/metamask-extension/pull/7327): eth_getTransactionByHash will now check metamask's local history for pending transactions
- [#7333](https://github.com/MetaMask/metamask-extension/pull/7333): Cleanup beforeunload handler after transaction is resolved - [#7333](https://github.com/MetaMask/metamask-extension/pull/7333): Cleanup beforeunload handler after transaction is resolved
@ -455,6 +516,7 @@
- [#7335](https://github.com/MetaMask/metamask-extension/pull/7335): Add onbeforeunload and have it call onCancel - [#7335](https://github.com/MetaMask/metamask-extension/pull/7335): Add onbeforeunload and have it call onCancel
## 7.4.0 Tue Oct 29 2019 ## 7.4.0 Tue Oct 29 2019
- [#7186](https://github.com/MetaMask/metamask-extension/pull/7186): Use `AdvancedGasInputs` in `AdvancedTabContent` - [#7186](https://github.com/MetaMask/metamask-extension/pull/7186): Use `AdvancedGasInputs` in `AdvancedTabContent`
- [#7304](https://github.com/MetaMask/metamask-extension/pull/7304): Move signTypedData signing out to keyrings - [#7304](https://github.com/MetaMask/metamask-extension/pull/7304): Move signTypedData signing out to keyrings
- [#7306](https://github.com/MetaMask/metamask-extension/pull/7306): correct the zh-TW translation - [#7306](https://github.com/MetaMask/metamask-extension/pull/7306): correct the zh-TW translation
@ -468,9 +530,11 @@
- [#7334](https://github.com/MetaMask/metamask-extension/pull/7334): Add web3 deprecation warning - [#7334](https://github.com/MetaMask/metamask-extension/pull/7334): Add web3 deprecation warning
## 7.3.1 Mon Oct 21 2019 ## 7.3.1 Mon Oct 21 2019
- [#7298](https://github.com/MetaMask/metamask-extension/pull/7298): Turn off full screen vs popup a/b test - [#7298](https://github.com/MetaMask/metamask-extension/pull/7298): Turn off full screen vs popup a/b test
## 7.3.0 Fri Sep 27 2019 ## 7.3.0 Fri Sep 27 2019
- [#6972](https://github.com/MetaMask/metamask-extension/pull/6972): 3box integration - [#6972](https://github.com/MetaMask/metamask-extension/pull/6972): 3box integration
- [#7168](https://github.com/MetaMask/metamask-extension/pull/7168): Add fixes for German translations - [#7168](https://github.com/MetaMask/metamask-extension/pull/7168): Add fixes for German translations
- [#7170](https://github.com/MetaMask/metamask-extension/pull/7170): Remove the disk store - [#7170](https://github.com/MetaMask/metamask-extension/pull/7170): Remove the disk store
@ -490,17 +554,21 @@
- [#7287](https://github.com/MetaMask/metamask-extension/pull/7287): Fix phishing detect script - [#7287](https://github.com/MetaMask/metamask-extension/pull/7287): Fix phishing detect script
## 7.2.3 Fri Oct 04 2019 ## 7.2.3 Fri Oct 04 2019
- [#7252](https://github.com/MetaMask/metamask-extension/pull/7252): Fix gas limit when sending tx without data to a contract - [#7252](https://github.com/MetaMask/metamask-extension/pull/7252): Fix gas limit when sending tx without data to a contract
- [#7260](https://github.com/MetaMask/metamask-extension/pull/7260): Do not transate on seed phrases - [#7260](https://github.com/MetaMask/metamask-extension/pull/7260): Do not transate on seed phrases
- [#7252](https://github.com/MetaMask/metamask-extension/pull/7252): Ensure correct tx category when sending to contracts without tx data - [#7252](https://github.com/MetaMask/metamask-extension/pull/7252): Ensure correct tx category when sending to contracts without tx data
## 7.2.2 Tue Sep 24 2019 ## 7.2.2 Tue Sep 24 2019
- [#7213](https://github.com/MetaMask/metamask-extension/pull/7213): Update minimum Firefox verison to 56.0 - [#7213](https://github.com/MetaMask/metamask-extension/pull/7213): Update minimum Firefox verison to 56.0
## 7.2.1 Tue Sep 17 2019 ## 7.2.1 Tue Sep 17 2019
- [#7180](https://github.com/MetaMask/metamask-extension/pull/7180): Add `appName` message to each locale - [#7180](https://github.com/MetaMask/metamask-extension/pull/7180): Add `appName` message to each locale
## 7.2.0 Mon Sep 8, 2019 ## 7.2.0 Mon Sep 8, 2019
- [#7099](https://github.com/MetaMask/metamask-extension/pull/7099): Update localization from Transifex Brave - [#7099](https://github.com/MetaMask/metamask-extension/pull/7099): Update localization from Transifex Brave
- [#7137](https://github.com/MetaMask/metamask-extension/pull/7137): Fix validation of empty block explorer url's in custom network form - [#7137](https://github.com/MetaMask/metamask-extension/pull/7137): Fix validation of empty block explorer url's in custom network form
- [#7128](https://github.com/MetaMask/metamask-extension/pull/7128): Support for eth_signTypedData_v4 - [#7128](https://github.com/MetaMask/metamask-extension/pull/7128): Support for eth_signTypedData_v4
@ -513,6 +581,7 @@
- [#7171](https://github.com/MetaMask/metamask-extension/pull/7171): Fix recipient field of approve screen - [#7171](https://github.com/MetaMask/metamask-extension/pull/7171): Fix recipient field of approve screen
## 7.1.1 Tue Aug 27 2019 ## 7.1.1 Tue Aug 27 2019
- [#7059](https://github.com/MetaMask/metamask-extension/pull/7059): Remove blockscale, replace with ethgasstation - [#7059](https://github.com/MetaMask/metamask-extension/pull/7059): Remove blockscale, replace with ethgasstation
- [#7037](https://github.com/MetaMask/metamask-extension/pull/7037): Remove Babel 6 from internal dependencies - [#7037](https://github.com/MetaMask/metamask-extension/pull/7037): Remove Babel 6 from internal dependencies
- [#7093](https://github.com/MetaMask/metamask-extension/pull/7093): Allow dismissing privacy mode notification from popup - [#7093](https://github.com/MetaMask/metamask-extension/pull/7093): Allow dismissing privacy mode notification from popup
@ -524,6 +593,7 @@
- [#7012](https://github.com/MetaMask/metamask-extension/pull/7012): Added missed phrases to RU locale - [#7012](https://github.com/MetaMask/metamask-extension/pull/7012): Added missed phrases to RU locale
## 7.1.0 Fri Aug 16 2019 ## 7.1.0 Fri Aug 16 2019
- [#7035](https://github.com/MetaMask/metamask-extension/pull/7035): Filter non-ERC-20 assets during mobile sync (#7035) - [#7035](https://github.com/MetaMask/metamask-extension/pull/7035): Filter non-ERC-20 assets during mobile sync (#7035)
- [#7021](https://github.com/MetaMask/metamask-extension/pull/7021): Using translated string for end of flow messaging (#7021) - [#7021](https://github.com/MetaMask/metamask-extension/pull/7021): Using translated string for end of flow messaging (#7021)
- [#7018](https://github.com/MetaMask/metamask-extension/pull/7018): Rename Contacts List settings tab to Contacts (#7018) - [#7018](https://github.com/MetaMask/metamask-extension/pull/7018): Rename Contacts List settings tab to Contacts (#7018)
@ -537,9 +607,11 @@
- [#7047](https://github.com/MetaMask/metamask-extension/pull/7047): Add warning about reload on network change - [#7047](https://github.com/MetaMask/metamask-extension/pull/7047): Add warning about reload on network change
## 7.0.1 Thu Aug 08 2019 ## 7.0.1 Thu Aug 08 2019
- [#6975](https://github.com/MetaMask/metamask-extension/pull/6975): Ensure seed phrase backup notification only shows up for new users - [#6975](https://github.com/MetaMask/metamask-extension/pull/6975): Ensure seed phrase backup notification only shows up for new users
## 7.0.0 Fri Aug 02 2019 ## 7.0.0 Fri Aug 02 2019
- [#6828](https://github.com/MetaMask/metamask-extension/pull/6828): Capitalized speed up label to match rest of UI - [#6828](https://github.com/MetaMask/metamask-extension/pull/6828): Capitalized speed up label to match rest of UI
- [#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/6928): Allows skipping of seed phrase challenge during onboarding, and completing it at a later time
- [#6900](https://github.com/MetaMask/metamask-extension/pull/6900): Prevent opening of asset dropdown if no tokens in account - [#6900](https://github.com/MetaMask/metamask-extension/pull/6900): Prevent opening of asset dropdown if no tokens in account
@ -554,10 +626,10 @@
## 6.7.2 Mon Jul 01 2019 ## 6.7.2 Mon Jul 01 2019
- [#6713](https://github.com/MetaMask/metamask-extension/pull/6713): * Normalize and Validate txParams in TransactionStateManager.addTx too - [#6713](https://github.com/MetaMask/metamask-extension/pull/6713): \* Normalize and Validate txParams in TransactionStateManager.addTx too
- [#6759](https://github.com/MetaMask/metamask-extension/pull/6759): Update to Node.js v10 - [#6759](https://github.com/MetaMask/metamask-extension/pull/6759): Update to Node.js v10
- [#6694](https://github.com/MetaMask/metamask-extension/pull/6694): Fixes #6694 - [#6694](https://github.com/MetaMask/metamask-extension/pull/6694): Fixes #6694
- [#6743](https://github.com/MetaMask/metamask-extension/pull/6743): * Add tests for ImportWithSeedPhrase#parseSeedPhrase - [#6743](https://github.com/MetaMask/metamask-extension/pull/6743): \* Add tests for ImportWithSeedPhrase#parseSeedPhrase
- [#6740](https://github.com/MetaMask/metamask-extension/pull/6740): Fixes #6740 - [#6740](https://github.com/MetaMask/metamask-extension/pull/6740): Fixes #6740
- [#6741](https://github.com/MetaMask/metamask-extension/pull/6741): Fixes #6741 - [#6741](https://github.com/MetaMask/metamask-extension/pull/6741): Fixes #6741
- [#6761](https://github.com/MetaMask/metamask-extension/pull/6761): Fixes #6760, correct PropTypes for nextRoute - [#6761](https://github.com/MetaMask/metamask-extension/pull/6761): Fixes #6760, correct PropTypes for nextRoute
@ -568,6 +640,7 @@
- [#6731](https://github.com/MetaMask/metamask-extension/pull/6731): Add brave as a platform type for MetaMask - [#6731](https://github.com/MetaMask/metamask-extension/pull/6731): Add brave as a platform type for MetaMask
## 6.7.1 Fri Jun 28 2019 ## 6.7.1 Fri Jun 28 2019
- [#6764](https://github.com/MetaMask/metamask-extension/pull/6764): Fix display of token amount on confirm transaction screen - [#6764](https://github.com/MetaMask/metamask-extension/pull/6764): Fix display of token amount on confirm transaction screen
## 6.7.0 Tue Jun 18 2019 ## 6.7.0 Tue Jun 18 2019
@ -607,6 +680,7 @@
## 6.5.2 Wed May 15 2019 ## 6.5.2 Wed May 15 2019
- [#6613](https://github.com/MetaMask/metamask-extension/pull/6613): Hardware Wallet Fix - [#6613](https://github.com/MetaMask/metamask-extension/pull/6613): Hardware Wallet Fix
## 6.5.1 Tue May 14 2019 ## 6.5.1 Tue May 14 2019
- Fix bug where approve method would show a warning. #6602 - Fix bug where approve method would show a warning. #6602
@ -629,7 +703,7 @@
## 6.4.0 Wed Apr 17 2019 ## 6.4.0 Wed Apr 17 2019
- [#6445](https://github.com/MetaMask/metamask-extension/pull/6445): * Move send to pages/ - [#6445](https://github.com/MetaMask/metamask-extension/pull/6445): \* Move send to pages/
- [#6470](https://github.com/MetaMask/metamask-extension/pull/6470): update publishing.md with dev diagram - [#6470](https://github.com/MetaMask/metamask-extension/pull/6470): update publishing.md with dev diagram
- [#6403](https://github.com/MetaMask/metamask-extension/pull/6403): Update to eth-method-registry@1.2.0 - [#6403](https://github.com/MetaMask/metamask-extension/pull/6403): Update to eth-method-registry@1.2.0
- [#6468](https://github.com/MetaMask/metamask-extension/pull/6468): Fix switcher height when Custom RPC is selected or loading - [#6468](https://github.com/MetaMask/metamask-extension/pull/6468): Fix switcher height when Custom RPC is selected or loading
@ -690,6 +764,7 @@
## 6.2.1 Wed Mar 06 2019 ## 6.2.1 Wed Mar 06 2019
## 6.2.0 Tue Mar 05 2019 ## 6.2.0 Tue Mar 05 2019
- [#6192](https://github.com/MetaMask/metamask-extension/pull/6192): Improves design and UX of onboarding flow - [#6192](https://github.com/MetaMask/metamask-extension/pull/6192): Improves design and UX of onboarding flow
- [#6195](https://github.com/MetaMask/metamask-extension/pull/6195): Fixes gas estimation when sending to contracts - [#6195](https://github.com/MetaMask/metamask-extension/pull/6195): Fixes gas estimation when sending to contracts
- [#6223](https://github.com/MetaMask/metamask-extension/pull/6223): Fixes display of notification windows when metamask is active in a tab - [#6223](https://github.com/MetaMask/metamask-extension/pull/6223): Fixes display of notification windows when metamask is active in a tab
@ -699,11 +774,10 @@
- [#6182](https://github.com/MetaMask/metamask-extension/pull/6182): Change "Token Address" to "Token Contract Address" - [#6182](https://github.com/MetaMask/metamask-extension/pull/6182): Change "Token Address" to "Token Contract Address"
- [#6177](https://github.com/MetaMask/metamask-extension/pull/6177): Fixes #6176 - [#6177](https://github.com/MetaMask/metamask-extension/pull/6177): Fixes #6176
- [#6146](https://github.com/MetaMask/metamask-extension/pull/6146): * Add Copy Tx ID button to transaction-list-item-details - [#6146](https://github.com/MetaMask/metamask-extension/pull/6146): \* Add Copy Tx ID button to transaction-list-item-details
- [#6133](https://github.com/MetaMask/metamask-extension/pull/6133): Checksum address before slicing it for the confirm screen - [#6133](https://github.com/MetaMask/metamask-extension/pull/6133): Checksum address before slicing it for the confirm screen
- [#6147](https://github.com/MetaMask/metamask-extension/pull/6147): Add button to force edit token symbol when adding custom token - [#6147](https://github.com/MetaMask/metamask-extension/pull/6147): Add button to force edit token symbol when adding custom token
- [#6124](https://github.com/MetaMask/metamask-extension/pull/6124): recent-blocks - dont listen for block when on infura providers - [#6124](https://github.com/MetaMask/metamask-extension/pull/6124): recent-blocks - dont listen for block when on infura providers -[#5973] (https://github.com/MetaMask/metamask-extension/pull/5973): Fix incorrectly showing checksums on non-ETH blockchains (issue 5838)
-[#5973] (https://github.com/MetaMask/metamask-extension/pull/5973): Fix incorrectly showing checksums on non-ETH blockchains (issue 5838)
## 6.0.1 Tue Feb 12 2019 ## 6.0.1 Tue Feb 12 2019
@ -712,7 +786,6 @@
- [#6119](https://github.com/MetaMask/metamask-extension/pull/6119) Update Italian translation - [#6119](https://github.com/MetaMask/metamask-extension/pull/6119) Update Italian translation
- [#6125](https://github.com/MetaMask/metamask-extension/pull/6125) Improved Traditional Chinese translation - [#6125](https://github.com/MetaMask/metamask-extension/pull/6125) Improved Traditional Chinese translation
## 6.0.0 Thu Feb 07 2019 ## 6.0.0 Thu Feb 07 2019
- [#6082](https://github.com/MetaMask/metamask-extension/pull/6082): Migrate all users to the new UI - [#6082](https://github.com/MetaMask/metamask-extension/pull/6082): Migrate all users to the new UI
@ -802,14 +875,14 @@
- [#5835](https://github.com/MetaMask/metamask-extension/pull/5835): Open full-screen UI on install - [#5835](https://github.com/MetaMask/metamask-extension/pull/5835): Open full-screen UI on install
- Locked versions for some dependencies to avoid possible issues from event-stream hack. - Locked versions for some dependencies to avoid possible issues from event-stream hack.
- [#5831](https://github.com/MetaMask/metamask-extension/pull/5831): Hide app-header when provider request pending - [#5831](https://github.com/MetaMask/metamask-extension/pull/5831): Hide app-header when provider request pending
- [#5786](https://github.com/MetaMask/metamask-extension/pull/5786): * transactions - autofill gasPrice for retry attempts with either the recomened gasprice or a %10 bump - [#5786](https://github.com/MetaMask/metamask-extension/pull/5786): \* transactions - autofill gasPrice for retry attempts with either the recomened gasprice or a %10 bump
- [#5801](https://github.com/MetaMask/metamask-extension/pull/5801): transactions - ensure err is defined when setting tx failed - [#5801](https://github.com/MetaMask/metamask-extension/pull/5801): transactions - ensure err is defined when setting tx failed
- [#5792](https://github.com/MetaMask/metamask-extension/pull/5792): Consider HW Wallets for signTypedMessage - [#5792](https://github.com/MetaMask/metamask-extension/pull/5792): Consider HW Wallets for signTypedMessage
- [#5829](https://github.com/MetaMask/metamask-extension/pull/5829): Show disabled cursor in .network-disabled state - [#5829](https://github.com/MetaMask/metamask-extension/pull/5829): Show disabled cursor in .network-disabled state
- [#5827](https://github.com/MetaMask/metamask-extension/pull/5827): Trim whitespace from seed phrase during import - [#5827](https://github.com/MetaMask/metamask-extension/pull/5827): Trim whitespace from seed phrase during import
- [#5832](https://github.com/MetaMask/metamask-extension/pull/5832): Show Connect Requests count in extension badge - [#5832](https://github.com/MetaMask/metamask-extension/pull/5832): Show Connect Requests count in extension badge
- [#5816](https://github.com/MetaMask/metamask-extension/pull/5816): Increase Token Symbol length to twelve - [#5816](https://github.com/MetaMask/metamask-extension/pull/5816): Increase Token Symbol length to twelve
- [#5819](https://github.com/MetaMask/metamask-extension/pull/5819): With the EIP 1102 updates, MetaMask *does* now open itself when visiting some websites. Changed the wording here to clarify that MetaMask will not open itself to ask you for your seed phrase. - [#5819](https://github.com/MetaMask/metamask-extension/pull/5819): With the EIP 1102 updates, MetaMask _does_ now open itself when visiting some websites. Changed the wording here to clarify that MetaMask will not open itself to ask you for your seed phrase.
- [#5810](https://github.com/MetaMask/metamask-extension/pull/5810): Bump Node version to 8.13 - [#5810](https://github.com/MetaMask/metamask-extension/pull/5810): Bump Node version to 8.13
- [#5797](https://github.com/MetaMask/metamask-extension/pull/5797): Add Firefox and Brave support for Trezor - [#5797](https://github.com/MetaMask/metamask-extension/pull/5797): Add Firefox and Brave support for Trezor
- [#5799](https://github.com/MetaMask/metamask-extension/pull/5799): Fix usage of setState in ConfirmTransactionBase#handleSubmit - [#5799](https://github.com/MetaMask/metamask-extension/pull/5799): Fix usage of setState in ConfirmTransactionBase#handleSubmit
@ -1266,7 +1339,6 @@ rollback to 3.10.0 due to bug
- Add validation preventing users from inputting their own addresses as token tracking addresses. - Add validation preventing users from inputting their own addresses as token tracking addresses.
- Added button to reject all transactions (thanks to davidp94! https://github.com/davidp94) - Added button to reject all transactions (thanks to davidp94! https://github.com/davidp94)
## 3.9.13 2017-9-8 ## 3.9.13 2017-9-8
- Changed the way we initialize the inpage provider to fix a bug affecting some developers. - Changed the way we initialize the inpage provider to fix a bug affecting some developers.
@ -1683,13 +1755,13 @@ rollback to 3.10.0 due to bug
- Add Parity compatibility, fixing Geth dependency issues. - Add Parity compatibility, fixing Geth dependency issues.
- Add a link to the transaction in history that goes to https://metamask.github.io/eth-tx-viz - Add a link to the transaction in history that goes to https://metamask.github.io/eth-tx-viz
too help visualize transactions and to where they are going. too help visualize transactions and to where they are going.
- Show "Buy Ether" button and warning on tx confirmation when sender balance is insufficient - Show "Buy Ether" button and warning on tx confirmation when sender balance is insufficient
## 2.12.1 2016-09-14 ## 2.12.1 2016-09-14
- Fixed bug where if you send a transaction from within MetaMask extension the - Fixed bug where if you send a transaction from within MetaMask extension the
popup notification opens up. popup notification opens up.
- Fixed bug where some tx errors would block subsequent txs until the plugin was refreshed. - Fixed bug where some tx errors would block subsequent txs until the plugin was refreshed.
## 2.12.0 2016-09-14 ## 2.12.0 2016-09-14
@ -1974,9 +2046,9 @@ popup notification opens up.
## 1.3.2 2016-04-04 ## 1.3.2 2016-04-04
- When unlocking, first account is auto-selected. - When unlocking, first account is auto-selected.
- When creating a first vault on the test-net, the first account is auto-funded. - When creating a first vault on the test-net, the first account is auto-funded.
- Fixed some styling issues. - Fixed some styling issues.
## 1.0.1-1.3.1 ## 1.0.1-1.3.1

@ -2,6 +2,8 @@
You can find the latest version of MetaMask on [our official website](https://metamask.io/). For help using MetaMask, visit our [User Support Site](https://metamask.zendesk.com/hc/en-us). You can find the latest version of MetaMask on [our official website](https://metamask.io/). For help using MetaMask, visit our [User Support Site](https://metamask.zendesk.com/hc/en-us).
For [general questions](https://metamask.zendesk.com/hc/en-us/community/topics/360000682532-General), [feature requests](https://metamask.zendesk.com/hc/en-us/community/topics/360000682552-Feature-Requests-Ideas), or [developer questions](https://metamask.zendesk.com/hc/en-us/community/topics/360001751291-Developer-Questions), visit our [Community Forum](https://metamask.zendesk.com/hc/en-us/community/topics).
MetaMask supports Firefox, Google Chrome, and Chromium-based browsers. We recommend using the latest available browser version. MetaMask supports Firefox, Google Chrome, and Chromium-based browsers. We recommend using the latest available browser version.
For up to the minute news, follow our [Twitter](https://twitter.com/metamask_io) or [Medium](https://medium.com/metamask) pages. For up to the minute news, follow our [Twitter](https://twitter.com/metamask_io) or [Medium](https://medium.com/metamask) pages.

@ -95,7 +95,13 @@
"message": "Browsing a website with an unconnected account selected" "message": "Browsing a website with an unconnected account selected"
}, },
"alertSettingsUnconnectedAccountDescription": { "alertSettingsUnconnectedAccountDescription": {
"message": "This alert is shown in the popup when you are browsing a connected Web3 site, but the currently selected account is not connected." "message": "This alert is shown in the popup when you are browsing a connected web3 site, but the currently selected account is not connected."
},
"alertSettingsWeb3ShimUsage": {
"message": "When a website tries to use the removed window.web3 API"
},
"alertSettingsWeb3ShimUsageDescription": {
"message": "This alert is shown in the popup when you are browsing a site that tries to use the removed window.web3 API, and may be broken as a result."
}, },
"alerts": { "alerts": {
"message": "Alerts" "message": "Alerts"
@ -233,6 +239,9 @@
"bytes": { "bytes": {
"message": "Bytes" "message": "Bytes"
}, },
"canToggleInSettings": {
"message": "You can re-enable this notification in Settings -> Alerts."
},
"cancel": { "cancel": {
"message": "Cancel" "message": "Cancel"
}, },
@ -1605,6 +1614,9 @@
"message": "You need $1 more $2 to complete this swap", "message": "You need $1 more $2 to complete this swap",
"description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol."
}, },
"swapBetterQuoteAvailable": {
"message": "A better quote is available"
},
"swapBuildQuotePlaceHolderText": { "swapBuildQuotePlaceHolderText": {
"message": "No tokens available matching $1", "message": "No tokens available matching $1",
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
@ -1701,8 +1713,8 @@
"message": "We find the best price from the top liquidity sources, every time. A fee of $1% is automatically factored into each quote, which supports ongoing development to make MetaMask even better.", "message": "We find the best price from the top liquidity sources, every time. A fee of $1% is automatically factored into each quote, which supports ongoing development to make MetaMask even better.",
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number."
}, },
"swapNQuotesAvailable": { "swapNQuotes": {
"message": "$1 quotes available", "message": "$1 quotes",
"description": "$1 is the number of quotes that the user can select from when opening the list of quotes on the 'view quote' screen" "description": "$1 is the number of quotes that the user can select from when opening the list of quotes on the 'view quote' screen"
}, },
"swapNetworkFeeSummary": { "swapNetworkFeeSummary": {
@ -1830,6 +1842,9 @@
"swapUnknown": { "swapUnknown": {
"message": "Unknown" "message": "Unknown"
}, },
"swapUsingBestQuote": {
"message": "Using the best quote"
},
"swapVerifyTokenExplanation": { "swapVerifyTokenExplanation": {
"message": "Multiple tokens can use the same name and symbol. Check Etherscan to verify this is the token you're looking for." "message": "Multiple tokens can use the same name and symbol. Check Etherscan to verify this is the token you're looking for."
}, },
@ -1846,13 +1861,6 @@
"swapsAdvancedOptions": { "swapsAdvancedOptions": {
"message": "Advanced Options" "message": "Advanced Options"
}, },
"swapsBestQuote": {
"message": "Best quote"
},
"swapsConvertToAbout": {
"message": "Convert $1 to about",
"description": "This message is part of a quote for a swap. The $1 is the amount being converted, and the amount it is being swapped for is below this message"
},
"swapsExcessiveSlippageWarning": { "swapsExcessiveSlippageWarning": {
"message": "Slippage amount is too high and will result in a bad rate. Please reduce your slippage tolerance to a value below 15%." "message": "Slippage amount is too high and will result in a bad rate. Please reduce your slippage tolerance to a value below 15%."
}, },
@ -2096,6 +2104,10 @@
"walletSeed": { "walletSeed": {
"message": "Seed phrase" "message": "Seed phrase"
}, },
"web3ShimUsageNotification": {
"message": "We noticed that the current website tried to use the removed window.web3 API. If the site appears to be broken, please click $1 for more information.",
"description": "$1 is a clickable link."
},
"welcome": { "welcome": {
"message": "Welcome to MetaMask" "message": "Welcome to MetaMask"
}, },

@ -525,7 +525,7 @@
"message": "Pas de réseau ETH, régler en minuscules" "message": "Pas de réseau ETH, régler en minuscules"
}, },
"invalidBlockExplorerURL": { "invalidBlockExplorerURL": {
"message": "URL Block Explorer invalide" "message": "URL de l'explorateur de blocs invalide"
}, },
"invalidIpfsGateway": { "invalidIpfsGateway": {
"message": "IPFS Gateway Invalide: la valeur doit être une URL valide" "message": "IPFS Gateway Invalide: la valeur doit être une URL valide"
@ -696,7 +696,7 @@
"message": "Activé" "message": "Activé"
}, },
"optionalBlockExplorerUrl": { "optionalBlockExplorerUrl": {
"message": "Bloquer l'URL de l'explorateur (facultatif)" "message": "URL de l'explorateur de blocs (facultatif)"
}, },
"optionalCurrencySymbol": { "optionalCurrencySymbol": {
"message": "Symbole (facultatif)" "message": "Symbole (facultatif)"

@ -6,7 +6,7 @@
"message": "版本、技术支持中心和联系方式。" "message": "版本、技术支持中心和联系方式。"
}, },
"acceleratingATransaction": { "acceleratingATransaction": {
"message": "* 设定更高天燃气价格,加快交易完成进度,提高网络快速处理机率,但无法保证每次均能够实现提速。" "message": "* 设定更高的 Gas 价格,加快交易的完成进度,提高网络快速处理机率,但无法保证每次均能够实现提速。"
}, },
"accessingYourCamera": { "accessingYourCamera": {
"message": "正在获取相机使用权限..." "message": "正在获取相机使用权限..."
@ -39,7 +39,7 @@
"message": "添加网络" "message": "添加网络"
}, },
"addRecipient": { "addRecipient": {
"message": "添加收件人" "message": "添加接受人"
}, },
"addSuggestedTokens": { "addSuggestedTokens": {
"message": "添加推荐代币" "message": "添加推荐代币"
@ -80,7 +80,7 @@
"message": "批准" "message": "批准"
}, },
"approved": { "approved": {
"message": "批准" "message": "批准"
}, },
"asset": { "asset": {
"message": "资产" "message": "资产"
@ -159,7 +159,7 @@
"message": "取消" "message": "取消"
}, },
"cancellationGasFee": { "cancellationGasFee": {
"message": "取消天然气费" "message": "取消 Gas 费"
}, },
"cancelled": { "cancelled": {
"message": "已取消" "message": "已取消"
@ -412,20 +412,26 @@
"functionType": { "functionType": {
"message": "功能类型" "message": "功能类型"
}, },
"gasLimit": {
"message": "Gas 上限"
},
"gasLimitInfoTooltipContent": { "gasLimitInfoTooltipContent": {
"message": "天然气限制即您个人希望购买的最大单位数量。" "message": "Gas 上限是您个人希望付出的 Gas 数量上限。"
}, },
"gasLimitTooLow": { "gasLimitTooLow": {
"message": "Gas Limit 至少要 21000" "message": "Gas 上限至少要 21000"
},
"gasPrice": {
"message": "Gas 价格 (gwei)"
}, },
"gasPriceExtremelyLow": { "gasPriceExtremelyLow": {
"message": "天然气价格极低" "message": "Gas 价格极低"
}, },
"gasPriceInfoTooltipContent": { "gasPriceInfoTooltipContent": {
"message": "天然气价格将详细列明每个付费天然气单位的以太币金额。" "message": "Gas 价格将详细列明每个付费 Gas 单位的以太币金额。"
}, },
"gasUsed": { "gasUsed": {
"message": "已使用天然气量" "message": "已使用 Gas 量"
}, },
"general": { "general": {
"message": "通用" "message": "通用"
@ -675,13 +681,13 @@
"message": "没有交易" "message": "没有交易"
}, },
"noWebcamFound": { "noWebcamFound": {
"message": "找不到您的个人电脑网络摄像头请重试。" "message": "找不到您的个人电脑网络摄像头请重试。"
}, },
"noWebcamFoundTitle": { "noWebcamFoundTitle": {
"message": "未找到网络摄像头" "message": "未找到网络摄像头"
}, },
"notEnoughGas": { "notEnoughGas": {
"message": "天然气不足" "message": "Gas 不足"
}, },
"ofTextNofM": { "ofTextNofM": {
"message": "/" "message": "/"
@ -905,7 +911,7 @@
"message": "助记词为12个单词" "message": "助记词为12个单词"
}, },
"selectAHigherGasFee": { "selectAHigherGasFee": {
"message": "请选择价格稍高的天然气费,加快交易处理速度。*" "message": "请选择价格稍高的 Gas 费,以加快交易处理速度。*"
}, },
"selectAnAccount": { "selectAnAccount": {
"message": "选择账户" "message": "选择账户"
@ -917,7 +923,7 @@
"message": "选择货币" "message": "选择货币"
}, },
"selectEachPhrase": { "selectEachPhrase": {
"message": "请选择每一个片语,以确保片语正确性。" "message": "请选择每一个单词,以确保单词正确性。"
}, },
"selectHdPath": { "selectHdPath": {
"message": "选择 HD 路径" "message": "选择 HD 路径"
@ -941,7 +947,7 @@
"message": "发送 ETH" "message": "发送 ETH"
}, },
"sendTokens": { "sendTokens": {
"message": "发送 代币" "message": "发送代币"
}, },
"sentEther": { "sentEther": {
"message": "以太币已发送" "message": "以太币已发送"
@ -953,10 +959,10 @@
"message": "设置" "message": "设置"
}, },
"showAdvancedGasInline": { "showAdvancedGasInline": {
"message": "高级天然气控制" "message": "高级 Gas 控制"
}, },
"showAdvancedGasInlineDescription": { "showAdvancedGasInlineDescription": {
"message": "请选择该选项,直接在发送和确认页面显示天然气价格和限制管理。" "message": "请选择该选项,直接在发送和确认页面显示 Gas 价格和限制管理。"
}, },
"showFiatConversionInTestnets": { "showFiatConversionInTestnets": {
"message": "在 Testnets 上显示兑换率" "message": "在 Testnets 上显示兑换率"
@ -980,7 +986,7 @@
"message": "签名" "message": "签名"
}, },
"signNotice": { "signNotice": {
"message": "签署此消息可能会产生危险的副作用。 \n只从你完全信任的网站上签名。 这种危险的方法将在未来的版本中被移除。" "message": "签署此消息可能会产生危险的副作用。 \n只从你完全信任的网站上签名。 未来的版本将移除这种危险的方法。"
}, },
"signatureRequest": { "signatureRequest": {
"message": "请求签名" "message": "请求签名"
@ -1028,7 +1034,7 @@
"message": "3. 开始体验 dApps 和更多功能!" "message": "3. 开始体验 dApps 和更多功能!"
}, },
"step3HardwareWalletMsg": { "step3HardwareWalletMsg": {
"message": "使用您的硬件钱包,操作与以太坊账户制作相同。登录 dApps,发送 ETH ,购买和保存 ERC20 代币和诸如 CryptoKitties 等不可替代代币。" "message": "使用您的硬件钱包,操作与以太坊账户制作相同。登录 dApps,发送 ETH ,购买和保存 ERC20 代币和非同质化代币如 CryptoKitties。"
}, },
"storePhrase": { "storePhrase": {
"message": "通过如 1Password 等密码管家保存该密语。" "message": "通过如 1Password 等密码管家保存该密语。"
@ -1091,7 +1097,7 @@
"message": "代币已经被添加." "message": "代币已经被添加."
}, },
"tokenContractAddress": { "tokenContractAddress": {
"message": "代币联系人地址" "message": "代币合约地址"
}, },
"tokenSymbol": { "tokenSymbol": {
"message": "代币符号" "message": "代币符号"
@ -1103,7 +1109,7 @@
"message": "交易" "message": "交易"
}, },
"transactionCancelAttempted": { "transactionCancelAttempted": {
"message": "在 $2 尝试取消交易,其天然气费为 $1" "message": "在 $2 尝试取消交易,其 Gas 费为 $1"
}, },
"transactionCancelSuccess": { "transactionCancelSuccess": {
"message": "成功在 $2 取消交易单" "message": "成功在 $2 取消交易单"
@ -1130,10 +1136,10 @@
"message": "交易费" "message": "交易费"
}, },
"transactionResubmitted": { "transactionResubmitted": {
"message": "在 $2 重新提交的交易单,其天然气费增加至 $1" "message": "在 $2 重新提交的交易单,其 Gas 费用增加至 $1"
}, },
"transactionSubmitted": { "transactionSubmitted": {
"message": "在 $2 提交的交易单,其天然气费为 $1 。" "message": "在 $2 提交的交易单,其 Gas 费用为 $1 。"
}, },
"transactionUpdated": { "transactionUpdated": {
"message": "交易单已于 $2 更新。" "message": "交易单已于 $2 更新。"
@ -1239,6 +1245,6 @@
"message": "正在请求你的签名" "message": "正在请求你的签名"
}, },
"zeroGasPriceOnSpeedUpError": { "zeroGasPriceOnSpeedUpError": {
"message": "天然气价格加速上涨" "message": "Gas 价格加速上涨"
} }
} }

@ -0,0 +1,3 @@
<svg width="12" height="16" viewBox="0 0 12 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.00163 15.2045C6.21347 15.1962 6.45833 15.109 6.61275 14.9637L11.6498 10.2232C11.9385 9.90194 12.0577 9.28597 11.7239 8.92693C11.3952 8.57328 10.7576 8.5838 10.4276 8.93611L6.89052 12.2693L6.89052 1.87164C6.89052 1.38076 6.49254 0.982788 6.00163 0.982788C5.51071 0.982788 5.11274 1.38076 5.11274 1.87164L5.11274 12.2693L1.57565 8.93611C1.27181 8.63281 0.611843 8.57675 0.279352 8.92693C-0.0531095 9.27702 0.0531145 9.91513 0.353459 10.2232L5.3905 14.9637C5.56288 15.126 5.76513 15.205 6.00163 15.2045Z" fill="#D6D9DC"/>
</svg>

After

Width:  |  Height:  |  Size: 636 B

@ -1 +0,0 @@
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fff"><path d="m8.00002 9.57037c.93765 0 1.69776-.76011 1.69776-1.69777 0-.93765-.76011-1.69776-1.69776-1.69776-.93766 0-1.69777.76011-1.69777 1.69776 0 .93766.76011 1.69777 1.69777 1.69777z"/><path d="m11.0582 11.6586c-.1862 0-.3725-.071-.5145-.2131-.2842-.2841-.2842-.7448 0-1.029.6795-.67946 1.0538-1.58294 1.0538-2.54391 0-.96098-.3743-1.86446-1.0538-2.54394-.2842-.28417-.2842-.74484 0-1.02901.2841-.2841.7449-.2841 1.029 0 .9543.95434 1.48 2.22329 1.48 3.57295 0 1.34965-.5257 2.61861-1.48 3.57291-.1421.1421-.3283.2131-.5145.2131z"/><path d="m4.94175 11.6586c-.18622 0-.37246-.071-.51451-.2131-.95434-.9543-1.47997-2.22326-1.47997-3.57291 0-1.34966.52563-2.61861 1.47997-3.57295.28411-.2841.74491-.2841 1.02902 0 .28417.28417.28417.74484 0 1.02901-.67954.67948-1.05376 1.58296-1.05376 2.54394 0 .96097.37422 1.86445 1.05376 2.54391.28417.2842.28417.7449 0 1.029-.14206.1421-.32828.2131-.51451.2131z"/><path d="m13.1451 13.7453c-.1862 0-.3724-.0711-.5145-.2131-.2842-.2842-.2842-.7449 0-1.0291 2.5533-2.55325 2.5533-6.70772 0-9.26101-.2842-.28417-.2842-.74484 0-1.02901.2841-.28411.7449-.28411 1.029 0 3.1207 3.12066 3.1207 8.19842 0 11.31912-.142.142-.3283.2131-.5145.2131z"/><path d="m2.855 13.7453c-.18622 0-.37245-.0711-.5145-.2131-3.120666-3.1207-3.120666-8.19846 0-11.31912.28411-.28411.74491-.28411 1.02901 0 .28417.28417.28417.74484 0 1.02901-2.553289 2.55329-2.553289 6.70776 0 9.26101.28417.2842.28417.7449 0 1.0291-.14206.142-.32828.2131-.51451.2131z"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

@ -78,6 +78,6 @@
"notifications" "notifications"
], ],
"short_name": "__MSG_appName__", "short_name": "__MSG_appName__",
"version": "8.1.11", "version": "9.0.0",
"web_accessible_resources": ["inpage.js", "phishing.html"] "web_accessible_resources": ["inpage.js", "phishing.html"]
} }

@ -3,5 +3,5 @@
"matches": ["https://metamask.io/*"], "matches": ["https://metamask.io/*"],
"ids": ["*"] "ids": ["*"]
}, },
"minimum_chrome_version": "58" "minimum_chrome_version": "63"
} }

@ -16,8 +16,7 @@ import pump from 'pump'
import debounce from 'debounce-stream' import debounce from 'debounce-stream'
import log from 'loglevel' import log from 'loglevel'
import extension from 'extensionizer' import extension from 'extensionizer'
import storeTransform from 'obs-store/lib/transform' import { storeAsStream, storeTransformStream } from '@metamask/obs-store'
import asStream from 'obs-store/lib/asStream'
import PortStream from 'extension-port-stream' import PortStream from 'extension-port-stream'
import { captureException } from '@sentry/browser' import { captureException } from '@sentry/browser'
import migrations from './migrations' import migrations from './migrations'
@ -119,6 +118,7 @@ initialize().catch(log.error)
* @property {number} unapprovedDecryptMsgCount - The number of messages in unapprovedDecryptMsgs. * @property {number} unapprovedDecryptMsgCount - The number of messages in unapprovedDecryptMsgs.
* @property {Object} unapprovedTypedMsgs - An object of messages pending approval, mapping a unique ID to the options. * @property {Object} unapprovedTypedMsgs - An object of messages pending approval, mapping a unique ID to the options.
* @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs. * @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs.
* @property {number} pendingApprovalCount - The number of pending request in the approval controller.
* @property {string[]} keyringTypes - An array of unique keyring identifying strings, representing available strategies for creating accounts. * @property {string[]} keyringTypes - An array of unique keyring identifying strings, representing available strategies for creating accounts.
* @property {Keyring[]} keyrings - An array of keyring descriptions, summarizing the accounts that are available for use, and what keyrings they belong to. * @property {Keyring[]} keyrings - An array of keyring descriptions, summarizing the accounts that are available for use, and what keyrings they belong to.
* @property {string} selectedAddress - A lower case hex string of the currently selected address. * @property {string} selectedAddress - A lower case hex string of the currently selected address.
@ -249,9 +249,9 @@ function setupController(initState, initLangCode) {
// setup state persistence // setup state persistence
pump( pump(
asStream(controller.store), storeAsStream(controller.store),
debounce(1000), debounce(1000),
storeTransform(versionifyData), storeTransformStream(versionifyData),
createStreamSink(persistData), createStreamSink(persistData),
(error) => { (error) => {
log.error('MetaMask - Persistence pipeline failed', error) log.error('MetaMask - Persistence pipeline failed', error)
@ -402,7 +402,7 @@ function setupController(initState, initLangCode) {
controller.decryptMessageManager.on('updateBadge', updateBadge) controller.decryptMessageManager.on('updateBadge', updateBadge)
controller.encryptionPublicKeyManager.on('updateBadge', updateBadge) controller.encryptionPublicKeyManager.on('updateBadge', updateBadge)
controller.typedMessageManager.on('updateBadge', updateBadge) controller.typedMessageManager.on('updateBadge', updateBadge)
controller.permissionsController.permissions.subscribe(updateBadge) controller.approvalController.subscribe(updateBadge)
controller.appStateController.on('updateBadge', updateBadge) controller.appStateController.on('updateBadge', updateBadge)
/** /**
@ -419,9 +419,7 @@ function setupController(initState, initLangCode) {
unapprovedEncryptionPublicKeyMsgCount, unapprovedEncryptionPublicKeyMsgCount,
} = controller.encryptionPublicKeyManager } = controller.encryptionPublicKeyManager
const { unapprovedTypedMessagesCount } = controller.typedMessageManager const { unapprovedTypedMessagesCount } = controller.typedMessageManager
const pendingPermissionRequests = Object.keys( const pendingApprovalCount = controller.approvalController.getTotalApprovalCount()
controller.permissionsController.permissions.state.permissionsRequests,
).length
const waitingForUnlockCount = const waitingForUnlockCount =
controller.appStateController.waitingForUnlock.length controller.appStateController.waitingForUnlock.length
const count = const count =
@ -431,7 +429,7 @@ function setupController(initState, initLangCode) {
unapprovedDecryptMsgCount + unapprovedDecryptMsgCount +
unapprovedEncryptionPublicKeyMsgCount + unapprovedEncryptionPublicKeyMsgCount +
unapprovedTypedMessagesCount + unapprovedTypedMessagesCount +
pendingPermissionRequests + pendingApprovalCount +
waitingForUnlockCount waitingForUnlockCount
if (count) { if (count) {
label = String(count) label = String(count)

@ -16,16 +16,13 @@ const inpageContent = fs.readFileSync(
const inpageSuffix = `//# sourceURL=${extension.runtime.getURL('inpage.js')}\n` const inpageSuffix = `//# sourceURL=${extension.runtime.getURL('inpage.js')}\n`
const inpageBundle = inpageContent + inpageSuffix const inpageBundle = inpageContent + inpageSuffix
// Eventually this streaming injection could be replaced with: const CONTENT_SCRIPT = 'metamask-contentscript'
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction const INPAGE = 'metamask-inpage'
// const PROVIDER = 'metamask-provider'
// But for now that is only Firefox
// If we create a FireFox-only code path using that API,
// MetaMask will be much faster loading and performant on Firefox.
if (shouldInjectProvider()) { if (shouldInjectProvider()) {
injectScript(inpageBundle) injectScript(inpageBundle)
start() setupStreams()
} }
/** /**
@ -46,15 +43,6 @@ function injectScript(content) {
} }
} }
/**
* Sets up the stream communication and submits site metadata
*
*/
async function start() {
await setupStreams()
await domIsReady()
}
/** /**
* Sets up two-way communication streams between the * Sets up two-way communication streams between the
* browser extension and local per-page browser context. * browser extension and local per-page browser context.
@ -63,10 +51,10 @@ async function start() {
async function setupStreams() { async function setupStreams() {
// the transport-specific streams for communication between inpage and background // the transport-specific streams for communication between inpage and background
const pageStream = new LocalMessageDuplexStream({ const pageStream = new LocalMessageDuplexStream({
name: 'contentscript', name: CONTENT_SCRIPT,
target: 'inpage', target: INPAGE,
}) })
const extensionPort = extension.runtime.connect({ name: 'contentscript' }) const extensionPort = extension.runtime.connect({ name: CONTENT_SCRIPT })
const extensionStream = new PortStream(extensionPort) const extensionStream = new PortStream(extensionPort)
// create and connect channel muxers // create and connect channel muxers
@ -79,20 +67,20 @@ async function setupStreams() {
pump(pageMux, pageStream, pageMux, (err) => pump(pageMux, pageStream, pageMux, (err) =>
logStreamDisconnectWarning('MetaMask Inpage Multiplex', err), logStreamDisconnectWarning('MetaMask Inpage Multiplex', err),
) )
pump(extensionMux, extensionStream, extensionMux, (err) => pump(extensionMux, extensionStream, extensionMux, (err) => {
logStreamDisconnectWarning('MetaMask Background Multiplex', err), logStreamDisconnectWarning('MetaMask Background Multiplex', err)
) notifyInpageOfStreamFailure()
})
// forward communication across inpage-background for these channels only // forward communication across inpage-background for these channels only
forwardTrafficBetweenMuxers('provider', pageMux, extensionMux) forwardTrafficBetweenMuxes(PROVIDER, pageMux, extensionMux)
forwardTrafficBetweenMuxers('publicConfig', pageMux, extensionMux)
// connect "phishing" channel to warning system // connect "phishing" channel to warning system
const phishingStream = extensionMux.createStream('phishing') const phishingStream = extensionMux.createStream('phishing')
phishingStream.once('data', redirectToPhishingWarning) phishingStream.once('data', redirectToPhishingWarning)
} }
function forwardTrafficBetweenMuxers(channelName, muxA, muxB) { function forwardTrafficBetweenMuxes(channelName, muxA, muxB) {
const channelA = muxA.createStream(channelName) const channelA = muxA.createStream(channelName)
const channelB = muxB.createStream(channelName) const channelB = muxB.createStream(channelName)
pump(channelA, channelB, channelA, (error) => pump(channelA, channelB, channelA, (error) =>
@ -116,6 +104,28 @@ function logStreamDisconnectWarning(remoteLabel, error) {
) )
} }
/**
* This function must ONLY be called in pump destruction/close callbacks.
* Notifies the inpage context that streams have failed, via window.postMessage.
* Relies on obj-multiplex and post-message-stream implementation details.
*/
function notifyInpageOfStreamFailure() {
window.postMessage(
{
target: INPAGE, // the post-message-stream "target"
data: {
// this object gets passed to obj-multiplex
name: PROVIDER, // the obj-multiplex channel name
data: {
jsonrpc: '2.0',
method: 'METAMASK_STREAM_FAILURE',
},
},
},
window.location.origin,
)
}
/** /**
* Determines if the provider should be injected * Determines if the provider should be injected
* *
@ -220,17 +230,3 @@ function redirectToPhishingWarning() {
href: window.location.href, href: window.location.href,
})}` })}`
} }
/**
* Returns a promise that resolves when the DOM is loaded (does not wait for images to load)
*/
async function domIsReady() {
// already loaded
if (['interactive', 'complete'].includes(document.readyState)) {
return undefined
}
// wait for load
return new Promise((resolve) =>
window.addEventListener('DOMContentLoaded', resolve, { once: true }),
)
}

@ -1,4 +1,8 @@
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import {
TOGGLEABLE_ALERT_TYPES,
WEB3_SHIM_USAGE_ALERT_STATES,
} from '../../../shared/constants/alerts'
/** /**
* @typedef {Object} AlertControllerInitState * @typedef {Object} AlertControllerInitState
@ -14,14 +18,8 @@ import ObservableStore from 'obs-store'
* @property {AlertControllerInitState} initState - The initial controller state * @property {AlertControllerInitState} initState - The initial controller state
*/ */
export const ALERT_TYPES = {
unconnectedAccount: 'unconnectedAccount',
// enumerated here but has no background state
invalidCustomNetwork: 'invalidCustomNetwork',
}
const defaultState = { const defaultState = {
alertEnabledness: Object.keys(ALERT_TYPES).reduce( alertEnabledness: TOGGLEABLE_ALERT_TYPES.reduce(
(alertEnabledness, alertType) => { (alertEnabledness, alertType) => {
alertEnabledness[alertType] = true alertEnabledness[alertType] = true
return alertEnabledness return alertEnabledness
@ -29,11 +27,11 @@ const defaultState = {
{}, {},
), ),
unconnectedAccountAlertShownOrigins: {}, unconnectedAccountAlertShownOrigins: {},
web3ShimUsageOrigins: {},
} }
/** /**
* Controller responsible for maintaining * Controller responsible for maintaining alert-related state.
* alert related state
*/ */
export default class AlertController { export default class AlertController {
/** /**
@ -41,11 +39,13 @@ export default class AlertController {
* @param {AlertControllerOptions} [opts] - Controller configuration parameters * @param {AlertControllerOptions} [opts] - Controller configuration parameters
*/ */
constructor(opts = {}) { constructor(opts = {}) {
const { initState, preferencesStore } = opts const { initState = {}, preferencesStore } = opts
const state = { const state = {
...defaultState, ...defaultState,
...initState, alertEnabledness: {
unconnectedAccountAlertShownOrigins: {}, ...defaultState.alertEnabledness,
...initState.alertEnabledness,
},
} }
this.store = new ObservableStore(state) this.store = new ObservableStore(state)
@ -83,4 +83,48 @@ export default class AlertController {
unconnectedAccountAlertShownOrigins[origin] = true unconnectedAccountAlertShownOrigins[origin] = true
this.store.updateState({ unconnectedAccountAlertShownOrigins }) this.store.updateState({ unconnectedAccountAlertShownOrigins })
} }
/**
* Gets the web3 shim usage state for the given origin.
*
* @param {string} origin - The origin to get the web3 shim usage state for.
* @returns {undefined | 1 | 2} The web3 shim usage state for the given
* origin, or undefined.
*/
getWeb3ShimUsageState(origin) {
return this.store.getState().web3ShimUsageOrigins[origin]
}
/**
* Sets the web3 shim usage state for the given origin to RECORDED.
*
* @param {string} origin - The origin the that used the web3 shim.
*/
setWeb3ShimUsageRecorded(origin) {
this._setWeb3ShimUsageState(origin, WEB3_SHIM_USAGE_ALERT_STATES.RECORDED)
}
/**
* Sets the web3 shim usage state for the given origin to DISMISSED.
*
* @param {string} origin - The origin that the web3 shim notification was
* dismissed for.
*/
setWeb3ShimUsageAlertDismissed(origin) {
this._setWeb3ShimUsageState(origin, WEB3_SHIM_USAGE_ALERT_STATES.DISMISSED)
}
/**
* @private
* @param {string} origin - The origin to set the state for.
* @param {number} value - The state value to set.
*/
_setWeb3ShimUsageState(origin, value) {
let { web3ShimUsageOrigins } = this.store.getState()
web3ShimUsageOrigins = {
...web3ShimUsageOrigins,
}
web3ShimUsageOrigins[origin] = value
this.store.updateState({ web3ShimUsageOrigins })
}
} }

@ -1,5 +1,5 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
export default class AppStateController extends EventEmitter { export default class AppStateController extends EventEmitter {
/** /**

@ -1,4 +1,4 @@
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
/** /**
* @typedef {Object} CachedBalancesOptions * @typedef {Object} CachedBalancesOptions

@ -2,7 +2,7 @@ import Web3 from 'web3'
import contracts from '@metamask/contract-metadata' import contracts from '@metamask/contract-metadata'
import { warn } from 'loglevel' import { warn } from 'loglevel'
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi' import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi'
import { MAINNET } from './network/enums' import { MAINNET_CHAIN_ID } from './network/enums'
// By default, poll every 3 minutes // By default, poll every 3 minutes
const DEFAULT_INTERVAL = 180 * 1000 const DEFAULT_INTERVAL = 180 * 1000
@ -38,7 +38,7 @@ export default class DetectTokensController {
if (!this.isActive) { if (!this.isActive) {
return return
} }
if (this._network.store.getState().provider.type !== MAINNET) { if (this._network.store.getState().provider.chainId !== MAINNET_CHAIN_ID) {
return return
} }
@ -47,7 +47,8 @@ export default class DetectTokensController {
for (const contractAddress in contracts) { for (const contractAddress in contracts) {
if ( if (
contracts[contractAddress].erc20 && contracts[contractAddress].erc20 &&
!this.tokenAddresses.includes(contractAddress.toLowerCase()) !this.tokenAddresses.includes(contractAddress.toLowerCase()) &&
!this.hiddenTokens.includes(contractAddress.toLowerCase())
) { ) {
tokensToDetect.push(contractAddress) tokensToDetect.push(contractAddress)
} }
@ -130,10 +131,12 @@ export default class DetectTokensController {
this.tokenAddresses = currentTokens this.tokenAddresses = currentTokens
? currentTokens.map((token) => token.address) ? currentTokens.map((token) => token.address)
: [] : []
preferences.store.subscribe(({ tokens = [] }) => { this.hiddenTokens = preferences.store.getState().hiddenTokens
preferences.store.subscribe(({ tokens = [], hiddenTokens = [] }) => {
this.tokenAddresses = tokens.map((token) => { this.tokenAddresses = tokens.map((token) => {
return token.address return token.address
}) })
this.hiddenTokens = hiddenTokens
}) })
preferences.store.subscribe(({ selectedAddress }) => { preferences.store.subscribe(({ selectedAddress }) => {
if (this.selectedAddress !== selectedAddress) { if (this.selectedAddress !== selectedAddress) {

@ -1,6 +1,6 @@
import punycode from 'punycode/punycode' import punycode from 'punycode/punycode'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import log from 'loglevel' import log from 'loglevel'
import Ens from './ens' import Ens from './ens'

@ -1,4 +1,4 @@
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import log from 'loglevel' import log from 'loglevel'
import BN from 'bn.js' import BN from 'bn.js'
import createId from '../lib/random-id' import createId from '../lib/random-id'
@ -249,9 +249,7 @@ export default class IncomingTransactionsController {
}) })
const incomingTxs = remoteTxs.filter( const incomingTxs = remoteTxs.filter(
(tx) => (tx) => tx.txParams?.to?.toLowerCase() === address.toLowerCase(),
tx.txParams.to &&
tx.txParams.to.toLowerCase() === address.toLowerCase(),
) )
incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1)) incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1))

@ -1,5 +1,5 @@
import { merge, omit } from 'lodash' import { merge, omit } from 'lodash'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import { bufferToHex, sha3 } from 'ethereumjs-util' import { bufferToHex, sha3 } from 'ethereumjs-util'
import { ENVIRONMENT_TYPE_BACKGROUND } from '../lib/enums' import { ENVIRONMENT_TYPE_BACKGROUND } from '../lib/enums'
import { import {

@ -7,7 +7,8 @@ import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware' import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'
import createInfuraMiddleware from 'eth-json-rpc-infura' import createInfuraMiddleware from 'eth-json-rpc-infura'
import BlockTracker from 'eth-block-tracker' import BlockTracker from 'eth-block-tracker'
import * as networkEnums from './enums'
import { NETWORK_TYPE_TO_ID_MAP } from './enums'
export default function createInfuraClient({ network, projectId }) { export default function createInfuraClient({ network, projectId }) {
const infuraMiddleware = createInfuraMiddleware({ const infuraMiddleware = createInfuraMiddleware({
@ -32,36 +33,14 @@ export default function createInfuraClient({ network, projectId }) {
} }
function createNetworkAndChainIdMiddleware({ network }) { function createNetworkAndChainIdMiddleware({ network }) {
let chainId if (!NETWORK_TYPE_TO_ID_MAP[network]) {
let netId
switch (network) {
case 'mainnet':
netId = networkEnums.MAINNET_NETWORK_ID
chainId = '0x01'
break
case 'ropsten':
netId = networkEnums.ROPSTEN_NETWORK_ID
chainId = '0x03'
break
case 'rinkeby':
netId = networkEnums.RINKEBY_NETWORK_ID
chainId = '0x04'
break
case 'kovan':
netId = networkEnums.KOVAN_NETWORK_ID
chainId = networkEnums.KOVAN_CHAIN_ID
break
case 'goerli':
netId = networkEnums.GOERLI_NETWORK_ID
chainId = '0x05'
break
default:
throw new Error(`createInfuraClient - unknown network "${network}"`) throw new Error(`createInfuraClient - unknown network "${network}"`)
} }
const { chainId, networkId } = NETWORK_TYPE_TO_ID_MAP[network]
return createScaffoldMiddleware({ return createScaffoldMiddleware({
eth_chainId: chainId, eth_chainId: chainId,
net_version: netId, net_version: networkId,
}) })
} }

@ -1,7 +1,6 @@
import assert from 'assert' import assert from 'assert'
import EventEmitter from 'events' import EventEmitter from 'events'
import ObservableStore from 'obs-store' import { ComposedStore, ObservableStore } from '@metamask/obs-store'
import ComposedStore from 'obs-store/lib/composed'
import { JsonRpcEngine } from 'json-rpc-engine' import { JsonRpcEngine } from 'json-rpc-engine'
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine' import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
import log from 'loglevel' import log from 'loglevel'
@ -52,9 +51,13 @@ export default class NetworkController extends EventEmitter {
this.providerStore = new ObservableStore( this.providerStore = new ObservableStore(
opts.provider || { ...defaultProviderConfig }, opts.provider || { ...defaultProviderConfig },
) )
this.previousProviderStore = new ObservableStore(
this.providerStore.getState(),
)
this.networkStore = new ObservableStore('loading') this.networkStore = new ObservableStore('loading')
this.store = new ComposedStore({ this.store = new ComposedStore({
provider: this.providerStore, provider: this.providerStore,
previousProviderStore: this.previousProviderStore,
network: this.networkStore, network: this.networkStore,
}) })
@ -189,6 +192,13 @@ export default class NetworkController extends EventEmitter {
* Sets the provider config and switches the network. * Sets the provider config and switches the network.
*/ */
setProviderConfig(config) { setProviderConfig(config) {
this.previousProviderStore.updateState(this.getProviderConfig())
this.providerStore.updateState(config)
this._switchNetwork(config)
}
rollbackToPreviousProvider() {
const config = this.previousProviderStore.getState()
this.providerStore.updateState(config) this.providerStore.updateState(config)
this._switchNetwork(config) this._switchNetwork(config)
} }

@ -1,4 +1,4 @@
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import log from 'loglevel' import log from 'loglevel'
/** /**

@ -1,3 +1,5 @@
export const APPROVAL_TYPE = 'wallet_requestPermissions'
export const WALLET_PREFIX = 'wallet_' export const WALLET_PREFIX = 'wallet_'
export const HISTORY_STORE_KEY = 'permissionsHistory' export const HISTORY_STORE_KEY = 'permissionsHistory'
@ -19,10 +21,15 @@ export const CAVEAT_TYPES = {
} }
export const NOTIFICATION_NAMES = { export const NOTIFICATION_NAMES = {
accountsChanged: 'wallet_accountsChanged', accountsChanged: 'metamask_accountsChanged',
unlockStateChanged: 'metamask_unlockStateChanged',
chainChanged: 'metamask_chainChanged',
} }
export const LOG_IGNORE_METHODS = ['wallet_sendDomainMetadata'] export const LOG_IGNORE_METHODS = [
'wallet_registerOnboarding',
'wallet_watchAsset',
]
export const LOG_METHOD_TYPES = { export const LOG_METHOD_TYPES = {
restricted: 'restricted', restricted: 'restricted',
@ -78,6 +85,7 @@ export const SAFE_METHODS = [
'eth_submitWork', 'eth_submitWork',
'eth_syncing', 'eth_syncing',
'eth_uninstallFilter', 'eth_uninstallFilter',
'metamask_getProviderState',
'metamask_watchAsset', 'metamask_watchAsset',
'net_listening', 'net_listening',
'net_peerCount', 'net_peerCount',

@ -1,6 +1,6 @@
import nanoid from 'nanoid' import nanoid from 'nanoid'
import { JsonRpcEngine } from 'json-rpc-engine' import { JsonRpcEngine } from 'json-rpc-engine'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import log from 'loglevel' import log from 'loglevel'
import { CapabilitiesController as RpcCap } from 'rpc-cap' import { CapabilitiesController as RpcCap } from 'rpc-cap'
import { ethErrors } from 'eth-json-rpc-errors' import { ethErrors } from 'eth-json-rpc-errors'
@ -11,6 +11,7 @@ import PermissionsLogController from './permissionsLog'
// Methods that do not require any permissions to use: // Methods that do not require any permissions to use:
import { import {
APPROVAL_TYPE,
SAFE_METHODS, // methods that do not require any permissions to use SAFE_METHODS, // methods that do not require any permissions to use
WALLET_PREFIX, WALLET_PREFIX,
METADATA_STORE_KEY, METADATA_STORE_KEY,
@ -22,16 +23,20 @@ import {
CAVEAT_TYPES, CAVEAT_TYPES,
} from './enums' } from './enums'
// instanbul ignore next
const noop = () => undefined
export class PermissionsController { export class PermissionsController {
constructor( constructor(
{ {
approvals,
getKeyringAccounts, getKeyringAccounts,
getRestrictedMethods, getRestrictedMethods,
getUnlockPromise, getUnlockPromise,
isUnlocked,
notifyDomain, notifyDomain,
notifyAllDomains, notifyAllDomains,
preferences, preferences,
showPermissionRequest,
} = {}, } = {},
restoredPermissions = {}, restoredPermissions = {},
restoredState = {}, restoredState = {},
@ -46,7 +51,7 @@ export class PermissionsController {
this._getUnlockPromise = getUnlockPromise this._getUnlockPromise = getUnlockPromise
this._notifyDomain = notifyDomain this._notifyDomain = notifyDomain
this._notifyAllDomains = notifyAllDomains this._notifyAllDomains = notifyAllDomains
this._showPermissionRequest = showPermissionRequest this._isUnlocked = isUnlocked
this._restrictedMethods = getRestrictedMethods({ this._restrictedMethods = getRestrictedMethods({
getKeyringAccounts: this.getKeyringAccounts.bind(this), getKeyringAccounts: this.getKeyringAccounts.bind(this),
@ -56,8 +61,12 @@ export class PermissionsController {
restrictedMethods: Object.keys(this._restrictedMethods), restrictedMethods: Object.keys(this._restrictedMethods),
store: this.store, store: this.store,
}) })
this.pendingApprovals = new Map()
this.pendingApprovalOrigins = new Set() /**
* @type {import('@metamask/controllers').ApprovalController}
* @public
*/
this.approvals = approvals
this._initializePermissions(restoredPermissions) this._initializePermissions(restoredPermissions)
this._lastSelectedAddress = preferences.getState().selectedAddress this._lastSelectedAddress = preferences.getState().selectedAddress
this.preferences = preferences this.preferences = preferences
@ -137,7 +146,7 @@ export class PermissionsController {
{ origin }, { origin },
req, req,
res, res,
() => undefined, noop,
_end, _end,
) )
@ -191,13 +200,7 @@ export class PermissionsController {
} }
const res = {} const res = {}
this.permissions.providerMiddlewareFunction( this.permissions.providerMiddlewareFunction(domain, req, res, noop, _end)
domain,
req,
res,
() => undefined,
_end,
)
function _end(_err) { function _end(_err) {
const err = _err || res.error const err = _err || res.error
@ -221,16 +224,16 @@ export class PermissionsController {
*/ */
async approvePermissionsRequest(approved, accounts) { async approvePermissionsRequest(approved, accounts) {
const { id } = approved.metadata const { id } = approved.metadata
const approval = this.pendingApprovals.get(id)
if (!approval) { if (!this.approvals.has({ id })) {
log.debug(`Permissions request with id '${id}' not found`) log.debug(`Permissions request with id '${id}' not found.`)
return return
} }
try { try {
if (Object.keys(approved.permissions).length === 0) { if (Object.keys(approved.permissions).length === 0) {
approval.reject( this.approvals.reject(
id,
ethErrors.rpc.invalidRequest({ ethErrors.rpc.invalidRequest({
message: 'Must request at least one permission.', message: 'Must request at least one permission.',
}), }),
@ -242,19 +245,18 @@ export class PermissionsController {
approved.permissions, approved.permissions,
accounts, accounts,
) )
approval.resolve(approved.permissions) this.approvals.resolve(id, approved.permissions)
} }
} catch (err) { } catch (err) {
// if finalization fails, reject the request // if finalization fails, reject the request
approval.reject( this.approvals.reject(
id,
ethErrors.rpc.invalidRequest({ ethErrors.rpc.invalidRequest({
message: err.message, message: err.message,
data: err, data: err,
}), }),
) )
} }
this._removePendingApproval(id)
} }
/** /**
@ -265,15 +267,12 @@ export class PermissionsController {
* @param {string} id - The id of the request rejected by the user * @param {string} id - The id of the request rejected by the user
*/ */
async rejectPermissionsRequest(id) { async rejectPermissionsRequest(id) {
const approval = this.pendingApprovals.get(id) if (!this.approvals.has({ id })) {
log.debug(`Permissions request with id '${id}' not found.`)
if (!approval) {
log.debug(`Permissions request with id '${id}' not found`)
return return
} }
approval.reject(ethErrors.provider.userRejectedRequest()) this.approvals.reject(id, ethErrors.provider.userRejectedRequest())
this._removePendingApproval(id)
} }
/** /**
@ -463,21 +462,20 @@ export class PermissionsController {
throw new Error('Invalid accounts', newAccounts) throw new Error('Invalid accounts', newAccounts)
} }
// We do not share accounts when the extension is locked.
if (this._isUnlocked()) {
this._notifyDomain(origin, { this._notifyDomain(origin, {
method: NOTIFICATION_NAMES.accountsChanged, method: NOTIFICATION_NAMES.accountsChanged,
result: newAccounts, params: newAccounts,
}) })
// if the accounts changed from the perspective of the dapp,
// update "last seen" time for the origin and account(s)
// exception: no accounts -> no times to update
this.permissionsLog.updateAccountsHistory(origin, newAccounts) this.permissionsLog.updateAccountsHistory(origin, newAccounts)
}
// NOTE: // NOTE:
// we don't check for accounts changing in the notifyAllDomains case, // We don't check for accounts changing in the notifyAllDomains case,
// because the log only records when accounts were last seen, // because the log only records when accounts were last seen, and the
// and the accounts only change for all domains at once when permissions // the accounts only change for all domains at once when permissions are
// are removed // removed.
} }
/** /**
@ -508,9 +506,11 @@ export class PermissionsController {
*/ */
clearPermissions() { clearPermissions() {
this.permissions.clearDomains() this.permissions.clearDomains()
// It's safe to notify that no accounts are available, regardless of
// extension lock state
this._notifyAllDomains({ this._notifyAllDomains({
method: NOTIFICATION_NAMES.accountsChanged, method: NOTIFICATION_NAMES.accountsChanged,
result: [], params: [],
}) })
} }
@ -667,37 +667,6 @@ export class PermissionsController {
this.notifyAccountsChanged(origin, permittedAccounts) this.notifyAccountsChanged(origin, permittedAccounts)
} }
/**
* Adds a pending approval.
* @param {string} id - The id of the pending approval.
* @param {string} origin - The origin of the pending approval.
* @param {Function} resolve - The function resolving the pending approval Promise.
* @param {Function} reject - The function rejecting the pending approval Promise.
*/
_addPendingApproval(id, origin, resolve, reject) {
if (
this.pendingApprovalOrigins.has(origin) ||
this.pendingApprovals.has(id)
) {
throw new Error(
`Pending approval with id '${id}' or origin '${origin}' already exists.`,
)
}
this.pendingApprovals.set(id, { origin, resolve, reject })
this.pendingApprovalOrigins.add(origin)
}
/**
* Removes the pending approval with the given id.
* @param {string} id - The id of the pending approval to remove.
*/
_removePendingApproval(id) {
const { origin } = this.pendingApprovals.get(id)
this.pendingApprovalOrigins.delete(origin)
this.pendingApprovals.delete(id)
}
/** /**
* A convenience method for retrieving a login object * A convenience method for retrieving a login object
* or creating a new one if needed. * or creating a new one if needed.
@ -732,16 +701,10 @@ export class PermissionsController {
metadata: { id, origin }, metadata: { id, origin },
} = req } = req
if (this.pendingApprovalOrigins.has(origin)) { return this.approvals.addAndShowApprovalRequest({
throw ethErrors.rpc.resourceUnavailable( id,
'Permissions request already pending; please wait.', origin,
) type: APPROVAL_TYPE,
}
this._showPermissionRequest()
return new Promise((resolve, reject) => {
this._addPendingApproval(id, origin, resolve, reject)
}) })
}, },
}, },
@ -749,7 +712,3 @@ export class PermissionsController {
) )
} }
} }
export function addInternalMethodPrefix(method) {
return WALLET_PREFIX + method
}

@ -58,6 +58,7 @@ export default class PermissionsLogController {
/** /**
* Updates the exposed account history for the given origin. * Updates the exposed account history for the given origin.
* Sets the 'last seen' time to Date.now() for the given accounts. * Sets the 'last seen' time to Date.now() for the given accounts.
* Returns if the accounts array is empty.
* *
* @param {string} origin - The origin that the accounts are exposed to. * @param {string} origin - The origin that the accounts are exposed to.
* @param {Array<string>} accounts - The accounts. * @param {Array<string>} accounts - The accounts.

@ -73,7 +73,7 @@ export default function createPermissionsMethodMiddleware({
// custom method for getting metadata from the requesting domain, // custom method for getting metadata from the requesting domain,
// sent automatically by the inpage provider when it's initialized // sent automatically by the inpage provider when it's initialized
case 'wallet_sendDomainMetadata': { case 'metamask_sendDomainMetadata': {
if (typeof req.domainMetadata?.name === 'string') { if (typeof req.domainMetadata?.name === 'string') {
addDomainMetadata(req.origin, req.domainMetadata) addDomainMetadata(req.origin, req.domainMetadata)
} }

@ -1,5 +1,5 @@
import { strict as assert } from 'assert' import { strict as assert } from 'assert'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import { ethErrors } from 'eth-json-rpc-errors' import { ethErrors } from 'eth-json-rpc-errors'
import { normalize as normalizeAddress } from 'eth-sig-util' import { normalize as normalizeAddress } from 'eth-sig-util'
import { isValidAddress } from 'ethereumjs-util' import { isValidAddress } from 'ethereumjs-util'
@ -34,8 +34,10 @@ export default class PreferencesController {
const initState = { const initState = {
frequentRpcListDetail: [], frequentRpcListDetail: [],
accountTokens: {}, accountTokens: {},
accountHiddenTokens: {},
assetImages: {}, assetImages: {},
tokens: [], tokens: [],
hiddenTokens: [],
suggestedTokens: {}, suggestedTokens: {},
useBlockie: false, useBlockie: false,
useNonceField: false, useNonceField: false,
@ -191,6 +193,7 @@ export default class PreferencesController {
setAddresses(addresses) { setAddresses(addresses) {
const oldIdentities = this.store.getState().identities const oldIdentities = this.store.getState().identities
const oldAccountTokens = this.store.getState().accountTokens const oldAccountTokens = this.store.getState().accountTokens
const oldAccountHiddenTokens = this.store.getState().accountHiddenTokens
const identities = addresses.reduce((ids, address, index) => { const identities = addresses.reduce((ids, address, index) => {
const oldId = oldIdentities[address] || {} const oldId = oldIdentities[address] || {}
@ -202,7 +205,12 @@ export default class PreferencesController {
tokens[address] = oldTokens tokens[address] = oldTokens
return tokens return tokens
}, {}) }, {})
this.store.updateState({ identities, accountTokens }) const accountHiddenTokens = addresses.reduce((hiddenTokens, address) => {
const oldHiddenTokens = oldAccountHiddenTokens[address] || {}
hiddenTokens[address] = oldHiddenTokens
return hiddenTokens
}, {})
this.store.updateState({ identities, accountTokens, accountHiddenTokens })
} }
/** /**
@ -212,14 +220,19 @@ export default class PreferencesController {
* @returns {string} the address that was removed * @returns {string} the address that was removed
*/ */
removeAddress(address) { removeAddress(address) {
const { identities } = this.store.getState() const {
const { accountTokens } = this.store.getState() identities,
accountTokens,
accountHiddenTokens,
} = this.store.getState()
if (!identities[address]) { if (!identities[address]) {
throw new Error(`${address} can't be deleted cause it was not found`) throw new Error(`${address} can't be deleted cause it was not found`)
} }
delete identities[address] delete identities[address]
delete accountTokens[address] delete accountTokens[address]
this.store.updateState({ identities, accountTokens }) delete accountHiddenTokens[address]
this.store.updateState({ identities, accountTokens, accountHiddenTokens })
// If the selected account is no longer valid, // If the selected account is no longer valid,
// select an arbitrary other account: // select an arbitrary other account:
@ -237,7 +250,11 @@ export default class PreferencesController {
* *
*/ */
addAddresses(addresses) { addAddresses(addresses) {
const { identities, accountTokens } = this.store.getState() const {
identities,
accountTokens,
accountHiddenTokens,
} = this.store.getState()
addresses.forEach((address) => { addresses.forEach((address) => {
// skip if already exists // skip if already exists
if (identities[address]) { if (identities[address]) {
@ -247,9 +264,10 @@ export default class PreferencesController {
const identityCount = Object.keys(identities).length const identityCount = Object.keys(identities).length
accountTokens[address] = {} accountTokens[address] = {}
accountHiddenTokens[address] = {}
identities[address] = { name: `Account ${identityCount + 1}`, address } identities[address] = { name: `Account ${identityCount + 1}`, address }
}) })
this.store.updateState({ identities, accountTokens }) this.store.updateState({ identities, accountTokens, accountHiddenTokens })
} }
/** /**
@ -346,7 +364,7 @@ export default class PreferencesController {
*/ */
/** /**
* Adds a new token to the token array, or updates the token if passed an address that already exists. * Adds a new token to the token array and removes it from the hiddenToken array, or updates the token if passed an address that already exists.
* Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects. * Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects.
* @see AddedToken {@link AddedToken} * @see AddedToken {@link AddedToken}
* *
@ -359,8 +377,11 @@ export default class PreferencesController {
async addToken(rawAddress, symbol, decimals, image) { async addToken(rawAddress, symbol, decimals, image) {
const address = normalizeAddress(rawAddress) const address = normalizeAddress(rawAddress)
const newEntry = { address, symbol, decimals } const newEntry = { address, symbol, decimals }
const { tokens } = this.store.getState() const { tokens, hiddenTokens } = this.store.getState()
const assetImages = this.getAssetImages() const assetImages = this.getAssetImages()
const updatedHiddenTokens = hiddenTokens.filter(
(tokenAddress) => tokenAddress !== rawAddress.toLowerCase(),
)
const previousEntry = tokens.find((token) => { const previousEntry = tokens.find((token) => {
return token.address === address return token.address === address
}) })
@ -372,23 +393,24 @@ export default class PreferencesController {
tokens.push(newEntry) tokens.push(newEntry)
} }
assetImages[address] = image assetImages[address] = image
this._updateAccountTokens(tokens, assetImages) this._updateAccountTokens(tokens, assetImages, updatedHiddenTokens)
return Promise.resolve(tokens) return Promise.resolve(tokens)
} }
/** /**
* Removes a specified token from the tokens array. * Removes a specified token from the tokens array and adds it to hiddenTokens array
* *
* @param {string} rawAddress - Hex address of the token contract to remove. * @param {string} rawAddress - Hex address of the token contract to remove.
* @returns {Promise<array>} The new array of AddedToken objects * @returns {Promise<array>} The new array of AddedToken objects
* *
*/ */
removeToken(rawAddress) { removeToken(rawAddress) {
const { tokens } = this.store.getState() const { tokens, hiddenTokens } = this.store.getState()
const assetImages = this.getAssetImages() const assetImages = this.getAssetImages()
const updatedTokens = tokens.filter((token) => token.address !== rawAddress) const updatedTokens = tokens.filter((token) => token.address !== rawAddress)
const updatedHiddenTokens = [...hiddenTokens, rawAddress.toLowerCase()]
delete assetImages[rawAddress] delete assetImages[rawAddress]
this._updateAccountTokens(updatedTokens, assetImages) this._updateAccountTokens(updatedTokens, assetImages, updatedHiddenTokens)
return Promise.resolve(updatedTokens) return Promise.resolve(updatedTokens)
} }
@ -643,47 +665,59 @@ export default class PreferencesController {
*/ */
_subscribeProviderType() { _subscribeProviderType() {
this.network.providerStore.subscribe(() => { this.network.providerStore.subscribe(() => {
const { tokens } = this._getTokenRelatedStates() const { tokens, hiddenTokens } = this._getTokenRelatedStates()
this.store.updateState({ tokens }) this._updateAccountTokens(tokens, this.getAssetImages(), hiddenTokens)
}) })
} }
/** /**
* Updates `accountTokens` and `tokens` of current account and network according to it. * Updates `accountTokens`, `tokens`, `accountHiddenTokens` and `hiddenTokens` of current account and network according to it.
* *
* @param {Array} tokens - Array of tokens to be updated. * @param {array} tokens - Array of tokens to be updated.
* @param {array} assetImages - Array of assets objects related to assets added
* @param {array} hiddenTokens - Array of tokens hidden by user
* *
*/ */
_updateAccountTokens(tokens, assetImages) { _updateAccountTokens(tokens, assetImages, hiddenTokens) {
const { const {
accountTokens, accountTokens,
providerType, providerType,
selectedAddress, selectedAddress,
accountHiddenTokens,
} = this._getTokenRelatedStates() } = this._getTokenRelatedStates()
accountTokens[selectedAddress][providerType] = tokens accountTokens[selectedAddress][providerType] = tokens
this.store.updateState({ accountTokens, tokens, assetImages }) accountHiddenTokens[selectedAddress][providerType] = hiddenTokens
this.store.updateState({
accountTokens,
tokens,
assetImages,
accountHiddenTokens,
hiddenTokens,
})
} }
/** /**
* Updates `tokens` of current account and network. * Updates `tokens` and `hiddenTokens` of current account and network.
* *
* @param {string} selectedAddress - Account address to be updated with. * @param {string} selectedAddress - Account address to be updated with.
* *
*/ */
_updateTokens(selectedAddress) { _updateTokens(selectedAddress) {
const { tokens } = this._getTokenRelatedStates(selectedAddress) const { tokens, hiddenTokens } = this._getTokenRelatedStates(
this.store.updateState({ tokens }) selectedAddress,
)
this.store.updateState({ tokens, hiddenTokens })
} }
/** /**
* A getter for `tokens` and `accountTokens` related states. * A getter for `tokens`, `accountTokens`, `hiddenTokens` and `accountHiddenTokens` related states.
* *
* @param {string} [selectedAddress] - A new hex address for an account * @param {string} [selectedAddress] - A new hex address for an account
* @returns {Object.<array, object, string, string>} States to interact with tokens in `accountTokens` * @returns {Object.<array, object, string, string>} States to interact with tokens in `accountTokens`
* *
*/ */
_getTokenRelatedStates(selectedAddress) { _getTokenRelatedStates(selectedAddress) {
const { accountTokens } = this.store.getState() const { accountTokens, accountHiddenTokens } = this.store.getState()
if (!selectedAddress) { if (!selectedAddress) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
selectedAddress = this.store.getState().selectedAddress selectedAddress = this.store.getState().selectedAddress
@ -692,11 +726,25 @@ export default class PreferencesController {
if (!(selectedAddress in accountTokens)) { if (!(selectedAddress in accountTokens)) {
accountTokens[selectedAddress] = {} accountTokens[selectedAddress] = {}
} }
if (!(selectedAddress in accountHiddenTokens)) {
accountHiddenTokens[selectedAddress] = {}
}
if (!(providerType in accountTokens[selectedAddress])) { if (!(providerType in accountTokens[selectedAddress])) {
accountTokens[selectedAddress][providerType] = [] accountTokens[selectedAddress][providerType] = []
} }
if (!(providerType in accountHiddenTokens[selectedAddress])) {
accountHiddenTokens[selectedAddress][providerType] = []
}
const tokens = accountTokens[selectedAddress][providerType] const tokens = accountTokens[selectedAddress][providerType]
return { tokens, accountTokens, providerType, selectedAddress } const hiddenTokens = accountHiddenTokens[selectedAddress][providerType]
return {
tokens,
accountTokens,
hiddenTokens,
accountHiddenTokens,
providerType,
selectedAddress,
}
} }
/** /**

@ -1,7 +1,7 @@
import { ethers } from 'ethers' import { ethers } from 'ethers'
import log from 'loglevel' import log from 'loglevel'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import { mapValues, cloneDeep } from 'lodash' import { mapValues, cloneDeep } from 'lodash'
import abi from 'human-standard-token-abi' import abi from 'human-standard-token-abi'
import { calcTokenAmount } from '../../../ui/app/helpers/utils/token-util' import { calcTokenAmount } from '../../../ui/app/helpers/utils/token-util'

@ -1,4 +1,4 @@
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
/* eslint-disable import/first,import/order */ /* eslint-disable import/first,import/order */
const Box = process.env.IN_TEST const Box = process.env.IN_TEST

@ -1,4 +1,4 @@
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import log from 'loglevel' import log from 'loglevel'
import { normalize as normalizeAddress } from 'eth-sig-util' import { normalize as normalizeAddress } from 'eth-sig-util'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'

@ -1,5 +1,5 @@
import EventEmitter from 'safe-event-emitter' import EventEmitter from 'safe-event-emitter'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
import Transaction from 'ethereumjs-tx' import Transaction from 'ethereumjs-tx'
import EthQuery from 'ethjs-query' import EthQuery from 'ethjs-query'

@ -1,5 +1,5 @@
import EventEmitter from 'safe-event-emitter' import EventEmitter from 'safe-event-emitter'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import log from 'loglevel' import log from 'loglevel'
import createId from '../../lib/random-id' import createId from '../../lib/random-id'
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction' import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction'

@ -33,11 +33,7 @@ cleanContextForImports()
/* eslint-disable import/first */ /* eslint-disable import/first */
import log from 'loglevel' import log from 'loglevel'
import LocalMessageDuplexStream from 'post-message-stream' import LocalMessageDuplexStream from 'post-message-stream'
import { initProvider } from '@metamask/inpage-provider' import { initializeProvider } from '@metamask/inpage-provider'
// TODO:deprecate:2020
import setupWeb3 from './lib/setupWeb3'
/* eslint-enable import/first */
restoreContextAfterImports() restoreContextAfterImports()
@ -49,24 +45,12 @@ log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
// setup background connection // setup background connection
const metamaskStream = new LocalMessageDuplexStream({ const metamaskStream = new LocalMessageDuplexStream({
name: 'inpage', name: 'metamask-inpage',
target: 'contentscript', target: 'metamask-contentscript',
}) })
initProvider({ initializeProvider({
connectionStream: metamaskStream, connectionStream: metamaskStream,
logger: log,
shouldShimWeb3: true,
}) })
// TODO:deprecate:2020
// Setup web3
if (typeof window.web3 === 'undefined') {
// proxy web3, assign to window, and set up site auto reload
setupWeb3(log)
} else {
log.warn(`MetaMask detected another web3.
MetaMask will not work reliably with another web3 extension.
This usually happens if you have two MetaMasks installed,
or MetaMask and another web3 extension. Please remove one
and try again.`)
}

@ -1,4 +1,4 @@
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
/** /**
* An ObservableStore that can composes a flat * An ObservableStore that can composes a flat

@ -9,7 +9,7 @@
import EthQuery from 'eth-query' import EthQuery from 'eth-query'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import log from 'loglevel' import log from 'loglevel'
import pify from 'pify' import pify from 'pify'
import Web3 from 'web3' import Web3 from 'web3'

@ -1,5 +1,5 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors' import { ethErrors } from 'eth-json-rpc-errors'
import log from 'loglevel' import log from 'loglevel'

@ -1,5 +1,5 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import { ethErrors } from 'eth-json-rpc-errors' import { ethErrors } from 'eth-json-rpc-errors'
import log from 'loglevel' import log from 'loglevel'
import createId from './random-id' import createId from './random-id'

@ -23,7 +23,8 @@ const MESSAGE_TYPE = {
ETH_GET_ENCRYPTION_PUBLIC_KEY: 'eth_getEncryptionPublicKey', ETH_GET_ENCRYPTION_PUBLIC_KEY: 'eth_getEncryptionPublicKey',
ETH_SIGN: 'eth_sign', ETH_SIGN: 'eth_sign',
ETH_SIGN_TYPED_DATA: 'eth_signTypedData', ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
LOG_WEB3_USAGE: 'metamask_logInjectedWeb3Usage', GET_PROVIDER_STATE: 'metamask_getProviderState',
LOG_WEB3_SHIM_USAGE: 'metamask_logWeb3ShimUsage',
PERSONAL_SIGN: 'personal_sign', PERSONAL_SIGN: 'personal_sign',
WATCH_ASSET: 'wallet_watchAsset', WATCH_ASSET: 'wallet_watchAsset',
WATCH_ASSET_LEGACY: 'metamask_watchAsset', WATCH_ASSET_LEGACY: 'metamask_watchAsset',

@ -1,10 +1,10 @@
const fetchWithTimeout = ({ timeout = 120000 } = {}) => { const fetchWithTimeout = ({ timeout = 120000 } = {}) => {
return async function _fetch(url, opts) { return async function _fetch(url, opts) {
const abortController = new window.AbortController() const abortController = new window.AbortController()
const abortSignal = abortController.signal const { signal } = abortController
const f = window.fetch(url, { const f = window.fetch(url, {
...opts, ...opts,
signal: abortSignal, signal,
}) })
const timer = setTimeout(() => abortController.abort(), timeout) const timer = setTimeout(() => abortController.abort(), timeout)

@ -1,5 +1,5 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors' import { ethErrors } from 'eth-json-rpc-errors'
import createId from './random-id' import createId from './random-id'

@ -1,5 +1,5 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors' import { ethErrors } from 'eth-json-rpc-errors'
import log from 'loglevel' import log from 'loglevel'

@ -21,7 +21,6 @@ const handlerMap = handlers.reduce((map, handler) => {
* Eventually, we'll want to extract this middleware into its own package. * Eventually, we'll want to extract this middleware into its own package.
* *
* @param {Object} opts - The middleware options * @param {Object} opts - The middleware options
* @param {string} opts.origin - The origin for the middleware stack
* @param {Function} opts.sendMetrics - A function for sending a metrics event * @param {Function} opts.sendMetrics - A function for sending a metrics event
* @returns {(req: Object, res: Object, next: Function, end: Function) => void} * @returns {(req: Object, res: Object, next: Function, end: Function) => void}
*/ */

@ -0,0 +1,46 @@
import { MESSAGE_TYPE } from '../../enums'
/**
* This RPC method gets background state relevant to the provider.
* The background sends RPC notifications on state changes, but the provider
* first requests state on initialization.
*/
const getProviderState = {
methodNames: [MESSAGE_TYPE.GET_PROVIDER_STATE],
implementation: getProviderStateHandler,
}
export default getProviderState
/**
* @typedef {Object} ProviderStateHandlerResult
* @property {string} chainId - The current chain ID.
* @property {boolean} isUnlocked - Whether the extension is unlocked or not.
* @property {string} networkVersion - The current network ID.
*/
/**
* @typedef {Object} ProviderStateHandlerOptions
* @property {() => ProviderStateHandlerResult} getProviderState - A function that
* gets the current provider state.
*/
/**
* @param {import('json-rpc-engine').JsonRpcRequest<[]>} req - The JSON-RPC request object.
* @param {import('json-rpc-engine').JsonRpcResponse<ProviderStateHandlerResult>} res - The JSON-RPC response object.
* @param {Function} _next - The json-rpc-engine 'next' callback.
* @param {Function} end - The json-rpc-engine 'end' callback.
* @param {ProviderStateHandlerOptions} options
*/
async function getProviderStateHandler(
req,
res,
_next,
end,
{ getProviderState: _getProviderState },
) {
res.result = {
...(await _getProviderState(req.origin)),
}
return end()
}

@ -1,5 +1,6 @@
import logWeb3Usage from './log-web3-usage' import getProviderState from './get-provider-state'
import logWeb3ShimUsage from './log-web3-shim-usage'
import watchAsset from './watch-asset' import watchAsset from './watch-asset'
const handlers = [logWeb3Usage, watchAsset] const handlers = [getProviderState, logWeb3ShimUsage, watchAsset]
export default handlers export default handlers

@ -0,0 +1,57 @@
import { MESSAGE_TYPE } from '../../enums'
/**
* This RPC method is called by the inpage provider whenever it detects the
* accessing of a non-existent property on our window.web3 shim.
* We collect this data to understand which sites are breaking due to the
* removal of our window.web3.
*/
const logWeb3ShimUsage = {
methodNames: [MESSAGE_TYPE.LOG_WEB3_SHIM_USAGE],
implementation: logWeb3ShimUsageHandler,
}
export default logWeb3ShimUsage
/**
* @typedef {Object} LogWeb3ShimUsageOptions
* @property {Function} sendMetrics - A function that registers a metrics event.
* @property {Function} getWeb3ShimUsageState - A function that gets web3 shim
* usage state for the given origin.
* @property {Function} setWeb3ShimUsageRecorded - A function that records web3 shim
* usage for a particular origin.
*/
/**
* @param {import('json-rpc-engine').JsonRpcRequest<unknown>} req - The JSON-RPC request object.
* @param {import('json-rpc-engine').JsonRpcResponse<true>} res - The JSON-RPC response object.
* @param {Function} _next - The json-rpc-engine 'next' callback.
* @param {Function} end - The json-rpc-engine 'end' callback.
* @param {LogWeb3ShimUsageOptions} options
*/
function logWeb3ShimUsageHandler(
req,
res,
_next,
end,
{ sendMetrics, getWeb3ShimUsageState, setWeb3ShimUsageRecorded },
) {
const { origin } = req
if (getWeb3ShimUsageState(origin) === undefined) {
setWeb3ShimUsageRecorded(origin)
sendMetrics({
event: `Website Accessed window.web3 Shim`,
category: 'inpage_provider',
eventContext: {
referrer: {
url: origin,
},
},
excludeMetaMetricsId: true,
})
}
res.result = true
return end()
}

@ -1,63 +0,0 @@
import { MESSAGE_TYPE } from '../../enums'
/**
* This RPC method is called by our inpage web3 proxy whenever window.web3 is
* accessed. We're collecting data on window.web3 usage so that we can warn
* website maintainers, and possibly our users, before we remove window.web3
* by November 16, 2020.
*/
const logWeb3Usage = {
methodNames: [MESSAGE_TYPE.LOG_WEB3_USAGE],
implementation: logWeb3UsageHandler,
}
export default logWeb3Usage
const recordedWeb3Usage = {}
/**
* @typedef {Object} LogWeb3UsageOptions
* @property {string} origin - The origin of the request.
* @property {Function} sendMetrics - A function that registers a metrics event.
*/
/**
* @typedef {Object} LogWeb3UsageParam
* @property {string} action - The action taken (get or set).
* @property {string} name - The window.web3 property name subject to the action.
*/
/**
* @param {import('json-rpc-engine').JsonRpcRequest<[LogWeb3UsageParam]>} req - The JSON-RPC request object.
* @param {import('json-rpc-engine').JsonRpcResponse<true>} res - The JSON-RPC response object.
* @param {Function} _next - The json-rpc-engine 'next' callback.
* @param {Function} end - The json-rpc-engine 'end' callback.
* @param {LogWeb3UsageOptions} options
*/
function logWeb3UsageHandler(req, res, _next, end, { origin, sendMetrics }) {
const { action, path } = req.params[0]
if (!recordedWeb3Usage[origin]) {
recordedWeb3Usage[origin] = {}
}
if (!recordedWeb3Usage[origin][path]) {
recordedWeb3Usage[origin][path] = true
sendMetrics(
{
event: `Website Used window.web3`,
category: 'inpage_provider',
properties: { action, web3Path: path },
referrer: {
url: origin,
},
},
{
excludeMetaMetricsId: true,
},
)
}
res.result = true
return end()
}

@ -1,251 +0,0 @@
/*global Web3*/
// TODO:deprecate:2020
// Delete this file
import web3Entitites from './web3-entities.json'
import 'web3/dist/web3.min'
const shouldLogUsage = ![
'docs.metamask.io',
'metamask.github.io',
'metamask.io',
].includes(window.location.hostname)
/**
* To understand how we arrived at this implementation, please see:
* https://github.com/ethereum/web3.js/blob/0.20.7/DOCUMENTATION.md
*/
export default function setupWeb3(log) {
// export web3 as a global, checking for usage
let reloadInProgress = false
let lastTimeUsed
let lastSeenNetwork
let hasBeenWarned = false
const web3 = new Web3(window.ethereum)
web3.setProvider = function () {
log.debug('MetaMask - overrode web3.setProvider')
}
Object.defineProperty(web3, '__isMetaMaskShim__', {
value: true,
enumerable: false,
configurable: false,
writable: false,
})
Object.defineProperty(window.ethereum, '_web3Ref', {
enumerable: false,
writable: true,
configurable: true,
value: web3.eth,
})
// Setup logging of nested property usage
if (shouldLogUsage) {
// web3 namespaces with common and uncommon dapp actions
const includedTopKeys = [
'eth',
'db',
'shh',
'net',
'personal',
'bzz',
'version',
]
// For each top-level property, create appropriate Proxy traps for all of
// their properties
includedTopKeys.forEach((topKey) => {
const applyTrapKeys = new Map()
const getTrapKeys = new Map()
Object.keys(web3[topKey]).forEach((key) => {
const path = `web3.${topKey}.${key}`
if (web3Entitites[path]) {
if (web3Entitites[path] === 'function') {
applyTrapKeys.set(key, path)
} else {
getTrapKeys.set(key, path)
}
}
})
// Create apply traps for function properties
for (const [key, path] of applyTrapKeys) {
web3[topKey][key] = new Proxy(web3[topKey][key], {
apply: (...params) => {
try {
window.ethereum.request({
method: 'metamask_logInjectedWeb3Usage',
params: [
{
action: 'apply',
path,
},
],
})
} catch (error) {
log.debug('Failed to log web3 usage.', error)
}
// Call function normally
return Reflect.apply(...params)
},
})
}
// Create get trap for non-function properties
web3[topKey] = new Proxy(web3[topKey], {
get: (web3Prop, key, ...params) => {
const name = stringifyKey(key)
if (getTrapKeys.has(name)) {
try {
window.ethereum.request({
method: 'metamask_logInjectedWeb3Usage',
params: [
{
action: 'get',
path: getTrapKeys.get(name),
},
],
})
} catch (error) {
log.debug('Failed to log web3 usage.', error)
}
}
// return value normally
return Reflect.get(web3Prop, key, ...params)
},
})
})
const topLevelFunctions = [
'isConnected',
'setProvider',
'reset',
'sha3',
'toHex',
'toAscii',
'fromAscii',
'toDecimal',
'fromDecimal',
'fromWei',
'toWei',
'toBigNumber',
'isAddress',
]
// apply-trap top-level functions
topLevelFunctions.forEach((key) => {
// This type check is probably redundant, but we've been burned before.
if (typeof web3[key] === 'function') {
web3[key] = new Proxy(web3[key], {
apply: (...params) => {
try {
window.ethereum.request({
method: 'metamask_logInjectedWeb3Usage',
params: [
{
action: 'apply',
path: `web3.${key}`,
},
],
})
} catch (error) {
log.debug('Failed to log web3 usage.', error)
}
// Call function normally
return Reflect.apply(...params)
},
})
}
})
}
const web3Proxy = new Proxy(web3, {
get: (...params) => {
// get the time of use
lastTimeUsed = Date.now()
// show warning once on web3 access
if (!hasBeenWarned) {
console.warn(
`MetaMask: We will stop injecting web3 in Q4 2020.\nPlease see this article for more information: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`,
)
hasBeenWarned = true
}
// return value normally
return Reflect.get(...params)
},
})
Object.defineProperty(window, 'web3', {
enumerable: false,
writable: true,
configurable: true,
value: web3Proxy,
})
log.debug('MetaMask - injected web3')
window.ethereum._publicConfigStore.subscribe((state) => {
// if the auto refresh on network change is false do not
// do anything
if (!window.ethereum.autoRefreshOnNetworkChange) {
return
}
// if reload in progress, no need to check reload logic
if (reloadInProgress) {
return
}
const currentNetwork = state.networkVersion
// set the initial network
if (!lastSeenNetwork) {
lastSeenNetwork = currentNetwork
return
}
// skip reload logic if web3 not used
if (!lastTimeUsed) {
return
}
// if network did not change, exit
if (currentNetwork === lastSeenNetwork) {
return
}
// initiate page reload
reloadInProgress = true
const timeSinceUse = Date.now() - lastTimeUsed
// if web3 was recently used then delay the reloading of the page
if (timeSinceUse > 500) {
triggerReset()
} else {
setTimeout(triggerReset, 500)
}
})
}
// reload the page
function triggerReset() {
window.location.reload()
}
/**
* Returns a "stringified" key. Keys that are already strings are returned
* unchanged, and any non-string values are returned as "typeof <type>".
*
* @param {any} key - The key to stringify
*/
function stringifyKey(key) {
return typeof key === 'string' ? key : `typeof ${typeof key}`
}

@ -1,6 +1,6 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import assert from 'assert' import assert from 'assert'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import { ethErrors } from 'eth-json-rpc-errors' import { ethErrors } from 'eth-json-rpc-errors'
import { typedSignatureHash, TYPED_MESSAGE_SCHEMA } from 'eth-sig-util' import { typedSignatureHash, TYPED_MESSAGE_SCHEMA } from 'eth-sig-util'
import { isValidAddress } from 'ethereumjs-util' import { isValidAddress } from 'ethereumjs-util'

@ -1,101 +0,0 @@
{
"web3.bzz.blockNetworkRead": "function",
"web3.bzz.download": "function",
"web3.bzz.get": "function",
"web3.bzz.getHive": "function",
"web3.bzz.getInfo": "function",
"web3.bzz.hive": "TRAP",
"web3.bzz.info": "TRAP",
"web3.bzz.modify": "function",
"web3.bzz.put": "function",
"web3.bzz.retrieve": "function",
"web3.bzz.store": "function",
"web3.bzz.swapEnabled": "function",
"web3.bzz.syncEnabled": "function",
"web3.bzz.upload": "function",
"web3.db.getHex": "function",
"web3.db.getString": "function",
"web3.db.putHex": "function",
"web3.db.putString": "function",
"web3.eth.accounts": "object",
"web3.eth.blockNumber": "TRAP",
"web3.eth.call": "function",
"web3.eth.coinbase": "object",
"web3.eth.compile": "object",
"web3.eth.estimateGas": "function",
"web3.eth.gasPrice": "TRAP",
"web3.eth.getAccounts": "function",
"web3.eth.getBalance": "function",
"web3.eth.getBlock": "function",
"web3.eth.getBlockNumber": "function",
"web3.eth.getBlockTransactionCount": "function",
"web3.eth.getBlockUncleCount": "function",
"web3.eth.getCode": "function",
"web3.eth.getCoinbase": "function",
"web3.eth.getCompilers": "function",
"web3.eth.getGasPrice": "function",
"web3.eth.getHashrate": "function",
"web3.eth.getMining": "function",
"web3.eth.getProtocolVersion": "function",
"web3.eth.getStorageAt": "function",
"web3.eth.getSyncing": "function",
"web3.eth.getTransaction": "function",
"web3.eth.getTransactionCount": "function",
"web3.eth.getTransactionFromBlock": "function",
"web3.eth.getTransactionReceipt": "function",
"web3.eth.getUncle": "function",
"web3.eth.getWork": "function",
"web3.eth.hashrate": "TRAP",
"web3.eth.iban": "function",
"web3.eth.mining": "TRAP",
"web3.eth.protocolVersion": "TRAP",
"web3.eth.sendIBANTransaction": "function",
"web3.eth.sendRawTransaction": "function",
"web3.eth.sendTransaction": "function",
"web3.eth.sign": "function",
"web3.eth.signTransaction": "function",
"web3.eth.submitWork": "function",
"web3.eth.syncing": "TRAP",
"web3.net.getListening": "function",
"web3.net.getPeerCount": "function",
"web3.net.listening": "TRAP",
"web3.net.peerCount": "TRAP",
"web3.personal.ecRecover": "function",
"web3.personal.getListAccounts": "function",
"web3.personal.importRawKey": "function",
"web3.personal.listAccounts": "TRAP",
"web3.personal.lockAccount": "function",
"web3.personal.newAccount": "function",
"web3.personal.sendTransaction": "function",
"web3.personal.sign": "function",
"web3.personal.unlockAccount": "function",
"web3.providers.HttpProvider": "function",
"web3.providers.IpcProvider": "function",
"web3.shh.addPrivateKey": "function",
"web3.shh.addSymKey": "function",
"web3.shh.deleteKeyPair": "function",
"web3.shh.deleteSymKey": "function",
"web3.shh.generateSymKeyFromPassword": "function",
"web3.shh.getPrivateKey": "function",
"web3.shh.getPublicKey": "function",
"web3.shh.getSymKey": "function",
"web3.shh.hasKeyPair": "function",
"web3.shh.hasSymKey": "function",
"web3.shh.info": "function",
"web3.shh.markTrustedPeer": "function",
"web3.shh.newKeyPair": "function",
"web3.shh.newSymKey": "function",
"web3.shh.post": "function",
"web3.shh.setMaxMessageSize": "function",
"web3.shh.setMinPoW": "function",
"web3.shh.version": "function",
"web3.version.api": "string",
"web3.version.ethereum": "TRAP",
"web3.version.getEthereum": "function",
"web3.version.getNetwork": "function",
"web3.version.getNode": "function",
"web3.version.getWhisper": "function",
"web3.version.network": "string",
"web3.version.node": "TRAP",
"web3.version.whisper": "TRAP"
}

@ -1,9 +1,6 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import pump from 'pump' import pump from 'pump'
import Dnode from 'dnode' import Dnode from 'dnode'
import ObservableStore from 'obs-store'
import asStream from 'obs-store/lib/asStream'
import { JsonRpcEngine } from 'json-rpc-engine' import { JsonRpcEngine } from 'json-rpc-engine'
import { debounce } from 'lodash' import { debounce } from 'lodash'
import createEngineStream from 'json-rpc-middleware-stream/engineStream' import createEngineStream from 'json-rpc-middleware-stream/engineStream'
@ -21,6 +18,7 @@ import nanoid from 'nanoid'
import contractMap from '@metamask/contract-metadata' import contractMap from '@metamask/contract-metadata'
import { import {
AddressBookController, AddressBookController,
ApprovalController,
CurrencyRateController, CurrencyRateController,
PhishingController, PhishingController,
} from '@metamask/controllers' } from '@metamask/controllers'
@ -53,6 +51,7 @@ import TokenRatesController from './controllers/token-rates'
import DetectTokensController from './controllers/detect-tokens' import DetectTokensController from './controllers/detect-tokens'
import SwapsController from './controllers/swaps' import SwapsController from './controllers/swaps'
import { PermissionsController } from './controllers/permissions' import { PermissionsController } from './controllers/permissions'
import { NOTIFICATION_NAMES } from './controllers/permissions/enums'
import getRestrictedMethods from './controllers/permissions/restrictedMethods' import getRestrictedMethods from './controllers/permissions/restrictedMethods'
import nodeify from './lib/nodeify' import nodeify from './lib/nodeify'
import accountImporter from './account-import-strategies' import accountImporter from './account-import-strategies'
@ -104,6 +103,11 @@ export default class MetamaskController extends EventEmitter {
// next, we will initialize the controllers // next, we will initialize the controllers
// controller initialization order matters // controller initialization order matters
this.approvalController = new ApprovalController({
showApprovalRequest: opts.showUserConfirmation,
defaultApprovalType: 'NO_TYPE',
})
this.networkController = new NetworkController(initState.NetworkController) this.networkController = new NetworkController(initState.NetworkController)
this.networkController.setInfuraProjectId(opts.infuraProjectId) this.networkController.setInfuraProjectId(opts.infuraProjectId)
@ -219,13 +223,15 @@ export default class MetamaskController extends EventEmitter {
initState: initState.KeyringController, initState: initState.KeyringController,
encryptor: opts.encryptor || undefined, encryptor: opts.encryptor || undefined,
}) })
this.keyringController.memStore.subscribe((s) => this.keyringController.memStore.subscribe((state) =>
this._onKeyringControllerUpdate(s), this._onKeyringControllerUpdate(state),
) )
this.keyringController.on('unlock', () => this.emit('unlock')) this.keyringController.on('unlock', () => this.emit('unlock'))
this.keyringController.on('lock', () => this._onLock())
this.permissionsController = new PermissionsController( this.permissionsController = new PermissionsController(
{ {
approvals: this.approvalController,
getKeyringAccounts: this.keyringController.getAccounts.bind( getKeyringAccounts: this.keyringController.getAccounts.bind(
this.keyringController, this.keyringController,
), ),
@ -233,6 +239,7 @@ export default class MetamaskController extends EventEmitter {
getUnlockPromise: this.appStateController.getUnlockPromise.bind( getUnlockPromise: this.appStateController.getUnlockPromise.bind(
this.appStateController, this.appStateController,
), ),
isUnlocked: this.isUnlocked.bind(this),
notifyDomain: this.notifyConnections.bind(this), notifyDomain: this.notifyConnections.bind(this),
notifyAllDomains: this.notifyAllConnections.bind(this), notifyAllDomains: this.notifyAllConnections.bind(this),
preferences: this.preferencesController.store, preferences: this.preferencesController.store,
@ -348,6 +355,9 @@ export default class MetamaskController extends EventEmitter {
tokenRatesStore: this.tokenRatesController.store, tokenRatesStore: this.tokenRatesController.store,
}) })
// ensure isClientOpenAndUnlocked is updated when memState updates
this.on('update', (memState) => this._onStateUpdate(memState))
this.store.updateStructure({ this.store.updateStructure({
AppStateController: this.appStateController.store, AppStateController: this.appStateController.store,
TransactionController: this.txController.store, TransactionController: this.txController.store,
@ -390,8 +400,8 @@ export default class MetamaskController extends EventEmitter {
PermissionsMetadata: this.permissionsController.store, PermissionsMetadata: this.permissionsController.store,
ThreeBoxController: this.threeBoxController.store, ThreeBoxController: this.threeBoxController.store,
SwapsController: this.swapsController.store, SwapsController: this.swapsController.store,
// ENS Controller
EnsController: this.ensController.store, EnsController: this.ensController.store,
ApprovalController: this.approvalController,
}) })
this.memStore.subscribe(this.sendUpdate.bind(this)) this.memStore.subscribe(this.sendUpdate.bind(this))
@ -450,39 +460,38 @@ export default class MetamaskController extends EventEmitter {
} }
/** /**
* Constructor helper: initialize a public config store. * Gets relevant state for the provider of an external origin.
* This store is used to make some config info available to Dapps synchronously. *
*/ * @param {string} origin - The origin to get the provider state for.
createPublicConfigStore() { * @returns {Promise<{
// subset of state for metamask inpage provider * isUnlocked: boolean,
const publicConfigStore = new ObservableStore() * networkVersion: string,
const { networkController } = this * chainId: string,
* accounts: string[],
// setup memStore subscription hooks * }>} An object with relevant state properties.
this.on('update', updatePublicConfigStore) */
updatePublicConfigStore(this.getState()) async getProviderState(origin) {
return {
publicConfigStore.destroy = () => { isUnlocked: this.isUnlocked(),
this.removeEventListener && ...this.getProviderNetworkState(),
this.removeEventListener('update', updatePublicConfigStore) accounts: await this.permissionsController.getAccounts(origin),
}
function updatePublicConfigStore(memState) {
const chainId = networkController.getCurrentChainId()
if (memState.network !== 'loading') {
publicConfigStore.putState(selectPublicState(chainId, memState))
} }
} }
function selectPublicState(chainId, { isUnlocked, network }) { /**
* Gets network state relevant for external providers.
*
* @param {Object} [memState] - The MetaMask memState. If not provided,
* this function will retrieve the most recent state.
* @returns {Object} An object with relevant network state properties.
*/
getProviderNetworkState(memState) {
const { network } = memState || this.getState()
return { return {
isUnlocked, chainId: this.networkController.getCurrentChainId(),
chainId,
networkVersion: network, networkVersion: network,
} }
} }
return publicConfigStore
}
//============================================================================= //=============================================================================
// EXPOSED TO THE UI SUBSYSTEM // EXPOSED TO THE UI SUBSYSTEM
@ -512,16 +521,16 @@ export default class MetamaskController extends EventEmitter {
*/ */
getApi() { getApi() {
const { const {
alertController,
keyringController, keyringController,
metaMetricsController,
networkController, networkController,
onboardingController, onboardingController,
alertController,
permissionsController, permissionsController,
preferencesController, preferencesController,
swapsController,
threeBoxController, threeBoxController,
txController, txController,
swapsController,
metaMetricsController,
} = this } = this
return { return {
@ -570,6 +579,10 @@ export default class MetamaskController extends EventEmitter {
networkController.setProviderType, networkController.setProviderType,
networkController, networkController,
), ),
rollbackToPreviousProvider: nodeify(
networkController.rollbackToPreviousProvider,
networkController,
),
setCustomRpc: nodeify(this.setCustomRpc, this), setCustomRpc: nodeify(this.setCustomRpc, this),
updateAndSetCustomRpc: nodeify(this.updateAndSetCustomRpc, this), updateAndSetCustomRpc: nodeify(this.updateAndSetCustomRpc, this),
delCustomRpc: nodeify(this.delCustomRpc, this), delCustomRpc: nodeify(this.delCustomRpc, this),
@ -704,8 +717,12 @@ export default class MetamaskController extends EventEmitter {
alertController, alertController,
), ),
setUnconnectedAccountAlertShown: nodeify( setUnconnectedAccountAlertShown: nodeify(
this.alertController.setUnconnectedAccountAlertShown, alertController.setUnconnectedAccountAlertShown,
this.alertController, alertController,
),
setWeb3ShimUsageAlertDismissed: nodeify(
alertController.setWeb3ShimUsageAlertDismissed,
alertController,
), ),
// 3Box // 3Box
@ -1813,8 +1830,7 @@ export default class MetamaskController extends EventEmitter {
const mux = setupMultiplex(connectionStream) const mux = setupMultiplex(connectionStream)
// messages between inpage and background // messages between inpage and background
this.setupProviderConnection(mux.createStream('provider'), sender) this.setupProviderConnection(mux.createStream('metamask-provider'), sender)
this.setupPublicConfig(mux.createStream('publicConfig'))
} }
/** /**
@ -1971,12 +1987,19 @@ export default class MetamaskController extends EventEmitter {
engine.push( engine.push(
createMethodMiddleware({ createMethodMiddleware({
origin, origin,
getProviderState: this.getProviderState.bind(this),
sendMetrics: this.metaMetricsController.trackEvent.bind( sendMetrics: this.metaMetricsController.trackEvent.bind(
this.metaMetricsController, this.metaMetricsController,
), ),
handleWatchAssetRequest: this.preferencesController.requestWatchAsset.bind( handleWatchAssetRequest: this.preferencesController.requestWatchAsset.bind(
this.preferencesController, this.preferencesController,
), ),
getWeb3ShimUsageState: this.alertController.getWeb3ShimUsageState.bind(
this.alertController,
),
setWeb3ShimUsageRecorded: this.alertController.setWeb3ShimUsageRecorded.bind(
this.alertController,
),
}), }),
) )
// filter and subscription polyfills // filter and subscription polyfills
@ -1993,29 +2016,6 @@ export default class MetamaskController extends EventEmitter {
return engine return engine
} }
/**
* A method for providing our public config info over a stream.
* This includes info we like to be synchronous if possible, like
* the current selected account, and network ID.
*
* Since synchronous methods have been deprecated in web3,
* this is a good candidate for deprecation.
*
* @param {*} outStream - The stream to provide public config over.
*/
setupPublicConfig(outStream) {
const configStore = this.createPublicConfigStore()
const configStream = asStream(configStore)
pump(configStream, outStream, (err) => {
configStore.destroy()
configStream.destroy()
if (err) {
log.error(err)
}
})
}
/** /**
* Adds a reference to a connection by origin. Ignores the 'metamask' origin. * Adds a reference to a connection by origin. Ignores the 'metamask' origin.
* Caller must ensure that the returned id is stored such that the reference * Caller must ensure that the returned id is stored such that the reference
@ -2066,37 +2066,51 @@ export default class MetamaskController extends EventEmitter {
/** /**
* Causes the RPC engines associated with the connections to the given origin * Causes the RPC engines associated with the connections to the given origin
* to emit a notification event with the given payload. * to emit a notification event with the given payload.
* Does nothing if the extension is locked or the origin is unknown. *
* The caller is responsible for ensuring that only permitted notifications
* are sent.
*
* Ignores unknown origins.
* *
* @param {string} origin - The connection's origin string. * @param {string} origin - The connection's origin string.
* @param {any} payload - The event payload. * @param {any} payload - The event payload.
*/ */
notifyConnections(origin, payload) { notifyConnections(origin, payload) {
const connections = this.connections[origin] const connections = this.connections[origin]
if (!this.isUnlocked() || !connections) {
return
}
if (connections) {
Object.values(connections).forEach((conn) => { Object.values(connections).forEach((conn) => {
conn.engine && conn.engine.emit('notification', payload) if (conn.engine) {
conn.engine.emit('notification', payload)
}
}) })
} }
}
/** /**
* Causes the RPC engines associated with all connections to emit a * Causes the RPC engines associated with all connections to emit a
* notification event with the given payload. * notification event with the given payload.
* Does nothing if the extension is locked.
* *
* @param {any} payload - The event payload. * If the "payload" parameter is a function, the payload for each connection
* will be the return value of that function called with the connection's
* origin.
*
* The caller is responsible for ensuring that only permitted notifications
* are sent.
*
* @param {any} payload - The event payload, or payload getter function.
*/ */
notifyAllConnections(payload) { notifyAllConnections(payload) {
if (!this.isUnlocked()) { const getPayload =
return typeof payload === 'function'
} ? (origin) => payload(origin)
: () => payload
Object.values(this.connections).forEach((origin) => { Object.values(this.connections).forEach((origin) => {
Object.values(origin).forEach((conn) => { Object.values(origin).forEach((conn) => {
conn.engine && conn.engine.emit('notification', payload) if (conn.engine) {
conn.engine.emit('notification', getPayload(origin))
}
}) })
}) })
} }
@ -2125,6 +2139,51 @@ export default class MetamaskController extends EventEmitter {
this.accountTracker.syncWithAddresses(addresses) this.accountTracker.syncWithAddresses(addresses)
} }
/**
* Handle global unlock, triggered by KeyringController unlock.
* Notifies all connections that the extension is unlocked.
*/
_onUnlock() {
this.notifyAllConnections((origin) => {
return {
method: NOTIFICATION_NAMES.unlockStateChanged,
params: {
isUnlocked: true,
accounts: this.permissionsController.getAccounts(origin),
},
}
})
this.emit('unlock')
}
/**
* Handle global lock, triggered by KeyringController lock.
* Notifies all connections that the extension is locked.
*/
_onLock() {
this.notifyAllConnections({
method: NOTIFICATION_NAMES.unlockStateChanged,
params: {
isUnlocked: false,
},
})
this.emit('lock')
}
/**
* Handle memory state updates.
* - Ensure isClientOpenAndUnlocked is updated
* - Notifies all connections with the new provider network state
* - The external providers handle diffing the state
*/
_onStateUpdate(newState) {
this.isClientOpenAndUnlocked = newState.isUnlocked && this._isClientOpen
this.notifyAllConnections({
method: NOTIFICATION_NAMES.chainChanged,
params: this.getProviderNetworkState(newState),
})
}
// misc // misc
/** /**

@ -0,0 +1,28 @@
import { cloneDeep } from 'lodash'
import { NETWORK_TYPE_TO_ID_MAP } from '../controllers/network/enums'
const version = 51
/**
* Set the chainId in the Network Controller provider data for all infura networks
*/
export default {
version,
async migrate(originalVersionedData) {
const versionedData = cloneDeep(originalVersionedData)
versionedData.meta.version = version
const state = versionedData.data
versionedData.data = transformState(state)
return versionedData
},
}
function transformState(state) {
const { chainId, type } = state?.NetworkController?.provider || {}
const enumChainId = NETWORK_TYPE_TO_ID_MAP[type]?.chainId
if (enumChainId && chainId !== enumChainId) {
state.NetworkController.provider.chainId = enumChainId
}
return state
}

@ -55,6 +55,7 @@ const migrations = [
require('./048').default, require('./048').default,
require('./049').default, require('./049').default,
require('./050').default, require('./050').default,
require('./051').default,
] ]
export default migrations export default migrations

@ -6,7 +6,7 @@ module.exports = function (api) {
'@babel/preset-env', '@babel/preset-env',
{ {
targets: { targets: {
browsers: ['chrome >= 58', 'firefox >= 56.2'], browsers: ['chrome >= 63', 'firefox >= 56.2'],
}, },
}, },
], ],

@ -79,6 +79,7 @@ function defineAllTasks() {
clean, clean,
styleTasks.prod, styleTasks.prod,
composeParallel(scriptTasks.test, staticTasks.prod, manifestTasks.test), composeParallel(scriptTasks.test, staticTasks.prod, manifestTasks.test),
zip,
), ),
) )

@ -55,6 +55,9 @@ async function start() {
}) })
.join(', ') .join(', ')
const coverageUrl = `${BUILD_LINK_BASE}/coverage/index.html`
const coverageLink = `<a href="${coverageUrl}">Report</a>`
// links to bundle browser builds // links to bundle browser builds
const depVizUrl = `${BUILD_LINK_BASE}/build-artifacts/deps-viz/background/index.html` const depVizUrl = `${BUILD_LINK_BASE}/build-artifacts/deps-viz/background/index.html`
const depVizLink = `<a href="${depVizUrl}">background</a>` const depVizLink = `<a href="${depVizUrl}">background</a>`
@ -65,6 +68,7 @@ async function start() {
const contentRows = [ const contentRows = [
`builds: ${buildLinks}`, `builds: ${buildLinks}`,
`bundle viz: ${bundleLinks}`, `bundle viz: ${bundleLinks}`,
`code coverage: ${coverageLink}`,
`dep viz: ${depVizLink}`, `dep viz: ${depVizLink}`,
`<a href="${allArtifactsUrl}">all artifacts</a>`, `<a href="${allArtifactsUrl}">all artifacts</a>`,
] ]

@ -29,7 +29,6 @@
"test:coverage": "nyc --silent --check-coverage yarn test:unit:strict && nyc --silent --no-clean yarn test:unit:lax && nyc report --reporter=text --reporter=html", "test:coverage": "nyc --silent --check-coverage yarn test:unit:strict && nyc --silent --no-clean yarn test:unit:lax && nyc report --reporter=text --reporter=html",
"test:coverage:strict": "nyc --check-coverage yarn test:unit:strict", "test:coverage:strict": "nyc --check-coverage yarn test:unit:strict",
"test:coverage:path": "nyc --check-coverage yarn test:unit:path", "test:coverage:path": "nyc --check-coverage yarn test:unit:path",
"test:coveralls-upload": "if [ \"$COVERALLS_REPO_TOKEN\" ]; then nyc report --reporter=text-lcov | coveralls; fi",
"ganache:start": "./development/run-ganache", "ganache:start": "./development/run-ganache",
"sentry:publish": "node ./development/sentry-publish.js", "sentry:publish": "node ./development/sentry-publish.js",
"lint": "prettier --check ./**/*.json && eslint . --ext js && yarn lint:styles", "lint": "prettier --check ./**/*.json && eslint . --ext js && yarn lint:styles",
@ -77,14 +76,15 @@
"@formatjs/intl-relativetimeformat": "^5.2.6", "@formatjs/intl-relativetimeformat": "^5.2.6",
"@fortawesome/fontawesome-free": "^5.13.0", "@fortawesome/fontawesome-free": "^5.13.0",
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@metamask/contract-metadata": "^1.19.0", "@metamask/contract-metadata": "^1.21.0",
"@metamask/controllers": "^5.1.0", "@metamask/controllers": "^5.1.0",
"@metamask/eth-ledger-bridge-keyring": "^0.2.6", "@metamask/eth-ledger-bridge-keyring": "^0.2.6",
"@metamask/eth-token-tracker": "^3.0.1", "@metamask/eth-token-tracker": "^3.0.1",
"@metamask/etherscan-link": "^1.4.0", "@metamask/etherscan-link": "^1.4.0",
"@metamask/inpage-provider": "^6.1.0", "@metamask/inpage-provider": "^8.0.1",
"@metamask/jazzicon": "^2.0.0", "@metamask/jazzicon": "^2.0.0",
"@metamask/logo": "^2.5.0", "@metamask/logo": "^2.5.0",
"@metamask/obs-store": "^5.0.0",
"@popperjs/core": "^2.4.0", "@popperjs/core": "^2.4.0",
"@reduxjs/toolkit": "^1.3.2", "@reduxjs/toolkit": "^1.3.2",
"@sentry/browser": "^5.26.0", "@sentry/browser": "^5.26.0",
@ -142,7 +142,6 @@
"nanoid": "^2.1.6", "nanoid": "^2.1.6",
"nonce-tracker": "^1.0.0", "nonce-tracker": "^1.0.0",
"obj-multiplex": "^1.0.0", "obj-multiplex": "^1.0.0",
"obs-store": "^4.0.3",
"pify": "^5.0.0", "pify": "^5.0.0",
"post-message-stream": "^3.0.0", "post-message-stream": "^3.0.0",
"promise-to-callback": "^1.0.0", "promise-to-callback": "^1.0.0",
@ -216,7 +215,6 @@
"chromedriver": "^79.0.0", "chromedriver": "^79.0.0",
"concurrently": "^5.2.0", "concurrently": "^5.2.0",
"copy-webpack-plugin": "^6.0.3", "copy-webpack-plugin": "^6.0.3",
"coveralls": "^3.0.0",
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.3",
"css-loader": "^2.1.1", "css-loader": "^2.1.1",
"del": "^3.0.0", "del": "^3.0.0",
@ -236,7 +234,7 @@
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"ganache-cli": "^6.12.1", "ganache-cli": "^6.12.1",
"ganache-core": "^2.13.1", "ganache-core": "^2.13.1",
"geckodriver": "^1.19.1", "geckodriver": "^1.21.0",
"get-port": "^5.1.0", "get-port": "^5.1.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-autoprefixer": "^5.0.0", "gulp-autoprefixer": "^5.0.0",
@ -278,7 +276,7 @@
"remotedev-server": "^0.3.1", "remotedev-server": "^0.3.1",
"resolve-url-loader": "^3.1.2", "resolve-url-loader": "^3.1.2",
"sass-loader": "^7.0.1", "sass-loader": "^7.0.1",
"selenium-webdriver": "^4.0.0-alpha.5", "selenium-webdriver": "4.0.0-alpha.7",
"serve-handler": "^6.1.2", "serve-handler": "^6.1.2",
"ses": "0.11.0", "ses": "0.11.0",
"sesify": "^4.2.1", "sesify": "^4.2.1",

@ -0,0 +1,18 @@
export const ALERT_TYPES = {
unconnectedAccount: 'unconnectedAccount',
web3ShimUsage: 'web3ShimUsage',
invalidCustomNetwork: 'invalidCustomNetwork',
}
/**
* Alerts that can be enabled or disabled by the user.
*/
export const TOGGLEABLE_ALERT_TYPES = [
ALERT_TYPES.unconnectedAccount,
ALERT_TYPES.web3ShimUsage,
]
export const WEB3_SHIM_USAGE_ALERT_STATES = {
RECORDED: 1,
DISMISSED: 2,
}

@ -5,8 +5,8 @@ const chrome = require('selenium-webdriver/chrome')
* A wrapper around a {@code WebDriver} instance exposing Chrome-specific functionality * A wrapper around a {@code WebDriver} instance exposing Chrome-specific functionality
*/ */
class ChromeDriver { class ChromeDriver {
static async build({ extensionPath, responsive, port }) { static async build({ responsive, port }) {
const args = [`load-extension=${extensionPath}`] const args = [`load-extension=dist/chrome`]
if (responsive) { if (responsive) {
args.push('--auto-open-devtools-for-tabs') args.push('--auto-open-devtools-for-tabs')
} }

@ -3,7 +3,7 @@ const os = require('os')
const path = require('path') const path = require('path')
const { Builder, By, until } = require('selenium-webdriver') const { Builder, By, until } = require('selenium-webdriver')
const firefox = require('selenium-webdriver/firefox') const firefox = require('selenium-webdriver/firefox')
const { Command } = require('selenium-webdriver/lib/command') const { version } = require('../../../app/manifest/_base.json')
/** /**
* The prefix for temporary Firefox profiles. All Firefox profiles used for e2e tests * The prefix for temporary Firefox profiles. All Firefox profiles used for e2e tests
@ -12,20 +12,16 @@ const { Command } = require('selenium-webdriver/lib/command')
*/ */
const TEMP_PROFILE_PATH_PREFIX = path.join(os.tmpdir(), 'MetaMask-Fx-Profile') const TEMP_PROFILE_PATH_PREFIX = path.join(os.tmpdir(), 'MetaMask-Fx-Profile')
const GeckoDriverCommand = {
INSTALL_ADDON: 'install addon',
}
/** /**
* A wrapper around a {@code WebDriver} instance exposing Firefox-specific functionality * A wrapper around a {@code WebDriver} instance exposing Firefox-specific functionality
*/ */
class FirefoxDriver { class FirefoxDriver {
/** /**
* Builds a {@link FirefoxDriver} instance * Builds a {@link FirefoxDriver} instance
* @param {{extensionPath: string}} options - the options for the build * @param {Object} options - the options for the build
* @returns {Promise<{driver: !ThenableWebDriver, extensionUrl: string, extensionId: string}>} * @returns {Promise<{driver: !ThenableWebDriver, extensionUrl: string, extensionId: string}>}
*/ */
static async build({ extensionPath, responsive, port }) { static async build({ responsive, port }) {
const templateProfile = fs.mkdtempSync(TEMP_PROFILE_PATH_PREFIX) const templateProfile = fs.mkdtempSync(TEMP_PROFILE_PATH_PREFIX)
const options = new firefox.Options().setProfile(templateProfile) const options = new firefox.Options().setProfile(templateProfile)
const builder = new Builder() const builder = new Builder()
@ -38,9 +34,9 @@ class FirefoxDriver {
const driver = builder.build() const driver = builder.build()
const fxDriver = new FirefoxDriver(driver) const fxDriver = new FirefoxDriver(driver)
await fxDriver.init() const extensionId = await fxDriver.installExtension(
`builds/metamask-firefox-${version}.zip`,
const extensionId = await fxDriver.installExtension(extensionPath) )
const internalExtensionId = await fxDriver.getInternalId() const internalExtensionId = await fxDriver.getInternalId()
if (responsive) { if (responsive) {
@ -62,31 +58,13 @@ class FirefoxDriver {
this._driver = driver this._driver = driver
} }
/**
* Initializes the driver
* @returns {Promise<void>}
*/
async init() {
await this._driver
.getExecutor()
.defineCommand(
GeckoDriverCommand.INSTALL_ADDON,
'POST',
'/session/:sessionId/moz/addon/install',
)
}
/** /**
* Installs the extension at the given path * Installs the extension at the given path
* @param {string} addonPath - the path to the unpacked extension or XPI * @param {string} addonPath - the path to the unpacked extension or XPI
* @returns {Promise<string>} the extension ID * @returns {Promise<string>} the extension ID
*/ */
async installExtension(addonPath) { async installExtension(addonPath) {
const cmd = new Command(GeckoDriverCommand.INSTALL_ADDON) return await this._driver.installAddon(addonPath, true)
.setParameter('path', path.resolve(addonPath))
.setParameter('temporary', true)
return await this._driver.execute(cmd)
} }
/** /**

@ -6,13 +6,12 @@ const FirefoxDriver = require('./firefox')
async function buildWebDriver({ responsive, port } = {}) { async function buildWebDriver({ responsive, port } = {}) {
const browser = process.env.SELENIUM_BROWSER const browser = process.env.SELENIUM_BROWSER
const extensionPath = `dist/${browser}`
const { const {
driver: seleniumDriver, driver: seleniumDriver,
extensionId, extensionId,
extensionUrl, extensionUrl,
} = await buildBrowserWebDriver(browser, { extensionPath, responsive, port }) } = await buildBrowserWebDriver(browser, { responsive, port })
await setupFetchMocking(seleniumDriver) await setupFetchMocking(seleniumDriver)
const driver = new Driver(seleniumDriver, browser, extensionUrl) const driver = new Driver(seleniumDriver, browser, extensionUrl)

@ -1,5 +1,5 @@
import assert from 'assert' import assert from 'assert'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import ComposableObservableStore from '../../../app/scripts/lib/ComposableObservableStore' import ComposableObservableStore from '../../../app/scripts/lib/ComposableObservableStore'
describe('ComposableObservableStore', function () { describe('ComposableObservableStore', function () {

@ -1,6 +1,6 @@
import assert from 'assert' import assert from 'assert'
import sinon from 'sinon' import sinon from 'sinon'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import contracts from '@metamask/contract-metadata' import contracts from '@metamask/contract-metadata'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
@ -85,6 +85,53 @@ describe('DetectTokensController', function () {
sandbox.assert.notCalled(stub) sandbox.assert.notCalled(stub)
}) })
it('should skip adding tokens listed in hiddenTokens array', async function () {
sandbox.useFakeTimers()
network.setProviderType(MAINNET)
const controller = new DetectTokensController({
preferences,
network,
keyringMemStore,
})
controller.isOpen = true
controller.isUnlocked = true
const contractAddresses = Object.keys(contracts)
const erc20ContractAddresses = contractAddresses.filter(
(contractAddress) => contracts[contractAddress].erc20 === true,
)
const existingTokenAddress = erc20ContractAddresses[0]
const existingToken = contracts[existingTokenAddress]
await preferences.addToken(
existingTokenAddress,
existingToken.symbol,
existingToken.decimals,
)
const tokenAddressToSkip = erc20ContractAddresses[1]
sandbox
.stub(controller, '_getTokenBalances')
.callsFake((tokensToDetect) =>
tokensToDetect.map((token) =>
token === tokenAddressToSkip ? new BigNumber(10) : 0,
),
)
await preferences.removeToken(tokenAddressToSkip)
await controller.detectNewTokens()
assert.deepEqual(preferences.store.getState().tokens, [
{
address: existingTokenAddress.toLowerCase(),
decimals: existingToken.decimals,
symbol: existingToken.symbol,
},
])
})
it('should check and add tokens while on main network', async function () { it('should check and add tokens while on main network', async function () {
sandbox.useFakeTimers() sandbox.useFakeTimers()
network.setProviderType(MAINNET) network.setProviderType(MAINNET)
@ -142,6 +189,63 @@ describe('DetectTokensController', function () {
]) ])
}) })
it('should check and add tokens while on non-default Mainnet', async function () {
sandbox.useFakeTimers()
network.setRpcTarget('https://some-fake-RPC-endpoint.metamask.io', '0x1')
const controller = new DetectTokensController({
preferences,
network,
keyringMemStore,
})
controller.isOpen = true
controller.isUnlocked = true
const contractAddresses = Object.keys(contracts)
const erc20ContractAddresses = contractAddresses.filter(
(contractAddress) => contracts[contractAddress].erc20 === true,
)
const existingTokenAddress = erc20ContractAddresses[0]
const existingToken = contracts[existingTokenAddress]
await preferences.addToken(
existingTokenAddress,
existingToken.symbol,
existingToken.decimals,
)
const tokenAddressToAdd = erc20ContractAddresses[1]
const tokenToAdd = contracts[tokenAddressToAdd]
const contractAddresssesToDetect = contractAddresses.filter(
(address) => address !== existingTokenAddress,
)
const indexOfTokenToAdd = contractAddresssesToDetect.indexOf(
tokenAddressToAdd,
)
const balances = new Array(contractAddresssesToDetect.length)
balances[indexOfTokenToAdd] = new BigNumber(10)
sandbox
.stub(controller, '_getTokenBalances')
.returns(Promise.resolve(balances))
await controller.detectNewTokens()
assert.deepEqual(preferences.store.getState().tokens, [
{
address: existingTokenAddress.toLowerCase(),
decimals: existingToken.decimals,
symbol: existingToken.symbol,
},
{
address: tokenAddressToAdd.toLowerCase(),
decimals: tokenToAdd.decimals,
symbol: tokenToAdd.symbol,
},
])
})
it('should trigger detect new tokens when change address', async function () { it('should trigger detect new tokens when change address', async function () {
sandbox.useFakeTimers() sandbox.useFakeTimers()
const controller = new DetectTokensController({ const controller = new DetectTokensController({

@ -1,6 +1,6 @@
import assert from 'assert' import assert from 'assert'
import sinon from 'sinon' import sinon from 'sinon'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import EnsController from '../../../../app/scripts/controllers/ens' import EnsController from '../../../../app/scripts/controllers/ens'
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'

@ -1022,7 +1022,7 @@ describe('MetaMaskController', function () {
} }
streamTest.write( streamTest.write(
{ {
name: 'provider', name: 'metamask-provider',
data: message, data: message,
}, },
null, null,
@ -1061,7 +1061,7 @@ describe('MetaMaskController', function () {
} }
streamTest.write( streamTest.write(
{ {
name: 'provider', name: 'metamask-provider',
data: message, data: message,
}, },
null, null,

@ -1,6 +1,8 @@
import { ethErrors, ERROR_CODES } from 'eth-json-rpc-errors' import { ethErrors, ERROR_CODES } from 'eth-json-rpc-errors'
import deepFreeze from 'deep-freeze-strict' import deepFreeze from 'deep-freeze-strict'
import { ApprovalController } from '@metamask/controllers'
import _getRestrictedMethods from '../../../../../app/scripts/controllers/permissions/restrictedMethods' import _getRestrictedMethods from '../../../../../app/scripts/controllers/permissions/restrictedMethods'
import { import {
@ -32,8 +34,6 @@ const keyringAccounts = deepFreeze([
'0xcc74c7a59194e5d9268476955650d1e285be703c', '0xcc74c7a59194e5d9268476955650d1e285be703c',
]) ])
const getKeyringAccounts = async () => [...keyringAccounts]
const getIdentities = () => { const getIdentities = () => {
return keyringAccounts.reduce((identities, address, index) => { return keyringAccounts.reduce((identities, address, index) => {
identities[address] = { address, name: `Account ${index}` } identities[address] = { address, name: `Account ${index}` }
@ -62,8 +62,6 @@ const getRestrictedMethods = (permController) => {
} }
} }
const getUnlockPromise = () => Promise.resolve()
/** /**
* Gets default mock constructor options for a permissions controller. * Gets default mock constructor options for a permissions controller.
* *
@ -71,10 +69,14 @@ const getUnlockPromise = () => Promise.resolve()
*/ */
export function getPermControllerOpts() { export function getPermControllerOpts() {
return { return {
showPermissionRequest: noop, approvals: new ApprovalController({
getKeyringAccounts, showApprovalRequest: noop,
getUnlockPromise, defaultApprovalType: 'NO_TYPE',
}),
getKeyringAccounts: async () => [...keyringAccounts],
getUnlockPromise: () => Promise.resolve(),
getRestrictedMethods, getRestrictedMethods,
isUnlocked: () => true,
notifyDomain: noop, notifyDomain: noop,
notifyAllDomains: noop, notifyAllDomains: noop,
preferences: { preferences: {
@ -86,6 +88,7 @@ export function getPermControllerOpts() {
}, },
subscribe: noop, subscribe: noop,
}, },
showPermissionRequest: noop,
} }
} }
@ -426,9 +429,9 @@ export const getters = deepFreeze({
message: `Pending approval with id '${id}' or origin '${origin}' already exists.`, message: `Pending approval with id '${id}' or origin '${origin}' already exists.`,
} }
}, },
requestAlreadyPending: () => { requestAlreadyPending: (origin) => {
return { return {
message: 'Permissions request already pending; please wait.', message: `Request of type 'wallet_requestPermissions' already pending for origin ${origin}. Please wait.`,
} }
}, },
}, },
@ -467,7 +470,7 @@ export const getters = deepFreeze({
removedAccounts: () => { removedAccounts: () => {
return { return {
method: NOTIFICATION_NAMES.accountsChanged, method: NOTIFICATION_NAMES.accountsChanged,
result: [], params: [],
} }
}, },
@ -480,7 +483,7 @@ export const getters = deepFreeze({
newAccounts: (accounts) => { newAccounts: (accounts) => {
return { return {
method: NOTIFICATION_NAMES.accountsChanged, method: NOTIFICATION_NAMES.accountsChanged,
result: accounts, params: accounts,
} }
}, },
}, },
@ -586,17 +589,17 @@ export const getters = deepFreeze({
}, },
/** /**
* Gets a wallet_sendDomainMetadata RPC request object. * Gets a metamask_sendDomainMetadata RPC request object.
* *
* @param {string} origin - The origin of the request * @param {string} origin - The origin of the request
* @param {Object} name - The domainMetadata name * @param {Object} name - The domainMetadata name
* @param {Array<any>} [args] - Any other data for the request's domainMetadata * @param {Array<any>} [args] - Any other data for the request's domainMetadata
* @returns {Object} An RPC request object * @returns {Object} An RPC request object
*/ */
wallet_sendDomainMetadata: (origin, name, ...args) => { metamask_sendDomainMetadata: (origin, name, ...args) => {
return { return {
origin, origin,
method: 'wallet_sendDomainMetadata', method: 'metamask_sendDomainMetadata',
domainMetadata: { domainMetadata: {
...args, ...args,
name, name,

@ -1,23 +1,17 @@
import { strict as assert } from 'assert' import { strict as assert } from 'assert'
import { find } from 'lodash' import { find } from 'lodash'
import nanoid from 'nanoid'
import sinon from 'sinon' import sinon from 'sinon'
import { import {
METADATA_STORE_KEY, METADATA_STORE_KEY,
METADATA_CACHE_MAX_SIZE, METADATA_CACHE_MAX_SIZE,
WALLET_PREFIX,
} from '../../../../../app/scripts/controllers/permissions/enums' } from '../../../../../app/scripts/controllers/permissions/enums'
import { import { PermissionsController } from '../../../../../app/scripts/controllers/permissions'
PermissionsController,
addInternalMethodPrefix,
} from '../../../../../app/scripts/controllers/permissions'
import { getRequestUserApprovalHelper, grantPermissions } from './helpers' import { getRequestUserApprovalHelper, grantPermissions } from './helpers'
import { import {
noop,
constants, constants,
getters, getters,
getNotifyDomain, getNotifyDomain,
@ -53,6 +47,15 @@ const initPermController = (notifications = initNotifications()) => {
} }
describe('permissions controller', function () { describe('permissions controller', function () {
describe('constructor', function () {
it('throws on undefined argument', function () {
assert.throws(
() => new PermissionsController(),
'should throw on undefined argument',
)
})
})
describe('getAccounts', function () { describe('getAccounts', function () {
let permController let permController
@ -187,7 +190,7 @@ describe('permissions controller', function () {
assert.deepEqual( assert.deepEqual(
notifications[origin], notifications[origin],
[NOTIFICATIONS.removedAccounts()], [NOTIFICATIONS.removedAccounts()],
'origin should have single wallet_accountsChanged:[] notification', 'origin should have single metamask_accountsChanged:[] notification',
) )
}) })
@ -1010,12 +1013,6 @@ describe('permissions controller', function () {
}) })
it('does nothing if called on non-existing request', async function () { it('does nothing if called on non-existing request', async function () {
assert.equal(
permController.pendingApprovals.size,
0,
'pending approvals should be empty on init',
)
sinon.spy(permController, 'finalizePermissionsRequest') sinon.spy(permController, 'finalizePermissionsRequest')
const request = PERMS.approvedRequest(REQUEST_IDS.a, null) const request = PERMS.approvedRequest(REQUEST_IDS.a, null)
@ -1029,12 +1026,6 @@ describe('permissions controller', function () {
permController.finalizePermissionsRequest.notCalled, permController.finalizePermissionsRequest.notCalled,
'should not call finalizePermissionRequest', 'should not call finalizePermissionRequest',
) )
assert.equal(
permController.pendingApprovals.size,
0,
'pending approvals should still be empty after request',
)
}) })
it('rejects request with bad accounts param', async function () { it('rejects request with bad accounts param', async function () {
@ -1051,12 +1042,6 @@ describe('permissions controller', function () {
await permController.approvePermissionsRequest(request, null) await permController.approvePermissionsRequest(request, null)
await rejectionPromise await rejectionPromise
assert.equal(
permController.pendingApprovals.size,
0,
'pending approvals should be empty after rejection',
)
}) })
it('rejects request with no permissions', async function () { it('rejects request with no permissions', async function () {
@ -1073,12 +1058,6 @@ describe('permissions controller', function () {
ACCOUNTS.a.permitted, ACCOUNTS.a.permitted,
) )
await requestRejection await requestRejection
assert.equal(
permController.pendingApprovals.size,
0,
'pending approvals should be empty after rejection',
)
}) })
it('approves valid request', async function () { it('approves valid request', async function () {
@ -1104,12 +1083,6 @@ describe('permissions controller', function () {
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted), PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted),
'should produce expected approved permissions', 'should produce expected approved permissions',
) )
assert.equal(
permController.pendingApprovals.size,
0,
'pending approvals should be empty after approval',
)
}) })
it('approves valid requests regardless of order', async function () { it('approves valid requests regardless of order', async function () {
@ -1165,12 +1138,6 @@ describe('permissions controller', function () {
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted), PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted),
'second request should produce expected approved permissions', 'second request should produce expected approved permissions',
) )
assert.equal(
permController.pendingApprovals.size,
0,
'pending approvals should be empty after approvals',
)
}) })
}) })
@ -1183,22 +1150,14 @@ describe('permissions controller', function () {
}) })
it('does nothing if called on non-existing request', async function () { it('does nothing if called on non-existing request', async function () {
assert.equal( permController.approvals.add = sinon.fake.throws(
permController.pendingApprovals.size, new Error('should not call add'),
0,
'pending approvals should be empty on init',
) )
await assert.doesNotReject( await assert.doesNotReject(
permController.rejectPermissionsRequest(REQUEST_IDS.a), permController.rejectPermissionsRequest(REQUEST_IDS.a),
'should not throw on non-existing request', 'should not throw on non-existing request',
) )
assert.equal(
permController.pendingApprovals.size,
0,
'pending approvals should still be empty after request',
)
}) })
it('rejects single existing request', async function () { it('rejects single existing request', async function () {
@ -1210,12 +1169,6 @@ describe('permissions controller', function () {
await permController.rejectPermissionsRequest(REQUEST_IDS.a) await permController.rejectPermissionsRequest(REQUEST_IDS.a)
await requestRejection await requestRejection
assert.equal(
permController.pendingApprovals.size,
0,
'pending approvals should be empty after rejection',
)
}) })
it('rejects requests regardless of order', async function () { it('rejects requests regardless of order', async function () {
@ -1239,12 +1192,6 @@ describe('permissions controller', function () {
await requestRejection1 await requestRejection1
await requestRejection2 await requestRejection2
assert.equal(
permController.pendingApprovals.size,
0,
'pending approvals should be empty after approval',
)
}) })
}) })
@ -1325,11 +1272,18 @@ describe('permissions controller', function () {
}) })
it('notifyAccountsChanged records history and sends notification', async function () { it('notifyAccountsChanged records history and sends notification', async function () {
sinon.spy(permController, '_isUnlocked')
permController.notifyAccountsChanged( permController.notifyAccountsChanged(
DOMAINS.a.origin, DOMAINS.a.origin,
ACCOUNTS.a.permitted, ACCOUNTS.a.permitted,
) )
assert.ok(
permController._isUnlocked.calledOnce,
'_isUnlocked should have been called once',
)
assert.ok( assert.ok(
permController.permissionsLog.updateAccountsHistory.calledOnce, permController.permissionsLog.updateAccountsHistory.calledOnce,
'permissionsLog.updateAccountsHistory should have been called once', 'permissionsLog.updateAccountsHistory should have been called once',
@ -1342,6 +1296,25 @@ describe('permissions controller', function () {
) )
}) })
it('notifyAccountsChanged does nothing if _isUnlocked returns false', async function () {
permController._isUnlocked = sinon.fake.returns(false)
permController.notifyAccountsChanged(
DOMAINS.a.origin,
ACCOUNTS.a.permitted,
)
assert.ok(
permController._isUnlocked.calledOnce,
'_isUnlocked should have been called once',
)
assert.ok(
permController.permissionsLog.updateAccountsHistory.notCalled,
'permissionsLog.updateAccountsHistory should not have been called',
)
})
it('notifyAccountsChanged throws on invalid origin', async function () { it('notifyAccountsChanged throws on invalid origin', async function () {
assert.throws( assert.throws(
() => permController.notifyAccountsChanged(4, ACCOUNTS.a.permitted), () => permController.notifyAccountsChanged(4, ACCOUNTS.a.permitted),
@ -1546,13 +1519,8 @@ describe('permissions controller', function () {
}) })
describe('miscellanea and edge cases', function () { describe('miscellanea and edge cases', function () {
let permController
beforeEach(function () {
permController = initPermController()
})
it('requestAccountsPermissionWithId calls _requestAccountsPermission with an explicit request ID', async function () { it('requestAccountsPermissionWithId calls _requestAccountsPermission with an explicit request ID', async function () {
const permController = initPermController()
const _requestPermissions = sinon const _requestPermissions = sinon
.stub(permController, '_requestPermissions') .stub(permController, '_requestPermissions')
.resolves() .resolves()
@ -1566,49 +1534,5 @@ describe('permissions controller', function () {
) )
_requestPermissions.restore() _requestPermissions.restore()
}) })
it('_addPendingApproval: should throw if adding origin twice', function () {
const id = nanoid()
const origin = DOMAINS.a
permController._addPendingApproval(id, origin, noop, noop)
const otherId = nanoid()
assert.throws(
() => permController._addPendingApproval(otherId, origin, noop, noop),
ERRORS.pendingApprovals.duplicateOriginOrId(otherId, origin),
'should throw expected error',
)
assert.equal(
permController.pendingApprovals.size,
1,
'pending approvals should have single entry',
)
assert.equal(
permController.pendingApprovalOrigins.size,
1,
'pending approval origins should have single item',
)
assert.deepEqual(
permController.pendingApprovals.get(id),
{ origin, resolve: noop, reject: noop },
'pending approvals should have expected entry',
)
assert.ok(
permController.pendingApprovalOrigins.has(origin),
'pending approval origins should have expected item',
)
})
it('addInternalMethodPrefix', function () {
const str = 'foo'
const res = addInternalMethodPrefix(str)
assert.equal(res, WALLET_PREFIX + str, 'should prefix correctly')
})
}) })
}) })

@ -1,5 +1,5 @@
import { strict as assert } from 'assert' import { strict as assert } from 'assert'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import nanoid from 'nanoid' import nanoid from 'nanoid'
import { useFakeTimers } from 'sinon' import { useFakeTimers } from 'sinon'
@ -286,7 +286,7 @@ describe('permissions log', function () {
assert.equal(log.length, 0, 'log should be empty') assert.equal(log.length, 0, 'log should be empty')
const res = { foo: 'bar' } const res = { foo: 'bar' }
const req1 = RPC_REQUESTS.wallet_sendDomainMetadata( const req1 = RPC_REQUESTS.metamask_sendDomainMetadata(
DOMAINS.c.origin, DOMAINS.c.origin,
'foobar', 'foobar',
) )

@ -18,6 +18,20 @@ const { CAVEATS, ERRORS, PERMS, RPC_REQUESTS } = getters
const { ACCOUNTS, DOMAINS, PERM_NAMES } = constants const { ACCOUNTS, DOMAINS, PERM_NAMES } = constants
const initPermController = () => {
return new PermissionsController({
...getPermControllerOpts(),
})
}
const createApprovalSpies = (permController) => {
sinon.spy(permController.approvals, '_add')
}
const getNextApprovalId = (permController) => {
return permController.approvals._approvals.keys().next().value
}
const validatePermission = (perm, name, origin, caveats) => { const validatePermission = (perm, name, origin, caveats) => {
assert.equal( assert.equal(
name, name,
@ -36,12 +50,6 @@ const validatePermission = (perm, name, origin, caveats) => {
} }
} }
const initPermController = () => {
return new PermissionsController({
...getPermControllerOpts(),
})
}
describe('permissions middleware', function () { describe('permissions middleware', function () {
describe('wallet_requestPermissions', function () { describe('wallet_requestPermissions', function () {
let permController let permController
@ -52,6 +60,8 @@ describe('permissions middleware', function () {
}) })
it('grants permissions on user approval', async function () { it('grants permissions on user approval', async function () {
createApprovalSpies(permController)
const aMiddleware = getPermissionsMiddleware( const aMiddleware = getPermissionsMiddleware(
permController, permController,
DOMAINS.a.origin, DOMAINS.a.origin,
@ -72,13 +82,12 @@ describe('permissions middleware', function () {
await userApprovalPromise await userApprovalPromise
assert.equal( assert.ok(
permController.pendingApprovals.size, permController.approvals._add.calledOnce,
1, 'should have added single approval request',
'perm controller should have single pending approval',
) )
const id = permController.pendingApprovals.keys().next().value const id = getNextApprovalId(permController)
const approvedReq = PERMS.approvedRequest( const approvedReq = PERMS.approvedRequest(
id, id,
PERMS.requests.eth_accounts(), PERMS.requests.eth_accounts(),
@ -150,7 +159,7 @@ describe('permissions middleware', function () {
await userApprovalPromise await userApprovalPromise
const id1 = permController.pendingApprovals.keys().next().value const id1 = getNextApprovalId(permController)
const approvedReq1 = PERMS.approvedRequest( const approvedReq1 = PERMS.approvedRequest(
id1, id1,
PERMS.requests.eth_accounts(), PERMS.requests.eth_accounts(),
@ -219,7 +228,7 @@ describe('permissions middleware', function () {
await userApprovalPromise await userApprovalPromise
const id2 = permController.pendingApprovals.keys().next().value const id2 = getNextApprovalId(permController)
const approvedReq2 = PERMS.approvedRequest(id2, { ...requestedPerms2 }) const approvedReq2 = PERMS.approvedRequest(id2, { ...requestedPerms2 })
await permController.approvePermissionsRequest( await permController.approvePermissionsRequest(
@ -275,6 +284,8 @@ describe('permissions middleware', function () {
}) })
it('rejects permissions on user rejection', async function () { it('rejects permissions on user rejection', async function () {
createApprovalSpies(permController)
const aMiddleware = getPermissionsMiddleware( const aMiddleware = getPermissionsMiddleware(
permController, permController,
DOMAINS.a.origin, DOMAINS.a.origin,
@ -298,13 +309,12 @@ describe('permissions middleware', function () {
await userApprovalPromise await userApprovalPromise
assert.equal( assert.ok(
permController.pendingApprovals.size, permController.approvals._add.calledOnce,
1, 'should have added single approval request',
'perm controller should have single pending approval',
) )
const id = permController.pendingApprovals.keys().next().value const id = getNextApprovalId(permController)
await permController.rejectPermissionsRequest(id) await permController.rejectPermissionsRequest(id)
await requestRejection await requestRejection
@ -328,6 +338,8 @@ describe('permissions middleware', function () {
}) })
it('rejects requests with unknown permissions', async function () { it('rejects requests with unknown permissions', async function () {
createApprovalSpies(permController)
const aMiddleware = getPermissionsMiddleware( const aMiddleware = getPermissionsMiddleware(
permController, permController,
DOMAINS.a.origin, DOMAINS.a.origin,
@ -349,10 +361,9 @@ describe('permissions middleware', function () {
'request should be rejected with correct error', 'request should be rejected with correct error',
) )
assert.equal( assert.ok(
permController.pendingApprovals.size, permController.approvals._add.notCalled,
0, 'no approval requests should have been added',
'perm controller should have no pending approvals',
) )
assert.ok( assert.ok(
@ -367,7 +378,7 @@ describe('permissions middleware', function () {
}) })
it('accepts only a single pending permissions request per origin', async function () { it('accepts only a single pending permissions request per origin', async function () {
const expectedError = ERRORS.pendingApprovals.requestAlreadyPending() createApprovalSpies(permController)
// two middlewares for two origins // two middlewares for two origins
@ -414,10 +425,9 @@ describe('permissions middleware', function () {
await userApprovalPromise await userApprovalPromise
assert.equal( assert.ok(
permController.pendingApprovals.size, permController.approvals._add.calledTwice,
2, 'should have added two approval requests',
'perm controller should have expected number of pending approvals',
) )
// create and start processing second request for first origin, // create and start processing second request for first origin,
@ -431,6 +441,10 @@ describe('permissions middleware', function () {
userApprovalPromise = getUserApprovalPromise(permController) userApprovalPromise = getUserApprovalPromise(permController)
const expectedError = ERRORS.pendingApprovals.requestAlreadyPending(
DOMAINS.a.origin,
)
const requestApprovalFail = assert.rejects( const requestApprovalFail = assert.rejects(
aMiddleware(reqA2, resA2), aMiddleware(reqA2, resA2),
expectedError, expectedError,
@ -447,17 +461,20 @@ describe('permissions middleware', function () {
'response should have expected error and no result', 'response should have expected error and no result',
) )
// first requests for both origins should remain
assert.equal( assert.equal(
permController.pendingApprovals.size, permController.approvals._add.callCount,
3,
'should have attempted to create three pending approvals',
)
assert.equal(
permController.approvals._approvals.size,
2, 2,
'perm controller should have expected number of pending approvals', 'should only have created two pending approvals',
) )
// now, remaining pending requests should be approved without issue // now, remaining pending requests should be approved without issue
for (const id of permController.pendingApprovals.keys()) { for (const id of permController.approvals._approvals.keys()) {
await permController.approvePermissionsRequest( await permController.approvePermissionsRequest(
PERMS.approvedRequest(id, PERMS.requests.test_method()), PERMS.approvedRequest(id, PERMS.requests.test_method()),
) )
@ -484,12 +501,6 @@ describe('permissions middleware', function () {
1, 1,
'second origin should have single approved permission', 'second origin should have single approved permission',
) )
assert.equal(
permController.pendingApprovals.size,
0,
'perm controller should have expected number of pending approvals',
)
}) })
}) })
@ -609,6 +620,8 @@ describe('permissions middleware', function () {
}) })
it('requests accounts for unpermitted origin, and approves on user approval', async function () { it('requests accounts for unpermitted origin, and approves on user approval', async function () {
createApprovalSpies(permController)
const userApprovalPromise = getUserApprovalPromise(permController) const userApprovalPromise = getUserApprovalPromise(permController)
const aMiddleware = getPermissionsMiddleware( const aMiddleware = getPermissionsMiddleware(
@ -626,13 +639,12 @@ describe('permissions middleware', function () {
await userApprovalPromise await userApprovalPromise
assert.equal( assert.ok(
permController.pendingApprovals.size, permController.approvals._add.calledOnce,
1, 'should have added single approval request',
'perm controller should have single pending approval',
) )
const id = permController.pendingApprovals.keys().next().value const id = getNextApprovalId(permController)
const approvedReq = PERMS.approvedRequest( const approvedReq = PERMS.approvedRequest(
id, id,
PERMS.requests.eth_accounts(), PERMS.requests.eth_accounts(),
@ -685,6 +697,8 @@ describe('permissions middleware', function () {
}) })
it('requests accounts for unpermitted origin, and rejects on user rejection', async function () { it('requests accounts for unpermitted origin, and rejects on user rejection', async function () {
createApprovalSpies(permController)
const userApprovalPromise = getUserApprovalPromise(permController) const userApprovalPromise = getUserApprovalPromise(permController)
const aMiddleware = getPermissionsMiddleware( const aMiddleware = getPermissionsMiddleware(
@ -705,13 +719,12 @@ describe('permissions middleware', function () {
await userApprovalPromise await userApprovalPromise
assert.equal( assert.ok(
permController.pendingApprovals.size, permController.approvals._add.calledOnce,
1, 'should have added single approval request',
'perm controller should have single pending approval',
) )
const id = permController.pendingApprovals.keys().next().value const id = getNextApprovalId(permController)
await permController.rejectPermissionsRequest(id) await permController.rejectPermissionsRequest(id)
await requestRejection await requestRejection
@ -788,7 +801,7 @@ describe('permissions middleware', function () {
// this will reject because of the already pending request // this will reject because of the already pending request
await assert.rejects( await assert.rejects(
cMiddleware({ ...req }, {}), cMiddleware({ ...req }, {}),
ERRORS.eth_requestAccounts.requestAlreadyPending(), ERRORS.eth_requestAccounts.requestAlreadyPending(DOMAINS.c.origin),
) )
// now unlock and let through the first request // now unlock and let through the first request
@ -808,7 +821,7 @@ describe('permissions middleware', function () {
}) })
}) })
describe('wallet_sendDomainMetadata', function () { describe('metamask_sendDomainMetadata', function () {
let permController, clock let permController, clock
beforeEach(function () { beforeEach(function () {
@ -828,7 +841,10 @@ describe('permissions middleware', function () {
DOMAINS.c.origin, DOMAINS.c.origin,
) )
const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, name) const req = RPC_REQUESTS.metamask_sendDomainMetadata(
DOMAINS.c.origin,
name,
)
const res = {} const res = {}
await assert.doesNotReject(cMiddleware(req, res), 'should not reject') await assert.doesNotReject(cMiddleware(req, res), 'should not reject')
@ -861,7 +877,10 @@ describe('permissions middleware', function () {
extensionId, extensionId,
) )
const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, name) const req = RPC_REQUESTS.metamask_sendDomainMetadata(
DOMAINS.c.origin,
name,
)
const res = {} const res = {}
await assert.doesNotReject(cMiddleware(req, res), 'should not reject') await assert.doesNotReject(cMiddleware(req, res), 'should not reject')
@ -885,7 +904,10 @@ describe('permissions middleware', function () {
DOMAINS.c.origin, DOMAINS.c.origin,
) )
const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, name) const req = RPC_REQUESTS.metamask_sendDomainMetadata(
DOMAINS.c.origin,
name,
)
const res = {} const res = {}
await assert.doesNotReject(cMiddleware(req, res), 'should not reject') await assert.doesNotReject(cMiddleware(req, res), 'should not reject')
@ -907,7 +929,7 @@ describe('permissions middleware', function () {
DOMAINS.c.origin, DOMAINS.c.origin,
) )
const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin) const req = RPC_REQUESTS.metamask_sendDomainMetadata(DOMAINS.c.origin)
delete req.domainMetadata delete req.domainMetadata
const res = {} const res = {}

@ -1,5 +1,5 @@
import assert from 'assert' import assert from 'assert'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import sinon from 'sinon' import sinon from 'sinon'
import PreferencesController from '../../../../app/scripts/controllers/preferences' import PreferencesController from '../../../../app/scripts/controllers/preferences'

@ -4,7 +4,7 @@ import sinon from 'sinon'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { mapValues } from 'lodash' import { mapValues } from 'lodash'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import { import {
ROPSTEN_NETWORK_ID, ROPSTEN_NETWORK_ID,
MAINNET_NETWORK_ID, MAINNET_NETWORK_ID,

@ -1,6 +1,6 @@
import assert from 'assert' import assert from 'assert'
import sinon from 'sinon' import sinon from 'sinon'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import TokenRatesController from '../../../../app/scripts/controllers/token-rates' import TokenRatesController from '../../../../app/scripts/controllers/token-rates'
describe('TokenRatesController', function () { describe('TokenRatesController', function () {

@ -2,7 +2,7 @@ import { strict as assert } from 'assert'
import EventEmitter from 'events' import EventEmitter from 'events'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
import EthTx from 'ethereumjs-tx' import EthTx from 'ethereumjs-tx'
import ObservableStore from 'obs-store' import { ObservableStore } from '@metamask/obs-store'
import sinon from 'sinon' import sinon from 'sinon'
import TransactionController from '../../../../../app/scripts/controllers/transactions' import TransactionController from '../../../../../app/scripts/controllers/transactions'

@ -0,0 +1,124 @@
import { strict as assert } from 'assert'
import migration51 from '../../../app/scripts/migrations/051'
import {
INFURA_PROVIDER_TYPES,
NETWORK_TYPE_TO_ID_MAP,
} from '../../../app/scripts/controllers/network/enums'
describe('migration #51', function () {
it('should update the version metadata', async function () {
const oldStorage = {
meta: {
version: 50,
},
data: {},
}
const newStorage = await migration51.migrate(oldStorage)
assert.deepEqual(newStorage.meta, {
version: 51,
})
})
describe('setting chainId', function () {
INFURA_PROVIDER_TYPES.forEach(function (type) {
it(`should correctly set the chainId for the Infura network "${type}", if no chainId is set`, async function () {
const oldStorage = {
meta: {},
data: {
NetworkController: {
settings: {
fizz: 'buzz',
},
provider: {
type,
},
},
foo: 'bar',
},
}
const newStorage = await migration51.migrate(oldStorage)
assert.deepEqual(newStorage.data, {
NetworkController: {
settings: {
fizz: 'buzz',
},
provider: {
type,
chainId: NETWORK_TYPE_TO_ID_MAP[type].chainId,
},
},
foo: 'bar',
})
})
it(`should correctly set the chainId for the Infura network "${type}", if an incorrect chainId is set`, async function () {
const oldStorage = {
meta: {},
data: {
NetworkController: {
settings: {
fizz: 'buzz',
},
provider: {
type,
chainId: 'foo',
},
},
foo: 'bar',
},
}
const newStorage = await migration51.migrate(oldStorage)
assert.deepEqual(newStorage.data, {
NetworkController: {
settings: {
fizz: 'buzz',
},
provider: {
type,
chainId: NETWORK_TYPE_TO_ID_MAP[type].chainId,
},
},
foo: 'bar',
})
})
})
it('should not set the chainId for a non-Infura network that does not have chainId set', async function () {
const oldStorage = {
meta: {},
data: {
NetworkController: {
settings: {
fizz: 'buzz',
},
provider: {
type: 'foo',
},
},
},
}
const newStorage = await migration51.migrate(oldStorage)
assert.deepEqual(newStorage.data, oldStorage.data)
})
it('should not set the chainId for a non-Infura network that does have chainId set', async function () {
const oldStorage = {
meta: {},
data: {
NetworkController: {
settings: {
fizz: 'buzz',
},
provider: {
type: 'foo',
chainId: '0x999',
},
},
},
}
const newStorage = await migration51.migrate(oldStorage)
assert.deepEqual(newStorage.data, oldStorage.data)
})
})
})

@ -40,9 +40,6 @@ function mapDispatchToProps(dispatch) {
setProviderType: (type) => { setProviderType: (type) => {
dispatch(actions.setProviderType(type)) dispatch(actions.setProviderType(type))
}, },
setPreviousProvider: (type) => {
dispatch(actions.setPreviousProvider(type))
},
setRpcTarget: (target, chainId, ticker, nickname) => { setRpcTarget: (target, chainId, ticker, nickname) => {
dispatch(actions.setRpcTarget(target, chainId, ticker, nickname)) dispatch(actions.setRpcTarget(target, chainId, ticker, nickname))
}, },
@ -85,7 +82,6 @@ class NetworkDropdown extends Component {
setRpcTarget: PropTypes.func.isRequired, setRpcTarget: PropTypes.func.isRequired,
hideNetworkDropdown: PropTypes.func.isRequired, hideNetworkDropdown: PropTypes.func.isRequired,
setNetworksTabAddMode: PropTypes.func.isRequired, setNetworksTabAddMode: PropTypes.func.isRequired,
setPreviousProvider: PropTypes.func.isRequired,
setSelectedSettingsRpcUrl: PropTypes.func.isRequired, setSelectedSettingsRpcUrl: PropTypes.func.isRequired,
frequentRpcListDetail: PropTypes.array.isRequired, frequentRpcListDetail: PropTypes.array.isRequired,
networkDropdownOpen: PropTypes.bool.isRequired, networkDropdownOpen: PropTypes.bool.isRequired,
@ -116,10 +112,6 @@ class NetworkDropdown extends Component {
} }
renderCustomRpcList(rpcListDetail, provider) { renderCustomRpcList(rpcListDetail, provider) {
const {
provider: { type: providerType },
setPreviousProvider,
} = this.props
const reversedRpcListDetail = rpcListDetail.slice().reverse() const reversedRpcListDetail = rpcListDetail.slice().reverse()
return reversedRpcListDetail.map((entry) => { return reversedRpcListDetail.map((entry) => {
@ -133,7 +125,6 @@ class NetworkDropdown extends Component {
closeMenu={() => this.props.hideNetworkDropdown()} closeMenu={() => this.props.hideNetworkDropdown()}
onClick={() => { onClick={() => {
if (isPrefixedFormattedHexString(chainId)) { if (isPrefixedFormattedHexString(chainId)) {
setPreviousProvider(providerType)
this.props.setRpcTarget(rpcUrl, chainId, ticker, nickname) this.props.setRpcTarget(rpcUrl, chainId, ticker, nickname)
} else { } else {
this.props.displayInvalidCustomNetworkAlert(nickname || rpcUrl) this.props.displayInvalidCustomNetworkAlert(nickname || rpcUrl)

@ -321,7 +321,7 @@ function calcCustomGasLimit(customGasLimitInHex) {
} }
function sumHexWEIsToRenderableEth(hexWEIs) { function sumHexWEIsToRenderableEth(hexWEIs) {
const hexWEIsSum = hexWEIs.filter((n) => n).reduce(addHexes) const hexWEIsSum = hexWEIs.filter(Boolean).reduce(addHexes)
return formatETHFee( return formatETHFee(
getValueFromWeiHex({ getValueFromWeiHex({
value: hexWEIsSum, value: hexWEIsSum,

@ -35,6 +35,7 @@
color: #4eade7; color: #4eade7;
position: absolute; position: absolute;
font-size: 0.75rem;
top: 4px; top: 4px;
right: 16px; right: 16px;
cursor: pointer; cursor: pointer;

@ -1,78 +1,46 @@
import React, { PureComponent } from 'react' import React, { useState } from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import { Tooltip as ReactTippy } from 'react-tippy'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Button from '../../ui/button' import Button from '../../ui/button'
import Checkbox from '../../ui/check-box'
import Tooltip from '../../ui/tooltip'
export default class HomeNotification extends PureComponent { const HomeNotification = ({
static contextTypes = {
metricsEvent: PropTypes.func,
}
static defaultProps = {
onAccept: null,
ignoreText: null,
onIgnore: null,
infoText: null,
}
static propTypes = {
acceptText: PropTypes.node.isRequired,
onAccept: PropTypes.func,
ignoreText: PropTypes.node,
onIgnore: PropTypes.func,
descriptionText: PropTypes.node.isRequired,
infoText: PropTypes.node,
classNames: PropTypes.array,
}
handleAccept = () => {
this.props.onAccept()
}
handleIgnore = () => {
this.props.onIgnore()
}
render() {
const {
descriptionText,
acceptText, acceptText,
onAccept, checkboxText,
checkboxTooltipText,
classNames = [],
descriptionText,
ignoreText, ignoreText,
onIgnore,
infoText, infoText,
classNames = [], onAccept,
} = this.props onIgnore,
}) => {
const [checkboxState, setCheckBoxState] = useState(false)
const checkboxElement = checkboxText && (
<Checkbox
id="homeNotification_checkbox"
checked={checkboxState}
className="home-notification__checkbox"
onClick={() => setCheckBoxState((checked) => !checked)}
/>
)
return ( return (
<div className={classnames('home-notification', ...classNames)}> <div className={classnames('home-notification', ...classNames)}>
<div className="home-notification__header"> <div className="home-notification__content">
<div className="home-notification__header-container"> <div className="home-notification__content-container">
<img
className="home-notification__icon"
alt=""
src="images/icons/connect.svg"
/>
<div className="home-notification__text">{descriptionText}</div> <div className="home-notification__text">{descriptionText}</div>
</div> </div>
{infoText ? ( {infoText ? (
<ReactTippy <Tooltip
style={{
display: 'flex',
}}
html={
<p className="home-notification-tooltip__content">{infoText}</p>
}
offset={-36}
distance={36}
animation="none"
position="top" position="top"
arrow title={infoText}
theme="tippy-tooltip-home" wrapperClassName="home-notification__tooltip-wrapper"
> >
<img alt="" src="images/icons/info.svg" /> <i className="fa fa-info-circle" />
</ReactTippy> </Tooltip>
) : null} ) : null}
</div> </div>
<div className="home-notification__buttons"> <div className="home-notification__buttons">
@ -80,7 +48,7 @@ export default class HomeNotification extends PureComponent {
<Button <Button
type="primary" type="primary"
className="home-notification__accept-button" className="home-notification__accept-button"
onClick={this.handleAccept} onClick={onAccept}
> >
{acceptText} {acceptText}
</Button> </Button>
@ -89,13 +57,49 @@ export default class HomeNotification extends PureComponent {
<Button <Button
type="secondary" type="secondary"
className="home-notification__ignore-button" className="home-notification__ignore-button"
onClick={this.handleIgnore} // Some onIgnore handlers use the checkboxState to determine whether
// to disable the notification
onClick={() => onIgnore(checkboxState)}
> >
{ignoreText} {ignoreText}
</Button> </Button>
) : null} ) : null}
{checkboxText ? (
<div className="home-notification__checkbox-wrapper">
{checkboxTooltipText ? (
<Tooltip
position="top"
title={checkboxTooltipText}
wrapperClassName="home-notification__checkbox-label-tooltip"
>
{checkboxElement}
</Tooltip>
) : (
checkboxElement
)}
<label
className="home-notification__checkbox-label"
htmlFor="homeNotification_checkbox"
>
{checkboxText}
</label>
</div>
) : null}
</div> </div>
</div> </div>
) )
}
} }
HomeNotification.propTypes = {
acceptText: PropTypes.node,
checkboxText: PropTypes.node,
checkboxTooltipText: PropTypes.node,
classNames: PropTypes.array,
descriptionText: PropTypes.node.isRequired,
ignoreText: PropTypes.node,
infoText: PropTypes.node,
onAccept: PropTypes.func,
onIgnore: PropTypes.func,
}
export default HomeNotification

@ -1,14 +1,7 @@
.tippy-tooltip {
// This looks weird, but its repeating the class name
// using interpolation for higher specificity.
&#{&}-home-theme {
background: rgba(36, 41, 46, 0.9);
color: $white;
border-radius: 8px;
}
}
.home-notification { .home-notification {
display: flex;
flex-flow: column;
justify-content: space-between;
background: rgba(36, 41, 46, 0.9); background: rgba(36, 41, 46, 0.9);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
border-radius: 8px; border-radius: 8px;
@ -19,37 +12,62 @@
min-width: 472px; min-width: 472px;
} }
display: flex; &__content-container {
flex-flow: column;
justify-content: space-between;
&__header-container {
display: flex; display: flex;
} }
&__header { &__content {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
} }
&__icon {
height: 16px;
align-self: center;
}
&__text { &__text {
@include H7; @include H7;
color: $white; color: $white;
margin-left: 10px; }
margin-right: 8px;
&__text-link {
@include H7;
color: $primary-blue;
cursor: pointer;
} }
.fa-info-circle { .fa-info-circle {
color: #6a737d; color: #6a737d;
} }
& &__checkbox-wrapper {
display: flex;
flex-direction: row;
align-items: center;
@media screen and (max-width: 575px) {
width: 160px;
}
}
& &__checkbox {
height: 13px;
width: 13px;
font-size: 16px;
cursor: pointer;
}
& &__checkbox-label {
@include H7;
color: $white;
margin-left: 10px;
margin-top: 1px;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
cursor: pointer;
}
& &__ignore-button { & &__ignore-button {
border-color: #6a737d; border-color: #6a737d;
box-sizing: border-box; box-sizing: border-box;
@ -102,23 +120,14 @@
&__buttons { &__buttons {
display: flex; display: flex;
width: 100%; width: 100%;
margin-top: 10px; padding-top: 10px;
justify-content: flex-start; align-items: center;
justify-content: space-between;
flex-direction: row-reverse; flex-direction: row-reverse;
} }
}
.home-notification-tooltip { &__tooltip-wrapper {
&__tooltip-container {
display: flex; display: flex;
} margin-left: 10px;
&__content {
@include H7;
color: $white;
text-align: left;
display: inline-block;
width: 200px;
} }
} }

@ -19,11 +19,8 @@ export default class LoadingNetworkScreen extends PureComponent {
providerId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), providerId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
showNetworkDropdown: PropTypes.func, showNetworkDropdown: PropTypes.func,
setProviderArgs: PropTypes.array, setProviderArgs: PropTypes.array,
lastSelectedProvider: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
]),
setProviderType: PropTypes.func, setProviderType: PropTypes.func,
rollbackToPreviousProvider: PropTypes.func,
isLoadingNetwork: PropTypes.bool, isLoadingNetwork: PropTypes.bool,
} }
@ -123,14 +120,14 @@ export default class LoadingNetworkScreen extends PureComponent {
} }
render() { render() {
const { lastSelectedProvider, setProviderType } = this.props const { rollbackToPreviousProvider } = this.props
return ( return (
<LoadingScreen <LoadingScreen
header={ header={
<div <div
className="page-container__header-close" className="page-container__header-close"
onClick={() => setProviderType(lastSelectedProvider || 'ropsten')} onClick={rollbackToPreviousProvider}
/> />
} }
showLoadingSpinner={!this.state.showErrorScreen} showLoadingSpinner={!this.state.showErrorScreen}

@ -4,7 +4,7 @@ import { getNetworkIdentifier } from '../../../selectors'
import LoadingNetworkScreen from './loading-network-screen.component' import LoadingNetworkScreen from './loading-network-screen.component'
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { loadingMessage, lastSelectedProvider } = state.appState const { loadingMessage } = state.appState
const { provider, network } = state.metamask const { provider, network } = state.metamask
const { rpcUrl, chainId, ticker, nickname, type } = provider const { rpcUrl, chainId, ticker, nickname, type } = provider
@ -14,7 +14,6 @@ const mapStateToProps = (state) => {
return { return {
isLoadingNetwork: network === 'loading', isLoadingNetwork: network === 'loading',
loadingMessage, loadingMessage,
lastSelectedProvider,
setProviderArgs, setProviderArgs,
provider, provider,
providerId: getNetworkIdentifier(state), providerId: getNetworkIdentifier(state),
@ -26,6 +25,8 @@ const mapDispatchToProps = (dispatch) => {
setProviderType: (type) => { setProviderType: (type) => {
dispatch(actions.setProviderType(type)) dispatch(actions.setProviderType(type))
}, },
rollbackToPreviousProvider: () =>
dispatch(actions.rollbackToPreviousProvider()),
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
} }
} }

@ -21,7 +21,7 @@ export default class MultipleNotifications extends PureComponent {
const { showAll } = this.state const { showAll } = this.state
const { children, classNames } = this.props const { children, classNames } = this.props
const childrenToRender = children.filter((child) => child) const childrenToRender = children.filter(Boolean)
if (childrenToRender.length === 0) { if (childrenToRender.length === 0) {
return null return null
} }

@ -67,7 +67,7 @@ export default class PageContainer extends PureComponent {
renderActiveTabContent() { renderActiveTabContent() {
const { tabsComponent } = this.props const { tabsComponent } = this.props
let { children } = tabsComponent.props let { children } = tabsComponent.props
children = children.filter((child) => child) children = children.filter(Boolean)
const { activeTabIndex } = this.state const { activeTabIndex } = this.state
return children[activeTabIndex] return children[activeTabIndex]

@ -20,9 +20,9 @@
border-radius: 50%; border-radius: 50%;
background: #bbc0c5; background: #bbc0c5;
flex: 0 1 auto; flex: 0 1 auto;
display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
text-align: center;
padding-top: 2px; padding-top: 2px;
} }
} }

@ -3,13 +3,13 @@ import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import IconWithFallback from '../icon-with-fallback' import IconWithFallback from '../icon-with-fallback'
export default function UrlIcon({ url, className, name }) { export default function UrlIcon({ url, className, name, fallbackClassName }) {
return ( return (
<IconWithFallback <IconWithFallback
className={classnames('url-icon', className)} className={classnames('url-icon', className)}
icon={url} icon={url}
name={name} name={name}
fallbackClassName="url-icon__fallback" fallbackClassName={classnames('url-icon__fallback', fallbackClassName)}
/> />
) )
} }
@ -18,4 +18,5 @@ UrlIcon.propTypes = {
url: PropTypes.string, url: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,
name: PropTypes.string, name: PropTypes.string,
fallbackClassName: PropTypes.string,
} }

@ -1,6 +1,6 @@
import { createSlice } from '@reduxjs/toolkit' import { createSlice } from '@reduxjs/toolkit'
import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert' import { ALERT_TYPES } from '../../../../shared/constants/alerts'
import { ALERT_STATE } from './enums' import { ALERT_STATE } from './enums'
// Constants // Constants

@ -1,7 +1,7 @@
import { createSlice } from '@reduxjs/toolkit' import { createSlice } from '@reduxjs/toolkit'
import { captureException } from '@sentry/browser' import { captureException } from '@sentry/browser'
import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert' import { ALERT_TYPES } from '../../../../shared/constants/alerts'
import * as actionConstants from '../../store/actionConstants' import * as actionConstants from '../../store/actionConstants'
import { import {
addPermittedAccount, addPermittedAccount,
@ -101,7 +101,7 @@ export const dismissAndDisableAlert = () => {
return async (dispatch) => { return async (dispatch) => {
try { try {
await dispatch(disableAlertRequested()) await dispatch(disableAlertRequested())
await dispatch(setAlertEnabledness(name, false)) await setAlertEnabledness(name, false)
await dispatch(disableAlertSucceeded()) await dispatch(disableAlertSucceeded())
} catch (error) { } catch (error) {
console.error(error) console.error(error)

@ -42,7 +42,6 @@ export default function reduceApp(state = {}, action) {
trezor: `m/44'/60'/0'/0`, trezor: `m/44'/60'/0'/0`,
ledger: `m/44'/60'/0'/0/0`, ledger: `m/44'/60'/0'/0/0`,
}, },
lastSelectedProvider: null,
networksTabSelectedRpcUrl: '', networksTabSelectedRpcUrl: '',
networksTabIsInAddMode: false, networksTabIsInAddMode: false,
loadingMethodData: false, loadingMethodData: false,
@ -305,15 +304,6 @@ export default function reduceApp(state = {}, action) {
gasIsLoading: false, gasIsLoading: false,
} }
case actionConstants.SET_PREVIOUS_PROVIDER:
if (action.value === 'loading') {
return appState
}
return {
...appState,
lastSelectedProvider: action.value,
}
case actionConstants.SET_SELECTED_SETTINGS_RPC_URL: case actionConstants.SET_SELECTED_SETTINGS_RPC_URL:
return { return {
...appState, ...appState,

@ -1,5 +1,5 @@
import { combineReducers } from 'redux' import { combineReducers } from 'redux'
import { ALERT_TYPES } from '../../../app/scripts/controllers/alert' import { ALERT_TYPES } from '../../../shared/constants/alerts'
import metamaskReducer from './metamask/metamask' import metamaskReducer from './metamask/metamask'
import localeMessagesReducer from './locale/locale' import localeMessagesReducer from './locale/locale'
import sendReducer from './send/send.duck' import sendReducer from './send/send.duck'

@ -1,5 +1,5 @@
import * as actionConstants from '../../store/actionConstants' import * as actionConstants from '../../store/actionConstants'
import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert' import { ALERT_TYPES } from '../../../../shared/constants/alerts'
export default function reduceMetamask(state = {}, action) { export default function reduceMetamask(state = {}, action) {
const metamaskState = { const metamaskState = {
@ -375,12 +375,12 @@ export const getCurrentLocale = (state) => state.metamask.currentLocale
export const getAlertEnabledness = (state) => state.metamask.alertEnabledness export const getAlertEnabledness = (state) => state.metamask.alertEnabledness
export const getInvalidCustomNetworkAlertEnabledness = (state) =>
getAlertEnabledness(state)[ALERT_TYPES.invalidCustomNetwork]
export const getUnconnectedAccountAlertEnabledness = (state) => export const getUnconnectedAccountAlertEnabledness = (state) =>
getAlertEnabledness(state)[ALERT_TYPES.unconnectedAccount] getAlertEnabledness(state)[ALERT_TYPES.unconnectedAccount]
export const getWeb3ShimUsageAlertEnabledness = (state) =>
getAlertEnabledness(state)[ALERT_TYPES.web3ShimUsage]
export const getUnconnectedAccountAlertShown = (state) => export const getUnconnectedAccountAlertShown = (state) =>
state.metamask.unconnectedAccountAlertShownOrigins state.metamask.unconnectedAccountAlertShownOrigins

@ -606,7 +606,7 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
customSwapsGas || customSwapsGas ||
(usedQuote?.gasEstimate (usedQuote?.gasEstimate
? estimatedGasLimitWithMultiplier ? estimatedGasLimitWithMultiplier
: usedQuote?.maxGas) : `0x${decimalToHex(usedQuote?.maxGas || 0)}`)
const usedGasPrice = getUsedSwapsGasPrice(state) const usedGasPrice = getUsedSwapsGasPrice(state)
usedTradeTxParams.gas = maxGasLimit usedTradeTxParams.gas = maxGasLimit

@ -172,7 +172,7 @@ export function addHexes(aHexWEI, bHexWEI) {
} }
export function sumHexWEIs(hexWEIs) { export function sumHexWEIs(hexWEIs) {
return hexWEIs.filter((n) => n).reduce(addHexes) return hexWEIs.filter(Boolean).reduce(addHexes)
} }
export function sumHexWEIsToUnformattedFiat( export function sumHexWEIsToUnformattedFiat(

@ -145,9 +145,7 @@ export function useTokensToSearch({
return new BigNumber(rawFiat || 0).gt(secondRawFiat || 0) ? -1 : 1 return new BigNumber(rawFiat || 0).gt(secondRawFiat || 0) ? -1 : 1
}, },
) )
tokensToSearchBuckets.top = tokensToSearchBuckets.top.filter( tokensToSearchBuckets.top = tokensToSearchBuckets.top.filter(Boolean)
(token) => token,
)
return [ return [
...tokensToSearchBuckets.owned, ...tokensToSearchBuckets.owned,
...tokensToSearchBuckets.top, ...tokensToSearchBuckets.top,

@ -4,7 +4,7 @@ import classnames from 'classnames'
import { checkExistingAddresses } from '../../../helpers/utils/util' import { checkExistingAddresses } from '../../../helpers/utils/util'
import TokenListPlaceholder from './token-list-placeholder' import TokenListPlaceholder from './token-list-placeholder'
export default class InfoBox extends Component { export default class TokenList extends Component {
static contextTypes = { static contextTypes = {
t: PropTypes.func, t: PropTypes.func,
} }

@ -141,7 +141,7 @@ export default class ConfirmTransactionBase extends Component {
nextNonce !== prevNextNonce || nextNonce !== prevNextNonce ||
customNonceValue !== prevCustomNonceValue customNonceValue !== prevCustomNonceValue
) { ) {
if (customNonceValue > nextNonce) { if (nextNonce !== null && customNonceValue > nextNonce) {
this.setState({ this.setState({
submitWarning: this.context.t('nextNonceWarning', [nextNonce]), submitWarning: this.context.t('nextNonceWarning', [nextNonce]),
}) })

@ -31,6 +31,8 @@ import {
const LEARN_MORE_URL = const LEARN_MORE_URL =
'https://metamask.zendesk.com/hc/en-us/articles/360045129011-Intro-to-MetaMask-v8-extension' 'https://metamask.zendesk.com/hc/en-us/articles/360045129011-Intro-to-MetaMask-v8-extension'
const LEGACY_WEB3_URL =
'https://metamask.zendesk.com/hc/en-us/articles/360053147012'
export default class Home extends PureComponent { export default class Home extends PureComponent {
static contextTypes = { static contextTypes = {
@ -42,7 +44,7 @@ export default class Home extends PureComponent {
forgottenPassword: PropTypes.bool, forgottenPassword: PropTypes.bool,
suggestedTokens: PropTypes.object, suggestedTokens: PropTypes.object,
unconfirmedTransactionsCount: PropTypes.number, unconfirmedTransactionsCount: PropTypes.number,
shouldShowSeedPhraseReminder: PropTypes.bool, shouldShowSeedPhraseReminder: PropTypes.bool.isRequired,
isPopup: PropTypes.bool, isPopup: PropTypes.bool,
isNotification: PropTypes.bool.isRequired, isNotification: PropTypes.bool.isRequired,
threeBoxSynced: PropTypes.bool, threeBoxSynced: PropTypes.bool,
@ -66,6 +68,10 @@ export default class Home extends PureComponent {
swapsFetchParams: PropTypes.object, swapsFetchParams: PropTypes.object,
swapsEnabled: PropTypes.bool, swapsEnabled: PropTypes.bool,
isMainnet: PropTypes.bool, isMainnet: PropTypes.bool,
shouldShowWeb3ShimUsageNotification: PropTypes.bool.isRequired,
setWeb3ShimUsageAlertDismissed: PropTypes.func.isRequired,
originOfCurrentTab: PropTypes.string,
disableWeb3ShimUsageAlert: PropTypes.func.isRequired,
} }
state = { state = {
@ -161,10 +167,39 @@ export default class Home extends PureComponent {
setShowRestorePromptToFalse, setShowRestorePromptToFalse,
showRestorePrompt, showRestorePrompt,
threeBoxLastUpdated, threeBoxLastUpdated,
shouldShowWeb3ShimUsageNotification,
setWeb3ShimUsageAlertDismissed,
originOfCurrentTab,
disableWeb3ShimUsageAlert,
} = this.props } = this.props
return ( return (
<MultipleNotifications> <MultipleNotifications>
{shouldShowWeb3ShimUsageNotification ? (
<HomeNotification
descriptionText={t('web3ShimUsageNotification', [
<span
key="web3ShimUsageNotificationLink"
className="home-notification__text-link"
onClick={() =>
global.platform.openTab({ url: LEGACY_WEB3_URL })
}
>
{t('here')}
</span>,
])}
ignoreText={t('dismiss')}
onIgnore={(disable) => {
setWeb3ShimUsageAlertDismissed(originOfCurrentTab)
if (disable) {
disableWeb3ShimUsageAlert()
}
}}
checkboxText={t('dontShowThisAgain')}
checkboxTooltipText={t('canToggleInSettings')}
key="home-web3ShimUsageNotification"
/>
) : null}
{shouldShowSeedPhraseReminder ? ( {shouldShowSeedPhraseReminder ? (
<HomeNotification <HomeNotification
descriptionText={t('backupApprovalNotice')} descriptionText={t('backupApprovalNotice')}

@ -2,11 +2,14 @@ import { compose } from 'redux'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom' import { withRouter } from 'react-router-dom'
import { import {
unconfirmedTransactionsCountSelector, activeTabHasPermissions,
getCurrentEthBalance, getCurrentEthBalance,
getFirstPermissionRequest, getFirstPermissionRequest,
getTotalUnapprovedCount,
getIsMainnet, getIsMainnet,
getOriginOfCurrentTab,
getTotalUnapprovedCount,
getWeb3ShimUsageStateForOrigin,
unconfirmedTransactionsCountSelector,
} from '../../selectors' } from '../../selectors'
import { import {
@ -17,8 +20,11 @@ import {
setConnectedStatusPopoverHasBeenShown, setConnectedStatusPopoverHasBeenShown,
setDefaultHomeActiveTabName, setDefaultHomeActiveTabName,
setSwapsWelcomeMessageHasBeenShown, setSwapsWelcomeMessageHasBeenShown,
setWeb3ShimUsageAlertDismissed,
setAlertEnabledness,
} from '../../store/actions' } from '../../store/actions'
import { setThreeBoxLastUpdated } from '../../ducks/app/app' import { setThreeBoxLastUpdated } from '../../ducks/app/app'
import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask'
import { import {
getSwapsWelcomeMessageSeenStatus, getSwapsWelcomeMessageSeenStatus,
getSwapsFeatureLiveness, getSwapsFeatureLiveness,
@ -28,6 +34,10 @@ import {
ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_POPUP, ENVIRONMENT_TYPE_POPUP,
} from '../../../../app/scripts/lib/enums' } from '../../../../app/scripts/lib/enums'
import {
ALERT_TYPES,
WEB3_SHIM_USAGE_ALERT_STATES,
} from '../../../../shared/constants/alerts'
import Home from './home.component' import Home from './home.component'
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
@ -58,6 +68,14 @@ const mapStateToProps = (state) => {
? firstPermissionsRequest.metadata.id ? firstPermissionsRequest.metadata.id
: null : null
const originOfCurrentTab = getOriginOfCurrentTab(state)
const shouldShowWeb3ShimUsageNotification =
isPopup &&
getWeb3ShimUsageAlertEnabledness(state) &&
activeTabHasPermissions(state) &&
getWeb3ShimUsageStateForOrigin(state, originOfCurrentTab) ===
WEB3_SHIM_USAGE_ALERT_STATES.RECORDED
return { return {
forgottenPassword, forgottenPassword,
suggestedTokens, suggestedTokens,
@ -81,6 +99,8 @@ const mapStateToProps = (state) => {
swapsFetchParams: swapsState.fetchParams, swapsFetchParams: swapsState.fetchParams,
showAwaitingSwapScreen: swapsState.routeState === 'awaiting', showAwaitingSwapScreen: swapsState.routeState === 'awaiting',
isMainnet: getIsMainnet(state), isMainnet: getIsMainnet(state),
originOfCurrentTab,
shouldShowWeb3ShimUsageNotification,
} }
} }
@ -103,6 +123,10 @@ const mapDispatchToProps = (dispatch) => ({
onTabClick: (name) => dispatch(setDefaultHomeActiveTabName(name)), onTabClick: (name) => dispatch(setDefaultHomeActiveTabName(name)),
setSwapsWelcomeMessageHasBeenShown: () => setSwapsWelcomeMessageHasBeenShown: () =>
dispatch(setSwapsWelcomeMessageHasBeenShown()), dispatch(setSwapsWelcomeMessageHasBeenShown()),
setWeb3ShimUsageAlertDismissed: (origin) =>
setWeb3ShimUsageAlertDismissed(origin),
disableWeb3ShimUsageAlert: () =>
setAlertEnabledness(ALERT_TYPES.web3ShimUsage, false),
}) })
export default compose( export default compose(

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save