Merge pull request #9976 from MetaMask/Version-v8.1.6

Version v8.1.6 RC
feature/default_network_editable
Mark Stacey 4 years ago committed by GitHub
commit 52e428fbe6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 85
      .circleci/config.yml
  2. 1
      .eslintrc.js
  3. 1
      .github/dependabot.yml
  4. 36
      .storybook/main.js
  5. 7
      .storybook/preview.js
  6. 41
      .storybook/webpack.config.js
  7. 11
      CHANGELOG.md
  8. 15
      app/_locales/am/messages.json
  9. 15
      app/_locales/ar/messages.json
  10. 15
      app/_locales/bg/messages.json
  11. 15
      app/_locales/bn/messages.json
  12. 15
      app/_locales/ca/messages.json
  13. 15
      app/_locales/da/messages.json
  14. 15
      app/_locales/de/messages.json
  15. 15
      app/_locales/el/messages.json
  16. 45
      app/_locales/en/messages.json
  17. 15
      app/_locales/es/messages.json
  18. 15
      app/_locales/es_419/messages.json
  19. 15
      app/_locales/et/messages.json
  20. 15
      app/_locales/fa/messages.json
  21. 15
      app/_locales/fi/messages.json
  22. 15
      app/_locales/fil/messages.json
  23. 15
      app/_locales/fr/messages.json
  24. 15
      app/_locales/he/messages.json
  25. 15
      app/_locales/hi/messages.json
  26. 15
      app/_locales/hr/messages.json
  27. 15
      app/_locales/hu/messages.json
  28. 15
      app/_locales/id/messages.json
  29. 18
      app/_locales/it/messages.json
  30. 3
      app/_locales/ja/messages.json
  31. 15
      app/_locales/kn/messages.json
  32. 15
      app/_locales/ko/messages.json
  33. 15
      app/_locales/lt/messages.json
  34. 15
      app/_locales/lv/messages.json
  35. 15
      app/_locales/ms/messages.json
  36. 15
      app/_locales/no/messages.json
  37. 15
      app/_locales/pl/messages.json
  38. 15
      app/_locales/pt_BR/messages.json
  39. 15
      app/_locales/ro/messages.json
  40. 15
      app/_locales/ru/messages.json
  41. 15
      app/_locales/sk/messages.json
  42. 15
      app/_locales/sl/messages.json
  43. 15
      app/_locales/sr/messages.json
  44. 15
      app/_locales/sv/messages.json
  45. 15
      app/_locales/sw/messages.json
  46. 6
      app/_locales/th/messages.json
  47. 15
      app/_locales/uk/messages.json
  48. 15
      app/_locales/zh_CN/messages.json
  49. 15
      app/_locales/zh_TW/messages.json
  50. BIN
      app/images/icon-48.png
  51. 13
      app/manifest/_base.json
  52. 2
      app/phishing.html
  53. 3
      app/popup.html
  54. 8
      app/scripts/background.js
  55. 4
      app/scripts/controllers/detect-tokens.js
  56. 366
      app/scripts/controllers/metametrics.js
  57. 3
      app/scripts/controllers/network/createInfuraClient.js
  58. 3
      app/scripts/controllers/network/createJsonRpcClient.js
  59. 3
      app/scripts/controllers/network/createMetamaskMiddleware.js
  60. 2
      app/scripts/controllers/network/middleware/pending.js
  61. 7
      app/scripts/controllers/network/network.js
  62. 5
      app/scripts/controllers/permissions/index.js
  63. 2
      app/scripts/controllers/permissions/permissionsMethodMiddleware.js
  64. 148
      app/scripts/controllers/preferences.js
  65. 2
      app/scripts/controllers/threebox.js
  66. 18
      app/scripts/controllers/transactions/index.js
  67. 9
      app/scripts/controllers/transactions/tx-gas-utils.js
  68. 7
      app/scripts/initSentry.js
  69. 2
      app/scripts/lib/enums.js
  70. 37
      app/scripts/lib/freezeGlobals.js
  71. 4
      app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js
  72. 3
      app/scripts/lib/rpc-method-middleware/handlers/index.js
  73. 10
      app/scripts/lib/rpc-method-middleware/handlers/log-web3-usage.js
  74. 40
      app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js
  75. 101
      app/scripts/lib/segment.js
  76. 12
      app/scripts/lib/setupWeb3.js
  77. 99
      app/scripts/metamask-controller.js
  78. 44
      app/scripts/migrations/049.js
  79. 32
      app/scripts/migrations/050.js
  80. 2
      app/scripts/migrations/index.js
  81. 7
      app/scripts/runLockdown.js
  82. 14
      app/scripts/ui.js
  83. 5
      development/build/index.js
  84. 86
      development/build/scripts.js
  85. 12
      development/build/static.js
  86. 6
      development/build/task.js
  87. 33
      package.json
  88. 140
      shared/constants/metametrics.js
  89. 4
      shared/constants/tokens.js
  90. 3
      shared/modules/README.md
  91. 302
      shared/modules/metametrics.js
  92. 5914
      test/data/fetch-mocks.json
  93. 1
      test/e2e/address-book.spec.js
  94. 1
      test/e2e/benchmark.js
  95. 1
      test/e2e/ethereum-on.spec.js
  96. 168
      test/e2e/fixtures/metrics-enabled/state.json
  97. 1
      test/e2e/from-import-ui.spec.js
  98. 34
      test/e2e/helpers.js
  99. 1
      test/e2e/incremental-security.spec.js
  100. 1
      test/e2e/metamask-responsive-ui.spec.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -18,6 +18,9 @@ workflows:
- prep-build-test:
requires:
- prep-deps
- prep-build-test-metrics:
requires:
- prep-deps
- prep-build-storybook:
requires:
- prep-deps
@ -34,6 +37,12 @@ workflows:
- test-e2e-firefox:
requires:
- prep-build-test
- test-e2e-chrome-metrics:
requires:
- prep-build-test-metrics
- test-e2e-firefox-metrics:
requires:
- prep-build-test-metrics
- test-unit:
requires:
- prep-deps
@ -58,6 +67,8 @@ workflows:
- test-mozilla-lint
- test-e2e-chrome
- test-e2e-firefox
- test-e2e-chrome-metrics
- test-e2e-firefox-metrics
- benchmark:
requires:
- prep-build-test
@ -122,8 +133,9 @@ jobs:
prep-build:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
resource_class: medium+
environment:
NODE_OPTIONS: --max_old_space_size=1024
NODE_OPTIONS: --max_old_space_size=2048
steps:
- checkout
- attach_workspace:
@ -143,8 +155,9 @@ jobs:
prep-build-test:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
resource_class: medium+
environment:
NODE_OPTIONS: --max_old_space_size=1024
NODE_OPTIONS: --max_old_space_size=2048
steps:
- checkout
- attach_workspace:
@ -160,6 +173,27 @@ jobs:
paths:
- dist-test
prep-build-test-metrics:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
resource_class: medium+
environment:
NODE_OPTIONS: --max_old_space_size=2048
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Build extension for testing metrics
command: yarn build:test:metrics
- run:
name: Move test build to 'dist-test-metrics' to avoid conflict with production build
command: mv ./dist ./dist-test-metrics
- persist_to_workspace:
root: .
paths:
- dist-test-metrics
prep-build-storybook:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
@ -243,6 +277,28 @@ jobs:
path: test-artifacts
destination: test-artifacts
test-e2e-chrome-metrics:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Move test build to dist
command: mv ./dist-test-metrics ./dist
- run:
name: test:e2e:chrome:metrics
command: |
if .circleci/scripts/test-run-e2e
then
yarn test:e2e:chrome:metrics
fi
no_output_timeout: 20m
- store_artifacts:
path: test-artifacts
destination: test-artifacts
test-e2e-firefox:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
@ -268,6 +324,31 @@ jobs:
path: test-artifacts
destination: test-artifacts
test-e2e-firefox-metrics:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps:
- checkout
- run:
name: Install Firefox
command: ./.circleci/scripts/firefox-install
- attach_workspace:
at: .
- run:
name: Move test build to dist
command: mv ./dist-test-metrics ./dist
- run:
name: test:e2e:firefox:metrics
command: |
if .circleci/scripts/test-run-e2e
then
yarn test:e2e:firefox:metrics
fi
no_output_timeout: 20m
- store_artifacts:
path: test-artifacts
destination: test-artifacts
benchmark:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88

@ -197,6 +197,7 @@ module.exports = {
'stylelint.config.js',
'development/**/*.js',
'test/e2e/**/*.js',
'test/lib/wait-until-called.js',
'test/env.js',
'test/setup.js',
],

@ -9,5 +9,4 @@ updates:
interval: "daily"
allow:
- dependency-name: "@metamask/*"
- dependency-name: "eth-contract-metadata"
versioning-strategy: "lockfile-only"

@ -1,3 +1,7 @@
const path = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
stories: ['../ui/app/**/*.stories.js'],
addons: [
@ -5,4 +9,36 @@ module.exports = {
'@storybook/addon-actions',
'@storybook/addon-backgrounds'
],
webpackFinal: async (config) => {
config.module.strictExportPresence = true
config.module.rules.push({
test: /\.scss$/,
loaders: [
'style-loader',
{
loader: 'css-loader',
options: {
import: false,
url: false,
},
},
'resolve-url-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
})
config.plugins.push(new CopyWebpackPlugin({
patterns: [
{
from: path.join('node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'),
to: path.join('fonts', 'fontawesome'),
},
],
}))
return config
},
}

@ -1,6 +1,6 @@
import React from 'react'
import { addDecorator, addParameters } from '@storybook/react'
import { withKnobs } from '@storybook/addon-knobs/react'
import { withKnobs } from '@storybook/addon-knobs'
import { I18nProvider, LegacyI18nProvider } from '../ui/app/contexts/i18n'
import { Provider } from 'react-redux'
import configureStore from '../ui/app/store/store'
@ -8,10 +8,13 @@ import '../ui/app/css/index.scss'
import en from '../app/_locales/en/messages'
addParameters({
backgrounds: [
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#FFFFFF'},
{ name: 'dark', value: '#333333' },
],
}
})
const styles = {

@ -1,41 +0,0 @@
const path = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
module: {
strictExportPresence: true,
rules: [
{
test: /\.scss$/,
loaders: [
'style-loader',
{
loader: 'css-loader',
options: {
import: false,
url: false,
},
},
'resolve-url-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
],
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.join('node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'),
to: path.join('fonts', 'fontawesome'),
},
],
}),
],
}

@ -2,6 +2,17 @@
## Current Develop Branch
## 8.1.6 Wed Dec 02 2020
- [#9916](https://github.com/MetaMask/metamask-extension/pull/9916): Fix QR code scans interpretting payment requests as token addresses
- [#9847](https://github.com/MetaMask/metamask-extension/pull/9847): Add alt text for images in list items
- [#9960](https://github.com/MetaMask/metamask-extension/pull/9960): Ensure watchAsset returns errors for invalid token symbols
- [#9968](https://github.com/MetaMask/metamask-extension/pull/9968): Adds tokens from v1.19.0 of metamask/contract-metadata to add token lists
- [#9970](https://github.com/MetaMask/metamask-extension/pull/9970): Etherscan links support Goerli network
- [#9899](https://github.com/MetaMask/metamask-extension/pull/9899): Show price impact warnings on swaps quote screen
- [#9867](https://github.com/MetaMask/metamask-extension/pull/9867): Replace use of ethgasstation
- [#9984](https://github.com/MetaMask/metamask-extension/pull/9984): Show correct gas estimates when users don't have sufficient balance for contract transaction
- [#9993](https://github.com/MetaMask/metamask-extension/pull/9993): Add 48x48 MetaMask icon for use by browsers
## 8.1.5 Wed Nov 18 2020
- [#9871](https://github.com/MetaMask/metamask-extension/pull/9871): Show send text upon hover in main asset list
- [#9855](https://github.com/MetaMask/metamask-extension/pull/9855): Make edit icon and account name in account details modal focusable

@ -167,9 +167,6 @@
"chainId": {
"message": "የሰንሰለት መታወቂያ"
},
"chartOnlyAvailableEth": {
"message": "ቻርት የሚገኘው በ Ethereum አውታረ መረቦች ላይ ብቻ ነው።"
},
"chromeRequiredForHardwareWallets": {
"message": "ከሃርድዌርዎ ቋት ጋር ለመገናኘት MetaMask በ Google Chrome ላይ መጠቀም አለብዎት።"
},
@ -398,9 +395,6 @@
"fast": {
"message": "ፈጣን"
},
"faster": {
"message": "በፍጥነት"
},
"fiat": {
"message": "ፊያት",
"description": "Exchange type"
@ -579,9 +573,6 @@
"links": {
"message": "ማስፈንጠሪያዎች"
},
"liveGasPricePredictions": {
"message": "ቀጥታ የነዳጅ ዋጋ ትንበያዎች"
},
"loadMore": {
"message": "ተጨማሪ ጫን"
},
@ -1015,9 +1006,6 @@
"slow": {
"message": "ቀስ"
},
"slower": {
"message": "ዘገምተኛ"
},
"somethingWentWrong": {
"message": "ኤጭ! የሆነ ችግር ተፈጥሯል።"
},
@ -1162,9 +1150,6 @@
"transactionSubmitted": {
"message": "ግብይቱ የቀረበው በነዳጅ ዋጋ $1በ$2ነው።"
},
"transactionTime": {
"message": "የግብይት ጊዜ"
},
"transactionUpdated": {
"message": "ግብይት የዘመነው በ $2ነው።"
},

@ -167,9 +167,6 @@
"chainId": {
"message": "معرّف السلسلة"
},
"chartOnlyAvailableEth": {
"message": "الرسم البياني متاح فقط على شبكات إيثيريوم."
},
"chromeRequiredForHardwareWallets": {
"message": "تحتاج إلى استخدام MetaMask على Google Chrome للاتصال بمحفظة الأجهزة الخاصة بك."
},
@ -398,9 +395,6 @@
"fast": {
"message": "سريع"
},
"faster": {
"message": "أسرع"
},
"fileImportFail": {
"message": "استيراد الملف لا ينجح؟ انقر هنا!",
"description": "Helps user import their account from a JSON file"
@ -575,9 +569,6 @@
"links": {
"message": "الروابط"
},
"liveGasPricePredictions": {
"message": "توقعات أسعار الجاس الحية"
},
"loadMore": {
"message": "تحميل المزيد"
},
@ -1011,9 +1002,6 @@
"slow": {
"message": "بطيء"
},
"slower": {
"message": "أبطأ"
},
"somethingWentWrong": {
"message": "عذراً! حدث خطأ ما."
},
@ -1158,9 +1146,6 @@
"transactionSubmitted": {
"message": "تم تقديم المعاملة برسوم $1 من عملة جاس في $2."
},
"transactionTime": {
"message": "وقت المعاملة"
},
"transactionUpdated": {
"message": "تم تحديث المعاملة في $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "Идентификатор на веригата"
},
"chartOnlyAvailableEth": {
"message": "Диаграмата е достъпна само в мрежи на Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "За да се свържете с хардуерния си портфейл, трябва да използвате MetaMask в Google Chrome."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Бързо"
},
"faster": {
"message": "По-бързо"
},
"fileImportFail": {
"message": "Импортирането на файл не работи? Натиснете тук!",
"description": "Helps user import their account from a JSON file"
@ -575,9 +569,6 @@
"links": {
"message": "Връзки"
},
"liveGasPricePredictions": {
"message": "Прогнози на живо за цената на газа"
},
"loadMore": {
"message": "Зареди повече"
},
@ -1014,9 +1005,6 @@
"slow": {
"message": "Бавно"
},
"slower": {
"message": "По-бавно"
},
"somethingWentWrong": {
"message": "Упс! Нещо се обърка."
},
@ -1161,9 +1149,6 @@
"transactionSubmitted": {
"message": "Транзакция, изпратена с такса за газ от $1 при $2."
},
"transactionTime": {
"message": "Време на транзакция"
},
"transactionUpdated": {
"message": "Транзакцията е актуализирана на $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "চন আইডি"
},
"chartOnlyAvailableEth": {
"message": "শর Ethereum নটওয়কগিট উপলভয। "
},
"chromeRequiredForHardwareWallets": {
"message": "আপনর হডওয়র ওয়র সগ করত আপন Google Chrome এ MetaMask বযবহর করত হব। "
},
@ -398,9 +395,6 @@
"fast": {
"message": "দত"
},
"faster": {
"message": "দততর"
},
"fiat": {
"message": "ফিট",
"description": "Exchange type"
@ -579,9 +573,6 @@
"links": {
"message": "লিকসমহ"
},
"liveGasPricePredictions": {
"message": "সরসরির মর অনন"
},
"loadMore": {
"message": "আরও লড করন"
},
@ -1018,9 +1009,6 @@
"slow": {
"message": "মনথর"
},
"slower": {
"message": "ধর গতির"
},
"somethingWentWrong": {
"message": "ওহ! কি সমস হয়।"
},
@ -1165,9 +1153,6 @@
"transactionSubmitted": {
"message": "$2 এ $1 এর গস ফ সহ লনদন জম কর হয়।"
},
"transactionTime": {
"message": "লনদর সময়"
},
"transactionUpdated": {
"message": "লনদন $2 এ আপডট কর হয়।"
},

@ -164,9 +164,6 @@
"chainId": {
"message": "Cadena ID"
},
"chartOnlyAvailableEth": {
"message": "Mostra només els disponibles a les xarxes Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Necessites fer servir MetaMask amb Google Chrome per a connectar-te al teu Moneder Hardware."
},
@ -395,9 +392,6 @@
"fast": {
"message": "Ràpid"
},
"faster": {
"message": "Més ràpid"
},
"fileImportFail": {
"message": "La importació no funciona? Fes clic aquí!",
"description": "Helps user import their account from a JSON file"
@ -566,9 +560,6 @@
"links": {
"message": "Enllaços"
},
"liveGasPricePredictions": {
"message": "Prediccions del preu del gas en directe"
},
"loadMore": {
"message": "Carregar Més"
},
@ -996,9 +987,6 @@
"slow": {
"message": "Lent"
},
"slower": {
"message": "Més lent"
},
"somethingWentWrong": {
"message": "Ui! Alguna cosa ha fallat."
},
@ -1134,9 +1122,6 @@
"transactionSubmitted": {
"message": "Transacció enviada amb un preu del gas de $1 a $2."
},
"transactionTime": {
"message": "Temps de transacció"
},
"transactionUpdated": {
"message": "Transacció actualitzada a $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "Kæde-ID"
},
"chartOnlyAvailableEth": {
"message": "Skema kun tilgængeligt på Ethereum-netværk."
},
"chromeRequiredForHardwareWallets": {
"message": "Du skal bruge MetaMask i Google Chrome for at forbinde med din Hardware Wallet."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Hurtig"
},
"faster": {
"message": "Hurtigere"
},
"fileImportFail": {
"message": "Virker filimportering ikke? Klik her!",
"description": "Helps user import their account from a JSON file"
@ -569,9 +563,6 @@
"likeToAddTokens": {
"message": "Ønsker du at tilføje disse tokens?"
},
"liveGasPricePredictions": {
"message": "Live forudsigelser af brændstofpriser"
},
"loadMore": {
"message": "Indlæs Mere"
},
@ -996,9 +987,6 @@
"slow": {
"message": "Langsom"
},
"slower": {
"message": "Langsommere"
},
"somethingWentWrong": {
"message": "Ups! Noget gik galt."
},
@ -1134,9 +1122,6 @@
"transactionSubmitted": {
"message": "Transaktion indsendt med brændstofgebyr på $1 til $2."
},
"transactionTime": {
"message": "Transaktionstid"
},
"transactionUpdated": {
"message": "Transaktion opdateret til $2."
},

@ -158,9 +158,6 @@
"cancelled": {
"message": "Abgebrochen"
},
"chartOnlyAvailableEth": {
"message": "Die Grafik ist nur in Ethereum-Netzwerken verfügbar."
},
"chromeRequiredForHardwareWallets": {
"message": "Sie müssen MetaMask unter Google Chrome nutzen, um sich mit Ihrem Hardware-Wallet zu verbinden."
},
@ -386,9 +383,6 @@
"fast": {
"message": "Schnell"
},
"faster": {
"message": "Schneller"
},
"fiat": {
"message": "FIAT",
"description": "Exchange type"
@ -564,9 +558,6 @@
"likeToAddTokens": {
"message": "Möchtest du diese Token hinzufügen?"
},
"liveGasPricePredictions": {
"message": "Live-Gaspreisprognosen"
},
"loadMore": {
"message": "Mehr laden"
},
@ -987,9 +978,6 @@
"slow": {
"message": "Langsam"
},
"slower": {
"message": "Langsamer"
},
"somethingWentWrong": {
"message": "Hoppla! Da hat etwas nicht geklappt."
},
@ -1128,9 +1116,6 @@
"transactionSubmitted": {
"message": "Transaktion mit einer Gasgebühr von $1 bei $2 übermittelt."
},
"transactionTime": {
"message": "Transaktionszeit"
},
"transactionUpdated": {
"message": "Transaktion für $2 aktualisiert."
},

@ -164,9 +164,6 @@
"chainId": {
"message": "Αναγνωριστικό Αλυσίδας"
},
"chartOnlyAvailableEth": {
"message": "Το διάγραμμα είναι διαθέσιμο μόνο σε δίκτυα Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Θα πρέπει να χρησιμοποιήσετε το MetaMask στο Google Chrome για να συνδεθείτε στο Πορτοφόλι Υλικού."
},
@ -395,9 +392,6 @@
"fast": {
"message": "Γρήγορα"
},
"faster": {
"message": "Πιο γρήγορα"
},
"fiat": {
"message": "Εντολή",
"description": "Exchange type"
@ -576,9 +570,6 @@
"links": {
"message": "Σύνδεσμοι"
},
"liveGasPricePredictions": {
"message": "Ζωντανές Προβλέψεις Τιμής Καυσίμου"
},
"loadMore": {
"message": "Φόρτωση Περισσότερων"
},
@ -1015,9 +1006,6 @@
"slow": {
"message": "Αργά"
},
"slower": {
"message": "Πιο αργά"
},
"somethingWentWrong": {
"message": "Ουπς! Κάτι πήγε στραβά."
},
@ -1159,9 +1147,6 @@
"transactionSubmitted": {
"message": "Η συναλλαγή στάλθηκε με τέλος gas του $1 σε $2."
},
"transactionTime": {
"message": "Χρόνος Συναλλαγής"
},
"transactionUpdated": {
"message": "Η συναλλαγή ενημερώθηκε σε $2."
},

@ -245,9 +245,6 @@
"chainId": {
"message": "Chain ID"
},
"chartOnlyAvailableEth": {
"message": "Chart only available on Ethereum networks."
},
"chromeRequiredForHardwareWallets": {
"message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet."
},
@ -660,9 +657,6 @@
"fast": {
"message": "Fast"
},
"faster": {
"message": "Faster"
},
"fastest": {
"message": "Fastest"
},
@ -914,9 +908,6 @@
"links": {
"message": "Links"
},
"liveGasPricePredictions": {
"message": "Live Gas Price Predictions"
},
"loadMore": {
"message": "Load More"
},
@ -1491,9 +1482,6 @@
"showSeedPhrase": {
"message": "Show seed phrase"
},
"showTransactionTimeDescription": {
"message": "Select this to display pending transaction time estimates in the activity tab while on the Ethereum Mainnet. Note: estimates are approximations based on network conditions."
},
"sigRequest": {
"message": "Signature Request"
},
@ -1515,9 +1503,6 @@
"slow": {
"message": "Slow"
},
"slower": {
"message": "Slower"
},
"somethingWentWrong": {
"message": "Oops! Something went wrong."
},
@ -1654,16 +1639,6 @@
"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."
},
"swapEstimatedTime": {
"message": "Estimated time:"
},
"swapEstimatedTimeCalculating": {
"message": "Calculating..."
},
"swapEstimatedTimeFull": {
"message": "$1 $2",
"description": "This message shows bolded swapEstimatedTime message, which is substited for $1, followed by either the estimated remaining transaction time in mm:ss, or the swapEstimatedTimeCalculating message, which are substituted for $2."
},
"swapFailedErrorDescription": {
"message": "Your funds are safe and still available in your wallet."
},
@ -1741,6 +1716,20 @@
"message": "Your $1 will be added to your account once this transaction has processed.",
"description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol."
},
"swapPriceDifference": {
"message": "You are about to swap $1 $2 (~$3) for $4 $5 (~$6).",
"description": "This message represents the price slippage for the swap. $1 and $4 are a number (ex: 2.89), $2 and $5 are symbols (ex: ETH), and $3 and $6 are fiat currency amounts."
},
"swapPriceDifferenceTitle": {
"message": "Price difference of ~$1%",
"description": "$1 is a number (ex: 1.23) that represents the price difference."
},
"swapPriceDifferenceTooltip": {
"message": "The difference in market prices can be affected by fees taken by intermediaries, size of market, size of trade, or market inefficiencies."
},
"swapPriceDifferenceUnavailable": {
"message": "Market price is unavailable. Make sure you feel comfortable with the returned amount before proceeding."
},
"swapProcessing": {
"message": "Processing"
},
@ -1857,9 +1846,6 @@
"swapsAdvancedOptions": {
"message": "Advanced Options"
},
"swapsAlmostDone": {
"message": "Almost done..."
},
"swapsBestQuote": {
"message": "Best quote"
},
@ -1998,9 +1984,6 @@
"transactionSubmitted": {
"message": "Transaction submitted with gas fee of $1 at $2."
},
"transactionTime": {
"message": "Transaction Time"
},
"transactionUpdated": {
"message": "Transaction updated at $2."
},

@ -139,9 +139,6 @@
"chainId": {
"message": "ID Cadena"
},
"chartOnlyAvailableEth": {
"message": "Tabla solo disponible en redes Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Hay que usar MetaMask en Google Chrome para poder conectarse con tu Monedero Físico."
},
@ -322,9 +319,6 @@
"fast": {
"message": "Rápido"
},
"faster": {
"message": "Más Rápido"
},
"fiat": {
"message": "FIAT",
"description": "Exchange type"
@ -470,9 +464,6 @@
"links": {
"message": "Enlaces"
},
"liveGasPricePredictions": {
"message": "Previsiones en vivo del precio de Gas"
},
"loadMore": {
"message": "Cargar Más"
},
@ -801,9 +792,6 @@
"slow": {
"message": "Lento"
},
"slower": {
"message": "Más lento"
},
"somethingWentWrong": {
"message": "¡Ups! Algo funcionó mal."
},
@ -912,9 +900,6 @@
"transactionSubmitted": {
"message": "Se propuso la transacción con una comisión de gas de $1, en $2."
},
"transactionTime": {
"message": "Tiempo de Transacción"
},
"transactionUpdated": {
"message": "Se actualizó la transacción en $2."
},

@ -164,9 +164,6 @@
"chainId": {
"message": "ID de cadena"
},
"chartOnlyAvailableEth": {
"message": "Chart está disponible únicamente en las redes de Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Debes utilizar MetaMask en Google Chrome para poder conectarte a tu billetera de hardware."
},
@ -395,9 +392,6 @@
"fast": {
"message": "Rápido"
},
"faster": {
"message": "Más rápido"
},
"fiat": {
"message": "Dinero fiduciario",
"description": "Exchange type"
@ -570,9 +564,6 @@
"links": {
"message": "Enlaces"
},
"liveGasPricePredictions": {
"message": "Predicciones del precio del gas en vivo"
},
"loadMore": {
"message": "Cargar más"
},
@ -1003,9 +994,6 @@
"slow": {
"message": "Lento"
},
"slower": {
"message": "Más lento"
},
"somethingWentWrong": {
"message": "¡Vaya! Se produjo un error."
},
@ -1144,9 +1132,6 @@
"transactionSubmitted": {
"message": "Se envió la transacción con una tasa de gas de $1 en $2."
},
"transactionTime": {
"message": "Tiempo de transacción"
},
"transactionUpdated": {
"message": "La transacción se actualizó en $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "Ahela ID"
},
"chartOnlyAvailableEth": {
"message": "Tabel on saadaval vaid Ethereumi võrkudes."
},
"chromeRequiredForHardwareWallets": {
"message": "Riistvararahakoti ühendamiseks peate kasutama MetaMaski Google Chrome'is."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Kiire"
},
"faster": {
"message": "Kiiremini"
},
"fileImportFail": {
"message": "Faili importimine ei toimi? Klõpsake siia!",
"description": "Helps user import their account from a JSON file"
@ -575,9 +569,6 @@
"links": {
"message": "Lingid"
},
"liveGasPricePredictions": {
"message": "Gaasihinna prognoosid reaalajas"
},
"loadMore": {
"message": "Laadi rohkem"
},
@ -1008,9 +999,6 @@
"slow": {
"message": "Aeglane"
},
"slower": {
"message": "Aeglasem"
},
"somethingWentWrong": {
"message": "Oih! Midagi läks valesti."
},
@ -1155,9 +1143,6 @@
"transactionSubmitted": {
"message": "Tehing edastatud gaasihinnaga $1 asukohas $2."
},
"transactionTime": {
"message": "Tehingu aeg"
},
"transactionUpdated": {
"message": "Tehing on uuendatud $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "آی دی زنجیره"
},
"chartOnlyAvailableEth": {
"message": "تنها قابل دسترس را در شبکه های ایتریوم جدول بندی نمایید"
},
"chromeRequiredForHardwareWallets": {
"message": "برای وصل شدن به کیف سخت افزار شما باید MetaMask را در گوگل کروم استفاده نمایید."
},
@ -398,9 +395,6 @@
"fast": {
"message": "سریع"
},
"faster": {
"message": "سریع تر"
},
"fiat": {
"message": "حکم قانونی",
"description": "Exchange type"
@ -579,9 +573,6 @@
"links": {
"message": "لینک ها"
},
"liveGasPricePredictions": {
"message": "پیش بینی های قیمت گاز"
},
"loadMore": {
"message": "بارگیری بیشتر"
},
@ -1018,9 +1009,6 @@
"slow": {
"message": "آهسته"
},
"slower": {
"message": "آهسته تر"
},
"somethingWentWrong": {
"message": "اوه! مشکلی پیش آمده."
},
@ -1165,9 +1153,6 @@
"transactionSubmitted": {
"message": "معامله با فیس گاز 1$1 در 2$2 ارائه شد."
},
"transactionTime": {
"message": "زمان معامله"
},
"transactionUpdated": {
"message": "معامله به 1$2 بروزرسانی شد."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "Ketjun tunnus"
},
"chartOnlyAvailableEth": {
"message": "Kaavio saatavilla vain Ethereum-verkoissa."
},
"chromeRequiredForHardwareWallets": {
"message": "Sinun tarvitsee käyttää MetaMaskia Google Chromessa voidaksesi yhdistää laitteistokukkaroosi."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Nopea"
},
"faster": {
"message": "Nopeammin"
},
"fiat": {
"message": "Kiinteä",
"description": "Exchange type"
@ -579,9 +573,6 @@
"links": {
"message": "Linkit"
},
"liveGasPricePredictions": {
"message": "Polttoaineen hintojen live-ennusteet"
},
"loadMore": {
"message": "Lataa lisää"
},
@ -1015,9 +1006,6 @@
"slow": {
"message": "Hidas"
},
"slower": {
"message": "Hitaammin"
},
"somethingWentWrong": {
"message": "Hupsis! Jotakin meni pieleen."
},
@ -1162,9 +1150,6 @@
"transactionSubmitted": {
"message": "Tapahtuma toimitettu $1 bensataksalla kohdassa $2."
},
"transactionTime": {
"message": "Tapahtuman aika"
},
"transactionUpdated": {
"message": "Tapahtuma päivitetty – $2."
},

@ -146,9 +146,6 @@
"cancelled": {
"message": "Nakansela"
},
"chartOnlyAvailableEth": {
"message": "Available lang ang chart sa mga Ethereum network."
},
"chromeRequiredForHardwareWallets": {
"message": "Kailangan mong gamitin ang MetaMask sa Google Chrome upang makakonekta sa iyong Hardware Wallet."
},
@ -371,9 +368,6 @@
"fast": {
"message": "Mabilis"
},
"faster": {
"message": "Mas Mabilis"
},
"fileImportFail": {
"message": "Hindi gumagana ang pag-import ng file? Mag-click dito!",
"description": "Helps user import their account from a JSON file"
@ -529,9 +523,6 @@
"links": {
"message": "Mga Link"
},
"liveGasPricePredictions": {
"message": "Mga Live na Prediksyon sa Presyo ng Gas"
},
"loadMore": {
"message": "Mag-load Pa"
},
@ -924,9 +915,6 @@
"slow": {
"message": "Mabagal"
},
"slower": {
"message": "Mas Mabagal"
},
"somethingWentWrong": {
"message": "Oops! Nagkaroon ng problema."
},
@ -1062,9 +1050,6 @@
"transactionSubmitted": {
"message": "Nasumite ang transaksyon nang may gas fee na $1 sa $2."
},
"transactionTime": {
"message": "Oras ng Transaksyon"
},
"transactionUpdated": {
"message": "Na-update ang transaksyon sa $2."
},

@ -158,9 +158,6 @@
"chainId": {
"message": "ID de chaîne"
},
"chartOnlyAvailableEth": {
"message": "Tableau disponible uniquement sur les réseaux Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Pour connecter votre portefeuille hardware, vous devez utiliser MetaMask pour Google Chrome."
},
@ -389,9 +386,6 @@
"fast": {
"message": "Rapide"
},
"faster": {
"message": "Plus rapide"
},
"fiat": {
"message": "FIAT",
"description": "Exchange type"
@ -573,9 +567,6 @@
"links": {
"message": "Liens"
},
"liveGasPricePredictions": {
"message": "Prévisions des prix de l'essence en direct"
},
"loadMore": {
"message": "Charger plus"
},
@ -1000,9 +991,6 @@
"slow": {
"message": "Lente"
},
"slower": {
"message": "Plus lent"
},
"somethingWentWrong": {
"message": "Oups ! Quelque chose a mal tourné. "
},
@ -1141,9 +1129,6 @@
"transactionSubmitted": {
"message": "Transaction envoyée sur $2."
},
"transactionTime": {
"message": "Heure de la transaction"
},
"transactionUpdated": {
"message": "Transaction mise à jour sur $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "מזהה שרשרת"
},
"chartOnlyAvailableEth": {
"message": "טבלה זמינה רק ברשתות אתריום."
},
"chromeRequiredForHardwareWallets": {
"message": "עליך להשתמש ב-MetaMask בגוגל כרום כדי להתחבר לארנק החומרה שלך."
},
@ -398,9 +395,6 @@
"fast": {
"message": "מהיר"
},
"faster": {
"message": "מהר יותר"
},
"fiat": {
"message": "פיאט",
"description": "Exchange type"
@ -579,9 +573,6 @@
"links": {
"message": "קישורים"
},
"liveGasPricePredictions": {
"message": "תחזיות מחירי גז בשידור חי"
},
"loadMore": {
"message": "טען עוד"
},
@ -1012,9 +1003,6 @@
"slow": {
"message": "אטי"
},
"slower": {
"message": "לאט יותר"
},
"somethingWentWrong": {
"message": "אופס! משהו השתבש."
},
@ -1159,9 +1147,6 @@
"transactionSubmitted": {
"message": "עסקה הוגשה עם עמלת דלק של $1 ב-$2."
},
"transactionTime": {
"message": "זמן העסקה"
},
"transactionUpdated": {
"message": "העסקה עודכנה ב- $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "चन आई.ड."
},
"chartOnlyAvailableEth": {
"message": "कवल ईथरअम नटवरक पर उपलबध चट।"
},
"chromeRequiredForHardwareWallets": {
"message": "अपनडवयर वट स कनट करनिए आपक Google Chrome म MetaMask क उपयग करन ज़र।"
},
@ -398,9 +395,6 @@
"fast": {
"message": "त"
},
"faster": {
"message": "तर"
},
"fiat": {
"message": "फ़ट",
"description": "Exchange type"
@ -576,9 +570,6 @@
"likeToAddTokens": {
"message": "क आप इन टकन क?"
},
"liveGasPricePredictions": {
"message": "लइव गस कमत क भवियव"
},
"loadMore": {
"message": "और लड कर"
},
@ -1012,9 +1003,6 @@
"slow": {
"message": "ध"
},
"slower": {
"message": "ध"
},
"somethingWentWrong": {
"message": "ओह! कछ गलत ह गय।"
},
@ -1159,9 +1147,6 @@
"transactionSubmitted": {
"message": "$2 पर $1 कस शक कथ टशन दरज कि गय।"
},
"transactionTime": {
"message": "टशन क समय"
},
"transactionUpdated": {
"message": "$2 पर टशन अपडट कि गय।"
},

@ -167,9 +167,6 @@
"chainId": {
"message": "Identifikacijska oznaka bloka"
},
"chartOnlyAvailableEth": {
"message": "Grafikon je dostupan samo na mrežama Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Trebate upotrebljavati MetaMask u pregledniku Google Chrome kako biste ga povezali s vašim hardverskim novčanikom."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Brzo"
},
"faster": {
"message": "Brže"
},
"fileImportFail": {
"message": "Uvoženje datoteke ne radi? Kliknite ovdje.",
"description": "Helps user import their account from a JSON file"
@ -575,9 +569,6 @@
"links": {
"message": "Poveznice"
},
"liveGasPricePredictions": {
"message": "Predviđanja uživo za cijenu goriva"
},
"loadMore": {
"message": "Učitaj više"
},
@ -1011,9 +1002,6 @@
"slow": {
"message": "Sporo"
},
"slower": {
"message": "Sporije"
},
"somethingWentWrong": {
"message": "Ups! Nešto je pošlo po zlu."
},
@ -1155,9 +1143,6 @@
"transactionSubmitted": {
"message": "Transakcija je poslana s naknadom za gorivo od $1 u $2."
},
"transactionTime": {
"message": "Vrijeme transkacije"
},
"transactionUpdated": {
"message": "Transakcija je ažurirana u $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "Lánc azonosítója"
},
"chartOnlyAvailableEth": {
"message": "A diagram csak Ethereum hálózatokon érhető el"
},
"chromeRequiredForHardwareWallets": {
"message": "A MetaMask-ot Google Chrome-mal kell használnia a Hardveres pénztárcához való csatlakozáshoz."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Gyors"
},
"faster": {
"message": "Gyorsabban"
},
"fileImportFail": {
"message": "Nem működik a fájl importálása? Kattintson ide!",
"description": "Helps user import their account from a JSON file"
@ -575,9 +569,6 @@
"links": {
"message": "Linkek"
},
"liveGasPricePredictions": {
"message": "Élő előrejelzések gázárak alakulására"
},
"loadMore": {
"message": "Továbbiak betöltése"
},
@ -1011,9 +1002,6 @@
"slow": {
"message": "Lassú"
},
"slower": {
"message": "Lassabban"
},
"somethingWentWrong": {
"message": "Hoppá! Valami hiba történt..."
},
@ -1155,9 +1143,6 @@
"transactionSubmitted": {
"message": "Tranzakció jóváhagyva $1 üzemanyag költséggel $2-kor."
},
"transactionTime": {
"message": "Tranzakció ideje"
},
"transactionUpdated": {
"message": "Tranzakció frissítve $2-nál"
},

@ -167,9 +167,6 @@
"chainId": {
"message": "ID Rantai"
},
"chartOnlyAvailableEth": {
"message": "Grafik hanya tersedia pada jaringan Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Anda harus menggunakan MetaMask di Google Chrome untuk menyambung ke dompet Perangkat Keras Anda."
},
@ -392,9 +389,6 @@
"fast": {
"message": "Cepat"
},
"faster": {
"message": "Lebih Cepat"
},
"fileImportFail": {
"message": "Impor berkas tidak tersedia? Klik di sini!",
"description": "Helps user import their account from a JSON file"
@ -569,9 +563,6 @@
"links": {
"message": "Tautan"
},
"liveGasPricePredictions": {
"message": "Prediksi Harga Gas Langsung"
},
"loadMore": {
"message": "Muat Lainnya"
},
@ -1002,9 +993,6 @@
"slow": {
"message": "Lambat"
},
"slower": {
"message": "Lebih Lambat"
},
"somethingWentWrong": {
"message": "Ups! Terjadi sesuatu."
},
@ -1143,9 +1131,6 @@
"transactionSubmitted": {
"message": "Transaksi diajukan dengan biaya gas sebesar $1 di $2."
},
"transactionTime": {
"message": "Waktu Transaksi"
},
"transactionUpdated": {
"message": "Transaksi diperbarui di $2."
},

@ -229,9 +229,6 @@
"chainId": {
"message": "Blockchain ID"
},
"chartOnlyAvailableEth": {
"message": "Grafico disponibile solo per le reti Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Devi usare MetaMask con Google Chrome per connettere il tuo Portafoglio Hardware"
},
@ -630,9 +627,6 @@
"fast": {
"message": "Veloce"
},
"faster": {
"message": "Più veloce"
},
"feeAssociatedRequest": {
"message": "Una tassa è associata a questa richiesta."
},
@ -851,9 +845,6 @@
"links": {
"message": "Collegamenti"
},
"liveGasPricePredictions": {
"message": "Previsione del prezzo del gas in tempo reale"
},
"loadMore": {
"message": "Carica di più"
},
@ -1382,9 +1373,6 @@
"showSeedPhrase": {
"message": "Mostra frase seed"
},
"showTransactionTimeDescription": {
"message": "Seleziona per mostrare nella scheda attività una stima dei tempi di transazione per le transazioni in corso sulla rete Ethereum principale. Nota: la stima è approssimativa basata sulle condizioni della rete."
},
"sigRequest": {
"message": "Firma Richiesta"
},
@ -1406,9 +1394,6 @@
"slow": {
"message": "Lenta"
},
"slower": {
"message": "Più lenta"
},
"somethingWentWrong": {
"message": "Oops! Qualcosa è andato storto."
},
@ -1600,9 +1585,6 @@
"transactionSubmitted": {
"message": "Transazione inviata alle $2."
},
"transactionTime": {
"message": "Tempo Conferma Transazione"
},
"transactionUpdated": {
"message": "Transazione aggiornata alle $2."
},

@ -115,9 +115,6 @@
"cancel": {
"message": "キャンセル"
},
"chartOnlyAvailableEth": {
"message": "チャートはEthereumネットワークでのみ利用可能です。"
},
"confirm": {
"message": "確認"
},

@ -167,9 +167,6 @@
"chainId": {
"message": "ಚ ID"
},
"chartOnlyAvailableEth": {
"message": "ಎಥಿಯಮವರಗಳಲಿರವಗಳ ಲಭಯವಿತವ."
},
"chromeRequiredForHardwareWallets": {
"message": "ನಿಮ ಹಪರಕಪಡಿವ ಸಲಿ Google Chrome ನಲಿಿಮಗ MetaMask ಅನ ಬಳಸವ ಅಗತಯವಿ."
},
@ -398,9 +395,6 @@
"fast": {
"message": "ವಗ"
},
"faster": {
"message": "ವಗವಿ"
},
"fiat": {
"message": "ಫಿ",
"description": "Exchange type"
@ -579,9 +573,6 @@
"links": {
"message": "ಲಿಗಳ"
},
"liveGasPricePredictions": {
"message": "ಲಯ ಭವಿಯಗಳ"
},
"loadMore": {
"message": "ಇನನಷಿ"
},
@ -1018,9 +1009,6 @@
"slow": {
"message": "ನಿನ"
},
"slower": {
"message": "ನಿನ"
},
"somethingWentWrong": {
"message": "ಓಹ! ಏನ ತಪಿ."
},
@ -1165,9 +1153,6 @@
"transactionSubmitted": {
"message": "ವಹಿಟನ $2 ನಲಿ $1 ಗಕದಿ ರಚಿಸಲಿ."
},
"transactionTime": {
"message": "ವಹಿ ಸಮಯ"
},
"transactionUpdated": {
"message": "$2 ನಲಿ ವಹಿಟನ ನವಕರಿಸಲಿ."
},

@ -164,9 +164,6 @@
"chainId": {
"message": "체인 ID"
},
"chartOnlyAvailableEth": {
"message": "이더리움 네트워크에서만 사용 가능한 차트."
},
"chromeRequiredForHardwareWallets": {
"message": "하드웨어 지갑을 연결하기 위해서는 구글 크롬에서 메타마스크를 사용하셔야 합니다."
},
@ -395,9 +392,6 @@
"fast": {
"message": "빠름"
},
"faster": {
"message": "빨라짐"
},
"fiat": {
"message": "FIAT",
"description": "Exchange type"
@ -573,9 +567,6 @@
"links": {
"message": "링크"
},
"liveGasPricePredictions": {
"message": "실시간 가스 가격 예측"
},
"loadMore": {
"message": "더 많이 로딩"
},
@ -1009,9 +1000,6 @@
"slow": {
"message": "느림"
},
"slower": {
"message": "느려짐"
},
"somethingWentWrong": {
"message": "헉! 뭔가 잘못됐어요."
},
@ -1156,9 +1144,6 @@
"transactionSubmitted": {
"message": "$1의 가스 요금으로 트랜잭션이 제출됨 $2."
},
"transactionTime": {
"message": "트랜잭션 시간"
},
"transactionUpdated": {
"message": "트랜잭션이 수정됨 $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "Grandinės ID"
},
"chartOnlyAvailableEth": {
"message": "Diagramos yra tik „Ethereum“ tinkluose."
},
"chromeRequiredForHardwareWallets": {
"message": "Norėdami prisijungti prie aparatinės įrangos slaptažodinės, „MetaMask“ naudokitės „Google Chrome“ naršyklėje."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Greitas"
},
"faster": {
"message": "Greičiau"
},
"fiat": {
"message": "Standartinė valiuta",
"description": "Exchange type"
@ -579,9 +573,6 @@
"links": {
"message": "Nuorodos"
},
"liveGasPricePredictions": {
"message": "Tiesioginiai dujų kainos spėjimai"
},
"loadMore": {
"message": "Įkelti daugiau"
},
@ -1018,9 +1009,6 @@
"slow": {
"message": "Lėtas"
},
"slower": {
"message": "Lėčiau"
},
"somethingWentWrong": {
"message": "Vaje! Kažkas negerai."
},
@ -1165,9 +1153,6 @@
"transactionSubmitted": {
"message": "Operacija pateikta $2 su $1 dujų mokesčiu."
},
"transactionTime": {
"message": "Operacijos laikas"
},
"transactionUpdated": {
"message": "Operacija atnaujinta$2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "Ķēdes ID"
},
"chartOnlyAvailableEth": {
"message": "Grafiks pieejams vienīgi Ethereum tīklos."
},
"chromeRequiredForHardwareWallets": {
"message": "MetaMask ir jāpalaiž pārlūkprogrammā Google Chrome, lai varētu pievienot aparatūras maku."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Ātrs"
},
"faster": {
"message": "Ātrāk"
},
"fileImportFail": {
"message": "Vai faila importēšanas iespēja nedarbojas? Klikšķiniet šeit!",
"description": "Helps user import their account from a JSON file"
@ -575,9 +569,6 @@
"links": {
"message": "Saites"
},
"liveGasPricePredictions": {
"message": "Reāllaika Gas cenu prognozes"
},
"loadMore": {
"message": "Ielādēt vairāk"
},
@ -1014,9 +1005,6 @@
"slow": {
"message": "Lēns"
},
"slower": {
"message": "Lēnāk"
},
"somethingWentWrong": {
"message": "Ak vai! Radās problēma."
},
@ -1161,9 +1149,6 @@
"transactionSubmitted": {
"message": "Darījums iesniegts ar maksu par Gas $1 pie $2."
},
"transactionTime": {
"message": "Darījuma ilgums"
},
"transactionUpdated": {
"message": "Darījums atjaunināts $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "ID Rantaian"
},
"chartOnlyAvailableEth": {
"message": "Carta hanya tersedia di rangkaian Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Anda perlu menggunakan MetaMask di Google Chrome untuk menyambung kepada Dompet Perkakasan anda."
},
@ -392,9 +389,6 @@
"fast": {
"message": "Cepat"
},
"faster": {
"message": "Lebih cepat"
},
"fileImportFail": {
"message": "Pengimportan fail tidak berfungsi? Klik di sini!",
"description": "Helps user import their account from a JSON file"
@ -565,9 +559,6 @@
"links": {
"message": "Pautan"
},
"liveGasPricePredictions": {
"message": "Ramalan Harga Gas Langsung"
},
"loadMore": {
"message": "Muat Lagi"
},
@ -995,9 +986,6 @@
"slow": {
"message": "Perlahan"
},
"slower": {
"message": "Lebih Perlahan"
},
"somethingWentWrong": {
"message": "Alamak! Ada yang tak kena."
},
@ -1139,9 +1127,6 @@
"transactionSubmitted": {
"message": "Transaksi dihantar dengan fi gas sebanyak $1 pada $2."
},
"transactionTime": {
"message": "Masa Transaksi"
},
"transactionUpdated": {
"message": "Transaksi dikemaskini pada $2."
},

@ -164,9 +164,6 @@
"chainId": {
"message": "Blokkjede "
},
"chartOnlyAvailableEth": {
"message": "Diagram kun tilgjengelig på Ethereum-nettverk."
},
"chromeRequiredForHardwareWallets": {
"message": "Du må bruke MetaMask på Google Chrome for å koble deg til maskinvare-lommeboken."
},
@ -395,9 +392,6 @@
"fast": {
"message": "Høy"
},
"faster": {
"message": "Raskere "
},
"fileImportFail": {
"message": "Virker ikke filimporteringen? Trykk her!",
"description": "Helps user import their account from a JSON file"
@ -566,9 +560,6 @@
"links": {
"message": "Lenker "
},
"liveGasPricePredictions": {
"message": "Prisforutsigelse av datakraft i sanntid"
},
"loadMore": {
"message": "Last mer "
},
@ -996,9 +987,6 @@
"slow": {
"message": "Lav"
},
"slower": {
"message": "Saktere"
},
"somethingWentWrong": {
"message": "Oisann! Noe gikk galt. "
},
@ -1137,9 +1125,6 @@
"transactionSubmitted": {
"message": "Transaksjon sendt med datakraftavgift på $1 til $2."
},
"transactionTime": {
"message": "Transaksjonstid"
},
"transactionUpdated": {
"message": "Transaksjonen oppdatert på $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "Identyfikator łańcucha"
},
"chartOnlyAvailableEth": {
"message": "Wykres dostępny tylko w sieciach Ethereum"
},
"chromeRequiredForHardwareWallets": {
"message": "Żeby połączyć się z portfelem sprzętowym, należy uruchomić MetaMask z przeglądarką Google Chrome."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Szybko"
},
"faster": {
"message": "Szybciej"
},
"fiat": {
"message": "FIAT",
"description": "Exchange type"
@ -579,9 +573,6 @@
"links": {
"message": "Łącza"
},
"liveGasPricePredictions": {
"message": "Przewidywanie ceny gazu na żywo"
},
"loadMore": {
"message": "Załaduj więcej"
},
@ -1012,9 +1003,6 @@
"slow": {
"message": "Powoli"
},
"slower": {
"message": "Wolniej"
},
"somethingWentWrong": {
"message": "Ups! Coś poszło nie tak."
},
@ -1153,9 +1141,6 @@
"transactionSubmitted": {
"message": "Transakcja wykonana z opłatą za gaz w wysokości $1 – $2."
},
"transactionTime": {
"message": "Czas transakcji"
},
"transactionUpdated": {
"message": "Transakcja zaktualizowana – $2."
},

@ -161,9 +161,6 @@
"chainId": {
"message": "ID da Cadeia"
},
"chartOnlyAvailableEth": {
"message": "Tabela disponível apenas em redes de Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Você precisa usar o MetaMask no Google Chrome para se conectar à sua Carteira de Hardware."
},
@ -392,9 +389,6 @@
"fast": {
"message": "Rápido"
},
"faster": {
"message": "Mais rápido"
},
"fiat": {
"message": "Ordem",
"description": "Exchange type"
@ -570,9 +564,6 @@
"likeToAddTokens": {
"message": "Deseja adicionar esses tokens?"
},
"liveGasPricePredictions": {
"message": "Previsões de Preços de Gás Ao Vivo"
},
"loadMore": {
"message": "Carregar Mais"
},
@ -1006,9 +997,6 @@
"slow": {
"message": "Lento"
},
"slower": {
"message": "Mais lento"
},
"somethingWentWrong": {
"message": "Opa! Algo deu errado."
},
@ -1147,9 +1135,6 @@
"transactionSubmitted": {
"message": "Transação enviada com taxa de gás de $1 a $2."
},
"transactionTime": {
"message": "Hora da Transação"
},
"transactionUpdated": {
"message": "Transação atualizada às $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "ID lanț"
},
"chartOnlyAvailableEth": {
"message": "Grafic disponibil numai pe rețelele Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Trebuie să folosiți MetaMask în Google Chrome pentru a vă conecta la portofelul hardware."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Rapid"
},
"faster": {
"message": "Mai repede"
},
"fileImportFail": {
"message": "Importarea fișierului nu funcționează? Dați clic aici!",
"description": "Helps user import their account from a JSON file"
@ -569,9 +563,6 @@
"links": {
"message": "Link-uri"
},
"liveGasPricePredictions": {
"message": "Predicții live de preț în gas"
},
"loadMore": {
"message": "Încărcați mai multe"
},
@ -1005,9 +996,6 @@
"slow": {
"message": "Lent"
},
"slower": {
"message": "Mai încet"
},
"somethingWentWrong": {
"message": "Hopa! A apărut o eroare."
},
@ -1149,9 +1137,6 @@
"transactionSubmitted": {
"message": "Tranzacția a fost trimisă, cu o taxă gas de $1 la $2."
},
"transactionTime": {
"message": "Ora tranzacției"
},
"transactionUpdated": {
"message": "Tranzacție actualizată la $2."
},

@ -166,9 +166,6 @@
"chainId": {
"message": "ID сети"
},
"chartOnlyAvailableEth": {
"message": "Диаграмма доступна только в сетях Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Вам необходимо использовать MetaMask в Google Chrome, чтобы подключиться к своему аппаратному кошельку."
},
@ -427,9 +424,6 @@
"fast": {
"message": "Быстро"
},
"faster": {
"message": "Быстрее"
},
"fiat": {
"message": "Валюта",
"description": "Exchange type"
@ -608,9 +602,6 @@
"links": {
"message": "Ссылки"
},
"liveGasPricePredictions": {
"message": "Прогноз цены газа в режиме реального времени"
},
"loadMore": {
"message": "Загрузить еще"
},
@ -1054,9 +1045,6 @@
"slow": {
"message": "Медленно"
},
"slower": {
"message": "Медленнее"
},
"somethingWentWrong": {
"message": "Опс! Что-то пошло не так."
},
@ -1201,9 +1189,6 @@
"transactionSubmitted": {
"message": "Сделка подана с оплатой за газ в размере 1$ за 2$."
},
"transactionTime": {
"message": "Время транзакции"
},
"transactionUpdated": {
"message": "Транзакция обновлена до $2."
},

@ -161,9 +161,6 @@
"chainId": {
"message": "ID reťazca"
},
"chartOnlyAvailableEth": {
"message": "Graf je k dispozícii iba v sieťach Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Ak sa chcete pripojiť k svojej hardvérovej peňaženke, musíte v Google Chrome použiť MetaMask."
},
@ -392,9 +389,6 @@
"fast": {
"message": "Rýchle"
},
"faster": {
"message": "Rýchlejšie"
},
"fiat": {
"message": "FIAT",
"description": "Exchange type"
@ -563,9 +557,6 @@
"links": {
"message": "Odkazy"
},
"liveGasPricePredictions": {
"message": "Predpoveď cien GAS naživo"
},
"loadMore": {
"message": "Načítať viac"
},
@ -981,9 +972,6 @@
"slow": {
"message": "Pomalé"
},
"slower": {
"message": "Pomalší"
},
"somethingWentWrong": {
"message": "Och! Niečo zlyhalo."
},
@ -1122,9 +1110,6 @@
"transactionSubmitted": {
"message": "Transakcia bola odoslaná s poplatkom za GAS z $1 na $2."
},
"transactionTime": {
"message": "Čas transakcie"
},
"transactionUpdated": {
"message": "Transakcia bola aktualizovaná na $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "ID verige"
},
"chartOnlyAvailableEth": {
"message": "Grafikon na voljo le v glavnih omrežjih."
},
"chromeRequiredForHardwareWallets": {
"message": "Za uporabo strojne denarnice potrebujete Google Chrome."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Hiter"
},
"faster": {
"message": "Hitrejši"
},
"fiat": {
"message": "Klasične",
"description": "Exchange type"
@ -570,9 +564,6 @@
"links": {
"message": "Povezave"
},
"liveGasPricePredictions": {
"message": "Napovedi o gas price"
},
"loadMore": {
"message": "Naloži več"
},
@ -1000,9 +991,6 @@
"slow": {
"message": "Počasen"
},
"slower": {
"message": "Počasnejši"
},
"somethingWentWrong": {
"message": "Oops! Nekaj je šlo narobe."
},
@ -1147,9 +1135,6 @@
"transactionSubmitted": {
"message": "Transakcija z gas fee $1 oddana na $2."
},
"transactionTime": {
"message": "Transakcijski čas"
},
"transactionUpdated": {
"message": "Transakcija na $2 spremenjena."
},

@ -164,9 +164,6 @@
"cancelled": {
"message": "Otkazano"
},
"chartOnlyAvailableEth": {
"message": "Grafikon dostupan jedino na mrežama Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Da biste se povezali sa Vašim hardverskim novčanikom morate da koristite MetaMask na Google Chrome-u."
},
@ -395,9 +392,6 @@
"fast": {
"message": "Брзо"
},
"faster": {
"message": "Brže"
},
"fiat": {
"message": "Dekret",
"description": "Exchange type"
@ -576,9 +570,6 @@
"links": {
"message": "Veze"
},
"liveGasPricePredictions": {
"message": "Predviđanja cene gasa uživo"
},
"loadMore": {
"message": "Učitati više"
},
@ -1009,9 +1000,6 @@
"slow": {
"message": "Споро"
},
"slower": {
"message": "Sporije"
},
"somethingWentWrong": {
"message": "Ups! Nešto nije u redu."
},
@ -1150,9 +1138,6 @@
"transactionSubmitted": {
"message": "Transakcija je podnešena sa gas naknadom od $1 na $2."
},
"transactionTime": {
"message": "Vreme transakcije"
},
"transactionUpdated": {
"message": "Transakcija je ažurirana na $2."
},

@ -161,9 +161,6 @@
"cancelled": {
"message": "Avbruten"
},
"chartOnlyAvailableEth": {
"message": "Tabellen är endast tillgänglig på Ethereum-nätverk."
},
"chromeRequiredForHardwareWallets": {
"message": "Du måste använda MetaMask på Google Chrome för att ansluta till din hårdvaruplånbok."
},
@ -392,9 +389,6 @@
"fast": {
"message": "Snabb"
},
"faster": {
"message": "Snabbare"
},
"fileImportFail": {
"message": "Fungerar inte filimporten? Klicka här!",
"description": "Helps user import their account from a JSON file"
@ -569,9 +563,6 @@
"links": {
"message": "Länkar"
},
"liveGasPricePredictions": {
"message": "Liveuppdaterad gaspris-förutsägelser"
},
"loadMore": {
"message": "Ladda mer"
},
@ -1002,9 +993,6 @@
"slow": {
"message": "Långsamt"
},
"slower": {
"message": "Långsammare"
},
"somethingWentWrong": {
"message": "Hoppsan! Något gick fel."
},
@ -1140,9 +1128,6 @@
"transactionSubmitted": {
"message": "Överföring angedd med gasavgift på $1 vid $2."
},
"transactionTime": {
"message": "Transaktionstid"
},
"transactionUpdated": {
"message": "Transaktionen uppdaterades $2."
},

@ -161,9 +161,6 @@
"chainId": {
"message": "Utambulisho wa Mnyororo"
},
"chartOnlyAvailableEth": {
"message": "Zogoa inapatikana kwenye mitandao ya Ethereum pekee."
},
"chromeRequiredForHardwareWallets": {
"message": "Unapaswa kutumia MetaMask kwenye Google Chrome ili kuungnisha kwenye Waleti yako ya Programu Maunzi."
},
@ -392,9 +389,6 @@
"fast": {
"message": "Haraka"
},
"faster": {
"message": "Ingiza"
},
"fileImportFail": {
"message": "Kuhamisha faili hakufanyi kazi? Bofya hapa!",
"description": "Helps user import their account from a JSON file"
@ -566,9 +560,6 @@
"links": {
"message": "Viungo"
},
"liveGasPricePredictions": {
"message": "Utabiri wa moja kwa moja wa Bei ya Gesi"
},
"loadMore": {
"message": "Pak zAIDI"
},
@ -996,9 +987,6 @@
"slow": {
"message": "Polepole"
},
"slower": {
"message": "Taratibu"
},
"somethingWentWrong": {
"message": "Ayaa! Hitilafu fulani imetokea."
},
@ -1143,9 +1131,6 @@
"transactionSubmitted": {
"message": "Muamala umewasilishwa ukiwa na ada ya gesi ya$1 mnamo $2."
},
"transactionTime": {
"message": "Muda wa Muamala"
},
"transactionUpdated": {
"message": "Muamala umesasishwa mnamo $2."
},

@ -190,9 +190,6 @@
"fast": {
"message": "เรว"
},
"faster": {
"message": "เรวขน"
},
"fiat": {
"message": "เงนตรา",
"description": "Exchange type"
@ -477,9 +474,6 @@
"signed": {
"message": "ลงชอแลว"
},
"slower": {
"message": "ชาลง"
},
"stateLogs": {
"message": "บนทกของสถานะ"
},

@ -167,9 +167,6 @@
"chainId": {
"message": "ID мережі"
},
"chartOnlyAvailableEth": {
"message": "Таблиця доступна тільки в мережах Ethereum."
},
"chromeRequiredForHardwareWallets": {
"message": "Щоб підключитися до апаратного гаманця, розширення MetaMask потрібно використовувати в Google Chrome."
},
@ -398,9 +395,6 @@
"fast": {
"message": "Швидка"
},
"faster": {
"message": "Швидше"
},
"fiat": {
"message": "Вказівка",
"description": "Exchange type"
@ -579,9 +573,6 @@
"links": {
"message": "Посилання"
},
"liveGasPricePredictions": {
"message": "Прогнози ціни на пальне наживо"
},
"loadMore": {
"message": "Завантажити більше"
},
@ -1018,9 +1009,6 @@
"slow": {
"message": "Повільна"
},
"slower": {
"message": "Повільніше"
},
"somethingWentWrong": {
"message": "Ой! Щось пішло не так."
},
@ -1165,9 +1153,6 @@
"transactionSubmitted": {
"message": "Транзакція надіслана з оплатою за газ $1 о $2."
},
"transactionTime": {
"message": "Час транзакції"
},
"transactionUpdated": {
"message": "Час оновлення транзакції: $2."
},

@ -167,9 +167,6 @@
"chainId": {
"message": "链 ID"
},
"chartOnlyAvailableEth": {
"message": "聊天功能仅对以太坊网络开放。"
},
"chromeRequiredForHardwareWallets": {
"message": "您需要通过 Google Chrome 浏览器使用 MetaMask ,连接个人硬件钱包。"
},
@ -398,9 +395,6 @@
"fast": {
"message": "快"
},
"faster": {
"message": "快捷操作"
},
"fiat": {
"message": "FIAT",
"description": "Exchange type"
@ -570,9 +564,6 @@
"links": {
"message": "链接"
},
"liveGasPricePredictions": {
"message": "实时天然气价格预测"
},
"loadMore": {
"message": "加载更多"
},
@ -1000,9 +991,6 @@
"slow": {
"message": "慢"
},
"slower": {
"message": "降速"
},
"somethingWentWrong": {
"message": "哎呀!出问题了。"
},
@ -1147,9 +1135,6 @@
"transactionSubmitted": {
"message": "在 $2 提交的交易单,其天然气费为 $1 。"
},
"transactionTime": {
"message": "交易时间"
},
"transactionUpdated": {
"message": "交易单已于 $2 更新。"
},

@ -167,9 +167,6 @@
"chainId": {
"message": "鏈 ID"
},
"chartOnlyAvailableEth": {
"message": "圖表僅適用於以太坊網路。"
},
"chromeRequiredForHardwareWallets": {
"message": "您需要在 Google Chrome 瀏覽器使用 MetaMask 連結您的硬體錢包"
},
@ -398,9 +395,6 @@
"fast": {
"message": "快"
},
"faster": {
"message": "更快"
},
"fiat": {
"message": "法定貨幣",
"description": "Exchange type"
@ -579,9 +573,6 @@
"links": {
"message": "連結"
},
"liveGasPricePredictions": {
"message": "即時 Gas 價格預估"
},
"loadMore": {
"message": "載入更多"
},
@ -997,9 +988,6 @@
"slow": {
"message": "慢"
},
"slower": {
"message": "更慢"
},
"somethingWentWrong": {
"message": "糟糕!出了點問題。"
},
@ -1144,9 +1132,6 @@
"transactionSubmitted": {
"message": "交易送出 手續費 $1 時間 $2"
},
"transactionTime": {
"message": "交易時間"
},
"transactionUpdated": {
"message": "交易狀態更新 時間 $2"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -1,7 +1,13 @@
{
"author": "https://metamask.io",
"background": {
"scripts": ["bg-libs.js", "background.js"],
"scripts": [
"initSentry.js",
"lockdown.cjs",
"runLockdown.js",
"bg-libs.js",
"background.js"
],
"persistent": true
},
"browser_action": {
@ -30,7 +36,7 @@
"content_scripts": [
{
"matches": ["file://*/*", "http://*/*", "https://*/*"],
"js": ["contentscript.js"],
"js": ["lockdown.cjs", "runLockdown.js", "contentscript.js"],
"run_at": "document_start",
"all_frames": true
},
@ -46,6 +52,7 @@
"19": "images/icon-19.png",
"32": "images/icon-32.png",
"38": "images/icon-38.png",
"48": "images/icon-48.png",
"64": "images/icon-64.png",
"128": "images/icon-128.png",
"512": "images/icon-512.png"
@ -64,6 +71,6 @@
"notifications"
],
"short_name": "__MSG_appName__",
"version": "8.1.5",
"version": "8.1.6",
"web_accessible_resources": ["inpage.js", "phishing.html"]
}

@ -2,6 +2,8 @@
<html lang="en">
<head>
<title>Ethereum Phishing Detection - MetaMask</title>
<script src="./lockdown.cjs" type="text/javascript" charset="utf-8"></script>
<script src="./runLockdown.js" type="text/javascript" charset="utf-8"></script>
<script src="./phishing-detect.js"></script>
<link rel="stylesheet" type="text/css" href="./index.css" title="ltr">
<link rel="stylesheet" type="text/css" href="./index-rtl.css" title="rtl" disabled>

@ -10,6 +10,9 @@
<body style="width:357px; height:600px;">
<div id="app-content"></div>
<div id="popover-content"></div>
<script src="./initSentry.js" type="text/javascript" charset="utf-8"></script>
<script src="./lockdown.cjs" type="text/javascript" charset="utf-8"></script>
<script src="./runLockdown.js" type="text/javascript" charset="utf-8"></script>
<script src="./ui-libs.js" type="text/javascript" charset="utf-8"></script>
<script src="./ui.js" type="text/javascript" charset="utf-8"></script>
</body>

@ -3,7 +3,6 @@
*/
// these need to run before anything else
/* eslint-disable import/first,import/order */
import './lib/freezeGlobals'
import setupFetchDebugging from './lib/setupFetchDebugging'
/* eslint-enable import/order */
@ -29,7 +28,6 @@ import createStreamSink from './lib/createStreamSink'
import NotificationManager from './lib/notification-manager'
import MetamaskController from './metamask-controller'
import rawFirstTimeState from './first-time-state'
import setupSentry from './lib/setupSentry'
import getFirstPreferredLangCode from './lib/get-first-preferred-lang-code'
import getObjStructure from './lib/getObjStructure'
import setupEnsIpfsResolver from './lib/ens-ipfs/setup'
@ -41,18 +39,16 @@ import {
} from './lib/enums'
/* eslint-enable import/first */
const { sentry } = global
const firstTimeState = { ...rawFirstTimeState }
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
const platform = new ExtensionPlatform()
const notificationManager = new NotificationManager()
global.METAMASK_NOTIFIER = notificationManager
// setup sentry error reporting
const release = platform.getVersion()
const sentry = setupSentry({ release })
let popupIsOpen = false
let notificationIsOpen = false
const openMetamaskTabsIDs = {}

@ -1,5 +1,5 @@
import Web3 from 'web3'
import contracts from 'eth-contract-metadata'
import contracts from '@metamask/contract-metadata'
import { warn } from 'loglevel'
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi'
import { MAINNET } from './network/enums'
@ -32,7 +32,7 @@ export default class DetectTokensController {
}
/**
* For each token in eth-contract-metadata, find check selectedAddress balance.
* For each token in @metamask/contract-metadata, find check selectedAddress balance.
*/
async detectNewTokens() {
if (!this.isActive) {

@ -0,0 +1,366 @@
import { merge, omit } from 'lodash'
import ObservableStore from 'obs-store'
import { bufferToHex, sha3 } from 'ethereumjs-util'
import { ENVIRONMENT_TYPE_BACKGROUND } from '../lib/enums'
import {
METAMETRICS_ANONYMOUS_ID,
METAMETRICS_BACKGROUND_PAGE_OBJECT,
} from '../../../shared/constants/metametrics'
/**
* Used to determine whether or not to attach a user's metametrics id
* to events that include on-chain data. This helps to prevent identifying
* a user by being able to trace their activity on etherscan/block exploring
*/
const trackableSendCounts = {
1: true,
10: true,
30: true,
50: true,
100: true,
250: true,
500: true,
1000: true,
2500: true,
5000: true,
10000: true,
25000: true,
}
export function sendCountIsTrackable(sendCount) {
return Boolean(trackableSendCounts[sendCount])
}
/**
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsContext} MetaMetricsContext
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventPayload} MetaMetricsEventPayload
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventOptions} MetaMetricsEventOptions
* @typedef {import('../../../shared/constants/metametrics').SegmentEventPayload} SegmentEventPayload
* @typedef {import('../../../shared/constants/metametrics').SegmentInterface} SegmentInterface
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsPagePayload} MetaMetricsPagePayload
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsPageOptions} MetaMetricsPageOptions
*/
/**
* @typedef {Object} MetaMetricsControllerState
* @property {?string} metaMetricsId - The user's metaMetricsId that will be
* attached to all non-anonymized event payloads
* @property {?boolean} participateInMetaMetrics - The user's preference for
* participating in the MetaMetrics analytics program. This setting controls
* whether or not events are tracked
* @property {number} metaMetricsSendCount - How many send transactions have
* been tracked through this controller. Used to prevent attaching sensitive
* data that can be traced through on chain data.
*/
export default class MetaMetricsController {
/**
* @param {Object} segment - an instance of analytics-node for tracking
* events that conform to the new MetaMetrics tracking plan.
* @param {Object} segmentLegacy - an instance of analytics-node for
* tracking legacy schema events. Will eventually be phased out
* @param {Object} preferencesStore - The preferences controller store, used
* to access and subscribe to preferences that will be attached to events
* @param {function} onNetworkDidChange - Used to attach a listener to the
* networkDidChange event emitted by the networkController
* @param {function} getCurrentChainId - Gets the current chain id from the
* network controller
* @param {function} getNetworkIdentifier - Gets the current network
* identifier from the network controller
* @param {string} version - The version of the extension
* @param {string} environment - The environment the extension is running in
* @param {MetaMetricsControllerState} initState - State to initialized with
*/
constructor({
segment,
segmentLegacy,
preferencesStore,
onNetworkDidChange,
getCurrentChainId,
getNetworkIdentifier,
version,
environment,
initState,
}) {
const prefState = preferencesStore.getState()
this.chainId = getCurrentChainId()
this.network = getNetworkIdentifier()
this.locale = prefState.currentLocale.replace('_', '-')
this.version =
environment === 'production' ? version : `${version}-${environment}`
this.store = new ObservableStore({
participateInMetaMetrics: null,
metaMetricsId: null,
metaMetricsSendCount: 0,
...initState,
})
preferencesStore.subscribe(({ currentLocale }) => {
this.locale = currentLocale.replace('_', '-')
})
onNetworkDidChange(() => {
this.chainId = getCurrentChainId()
this.network = getNetworkIdentifier()
})
this.segment = segment
this.segmentLegacy = segmentLegacy
}
generateMetaMetricsId() {
return bufferToHex(
sha3(
String(Date.now()) +
String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)),
),
)
}
/**
* Setter for the `participateInMetaMetrics` property
*
* @param {boolean} participateInMetaMetrics - Whether or not the user wants
* to participate in MetaMetrics
* @returns {string|null} the string of the new metametrics id, or null
* if not set
*/
setParticipateInMetaMetrics(participateInMetaMetrics) {
let { metaMetricsId } = this.state
if (participateInMetaMetrics && !metaMetricsId) {
metaMetricsId = this.generateMetaMetricsId()
} else if (participateInMetaMetrics === false) {
metaMetricsId = null
}
this.store.updateState({ participateInMetaMetrics, metaMetricsId })
return metaMetricsId
}
get state() {
return this.store.getState()
}
setMetaMetricsSendCount(val) {
this.store.updateState({ metaMetricsSendCount: val })
}
/**
* Build the context object to attach to page and track events.
* @private
* @param {Pick<MetaMetricsContext, 'referrer'>} [referrer] - dapp origin that initialized
* the notification window.
* @param {Pick<MetaMetricsContext, 'page'>} [page] - page object describing the current
* view of the extension. Defaults to the background-process object.
* @returns {MetaMetricsContext}
*/
_buildContext(referrer, page = METAMETRICS_BACKGROUND_PAGE_OBJECT) {
return {
app: {
name: 'MetaMask Extension',
version: this.version,
},
userAgent: window.navigator.userAgent,
page,
referrer,
}
}
/**
* Build's the event payload, processing all fields into a format that can be
* fed to Segment's track method
* @private
* @param {
* Omit<MetaMetricsEventPayload, 'sensitiveProperties'>
* } rawPayload - raw payload provided to trackEvent
* @returns {SegmentEventPayload} - formatted event payload for segment
*/
_buildEventPayload(rawPayload) {
const {
event,
properties,
revenue,
value,
currency,
category,
page,
referrer,
environmentType = ENVIRONMENT_TYPE_BACKGROUND,
} = rawPayload
return {
event,
properties: {
// These values are omitted from properties because they have special meaning
// in segment. https://segment.com/docs/connections/spec/track/#properties.
// to avoid accidentally using these inappropriately, you must add them as top
// level properties on the event payload. We also exclude locale to prevent consumers
// from overwriting this context level property. We track it as a property
// because not all destinations map locale from context.
...omit(properties, ['revenue', 'locale', 'currency', 'value']),
revenue,
value,
currency,
category,
network: this.network,
locale: this.locale,
chain_id: this.chainId,
environment_type: environmentType,
},
context: this._buildContext(referrer, page),
}
}
/**
* Perform validation on the payload and update the id type to use before
* sending to Segment. Also examines the options to route and handle the
* event appropriately.
* @private
* @param {SegmentEventPayload} payload - properties to attach to event
* @param {MetaMetricsEventOptions} [options] - options for routing and
* handling the event
* @returns {Promise<void>}
*/
_track(payload, options) {
const {
isOptIn,
metaMetricsId: metaMetricsIdOverride,
matomoEvent,
flushImmediately,
} = options || {}
let idType = 'userId'
let idValue = this.state.metaMetricsId
let excludeMetaMetricsId = options?.excludeMetaMetricsId ?? false
// This is carried over from the old implementation, and will likely need
// to be updated to work with the new tracking plan. I think we should use
// a config setting for this instead of trying to match the event name
const isSendFlow = Boolean(payload.event.match(/^send|^confirm/iu))
if (
isSendFlow &&
this.state.metaMetricsSendCount &&
!sendCountIsTrackable(this.state.metaMetricsSendCount + 1)
) {
excludeMetaMetricsId = true
}
// If we are tracking sensitive data we will always use the anonymousId
// property as well as our METAMETRICS_ANONYMOUS_ID. This prevents us from
// associating potentially identifiable information with a specific id.
// During the opt in flow we will track all events, but do so with the
// anonymous id. The one exception to that rule is after the user opts in
// to MetaMetrics. When that happens we receive back the user's new
// MetaMetrics id before it is fully persisted to state. To avoid a race
// condition we explicitly pass the new id to the track method. In that
// case we will track the opt in event to the user's id. In all other cases
// we use the metaMetricsId from state.
if (excludeMetaMetricsId || (isOptIn && !metaMetricsIdOverride)) {
idType = 'anonymousId'
idValue = METAMETRICS_ANONYMOUS_ID
} else if (isOptIn && metaMetricsIdOverride) {
idValue = metaMetricsIdOverride
}
payload[idType] = idValue
// Promises will only resolve when the event is sent to segment. For any
// event that relies on this promise being fulfilled before performing UI
// updates, or otherwise delaying user interaction, supply the
// 'flushImmediately' flag to the trackEvent method.
return new Promise((resolve, reject) => {
const callback = (err) => {
if (err) {
return reject(err)
}
return resolve()
}
const target = matomoEvent === true ? this.segmentLegacy : this.segment
target.track(payload, callback)
if (flushImmediately) {
target.flush()
}
})
}
/**
* track a page view with Segment
* @param {MetaMetricsPagePayload} payload - details of the page viewed
* @param {MetaMetricsPageOptions} [options] - options for handling the page
* view
*/
trackPage({ name, params, environmentType, page, referrer }, options) {
if (this.state.participateInMetaMetrics === false) {
return
}
if (this.state.participateInMetaMetrics === null && !options?.isOptInPath) {
return
}
const { metaMetricsId } = this.state
const idTrait = metaMetricsId ? 'userId' : 'anonymousId'
const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID
this.segment.page({
[idTrait]: idValue,
name,
properties: {
params,
locale: this.locale,
network: this.network,
chain_id: this.chainId,
environment_type: environmentType,
},
context: this._buildContext(referrer, page),
})
}
/**
* track a metametrics event, performing necessary payload manipulation and
* routing the event to the appropriate segment source. Will split events
* with sensitiveProperties into two events, tracking the sensitiveProperties
* with the anonymousId only.
* @param {MetaMetricsEventPayload} payload - details of the event
* @param {MetaMetricsEventOptions} [options] - options for handling/routing the event
* @returns {Promise<void>}
*/
async trackEvent(payload, options) {
// event and category are required fields for all payloads
if (!payload.event || !payload.category) {
throw new Error('Must specify event and category.')
}
if (!this.state.participateInMetaMetrics && !options?.isOptIn) {
return
}
// We might track multiple events if sensitiveProperties is included, this array will hold
// the promises returned from this._track.
const events = []
if (payload.sensitiveProperties) {
// sensitiveProperties will only be tracked using the anonymousId property and generic id
// If the event options already specify to exclude the metaMetricsId we throw an error as
// a signal to the developer that the event was implemented incorrectly
if (options?.excludeMetaMetricsId === true) {
throw new Error(
'sensitiveProperties was specified in an event payload that also set the excludeMetaMetricsId flag',
)
}
const combinedProperties = merge(
payload.sensitiveProperties,
payload.properties,
)
events.push(
this._track(
this._buildEventPayload({
...payload,
properties: combinedProperties,
}),
{ ...options, excludeMetaMetricsId: true },
),
)
}
events.push(this._track(this._buildEventPayload(payload), options))
await Promise.all(events)
}
}

@ -1,5 +1,4 @@
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
import createScaffoldMiddleware from 'json-rpc-engine/src/createScaffoldMiddleware'
import { createScaffoldMiddleware, mergeMiddleware } from 'json-rpc-engine'
import createBlockReRefMiddleware from 'eth-json-rpc-middleware/block-ref'
import createRetryOnEmptyMiddleware from 'eth-json-rpc-middleware/retryOnEmpty'
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'

@ -1,5 +1,4 @@
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
import { createAsyncMiddleware, mergeMiddleware } from 'json-rpc-engine'
import createFetchMiddleware from 'eth-json-rpc-middleware/fetch'
import createBlockRefRewriteMiddleware from 'eth-json-rpc-middleware/block-ref-rewrite'
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'

@ -1,5 +1,4 @@
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
import createScaffoldMiddleware from 'json-rpc-engine/src/createScaffoldMiddleware'
import { createScaffoldMiddleware, mergeMiddleware } from 'json-rpc-engine'
import createWalletSubprovider from 'eth-json-rpc-middleware/wallet'
import {
createPendingNonceMiddleware,

@ -1,4 +1,4 @@
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
import { createAsyncMiddleware } from 'json-rpc-engine'
import { formatTxMetaForRpcResult } from '../util'
export function createPendingNonceMiddleware({ getPendingNonce }) {

@ -2,7 +2,7 @@ import assert from 'assert'
import EventEmitter from 'events'
import ObservableStore from 'obs-store'
import ComposedStore from 'obs-store/lib/composed'
import JsonRpcEngine from 'json-rpc-engine'
import { JsonRpcEngine } from 'json-rpc-engine'
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
import log from 'loglevel'
import {
@ -195,6 +195,11 @@ export default class NetworkController extends EventEmitter {
return this.providerStore.getState()
}
getNetworkIdentifier() {
const provider = this.providerStore.getState()
return provider.type === 'rpc' ? provider.rpcUrl : provider.type
}
//
// Private
//

@ -1,6 +1,5 @@
import nanoid from 'nanoid'
import JsonRpcEngine from 'json-rpc-engine'
import asMiddleware from 'json-rpc-engine/src/asMiddleware'
import { JsonRpcEngine } from 'json-rpc-engine'
import ObservableStore from 'obs-store'
import log from 'loglevel'
import { CapabilitiesController as RpcCap } from 'rpc-cap'
@ -109,7 +108,7 @@ export class PermissionsController {
}),
)
return asMiddleware(engine)
return engine.asMiddleware()
}
/**

@ -1,4 +1,4 @@
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
import { createAsyncMiddleware } from 'json-rpc-engine'
import { ethErrors } from 'eth-json-rpc-errors'
/**

@ -1,12 +1,12 @@
import { strict as assert } from 'assert'
import ObservableStore from 'obs-store'
import { ethErrors } from 'eth-json-rpc-errors'
import { normalize as normalizeAddress } from 'eth-sig-util'
import { isValidAddress, sha3, bufferToHex } from 'ethereumjs-util'
import { isValidAddress } from 'ethereumjs-util'
import ethers from 'ethers'
import log from 'loglevel'
import { isPrefixedFormattedHexString } from '../lib/util'
import { LISTED_CONTRACT_ADDRESSES } from '../../../shared/constants/tokens'
import { addInternalMethodPrefix } from './permissions'
import { NETWORK_TYPE_TO_ID_MAP } from './network/enums'
export default class PreferencesController {
@ -47,10 +47,8 @@ export default class PreferencesController {
// perform sensitive operations.
featureFlags: {
showIncomingTransactions: true,
transactionTime: false,
},
knownMethodData: {},
participateInMetaMetrics: null,
firstTimeFlowType: null,
currentLocale: opts.initLangCode,
identities: {},
@ -62,9 +60,6 @@ export default class PreferencesController {
useNativeCurrencyAsPrimaryCurrency: true,
},
completedOnboarding: false,
metaMetricsId: null,
metaMetricsSendCount: 0,
// ENS decentralized website resolution
ipfsGateway: 'dweb.link',
...opts.initState,
@ -121,38 +116,6 @@ export default class PreferencesController {
this.store.updateState({ usePhishDetect: val })
}
/**
* Setter for the `participateInMetaMetrics` property
*
* @param {boolean} bool - Whether or not the user wants to participate in MetaMetrics
* @returns {string|null} the string of the new metametrics id, or null if not set
*
*/
setParticipateInMetaMetrics(bool) {
this.store.updateState({ participateInMetaMetrics: bool })
let metaMetricsId = null
if (bool && !this.store.getState().metaMetricsId) {
metaMetricsId = bufferToHex(
sha3(
String(Date.now()) +
String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)),
),
)
this.store.updateState({ metaMetricsId })
} else if (bool === false) {
this.store.updateState({ metaMetricsId })
}
return metaMetricsId
}
getParticipateInMetaMetrics() {
return this.store.getState().participateInMetaMetrics
}
setMetaMetricsSendCount(val) {
this.store.updateState({ metaMetricsSendCount: val })
}
/**
* Setter for the `firstTimeFlowType` property
*
@ -171,22 +134,6 @@ export default class PreferencesController {
return this.store.getState().assetImages
}
addSuggestedERC20Asset(tokenOpts) {
this._validateERC20AssetParams(tokenOpts)
const suggested = this.getSuggestedTokens()
const { rawAddress, symbol, decimals, image } = tokenOpts
const address = normalizeAddress(rawAddress)
const newEntry = {
address,
symbol,
decimals,
image,
unlisted: !LISTED_CONTRACT_ADDRESSES.includes(address.toLowerCase()),
}
suggested[address] = newEntry
this.store.updateState({ suggestedTokens: suggested })
}
/**
* Add new methodData to state, to avoid requesting this information again through Infura
*
@ -200,37 +147,21 @@ export default class PreferencesController {
}
/**
* RPC engine middleware for requesting new asset added
* wallet_watchAsset request handler.
*
* @param {any} req
* @param {any} res
* @param {Function} next
* @param {Function} end
* @param {Object} req - The watchAsset JSON-RPC request object.
*/
async requestWatchAsset(req, res, next, end) {
if (
req.method === 'metamask_watchAsset' ||
req.method === addInternalMethodPrefix('watchAsset')
) {
async requestWatchAsset(req) {
const { type, options } = req.params
switch (type) {
case 'ERC20': {
const result = await this._handleWatchAssetERC20(options)
if (result instanceof Error) {
end(result)
} else {
res.result = result
end()
}
return
}
case 'ERC20':
return await this._handleWatchAssetERC20(options)
default:
end(new Error(`Asset of type ${type} not supported`))
return
}
throw ethErrors.rpc.invalidParams(
`Asset of type "${type}" not supported.`,
)
}
next()
}
/**
@ -775,21 +706,17 @@ export default class PreferencesController {
*
*/
async _handleWatchAssetERC20(tokenMetadata) {
const { address, symbol, decimals, image } = tokenMetadata
const rawAddress = address
try {
this._validateERC20AssetParams({ rawAddress, symbol, decimals })
} catch (err) {
return err
}
const tokenOpts = { rawAddress, decimals, symbol, image }
this.addSuggestedERC20Asset(tokenOpts)
return this.openPopup().then(() => {
this._validateERC20AssetParams(tokenMetadata)
const address = normalizeAddress(tokenMetadata.address)
const { symbol, decimals, image } = tokenMetadata
this._addSuggestedERC20Asset(address, symbol, decimals, image)
await this.openPopup()
const tokenAddresses = this.getTokens().filter(
(token) => token.address === normalizeAddress(rawAddress),
(token) => token.address === address,
)
return tokenAddresses.length > 0
})
}
/**
@ -800,24 +727,41 @@ export default class PreferencesController {
* doesn't fulfill requirements
*
*/
_validateERC20AssetParams(opts) {
const { rawAddress, symbol, decimals } = opts
if (!rawAddress || !symbol || typeof decimals === 'undefined') {
throw new Error(
`Cannot suggest token without address, symbol, and decimals`,
_validateERC20AssetParams({ address, symbol, decimals }) {
if (!address || !symbol || typeof decimals === 'undefined') {
throw ethErrors.rpc.invalidParams(
`Must specify address, symbol, and decimals.`,
)
}
if (typeof symbol !== 'string') {
throw ethErrors.rpc.invalidParams(`Invalid symbol: not a string.`)
}
if (!(symbol.length < 7)) {
throw new Error(`Invalid symbol ${symbol} more than six characters`)
throw ethErrors.rpc.invalidParams(
`Invalid symbol "${symbol}": longer than 6 characters.`,
)
}
const numDecimals = parseInt(decimals, 10)
if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {
throw new Error(
`Invalid decimals ${decimals} must be at least 0, and not over 36`,
throw ethErrors.rpc.invalidParams(
`Invalid decimals "${decimals}": must be 0 <= 36.`,
)
}
if (!isValidAddress(rawAddress)) {
throw new Error(`Invalid address ${rawAddress}`)
if (!isValidAddress(address)) {
throw ethErrors.rpc.invalidParams(`Invalid address "${address}".`)
}
}
_addSuggestedERC20Asset(address, symbol, decimals, image) {
const newEntry = {
address,
symbol,
decimals,
image,
unlisted: !LISTED_CONTRACT_ADDRESSES.includes(address),
}
const suggested = this.getSuggestedTokens()
suggested[address] = newEntry
this.store.updateState({ suggestedTokens: suggested })
}
}

@ -7,7 +7,7 @@ const Box = process.env.IN_TEST
/* eslint-enable import/order */
import log from 'loglevel'
import JsonRpcEngine from 'json-rpc-engine'
import { JsonRpcEngine } from 'json-rpc-engine'
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
import Migrator from '../lib/migrator'
import migrations from '../migrations'

@ -929,15 +929,8 @@ export default class TransactionController extends EventEmitter {
if (txMeta.txReceipt.status === '0x0') {
this._trackMetaMetricsEvent({
event: 'Swap Failed',
sensitiveProperties: { ...txMeta.swapMetaData },
category: 'swaps',
excludeMetaMetricsId: false,
})
this._trackMetaMetricsEvent({
event: 'Swap Failed',
properties: { ...txMeta.swapMetaData },
category: 'swaps',
excludeMetaMetricsId: true,
})
} else {
const tokensReceived = getSwapsTokensReceivedFromTxMeta(
@ -965,19 +958,12 @@ export default class TransactionController extends EventEmitter {
this._trackMetaMetricsEvent({
event: 'Swap Completed',
category: 'swaps',
excludeMetaMetricsId: false,
})
this._trackMetaMetricsEvent({
event: 'Swap Completed',
category: 'swaps',
properties: {
sensitiveProperties: {
...txMeta.swapMetaData,
token_to_amount_received: tokensReceived,
quote_vs_executionRatio: quoteVsExecutionRatio,
estimated_vs_used_gasRatio: estimatedVsUsedGasRatio,
},
excludeMetaMetricsId: true,
})
}
}

@ -1,6 +1,7 @@
import EthQuery from 'ethjs-query'
import log from 'loglevel'
import ethUtil from 'ethereumjs-util'
import { cloneDeep } from 'lodash'
import { hexToBn, BnMultiplyByFraction, bnToHex } from '../../lib/util'
/**
@ -56,7 +57,13 @@ export default class TxGasUtil {
@returns {string} the estimated gas limit as a hex string
*/
async estimateTxGas(txMeta) {
const { txParams } = txMeta
const txParams = cloneDeep(txMeta.txParams)
// `eth_estimateGas` can fail if the user has insufficient balance for the
// value being sent, or for the gas cost. We don't want to check their
// balance here, we just want the gas estimate. The gas price is removed
// to skip those balance checks. We check balance elsewhere.
delete txParams.gasPrice
// estimate tx gas requirements
return await this.query.estimateGas(txParams)

@ -0,0 +1,7 @@
import setupSentry from './lib/setupSentry'
// setup sentry error reporting
global.sentry = setupSentry({
release: process.env.METAMASK_VERSION,
getState: () => global.getSentryState?.() || {},
})

@ -25,6 +25,8 @@ const MESSAGE_TYPE = {
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
LOG_WEB3_USAGE: 'metamask_logInjectedWeb3Usage',
PERSONAL_SIGN: 'personal_sign',
WATCH_ASSET: 'wallet_watchAsset',
WATCH_ASSET_LEGACY: 'metamask_watchAsset',
}
export {

@ -1,37 +0,0 @@
/**
* Freezes the Promise global and prevents its reassignment.
*/
import deepFreeze from 'deep-freeze-strict'
if (process.env.IN_TEST !== 'true' && process.env.METAMASK_ENV !== 'test') {
freeze(global, 'Promise')
}
/**
* Makes a key:value pair on a target object immutable, with limitations.
* The key cannot be reassigned or deleted, and the value is recursively frozen
* using Object.freeze.
*
* Because of JavaScript language limitations, this is does not mean that the
* value is completely immutable. It is, however, better than nothing.
*
* @param {Object} target - The target object to freeze a property on.
* @param {string} key - The key to freeze.
* @param {any} [value] - The value to freeze, if different from the existing value on the target.
* @param {boolean} [enumerable=true] - If given a value, whether the property is enumerable.
*/
function freeze(target, key, value, enumerable = true) {
const opts = {
configurable: false,
writable: false,
}
if (value === undefined) {
target[key] = deepFreeze(target[key])
} else {
opts.value = deepFreeze(value)
opts.enumerable = enumerable
}
Object.defineProperty(target, key, opts)
}

@ -1,7 +1,9 @@
import handlers from './handlers'
const handlerMap = handlers.reduce((map, handler) => {
map.set(handler.methodName, handler.implementation)
for (const methodName of handler.methodNames) {
map.set(methodName, handler.implementation)
}
return map
}, new Map())

@ -1,4 +1,5 @@
import logWeb3Usage from './log-web3-usage'
import watchAsset from './watch-asset'
const handlers = [logWeb3Usage]
const handlers = [logWeb3Usage, watchAsset]
export default handlers

@ -8,7 +8,7 @@ import { MESSAGE_TYPE } from '../../enums'
*/
const logWeb3Usage = {
methodName: MESSAGE_TYPE.LOG_WEB3_USAGE,
methodNames: [MESSAGE_TYPE.LOG_WEB3_USAGE],
implementation: logWeb3UsageHandler,
}
export default logWeb3Usage
@ -43,17 +43,19 @@ function logWeb3UsageHandler(req, res, _next, end, { origin, sendMetrics }) {
if (!recordedWeb3Usage[origin][path]) {
recordedWeb3Usage[origin][path] = true
sendMetrics({
sendMetrics(
{
event: `Website Used window.web3`,
category: 'inpage_provider',
properties: { action, web3Path: path },
eventContext: {
referrer: {
url: origin,
},
},
{
excludeMetaMetricsId: true,
})
},
)
}
res.result = true

@ -0,0 +1,40 @@
import { MESSAGE_TYPE } from '../../enums'
const watchAsset = {
methodNames: [MESSAGE_TYPE.WATCH_ASSET, MESSAGE_TYPE.WATCH_ASSET_LEGACY],
implementation: watchAssetHandler,
}
export default watchAsset
/**
* @typedef {Object} WatchAssetOptions
* @property {Function} handleWatchAssetRequest - The wallet_watchAsset method implementation.
*/
/**
* @typedef {Object} WatchAssetParam
* @property {string} type - The type of the asset to watch.
* @property {Object} options - Watch options for the asset.
*/
/**
* @param {import('json-rpc-engine').JsonRpcRequest<WatchAssetParam>} req - The JSON-RPC request object.
* @param {import('json-rpc-engine').JsonRpcResponse<true>} res - The JSON-RPC response object.
* @param {Function} _next - The json-rpc-engine 'next' callback.
* @param {Function} end - The json-rpc-engine 'end' callback.
* @param {WatchAssetOptions} options
*/
async function watchAssetHandler(
req,
res,
_next,
end,
{ handleWatchAssetRequest },
) {
try {
res.result = await handleWatchAssetRequest(req)
return end()
} catch (error) {
return end(error)
}
}

@ -0,0 +1,101 @@
import Analytics from 'analytics-node'
const isDevOrTestEnvironment = Boolean(
process.env.METAMASK_DEBUG || process.env.IN_TEST,
)
const SEGMENT_WRITE_KEY = process.env.SEGMENT_WRITE_KEY ?? null
const SEGMENT_LEGACY_WRITE_KEY = process.env.SEGMENT_LEGACY_WRITE_KEY ?? null
const SEGMENT_HOST = process.env.SEGMENT_HOST ?? null
// flushAt controls how many events are sent to segment at once. Segment will
// hold onto a queue of events until it hits this number, then it sends them as
// a batch. This setting defaults to 20, but in development we likely want to
// see events in real time for debugging, so this is set to 1 to disable the
// queueing mechanism.
const SEGMENT_FLUSH_AT =
process.env.METAMASK_ENVIRONMENT === 'production' ? undefined : 1
// flushInterval controls how frequently the queue is flushed to segment.
// This happens regardless of the size of the queue. The default setting is
// 10,000ms (10 seconds). This default is rather high, though thankfully
// using the background process as our event handler means we don't have to
// deal with short lived sessions that happen faster than the interval
// e.g confirmations. This is set to 5,000ms (5 seconds) arbitrarily with the
// intent of having a value less than 10 seconds.
const SEGMENT_FLUSH_INTERVAL = 5000
/**
* Creates a mock segment module for usage in test environments. This is used
* when building the application in test mode to catch event calls and prevent
* them from being sent to segment. It is also used in unit tests to mock and
* spy on the methods to ensure proper behavior
* @param {number} flushAt - number of events to queue before sending to segment
* @param {number} flushInterval - ms interval to flush queue and send to segment
* @returns {SegmentInterface}
*/
export const createSegmentMock = (
flushAt = SEGMENT_FLUSH_AT,
flushInterval = SEGMENT_FLUSH_INTERVAL,
) => {
const segmentMock = {
// Internal queue to keep track of events and properly mimic segment's
// queueing behavior.
queue: [],
/**
* Used to immediately send all queued events and reset the queue to zero.
* For our purposes this simply triggers the callback method registered with
* the event.
*/
flush() {
segmentMock.queue.forEach(([_, callback]) => {
callback()
})
segmentMock.queue = []
},
/**
* Track an event and add it to the queue. If the queue size reaches the
* flushAt threshold, flush the queue.
*/
track(payload, callback = () => undefined) {
segmentMock.queue.push([payload, callback])
if (segmentMock.queue.length >= flushAt) {
segmentMock.flush()
}
},
/**
* A true NOOP, these methods are either not used or do not await callback
* and therefore require no functionality.
*/
page() {
// noop
},
identify() {
// noop
},
}
// Mimic the flushInterval behavior with an interval
setInterval(segmentMock.flush, flushInterval)
return segmentMock
}
export const segment =
!SEGMENT_WRITE_KEY || (isDevOrTestEnvironment && !SEGMENT_HOST)
? createSegmentMock(SEGMENT_FLUSH_AT, SEGMENT_FLUSH_INTERVAL)
: new Analytics(SEGMENT_WRITE_KEY, {
host: SEGMENT_HOST,
flushAt: SEGMENT_FLUSH_AT,
flushInterval: SEGMENT_FLUSH_INTERVAL,
})
export const segmentLegacy =
!SEGMENT_LEGACY_WRITE_KEY || (isDevOrTestEnvironment && !SEGMENT_HOST)
? createSegmentMock(SEGMENT_FLUSH_AT, SEGMENT_FLUSH_INTERVAL)
: new Analytics(SEGMENT_LEGACY_WRITE_KEY, {
host: SEGMENT_HOST,
flushAt: SEGMENT_FLUSH_AT,
flushInterval: SEGMENT_FLUSH_INTERVAL,
})

@ -27,7 +27,12 @@ export default function setupWeb3(log) {
web3.setProvider = function () {
log.debug('MetaMask - overrode web3.setProvider')
}
log.debug('MetaMask - injected web3')
Object.defineProperty(web3, '__isMetaMaskShim__', {
value: true,
enumerable: false,
configurable: false,
writable: false,
})
Object.defineProperty(window.ethereum, '_web3Ref', {
enumerable: false,
@ -180,12 +185,13 @@ export default function setupWeb3(log) {
},
})
Object.defineProperty(global, 'web3', {
Object.defineProperty(window, 'web3', {
enumerable: false,
writable: true,
configurable: true,
value: web3Proxy,
})
log.debug('MetaMask - injected web3')
window.ethereum._publicConfigStore.subscribe((state) => {
// if the auto refresh on network change is false do not
@ -231,7 +237,7 @@ export default function setupWeb3(log) {
// reload the page
function triggerReset() {
global.location.reload()
window.location.reload()
}
/**

@ -4,7 +4,7 @@ import pump from 'pump'
import Dnode from 'dnode'
import ObservableStore from 'obs-store'
import asStream from 'obs-store/lib/asStream'
import RpcEngine from 'json-rpc-engine'
import { JsonRpcEngine } from 'json-rpc-engine'
import { debounce } from 'lodash'
import createEngineStream from 'json-rpc-middleware-stream/engineStream'
import createFilterMiddleware from 'eth-json-rpc-filters'
@ -18,13 +18,12 @@ import TrezorKeyring from 'eth-trezor-keyring'
import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring'
import EthQuery from 'eth-query'
import nanoid from 'nanoid'
import contractMap from 'eth-contract-metadata'
import contractMap from '@metamask/contract-metadata'
import {
AddressBookController,
CurrencyRateController,
PhishingController,
} from '@metamask/controllers'
import { getTrackMetaMetricsEvent } from '../../shared/modules/metametrics'
import { getBackgroundMetaMetricState } from '../../ui/app/selectors'
import { TRANSACTION_STATUSES } from '../../shared/constants/transaction'
import ComposableObservableStore from './lib/ComposableObservableStore'
@ -58,7 +57,8 @@ import getRestrictedMethods from './controllers/permissions/restrictedMethods'
import nodeify from './lib/nodeify'
import accountImporter from './account-import-strategies'
import seedPhraseVerifier from './lib/seed-phrase-verifier'
import { ENVIRONMENT_TYPE_BACKGROUND } from './lib/enums'
import MetaMetricsController from './controllers/metametrics'
import { segment, segmentLegacy } from './lib/segment'
export default class MetamaskController extends EventEmitter {
/**
@ -115,35 +115,24 @@ export default class MetamaskController extends EventEmitter {
migrateAddressBookState: this.migrateAddressBookState.bind(this),
})
this.trackMetaMetricsEvent = getTrackMetaMetricsEvent(
this.platform.getVersion(),
() => {
const participateInMetaMetrics = this.preferencesController.getParticipateInMetaMetrics()
const {
currentLocale,
metaMetricsId,
} = this.preferencesController.store.getState()
const chainId = this.networkController.getCurrentChainId()
const provider = this.networkController.getProviderConfig()
const network =
provider.type === 'rpc' ? provider.rpcUrl : provider.type
return {
participateInMetaMetrics,
metaMetricsId,
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
chainId,
network,
context: {
page: {
path: '/background-process',
title: 'Background Process',
url: '/background-process',
},
locale: currentLocale.replace('_', '-'),
},
}
},
)
this.metaMetricsController = new MetaMetricsController({
segment,
segmentLegacy,
preferencesStore: this.preferencesController.store,
onNetworkDidChange: this.networkController.on.bind(
this.networkController,
'networkDidChange',
),
getNetworkIdentifier: this.networkController.getNetworkIdentifier.bind(
this.networkController,
),
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
),
version: this.platform.getVersion(),
environment: process.env.METAMASK_ENVIRONMENT,
initState: initState.MetaMetricsController,
})
this.appStateController = new AppStateController({
addUnlockListener: this.on.bind(this, 'unlock'),
@ -298,9 +287,9 @@ export default class MetamaskController extends EventEmitter {
),
provider: this.provider,
blockTracker: this.blockTracker,
trackMetaMetricsEvent: this.trackMetaMetricsEvent,
trackMetaMetricsEvent: this.metaMetricsController.trackEvent,
getParticipateInMetrics: () =>
this.preferencesController.getParticipateInMetaMetrics(),
this.metaMetricsController.state.participateInMetaMetrics,
})
this.txController.on('newUnapprovedTx', () => opts.showUserConfirmation())
@ -362,6 +351,7 @@ export default class MetamaskController extends EventEmitter {
TransactionController: this.txController.store,
KeyringController: this.keyringController.store,
PreferencesController: this.preferencesController.store,
MetaMetricsController: this.metaMetricsController.store,
AddressBookController: this.addressBookController,
CurrencyController: this.currencyRateController,
NetworkController: this.networkController.store,
@ -388,6 +378,7 @@ export default class MetamaskController extends EventEmitter {
TypesMessageManager: this.typedMessageManager.memStore,
KeyringController: this.keyringController.memStore,
PreferencesController: this.preferencesController.store,
MetaMetricsController: this.metaMetricsController.store,
AddressBookController: this.addressBookController,
CurrencyController: this.currencyRateController,
AlertController: this.alertController.store,
@ -528,6 +519,7 @@ export default class MetamaskController extends EventEmitter {
threeBoxController,
txController,
swapsController,
metaMetricsController,
} = this
return {
@ -825,6 +817,16 @@ export default class MetamaskController extends EventEmitter {
swapsController.setSwapsLiveness,
swapsController,
),
// MetaMetrics
trackMetaMetricsEvent: nodeify(
metaMetricsController.trackEvent,
metaMetricsController,
),
trackMetaMetricsPage: nodeify(
metaMetricsController.trackPage,
metaMetricsController,
),
}
}
@ -1935,7 +1937,7 @@ export default class MetamaskController extends EventEmitter {
isInternal = false,
}) {
// setup json rpc engine stack
const engine = new RpcEngine()
const engine = new JsonRpcEngine()
const { provider, blockTracker } = this
// create filter polyfill middleware
@ -1967,7 +1969,10 @@ export default class MetamaskController extends EventEmitter {
engine.push(
createMethodMiddleware({
origin,
sendMetrics: this.trackMetaMetricsEvent,
sendMetrics: this.metaMetricsController.trackEvent,
handleWatchAssetRequest: this.preferencesController.requestWatchAsset.bind(
this.preferencesController,
),
}),
)
// filter and subscription polyfills
@ -1979,12 +1984,6 @@ export default class MetamaskController extends EventEmitter {
this.permissionsController.createMiddleware({ origin, extensionId }),
)
}
// watch asset
engine.push(
this.preferencesController.requestWatchAsset.bind(
this.preferencesController,
),
)
// forward to metamask primary provider
engine.push(providerAsMiddleware(provider))
return engine
@ -2180,16 +2179,20 @@ export default class MetamaskController extends EventEmitter {
metamask: metamaskState,
})
this.trackMetaMetricsEvent({
this.metaMetricsController.trackEvent(
{
event: name,
category: 'Background',
matomoEvent: true,
properties: {
action,
...additionalProperties,
...customVariables,
},
})
},
{
matomoEvent: true,
},
)
}
/**
@ -2424,7 +2427,7 @@ export default class MetamaskController extends EventEmitter {
*/
setParticipateInMetaMetrics(bool, cb) {
try {
const metaMetricsId = this.preferencesController.setParticipateInMetaMetrics(
const metaMetricsId = this.metaMetricsController.setParticipateInMetaMetrics(
bool,
)
cb(null, metaMetricsId)
@ -2438,7 +2441,7 @@ export default class MetamaskController extends EventEmitter {
setMetaMetricsSendCount(val, cb) {
try {
this.preferencesController.setMetaMetricsSendCount(val)
this.metaMetricsController.setMetaMetricsSendCount(val)
cb(null)
return
} catch (err) {

@ -0,0 +1,44 @@
import { cloneDeep } from 'lodash'
const version = 49
/**
* Migrate metaMetrics state to the new MetaMetrics controller
*/
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 {
metaMetricsId,
participateInMetaMetrics,
metaMetricsSendCount,
} = state.PreferencesController
state.MetaMetricsController = state.MetaMetricsController ?? {}
if (metaMetricsId !== undefined) {
state.MetaMetricsController.metaMetricsId = metaMetricsId
delete state.PreferencesController.metaMetricsId
}
if (participateInMetaMetrics !== undefined) {
state.MetaMetricsController.participateInMetaMetrics = participateInMetaMetrics
delete state.PreferencesController.participateInMetaMetrics
}
if (metaMetricsSendCount !== undefined) {
state.MetaMetricsController.metaMetricsSendCount = metaMetricsSendCount
delete state.PreferencesController.metaMetricsSendCount
}
}
return state
}

@ -0,0 +1,32 @@
import { cloneDeep } from 'lodash'
const version = 50
const LEGACY_LOCAL_STORAGE_KEYS = [
'METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED',
'METASWAP_GAS_PRICE_ESTIMATES',
'cachedFetch',
'BASIC_PRICE_ESTIMATES_LAST_RETRIEVED',
'BASIC_PRICE_ESTIMATES',
'BASIC_GAS_AND_TIME_API_ESTIMATES',
'BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED',
'GAS_API_ESTIMATES_LAST_RETRIEVED',
'GAS_API_ESTIMATES',
]
/**
* Migrate metaMetrics state to the new MetaMetrics controller
*/
export default {
version,
async migrate(originalVersionedData) {
const versionedData = cloneDeep(originalVersionedData)
versionedData.meta.version = version
LEGACY_LOCAL_STORAGE_KEYS.forEach((key) =>
window.localStorage.removeItem(key),
)
return versionedData
},
}

@ -53,6 +53,8 @@ const migrations = [
require('./046').default,
require('./047').default,
require('./048').default,
require('./049').default,
require('./050').default,
]
export default migrations

@ -0,0 +1,7 @@
// Freezes all intrinsics
// eslint-disable-next-line no-undef,import/unambiguous
lockdown({
errorTaming: 'unsafe',
mathTaming: 'unsafe',
dateTaming: 'unsafe',
})

@ -1,13 +1,9 @@
// this must run before anything else
import './lib/freezeGlobals'
// polyfills
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'
import '@formatjs/intl-relativetimeformat/polyfill'
import { EventEmitter } from 'events'
import PortStream from 'extension-port-stream'
import extension from 'extensionizer'
import Dnode from 'dnode'
@ -16,9 +12,8 @@ import EthQuery from 'eth-query'
import StreamProvider from 'web3-stream-provider'
import log from 'loglevel'
import launchMetaMaskUi from '../../ui'
import { setupMultiplex } from './lib/stream-utils'
import setupSentry from './lib/setupSentry'
import ExtensionPlatform from './platforms/extension'
import { setupMultiplex } from './lib/stream-utils'
import {
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_POPUP,
@ -31,13 +26,6 @@ async function start() {
// create platform global
global.platform = new ExtensionPlatform()
// setup sentry error reporting
const release = global.platform.getVersion()
setupSentry({
release,
getState: () => window.getSentryState?.() || {},
})
// identify window type (popup, notification)
const windowType = getEnvironmentType()

@ -3,11 +3,6 @@
//
// run any task with "yarn build ${taskName}"
//
global.globalThis = global // eslint-disable-line node/no-unsupported-features/es-builtins
require('lavamoat-core/lib/ses.umd.js')
lockdown() // eslint-disable-line no-undef
const livereload = require('gulp-livereload')
const {
createTask,

@ -1,18 +1,18 @@
const fs = require('fs')
const gulp = require('gulp')
const watch = require('gulp-watch')
const pify = require('pify')
const pump = pify(require('pump'))
const source = require('vinyl-source-stream')
const buffer = require('vinyl-buffer')
const log = require('fancy-log')
const { assign } = require('lodash')
const watchify = require('watchify')
const browserify = require('browserify')
const envify = require('envify/custom')
const envify = require('loose-envify/custom')
const sourcemaps = require('gulp-sourcemaps')
const sesify = require('sesify')
const terser = require('gulp-terser-js')
const pify = require('pify')
const endOfStream = pify(require('end-of-stream'))
const { makeStringTransform } = require('browserify-transform-tools')
const conf = require('rc')('metamask', {
@ -22,6 +22,8 @@ const conf = require('rc')('metamask', {
SEGMENT_LEGACY_WRITE_KEY: process.env.SEGMENT_LEGACY_WRITE_KEY,
})
const baseManifest = require('../../app/manifest/_base.json')
const packageJSON = require('../../package.json')
const {
createTask,
@ -37,11 +39,10 @@ const dependencies = Object.keys(
)
const materialUIDependencies = ['@material-ui/core']
const reactDepenendencies = dependencies.filter((dep) => dep.match(/react/u))
const d3Dependencies = ['c3', 'd3']
const externalDependenciesMap = {
background: ['3box'],
ui: [...materialUIDependencies, ...reactDepenendencies, ...d3Dependencies],
ui: [...materialUIDependencies, ...reactDepenendencies],
}
function createScriptTasks({ browserPlatforms, livereload }) {
@ -97,7 +98,12 @@ function createScriptTasks({ browserPlatforms, livereload }) {
}
function createTasksForBuildJsExtension({ taskPrefix, devMode, testing }) {
const standardBundles = ['background', 'ui', 'phishing-detect']
const standardBundles = [
'background',
'ui',
'phishing-detect',
'initSentry',
]
const standardSubtasks = standardBundles.map((filename) => {
return createTask(
@ -200,33 +206,19 @@ function createScriptTasks({ browserPlatforms, livereload }) {
bundler.on('log', log)
}
let buildStream = bundler.bundle()
// handle errors
buildStream.on('error', (err) => {
beep()
if (opts.devMode) {
console.warn(err.stack)
} else {
throw err
}
})
// process bundles
buildStream = buildStream
const buildPipeline = [
bundler.bundle(),
// convert bundle stream to gulp vinyl stream
.pipe(source(opts.filename))
// buffer file contents (?)
.pipe(buffer())
source(opts.filename),
// Initialize Source Maps
buildStream = buildStream
buffer(),
// loads map from browserify file
.pipe(sourcemaps.init({ loadMaps: true }))
sourcemaps.init({ loadMaps: true }),
]
// Minification
if (!opts.devMode) {
buildStream = buildStream.pipe(
buildPipeline.push(
terser({
mangle: {
reserved: ['MetamaskInpageProvider'],
@ -242,18 +234,28 @@ function createScriptTasks({ browserPlatforms, livereload }) {
if (opts.devMode) {
// Use inline source maps for development due to Chrome DevTools bug
// https://bugs.chromium.org/p/chromium/issues/detail?id=931675
buildStream = buildStream.pipe(sourcemaps.write())
// note: sourcemaps call arity is important
buildPipeline.push(sourcemaps.write())
} else {
buildStream = buildStream.pipe(sourcemaps.write('../sourcemaps'))
buildPipeline.push(sourcemaps.write('../sourcemaps'))
}
// write completed bundles
browserPlatforms.forEach((platform) => {
const dest = `./dist/${platform}`
buildStream = buildStream.pipe(gulp.dest(dest))
buildPipeline.push(gulp.dest(dest))
})
await endOfStream(buildStream)
// process bundles
if (opts.devMode) {
try {
await pump(buildPipeline)
} catch (err) {
gracefulError(err)
}
} else {
await pump(buildPipeline)
}
}
}
@ -331,14 +333,6 @@ function createScriptTasks({ browserPlatforms, livereload }) {
let bundler = browserify(browserifyOpts)
.transform('babelify')
// Transpile any dependencies using the object spread/rest operator
// because it is incompatible with `esprima`, which is used by `envify`
// See https://github.com/jquery/esprima/issues/1927
.transform('babelify', {
only: ['./**/node_modules/libp2p'],
global: true,
plugins: ['@babel/plugin-proposal-object-rest-spread'],
})
.transform('brfs')
if (opts.buildLib) {
@ -362,6 +356,7 @@ function createScriptTasks({ browserPlatforms, livereload }) {
envify({
METAMASK_DEBUG: opts.devMode,
METAMASK_ENVIRONMENT: environment,
METAMASK_VERSION: baseManifest.version,
METAMETRICS_PROJECT_ID: process.env.METAMETRICS_PROJECT_ID,
NODE_ENV: opts.devMode ? 'development' : 'production',
IN_TEST: opts.testing ? 'true' : false,
@ -406,10 +401,6 @@ function createScriptTasks({ browserPlatforms, livereload }) {
}
}
function beep() {
process.stdout.write('\x07')
}
function getEnvironment({ devMode, test }) {
// get environment slug
if (devMode) {
@ -429,3 +420,12 @@ function getEnvironment({ devMode, test }) {
}
return 'other'
}
function beep() {
process.stdout.write('\x07')
}
function gracefulError(err) {
console.warn(err)
beep()
}

@ -19,7 +19,7 @@ const copyTargets = [
dest: `images`,
},
{
src: `./node_modules/eth-contract-metadata/images/`,
src: `./node_modules/@metamask/contract-metadata/images/`,
dest: `images/contract`,
},
{
@ -44,6 +44,16 @@ const copyTargets = [
pattern: `*.html`,
dest: ``,
},
{
src: `./node_modules/ses/dist/`,
pattern: `lockdown.cjs`,
dest: ``,
},
{
src: `./app/scripts/`,
pattern: `runLockdown.js`,
dest: ``,
},
]
const languageTags = new Set()

@ -68,7 +68,9 @@ function runInChildProcess(task) {
)
}
return instrumentForTaskStats(taskName, async () => {
const childProcess = spawn('yarn', ['build', taskName, '--skip-stats'])
const childProcess = spawn('yarn', ['build', taskName, '--skip-stats'], {
env: process.env,
})
// forward logs to main process
// skip the first stdout event (announcing the process command)
childProcess.stdout.once('data', () => {
@ -85,7 +87,7 @@ function runInChildProcess(task) {
if (errCode !== 0) {
reject(
new Error(
`MetaMask build: runInChildProcess for task "${taskName}" encountered an error`,
`MetaMask build: runInChildProcess for task "${taskName}" encountered an error ${errCode}`,
),
)
return

@ -10,6 +10,7 @@
"benchmark:chrome": "SELENIUM_BROWSER=chrome node test/e2e/benchmark.js",
"benchmark:firefox": "SELENIUM_BROWSER=firefox node test/e2e/benchmark.js",
"build:test": "yarn build test",
"build:test:metrics": "SEGMENT_HOST='http://localhost:9090' SEGMENT_WRITE_KEY='FAKE' SEGMENT_LEGACY_WRITE_KEY='FAKE' yarn build test",
"test": "yarn test:unit && yarn lint",
"dapp": "node development/static-server.js node_modules/@metamask/test-dapp/dist --port 8080",
"dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'",
@ -22,7 +23,9 @@
"test:unit:strict": "mocha --exit --require test/env.js --require test/setup.js --recursive \"test/unit/**/permissions/*.js\"",
"test:unit:path": "mocha --exit --require test/env.js --require test/setup.js --recursive",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-all.sh",
"test:e2e:chrome:metrics": "SELENIUM_BROWSER=chrome mocha test/e2e/metrics.spec.js",
"test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-all.sh",
"test:e2e:firefox:metrics": "SELENIUM_BROWSER=firefox mocha test/e2e/metrics.spec.js",
"test:coverage": "nyc --silent --check-coverage yarn test:unit:strict && nyc --silent --no-clean yarn test:unit:lax && nyc report --reporter=text --reporter=html",
"test:coverage:strict": "nyc --check-coverage yarn test:unit:strict",
"test:coverage:path": "nyc --check-coverage yarn test:unit:path",
@ -57,6 +60,9 @@
"**/knex/minimist": "^1.2.5",
"**/optimist/minimist": "^1.2.5",
"**/socketcluster/minimist": "^1.2.5",
"**/redux/symbol-observable": "^2.0.3",
"**/redux-devtools-instrument/symbol-observable": "^2.0.3",
"**/rxjs/symbol-observable": "^2.0.3",
"3box/ipfs/ipld-zcash/zcash-bitcore-lib/lodash": "^4.17.19",
"3box/ipfs/ipld-zcash/zcash-bitcore-lib/elliptic": "^6.5.3",
"3box/**/libp2p-crypto/node-forge": "^0.10.0",
@ -70,10 +76,11 @@
"@formatjs/intl-relativetimeformat": "^5.2.6",
"@fortawesome/fontawesome-free": "^5.13.0",
"@material-ui/core": "^4.11.0",
"@metamask/contract-metadata": "^1.19.0",
"@metamask/controllers": "^4.2.0",
"@metamask/eth-ledger-bridge-keyring": "^0.2.6",
"@metamask/eth-token-tracker": "^3.0.1",
"@metamask/etherscan-link": "^1.3.0",
"@metamask/etherscan-link": "^1.4.0",
"@metamask/inpage-provider": "^6.1.0",
"@metamask/jazzicon": "^2.0.0",
"@metamask/logo": "^2.5.0",
@ -87,18 +94,15 @@
"await-semaphore": "^0.1.1",
"bignumber.js": "^4.1.0",
"bn.js": "^4.11.7",
"c3": "^0.7.10",
"classnames": "^2.2.6",
"content-hash": "^2.5.2",
"copy-to-clipboard": "^3.0.8",
"currency-formatter": "^1.4.2",
"d3": "^5.15.0",
"debounce-stream": "^2.0.0",
"deep-freeze-strict": "1.1.1",
"dnode": "^1.2.2",
"end-of-stream": "^1.4.4",
"eth-block-tracker": "^4.4.2",
"eth-contract-metadata": "^1.16.0",
"eth-ens-namehash": "^2.0.8",
"eth-json-rpc-errors": "^2.0.2",
"eth-json-rpc-filters": "^4.2.1",
@ -120,14 +124,15 @@
"ethjs-contract": "^0.2.3",
"ethjs-ens": "^2.0.0",
"ethjs-query": "^0.3.4",
"extension-port-stream": "^1.0.0",
"extension-port-stream": "^2.0.0",
"extensionizer": "^1.0.1",
"fast-json-patch": "^2.0.4",
"fuse.js": "^3.2.0",
"human-standard-token-abi": "^2.0.0",
"json-rpc-engine": "^5.3.0",
"json-rpc-engine": "^6.1.0",
"json-rpc-middleware-stream": "^2.1.1",
"jsonschema": "^1.2.4",
"localforage": "^1.9.0",
"lodash": "^4.17.19",
"loglevel": "^1.4.1",
"luxon": "^1.24.1",
@ -188,11 +193,11 @@
"@metamask/forwarder": "^1.1.0",
"@metamask/test-dapp": "^4.0.1",
"@sentry/cli": "^1.58.0",
"@storybook/addon-actions": "^5.3.14",
"@storybook/addon-backgrounds": "^5.3.14",
"@storybook/addon-knobs": "^5.3.14",
"@storybook/core": "^5.3.14",
"@storybook/react": "^5.3.14",
"@storybook/addon-actions": "^6.1.9",
"@storybook/addon-backgrounds": "^6.1.9",
"@storybook/addon-knobs": "^6.1.9",
"@storybook/core": "^6.1.9",
"@storybook/react": "^6.1.9",
"@storybook/storybook-deployer": "^2.8.6",
"@testing-library/react": "^10.4.8",
"@testing-library/react-hooks": "^3.2.1",
@ -214,7 +219,6 @@
"css-loader": "^2.1.1",
"del": "^3.0.0",
"deps-dump": "^1.1.0",
"envify": "^4.1.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"eslint": "^7.7.0",
@ -250,8 +254,8 @@
"gulp-zip": "^4.0.0",
"jsdom": "^11.2.0",
"koa": "^2.7.0",
"lavamoat-core": "^6.1.0",
"lockfile-lint": "^4.0.0",
"loose-envify": "^1.4.0",
"mocha": "^7.2.0",
"nock": "^9.0.14",
"node-fetch": "^2.6.1",
@ -270,10 +274,11 @@
"regenerator-runtime": "^0.13.3",
"remote-redux-devtools": "^0.5.16",
"remotedev-server": "^0.3.1",
"resolve-url-loader": "^2.3.0",
"resolve-url-loader": "^3.1.2",
"sass-loader": "^7.0.1",
"selenium-webdriver": "^4.0.0-alpha.5",
"serve-handler": "^6.1.2",
"ses": "0.11.0",
"sesify": "^4.2.1",
"sesify-viz": "^3.0.10",
"sinon": "^9.0.0",

@ -0,0 +1,140 @@
// Type Imports
/**
* @typedef {import('../../app/scripts/lib/enums').EnvironmentType} EnvironmentType
*/
// Type Declarations
/**
* Used to attach context of where the user was at in the application when the
* event was triggered. Also included as full details of the current page in
* page events.
* @typedef {Object} MetaMetricsPageObject
* @property {string} [path] - the path of the current page (e.g /home)
* @property {string} [title] - the title of the current page (e.g 'home')
* @property {string} [url] - the fully qualified url of the current page
*/
/**
* For metamask, this is the dapp that triggered an interaction
* @typedef {Object} MetaMetricsReferrerObject
* @property {string} [url] - the origin of the dapp issuing the
* notification
*/
/**
* We attach context to every meta metrics event that help to qualify our
* analytics. This type has all optional values because it represents a
* returned object from a method call. Ideally app and userAgent are
* defined on every event. This is confirmed in the getTrackMetaMetricsEvent
* function, but still provides the consumer a way to override these values if
* necessary.
* @typedef {Object} MetaMetricsContext
* @property {Object} app
* @property {string} app.name - the name of the application tracking the event
* @property {string} app.version - the version of the application
* @property {string} userAgent - the useragent string of the user
* @property {MetaMetricsPageObject} [page] - an object representing details of
* the current page
* @property {MetaMetricsReferrerObject} [referrer] - for metamask, this is the
* dapp that triggered an interaction
*/
/**
* @typedef {Object} MetaMetricsEventPayload
* @property {string} event - event name to track
* @property {string} category - category to associate event to
* @property {string} [environmentType] - The type of environment this event
* occurred in. Defaults to the background process type
* @property {object} [properties] - object of custom values to track, keys
* in this object must be in snake_case
* @property {object} [sensitiveProperties] - Object of sensitive values to
* track. Keys in this object must be in snake_case. These properties will be
* sent in an additional event that excludes the user's metaMetricsId
* @property {number} [revenue] - amount of currency that event creates in
* revenue for MetaMask
* @property {string} [currency] - ISO 4127 format currency for events with
* revenue, defaults to US dollars
* @property {number} [value] - Abstract business "value" attributable to
* customers who trigger this event
* @property {MetaMetricsPageObject} [page] - the page/route that the event
* occurred on
* @property {MetaMetricsReferrerObject} [referrer] - the origin of the dapp
* that triggered the event
*/
/**
* @typedef {Object} MetaMetricsEventOptions
* @property {boolean} [isOptIn] - happened during opt in/out workflow
* @property {boolean} [flushImmediately] - When true will automatically flush
* the segment queue after tracking the event. Recommended if the result of
* tracking the event must be known before UI transition or update
* @property {boolean} [excludeMetaMetricsId] - whether to exclude the user's
* metametrics id for anonymity
* @property {string} [metaMetricsId] - an override for the metaMetricsId in
* the event one is created as part of an asynchronous workflow, such as
* awaiting the result of the metametrics opt-in function that generates the
* user's metametrics id
* @property {boolean} [matomoEvent] - is this event a holdover from matomo
* that needs further migration? when true, sends the data to a special
* segment source that marks the event data as not conforming to our schema
*/
/**
* Represents the shape of data sent to the segment.track method.
* @typedef {Object} SegmentEventPayload
* @property {string} [userId] - The metametrics id for the user
* @property {string} [anonymousId] - An anonymousId that is used to track
* sensitive data while preserving anonymity.
* @property {string} event - name of the event to track
* @property {Object} properties - properties to attach to the event
* @property {MetaMetricsContext} context - the context the event occurred in
*/
/**
* @typedef {Object} MetaMetricsPagePayload
* @property {string} name - The name of the page that was viewed
* @property {Object} [params] - The variadic parts of the page url
* example (route: `/asset/:asset`, path: `/asset/ETH`)
* params: { asset: 'ETH' }
* @property {EnvironmentType} environmentType - the environment type that the
* page was viewed in
* @property {MetaMetricsPageObject} [page] - the details of the page
* @property {MetaMetricsReferrerObject} [referrer] - dapp that triggered the page
* view
*/
/**
* @typedef {Object} MetaMetricsPageOptions
* @property {boolean} [isOptInPath] - is the current path one of the pages in
* the onboarding workflow? If true and participateInMetaMetrics is null track
* the page view
*/
export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000'
/**
* This object is used to identify events that are triggered by the background
* process.
* @type {MetaMetricsPageObject}
*/
export const METAMETRICS_BACKGROUND_PAGE_OBJECT = {
path: '/background-process',
title: 'Background Process',
url: '/background-process',
}
/**
* @typedef {Object} SegmentInterface
* @property {SegmentEventPayload[]} queue - A queue of events to be sent when
* the flushAt limit has been reached, or flushInterval occurs
* @property {() => void} flush - Immediately flush the queue, resetting it to
* an empty array and sending the pending events to Segment
* @property {(
* payload: SegmentEventPayload,
* callback: (err?: Error) => void
* ) => void} track - Track an event with Segment, using the internal batching
* mechanism to optimize network requests
* @property {(payload: Object) => void} page - Track a page view with Segment
* @property {() => void} identify - Identify an anonymous user. We do not
* currently use this method.
*/

@ -1,8 +1,8 @@
import contractMap from 'eth-contract-metadata'
import contractMap from '@metamask/contract-metadata'
/**
* A normalized list of addresses exported as part of the contractMap in
* eth-contract-metadata. Used primarily to validate if manually entered
* @metamask/contract-metadata. Used primarily to validate if manually entered
* contract addresses do not match one of our listed tokens
*/
export const LISTED_CONTRACT_ADDRESSES = Object.keys(

@ -0,0 +1,3 @@
### Shared Modules
This folder is reserved for modules that can be used globally within both the background and ui applications.

@ -1,302 +0,0 @@
import Analytics from 'analytics-node'
import { merge, omit, pick } from 'lodash'
// flushAt controls how many events are sent to segment at once. Segment
// will hold onto a queue of events until it hits this number, then it sends
// them as a batch. This setting defaults to 20, but that is too high for
// notification workflows. We also cannot send each event as singular payloads
// because it seems to bombard segment and potentially cause event loss.
// I chose 5 here because it is sufficiently high enough to optimize our network
// requests, while also being low enough to be reasonable.
const flushAt = process.env.METAMASK_ENVIRONMENT === 'production' ? 5 : 1
// flushInterval controls how frequently the queue is flushed to segment.
// This happens regardless of the size of the queue. The default setting is
// 10,000ms (10 seconds). This default is absurdly high for our typical user
// flow through confirmations. I have chosen 10 ms here because it works really
// well with our wrapped track function. The track function returns a promise
// that is only fulfilled when it has been sent to segment. A 10 ms delay is
// negligible to the user, but allows us to properly batch events that happen
// in rapid succession.
const flushInterval = 10
export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000'
const segmentNoop = {
track(_, callback = () => undefined) {
// Need to call the callback so that environments without a segment id still
// resolve the promise from trackMetaMetricsEvent
return callback()
},
page() {
// noop
},
identify() {
// noop
},
}
/**
* Used to determine whether or not to attach a user's metametrics id
* to events that include on-chain data. This helps to prevent identifying
* a user by being able to trace their activity on etherscan/block exploring
*/
const trackableSendCounts = {
1: true,
10: true,
30: true,
50: true,
100: true,
250: true,
500: true,
1000: true,
2500: true,
5000: true,
10000: true,
25000: true,
}
export function sendCountIsTrackable(sendCount) {
return Boolean(trackableSendCounts[sendCount])
}
const isDevOrTestEnvironment = Boolean(
process.env.METAMASK_DEBUG || process.env.IN_TEST,
)
// This allows us to overwrite the metric destination for testing purposes
const host = process.env.SEGMENT_HOST ?? undefined
// We do not want to track events on development builds unless specifically
// provided a SEGMENT_WRITE_KEY. This also holds true for test environments and
// E2E, which is handled in the build process by never providing the SEGMENT_WRITE_KEY
// when process.env.IN_TEST is truthy
export const segment =
!process.env.SEGMENT_WRITE_KEY || (isDevOrTestEnvironment && !host)
? segmentNoop
: new Analytics(process.env.SEGMENT_WRITE_KEY, {
host,
flushAt,
flushInterval,
})
export const segmentLegacy =
!process.env.SEGMENT_LEGACY_WRITE_KEY || (isDevOrTestEnvironment && !host)
? segmentNoop
: new Analytics(process.env.SEGMENT_LEGACY_WRITE_KEY, {
host,
flushAt,
flushInterval,
})
/**
* We attach context to every meta metrics event that help to qualify our analytics.
* This type has all optional values because it represents a returned object from a
* method call. Ideally app and userAgent are defined on every event. This is confirmed
* in the getTrackMetaMetricsEvent function, but still provides the consumer a way to
* override these values if necessary.
* @typedef {Object} MetaMetricsContext
* @property {Object} app
* @property {string} app.name - the name of the application tracking the event
* @property {string} app.version - the version of the application
* @property {string} userAgent - the useragent string of the user
* @property {Object} [page] - an object representing details of the current page
* @property {string} [page.path] - the path of the current page (e.g /home)
* @property {string} [page.title] - the title of the current page (e.g 'home')
* @property {string} [page.url] - the fully qualified url of the current page
* @property {Object} [referrer] - for metamask, this is the dapp that triggered an interaction
* @property {string} [referrer.url] - the origin of the dapp issuing the notification
*/
/**
* page and referrer from the MetaMetricsContext are very dynamic in nature and may be
* provided as part of the initial context payload when creating the trackMetaMetricsEvent function,
* or at the event level when calling the trackMetaMetricsEvent function.
* @typedef {Pick<MetaMetricsContext, 'page' | 'referrer'>} MetaMetricsDynamicContext
*/
/**
* @typedef {import('../../app/scripts/lib/enums').EnvironmentType} EnvironmentType
*/
/**
* @typedef {Object} MetaMetricsRequiredState
* @property {bool} participateInMetaMetrics - has the user opted into metametrics
* @property {string} [metaMetricsId] - the user's metaMetricsId, if they have opted in
* @property {MetaMetricsDynamicContext} context - context about the event
* @property {string} chainId - the chain id of the current network
* @property {string} locale - the locale string of the current user
* @property {string} network - the name of the current network
* @property {EnvironmentType} environmentType - environment that the event happened in
* @property {string} [metaMetricsSendCount] - number of transactions sent, used to add metametricsId
* intermittently to events with onchain data attached to them used to protect identity of users.
*/
/**
* @typedef {Object} MetaMetricsEventPayload
* @property {string} event - event name to track
* @property {string} category - category to associate event to
* @property {boolean} [isOptIn] - happened during opt in/out workflow
* @property {object} [properties] - object of custom values to track, snake_case
* @property {object} [sensitiveProperties] - Object of sensitive values to track, snake_case.
* These properties will be sent in an additional event that excludes the user's metaMetricsId.
* @property {number} [revenue] - amount of currency that event creates in revenue for MetaMask
* @property {string} [currency] - ISO 4127 format currency for events with revenue, defaults to US dollars
* @property {number} [value] - Abstract "value" that this event has for MetaMask.
* @property {boolean} [excludeMetaMetricsId] - whether to exclude the user's metametrics id for anonymity
* @property {string} [metaMetricsId] - an override for the metaMetricsId in the event one is created as part
* of an asynchronous workflow, such as awaiting the result of the metametrics opt-in function that generates the
* user's metametrics id.
* @property {boolean} [matomoEvent] - is this event a holdover from matomo that needs further migration?
* when true, sends the data to a special segment source that marks the event data as not conforming to our
* ideal schema
* @property {MetaMetricsDynamicContext} [eventContext] - additional context to attach to event
*/
/**
* Returns a function for tracking Segment events.
*
* @param {string} metamaskVersion - The current version of the MetaMask extension.
* @param {() => MetaMetricsRequiredState} getDynamicState - A function returning required fields
* @returns {(payload: MetaMetricsEventPayload) => Promise<void>} function to track an event
*/
export function getTrackMetaMetricsEvent(metamaskVersion, getDynamicState) {
const version =
process.env.METAMASK_ENVIRONMENT === 'production'
? metamaskVersion
: `${metamaskVersion}-${process.env.METAMASK_ENVIRONMENT}`
return function trackMetaMetricsEvent({
event,
category,
isOptIn,
properties = {},
sensitiveProperties,
revenue,
currency,
value,
metaMetricsId: metaMetricsIdOverride,
excludeMetaMetricsId: excludeId,
matomoEvent = false,
eventContext = {},
}) {
if (!event || !category) {
throw new Error('Must specify event and category.')
}
// Uses recursion to track a duplicate event with sensitive properties included,
// but metaMetricsId excluded
if (sensitiveProperties) {
if (excludeId === true) {
throw new Error(
'sensitiveProperties was specified in an event payload that also set the excludeMetaMetricsId flag',
)
}
trackMetaMetricsEvent({
event,
category,
isOptIn,
properties: merge(sensitiveProperties, properties),
revenue,
currency,
value,
excludeMetaMetricsId: true,
matomoEvent,
eventContext,
})
}
const {
participateInMetaMetrics,
context: providedContext,
metaMetricsId,
environmentType,
chainId,
locale,
network,
metaMetricsSendCount,
} = getDynamicState()
let excludeMetaMetricsId = excludeId ?? false
// This is carried over from the old implementation, and will likely need
// to be updated to work with the new tracking plan. I think we should use
// a config setting for this instead of trying to match the event name
const isSendFlow = Boolean(event.match(/^send|^confirm/u))
if (
isSendFlow &&
metaMetricsSendCount &&
!sendCountIsTrackable(metaMetricsSendCount + 1)
) {
excludeMetaMetricsId = true
}
if (!participateInMetaMetrics && !isOptIn) {
return Promise.resolve()
}
/** @type {MetaMetricsContext} */
const context = {
app: {
name: 'MetaMask Extension',
version,
},
userAgent: window.navigator.userAgent,
...pick(providedContext, ['page', 'referrer']),
...pick(eventContext, ['page', 'referrer']),
}
const trackOptions = {
event,
properties: {
// These values are omitted from properties because they have special meaning
// in segment. https://segment.com/docs/connections/spec/track/#properties.
// to avoid accidentally using these inappropriately, you must add them as top
// level properties on the event payload. We also exclude locale to prevent consumers
// from overwriting this context level property. We track it as a property
// because not all destinations map locale from context.
...omit(properties, ['revenue', 'locale', 'currency', 'value']),
revenue,
value,
currency,
category,
network,
locale,
chain_id: chainId,
environment_type: environmentType,
},
context,
}
// If we are tracking sensitive data we will always use the anonymousId property
// as well as our METAMETRICS_ANONYMOUS_ID. This prevents us from associating potentially
// identifiable information with a specific id. During the opt in flow we will track all
// events, but do so with the anonymous id. The one exception to that rule is after the
// user opts in to MetaMetrics. When that happens we receive back the user's new MetaMetrics
// id before it is fully persisted to state. To avoid a race condition we explicitly pass the
// new id to the track method. In that case we will track the opt in event to the user's id.
// In all other cases we use the metaMetricsId from state.
if (excludeMetaMetricsId) {
trackOptions.anonymousId = METAMETRICS_ANONYMOUS_ID
} else if (isOptIn && metaMetricsIdOverride) {
trackOptions.userId = metaMetricsIdOverride
} else if (isOptIn) {
trackOptions.anonymousId = METAMETRICS_ANONYMOUS_ID
} else {
trackOptions.userId = metaMetricsId
}
return new Promise((resolve, reject) => {
// This is only safe to do because we have set an extremely low (10ms) flushInterval.
const callback = (err) => {
if (err) {
return reject(err)
}
return resolve()
}
if (matomoEvent === true) {
segmentLegacy.track(trackOptions, callback)
} else {
segment.track(trackOptions, callback)
}
})
}
}

File diff suppressed because it is too large Load Diff

@ -29,6 +29,7 @@ describe('MetaMask', function () {
})
const result = await buildWebDriver()
driver = result.driver
await driver.navigate()
})
afterEach(async function () {

@ -13,6 +13,7 @@ const ALL_PAGES = Object.values(PAGES)
async function measurePage(pageName) {
let metrics
await withFixtures({ fixtures: 'imported-account' }, async ({ driver }) => {
await driver.navigate()
const passwordField = await driver.findElement(By.css('#password'))
await passwordField.sendKeys('correct horse battery staple')
await passwordField.sendKeys(Key.ENTER)

@ -28,6 +28,7 @@ describe('MetaMask', function () {
})
const result = await buildWebDriver()
driver = result.driver
await driver.navigate()
})
afterEach(async function () {

@ -0,0 +1,168 @@
{
"data": {
"AppStateController": {
"connectedStatusPopoverHasBeenShown": false,
"swapsWelcomeMessageHasBeenShown": true
},
"CachedBalancesController": {
"cachedBalances": {
"4": {},
"1337": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": "0x15af1d78b58c40000"
}
}
},
"CurrencyController": {
"conversionDate": 1594348502.519,
"conversionRate": 240.09,
"currentCurrency": "usd",
"nativeCurrency": "ETH"
},
"IncomingTransactionsController": {
"incomingTransactions": {},
"incomingTxLastFetchedBlocksByNetwork": {
"goerli": null,
"kovan": null,
"mainnet": null,
"rinkeby": 5570536,
"localhost": 98
}
},
"KeyringController": {
"vault": "{\"data\":\"s6TpYjlUNsn7ifhEFTkuDGBUM1GyOlPrim7JSjtfIxgTt8/6MiXgiR/CtFfR4dWW2xhq85/NGIBYEeWrZThGdKGarBzeIqBfLFhw9n509jprzJ0zc2Rf+9HVFGLw+xxC4xPxgCS0IIWeAJQ+XtGcHmn0UZXriXm8Ja4kdlow6SWinB7sr/WM3R0+frYs4WgllkwggDf2/Tv6VHygvLnhtzp6hIJFyTjh+l/KnyJTyZW1TkZhDaNDzX3SCOHT\",\"iv\":\"FbeHDAW5afeWNORfNJBR0Q==\",\"salt\":\"TxZ+WbCW6891C9LK/hbMAoUsSEW1E8pyGLVBU6x5KR8=\"}"
},
"NetworkController": {
"provider": {
"nickname": "Localhost 8545",
"rpcUrl": "http://localhost:8545",
"chainId": "0x539",
"ticker": "ETH",
"type": "rpc"
},
"network": "1337"
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false
},
"PermissionsMetadata": {
"permissionsLog": [
{
"id": 1764280960,
"method": "eth_requestAccounts",
"methodType": "restricted",
"origin": "http://127.0.0.1:8080",
"request": {
"method": "eth_requestAccounts",
"jsonrpc": "2.0",
"id": 1764280960,
"origin": "http://127.0.0.1:8080",
"tabId": 2
},
"requestTime": 1594348329232,
"response": {
"id": 1764280960,
"jsonrpc": "2.0",
"result": ["0x5cfe73b6021e818b776b421b1c4db2474086a7e1"]
},
"responseTime": 1594348332276,
"success": true
}
],
"permissionsHistory": {
"http://127.0.0.1:8080": {
"eth_accounts": {
"accounts": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": 1594348332276
},
"lastApproved": 1594348332276
}
}
},
"domainMetadata": {
"http://127.0.0.1:8080": {
"name": "E2E Test Dapp",
"icon": "http://127.0.0.1:8080/metamask-fox.svg",
"lastUpdated": 1594348323811,
"host": "127.0.0.1:8080"
}
}
},
"PreferencesController": {
"frequentRpcListDetail": [],
"accountTokens": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
"rinkeby": [],
"ropsten": []
}
},
"assetImages": {},
"tokens": [],
"suggestedTokens": {},
"useBlockie": false,
"useNonceField": false,
"usePhishDetect": true,
"featureFlags": {
"showIncomingTransactions": true,
"transactionTime": false
},
"knownMethodData": {},
"participateInMetaMetrics": true,
"firstTimeFlowType": "create",
"currentLocale": "en",
"identities": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
"address": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"name": "Account 1"
}
},
"lostIdentities": {},
"forgottenPassword": false,
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
},
"completedOnboarding": true,
"metaMetricsId": "fake-metrics-id",
"metaMetricsSendCount": 0,
"ipfsGateway": "dweb.link",
"selectedAddress": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1"
},
"config": {},
"firstTimeInfo": {
"date": 1575697234195,
"version": "7.7.0"
},
"PermissionsController": {
"permissionsRequests": [],
"permissionsDescriptions": {},
"domains": {
"http://127.0.0.1:8080": {
"permissions": [
{
"@context": ["https://github.com/MetaMask/rpc-cap"],
"parentCapability": "eth_accounts",
"id": "f55a1c15-ea48-4088-968e-63be474d42fa",
"date": 1594348332268,
"invoker": "http://127.0.0.1:8080",
"caveats": [
{
"type": "limitResponseLength",
"value": 1,
"name": "primaryAccountOnly"
},
{
"type": "filterResponse",
"value": ["0x5cfe73b6021e818b776b421b1c4db2474086a7e1"],
"name": "exposedAccounts"
}
]
}
]
}
}
}
},
"meta": {
"version": 47
}
}

@ -35,6 +35,7 @@ describe('Using MetaMask with an existing account', function () {
})
const result = await buildWebDriver()
driver = result.driver
await driver.navigate()
})
afterEach(async function () {

@ -1,5 +1,9 @@
const path = require('path')
const sinon = require('sinon')
const createStaticServer = require('../../development/create-static-server')
const {
createSegmentServer,
} = require('../../development/lib/create-segment-server')
const Ganache = require('./ganache')
const FixtureServer = require('./fixture-server')
const { buildWebDriver } = require('./webdriver')
@ -11,10 +15,19 @@ const largeDelayMs = regularDelayMs * 2
const dappPort = 8080
async function withFixtures(options, testSuite) {
const { dapp, fixtures, ganacheOptions, driverOptions, title } = options
const {
dapp,
fixtures,
ganacheOptions,
driverOptions,
mockSegment,
title,
} = options
const fixtureServer = new FixtureServer()
const ganacheServer = new Ganache()
let dappServer
let segmentServer
let segmentStub
let webDriver
try {
@ -38,11 +51,23 @@ async function withFixtures(options, testSuite) {
dappServer.on('error', reject)
})
}
if (mockSegment) {
segmentStub = sinon.stub()
segmentServer = createSegmentServer((_request, response, events) => {
for (const event of events) {
segmentStub(event)
}
response.statusCode = 200
response.end()
})
await segmentServer.start(9090)
}
const { driver } = await buildWebDriver(driverOptions)
webDriver = driver
await testSuite({
driver,
segmentStub,
})
if (process.env.SELENIUM_BROWSER === 'chrome') {
@ -57,7 +82,11 @@ async function withFixtures(options, testSuite) {
}
} catch (error) {
if (webDriver) {
try {
await webDriver.verboseReportOnFailure(title)
} catch (verboseReportError) {
console.error(verboseReportError)
}
}
throw error
} finally {
@ -76,6 +105,9 @@ async function withFixtures(options, testSuite) {
})
})
}
if (segmentServer) {
await segmentServer.stop()
}
}
}

@ -33,6 +33,7 @@ describe('MetaMask', function () {
})
const result = await buildWebDriver()
driver = result.driver
await driver.navigate()
})
afterEach(async function () {

@ -22,6 +22,7 @@ describe('MetaMask', function () {
await ganacheServer.start()
const result = await buildWebDriver({ responsive: true })
driver = result.driver
await driver.navigate()
})
afterEach(async function () {

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

Loading…
Cancel
Save