Merge pull request #10513 from MetaMask/Version-v9.1.0

feature/default_network_editable
Brad Decker 4 years ago committed by GitHub
commit 2e88bd243d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 53
      .circleci/config.yml
  2. 17
      .circleci/scripts/create-lavamoat-viz.sh
  3. 7
      .circleci/scripts/deps-install.sh
  4. 0
      .circleci/scripts/firefox-install.sh
  5. 0
      .circleci/scripts/release-bump-changelog-version.sh
  6. 0
      .circleci/scripts/release-bump-manifest-version.sh
  7. 0
      .circleci/scripts/release-create-gh-release.sh
  8. 0
      .circleci/scripts/release-create-master-pr.sh
  9. 0
      .circleci/scripts/release-create-release-pr.sh
  10. 0
      .circleci/scripts/test-run-e2e.sh
  11. 15
      .circleci/scripts/validate-allow-scripts.sh
  12. 15
      .circleci/scripts/validate-lavamoat-policy.sh
  13. 0
      .circleci/scripts/yarn-audit.sh
  14. 1
      .eslintrc.js
  15. 1
      .prettierignore
  16. 37
      .storybook/i18n-party-addon/register.js
  17. 1
      .storybook/main.js
  18. 52
      .storybook/preview.js
  19. 218
      .storybook/test-data.js
  20. 36
      CHANGELOG.md
  21. 47
      README.md
  22. 6
      app/_locales/am/messages.json
  23. 6
      app/_locales/ar/messages.json
  24. 6
      app/_locales/bg/messages.json
  25. 6
      app/_locales/bn/messages.json
  26. 6
      app/_locales/ca/messages.json
  27. 3
      app/_locales/cs/messages.json
  28. 6
      app/_locales/da/messages.json
  29. 6
      app/_locales/de/messages.json
  30. 6
      app/_locales/el/messages.json
  31. 105
      app/_locales/en/messages.json
  32. 1311
      app/_locales/es/messages.json
  33. 1135
      app/_locales/es_419/messages.json
  34. 6
      app/_locales/et/messages.json
  35. 6
      app/_locales/fa/messages.json
  36. 6
      app/_locales/fi/messages.json
  37. 6
      app/_locales/fil/messages.json
  38. 6
      app/_locales/fr/messages.json
  39. 6
      app/_locales/he/messages.json
  40. 9
      app/_locales/hi/messages.json
  41. 3
      app/_locales/hn/messages.json
  42. 6
      app/_locales/hr/messages.json
  43. 6
      app/_locales/ht/messages.json
  44. 6
      app/_locales/hu/messages.json
  45. 9
      app/_locales/id/messages.json
  46. 1
      app/_locales/index.json
  47. 6
      app/_locales/it/messages.json
  48. 1831
      app/_locales/ja/messages.json
  49. 6
      app/_locales/kn/messages.json
  50. 9
      app/_locales/ko/messages.json
  51. 6
      app/_locales/lt/messages.json
  52. 6
      app/_locales/lv/messages.json
  53. 6
      app/_locales/ms/messages.json
  54. 7
      app/_locales/nl/messages.json
  55. 6
      app/_locales/no/messages.json
  56. 6
      app/_locales/pl/messages.json
  57. 3
      app/_locales/pt/messages.json
  58. 6
      app/_locales/pt_BR/messages.json
  59. 6
      app/_locales/ro/messages.json
  60. 1382
      app/_locales/ru/messages.json
  61. 6
      app/_locales/sk/messages.json
  62. 6
      app/_locales/sl/messages.json
  63. 6
      app/_locales/sr/messages.json
  64. 6
      app/_locales/sv/messages.json
  65. 6
      app/_locales/sw/messages.json
  66. 3
      app/_locales/ta/messages.json
  67. 3
      app/_locales/th/messages.json
  68. 2090
      app/_locales/tl/messages.json
  69. 3
      app/_locales/tr/messages.json
  70. 6
      app/_locales/uk/messages.json
  71. 1965
      app/_locales/vi/messages.json
  72. 1262
      app/_locales/zh_CN/messages.json
  73. 6
      app/_locales/zh_TW/messages.json
  74. 2
      app/manifest/_base.json
  75. 57
      app/scripts/background.js
  76. 5
      app/scripts/controllers/app-state.js
  77. 10
      app/scripts/controllers/ens/index.js
  78. 4
      app/scripts/controllers/incoming-transactions.js
  79. 4
      app/scripts/controllers/metametrics.js
  80. 2
      app/scripts/controllers/network/index.js
  81. 14
      app/scripts/controllers/network/network.js
  82. 7
      app/scripts/controllers/permissions/index.js
  83. 36
      app/scripts/controllers/preferences.js
  84. 3
      app/scripts/controllers/swaps.js
  85. 28
      app/scripts/controllers/token-rates.js
  86. 6
      app/scripts/controllers/transactions/index.js
  87. 17
      app/scripts/controllers/transactions/tx-state-manager.js
  88. 11
      app/scripts/lib/decrypt-message-manager.js
  89. 11
      app/scripts/lib/encryption-public-key-manager.js
  90. 4
      app/scripts/lib/ens-ipfs/setup.js
  91. 11
      app/scripts/lib/message-manager.js
  92. 11
      app/scripts/lib/personal-message-manager.js
  93. 272
      app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js
  94. 8
      app/scripts/lib/rpc-method-middleware/handlers/index.js
  95. 11
      app/scripts/lib/typed-message-manager.js
  96. 118
      app/scripts/metamask-controller.js
  97. 125
      app/scripts/migrations/052.js
  98. 1
      app/scripts/migrations/index.js
  99. 4
      app/scripts/platforms/extension.js
  100. 11
      development/build/index.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -26,6 +26,13 @@ workflows:
- /^Version-v(\d+)[.](\d+)[.](\d+)/
- prep-deps
- test-deps
- validate-lavamoat-config:
filters:
branches:
only:
- /^Version-v(\d+)[.](\d+)[.](\d+)|master/
requires:
- prep-deps
- prep-build:
requires:
- prep-deps
@ -72,6 +79,7 @@ workflows:
- prep-build
- all-tests-pass:
requires:
- validate-lavamoat-config
- test-lint
- test-lint-shellcheck
- test-lint-lockfile
@ -118,9 +126,9 @@ jobs:
- run:
name: Create GitHub Pull Request for version
command: |
.circleci/scripts/release-bump-changelog-version
.circleci/scripts/release-bump-manifest-version
.circleci/scripts/release-create-release-pr
.circleci/scripts/release-bump-changelog-version.sh
.circleci/scripts/release-bump-manifest-version.sh
.circleci/scripts/release-create-release-pr.sh
prep-deps:
executor: node-browsers
@ -143,6 +151,21 @@ jobs:
- node_modules
- build-artifacts
validate-lavamoat-config:
executor: node-browsers
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Validate allow-scripts config
command: |
.circleci/scripts/validate-allow-scripts.sh
- run:
name: Validate LavaMoat policy
command: |
.circleci/scripts/validate-lavamoat-policy.sh
prep-build:
executor: node-browsers-medium-plus
steps:
@ -257,7 +280,7 @@ jobs:
at: .
- run:
name: yarn audit
command: .circleci/scripts/yarn-audit
command: .circleci/scripts/yarn-audit.sh
test-e2e-chrome:
executor: node-browsers
@ -277,7 +300,7 @@ jobs:
- run:
name: test:e2e:chrome
command: |
if .circleci/scripts/test-run-e2e
if .circleci/scripts/test-run-e2e.sh
then
yarn test:e2e:chrome
fi
@ -304,7 +327,7 @@ jobs:
- run:
name: test:e2e:chrome:metrics
command: |
if .circleci/scripts/test-run-e2e
if .circleci/scripts/test-run-e2e.sh
then
yarn test:e2e:chrome:metrics
fi
@ -319,7 +342,7 @@ jobs:
- checkout
- run:
name: Install Firefox
command: ./.circleci/scripts/firefox-install
command: ./.circleci/scripts/firefox-install.sh
- attach_workspace:
at: .
- run:
@ -331,7 +354,7 @@ jobs:
- run:
name: test:e2e:firefox
command: |
if .circleci/scripts/test-run-e2e
if .circleci/scripts/test-run-e2e.sh
then
yarn test:e2e:firefox
fi
@ -346,7 +369,7 @@ jobs:
- checkout
- run:
name: Install Firefox
command: ./.circleci/scripts/firefox-install
command: ./.circleci/scripts/firefox-install.sh
- attach_workspace:
at: .
- run:
@ -358,7 +381,7 @@ jobs:
- run:
name: test:e2e:firefox:metrics
command: |
if .circleci/scripts/test-run-e2e
if .circleci/scripts/test-run-e2e.sh
then
yarn test:e2e:firefox:metrics
fi
@ -414,6 +437,12 @@ jobs:
- store_artifacts:
path: test-artifacts
destination: test-artifacts
# important: generate lavamoat viz AFTER uploading builds as artifacts
# Temporarily disabled until we can update to a version of `sesify` with
# this fix included: https://github.com/LavaMoat/LavaMoat/pull/121
- run:
name: build:lavamoat-viz
command: ./.circleci/scripts/create-lavamoat-viz.sh
- store_artifacts:
path: build-artifacts
destination: build-artifacts
@ -436,10 +465,10 @@ jobs:
- run:
name: Create GitHub release
command: |
.circleci/scripts/release-create-gh-release
.circleci/scripts/release-create-gh-release.sh
- run:
name: Create GitHub Pull Request to sync master with develop
command: .circleci/scripts/release-create-master-pr
command: .circleci/scripts/release-create-master-pr.sh
job-publish-storybook:
executor: node-browsers

@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -x
set -e
set -u
set -o pipefail
BUILD_DEST="./build-artifacts/build-viz/"
# prepare artifacts dir
mkdir -p "${BUILD_DEST}"
# generate lavamoat debug config
yarn lavamoat:debug
# generate viz
npx lavamoat-viz --dest "${BUILD_DEST}"

@ -5,7 +5,7 @@ set -x
# Exit immediately if a command exits with a non-zero status.
set -e
yarn --frozen-lockfile --har
yarn setup-ci
# Move HAR file into directory with consistent name so that we can cache it
mkdir -p build-artifacts/yarn-install-har
@ -13,7 +13,4 @@ har_files=(./*.har)
if [[ -f "${har_files[0]}" ]]
then
mv ./*.har build-artifacts/yarn-install-har/
fi
# use @lavamoat/allow-scripts instead of manually running install scripts so directory change does not persist
yarn allow-scripts
fi

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
yarn allow-scripts auto
if git diff --exit-code --quiet
then
echo "allow-scripts configuration is up-to-date"
else
echo "allow-scripts configuration requires updates"
exit 1
fi

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
yarn lavamoat:auto
if git diff --exit-code --quiet
then
echo "LavaMoat policy is up-to-date"
else
echo "LavaMoat policy requires updates"
exit 1
fi

@ -31,6 +31,7 @@ module.exports = {
'test/e2e/send-eth-with-private-key-test/**',
'nyc_output/**',
'.vscode/**',
'lavamoat/*/policy.json',
],
extends: [

@ -1,4 +1,5 @@
node_modules/**
lavamoat/*/policy.json
dist/**
builds/**
test-*/**

@ -0,0 +1,37 @@
// import { useGlobals } from '@storybook/api';
const { useGlobals } = require('@storybook/api')
const React = require("react")
const { addons, types } = require("@storybook/addons")
const { Icons, IconButton } = require('@storybook/components')
const localeList = require('../../app/_locales/index.json')
const { useEffect } = React
addons.register("i18n-party", () => {
addons.add("i18n-party", {
title: "rotates through every i18n locale",
//👇 Sets the type of UI element in Storybook
type: types.TOOL,
match: () => true,
render: (...args) => {
// https://github.com/storybookjs/storybook/blob/6490a0d646dbaa293b76bbde477daca615efe789/addons/toolbars/src/components/MenuToolbar.tsx#L2
const [globals, updateGlobals] = useGlobals()
useEffect(() => {
if (!globals.localeParty) return
const interval = setInterval((...args) => {
const currentIndex = localeList.findIndex(({ code }) => code === globals.locale)
const nextIndex = (currentIndex + 1) % localeList.length
const nextLocale = localeList[nextIndex].code
updateGlobals({ locale: nextLocale })
}, 2000)
return () => clearInterval(interval)
})
return (
<IconButton onClick={() => updateGlobals({ localeParty: !globals.localeParty })}>
<Icons icon={globals.localeParty ? 'star' : 'starhollow'}/>
</IconButton>
)
},
})
})

@ -9,6 +9,7 @@ module.exports = {
'@storybook/addon-actions',
'@storybook/addon-backgrounds',
'@storybook/addon-toolbars',
'./i18n-party-addon/register.js',
],
webpackFinal: async (config) => {
config.module.strictExportPresence = true

@ -1,22 +1,24 @@
import React from 'react'
import { addDecorator, addParameters } from '@storybook/react'
import { withKnobs } from '@storybook/addon-knobs'
import { Provider } from 'react-redux'
import configureStore from '../ui/app/store/store'
import '../ui/app/css/index.scss'
import localeList from '../app/_locales/index.json'
import * as allLocales from './locales'
import { I18nProvider, LegacyI18nProvider } from './i18n'
import React, { useEffect } from 'react';
import { addDecorator, addParameters } from '@storybook/react';
import { useGlobals } from '@storybook/api';
import { withKnobs } from '@storybook/addon-knobs';
import { Provider } from 'react-redux';
import configureStore from '../ui/app/store/store';
import '../ui/app/css/index.scss';
import localeList from '../app/_locales/index.json';
import * as allLocales from './locales';
import { I18nProvider, LegacyI18nProvider } from './i18n';
import testData from './test-data.js'
addParameters({
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#FFFFFF'},
{ name: 'light', value: '#FFFFFF' },
{ name: 'dark', value: '#333333' },
],
}
})
},
});
export const globalTypes = {
locale: {
@ -26,8 +28,8 @@ export const globalTypes = {
toolbar: {
icon: 'globe',
items: localeList.map(({ code, name }) => {
return { value: code, right: code, title: name }
})
return { value: code, right: code, title: name };
}),
},
},
};
@ -37,15 +39,13 @@ const styles = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}
};
const store = configureStore({
metamask: { metamask: { } },
})
const store = configureStore(testData)
const metamaskDecorator = (story, context) => {
const currentLocale = context.globals.locale
const current = allLocales[currentLocale]
const currentLocale = context.globals.locale;
const current = allLocales[currentLocale];
return (
<Provider store={store}>
<I18nProvider
@ -54,14 +54,12 @@ const metamaskDecorator = (story, context) => {
en={allLocales.en}
>
<LegacyI18nProvider>
<div style={styles}>
{ story() }
</div>
<div style={styles}>{story()}</div>
</LegacyI18nProvider>
</I18nProvider>
</Provider>
)
}
);
};
addDecorator(withKnobs)
addDecorator(metamaskDecorator)
addDecorator(withKnobs);
addDecorator(metamaskDecorator);

@ -0,0 +1,218 @@
import { TRANSACTION_STATUSES } from '../shared/constants/transaction';
const state = {
metamask: {
isInitialized: true,
isUnlocked: true,
featureFlags: { sendHexData: true },
rpcUrl: 'https://rawtestrpc.metamask.io/',
identities: {
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': {
address: '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825',
name: 'Send Account 1',
},
'0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb': {
address: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
name: 'Send Account 2',
},
'0x2f8d4a878cfa04a6e60d46362f5644deab66572d': {
address: '0x2f8d4a878cfa04a6e60d46362f5644deab66572d',
name: 'Send Account 3',
},
'0xd85a4b6a394794842887b8284293d69163007bbb': {
address: '0xd85a4b6a394794842887b8284293d69163007bbb',
name: 'Send Account 4',
},
},
cachedBalances: {},
currentBlockGasLimit: '0x4c1878',
currentCurrency: 'USD',
conversionRate: 1200.88200327,
conversionDate: 1489013762,
nativeCurrency: 'ETH',
frequentRpcList: [],
network: '3',
provider: {
type: 'ropsten',
chainId: '0x3',
},
accounts: {
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': {
code: '0x',
balance: '0x47c9d71831c76efe',
nonce: '0x1b',
address: '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825',
},
'0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb': {
code: '0x',
balance: '0x37452b1315889f80',
nonce: '0xa',
address: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
},
'0x2f8d4a878cfa04a6e60d46362f5644deab66572d': {
code: '0x',
balance: '0x30c9d71831c76efe',
nonce: '0x1c',
address: '0x2f8d4a878cfa04a6e60d46362f5644deab66572d',
},
'0xd85a4b6a394794842887b8284293d69163007bbb': {
code: '0x',
balance: '0x0',
nonce: '0x0',
address: '0xd85a4b6a394794842887b8284293d69163007bbb',
},
},
addressBook: {
'0x3': {
'0x06195827297c7a80a443b6894d3bdb8824b43896': {
address: '0x06195827297c7a80a443b6894d3bdb8824b43896',
name: 'Address Book Account 1',
chainId: '0x3',
},
},
},
tokens: [
{
address: '0x1a195821297c7a80a433b6894d3bdb8824b43896',
decimals: 18,
symbol: 'ABC',
},
{
address: '0x8d6b81208414189a58339873ab429b6c47ab92d3',
decimals: 4,
symbol: 'DEF',
},
{
address: '0xa42084c8d1d9a2198631988579bb36b48433a72b',
decimals: 18,
symbol: 'GHI',
},
],
transactions: {},
currentNetworkTxList: [
{
id: 'mockTokenTx1',
txParams: {
to: '0x8d6b81208414189a58339873ab429b6c47ab92d3',
from: '0xd85a4b6a394794842887b8284293d69163007bbb',
},
time: 1700000000000,
},
{
id: 'mockTokenTx2',
txParams: {
to: '0xafaketokenaddress',
from: '0xd85a4b6a394794842887b8284293d69163007bbb',
},
time: 1600000000000,
},
{
id: 'mockTokenTx3',
txParams: {
to: '0x8d6b81208414189a58339873ab429b6c47ab92d3',
from: '0xd85a4b6a394794842887b8284293d69163007bbb',
},
time: 1500000000000,
},
{
id: 'mockEthTx1',
txParams: {
to: '0xd85a4b6a394794842887b8284293d69163007bbb',
from: '0xd85a4b6a394794842887b8284293d69163007bbb',
},
time: 1400000000000,
},
],
unapprovedMsgs: {
'0xabc': { id: 'unapprovedMessage1', time: 1650000000000 },
'0xdef': { id: 'unapprovedMessage2', time: 1550000000000 },
'0xghi': { id: 'unapprovedMessage3', time: 1450000000000 },
},
unapprovedMsgCount: 0,
unapprovedPersonalMsgs: {},
unapprovedPersonalMsgCount: 0,
unapprovedDecryptMsgs: {},
unapprovedDecryptMsgCount: 0,
unapprovedEncryptionPublicKeyMsgs: {},
unapprovedEncryptionPublicKeyMsgCount: 0,
keyringTypes: ['Simple Key Pair', 'HD Key Tree'],
keyrings: [
{
type: 'HD Key Tree',
accounts: [
'fdea65c8e26263f6d9a1b5de9555d2931a33b825',
'c5b8dbac4c1d3f152cdeb400e2313f309c410acb',
'2f8d4a878cfa04a6e60d46362f5644deab66572d',
],
},
{
type: 'Simple Key Pair',
accounts: ['0xd85a4b6a394794842887b8284293d69163007bbb'],
},
],
selectedAddress: '0xd85a4b6a394794842887b8284293d69163007bbb',
send: {
gasLimit: '0xFFFF',
gasPrice: '0xaa',
gasTotal: '0xb451dc41b578',
tokenBalance: 3434,
from: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
to: '0x987fedabc',
amount: '0x080',
memo: '',
errors: {
someError: null,
},
maxModeOn: false,
editingTransactionId: 97531,
},
unapprovedTxs: {
4768706228115573: {
id: 4768706228115573,
time: 1487363153561,
status: TRANSACTION_STATUSES.UNAPPROVED,
gasMultiplier: 1,
metamaskNetworkId: '3',
txParams: {
from: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
to: '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761',
value: '0xde0b6b3a7640000',
metamaskId: 4768706228115573,
metamaskNetworkId: '3',
gas: '0x5209',
},
txFee: '17e0186e60800',
txValue: 'de0b6b3a7640000',
maxCost: 'de234b52e4a0800',
gasPrice: '4a817c800',
},
},
currentLocale: 'en',
},
appState: {
menuOpen: false,
currentView: {
name: 'accountDetail',
detailView: null,
context: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
},
accountDetail: {
subview: 'transactions',
},
modal: {
modalState: {},
previousModalState: {},
},
isLoading: false,
warning: null,
scrollToBottom: false,
forgottenPassword: null,
},
send: {
fromDropdownOpen: false,
toDropdownOpen: false,
errors: { someError: null },
},
};
export default state;

@ -2,6 +2,41 @@
## Current Develop Branch
## 9.1.0 Mon Mar 01 2021
- [#10265](https://github.com/MetaMask/metamask-extension/pull/10265): Update Japanese translations.
- [#9388](https://github.com/MetaMask/metamask-extension/pull/9388): Update Chinese(Simplified) translations.
- [#10270](https://github.com/MetaMask/metamask-extension/pull/10270): Update Vietnamese translations.
- [#10258](https://github.com/MetaMask/metamask-extension/pull/10258): Update Spanish and Spanish(Latin American and Carribean) translations.
- [#10268](https://github.com/MetaMask/metamask-extension/pull/10268): Update Russian translations.
- [#10269](https://github.com/MetaMask/metamask-extension/pull/10269): Update Tagalog localized messages.
- [#10448](https://github.com/MetaMask/metamask-extension/pull/10448): Fix 'imported' translation use case for Dutch.
- [#10391](https://github.com/MetaMask/metamask-extension/pull/10391): Use translated transaction category for confirmations.
- [#10357](https://github.com/MetaMask/metamask-extension/pull/10357): Cancel unapproved confirmations on network change
- [#10413](https://github.com/MetaMask/metamask-extension/pull/10413): Use native currency in asset row.
- [#10421](https://github.com/MetaMask/metamask-extension/pull/10421): Fix color indicator size on connected site indicator.
- [#10423](https://github.com/MetaMask/metamask-extension/pull/10423): Fix multiple notification window prompts.
- [#10424](https://github.com/MetaMask/metamask-extension/pull/10424): Fix icons on token options menu.
- [#10414](https://github.com/MetaMask/metamask-extension/pull/10414): Fix token fiat conversion rates when switching from certain custom networks.
- [#10453](https://github.com/MetaMask/metamask-extension/pull/10453): Disable BUY button from home screen when not on Ethereum Mainnet.
- [#10465](https://github.com/MetaMask/metamask-extension/pull/10465): Fixes gas selection check mark on the notification view.
- [#10467](https://github.com/MetaMask/metamask-extension/pull/10467): Fix confirm page header with from/to addresses in fullscreen for tx confirmations.
- [#10455](https://github.com/MetaMask/metamask-extension/pull/10455): Hide links to etherscan when no block explorer is specified for a custom network for notifications.
- [#10456](https://github.com/MetaMask/metamask-extension/pull/10456): Fix swap insufficient balance error message.
- [#10350](https://github.com/MetaMask/metamask-extension/pull/10350): Fix encypt/decrypt tx queueing.
- [#10473](https://github.com/MetaMask/metamask-extension/pull/10473): Improve autofocus in the add network form.
- [#10444](https://github.com/MetaMask/metamask-extension/pull/10444): Use eth_gasprice for tx gas price estimation on non-Mainnet networks.
- [#10477](https://github.com/MetaMask/metamask-extension/pull/10477): Fix accountsChanged event not triggering when manually connecting.
- [#10471](https://github.com/MetaMask/metamask-extension/pull/10471): Fix navigation from jumping vertically when clicking into token.
- [#9724](https://github.com/MetaMask/metamask-extension/pull/9724): Add custom network RPC method.
- [#10496](https://github.com/MetaMask/metamask-extension/pull/10496): Eliminate artificial delay in swaps loading screen after request loading is complete.
- [#10501](https://github.com/MetaMask/metamask-extension/pull/10501): Ensure that swap approve tx and swap tx always have the same gas price.
- [#10485](https://github.com/MetaMask/metamask-extension/pull/10485): Fixes signTypedData message overflow.
- [#10525](https://github.com/MetaMask/metamask-extension/pull/10525): Update swaps failure message to include a support link.
- [#10521](https://github.com/MetaMask/metamask-extension/pull/10521): Accommodate for 0 sources verifying swap token
- [#10530](https://github.com/MetaMask/metamask-extension/pull/10530): Show warnings on Add Recipient page of Send flow
- [#9187](https://github.com/MetaMask/metamask-extension/pull/9187): Warn users when an ENS name contains 'confusable' characters
- [#10507](https://github.com/MetaMask/metamask-extension/pull/10507): Fixes ENS IPFS resolution on custom networks with the chainID of 1.
## 9.0.5 Mon Feb 08 2021
- [#10278](https://github.com/MetaMask/metamask-extension/pull/10278): Allow editing transaction amount after clicking max
- [#10214](https://github.com/MetaMask/metamask-extension/pull/10214): Standardize size, shape and color of network color indicators
@ -19,6 +54,7 @@
## 9.0.4 Fri Jan 22 2021
- [#10285](https://github.com/MetaMask/metamask-extension/pull/10285): Update @metamask/contract-metadata from v1.21.0 to 1.22.0
- [#10264](https://github.com/MetaMask/metamask-extension/pull/10264): Update `hi` localized messages
- [#10174](https://github.com/MetaMask/metamask-extension/pull/10174): Move fox to bottom of 'About' page
- [#10198](https://github.com/MetaMask/metamask-extension/pull/10198): Fix hardware account selection
- [#10101](https://github.com/MetaMask/metamask-extension/pull/10101): Add a timeout to all network requests

@ -9,7 +9,7 @@ For [general questions](https://metamask.zendesk.com/hc/en-us/community/topics/3
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) or [Medium](https://medium.com/metamask) pages.
To learn how to develop MetaMask-compatible applications, visit our [Developer Docs](https://metamask.github.io/metamask-docs/).
@ -25,24 +25,49 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D
- Replace the `INFURA_PROJECT_ID` value with your own personal [Infura Project ID](https://infura.io/docs).
- If debugging MetaMetrics, you'll need to add a value for `SEGMENT_WRITE_KEY` [Segment write key](https://segment.com/docs/connections/find-writekey/).
- Build the project to the `./dist/` folder with `yarn dist`.
- Optionally, to start a development build (e.g. with logging and file watching) run `yarn start` instead.
- To start the [React DevTools](https://github.com/facebook/react-devtools) and [Redux DevTools Extension](http://extension.remotedev.io)
alongside the app, use `yarn start:dev`.
- React DevTools will open in a separate window; no browser extension is required
- Redux DevTools will need to be installed as a browser extension. Open the Redux Remote Devtools to access Redux state logs. This can be done by either right clicking within the web browser to bring up the context menu, expanding the Redux DevTools panel and clicking Open Remote DevTools OR clicking the Redux DevTools extension icon and clicking Open Remote DevTools.
- You will also need to check the "Use custom (local) server" checkbox in the Remote DevTools Settings, using the default server configuration (host `localhost`, port `8000`, secure connection checkbox unchecked)
Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built.
## Contributing
### Running Tests
### Development builds
Run tests with `yarn test`.
To start a development build (e.g. with logging and file watching) run `yarn start`.
You can also test with a continuously watching process, via `yarn watch`.
To start the [React DevTools](https://github.com/facebook/react-devtools) and [Redux DevTools Extension](http://extension.remotedev.io)
alongside the app, use `yarn start:dev`.
- React DevTools will open in a separate window; no browser extension is required
- Redux DevTools will need to be installed as a browser extension. Open the Redux Remote Devtools to access Redux state logs. This can be done by either right clicking within the web browser to bring up the context menu, expanding the Redux DevTools panel and clicking Open Remote DevTools OR clicking the Redux DevTools extension icon and clicking Open Remote DevTools.
- You will also need to check the "Use custom (local) server" checkbox in the Remote DevTools Settings, using the default server configuration (host `localhost`, port `8000`, secure connection checkbox unchecked)
You can run the linter by itself with `yarn lint`.
### Running Unit Tests and Linting
Run unit tests and the linter with `yarn test`.
To run just unit tests, run `yarn test:unit`. To run unit tests continuously with a file watcher, run `yarn watch`.
You can run the linter by itself with `yarn lint`, and you can automatically fix some lint problems with `yarn lint:fix`. You can also run these two commands just on your local changes to save time with `yarn lint:changed` and `yarn lint:changed:fix` respectively.
### Running E2E Tests
Our e2e test suite can be run on either Firefox or Chrome. In either case, start by creating a test build by running `yarn build:test`.
Firefox e2e tests can be run with `yarn test:e2e:firefox`.
Chrome e2e tests can be run with `yarn test:e2e:chrome`, but they will only work if you have Chrome v79 installed. Update the `chromedriver` package to a version matching your local Chrome installation to run e2e tests on newer Chrome versions.
### Changing dependencies
Whenever you change dependencies (adding, removing, or updating, either in `package.json` or `yarn.lock`), there are various files that must be kept up-to-date.
* `yarn.lock`:
* Run `yarn setup` again after your changes to ensure `yarn.lock` has been properly updated.
* The `allow-scripts` configuration in `package.json`
* Run `yarn allow-scripts auto` to update the `allow-scripts` configuration automatically. This config determines whether the package's install/postinstall scripts are allowed to run. Review each new package to determine whether the install script needs to run or not, testing if necessary.
* Unfortunately, `yarn allow-scripts auto` will behave inconsistently on different platforms. macOS and Windows users may see extraneous changes relating to optional dependencies.
* The LavaMoat auto-generated policy in `lavamoat/node/policy.json`
* Run `yarn lavamoat:auto` to re-generate this policy file. Review the changes to determine whether the access granted to each package seems appropriate.
* Unfortunately, `yarn lavamoat:auto` will behave inconsistently on different platforms. macOS and Windows users may see extraneous changes relating to optional dependencies.
## Architecture

@ -928,18 +928,12 @@
"selectAnAccountHelp": {
"message": "መለያውን በ MetaMask ለማየት ይምረጡ"
},
"selectCurrency": {
"message": "የገንዘብ ዓይነት ይምረጡ"
},
"selectEachPhrase": {
"message": "እባክዎ እያንዳንዱን ሐረግ በመምረጥ ትክክለኛነቱን ያረጋግጡ።"
},
"selectHdPath": {
"message": "የ HD ዱካ ይምረጡ"
},
"selectLocale": {
"message": "አካባቢ ይምረጡ"
},
"selectPathHelp": {
"message": "ነባር የሌጄር መለያዎችን ከታች ካላዩ፣ ዱካዎችን ወደ \"Legacy (MEW / MyCrypto)\" ለመቀየር ይሞክሩ"
},

@ -924,18 +924,12 @@
"selectAnAccountHelp": {
"message": "حدد الحساب لعرضه في MetaMask"
},
"selectCurrency": {
"message": "تحديد العملة"
},
"selectEachPhrase": {
"message": ُرجى تحديد كل عبارة للتأكد من صحتها."
},
"selectHdPath": {
"message": "حدد مسار HD "
},
"selectLocale": {
"message": "تحديد الموقع المحلي"
},
"selectPathHelp": {
"message": "إذا لم تشاهد حسابات Ledger الحالية الخاصة بك أدناه، فحاول تبديل المسارات \"Legacy (MEW / MyCrypto)\""
},

@ -927,18 +927,12 @@
"selectAnAccountHelp": {
"message": "Изберете акаунта за преглед в MetaMask"
},
"selectCurrency": {
"message": "Изберете валута"
},
"selectEachPhrase": {
"message": "Моля, изберете всяка фраза, за да се уверите, че е правилна."
},
"selectHdPath": {
"message": "Изберете HD Path"
},
"selectLocale": {
"message": "Изберете местоположение"
},
"selectPathHelp": {
"message": "Ако не виждате съществуващите си акаунти в Ledger по-долу, опитайте да превключите пътеки към „Legacy (MEW / MyCrypto)“"
},

@ -931,18 +931,12 @@
"selectAnAccountHelp": {
"message": "MetaMask এ দখতউনটটিিচন করন"
},
"selectCurrency": {
"message": "কিিচন করন"
},
"selectEachPhrase": {
"message": "ফজগি সঠিক তিিত করত অনরহ কররতিিজ নিচন করন।"
},
"selectHdPath": {
"message": "HD পথ নিচন করন"
},
"selectLocale": {
"message": "সয় নিচন করন"
},
"selectPathHelp": {
"message": "আপনিি আপনর বিযমন লর বস নখল, \"Legacy (MEW / MyCrypto)\" পথগিইচ করর চ করন "
},

@ -909,18 +909,12 @@
"selectAnAccountHelp": {
"message": "Selecciona el compte que vols veure a MetaMask"
},
"selectCurrency": {
"message": "Selecciona divisa"
},
"selectEachPhrase": {
"message": "Si us plau selecciona cada frase per a assegurar-te que és correcta."
},
"selectHdPath": {
"message": "Selecciona Ruta HD"
},
"selectLocale": {
"message": "Selecciona Configuració Regional"
},
"selectPathHelp": {
"message": "Si no veus els teus comptes Ledger a sota, prova canviant la ruta a \"Legacy (MEW / MyCrypto)\""
},

@ -350,9 +350,6 @@
"seedPhraseReq": {
"message": "klíčové fráze mají 12 slov"
},
"selectCurrency": {
"message": "Vybrat měnu"
},
"selectType": {
"message": "Vybrat typ"
},

@ -912,18 +912,12 @@
"selectAnAccountHelp": {
"message": "Vælg kontoen der skal vises i MetaMask"
},
"selectCurrency": {
"message": "Vælg valuta"
},
"selectEachPhrase": {
"message": "Vælg venligst hver sætning, for at sikre at de er korrekte."
},
"selectHdPath": {
"message": "Vælg HD-Sti"
},
"selectLocale": {
"message": "Vælg Lokalitet"
},
"selectPathHelp": {
"message": "Hvis du ikke ser dine eksisterende Ledger-konti nedenfor, kan du prøve at skifte til \"Legacy (MEW / MyCrypto)\""
},

@ -900,18 +900,12 @@
"selectAnAccountHelp": {
"message": "Wählen Sie das Konto aus, das in MetaMask angezeigt werden soll."
},
"selectCurrency": {
"message": "Währung auswählen"
},
"selectEachPhrase": {
"message": "Bitte wählen Sie jede Phrase aus, um sicherzustellen, dass sie korrekt ist."
},
"selectHdPath": {
"message": "HD-Pfad auswählen"
},
"selectLocale": {
"message": "Gebietsschema auswählen"
},
"selectPathHelp": {
"message": "Wenn Sie Ihre bestehenden Ledger-Konten nachfolgend nicht sehen, versuchen Sie, die Pfade zu \"Legacy (MEW / MyCrypto)\" zu ändern"
},

@ -928,18 +928,12 @@
"selectAnAccountHelp": {
"message": "Επιλέξτε τον λογαριασμό για προβολή στο MetaMask"
},
"selectCurrency": {
"message": "Επιλέξτε Νόμισμα"
},
"selectEachPhrase": {
"message": "Παρακαλούμε επιλέξτε κάθε φράση, για να βεβαιωθείτε ότι είναι σωστή."
},
"selectHdPath": {
"message": "Επιλέξτε Διαδρομή HD"
},
"selectLocale": {
"message": "Επιλέξτε Τοπικές Ρυθμίσεις"
},
"selectPathHelp": {
"message": "Εάν δεν βλέπετε τους υπάρχοντες λογαριασμούς σας Καθολικού παρακάτω, προσπαθήστε να αλλάξετε τις διαδρομές σε \"Legacy (MEW / MyCrypto)\""
},

@ -52,6 +52,23 @@
"addAlias": {
"message": "Add alias"
},
"addEthereumChainConfirmationDescription": {
"message": "This will allow this network to be used within MetaMask."
},
"addEthereumChainConfirmationRisks": {
"message": "MetaMask does not verify custom networks."
},
"addEthereumChainConfirmationRisksLearnMore": {
"message": "Learn about $1.",
"description": "$1 is a link with text that is provided by the 'addEthereumChainConfirmationRisksLearnMoreLink' key"
},
"addEthereumChainConfirmationRisksLearnMoreLink": {
"message": "scams and network security risks",
"description": "Link text for the 'addEthereumChainConfirmationRisksLearnMore' translation key"
},
"addEthereumChainConfirmationTitle": {
"message": "Allow this site to add a network?"
},
"addNetwork": {
"message": "Add Network"
},
@ -150,6 +167,9 @@
"approve": {
"message": "Approve spend limit"
},
"approveButtonText": {
"message": "Approve"
},
"approveSpendLimit": {
"message": "Approve $1 spend limit",
"description": "The token symbol that is being approved"
@ -212,7 +232,10 @@
"message": "Basic"
},
"blockExplorerUrl": {
"message": "Block Explorer"
"message": "Block Explorer URL"
},
"blockExplorerUrlDefinition": {
"message": "The URL used as the block explorer for this network."
},
"blockExplorerView": {
"message": "View account at $1",
@ -254,6 +277,9 @@
"chainId": {
"message": "Chain ID"
},
"chainIdDefinition": {
"message": "The chain ID used to sign transactions for this network."
},
"chromeRequiredForHardwareWallets": {
"message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet."
},
@ -275,6 +301,15 @@
"confirmed": {
"message": "Confirmed"
},
"confusableUnicode": {
"message": "'$1' is similar to '$2'."
},
"confusableZeroWidthUnicode": {
"message": "Zero-width character found."
},
"confusingEnsDomain": {
"message": "We have detected a confusable character in the ENS name. Check the ENS name to avoid a potential scam."
},
"congratulations": {
"message": "Congratulations"
},
@ -409,6 +444,12 @@
"currencyConversion": {
"message": "Currency Conversion"
},
"currencySymbol": {
"message": "Currency Symbol"
},
"currencySymbolDefinition": {
"message": "The ticker symbol displayed for this network’s currency."
},
"currentAccountNotConnected": {
"message": "Your current account is not connected"
},
@ -1005,6 +1046,14 @@
"metametricsOptInDescription": {
"message": "MetaMask would like to gather usage data to better understand how our users interact with the extension. This data will be used to continually improve the usability and user experience of our product and the Ethereum ecosystem."
},
"mismatchedChain": {
"message": "This network details for this Chain ID do not match our records. We recommend that you $1 before proceeding.",
"description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key"
},
"mismatchedChainLinkText": {
"message": "verify the network details",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
"mobileSyncText": {
"message": "Please enter your password to confirm it's you!"
},
@ -1030,15 +1079,27 @@
"negativeETH": {
"message": "Can not send negative amounts of ETH."
},
"networkDetails": {
"message": "Network Details"
},
"networkName": {
"message": "Network Name"
},
"networkNameDefinition": {
"message": "The name associated with this network."
},
"networkSettingsChainIdDescription": {
"message": "The chain ID is used for signing transactions. It must match the chain ID returned by the network. You can enter a decimal or '0x'-prefixed hexadecimal number, but we will display the number in decimal."
},
"networkSettingsDescription": {
"message": "Add and edit custom RPC networks"
},
"networkURL": {
"message": "Network URL"
},
"networkURLDefinition": {
"message": "The URL used to access this network."
},
"networks": {
"message": "Networks"
},
@ -1418,18 +1479,12 @@
"selectAnAccountHelp": {
"message": "Select the account to view in MetaMask"
},
"selectCurrency": {
"message": "Select Currency"
},
"selectEachPhrase": {
"message": "Please select each phrase in order to make sure it is correct."
},
"selectHdPath": {
"message": "Select HD Path"
},
"selectLocale": {
"message": "Select Locale"
},
"selectPathHelp": {
"message": "If you don't see your existing Ledger accounts below, try switching paths to \"Legacy (MEW / MyCrypto)\""
},
@ -1657,8 +1712,9 @@
"swapEstimatedNetworkFeesInfo": {
"message": "This is an estimate of the network fee that will be used to complete your swap. The actual amount may change according to network conditions."
},
"swapFailedErrorDescription": {
"message": "Your funds are safe and still available in your wallet."
"swapFailedErrorDescriptionWithSupportLink": {
"message": "Transaction failures happen and we are here to help. If this issue persists, you can reach our customer support at $1 for further assistance.",
"description": "This message is shown to a user if their swap fails. The $1 will be replaced by support.metamask.io"
},
"swapFailedErrorTitle": {
"message": "Swap failed"
@ -1841,6 +1897,10 @@
"message": "Your $1 has been added to your account.",
"description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol."
},
"swapTokenBalanceUnavailable": {
"message": "We were unable to retrieve your $1 balance",
"description": "This message communicates to the user that their balance of a given token is currently unavailable. $1 will be replaced by a token symbol"
},
"swapTokenToToken": {
"message": "Swap $1 to $2",
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
@ -1849,6 +1909,9 @@
"message": "Always confirm the token address on $1.",
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
},
"swapTokenVerificationNoSource": {
"message": "This token has not been verified."
},
"swapTokenVerificationOnlyOneSource": {
"message": "Only verified on 1 source."
},
@ -1894,12 +1957,24 @@
"swapsViewInActivity": {
"message": "View in activity"
},
"switchEthereumChainConfirmationDescription": {
"message": "This will switch the selected network within MetaMask to a previously added network:"
},
"switchEthereumChainConfirmationTitle": {
"message": "Allow this site to switch the network?"
},
"switchNetwork": {
"message": "Switch network"
},
"switchNetworks": {
"message": "Switch Networks"
},
"switchToThisAccount": {
"message": "Switch to this account"
},
"switchingNetworksCancelsPendingConfirmations": {
"message": "Switching networks will cancel all pending confirmations"
},
"symbol": {
"message": "Symbol"
},
@ -2075,6 +2150,14 @@
"unlockMessage": {
"message": "The decentralized web awaits"
},
"unrecognizedChain": {
"message": "This custom network is not recognized. We recommend that you $1 before proceeding",
"description": "$1 is a clickable link with text defined by the 'unrecognizedChanLinkText' key. The link will open to instructions for users to validate custom network details."
},
"unrecognizedChainLinkText": {
"message": "verify the network details",
"description": "Serves as link text for the 'unrecognizedChain' key. This text will be embedded inside the translation for that key."
},
"updatedWithDate": {
"message": "Updated $1"
},
@ -2147,6 +2230,10 @@
"message": "$1 of $2",
"description": "$1 and $2 are intended to be two numbers, where $2 is a total, and $1 is a count towards that total"
},
"xOfYPending": {
"message": "$1 of $2 pending",
"description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total"
},
"yesLetsTry": {
"message": "Yes, let's try"
},

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -921,18 +921,12 @@
"selectAnAccountHelp": {
"message": "Valige konto, mida MetaMaskis vaadata"
},
"selectCurrency": {
"message": "Valitud valuuta"
},
"selectEachPhrase": {
"message": "Valige iga fraas, veendumaks, et see on õige."
},
"selectHdPath": {
"message": "Valige HD-teekond"
},
"selectLocale": {
"message": "Valige Lokaat"
},
"selectPathHelp": {
"message": "Kui te ei näe allpool oma olemasolevaid pearaamatu kontosid, vahetage tee kohta \"Legacy (MEW/MyCrypto)\""
},

@ -931,18 +931,12 @@
"selectAnAccountHelp": {
"message": "برای مشاهده MetaMask حساب را انتخاب نمایید"
},
"selectCurrency": {
"message": "واحد پول را انتخاب کنید"
},
"selectEachPhrase": {
"message": "لطفًا جهت اطمینان از درستی، هر عبارت را انتخاب کنید."
},
"selectHdPath": {
"message": "انتخاب مسیر HD"
},
"selectLocale": {
"message": "محل را انتخاب کنید"
},
"selectPathHelp": {
"message": "در صورتیکه شما حساب های موجود دفتر حساب را در ذیل نمیبینید، مسیر ها به \"Legacy (MEW / MyCrypto)\"  تغییر دهید"
},

@ -928,18 +928,12 @@
"selectAnAccountHelp": {
"message": "Valitse MetaMaskissa näytettävä tili"
},
"selectCurrency": {
"message": "Valitse valuutta"
},
"selectEachPhrase": {
"message": "Ole hyvä ja valitse jokainen teksti järjestyksessä varmistaaksesi, että se on oikein."
},
"selectHdPath": {
"message": "Valitse HD-polku"
},
"selectLocale": {
"message": "Valitse kielialue"
},
"selectPathHelp": {
"message": "Jos et näe nykyisiä Ledger-tilejäsi tämän alla, koeta vaihtaa poluksi ”Vanha (MEW / MyCrypto)”"
},

@ -843,18 +843,12 @@
"selectAnAccountHelp": {
"message": "Piliin ang account na titingnan sa MetaMask"
},
"selectCurrency": {
"message": "Pumili ng Currency"
},
"selectEachPhrase": {
"message": "Pakipili ang bawat parirala para tiyaking tama ito."
},
"selectHdPath": {
"message": "Piliin ang HD Path"
},
"selectLocale": {
"message": "Pumili ng Locale"
},
"selectPathHelp": {
"message": "Kung hindi mo makita ang kasalukuyan mong mga Ledger account sa ibaba, subukang ilipat ang mga path sa \"Legacy (MEW / MyCrypto)\""
},

@ -913,18 +913,12 @@
"selectAnAccountHelp": {
"message": "Selectionner le compte à afficher dans MetaMask"
},
"selectCurrency": {
"message": "Selectionner Devise"
},
"selectEachPhrase": {
"message": "Veuillez sélectionner chaque phrase afin de vous assurer qu'elle est correcte."
},
"selectHdPath": {
"message": "Selectioner le \"Path HD\""
},
"selectLocale": {
"message": "Selectionner la localisation"
},
"selectPathHelp": {
"message": "Si vos comptes Ledger n'apparaissent pas ci-dessous, essayez de selectionner le path \"Legacy (MEW / MyCrypto)\""
},

@ -925,18 +925,12 @@
"selectAnAccountHelp": {
"message": "בחר את החשבון לצפייה ב- MetaMask"
},
"selectCurrency": {
"message": "בחר מטבע"
},
"selectEachPhrase": {
"message": "נא לבחור כל צירוף מילים כדי להבטיח שהוא נכון."
},
"selectHdPath": {
"message": "בחר/י נתיב HD"
},
"selectLocale": {
"message": "בחר/י מיקום"
},
"selectPathHelp": {
"message": "אם אינך רואה את חשבונות ה-Ledger הקיימים שלך בהמשך, נסה/י להחליף נתיב ל-Legacy (MEW / MyCrypto)"
},

@ -1403,18 +1403,12 @@
"selectAnAccountHelp": {
"message": "MetaMask मखनिए ख चयन कर"
},
"selectCurrency": {
"message": "म चयन कर"
},
"selectEachPhrase": {
"message": "कपयरतक वश क चयन कर, ति यह सिित ह सकि यह सह।"
},
"selectHdPath": {
"message": "HD पथ क चयन कर"
},
"selectLocale": {
"message": "लल क चयन कर"
},
"selectPathHelp": {
"message": "यदि आपक अपनजर ख नहिई द, त पथ क \"Legacy (MEW / MyCrypto)\" पर सिच करनरयस कर"
},
@ -1639,9 +1633,6 @@
"swapEstimatedNetworkFeesInfo": {
"message": "यह नटवरक शक क एक अनन ह, जि आपकप क करनिए उपयग किएग। नटवरक किि अनर वतविक रि बदल सकत।"
},
"swapFailedErrorDescription": {
"message": "आपक धन सरकित ह और अभ आपकट म उपलबध ह।"
},
"swapFailedErrorTitle": {
"message": "सप विफल रह"
},

@ -330,9 +330,6 @@
"seedPhraseReq": {
"message": "बज वश 12 शबद ल"
},
"selectCurrency": {
"message": "म"
},
"selectType": {
"message": "परकर च"
},

@ -924,18 +924,12 @@
"selectAnAccountHelp": {
"message": "Odaberi račun za prikaz u usluzi MetaMask"
},
"selectCurrency": {
"message": "Odaberite valutu"
},
"selectEachPhrase": {
"message": "Odaberite svaku rečenicu kako biste provjerili je li točna."
},
"selectHdPath": {
"message": "Odaberi put HD-a"
},
"selectLocale": {
"message": "Odaberi lokalnu postavku"
},
"selectPathHelp": {
"message": "Ako ne vidite u nastavku postojeće račune Ledger, pokušajte promijeniti puteve na „Nasljeđe (MEW / MyCrypto)”"
},

@ -573,15 +573,9 @@
"selectAnAccountHelp": {
"message": "Chwazi kont pou wè nan MetaMask"
},
"selectCurrency": {
"message": "Chwazi Lajan"
},
"selectHdPath": {
"message": "Chwazi chemen HD"
},
"selectLocale": {
"message": "Chwazi Lokasyon"
},
"selectPathHelp": {
"message": "Si ou pa wè kont Ledger ou te genyen an anba a, eseye chanje chemen an \"Eritaj (MEW / MyCrypto)\""
},

@ -924,18 +924,12 @@
"selectAnAccountHelp": {
"message": "Válássza ki a MetaMask-ban megtekinteni kívánt fiókot"
},
"selectCurrency": {
"message": "Válasszon valutát"
},
"selectEachPhrase": {
"message": "Kérjük, válassza ki az egyes mondatokat, hogy meggyőződjön azok helyességéről. "
},
"selectHdPath": {
"message": "Válassz HD elérési útvonalat"
},
"selectLocale": {
"message": "Válaszd ki a területi beállítást"
},
"selectPathHelp": {
"message": "Ha nem láthatók alább Ledger számláid, próbáld kicserélni az elérési útvonalat: „Legacy (MEW / MyCypto)”"
},

@ -1403,18 +1403,12 @@
"selectAnAccountHelp": {
"message": "Pilih akun untuk dilihat di MetaMask"
},
"selectCurrency": {
"message": "Pilih Mata Uang"
},
"selectEachPhrase": {
"message": "Pilih masing-masing frasa untuk memastikan kebenarannya."
},
"selectHdPath": {
"message": "Pilih Jalur HD"
},
"selectLocale": {
"message": "Pilih Lokal"
},
"selectPathHelp": {
"message": "Jika Anda tidak melihat akun Ledger Anda yang ada di bawah, coba untuk beralih jalur ke \"Warisan (MEW / MyCrypto)\""
},
@ -1639,9 +1633,6 @@
"swapEstimatedNetworkFeesInfo": {
"message": "Ini adalah perkiraan biaya jaringan yang akan digunakan untuk menyelesaikan penukaran Anda. Jumlah aktual dapat berubah sesuai dengan kondisi jaringan."
},
"swapFailedErrorDescription": {
"message": "Dana Anda aman dan masih tersedia di dompet Anda."
},
"swapFailedErrorTitle": {
"message": "Penukaran gagal"
},

@ -50,6 +50,7 @@
{ "code": "ta", "name": "தமி" },
{ "code": "te", "name": "త" },
{ "code": "th", "name": "ไทย" },
{ "code": "tl", "name": "Wikang Tagalog" },
{ "code": "tr", "name": "Türkçe" },
{ "code": "uk", "name": "Українська мова" },
{ "code": "vi", "name": "Tiếng Việt" },

@ -1294,18 +1294,12 @@
"selectAnAccountHelp": {
"message": "Selezione l'account da visualizzare in MetaMask"
},
"selectCurrency": {
"message": "Seleziona Moneta"
},
"selectEachPhrase": {
"message": "Per favore seleziona ogni frase in ordine per assicurarti che sia corretta."
},
"selectHdPath": {
"message": "Seleziona Percorso HD"
},
"selectLocale": {
"message": "Selezione Lingua"
},
"selectPathHelp": {
"message": "Se non vedi il tuo account Ledger esistente di seguito, prova a cambiare il percorso in \"Legacy (MEW / MyCrypto)\""
},

File diff suppressed because it is too large Load Diff

@ -931,18 +931,12 @@
"selectAnAccountHelp": {
"message": "MetaMask ನಲಿಿಸಲಯನ ಆಯಿ"
},
"selectCurrency": {
"message": "ಕರಿಯನ ಆಯಿ"
},
"selectEachPhrase": {
"message": "ಅದ ಸರಿಿದನ ಖಚಿತಪಡಿಿಳಲ ದಯವಿರತಿ ಅನ ಆಯಿ."
},
"selectHdPath": {
"message": "HD ಪ ಆಯಿ"
},
"selectLocale": {
"message": "ಪರದಶವನ ಆಯಿ"
},
"selectPathHelp": {
"message": "ನಿಮ ಅಸಿವದಲಿವ ಲಜರಗಳನಳಗಡದಿದರ, ಪಗಳನ \"ಲಗಸಿ (MEW / MyCrypto)\" ಗ ಬದಲಿಸಲರಯತಿಿ"
},

@ -1403,18 +1403,12 @@
"selectAnAccountHelp": {
"message": "MetaMask에서 확인할 계정 선택"
},
"selectCurrency": {
"message": "통화 선택"
},
"selectEachPhrase": {
"message": "각 구문을 선택하여 구문이 올바른지 확인하세요."
},
"selectHdPath": {
"message": "HD 경로 선택"
},
"selectLocale": {
"message": "로캘 선택"
},
"selectPathHelp": {
"message": "아래에 기존 Ledger 계정이 표시되지 않는다면 경로를 \"Legacy(MEW / MyCrypto)\"로 변경해 보세요."
},
@ -1639,9 +1633,6 @@
"swapEstimatedNetworkFeesInfo": {
"message": "스왑을 완료하는 데 사용할 네트워크 요금 예상치입니다. 실제 금액은 네트워크 조건에 따라 달라질 수 있습니다."
},
"swapFailedErrorDescription": {
"message": "자금은 안전하며 지갑에서 계속 사용할 수 있습니다."
},
"swapFailedErrorTitle": {
"message": "스왑 실패"
},

@ -931,18 +931,12 @@
"selectAnAccountHelp": {
"message": "Pasirinkite paskyrą peržiūrėti „MetaMask“"
},
"selectCurrency": {
"message": "Pasirinkite valiutą"
},
"selectEachPhrase": {
"message": "Pasirinkite kiekvieną frazę, kad patikrintumėte, ar ji tinkama"
},
"selectHdPath": {
"message": "Pasirinkite HD kelią"
},
"selectLocale": {
"message": "Pasirinkite lokalę"
},
"selectPathHelp": {
"message": "Jeigu toliau nematote savo esamų operacijų sąskaitų, mėginkite pakeisti kelius į „Legacy (MEW / MyCrypto)“"
},

@ -927,18 +927,12 @@
"selectAnAccountHelp": {
"message": "Atlasiet kontu, ko skatīt MetaMask"
},
"selectCurrency": {
"message": "Atlasiet valūtu"
},
"selectEachPhrase": {
"message": "Lūdzu, atlasiet katru frāzi, lai pārliecinātos par tās pareizību."
},
"selectHdPath": {
"message": "Atlasīt HD ceļu"
},
"selectLocale": {
"message": "Izvēlieties lokalizāciju"
},
"selectPathHelp": {
"message": "Ja zemāk neredzat savus esošos žurnāla kontus, mēģiniet pārslēgt ceļus uz \"Legacy (MEW / MyCrypto)\""
},

@ -908,18 +908,12 @@
"selectAnAccountHelp": {
"message": "Pilih akaun untuk dilihat dalam MetaMask"
},
"selectCurrency": {
"message": "Pilih Mata Wang"
},
"selectEachPhrase": {
"message": "Sila pilih setiap ungkapan untuk memastikan ia betul."
},
"selectHdPath": {
"message": "Pilih Laluan HD"
},
"selectLocale": {
"message": "Pilih Penempatan"
},
"selectPathHelp": {
"message": "Jika anda tidak melihat akaun Ledger sedia ada anda di bawah, cuba tukar laluan kepada \"Legacy (MEW / MyCrypto)\""
},

@ -56,7 +56,7 @@
"message": "Contractimplementatie"
},
"copiedExclamation": {
"message": "Gekopieerde!"
"message": "Gekopieerd!"
},
"copyPrivateKey": {
"message": "Dit is uw privésleutel (klik om te kopiëren)"
@ -163,7 +163,7 @@
"message": " Geïmporteerde accounts worden niet gekoppeld aan de seedphrase van uw oorspronkelijk gemaakte MetaMask-account. Meer informatie over geïmporteerde accounts"
},
"imported": {
"message": "geïmporteerde",
"message": "geïmporteerd",
"description": "status showing that an account has been fully loaded into the keyring"
},
"infoHelp": {
@ -317,9 +317,6 @@
"seedPhraseReq": {
"message": "Back-up woorden zijn 12 woorden lang"
},
"selectCurrency": {
"message": "selecteer valuta"
},
"selectType": {
"message": "Selecteer type"
},

@ -915,18 +915,12 @@
"selectAnAccountHelp": {
"message": "Velg konto for å vise i MetaMask"
},
"selectCurrency": {
"message": "Velg valuta"
},
"selectEachPhrase": {
"message": "Velg hver frase for å kontrollere at den er riktig."
},
"selectHdPath": {
"message": "Velg HD-bane"
},
"selectLocale": {
"message": "Velg plassering"
},
"selectPathHelp": {
"message": "Hvis du ikke ser de eksisterende Ledger-kontoene nedenfor, kan du prøve å bytte baner til «Legacy (MEW/MyCrypto)»"
},

@ -925,18 +925,12 @@
"selectAnAccountHelp": {
"message": "Wybierz konto do przeglądania w MetaMask"
},
"selectCurrency": {
"message": "Wybierz walutę"
},
"selectEachPhrase": {
"message": "Wybierz każdą frazę, aby upewnić się, że jest poprawna."
},
"selectHdPath": {
"message": "Wybierz ścieżkę HD"
},
"selectLocale": {
"message": "Wybierz lokalizację"
},
"selectPathHelp": {
"message": "Jeśli nie widzisz poniżej swoich kont Ledger, spróbuj przełączyć się na \"Legacy (MEW / MyCrypto)\""
},

@ -327,9 +327,6 @@
"seedPhraseReq": {
"message": "seed phrases are 12 words long"
},
"selectCurrency": {
"message": "Selecionar Moeda"
},
"selectType": {
"message": "Selecionar Tipo"
},

@ -919,18 +919,12 @@
"selectAnAccountHelp": {
"message": "Selecione a conta para ver no MetaMask"
},
"selectCurrency": {
"message": "Selecione a Moeda"
},
"selectEachPhrase": {
"message": "Selecione cada frase para se certificar de que esteja correta."
},
"selectHdPath": {
"message": "Selecione o caminho no HD"
},
"selectLocale": {
"message": "Selecionar Local"
},
"selectPathHelp": {
"message": "Se não conseguir ver as contas da Ledger existentes abaixo, experimente alternar os caminhos para \"Legacy (MEW / MyCrypto)\""
},

@ -918,18 +918,12 @@
"selectAnAccountHelp": {
"message": "Selectați contul de vizualizat în MetaMask"
},
"selectCurrency": {
"message": "Selectați moneda"
},
"selectEachPhrase": {
"message": "Vă rugăm să selectați fiecare expresie pentru a vă asigura că este corectă."
},
"selectHdPath": {
"message": "Selectare cale HD"
},
"selectLocale": {
"message": "Alegeți setările regionale"
},
"selectPathHelp": {
"message": "Dacă nu vedeți mai jos conturile dvs. Ledger existente, încercați să comutați calea la „Legacy (MEW / MyCrypto)”"
},

File diff suppressed because it is too large Load Diff

@ -894,18 +894,12 @@
"selectAnAccountHelp": {
"message": "Vyberte účet, ktorý chcete zobraziť v MetaMask"
},
"selectCurrency": {
"message": "Vybrat měnu"
},
"selectEachPhrase": {
"message": "Vyberte každú frázu, aby ste sa uistili, že je správna."
},
"selectHdPath": {
"message": "Vyberte cestu HD"
},
"selectLocale": {
"message": "Vybrať miestne nastavenia"
},
"selectPathHelp": {
"message": "Ak nevidíte svoje existujúce účty v Hlavnej knihe dole, skúste prepnúť cesty na „Odkaz (MEW/MyCrypto)“"
},

@ -913,18 +913,12 @@
"selectAnAccountHelp": {
"message": "Izberi račun za prikaz v MetaMask"
},
"selectCurrency": {
"message": "Izberi valuto"
},
"selectEachPhrase": {
"message": "Izberite vsako geslo ter se prepričajte, da je pravilno."
},
"selectHdPath": {
"message": "Izberi HD Path"
},
"selectLocale": {
"message": "Izberi jezik"
},
"selectPathHelp": {
"message": "Če obstoječih Ledger ne vidite, poskusite izbrati \"Legacy (MEW / MyCrypto)\""
},

@ -922,18 +922,12 @@
"selectAnAccountHelp": {
"message": "Izaberite nalog za prikaz u MetaMask-u"
},
"selectCurrency": {
"message": "Izaberite valutu"
},
"selectEachPhrase": {
"message": "Molimo vas izaberite svaki izraz kako biste proverili da je tačan."
},
"selectHdPath": {
"message": "Izaberite HD Path"
},
"selectLocale": {
"message": "Izaberite lokalni standard"
},
"selectPathHelp": {
"message": "Ako ne vidite svoje postojeće Ledger naloge dole, pokušajte prebaciti putanje na „Legacy (MEW / MyCrypto)”"
},

@ -915,18 +915,12 @@
"selectAnAccountHelp": {
"message": "Välj konto att visa i MetaMask"
},
"selectCurrency": {
"message": "Välj valuta"
},
"selectEachPhrase": {
"message": "Välj varje fras för att säkerställa att den är korrekt."
},
"selectHdPath": {
"message": "Välj HD-sökväg"
},
"selectLocale": {
"message": "Välj plats"
},
"selectPathHelp": {
"message": "Om du inte ser dina befintliga Ledger-konton nedan, prova att byta sökväg till \"Legacy (MEW / MyCrypto)\""
},

@ -909,18 +909,12 @@
"selectAnAccountHelp": {
"message": "Chagua akaunti kuangalia kwenye MetaMask"
},
"selectCurrency": {
"message": "Chagua Sarafu"
},
"selectEachPhrase": {
"message": "Tafadhali chagua kila kirai ili kuhakikisha kuwa hii ni sahihi."
},
"selectHdPath": {
"message": "Chagua Njia ya HD"
},
"selectLocale": {
"message": "Chagua Lugha"
},
"selectPathHelp": {
"message": "Ikiwa huoni akaunti zako za Leja za sasa hapa chini, charibu kubadilisha njia kwenda \"Legacy (MEW / MyCrypto)\"  "
},

@ -432,9 +432,6 @@
"seedPhraseReq": {
"message": "விியஙகள 12 வகளடவ"
},
"selectCurrency": {
"message": "நணயத"
},
"selectType": {
"message": "வக"
},

@ -429,9 +429,6 @@
"selectAnAccount": {
"message": "เลอกบญช"
},
"selectCurrency": {
"message": "เลอกสกลเงน"
},
"selectHdPath": {
"message": "เลอกเสนทาง HD"
},

File diff suppressed because it is too large Load Diff

@ -372,9 +372,6 @@
"seedPhraseReq": {
"message": "Kaynak ifadeleri 12 kelimedir."
},
"selectCurrency": {
"message": "Döviz Seç"
},
"selectType": {
"message": "Tip Seç"
},

@ -931,18 +931,12 @@
"selectAnAccountHelp": {
"message": "Оберіть обліковий запис для перегляду в MetaMask"
},
"selectCurrency": {
"message": "Виберіть валюту"
},
"selectEachPhrase": {
"message": "Виберіть кожну фразу, щоб переконатися, що вона правильна."
},
"selectHdPath": {
"message": "Виберіть шлях до HD-гаманця"
},
"selectLocale": {
"message": "Оберіть локаль"
},
"selectPathHelp": {
"message": "Якщо ви не бачите ваш обліковий запис Ledger, який існує, спробуйте переключити шляхи на \"Legacy (MEW / MyCrypto)\""
},

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -922,15 +922,9 @@
"selectAnAccount": {
"message": "選擇帳戶"
},
"selectCurrency": {
"message": "選擇幣別"
},
"selectEachPhrase": {
"message": "請依照正確順序點選助憶詞"
},
"selectLocale": {
"message": "選擇地區"
},
"selectPathHelp": {
"message": "若看不到您已經擁有的 Ledger 帳戶,請嘗試切換路徑為 \"Legacy (MEW / MyCrypto)\""
},

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

@ -32,7 +32,9 @@ import LocalStore from './lib/local-store';
import ReadOnlyNetworkStore from './lib/network-store';
import createStreamSink from './lib/createStreamSink';
import NotificationManager from './lib/notification-manager';
import MetamaskController from './metamask-controller';
import MetamaskController, {
METAMASK_CONTROLLER_EVENTS,
} from './metamask-controller';
import rawFirstTimeState from './first-time-state';
import getFirstPreferredLangCode from './lib/get-first-preferred-lang-code';
import getObjStructure from './lib/getObjStructure';
@ -51,6 +53,7 @@ global.METAMASK_NOTIFIER = notificationManager;
let popupIsOpen = false;
let notificationIsOpen = false;
let uiIsTriggering = false;
const openMetamaskTabsIDs = {};
const requestAccountTabIds = {};
@ -240,7 +243,9 @@ function setupController(initState, initLangCode) {
});
setupEnsIpfsResolver({
getCurrentNetwork: controller.getCurrentNetwork,
getCurrentChainId: controller.networkController.getCurrentChainId.bind(
controller.networkController,
),
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(
controller.preferencesController,
),
@ -396,14 +401,35 @@ function setupController(initState, initLangCode) {
//
updateBadge();
controller.txController.on('update:badge', updateBadge);
controller.messageManager.on('updateBadge', updateBadge);
controller.personalMessageManager.on('updateBadge', updateBadge);
controller.decryptMessageManager.on('updateBadge', updateBadge);
controller.encryptionPublicKeyManager.on('updateBadge', updateBadge);
controller.typedMessageManager.on('updateBadge', updateBadge);
controller.txController.on(
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
updateBadge,
);
controller.messageManager.on(
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
updateBadge,
);
controller.personalMessageManager.on(
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
updateBadge,
);
controller.decryptMessageManager.on(
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
updateBadge,
);
controller.encryptionPublicKeyManager.on(
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
updateBadge,
);
controller.typedMessageManager.on(
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
updateBadge,
);
controller.approvalController.subscribe(updateBadge);
controller.appStateController.on('updateBadge', updateBadge);
controller.appStateController.on(
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
updateBadge,
);
/**
* Updates the Web Extension's "badge" number, on the little fox in the toolbar.
@ -459,8 +485,17 @@ async function triggerUi() {
tabs.length > 0 &&
tabs[0].extData &&
tabs[0].extData.indexOf('vivaldi_tab') > -1;
if ((isVivaldi || !popupIsOpen) && !currentlyActiveMetamaskTab) {
await notificationManager.showPopup();
if (
!uiIsTriggering &&
(isVivaldi || !popupIsOpen) &&
!currentlyActiveMetamaskTab
) {
uiIsTriggering = true;
try {
await notificationManager.showPopup();
} finally {
uiIsTriggering = false;
}
}
}

@ -1,5 +1,6 @@
import EventEmitter from 'events';
import { ObservableStore } from '@metamask/obs-store';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
export default class AppStateController extends EventEmitter {
/**
@ -74,7 +75,7 @@ export default class AppStateController extends EventEmitter {
*/
waitForUnlock(resolve, shouldShowUnlockRequest) {
this.waitingForUnlock.push({ resolve });
this.emit('updateBadge');
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
if (shouldShowUnlockRequest) {
this._showUnlockRequest();
}
@ -88,7 +89,7 @@ export default class AppStateController extends EventEmitter {
while (this.waitingForUnlock.length > 0) {
this.waitingForUnlock.shift().resolve();
}
this.emit('updateBadge');
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
}
}

@ -2,20 +2,22 @@ import punycode from 'punycode/punycode';
import ethUtil from 'ethereumjs-util';
import { ObservableStore } from '@metamask/obs-store';
import log from 'loglevel';
import { CHAIN_ID_TO_NETWORK_ID_MAP } from '../../../../shared/constants/network';
import Ens from './ens';
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
const ZERO_X_ERROR_ADDRESS = '0x';
export default class EnsController {
constructor({ ens, provider, networkStore } = {}) {
constructor({ ens, provider, onNetworkDidChange, getCurrentChainId } = {}) {
const initState = {
ensResolutionsByAddress: {},
};
this._ens = ens;
if (!this._ens) {
const network = networkStore.getState();
const chainId = getCurrentChainId();
const network = CHAIN_ID_TO_NETWORK_ID_MAP[chainId];
if (Ens.getNetworkEnsSupport(network)) {
this._ens = new Ens({
network,
@ -25,8 +27,10 @@ export default class EnsController {
}
this.store = new ObservableStore(initState);
networkStore.subscribe((network) => {
onNetworkDidChange(() => {
this.store.putState(initState);
const chainId = getCurrentChainId();
const network = CHAIN_ID_TO_NETWORK_ID_MAP[chainId];
if (Ens.getNetworkEnsSupport(network)) {
this._ens = new Ens({
network,

@ -23,6 +23,7 @@ import {
ROPSTEN,
ROPSTEN_CHAIN_ID,
} from '../../../shared/constants/network';
import { NETWORK_EVENTS } from './network';
const fetchWithTimeout = getFetchWithTimeout(30000);
@ -111,7 +112,7 @@ export default class IncomingTransactionsController {
}),
);
this.networkController.on('networkDidChange', async () => {
this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, async () => {
const address = this.preferencesController.getSelectedAddress();
await this._update({
address,
@ -282,6 +283,7 @@ export default class IncomingTransactionsController {
return {
blockNumber: txMeta.blockNumber,
id: createId(),
chainId,
metamaskNetworkId: CHAIN_ID_TO_NETWORK_ID_MAP[chainId],
status,
time,

@ -200,9 +200,9 @@ export default class MetaMetricsController {
value,
currency,
category,
network: this.network,
network: properties?.network ?? this.network,
locale: this.locale,
chain_id: this.chainId,
chain_id: properties?.chain_id ?? this.chainId,
environment_type: environmentType,
},
context: this._buildContext(referrer, page),

@ -1 +1 @@
export { default } from './network';
export { default, NETWORK_EVENTS } from './network';

@ -21,7 +21,7 @@ import {
import {
isPrefixedFormattedHexString,
isSafeChainId,
} from '../../../../shared/modules/utils';
} from '../../../../shared/modules/network.utils';
import createMetamaskMiddleware from './createMetamaskMiddleware';
import createInfuraClient from './createInfuraClient';
import createJsonRpcClient from './createJsonRpcClient';
@ -47,6 +47,13 @@ const defaultProviderConfig = {
...defaultProviderConfigOpts,
};
export const NETWORK_EVENTS = {
// Fired after the actively selected network is changed
NETWORK_DID_CHANGE: 'networkDidChange',
// Fired when the actively selected network *will* change
NETWORK_WILL_CHANGE: 'networkWillChange',
};
export default class NetworkController extends EventEmitter {
constructor(opts = {}) {
super();
@ -73,7 +80,7 @@ export default class NetworkController extends EventEmitter {
this._providerProxy = null;
this._blockTrackerProxy = null;
this.on('networkDidChange', this.lookupNetwork);
this.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, this.lookupNetwork);
}
/**
@ -229,9 +236,10 @@ export default class NetworkController extends EventEmitter {
//
_switchNetwork(opts) {
this.emit(NETWORK_EVENTS.NETWORK_WILL_CHANGE);
this.setNetworkState('loading');
this._configureProvider(opts);
this.emit('networkDidChange', opts.type);
this.emit(NETWORK_EVENTS.NETWORK_DID_CHANGE, opts.type);
}
_configureProvider({ type, rpcUrl, chainId }) {

@ -126,7 +126,12 @@ export class PermissionsController {
*/
async requestAccountsPermissionWithId(origin) {
const id = nanoid();
this._requestPermissions({ origin }, { eth_accounts: {} }, id);
this._requestPermissions({ origin }, { eth_accounts: {} }, id).then(
async () => {
const permittedAccounts = await this.getAccounts(origin);
this.notifyAccountsChanged(origin, permittedAccounts);
},
);
return id;
}

@ -7,7 +7,8 @@ import ethers from 'ethers';
import log from 'loglevel';
import { LISTED_CONTRACT_ADDRESSES } from '../../../shared/constants/tokens';
import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network';
import { isPrefixedFormattedHexString } from '../../../shared/modules/utils';
import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils';
import { NETWORK_EVENTS } from './network';
export default class PreferencesController {
/**
@ -72,7 +73,7 @@ export default class PreferencesController {
this.store.setMaxListeners(12);
this.openPopup = opts.openPopup;
this.migrateAddressBookState = opts.migrateAddressBookState;
this._subscribeProviderType();
this._subscribeToNetworkDidChange();
global.setPreference = (key, value) => {
return this.setFeatureFlag(key, value);
@ -667,12 +668,11 @@ export default class PreferencesController {
//
/**
* Subscription to network provider type.
*
*
* Handle updating token list to reflect current network by listening for the
* NETWORK_DID_CHANGE event.
*/
_subscribeProviderType() {
this.network.providerStore.subscribe(() => {
_subscribeToNetworkDidChange() {
this.network.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
const { tokens, hiddenTokens } = this._getTokenRelatedStates();
this._updateAccountTokens(tokens, this.getAssetImages(), hiddenTokens);
});
@ -689,12 +689,12 @@ export default class PreferencesController {
_updateAccountTokens(tokens, assetImages, hiddenTokens) {
const {
accountTokens,
providerType,
chainId,
selectedAddress,
accountHiddenTokens,
} = this._getTokenRelatedStates();
accountTokens[selectedAddress][providerType] = tokens;
accountHiddenTokens[selectedAddress][providerType] = hiddenTokens;
accountTokens[selectedAddress][chainId] = tokens;
accountHiddenTokens[selectedAddress][chainId] = hiddenTokens;
this.store.updateState({
accountTokens,
tokens,
@ -730,27 +730,27 @@ export default class PreferencesController {
// eslint-disable-next-line no-param-reassign
selectedAddress = this.store.getState().selectedAddress;
}
const providerType = this.network.providerStore.getState().type;
const chainId = this.network.getCurrentChainId();
if (!(selectedAddress in accountTokens)) {
accountTokens[selectedAddress] = {};
}
if (!(selectedAddress in accountHiddenTokens)) {
accountHiddenTokens[selectedAddress] = {};
}
if (!(providerType in accountTokens[selectedAddress])) {
accountTokens[selectedAddress][providerType] = [];
if (!(chainId in accountTokens[selectedAddress])) {
accountTokens[selectedAddress][chainId] = [];
}
if (!(providerType in accountHiddenTokens[selectedAddress])) {
accountHiddenTokens[selectedAddress][providerType] = [];
if (!(chainId in accountHiddenTokens[selectedAddress])) {
accountHiddenTokens[selectedAddress][chainId] = [];
}
const tokens = accountTokens[selectedAddress][providerType];
const hiddenTokens = accountHiddenTokens[selectedAddress][providerType];
const tokens = accountTokens[selectedAddress][chainId];
const hiddenTokens = accountHiddenTokens[selectedAddress][chainId];
return {
tokens,
accountTokens,
hiddenTokens,
accountHiddenTokens,
providerType,
chainId,
selectedAddress,
};
}

@ -19,6 +19,7 @@ import {
fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness,
fetchSwapsQuoteRefreshTime as defaultFetchSwapsQuoteRefreshTime,
} from '../../../ui/app/pages/swaps/swaps.util';
import { NETWORK_EVENTS } from './network';
const METASWAP_ADDRESS = '0x881d40237659c251811cec9c364ef91dc08d300c';
@ -103,7 +104,7 @@ export default class SwapsController {
this.ethersProvider = new ethers.providers.Web3Provider(provider);
this._currentNetwork = networkController.store.getState().network;
networkController.on('networkDidChange', (network) => {
networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, (network) => {
if (network !== 'loading' && network !== this._currentNetwork) {
this._currentNetwork = network;
this.ethersProvider = new ethers.providers.Web3Provider(provider);

@ -19,10 +19,13 @@ export default class TokenRatesController {
*
* @param {Object} [config] - Options to configure controller
*/
constructor({ currency, preferences } = {}) {
constructor({ preferences, getNativeCurrency } = {}) {
this.store = new ObservableStore();
this.currency = currency;
this.preferences = preferences;
this.getNativeCurrency = getNativeCurrency;
this.tokens = preferences.getState().tokens;
preferences.subscribe(({ tokens = [] }) => {
this.tokens = tokens;
});
}
/**
@ -30,9 +33,7 @@ export default class TokenRatesController {
*/
async updateExchangeRates() {
const contractExchangeRates = {};
const nativeCurrency = this.currency
? this.currency.state.nativeCurrency.toLowerCase()
: 'eth';
const nativeCurrency = this.getNativeCurrency().toLowerCase();
const pairs = this._tokens.map((token) => token.address).join(',');
const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}`;
if (this._tokens.length > 0) {
@ -60,21 +61,6 @@ export default class TokenRatesController {
}
/* eslint-disable accessor-pairs */
/**
* @type {Object}
*/
set preferences(preferences) {
this._preferences && this._preferences.unsubscribe();
if (!preferences) {
return;
}
this._preferences = preferences;
this.tokens = preferences.getState().tokens;
preferences.subscribe(({ tokens = [] }) => {
this.tokens = tokens;
});
}
/**
* @type {Array}
*/

@ -23,6 +23,7 @@ import {
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
} from '../../../../shared/constants/transaction';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import TransactionStateManager from './tx-state-manager';
import TxGasUtil from './tx-gas-utils';
import PendingTransactionTracker from './pending-tx-tracker';
@ -83,6 +84,7 @@ export default class TransactionController extends EventEmitter {
initState: opts.initState,
txHistoryLimit: opts.txHistoryLimit,
getNetwork: this.getNetwork.bind(this),
getCurrentChainId: opts.getCurrentChainId,
});
this._onBootCleanUp();
@ -113,7 +115,9 @@ export default class TransactionController extends EventEmitter {
),
});
this.txStateManager.store.subscribe(() => this.emit('update:badge'));
this.txStateManager.store.subscribe(() =>
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE),
);
this._setupListeners();
// memstore is computed from a few different stores
this._updateMemstore();

@ -3,6 +3,8 @@ import { ObservableStore } from '@metamask/obs-store';
import log from 'loglevel';
import createId from '../../lib/random-id';
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import { transactionMatchesNetwork } from '../../../../shared/modules/transaction.utils';
import {
generateHistoryEntry,
replayHistory,
@ -27,12 +29,13 @@ import { getFinalStates, normalizeTxParams } from './lib/util';
* @class
*/
export default class TransactionStateManager extends EventEmitter {
constructor({ initState, txHistoryLimit, getNetwork }) {
constructor({ initState, txHistoryLimit, getNetwork, getCurrentChainId }) {
super();
this.store = new ObservableStore({ transactions: [], ...initState });
this.txHistoryLimit = txHistoryLimit;
this.getNetwork = getNetwork;
this.getCurrentChainId = getCurrentChainId;
}
/**
@ -41,6 +44,7 @@ export default class TransactionStateManager extends EventEmitter {
*/
generateTxMeta(opts) {
const netId = this.getNetwork();
const chainId = this.getCurrentChainId();
if (netId === 'loading') {
throw new Error('MetaMask is having trouble connecting to the network');
}
@ -49,6 +53,7 @@ export default class TransactionStateManager extends EventEmitter {
time: new Date().getTime(),
status: TRANSACTION_STATUSES.UNAPPROVED,
metamaskNetworkId: netId,
chainId,
loadingDefaults: true,
...opts,
};
@ -64,13 +69,14 @@ export default class TransactionStateManager extends EventEmitter {
*/
getTxList(limit) {
const network = this.getNetwork();
const chainId = this.getCurrentChainId();
const fullTxList = this.getFullTxList();
const nonces = new Set();
const txs = [];
for (let i = fullTxList.length - 1; i > -1; i--) {
const txMeta = fullTxList[i];
if (txMeta.metamaskNetworkId !== network) {
if (transactionMatchesNetwork(txMeta, chainId, network) === false) {
continue;
}
@ -451,13 +457,14 @@ export default class TransactionStateManager extends EventEmitter {
// network only tx
const txs = this.getFullTxList();
const network = this.getNetwork();
const chainId = this.getCurrentChainId();
// Filter out the ones from the current account and network
const otherAccountTxs = txs.filter(
(txMeta) =>
!(
txMeta.txParams.from === address &&
txMeta.metamaskNetworkId === network
transactionMatchesNetwork(txMeta, chainId, network)
),
);
@ -474,7 +481,7 @@ export default class TransactionStateManager extends EventEmitter {
* @param {TransactionStatuses[keyof TransactionStatuses]} status - the status to set on the txMeta
* @emits tx:status-update - passes txId and status
* @emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
* @emits update:badge
* @emits 'updateBadge'
*/
_setTxStatus(txId, status) {
const txMeta = this.getTx(txId);
@ -497,7 +504,7 @@ export default class TransactionStateManager extends EventEmitter {
) {
this.emit(`${txMeta.id}:finished`, txMeta);
}
this.emit('update:badge');
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
} catch (error) {
log.error(error);
}

@ -4,6 +4,7 @@ import ethUtil from 'ethereumjs-util';
import { ethErrors } from 'eth-rpc-errors';
import log from 'loglevel';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import { addHexPrefix } from './util';
import createId from './random-id';
@ -253,6 +254,14 @@ export default class DecryptMessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'errored');
}
/**
* Clears all unapproved messages from memory.
*/
clearUnapproved() {
this.messages = this.messages.filter((msg) => msg.status !== 'unapproved');
this._saveMsgList();
}
/**
* Updates the status of a DecryptMessage in this.messages via a call to this._updateMsg
*
@ -316,7 +325,7 @@ export default class DecryptMessageManager extends EventEmitter {
unapprovedDecryptMsgs,
unapprovedDecryptMsgCount,
});
this.emit('updateBadge');
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
}
/**

@ -3,6 +3,7 @@ import { ObservableStore } from '@metamask/obs-store';
import { ethErrors } from 'eth-rpc-errors';
import log from 'loglevel';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import createId from './random-id';
/**
@ -242,6 +243,14 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
this._setMsgStatus(msgId, 'errored');
}
/**
* Clears all unapproved messages from memory.
*/
clearUnapproved() {
this.messages = this.messages.filter((msg) => msg.status !== 'unapproved');
this._saveMsgList();
}
/**
* Updates the status of a EncryptionPublicKey in this.messages via a call to this._updateMsg
*
@ -303,6 +312,6 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
unapprovedEncryptionPublicKeyMsgs,
unapprovedEncryptionPublicKeyMsgCount,
});
this.emit('updateBadge');
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
}
}

@ -8,7 +8,7 @@ const supportedTopLevelDomains = ['eth'];
export default function setupEnsIpfsResolver({
provider,
getCurrentNetwork,
getCurrentChainId,
getIpfsGateway,
}) {
// install listener
@ -30,7 +30,7 @@ export default function setupEnsIpfsResolver({
const { tabId, url } = details;
// ignore requests that are not associated with tabs
// only attempt ENS resolution on mainnet
if (tabId === -1 || getCurrentNetwork() !== '1') {
if (tabId === -1 || getCurrentChainId() !== '0x1') {
return;
}
// parse ens name

@ -3,6 +3,7 @@ import { ObservableStore } from '@metamask/obs-store';
import ethUtil from 'ethereumjs-util';
import { ethErrors } from 'eth-rpc-errors';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import createId from './random-id';
/**
@ -220,6 +221,14 @@ export default class MessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'rejected');
}
/**
* Clears all unapproved messages from memory.
*/
clearUnapproved() {
this.messages = this.messages.filter((msg) => msg.status !== 'unapproved');
this._saveMsgList();
}
/**
* Updates the status of a Message in this.messages via a call to this._updateMsg
*
@ -272,7 +281,7 @@ export default class MessageManager extends EventEmitter {
const unapprovedMsgs = this.getUnapprovedMsgs();
const unapprovedMsgCount = Object.keys(unapprovedMsgs).length;
this.memStore.updateState({ unapprovedMsgs, unapprovedMsgCount });
this.emit('updateBadge');
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
}
}

@ -4,6 +4,7 @@ import ethUtil from 'ethereumjs-util';
import { ethErrors } from 'eth-rpc-errors';
import log from 'loglevel';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import { addHexPrefix } from './util';
import createId from './random-id';
@ -241,6 +242,14 @@ export default class PersonalMessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'rejected');
}
/**
* Clears all unapproved messages from memory.
*/
clearUnapproved() {
this.messages = this.messages.filter((msg) => msg.status !== 'unapproved');
this._saveMsgList();
}
/**
* Updates the status of a PersonalMessage in this.messages via a call to this._updateMsg
*
@ -301,7 +310,7 @@ export default class PersonalMessageManager extends EventEmitter {
unapprovedPersonalMsgs,
unapprovedPersonalMsgCount,
});
this.emit('updateBadge');
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
}
/**

@ -0,0 +1,272 @@
import { ethErrors } from 'eth-rpc-errors';
import validUrl from 'valid-url';
import { omit } from 'lodash';
import { MESSAGE_TYPE } from '../../../../../shared/constants/app';
import {
isPrefixedFormattedHexString,
isSafeChainId,
} from '../../../../../shared/modules/network.utils';
import { jsonRpcRequest } from '../../../../../shared/modules/rpc.utils';
import { CHAIN_ID_TO_NETWORK_ID_MAP } from '../../../../../shared/constants/network';
const addEthereumChain = {
methodNames: [MESSAGE_TYPE.ADD_ETHEREUM_CHAIN],
implementation: addEthereumChainHandler,
};
export default addEthereumChain;
async function addEthereumChainHandler(
req,
res,
_next,
end,
{
addCustomRpc,
getCurrentChainId,
findCustomRpcBy,
updateRpcTarget,
requestUserApproval,
sendMetrics,
},
) {
if (!req.params?.[0] || typeof req.params[0] !== 'object') {
return end(
ethErrors.rpc.invalidParams({
message: `Expected single, object parameter. Received:\n${JSON.stringify(
req.params,
)}`,
}),
);
}
const { origin } = req;
const {
chainId,
chainName = null,
blockExplorerUrls = null,
nativeCurrency = null,
rpcUrls,
} = req.params[0];
const otherKeys = Object.keys(
omit(req.params[0], [
'chainId',
'chainName',
'blockExplorerUrls',
'iconUrls',
'rpcUrls',
'nativeCurrency',
]),
);
if (otherKeys.length > 0) {
return end(
ethErrors.rpc.invalidParams({
message: `Received unexpected keys on object parameter. Unsupported keys:\n${otherKeys}`,
}),
);
}
const firstValidRPCUrl = Array.isArray(rpcUrls)
? rpcUrls.find((rpcUrl) => validUrl.isHttpsUri(rpcUrl))
: null;
const firstValidBlockExplorerUrl =
blockExplorerUrls !== null && Array.isArray(blockExplorerUrls)
? blockExplorerUrls.find((blockExplorerUrl) =>
validUrl.isHttpsUri(blockExplorerUrl),
)
: null;
if (!firstValidRPCUrl) {
return end(
ethErrors.rpc.invalidParams({
message: `Expected an array with at least one valid string HTTPS url 'rpcUrls', Received:\n${rpcUrls}`,
}),
);
}
if (blockExplorerUrls !== null && !firstValidBlockExplorerUrl) {
return end(
ethErrors.rpc.invalidParams({
message: `Expected null or array with at least one valid string HTTPS URL 'blockExplorerUrl'. Received: ${blockExplorerUrls}`,
}),
);
}
const _chainId = typeof chainId === 'string' && chainId.toLowerCase();
if (!isPrefixedFormattedHexString(_chainId)) {
return end(
ethErrors.rpc.invalidParams({
message: `Expected 0x-prefixed, unpadded, non-zero hexadecimal string 'chainId'. Received:\n${chainId}`,
}),
);
}
if (!isSafeChainId(parseInt(_chainId, 16))) {
return end(
ethErrors.rpc.invalidParams({
message: `Invalid chain ID "${_chainId}": numerical value greater than max safe value. Received:\n${chainId}`,
}),
);
}
if (CHAIN_ID_TO_NETWORK_ID_MAP[_chainId]) {
return end(
ethErrors.rpc.invalidParams({
message: `May not specify default MetaMask chain.`,
}),
);
}
const existingNetwork = findCustomRpcBy({ chainId: _chainId });
if (existingNetwork !== null) {
const currentChainId = getCurrentChainId();
if (currentChainId === _chainId) {
res.result = null;
return end();
}
try {
await updateRpcTarget(
await requestUserApproval({
origin,
type: MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN,
requestData: {
rpcUrl: existingNetwork.rpcUrl,
chainId: existingNetwork.chainId,
nickname: existingNetwork.nickname,
ticker: existingNetwork.ticker,
},
}),
);
res.result = null;
} catch (error) {
return end(error);
}
return end();
}
let endpointChainId;
try {
endpointChainId = await jsonRpcRequest(firstValidRPCUrl, 'eth_chainId');
} catch (err) {
return end(
ethErrors.rpc.internal({
message: `Request for method 'eth_chainId on ${firstValidRPCUrl} failed`,
data: { networkErr: err },
}),
);
}
if (_chainId !== endpointChainId) {
return end(
ethErrors.rpc.invalidParams({
message: `Chain ID returned by RPC URL ${firstValidRPCUrl} does not match ${_chainId}`,
data: { chainId: endpointChainId },
}),
);
}
if (typeof chainName !== 'string' || !chainName) {
return end(
ethErrors.rpc.invalidParams({
message: `Expected non-empty string 'chainName'. Received:\n${chainName}`,
}),
);
}
const _chainName =
chainName.length > 100 ? chainName.substring(0, 100) : chainName;
if (nativeCurrency !== null) {
if (typeof nativeCurrency !== 'object' || Array.isArray(nativeCurrency)) {
return end(
ethErrors.rpc.invalidParams({
message: `Expected null or object 'nativeCurrency'. Received:\n${nativeCurrency}`,
}),
);
}
if (nativeCurrency.decimals !== 18) {
return end(
ethErrors.rpc.invalidParams({
message: `Expected the number 18 for 'nativeCurrency.decimals' when 'nativeCurrency' is provided. Received: ${nativeCurrency.decimals}`,
}),
);
}
if (!nativeCurrency.symbol || typeof nativeCurrency.symbol !== 'string') {
return end(
ethErrors.rpc.invalidParams({
message: `Expected a string 'nativeCurrency.symbol'. Received: ${nativeCurrency.symbol}`,
}),
);
}
}
const ticker = nativeCurrency?.symbol || 'ETH';
if (typeof ticker !== 'string' || ticker.length < 2 || ticker.length > 6) {
return end(
ethErrors.rpc.invalidParams({
message: `Expected 2-6 character string 'nativeCurrency.symbol'. Received:\n${ticker}`,
}),
);
}
try {
await addCustomRpc(
await requestUserApproval({
origin,
type: MESSAGE_TYPE.ADD_ETHEREUM_CHAIN,
requestData: {
chainId: _chainId,
blockExplorerUrl: firstValidBlockExplorerUrl,
chainName: _chainName,
rpcUrl: firstValidRPCUrl,
ticker,
},
}),
);
sendMetrics({
event: 'Custom Network Added',
category: 'Network',
referrer: {
url: origin,
},
sensitiveProperties: {
chain_id: _chainId,
rpc_url: firstValidRPCUrl,
network_name: _chainName,
// Including network to override the default network
// property included in all events. For RPC type networks
// the MetaMetrics controller uses the rpcUrl for the network
// property.
network: firstValidRPCUrl,
symbol: ticker,
block_explorer_url: firstValidBlockExplorerUrl,
source: 'dapp',
},
});
await updateRpcTarget(
await requestUserApproval({
origin,
type: MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN,
requestData: {
rpcUrl: firstValidRPCUrl,
chainId: _chainId,
nickname: _chainName,
ticker,
},
}),
);
res.result = null;
} catch (error) {
return end(error);
}
return end();
}

@ -1,6 +1,12 @@
import addEthereumChain from './add-ethereum-chain';
import getProviderState from './get-provider-state';
import logWeb3ShimUsage from './log-web3-shim-usage';
import watchAsset from './watch-asset';
const handlers = [getProviderState, logWeb3ShimUsage, watchAsset];
const handlers = [
addEthereumChain,
getProviderState,
logWeb3ShimUsage,
watchAsset,
];
export default handlers;

@ -7,6 +7,7 @@ import { isValidAddress } from 'ethereumjs-util';
import log from 'loglevel';
import jsonschema from 'jsonschema';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import createId from './random-id';
/**
@ -313,6 +314,14 @@ export default class TypedMessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'errored');
}
/**
* Clears all unapproved messages from memory.
*/
clearUnapproved() {
this.messages = this.messages.filter((msg) => msg.status !== 'unapproved');
this._saveMsgList();
}
//
// PRIVATE METHODS
//
@ -377,6 +386,6 @@ export default class TypedMessageManager extends EventEmitter {
unapprovedTypedMessages,
unapprovedTypedMessagesCount,
});
this.emit('updateBadge');
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
}
}

@ -35,7 +35,7 @@ import createTabIdMiddleware from './lib/createTabIdMiddleware';
import createOnboardingMiddleware from './lib/createOnboardingMiddleware';
import { setupMultiplex } from './lib/stream-utils';
import EnsController from './controllers/ens';
import NetworkController from './controllers/network';
import NetworkController, { NETWORK_EVENTS } from './controllers/network';
import PreferencesController from './controllers/preferences';
import AppStateController from './controllers/app-state';
import CachedBalancesController from './controllers/cached-balances';
@ -61,6 +61,12 @@ import seedPhraseVerifier from './lib/seed-phrase-verifier';
import MetaMetricsController from './controllers/metametrics';
import { segment, segmentLegacy } from './lib/segment';
export const METAMASK_CONTROLLER_EVENTS = {
// Fired after state changes that impact the extension badge (unapproved msg count)
// The process of updating the badge happens in app/scripts/background.js.
UPDATE_BADGE: 'updateBadge',
};
export default class MetamaskController extends EventEmitter {
/**
* @constructor
@ -127,7 +133,7 @@ export default class MetamaskController extends EventEmitter {
preferencesStore: this.preferencesController.store,
onNetworkDidChange: this.networkController.on.bind(
this.networkController,
'networkDidChange',
NETWORK_EVENTS.NETWORK_DID_CHANGE,
),
getNetworkIdentifier: this.networkController.getNetworkIdentifier.bind(
this.networkController,
@ -163,13 +169,22 @@ export default class MetamaskController extends EventEmitter {
// token exchange rate tracker
this.tokenRatesController = new TokenRatesController({
currency: this.currencyRateController,
preferences: this.preferencesController.store,
getNativeCurrency: () => {
const { ticker } = this.networkController.getProviderConfig();
return ticker ?? 'ETH';
},
});
this.ensController = new EnsController({
provider: this.provider,
networkStore: this.networkController.networkStore,
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
),
onNetworkDidChange: this.networkController.on.bind(
this.networkController,
NETWORK_EVENTS.NETWORK_DID_CHANGE,
),
});
this.incomingTransactionsController = new IncomingTransactionsController({
@ -214,11 +229,6 @@ export default class MetamaskController extends EventEmitter {
preferencesController: this.preferencesController,
});
// ensure accountTracker updates balances after network change
this.networkController.on('networkDidChange', () => {
this.accountTracker._updateAccounts();
});
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring];
this.keyringController = new KeyringController({
keyringTypes: additionalKeyrings,
@ -245,7 +255,6 @@ export default class MetamaskController extends EventEmitter {
notifyDomain: this.notifyConnections.bind(this),
notifyAllDomains: this.notifyAllConnections.bind(this),
preferences: this.preferencesController.store,
showPermissionRequest: opts.showUserConfirmation,
},
initState.PermissionsController,
initState.PermissionsMetadata,
@ -290,7 +299,6 @@ export default class MetamaskController extends EventEmitter {
),
preferencesStore: this.preferencesController.store,
txHistoryLimit: 40,
getNetwork: this.networkController.getNetworkState.bind(this),
signTransaction: this.keyringController.signTransaction.bind(
this.keyringController,
),
@ -323,7 +331,7 @@ export default class MetamaskController extends EventEmitter {
}
});
this.networkController.on('networkDidChange', () => {
this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
this.setCurrentCurrency(
this.currencyRateController.state.currentCurrency,
(error) => {
@ -333,7 +341,8 @@ export default class MetamaskController extends EventEmitter {
},
);
});
const { ticker } = this.networkController.getProviderConfig();
this.currencyRateController.configure({ nativeCurrency: ticker ?? 'ETH' });
this.networkController.lookupNetwork();
this.messageManager = new MessageManager();
this.personalMessageManager = new PersonalMessageManager();
@ -357,6 +366,21 @@ export default class MetamaskController extends EventEmitter {
tokenRatesStore: this.tokenRatesController.store,
});
// ensure accountTracker updates balances after network change
this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
this.accountTracker._updateAccounts();
});
// clear unapproved transactions and messages when the network will change
this.networkController.on(NETWORK_EVENTS.NETWORK_WILL_CHANGE, () => {
this.txController.txStateManager.clearUnapprovedTxs();
this.encryptionPublicKeyManager.clearUnapproved();
this.personalMessageManager.clearUnapproved();
this.typedMessageManager.clearUnapproved();
this.decryptMessageManager.clearUnapproved();
this.messageManager.clearUnapproved();
});
// ensure isClientOpenAndUnlocked is updated when memState updates
this.on('update', (memState) => this._onStateUpdate(memState));
@ -559,6 +583,7 @@ export default class MetamaskController extends EventEmitter {
getApi() {
const {
alertController,
approvalController,
keyringController,
metaMetricsController,
networkController,
@ -883,6 +908,16 @@ export default class MetamaskController extends EventEmitter {
metaMetricsController.trackPage,
metaMetricsController,
),
// approval controller
resolvePendingApproval: nodeify(
approvalController.resolve,
approvalController,
),
rejectPendingApproval: nodeify(
approvalController.reject,
approvalController,
),
};
}
@ -2078,6 +2113,38 @@ export default class MetamaskController extends EventEmitter {
setWeb3ShimUsageRecorded: this.alertController.setWeb3ShimUsageRecorded.bind(
this.alertController,
),
findCustomRpcBy: this.findCustomRpcBy.bind(this),
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
),
requestUserApproval: this.approvalController.addAndShowApprovalRequest.bind(
this.approvalController,
),
updateRpcTarget: ({ rpcUrl, chainId, ticker, nickname }) => {
this.networkController.setRpcTarget(
rpcUrl,
chainId,
ticker,
nickname,
);
},
addCustomRpc: async ({
chainId,
blockExplorerUrl,
ticker,
chainName,
rpcUrl,
} = {}) => {
await this.preferencesController.addToFrequentRpcList(
rpcUrl,
chainId,
ticker,
chainName,
{
blockExplorerUrl,
},
);
},
}),
);
// filter and subscription polyfills
@ -2431,8 +2498,10 @@ export default class MetamaskController extends EventEmitter {
* @param {string} rpcUrl - A URL for a valid Ethereum RPC API.
* @param {string} chainId - The chainId of the selected network.
* @param {string} ticker - The ticker symbol of the selected network.
* @param {string} nickname - Optional nickname of the selected network.
* @returns {Promise<String>} The RPC Target URL confirmed.
* @param {string} [nickname] - Nickname of the selected network.
* @param {Object} [rpcPrefs] - RPC preferences.
* @param {string} [rpcPrefs.blockExplorerUrl] - URL of block explorer for the chain.
* @returns {Promise<String>} - The RPC Target URL confirmed.
*/
async updateAndSetCustomRpc(
rpcUrl,
@ -2513,6 +2582,25 @@ export default class MetamaskController extends EventEmitter {
await this.preferencesController.removeFromFrequentRpcList(rpcUrl);
}
/**
* Returns the first RPC info object that matches at least one field of the
* provided search criteria. Returns null if no match is found
*
* @param {Object} rpcInfo - The RPC endpoint properties and values to check.
* @returns {Object} rpcInfo found in the frequentRpcList
*/
findCustomRpcBy(rpcInfo) {
const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail();
for (const existingRpcInfo of frequentRpcListDetail) {
for (const key of Object.keys(rpcInfo)) {
if (existingRpcInfo[key] === rpcInfo[key]) {
return existingRpcInfo;
}
}
}
return null;
}
async initializeThreeBox() {
await this.threeBoxController.init();
}

@ -0,0 +1,125 @@
import { cloneDeep } from 'lodash';
import {
GOERLI,
GOERLI_CHAIN_ID,
KOVAN,
KOVAN_CHAIN_ID,
MAINNET,
MAINNET_CHAIN_ID,
NETWORK_TYPE_RPC,
RINKEBY,
RINKEBY_CHAIN_ID,
ROPSTEN,
ROPSTEN_CHAIN_ID,
} from '../../../shared/constants/network';
const version = 52;
/**
* Migrate tokens in Preferences to be keyed by chainId instead of
* providerType. To prevent breaking user's MetaMask and selected
* tokens, this migration copies the RPC entry into *every* custom RPC
* chainId.
*/
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 = {}) {
if (state.PreferencesController) {
const {
accountTokens,
accountHiddenTokens,
frequentRpcListDetail,
} = state.PreferencesController;
const newAccountTokens = {};
const newAccountHiddenTokens = {};
if (accountTokens && Object.keys(accountTokens).length > 0) {
for (const address of Object.keys(accountTokens)) {
newAccountTokens[address] = {};
if (accountTokens[address][NETWORK_TYPE_RPC]) {
frequentRpcListDetail.forEach((detail) => {
newAccountTokens[address][detail.chainId] =
accountTokens[address][NETWORK_TYPE_RPC];
});
}
for (const providerType of Object.keys(accountTokens[address])) {
switch (providerType) {
case MAINNET:
newAccountTokens[address][MAINNET_CHAIN_ID] =
accountTokens[address][MAINNET];
break;
case ROPSTEN:
newAccountTokens[address][ROPSTEN_CHAIN_ID] =
accountTokens[address][ROPSTEN];
break;
case RINKEBY:
newAccountTokens[address][RINKEBY_CHAIN_ID] =
accountTokens[address][RINKEBY];
break;
case GOERLI:
newAccountTokens[address][GOERLI_CHAIN_ID] =
accountTokens[address][GOERLI];
break;
case KOVAN:
newAccountTokens[address][KOVAN_CHAIN_ID] =
accountTokens[address][KOVAN];
break;
default:
break;
}
}
}
state.PreferencesController.accountTokens = newAccountTokens;
}
if (accountHiddenTokens && Object.keys(accountHiddenTokens).length > 0) {
for (const address of Object.keys(accountHiddenTokens)) {
newAccountHiddenTokens[address] = {};
if (accountHiddenTokens[address][NETWORK_TYPE_RPC]) {
frequentRpcListDetail.forEach((detail) => {
newAccountHiddenTokens[address][detail.chainId] =
accountHiddenTokens[address][NETWORK_TYPE_RPC];
});
}
for (const providerType of Object.keys(accountHiddenTokens[address])) {
switch (providerType) {
case MAINNET:
newAccountHiddenTokens[address][MAINNET_CHAIN_ID] =
accountHiddenTokens[address][MAINNET];
break;
case ROPSTEN:
newAccountHiddenTokens[address][ROPSTEN_CHAIN_ID] =
accountHiddenTokens[address][ROPSTEN];
break;
case RINKEBY:
newAccountHiddenTokens[address][RINKEBY_CHAIN_ID] =
accountHiddenTokens[address][RINKEBY];
break;
case GOERLI:
newAccountHiddenTokens[address][GOERLI_CHAIN_ID] =
accountHiddenTokens[address][GOERLI];
break;
case KOVAN:
newAccountHiddenTokens[address][KOVAN_CHAIN_ID] =
accountHiddenTokens[address][KOVAN];
break;
default:
break;
}
}
}
state.PreferencesController.accountHiddenTokens = newAccountHiddenTokens;
}
}
return state;
}

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

@ -196,7 +196,9 @@ export default class ExtensionPlatform {
const nonce = parseInt(txMeta.txParams.nonce, 16);
const title = 'Confirmed transaction';
const message = `Transaction ${nonce} confirmed! View on Etherscan`;
const message = `Transaction ${nonce} confirmed! ${
url.length ? 'View on Etherscan' : ''
}`;
this._showNotification(title, message, url);
}

@ -16,6 +16,17 @@ const createStyleTasks = require('./styles');
const createStaticAssetTasks = require('./static');
const createEtcTasks = require('./etc');
// packages required dynamically via browserify configuration in dependencies
require('loose-envify');
require('@babel/plugin-proposal-object-rest-spread');
require('@babel/plugin-transform-runtime');
require('@babel/plugin-proposal-class-properties');
require('@babel/plugin-proposal-optional-chaining');
require('@babel/plugin-proposal-nullish-coalescing-operator');
require('@babel/preset-env');
require('@babel/preset-react');
require('@babel/core');
const browserPlatforms = ['firefox', 'chrome', 'brave', 'opera'];
defineAllTasks();

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

Loading…
Cancel
Save