mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #9976 from MetaMask/Version-v8.1.6
Version v8.1.6 RC
This commit is contained in:
commit
52e428fbe6
@ -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',
|
||||
],
|
||||
|
1
.github/dependabot.yml
vendored
1
.github/dependabot.yml
vendored
@ -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: [
|
||||
{ name: 'light', value: '#FFFFFF'},
|
||||
{ name: 'dark', value: '#333333' },
|
||||
],
|
||||
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'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}
|
11
CHANGELOG.md
11
CHANGELOG.md
@ -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"
|
||||
},
|
||||
|
BIN
app/images/icon-48.png
Normal file
BIN
app/images/icon-48.png
Normal file
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) {
|
||||
|
366
app/scripts/controllers/metametrics.js
Normal file
366
app/scripts/controllers/metametrics.js
Normal file
@ -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')
|
||||
) {
|
||||
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
|
||||
}
|
||||
default:
|
||||
end(new Error(`Asset of type ${type} not supported`))
|
||||
return
|
||||
}
|
||||
}
|
||||
async requestWatchAsset(req) {
|
||||
const { type, options } = req.params
|
||||
|
||||
next()
|
||||
switch (type) {
|
||||
case 'ERC20':
|
||||
return await this._handleWatchAssetERC20(options)
|
||||
default:
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
`Asset of type "${type}" not supported.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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(() => {
|
||||
const tokenAddresses = this.getTokens().filter(
|
||||
(token) => token.address === normalizeAddress(rawAddress),
|
||||
)
|
||||
return tokenAddresses.length > 0
|
||||
})
|
||||
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 === 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)
|
||||
|
7
app/scripts/initSentry.js
Normal file
7
app/scripts/initSentry.js
Normal file
@ -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({
|
||||
event: `Website Used window.web3`,
|
||||
category: 'inpage_provider',
|
||||
properties: { action, web3Path: path },
|
||||
eventContext: {
|
||||
sendMetrics(
|
||||
{
|
||||
event: `Website Used window.web3`,
|
||||
category: 'inpage_provider',
|
||||
properties: { action, web3Path: path },
|
||||
referrer: {
|
||||
url: origin,
|
||||
},
|
||||
},
|
||||
excludeMetaMetricsId: true,
|
||||
})
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
101
app/scripts/lib/segment.js
Normal file
101
app/scripts/lib/segment.js
Normal file
@ -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({
|
||||
event: name,
|
||||
category: 'Background',
|
||||
matomoEvent: true,
|
||||
properties: {
|
||||
action,
|
||||
...additionalProperties,
|
||||
...customVariables,
|
||||
this.metaMetricsController.trackEvent(
|
||||
{
|
||||
event: name,
|
||||
category: 'Background',
|
||||
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) {
|
||||
|
44
app/scripts/migrations/049.js
Normal file
44
app/scripts/migrations/049.js
Normal file
@ -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
|
||||
}
|
32
app/scripts/migrations/050.js
Normal file
32
app/scripts/migrations/050.js
Normal file
@ -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
|
||||
|
7
app/scripts/runLockdown.js
Normal file
7
app/scripts/runLockdown.js
Normal file
@ -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())
|
||||
|
||||
// Initialize Source Maps
|
||||
buildStream = buildStream
|
||||
source(opts.filename),
|
||||
// Initialize Source Maps
|
||||
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
|
||||
|
33
package.json
33
package.json
@ -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",
|
||||
|
140
shared/constants/metametrics.js
Normal file
140
shared/constants/metametrics.js
Normal file
@ -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(
|
||||
|
3
shared/modules/README.md
Normal file
3
shared/modules/README.md
Normal file
@ -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 () {
|
||||
|
168
test/e2e/fixtures/metrics-enabled/state.json
Normal file
168
test/e2e/fixtures/metrics-enabled/state.json
Normal file
@ -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) {
|
||||
await webDriver.verboseReportOnFailure(title)
|
||||
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…
Reference in New Issue
Block a user