diff --git a/.circleci/config.yml b/.circleci/config.yml
index 92afe9183..183808490 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -53,6 +53,9 @@ workflows:
- prep-build-test:
requires:
- prep-deps
+ - prep-build-test-mv3:
+ requires:
+ - prep-deps
- prep-build-test-flask:
requires:
- prep-deps
@@ -62,6 +65,9 @@ workflows:
- prep-build-storybook:
requires:
- test-storybook
+ - prep-build-ts-migration-dashboard:
+ requires:
+ - prep-deps
- test-lint:
requires:
- prep-deps
@@ -133,6 +139,12 @@ workflows:
- benchmark:
requires:
- prep-build-test
+ - user-actions-benchmark:
+ requires:
+ - prep-build-test
+ - stats-module-load-init:
+ requires:
+ - prep-build-test-mv3
- job-publish-prerelease:
requires:
- prep-deps
@@ -140,7 +152,11 @@ workflows:
- prep-build-beta
- prep-build-flask
- prep-build-storybook
+ - prep-build-ts-migration-dashboard
+ - prep-build-test-mv3
- benchmark
+ - user-actions-benchmark
+ - stats-module-load-init
- all-tests-pass
- job-publish-release:
filters:
@@ -157,6 +173,12 @@ workflows:
only: develop
requires:
- prep-build-storybook
+ - job-publish-ts-migration-dashboard:
+ filters:
+ branches:
+ only: develop
+ requires:
+ - prep-build-ts-migration-dashboard
jobs:
create_release_pull_request:
@@ -200,8 +222,8 @@ jobs:
- persist_to_workspace:
root: .
paths:
- - node_modules
- - build-artifacts
+ - node_modules
+ - build-artifacts
validate-lavamoat-config:
executor: node-browsers-medium-plus
@@ -224,9 +246,25 @@ jobs:
- checkout
- attach_workspace:
at: .
- - run:
- name: build:dist
- command: yarn dist
+ - when:
+ condition:
+ not:
+ matches:
+ pattern: /^master$/
+ value: << pipeline.git.branch >>
+ steps:
+ - run:
+ name: build:dist
+ command: yarn build dist
+ - when:
+ condition:
+ matches:
+ pattern: /^master$/
+ value: << pipeline.git.branch >>
+ steps:
+ - run:
+ name: build:prod
+ command: yarn build prod
- run:
name: build:debug
command: find dist/ -type f -exec md5sum {} \; | sort -k 2
@@ -244,7 +282,7 @@ jobs:
at: .
- run:
name: build:dist
- command: yarn build --build-type beta prod
+ command: yarn build --build-type beta dist
- run:
name: build:debug
command: find dist/ -type f -exec md5sum {} \; | sort -k 2
@@ -268,7 +306,7 @@ jobs:
at: .
- run:
name: build:dist
- command: yarn build --build-type flask prod
+ command: yarn build --build-type flask dist
- run:
name: build:debug
command: find dist/ -type f -exec md5sum {} \; | sort -k 2
@@ -305,6 +343,26 @@ jobs:
- dist-test-flask
- builds-test-flask
+ prep-build-test-mv3:
+ executor: node-browsers-medium-plus
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: Build extension in mv3 for testing
+ command: yarn build:test:mv3
+ - run:
+ name: Move test build to 'dist-test' to avoid conflict with production build
+ command: mv ./dist ./dist-test-mv3
+ - run:
+ name: Move test zips to 'builds-test' to avoid conflict with production build
+ command: mv ./builds ./builds-test-mv3
+ - persist_to_workspace:
+ root: .
+ paths:
+ - dist-test-mv3
+ - builds-test-mv3
prep-build-test:
executor: node-browsers-medium-plus
@@ -341,6 +399,20 @@ jobs:
paths:
- storybook-build
+ prep-build-ts-migration-dashboard:
+ executor: node-browsers
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: Build TypeScript migration dashboard
+ command: yarn ts-migration:dashboard:build
+ - persist_to_workspace:
+ root: .
+ paths:
+ - development/ts-migration-dashboard/build
+
test-storybook:
executor: node-browsers
steps:
@@ -419,7 +491,6 @@ jobs:
name: Validate release candidate changelog
command: yarn lint:changelog:rc
-
test-deps-audit:
executor: node-browsers
steps:
@@ -573,6 +644,69 @@ jobs:
root: .
paths:
- test-artifacts
+
+ user-actions-benchmark:
+ executor: node-browsers-medium-plus
+ steps:
+ - checkout
+ - run:
+ name: Re-Install Chrome
+ command: ./.circleci/scripts/chrome-install.sh
+ - attach_workspace:
+ at: .
+ - run:
+ name: Move test build to dist
+ command: mv ./dist-test ./dist
+ - run:
+ name: Move test zips to builds
+ command: mv ./builds-test ./builds
+ - run:
+ name: Run page load benchmark
+ command: yarn user-actions-benchmark:chrome --out test-artifacts/chrome/benchmark/user_actions.json --retries 2
+ - store_artifacts:
+ path: test-artifacts
+ destination: test-artifacts
+ - persist_to_workspace:
+ root: .
+ paths:
+ - test-artifacts
+
+ stats-module-load-init:
+ executor: node-browsers-medium-plus
+ steps:
+ - checkout
+ - run:
+ name: Re-Install Chrome
+ command: ./.circleci/scripts/chrome-install.sh
+ - attach_workspace:
+ at: .
+ - run:
+ name: Move test build to dist
+ command: mv ./dist-test-mv3 ./dist
+ - run:
+ name: Move test zips to builds
+ command: mv ./builds-test-mv3 ./builds
+ - run:
+ name: Run page load benchmark
+ command: |
+ mkdir -p test-artifacts/chrome/mv3
+ cp -R development/charts/flamegraph test-artifacts/chrome/mv3/initialisation
+ cp -R development/charts/flamegraph/chart test-artifacts/chrome/mv3/initialisation/background
+ cp -R development/charts/flamegraph/chart test-artifacts/chrome/mv3/initialisation/ui
+ cp -R development/charts/table test-artifacts/chrome/mv3/load_time
+ - run:
+ name: Run page load benchmark
+ command: yarn mv3:stats:chrome --out test-artifacts/chrome/mv3
+ - run:
+ name: Record bundle size at commit
+ command: ./.circleci/scripts/bundle-stats-commit.sh
+ - store_artifacts:
+ path: test-artifacts
+ destination: test-artifacts
+ - persist_to_workspace:
+ root: .
+ paths:
+ - test-artifacts
job-publish-prerelease:
executor: node-browsers
@@ -622,6 +756,9 @@ jobs:
- store_artifacts:
path: storybook-build
destination: storybook
+ - store_artifacts:
+ path: development/ts-migration-dashboard/build
+ destination: ts-migration-dashboard
- run:
name: build:announce
command: ./development/metamaskbot-build-announce.js
@@ -648,7 +785,7 @@ jobs:
steps:
- add_ssh_keys:
fingerprints:
- - "3d:49:29:f4:b2:e8:ea:af:d1:32:eb:2a:fc:15:85:d8"
+ - '3d:49:29:f4:b2:e8:ea:af:d1:32:eb:2a:fc:15:85:d8'
- checkout
- attach_workspace:
at: .
@@ -658,6 +795,23 @@ jobs:
git remote add storybook git@github.com:MetaMask/metamask-storybook.git
yarn storybook:deploy
+ job-publish-ts-migration-dashboard:
+ executor: node-browsers
+ steps:
+ - add_ssh_keys:
+ fingerprints:
+ - "8b:21:e3:20:7c:c9:db:82:74:2d:86:d6:11:a7:2f:49"
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: ts-migration-dashboard:deploy
+ command: |
+ git remote add ts-migration-dashboard git@github.com:MetaMask/metamask-extension-ts-migration-dashboard.git
+ git config user.name "MetaMask Bot"
+ git config user.email metamaskbot@users.noreply.github.com
+ yarn ts-migration:dashboard:deploy
+
test-unit:
executor: node-browsers
steps:
@@ -670,7 +824,7 @@ jobs:
- run:
name: test:coverage:jest
command: yarn test:coverage:jest
- - run:
+ - run:
name: Validate coverage thresholds
command: |
if ! git diff --exit-code jest.config.js development/jest.config.js; then
diff --git a/.circleci/scripts/bundle-stats-commit.sh b/.circleci/scripts/bundle-stats-commit.sh
new file mode 100755
index 000000000..db5fe101b
--- /dev/null
+++ b/.circleci/scripts/bundle-stats-commit.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+
+set -e
+set -u
+set -o pipefail
+
+if [[ "${CI:-}" != 'true' ]]
+then
+ printf '%s\n' 'CI environment variable must be set to true'
+ exit 1
+fi
+
+if [[ "${CIRCLECI:-}" != 'true' ]]
+then
+ printf '%s\n' 'CIRCLECI environment variable must be set to true'
+ exit 1
+fi
+
+if [[ "${CIRCLE_BRANCH}" != "develop" ]]
+then
+ printf 'This is not develop branch'
+ exit 0
+fi
+
+if [[ -z "${GITHUB_TOKEN:-}" ]]
+then
+ printf '%s\n' 'GITHUB_TOKEN environment variable must be set'
+ exit 1
+elif [[ -z "${GITHUB_TOKEN_USER:-}" ]]
+then
+ printf '%s\n' 'GITHUB_TOKEN_USER environment variable must be set'
+ exit 1
+fi
+
+mkdir temp
+
+git config --global user.email "metamaskbot@users.noreply.github.com"
+
+git config --global user.name "MetaMask Bot"
+
+git clone git@github.com:MetaMask/extension_bundlesize_stats.git temp
+
+if [[ -f "temp/stats/bundle_size_stats-${CIRCLE_SHA1}.json" ]]
+then
+ printf 'Bundle size of the commit is already recorded'
+ cd ..
+ rm -rf temp
+ exit 0
+fi
+
+cp -R test-artifacts/chrome/mv3/bundle_size_stats.json temp/stats
+
+echo " bundle_size_stats-${CIRCLE_SHA1}.json" >> temp/stats/fileList.txt
+
+mv temp/stats/bundle_size_stats.json "temp/stats/bundle_size_stats-${CIRCLE_SHA1}.json"
+
+cd temp
+
+git add .
+
+git commit --message "Bundle size at commit: ${CIRCLE_SHA1}"
+
+repo_slug="$CIRCLE_PROJECT_USERNAME/extension_bundlesize_stats"
+git push "https://$GITHUB_TOKEN_USER:$GITHUB_TOKEN@github.com/$repo_slug" main
+
+cd ..
+
+rm -rf temp
diff --git a/.circleci/scripts/create-lavamoat-viz.sh b/.circleci/scripts/create-lavamoat-viz.sh
index db1dc3979..4f2df2cdb 100755
--- a/.circleci/scripts/create-lavamoat-viz.sh
+++ b/.circleci/scripts/create-lavamoat-viz.sh
@@ -14,4 +14,4 @@ mkdir -p "${BUILD_DEST}"
yarn lavamoat:debug:build
# generate viz
-npx lavamoat-viz --dest "${BUILD_DEST}"
\ No newline at end of file
+npx lavamoat-viz --dest "${BUILD_DEST}"
diff --git a/.circleci/scripts/deps-install.sh b/.circleci/scripts/deps-install.sh
index 7ea3da9cb..1c450a9bc 100755
--- a/.circleci/scripts/deps-install.sh
+++ b/.circleci/scripts/deps-install.sh
@@ -13,4 +13,4 @@ har_files=(./*.har)
if [[ -f "${har_files[0]}" ]]
then
mv ./*.har build-artifacts/yarn-install-har/
-fi
\ No newline at end of file
+fi
diff --git a/.circleci/scripts/validate-lavamoat-policy.sh b/.circleci/scripts/validate-lavamoat-policy.sh
index d674cd3f0..177fabe0b 100755
--- a/.circleci/scripts/validate-lavamoat-policy.sh
+++ b/.circleci/scripts/validate-lavamoat-policy.sh
@@ -4,7 +4,7 @@ set -e
set -u
set -o pipefail
-yarn lavamoat:auto
+yarn lavamoat:auto:ci
if git diff --exit-code
then
diff --git a/.depcheckrc.yml b/.depcheckrc.yml
index 2652ffb2b..8883375e7 100644
--- a/.depcheckrc.yml
+++ b/.depcheckrc.yml
@@ -12,6 +12,8 @@ ignores:
# dev deps
#
+ # all @types/* packages are imported implicitly by TypeScript
+ - '@types/*'
# safety fallback for npm lifecycle scripts, not used normally
- '@lavamoat/preinstall-always-fail'
# used in testing + ci
@@ -20,7 +22,7 @@ ignores:
- '@metamask/phishing-warning' # statically hosted as part of some e2e tests
- '@metamask/test-dapp'
- '@metamask/design-tokens' # Only imported in index.css
- - '@tsconfig/node14' # required dynamically by TS, used in tsconfig.json
+ - '@tsconfig/node16' # required dynamically by TS, used in tsconfig.json
- '@sentry/cli' # invoked as `sentry-cli`
- 'chromedriver'
- 'depcheck' # ooo meta
@@ -42,6 +44,8 @@ ignores:
- 'css-loader'
- 'sass-loader'
- 'resolve-url-loader'
+ # jest environments
+ - 'jest-environment-jsdom'
# files depcheck should not parse
ignorePatterns:
diff --git a/.eslintrc.base.js b/.eslintrc.base.js
index 47c379969..b4e93c1c9 100644
--- a/.eslintrc.base.js
+++ b/.eslintrc.base.js
@@ -63,5 +63,14 @@ module.exports = {
// a browser context. For instance, we may import polyfills which change
// global variables, or we may import stylesheets.
'import/no-unassigned-import': 'off',
+
+ // import/no-named-as-default-member checks if default imports also have
+ // named exports matching properties used on the default import. Example:
+ // in confirm-seed-phrase-component.test.js we import sinon from 'sinon'
+ // and later access sinon.spy. spy is also exported from sinon directly and
+ // thus triggers the error. Turning this rule off to prevent churn when
+ // upgrading eslint and dependencies. This rule should be evaluated and
+ // if agreeable turned on upstream in @metamask/eslint-config
+ 'import/no-named-as-default-member': 'off',
},
};
diff --git a/.eslintrc.js b/.eslintrc.js
index 2001994f4..31c60bd51 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -8,6 +8,8 @@ module.exports = {
'app/vendor/**',
'builds/**/*',
'development/chromereload.js',
+ 'development/charts/**',
+ 'development/ts-migration-dashboard/build/**',
'dist/**/*',
'node_modules/**/*',
],
@@ -277,6 +279,20 @@ module.exports = {
{ maxSize: 50, inlineMaxSize: 50 },
],
'jest/no-restricted-matchers': 'off',
+ /**
+ * jest/prefer-to-be is a new rule that was disabled to reduce churn
+ * when upgrading eslint. It should be considered for use and enabled
+ * in a future PR if agreeable.
+ */
+ 'jest/prefer-to-be': 'off',
+ /**
+ * jest/lowercase-name was renamed to jest/prefer-lowercase-title this
+ * change was made to essentially retain the same state as the original
+ * eslint-config-jest until it is updated. At which point the following
+ * two lines can be deleted.
+ */
+ 'jest/lowercase-name': 'off',
+ 'jest/prefer-lowercase-title': ['error', { ignore: ['describe'] }],
},
},
/**
@@ -328,5 +344,11 @@ module.exports = {
sourceType: 'script',
},
},
+ {
+ files: ['ui/pages/settings/*.js'],
+ rules: {
+ 'sort-keys': ['error', 'asc', { natural: true }],
+ },
+ },
],
};
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 95077129c..0eaf7de40 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -4,5 +4,5 @@ contact_links:
url: https://community.metamask.io/c/feature-requests-ideas/
about: Request new features and vote on the ones that are important to you
- name: Get support or ask a question
- url: https://metamask.zendesk.com/hc/en-us/requests/new
+ url: https://metamask.zendesk.com/hc/en-us
about: Use the MetaMask support system to get help and ask questions
diff --git a/.gitignore b/.gitignore
index 693537314..c86fb82db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,8 @@ jest-coverage/
dist
builds/
builds.zip
+development/ts-migration-dashboard/build
+development/ts-migration-dashboard/intermediate
test-artifacts
test-builds
@@ -45,7 +47,9 @@ notes.txt
.nyc_output
+# MetaMask configuration
.metamaskrc
+.metamaskprodrc
# TypeScript
tsout/
diff --git a/.iyarc b/.iyarc
index 0d2a2e59f..bbd5d06c1 100644
--- a/.iyarc
+++ b/.iyarc
@@ -2,4 +2,4 @@
GHSA-93q8-gq69-wqmw
GHSA-257v-vj4p-3w2h
GHSA-wm7h-9275-46v2
-GHSA-pfrx-2q88-qq97
\ No newline at end of file
+GHSA-pfrx-2q88-qq97
diff --git a/.metamaskrc.dist b/.metamaskrc.dist
index 8ab56bf00..0554ac85e 100644
--- a/.metamaskrc.dist
+++ b/.metamaskrc.dist
@@ -5,8 +5,12 @@ SEGMENT_WRITE_KEY=
ONBOARDING_V2=
SWAPS_USE_DEV_APIS=
COLLECTIBLES_V1=
-TOKEN_DETECTION_V2=
-ADD_POPULAR_NETWORKS=
+PUBNUB_PUB_KEY=
+PUBNUB_SUB_KEY=
+TOKEN_ALLOWANCE_IMPROVEMENTS=
+
+; Set this to '1' to enable support for Sign-In with Ethereum [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361)
+SIWE_V1=
; Set this to test changes to the phishing warning page.
PHISHING_WARNING_PAGE_URL=
diff --git a/.prettierignore b/.prettierignore
index a98c312ad..75bdc6e52 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -11,3 +11,4 @@ app/vendor/**
test/e2e/send-eth-with-private-key-test/**
*.scss
development/chromereload.js
+development/ts-migration-dashboard/filesToConvert.json
diff --git a/.storybook/2.DOCUMENTATION.stories.mdx b/.storybook/2.DOCUMENTATION.stories.mdx
index da30c959e..17fb97396 100644
--- a/.storybook/2.DOCUMENTATION.stories.mdx
+++ b/.storybook/2.DOCUMENTATION.stories.mdx
@@ -50,7 +50,7 @@ import React from 'react';
import BuyIcon from '../icon/overview-buy-icon.component';
-// The mdx file to document component API and usage
+// The mdx file to document props and usage
import README from './README.mdx';
import Button from '.';
@@ -149,7 +149,7 @@ Now the storybook components are complete, the `README.mdx` documentation should
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
-
+
import Button from '.';
@@ -167,9 +167,9 @@ Buttons communicate actions that users can take.
-## Component API
+## Props
-
diff --git a/.storybook/3.COLORS.stories.mdx b/.storybook/3.COLORS.stories.mdx
index 507fade1c..fce6ef662 100644
--- a/.storybook/3.COLORS.stories.mdx
+++ b/.storybook/3.COLORS.stories.mdx
@@ -1,20 +1,12 @@
import { Meta } from '@storybook/addon-docs';
-import ActionaleMessage from '../ui/components/ui/actionable-message';
import designTokenDiagramImage from './images/design.token.graphic.svg';
-
+
# Color
Color is used to express style and communicate meaning.
-
-
-
-
## Design tokens
We are importing design tokens as CSS variables from [@metamask/design-tokens](https://github.com/MetaMask/design-tokens) repo to help consolidate colors and enable theming across all MetaMask products.
diff --git a/.storybook/4.SHADOW.stories.mdx b/.storybook/4.SHADOW.stories.mdx
new file mode 100644
index 000000000..84d6984ee
--- /dev/null
+++ b/.storybook/4.SHADOW.stories.mdx
@@ -0,0 +1,307 @@
+import { Meta, Canvas, Story } from '@storybook/addon-docs';
+
+
+
+# Shadow
+
+Shadows convey elevation of elements on a surface
+
+## Size
+
+There are 4 different sizes of shadow in MetaMask
+
+
+
+ XS
+
+
+ SM
+
+
+ MD
+
+
+ LG
+
+
+
+| Size | CSS |
+| ------ | ----------------------- |
+| **XS** | `var(--shadow-size-xs)` |
+| **SM** | `var(--shadow-size-sm)` |
+| **MD** | `var(--shadow-size-md)` |
+| **LG** | `var(--shadow-size-lg)` |
+
+## Color
+
+As well as the neutral colors for shadow 2 other colors exist that are used for the primary and error/danger button hover states
+
+
+
+ Default
+
+
+ Primary
+
+
+ Error/Danger
+
+
+
+| Color | CSS |
+| ----------- | ----------------------------- |
+| **neutral** | `var(--color-shadow-default)` |
+| **primary** | `var(--color-primary-shadow)` |
+| **danger** | `var(--color-error-shadow)` |
+
+## Example usage
+
+Using both size and color tokens, different shadows can be applied to components
+
+
+
+
+ Card
+
+
+ Dropdown
+
+
+ Toast
+
+
+ Modal
+
+
+
+
+ Button Primary Hover
+
+
+ Button Error/Danger Hover
+
+
+
+
+| Component | JS | CSS |
+| ------------------------ | ---------------------------------------------------------------- | --- |
+| **Card** | `box-shadow: var(--shadow-size-xs) var(--color-shadow-default);` |
+| **Dropdown** | `box-shadow: var(--shadow-size-sm) var(--color-shadow-default);` |
+| **Toast** | `box-shadow: var(--shadow-size-md) var(--color-shadow-default);` |
+| **Modal** | `box-shadow: var(--shadow-size-lg) var(--color-shadow-default);` |
+| **Button Primary Hover** | `box-shadow: var(--shadow-size-sm) var(--color-primary-shadow);` |
+| **Button Danger Hover** | `box-shadow: var(--shadow-size-sm) var(--color-error-shadow);` |
+
+## Takeaways
+
+- Try to avoid using static media queries in your code
+- Try to use the provided SCSS mixins
+
+### ❌ Don't do this
+
+Don't use static media queries in your code
+
+```css
+/**
+* Don't do this
+* Static box-shadows create inconsistency in elevation of elements
+**/
+.card {
+ box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2);
+}
+```
+
+### ✅ Do this
+
+Do use the provided shadow design token css variables
+
+```css
+.card {
+ box-shadow: var(--shadow-size-xs) var(--color-shadow-default);
+}
+```
+
+## References
+
+- [Shadow design tokens](https://metamask.github.io/design-tokens/?path=/docs/shadows-shadows--shadow)
+- [Figma light theme colors library(shadows page)](https://www.figma.com/file/kdFzEC7xzSNw7cXteqgzDW/%5BColor%5D-Light-Theme?node-id=753%3A719)(internal use only)
+- [Figma dark theme colors library(shadows page)](https://www.figma.com/file/rLKsoqpjyoKauYnFDcBIbO/%5BColor%5D-Dark-Theme?node-id=522%3A1022)(internal use only)
diff --git a/.storybook/5.BREAKPOINTS.stories.mdx b/.storybook/5.BREAKPOINTS.stories.mdx
new file mode 100644
index 000000000..1ddd2c420
--- /dev/null
+++ b/.storybook/5.BREAKPOINTS.stories.mdx
@@ -0,0 +1,125 @@
+import { Meta } from '@storybook/addon-docs';
+
+
+
+# Breakpoints
+
+Breakpoints are used for responsive layout
+
+## Screen Sizes
+
+There are 4 screen sizes that make up the breakpoints for the MetaMask extension
+
+- base: `0px`
+- sm: `576px`
+- md: `768px`
+- lg: `1280px`
+
+### SCSS
+
+There are Sass variables and mixins available for use for both min and max screens sizes
+
+### Variables
+
+```css
+$screen-sm-max /* 575px */
+$screen-md-max /* 767px */
+$screen-lg-max /* 1279px */
+
+$screen-sm-min /* 576px */
+$screen-md-min /* 768px */
+$screen-lg-min /* 1280px */
+```
+
+### Mixins
+
+```css
+/* Max screen size */
+@include screen-sm-max {
+ /* equivalent css @media screen and (max-width: 575px) */
+}
+@include screen-md-max {
+ /* equivalent css @media screen and (max-width: 767px) */
+}
+@include screen-lg-max {
+ /* equivalent css @media screen and (max-width: 1279px) */
+}
+
+/* Min screen size */
+@include screen-sm-min {
+ /* equivalent css @media screen and (min-width: 576px) */
+}
+@include screen-md-min {
+ /* equivalent css @media screen and (min-width: 768px) */
+}
+@include screen-lg-min {
+ /* equivalent css @media screen and (min-width: 1280px) */
+}
+```
+
+Migrating from the old sass variables to the new mixins looks like this
+
+```css
+/* Max width */
+/* Instead of the media query and sass variable */
+@media screen and (max-width: $break-small) {
+ right: 16px;
+}
+
+/* Use the sass mixin */
+@include screen-sm-max {
+ right: 16px;
+}
+
+/* Min width */
+/* Instead of the media query and sass variable */
+@media screen and (min-width: $break-large) {
+ left: 16px;
+}
+
+/* Use the sass mixin */
+@include screen-sm-min {
+ left: 16px;
+}
+```
+
+## Takeaways
+
+- Try to avoid using static media queries in your code.
+- Try to use the provided SCSS mixins
+
+### ❌ Don't do this
+
+Don't use static media queries in your code.
+
+```css
+/**
+* Don't do this
+* Static media queries create inconsistency and could break the UI if we want to update them in future
+**/
+.account-menu {
+ @media screen and (min-width: 769px) {
+ right: calc((100vw - 80vw) / 2);
+ }
+
+ @media screen and (min-width: 1281px) {
+ right: calc((100vw - 65vw) / 2);
+ }
+}
+```
+
+### ✅ Do this
+
+Do use the provided Sass mixins
+
+```css
+.account-menu {
+ @include screen-md-min {
+ right: calc((100vw - 80vw) / 2);
+ }
+
+ @include screen-lg-min {
+ right: calc((100vw - 65vw) / 2);
+ }
+}
+```
diff --git a/.storybook/metamask-storybook-theme.js b/.storybook/metamask-storybook-theme.js
index 49f8c814d..2bba8734c 100644
--- a/.storybook/metamask-storybook-theme.js
+++ b/.storybook/metamask-storybook-theme.js
@@ -7,6 +7,6 @@ export default create({
brandTitle: 'MetaMask Storybook',
// Typography
- fontBase: 'Euclid, Roboto, Helvetica, Arial, sans-serif',
+ fontBase: 'Euclid Circular B, Roboto, Helvetica, Arial, sans-serif',
fontCode: 'Inconsolata, monospace',
});
diff --git a/.storybook/preview.js b/.storybook/preview.js
index 517e7820a..d068ba34d 100644
--- a/.storybook/preview.js
+++ b/.storybook/preview.js
@@ -30,9 +30,10 @@ addParameters({
storySort: {
order: [
'Getting Started',
- 'Design Tokens',
+ 'Foundations',
+ ['Color', 'Shadow', 'Breakpoints'],
'Components',
- ['UI', 'App'],
+ ['UI', 'App', 'Component Library'],
'Pages',
],
},
diff --git a/.storybook/test-data.js b/.storybook/test-data.js
index 44038239d..44aca95a6 100644
--- a/.storybook/test-data.js
+++ b/.storybook/test-data.js
@@ -1,3 +1,4 @@
+import { draftTransactionInitialState } from '../ui/ducks/send';
const state = {
invalidCustomNetwork: {
state: 'CLOSED',
@@ -15,99 +16,99 @@ const state = {
},
networkList: [
{
- blockExplorerUrl: "https://etherscan.io",
- chainId: "0x1",
+ blockExplorerUrl: 'https://etherscan.io',
+ chainId: '0x1',
iconColor: 'var(--mainnet)',
isATestNetwork: false,
- labelKey: "mainnet",
- providerType: "mainnet",
- rpcUrl: "https://mainnet.infura.io/v3/",
- ticker: "ETH",
+ labelKey: 'mainnet',
+ providerType: 'mainnet',
+ rpcUrl: 'https://mainnet.infura.io/v3/',
+ ticker: 'ETH',
viewOnly: true,
},
{
- blockExplorerUrl: "https://ropsten.etherscan.io",
- chainId: "0x3",
+ blockExplorerUrl: 'https://ropsten.etherscan.io',
+ chainId: '0x3',
iconColor: 'var(--ropsten)',
isATestNetwork: true,
- labelKey: "ropsten",
- providerType: "ropsten",
- rpcUrl: "https://ropsten.infura.io/v3/",
- ticker: "ETH",
+ labelKey: 'ropsten',
+ providerType: 'ropsten',
+ rpcUrl: 'https://ropsten.infura.io/v3/',
+ ticker: 'ETH',
viewOnly: true,
},
{
- blockExplorerUrl: "https://rinkeby.etherscan.io",
- chainId: "0x4",
+ blockExplorerUrl: 'https://rinkeby.etherscan.io',
+ chainId: '0x4',
iconColor: 'var(--rinkeby)',
isATestNetwork: true,
- labelKey: "rinkeby",
- providerType: "rinkeby",
- rpcUrl: "https://rinkeby.infura.io/v3/",
- ticker: "ETH",
+ labelKey: 'rinkeby',
+ providerType: 'rinkeby',
+ rpcUrl: 'https://rinkeby.infura.io/v3/',
+ ticker: 'ETH',
viewOnly: true,
},
{
- blockExplorerUrl: "https://goerli.etherscan.io",
- chainId: "0x5",
+ blockExplorerUrl: 'https://goerli.etherscan.io',
+ chainId: '0x5',
iconColor: 'var(--goerli)',
isATestNetwork: true,
- labelKey: "goerli",
- providerType: "goerli",
- rpcUrl: "https://goerli.infura.io/v3/",
- ticker: "ETH",
+ labelKey: 'goerli',
+ providerType: 'goerli',
+ rpcUrl: 'https://goerli.infura.io/v3/',
+ ticker: 'ETH',
viewOnly: true,
},
{
- blockExplorerUrl: "https://kovan.etherscan.io",
- chainId: "0x2a",
+ blockExplorerUrl: 'https://kovan.etherscan.io',
+ chainId: '0x2a',
iconColor: 'var(--kovan)',
isATestNetwork: true,
- labelKey: "kovan",
- providerType: "kovan",
- rpcUrl: "https://kovan.infura.io/v3/",
- ticker: "ETH",
+ labelKey: 'kovan',
+ providerType: 'kovan',
+ rpcUrl: 'https://kovan.infura.io/v3/',
+ ticker: 'ETH',
viewOnly: true,
},
{
- blockExplorerUrl: "",
- chainId: "0x539",
+ blockExplorerUrl: '',
+ chainId: '0x539',
iconColor: 'var(--localhost)',
isATestNetwork: true,
- label: "Localhost 8545",
- providerType: "rpc",
- rpcUrl: "http://localhost:8545",
- ticker: "ETH",
+ label: 'Localhost 8545',
+ providerType: 'rpc',
+ rpcUrl: 'http://localhost:8545',
+ ticker: 'ETH',
},
{
- blockExplorerUrl: "https://bscscan.com",
- chainId: "0x38",
+ blockExplorerUrl: 'https://bscscan.com',
+ chainId: '0x38',
iconColor: 'var(--localhost)',
isATestNetwork: false,
- label: "Binance Smart Chain",
- providerType: "rpc",
- rpcUrl: "https://bsc-dataseed.binance.org/",
- ticker: "BNB",
+ label: 'Binance Smart Chain',
+ providerType: 'rpc',
+ rpcUrl: 'https://bsc-dataseed.binance.org/',
+ ticker: 'BNB',
},
{
- blockExplorerUrl: "https://cchain.explorer.avax.network/",
- chainId: "0xa86a",
+ blockExplorerUrl: 'https://cchain.explorer.avax.network/',
+ chainId: '0xa86a',
iconColor: 'var(--localhost)',
isATestNetwork: false,
- label: "Avalanche",
- providerType: "rpc",
- rpcUrl: "https://api.avax.network/ext/bc/C/rpc",
- ticker: "AVAX",
+ label: 'Avalanche',
+ providerType: 'rpc',
+ rpcUrl: 'https://api.avax.network/ext/bc/C/rpc',
+ ticker: 'AVAX',
},
{
- blockExplorerUrl: "https://polygonscan.com",
- chainId: "0x89",
+ blockExplorerUrl: 'https://polygonscan.com',
+ chainId: '0x89',
iconColor: 'var(--localhost)',
isATestNetwork: false,
- label: "Polygon",
- providerType: "rpc",
- rpcUrl: "https://polygon-rpc.com",
- ticker: "MATIC",
+ label: 'Polygon',
+ providerType: 'rpc',
+ rpcUrl: 'https://polygon-rpc.com',
+ ticker: 'MATIC',
},
],
metamask: {
@@ -132,7 +133,7 @@ const state = {
'0x',
],
occurrences: 12,
- unlisted: false
+ unlisted: false,
},
'0xc00e94cb662c3520282e6f5717214004a7f26888': {
address: '0xc00e94cb662c3520282e6f5717214004a7f26888',
@@ -153,23 +154,18 @@ const state = {
'0x',
],
occurrences: 12,
- unlisted: false
+ unlisted: false,
},
'0xfffffffff15abf397da76f1dcc1a1604f45126db': {
address: '0xfffffffff15abf397da76f1dcc1a1604f45126db',
symbol: 'FSW',
decimals: 18,
name: 'Falconswap',
- iconUrl: 'https://assets.coingecko.com/coins/images/12256/thumb/falconswap.png?1598534184',
- aggregators: [
- 'CoinGecko',
- '1inch',
- 'Paraswap',
- 'Zapper',
- 'Zerion',
- ],
+ iconUrl:
+ 'https://assets.coingecko.com/coins/images/12256/thumb/falconswap.png?1598534184',
+ aggregators: ['CoinGecko', '1inch', 'Paraswap', 'Zapper', 'Zerion'],
occurrences: 12,
- unlisted: false
+ unlisted: false,
},
'0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f': {
address: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f',
@@ -192,70 +188,70 @@ const state = {
'0x',
],
occurrences: 12,
- unlisted: false
+ unlisted: false,
},
'0x6b175474e89094c44da98b954eedeac495271d0f': {
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
symbol: 'META',
decimals: 18,
image: 'metamark.svg',
- unlisted: false
+ unlisted: false,
},
'0xB8c77482e45F1F44dE1745F52C74426C631bDD52': {
address: '0xB8c77482e45F1F44dE1745F52C74426C631bDD52',
symbol: '0X',
decimals: 18,
image: '0x.svg',
- unlisted: false
+ unlisted: false,
},
'0x1f9840a85d5af5bf1d1762f925bdaddc4201f984': {
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
symbol: 'AST',
decimals: 18,
image: 'ast.png',
- unlisted: false
+ unlisted: false,
},
'0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2': {
address: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
symbol: 'BAT',
decimals: 18,
image: 'BAT_icon.svg',
- unlisted: false
+ unlisted: false,
},
'0xe83cccfabd4ed148903bf36d4283ee7c8b3494d1': {
address: '0xe83cccfabd4ed148903bf36d4283ee7c8b3494d1',
symbol: 'CVL',
decimals: 18,
image: 'CVL_token.svg',
- unlisted: false
+ unlisted: false,
},
'0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e': {
address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e',
symbol: 'GLA',
decimals: 18,
image: 'gladius.svg',
- unlisted: false
+ unlisted: false,
},
'0x467Bccd9d29f223BcE8043b84E8C8B282827790F': {
address: '0x467Bccd9d29f223BcE8043b84E8C8B282827790F',
symbol: 'GNO',
decimals: 18,
image: 'gnosis.svg',
- unlisted: false
+ unlisted: false,
},
'0xff20817765cb7f73d4bde2e66e067e58d11095c2': {
address: '0xff20817765cb7f73d4bde2e66e067e58d11095c2',
symbol: 'OMG',
decimals: 18,
image: 'omg.jpg',
- unlisted: false
+ unlisted: false,
},
'0x8e870d67f660d95d5be530380d0ec0bd388289e1': {
address: '0x8e870d67f660d95d5be530380d0ec0bd388289e1',
symbol: 'WED',
decimals: 18,
image: 'wed.png',
- unlisted: false
+ unlisted: false,
},
},
networkDetails: {
@@ -281,83 +277,84 @@ const state = {
swapsFeatureIsLive: false,
swapsQuoteRefreshTime: 60000,
},
- "snapStates": {},
- "snaps": {
- "local:http://localhost:8080/": {
- "enabled": true,
- "id": "local:http://localhost:8080/",
- "initialPermissions": {
- "snap_confirm": {}
+ snapStates: {},
+ snaps: {
+ 'local:http://localhost:8080/': {
+ enabled: true,
+ id: 'local:http://localhost:8080/',
+ initialPermissions: {
+ snap_confirm: {},
},
- "manifest": {
- "description": "An example MetaMask Snap.",
- "initialPermissions": {
- "snap_confirm": {}
+ manifest: {
+ description: 'An example MetaMask Snap.',
+ initialPermissions: {
+ snap_confirm: {},
},
- "manifestVersion": "0.1",
- "proposedName": "MetaMask Example Snap",
- "repository": {
- "type": "git",
- "url": "https://github.com/MetaMask/snaps-skunkworks.git"
+ manifestVersion: '0.1',
+ proposedName: 'MetaMask Example Snap',
+ repository: {
+ type: 'git',
+ url: 'https://github.com/MetaMask/snaps-skunkworks.git',
},
- "source": {
- "location": {
- "npm": {
- "filePath": "dist/bundle.js",
- "iconPath": "images/icon.svg",
- "packageName": "@metamask/example-snap",
- "registry": "https://registry.npmjs.org/"
- }
+ source: {
+ location: {
+ npm: {
+ filePath: 'dist/bundle.js',
+ iconPath: 'images/icon.svg',
+ packageName: '@metamask/example-snap',
+ registry: 'https://registry.npmjs.org/',
+ },
},
- "shasum": "3lEt0yUu080DwV78neROaAAIQWXukSkMnP4OBhOhBnE="
+ shasum: '3lEt0yUu080DwV78neROaAAIQWXukSkMnP4OBhOhBnE=',
},
- "version": "0.6.0"
+ version: '0.6.0',
},
- "permissionName": "wallet_snap_local:http://localhost:8080/",
- "sourceCode": "(...)",
- "status": "stopped",
- "svgIcon": "",
- "version": "0.6.0"
- },
- "Filecoin Snap": {
- "enabled": true,
- "id": "npm:http://localhost:8080/",
- "initialPermissions": {
- "snap_confirm": {},
- "eth_accounts": {},
- "snap_manageState": {},
+ permissionName: 'wallet_snap_local:http://localhost:8080/',
+ sourceCode: '(...)',
+ status: 'stopped',
+ svgIcon: '',
+ version: '0.6.0',
+ },
+ 'Filecoin Snap': {
+ enabled: true,
+ id: 'npm:http://localhost:8080/',
+ initialPermissions: {
+ snap_confirm: {},
+ eth_accounts: {},
+ snap_manageState: {},
},
- "manifest": {
- "description": "This swap provides developers everywhere access to an entirely new data storage paradigm, even letting your programs store data autonomously. Learn more.",
- "initialPermissions": {
- "snap_confirm": {},
- "eth_accounts": {},
- "snap_manageState": {},
+ manifest: {
+ description:
+ 'This swap provides developers everywhere access to an entirely new data storage paradigm, even letting your programs store data autonomously. Learn more.',
+ initialPermissions: {
+ snap_confirm: {},
+ eth_accounts: {},
+ snap_manageState: {},
},
- "manifestVersion": "0.1",
- "proposedName": "Filecoin Snap",
- "repository": {
- "type": "git",
- "url": "https://github.com/MetaMask/snaps-skunkworks.git"
+ manifestVersion: '0.1',
+ proposedName: 'Filecoin Snap',
+ repository: {
+ type: 'git',
+ url: 'https://github.com/MetaMask/snaps-skunkworks.git',
},
- "source": {
- "location": {
- "npm": {
- "filePath": "dist/bundle.js",
- "iconPath": "images/icon.svg",
- "packageName": "@metamask/example-snap",
- "registry": "https://registry.npmjs.org/"
- }
+ source: {
+ location: {
+ npm: {
+ filePath: 'dist/bundle.js',
+ iconPath: 'images/icon.svg',
+ packageName: '@metamask/example-snap',
+ registry: 'https://registry.npmjs.org/',
+ },
},
- "shasum": "3lEt0yUu080DwV78neROaAAIQWXukSkMnP4OBhOhBnE="
+ shasum: '3lEt0yUu080DwV78neROaAAIQWXukSkMnP4OBhOhBnE=',
},
- "version": "0.6.0"
+ version: '0.6.0',
},
- "permissionName": "wallet_snap_npm:http://localhost:8080/",
- "sourceCode": "(...)",
- "status": "stopped",
- "svgIcon": "",
- "version": "0.6.0"
+ permissionName: 'wallet_snap_npm:http://localhost:8080/',
+ sourceCode: '(...)',
+ status: 'stopped',
+ svgIcon: '',
+ version: '0.6.0',
},
},
accountArray: [
@@ -486,32 +483,46 @@ const state = {
],
detectedTokens: [
{
- address: "0x514910771AF9Ca656af840dff83E8264EcF986CA",
+ address: '0x514910771AF9Ca656af840dff83E8264EcF986CA',
decimals: 18,
- symbol: "LINK",
- image: "https://crypto.com/price/coin-data/icon/LINK/color_icon.png",
- aggregators:[
- "coinGecko","oneInch","paraswap","zapper","zerion"
- ]
+ symbol: 'LINK',
+ image: 'https://crypto.com/price/coin-data/icon/LINK/color_icon.png',
+ aggregators: ['coinGecko', 'oneInch', 'paraswap', 'zapper', 'zerion'],
},
{
- address: "0xc00e94Cb662C3520282E6f5717214004A7f26888",
+ address: '0xc00e94Cb662C3520282E6f5717214004A7f26888',
decimals: 18,
- symbol: "COMP",
- image: "https://crypto.com/price/coin-data/icon/COMP/color_icon.png",
- aggregators:[
- "bancor","cmc","cryptocom","coinGecko","oneInch","paraswap","pmm","zapper","zerion","zeroEx"
- ]
+ symbol: 'COMP',
+ image: 'https://crypto.com/price/coin-data/icon/COMP/color_icon.png',
+ aggregators: [
+ 'bancor',
+ 'cmc',
+ 'cryptocom',
+ 'coinGecko',
+ 'oneInch',
+ 'paraswap',
+ 'pmm',
+ 'zapper',
+ 'zerion',
+ 'zeroEx',
+ ],
},
{
- address: "0xfffffffFf15AbF397dA76f1dcc1A1604F45126DB",
+ address: '0xfffffffFf15AbF397dA76f1dcc1A1604F45126DB',
decimals: 18,
- symbol: "FSW",
- image: "https://assets.coingecko.com/coins/images/12256/thumb/falconswap.png?1598534184",
- aggregators:[
- "aave", "cmc","coinGecko","oneInch","paraswap","zapper","zerion"
- ]
- }
+ symbol: 'FSW',
+ image:
+ 'https://assets.coingecko.com/coins/images/12256/thumb/falconswap.png?1598534184',
+ aggregators: [
+ 'aave',
+ 'cmc',
+ 'coinGecko',
+ 'oneInch',
+ 'paraswap',
+ 'zapper',
+ 'zerion',
+ ],
+ },
],
pendingTokens: {},
customNonceValue: '',
@@ -559,18 +570,18 @@ const state = {
swapsWelcomeMessageHasBeenShown: true,
defaultHomeActiveTabName: 'Assets',
provider: {
- type: 'ropsten',
+ type: 'rinkeby',
ticker: 'ETH',
nickname: '',
rpcUrl: '',
- chainId: '0x3',
+ chainId: '0x4',
},
previousProviderStore: {
- type: 'ropsten',
+ type: 'rinkeby',
ticker: 'ETH',
nickname: '',
rpcUrl: '',
- chainId: '0x3',
+ chainId: '0x4',
},
network: '3',
accounts: {
@@ -1304,7 +1315,7 @@ const state = {
subjects: {
'https://app.uniswap.org': {
permissions: {
- 'eth_accounts': {
+ eth_accounts: {
invoker: 'https://app.uniswap.org',
parentCapability: 'eth_accounts',
id: 'a7342e4b-beae-4525-a36c-c0635fd03359',
@@ -1318,14 +1329,14 @@ const state = {
},
},
},
- "local:http://localhost:8080/": {
+ 'local:http://localhost:8080/': {
permissions: {
- 'snap_confirm': {
- invoker: "local:http://localhost:8080/",
+ snap_confirm: {
+ invoker: 'local:http://localhost:8080/',
parentCapability: 'snap_confirm',
id: 'a7342F4b-beae-4525-a36c-c0635fd03359',
date: 1620710693178,
- caveats: []
+ caveats: [],
},
},
},
@@ -1423,30 +1434,30 @@ const state = {
pendingApprovals: {},
pendingApprovalCount: 0,
subjectMetadata: {
- "http://localhost:8080": {
+ 'http://localhost:8080': {
extensionId: null,
iconUrl: null,
- name: "Hello, Snaps!",
- origin: "http://localhost:8080",
- subjectType: "website"
+ name: 'Hello, Snaps!',
+ origin: 'http://localhost:8080',
+ subjectType: 'website',
},
- "https://metamask.github.io": {
+ 'https://metamask.github.io': {
extensionId: null,
iconUrl: null,
- name: "Snaps Iframe Execution Environment",
- origin: "https://metamask.github.io",
- subjectType: "website"
+ name: 'Snaps Iframe Execution Environment',
+ origin: 'https://metamask.github.io',
+ subjectType: 'website',
},
- "local:http://localhost:8080/": {
+ 'local:http://localhost:8080/': {
extensionId: null,
iconUrl: null,
- name: "MetaMask Example Snap",
- origin: "local:http://localhost:8080/",
- subjectType: "snap",
- svgIcon: "",
- version: "0.6.0"
- }
- }
+ name: 'MetaMask Example Snap',
+ origin: 'local:http://localhost:8080/',
+ subjectType: 'snap',
+ svgIcon: '',
+ version: '0.6.0',
+ },
+ },
},
appState: {
shouldClose: false,
@@ -1455,7 +1466,14 @@ const state = {
open: false,
modalState: {
name: null,
- props: {},
+ props: {
+ token: {
+ address: '0xaD6D458402F60fD3Bd25163575031ACDce07538D',
+ symbol: 'DAI',
+ decimals: 18,
+ },
+ history: {},
+ },
},
previousModalState: {
name: null,
@@ -1507,6 +1525,12 @@ const state = {
amount: {
error: 'amount',
},
+ currentTransactionUUID: 'test-uuid',
+ draftTransactions: {
+ 'test-uuid': {
+ ...draftTransactionInitialState,
+ },
+ },
},
confirmTransaction: {
txData: {
diff --git a/.yarnrc b/.yarnrc
index 2088d635c..5455c6c5d 100644
--- a/.yarnrc
+++ b/.yarnrc
@@ -1 +1 @@
-ignore-scripts true
\ No newline at end of file
+ignore-scripts true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9fd2689d6..78c96256a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -46,7 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [10.18.0]
### Added
-- Add setApprovalForAll confirmation view so granted permissions are displayed in a digested manner, instead of a simple contract interaction([#15010](https://github.com/MetaMask/metamask-extension/pull/15010))
+- Add setApprovalForAll confirmation view so granted permissions are displayed in a digested manner, instead of a simple contract interaction([#15010](https://github.com/MetaMask/metamask-extension/pull/15010))
- Add warning when performing a Send directly to a token contract([#13588](https://github.com/MetaMask/metamask-extension/pull/13588))
### Changed
@@ -131,7 +131,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix switching between ETH and USD in the amount field on the send screen ([#13827](https://github.com/MetaMask/metamask-extension/pull/13827))
- Fix addition of 'add recipient' events to the send flow change logs so that 'contact' and 'recent' recipient are correctly distinguished ([#14771](https://github.com/MetaMask/metamask-extension/pull/14771))
- Fix lock button sizing for text exceeding button boundaries ([#14335](https://github.com/MetaMask/metamask-extension/pull/14335))
-- Fix all "MetaMask" instances wrongly written as "Metamask"
+- Fix all "MetaMask" instances wrongly written as "Metamask"
- ([#14851](https://github.com/MetaMask/metamask-extension/pull/14851))
- ([#14848](https://github.com/MetaMask/metamask-extension/pull/14848))
- Fix design break on the Settings navbar for certain locales ([#14012](https://github.com/MetaMask/metamask-extension/pull/14012))
diff --git a/README.md b/README.md
index 835cfe3bb..7d7c5f19b 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D
## Building locally
-- Install [Node.js](https://nodejs.org) version 14
+- Install [Node.js](https://nodejs.org) version 16
- If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you.
- Install [Yarn](https://yarnpkg.com/en/docs/install)
- Install dependencies: `yarn setup` (not the usual install command)
@@ -72,9 +72,9 @@ Our e2e test suite can be run on either Firefox or Chrome. In either case, start
```console
--browser Set the browser used; either 'chrome' or 'firefox'.
---leave-running Leaves the browser running after a test fails, along with anything else
+--leave-running Leaves the browser running after a test fails, along with anything else
that the test used (ganache, the test dapp, etc.).
-
+
--retries Set how many times the test should be retried upon failure. Default is 0.
```
@@ -91,20 +91,25 @@ Whenever you change dependencies (adding, removing, or updating, either in `pack
* The `allow-scripts` configuration in `package.json`
* Run `yarn allow-scripts auto` to update the `allow-scripts` configuration automatically. This config determines whether the package's install/postinstall scripts are allowed to run. Review each new package to determine whether the install script needs to run or not, testing if necessary.
* Unfortunately, `yarn allow-scripts auto` will behave inconsistently on different platforms. macOS and Windows users may see extraneous changes relating to optional dependencies.
-* The LavaMoat policy files. The _tl;dr_ is to run `yarn lavamoat:auto` to update these files, but there can be devils in the details. Continue reading for more information.
+* The LavaMoat policy files. The _tl;dr_ is to run `yarn lavamoat:auto` to update these files, but there can be devils in the details:
* There are two sets of LavaMoat policy files:
- * The production LavaMoat policy files (`lavamoat/browserify/*/policy.json`), which are re-generated using `yarn lavamoat:background:auto`.
+ * The production LavaMoat policy files (`lavamoat/browserify/*/policy.json`), which are re-generated using `yarn lavamoat:background:auto`. Add `--help` for usage.
* These should be regenerated whenever the production dependencies for the background change.
* The build system LavaMoat policy file (`lavamoat/build-system/policy.json`), which is re-generated using `yarn lavamoat:build:auto`.
* This should be regenerated whenever the dependencies used by the build system itself change.
* Whenever you regenerate a policy file, review the changes to determine whether the access granted to each package seems appropriate.
* Unfortunately, `yarn lavamoat:auto` will behave inconsistently on different platforms.
macOS and Windows users may see extraneous changes relating to optional dependencies.
+ * If you keep getting policy failures even after regenerating the policy files, try regenerating the policies after a clean install by doing:
+ * `rm -rf node_modules/ && yarn setup && yarn lavamoat:auto`
* Keep in mind that any kind of dynamic import or dynamic use of globals may elude LavaMoat's static analysis.
Refer to the LavaMoat documentation or ask for help if you run into any issues.
## Architecture
+- [Visual of the controller heirarchy and dependencies as of summer 2022.](https://gist.github.com/rekmarks/8dba6306695dcd44967cce4b6a94ae33)
+- [Visual of the entire codebase.](https://mango-dune-07a8b7110.1.azurestaticapps.net/?repo=metamask%2Fmetamask-extension)
+
[![Architecture Diagram](./docs/architecture.png)][1]
## Other Docs
@@ -114,7 +119,12 @@ Whenever you change dependencies (adding, removing, or updating, either in `pack
- [How to add a new translation to MetaMask](./docs/translating-guide.md)
- [Publishing Guide](./docs/publishing.md)
- [How to use the TREZOR emulator](./docs/trezor-emulator.md)
-- [Developing on MetaMask](./development/README.md)
+- [Developing on MetaMask](./development/README.md)
- [How to generate a visualization of this repository's development](./development/gource-viz.sh)
+## Dapp Developer Resources
+- [Extend MetaMask's features w/ MetaMask Snaps.](https://docs.metamask.io/guide/snaps.html)
+- [Prompt your users to add and switch to a new network.](https://medium.com/metamask/connect-users-to-layer-2-networks-with-the-metamask-custom-networks-api-d0873fac51e5)
+- [Change the logo that appears when your dapp connects to MetaMask.](https://docs.metamask.io/guide/defining-your-icon.html)
+
[1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BbackgroundConnection%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A
diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json
index 7951a4640..9b6290245 100644
--- a/app/_locales/de/messages.json
+++ b/app/_locales/de/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "Notiz hinzufügen"
},
+ "addMoreNetworks": {
+ "message": "weitere Netzwerke manuell hinzufügen"
+ },
"addNetwork": {
"message": "Netzwerk hinzufügen"
},
@@ -282,9 +285,6 @@
"approvedAsset": {
"message": "Genehmigtes Asset"
},
- "areYouDeveloper": {
- "message": "Sind Sie ein Entwickler?"
- },
"areYouSure": {
"message": "Sind Sie sicher?"
},
@@ -438,7 +438,7 @@
"message": "$1 mit Wyre kaufen"
},
"buyWithWyreDescription": {
- "message": "Wyre ermöglicht Ihnen die Verwendung einer Kreditkarte zum direkten Einzahlen von $1 auf Ihr MetaMask-Konto."
+ "message": "Einfaches Onboarding für Käufe bis zu 1000 $. Schnelle interaktive Überprüfung von Käufen mit hohem Limit. Unterstützt Debit-/Kreditkarte, Apple Pay, Banküberweisungen. Verfügbar in über 100 Ländern. Einzahlung von Token auf Ihr MetaMask-Konto"
},
"bytes": {
"message": "Bytes"
@@ -466,6 +466,13 @@
"message": "Für eine Transaktion im Wert von $1 muss die Gasgebühr um mindestens 10 % erhöht werden, damit sie vom Netz erkannt wird.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "Tausch für 1 $ abbrechen",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "Tausch kostenlos abbrechen"
+ },
"cancellationGasFee": {
"message": "Stornierungs-Gasgebühr"
},
@@ -732,7 +739,7 @@
},
"customGasSettingToolTipMessage": {
"message": "$1 verwenden, um den Gaspreis anzupassen. Das kann verwirrend sein, wenn Sie damit nicht vertraut sind. Interaktion auf eigene Gefahr.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Höhere Gebühren können Bearbeitungszeiten verkürzen, wofür es allerdings keine Garantie gibt."
@@ -818,7 +825,7 @@
},
"depositCrypto": {
"message": "$1 einzahlen",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Beschreibung"
@@ -1196,9 +1203,6 @@
"failureMessage": {
"message": "Etwas ist schief gelaufen und wir konnten die Aktion nicht abschließen"
},
- "fakeTokenWarning": {
- "message": "Jeder kann ein Token erstellen, einschließlich der Erstellung gefälschter Versionen bestehender Token. Erfahren Sie mehr über $1"
- },
"fast": {
"message": "Schnell"
},
@@ -1463,7 +1467,7 @@
},
"highGasSettingToolTipMessage": {
"message": "Hohe Wahrscheinlichkeit, auch in volatilen Märkten. Verwenden Sie $1, um Schwankungen im Netzwerkverkehr, die z. B. durch den Ausfall beliebter NFTs entstehen, abzudecken.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "hoch"
@@ -1766,7 +1770,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Verwenden Sie $1, um auf einen günstigeren Preis zu warten. Zeitschätzungen sind viel ungenauer, da die Preise nicht vorhersehbar sind.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "niedrig"
@@ -1810,7 +1814,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Verwenden Sie $1 für schnelle Verarbeitung zum aktuellen Marktpreis.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": " Memo"
@@ -1943,6 +1947,9 @@
"network": {
"message": "Netzwerk:"
},
+ "networkAddedSuccessfully": {
+ "message": "Netzwerk erfolgreich hinzugefügt!"
+ },
"networkDetails": {
"message": "Netzwerkdetails"
},
@@ -2059,6 +2066,9 @@
"message": "Nonce ist höher als vorgeschlagen nonce von $1",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "Token-ID eingeben"
},
@@ -2130,7 +2140,7 @@
},
"notifications10ActionText": {
"message": "Einstellungen ansehen",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "Die verbesserte Token-Erkennung ist derzeit in den Netzwerken Ethereum Mainnet, Polygon, BSC und Avalanche verfügbar. Es gibt bald mehr!"
@@ -2225,7 +2235,7 @@
},
"notifications8ActionText": {
"message": "Zu den erweiterten Einstellungen gehen",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "Ab MetaMask v10.4.0 benötigen Sie kein Ledger Live mehr, um Ihr Ledger Gerät mit MetaMask zu verbinden.",
@@ -2261,10 +2271,6 @@
"notificationsMarkAllAsRead": {
"message": "Alle als gelesen markieren"
},
- "numberOfNewTokensDetected": {
- "message": "$1 neue Tokens in diesem Konto gefunden\n",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "von"
},
@@ -2344,9 +2350,6 @@
"message": "Öffnen Sie MetaMask im Vollbildmodus, um Ihren Ledger über WebHID zu verbinden.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Prüfen Sie den Quellcode"
- },
"optional": {
"message": "Optional"
},
@@ -2473,7 +2476,7 @@
},
"preferredLedgerConnectionType": {
"message": "Bevorzugter Ledger-Verbindungstyp",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Swap wird vorbereitet ..."
@@ -2877,6 +2880,12 @@
"showAdvancedGasInlineDescription": {
"message": "Wählen Sie dies aus, um den Gaspreis und die Limitkontrollen direkt auf den Senden- und Bestätigen-Bildschirmen anzuzeigen."
},
+ "showCustomNetworkList": {
+ "message": "Benutzerdefinierte Netzwerkliste anzeigen"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "Diese Option zeigt eine Liste von Netzwerken mit vorausgefüllten Details an, sobald ein neues Netzwerk hinzugefügt wird."
+ },
"showFiatConversionInTestnets": {
"message": "Umwandlung auf Testnets anzeigen"
},
@@ -2967,10 +2976,6 @@
"snapInstallWarningCheck": {
"message": "Um zu bestätigen, dass Sie alles verstanden haben, kreuzen Sie alles an."
},
- "snapInstallWarningKeyAccess": {
- "message": "Sie gewähren dem Snap „$1“ wichtige Zugriffsrechte. Dies kann nicht rückgängig gemacht werden und gibt „$1“ Kontrolle über Ihre Konten und Vermögenswerte. Stellen Sie sicher, dass Sie „$1“ vertrauen, bevor Sie fortfahren.",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "Für diesen Snap werden die folgenden Berechtigungen beantragt:"
},
@@ -2986,6 +2991,9 @@
"snapsToggle": {
"message": "Ein Snap wird nur ausgeführt, wenn er aktiviert ist"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "Einige Netzwerke können Sicherheits- und/oder Datenschutzrisiken bergen. Informieren Sie sich über die Risiken, bevor Sie ein Netzwerk hinzufügen und nutzen."
+ },
"somethingWentWrong": {
"message": "Hoppla! Da hat etwas nicht geklappt."
},
@@ -3075,7 +3083,7 @@
"message": "Status"
},
"statusConnected": {
- "message": "Verbunden"
+ "message": "Verbinden"
},
"statusNotConnected": {
"message": "Nicht verbunden"
@@ -3548,6 +3556,10 @@
"switchNetworks": {
"message": "Netzwerk wechseln"
},
+ "switchToNetwork": {
+ "message": "Zu 1 $ wechseln",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "Zu diesem Konto wechseln"
},
@@ -3635,7 +3647,7 @@
},
"toggleTestNetworks": {
"message": "$1 Test-Netzwerke",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Token"
@@ -3664,12 +3676,6 @@
"tokenDetectionAlertMessage": {
"message": "Die Token-Erkennung ist derzeit für $1 verfügbar. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "Neu! Verbesserte Token Erkennung ist im Ethereum Mainnet als experimentelle Funktion verfügbar. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "Die Token-API von ConsenSys sammelt eine Liste von Token aus verschiedenen Token-Listen von Drittanbietern. Wenn Sie diese Funktion deaktivieren, werden keine neuen Token mehr erkannt, die zu Ihrer Wallet hinzugefügt werden, aber die Option zur Suche nach Token für den Import bleibt erhalten."
- },
"tokenId": {
"message": "Token-ID"
},
@@ -3851,6 +3857,9 @@
"unknownCameraErrorTitle": {
"message": "Hoppla! Etwas ist schiefgegangen ..."
},
+ "unknownCollection": {
+ "message": "Unbenannte Sammlung"
+ },
"unknownNetwork": {
"message": "Unbekanntes privates Netzwerk"
},
@@ -3901,12 +3910,6 @@
"usePhishingDetectionDescription": {
"message": "Zeigt eine Warnung für Phishing-Domänen, die Ethereum Benutzer ansprechen"
},
- "useTokenDetection": {
- "message": "Token-Erkennung verwenden"
- },
- "useTokenDetectionDescription": {
- "message": "Wir verwenden Drittanbieter-API, um neue an Ihre Wallet gesendete Token zu erkennen und anzuzeigen. Deaktivieren Sie diese, wenn Sie nicht möchten, dass MetaMask Daten von diesen Diensten abruft."
- },
"useTokenDetectionPrivacyDesc": {
"message": "Die automatische Anzeige der an Ihr Konto gesendeten Token erfordert die Kommunikation mit Servern von Drittanbietern, um die Bilder der Token abzurufen. Diese Server haben Zugriff auf Ihre IP-Adresse."
},
@@ -3962,7 +3965,7 @@
"description": "$1 is the action type. e.g (Account, Transaction, Swap)"
},
"visitWebSite": {
- "message": "Gehe zu unserer Webseite"
+ "message": "Besuchen Sie unsere Webseite"
},
"walletConnectionGuide": {
"message": "unsere Hardware-Wallet-Verbindungsanleitung"
@@ -3987,6 +3990,9 @@
"walletCreationSuccessTitle": {
"message": "Wallet-Erstellung erfolgreich"
},
+ "wantToAddThisNetwork": {
+ "message": "Möchten Sie dieses Netzwerk hinzufügen?"
+ },
"warning": {
"message": "Warnung"
},
@@ -4049,6 +4055,10 @@
"yesLetsTry": {
"message": "Ja, versuchen wir es"
},
+ "youHaveAddedAll": {
+ "message": "Sie haben alle beliebten Netzwerke hinzugefügt. Sie können weitere Netzwerke entdecken 1 $ oder Sie können 2 $",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "Sie müssen Zugriff auf die Kamera erlauben, um diese Funktion nutzen zu können."
},
diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json
index 649655d5e..1afdfcc9e 100644
--- a/app/_locales/el/messages.json
+++ b/app/_locales/el/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "Προσθήκη σημειώματος"
},
+ "addMoreNetworks": {
+ "message": "προσθέστε περισσότερα δίκτυα χειροκίνητα"
+ },
"addNetwork": {
"message": "Προσθήκη Δικτύου"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "Ειδοποιήσεις"
},
+ "allOfYour": {
+ "message": "Όλα σας τα $1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "Επιτρέψτε σε αυτή την εξωτερική επέκταση να:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "Έγκριση"
},
+ "approveAllTokensTitle": {
+ "message": "Δίνετε άδεια για να αποκτήσετε πρόσβαση σε όλα σας τα $1;",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "Έγκριση και Εγκατάσταση"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "Εγκεκριμένο περιουσιακό στοιχείο"
},
- "areYouDeveloper": {
- "message": "Είστε προγραμματιστής;"
- },
"areYouSure": {
"message": "Είστε βέβαιος/η;"
},
@@ -438,7 +446,7 @@
"message": "Αγοράστε $1 με το Wyre"
},
"buyWithWyreDescription": {
- "message": "Το Wyre σας επιτρέπει να χρησιμοποιήσετε μια πιστωτική κάρτα για να καταθέσετε $1 απευθείας στον λογαριασμό σας MetaTask."
+ "message": "Εύκολη ενσωμάτωση για αγορές μέχρι και $ 1000. Γρήγορη διαδραστική επαλήθευση αγοράς υψηλού ορίου. Υποστηρίζει χρεωστικές/πιστωτικές κάρτες, Apple Pay, Τραπεζικές Μεταφορές. Διαθέσιμο σε 100+ χώρες. Καταθέσεις token στον λογαριασμό σας MetaMask"
},
"bytes": {
"message": "Bytes"
@@ -466,6 +474,13 @@
"message": "Για να $1 τη συναλλαγή, τα τέλη συναλλαγής πρέπει να αυξηθούν κατά τουλάχιστον 10% ώστε να αναγνωριστούν από το δίκτυο.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "Ακυρώστε τη συναλλαγή για ~$1",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "Ακυρώστε τη συναλλαγή δωρεάν"
+ },
"cancellationGasFee": {
"message": "Ακύρωση Χρέωσης Αερίου"
},
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "Χρησιμοποιήστε 1 $ για να προσαρμόσετε την τιμή του τέλους συναλλαγής. Αυτό μπορεί να προκαλέσει σύγχυση, αν δεν είστε εξοικειωμένοι. Επεξεργαστείτε το με δικό σας ρίσκο.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Η αύξηση των τελών μπορεί να μειώσει τους χρόνους επεξεργασίας, αλλά αυτό δεν είναι εγγυημένο."
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "Κατάθεση $1",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Περιγραφή"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "Κάτι πήγε λάθος και δεν μπορέσαμε να ολοκληρώσουμε την ενέργεια"
},
- "fakeTokenWarning": {
- "message": "Οποιοσδήποτε μπορεί να δημιουργήσει ένα token, συμπεριλαμβανομένης της δημιουργίας ψεύτικων εκδόσεων των υφιστάμενων tokens. Μάθετε περισσότερα γι'αυτό $1"
- },
"fast": {
"message": "Γρήγορα"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "Λειτουργία: Έγκριση"
},
+ "functionSetApprovalForAll": {
+ "message": "Λειτουργία: SetApprovalForAll"
+ },
"functionType": {
"message": "Τύπος Λειτουργίας"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "Χρησιμοποιήστε $1 για να καλύψετε απότομες αυξήσεις της κίνησης του δικτύου λόγω των δημοφιλών ξεκινημάτων NFT.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "υψηλό"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "Μη έγκυρη Μυστική Φράση Ανάκτησής"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "Μη έγκυρη εισαγωγή! Η Μυστική σας Φράση Ανάκτησης κάνει διάκριση πεζών-κεφαλαίων."
+ },
"ipfsGateway": {
"message": "Πύλη IPFS"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Χρησιμοποιήστε 1 $ για να περιμένετε για μια φθηνότερη τιμή. Οι εκτιμήσεις του χρόνου είναι πολύ λιγότερο ακριβείς καθώς οι τιμές είναι κάπως απρόβλεπτες.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "χαμηλό"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Χρησιμοποιήστε $1 για γρήγορη επεξεργασία στην τρέχουσα τιμή της αγοράς.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "σημείωμα"
@@ -1892,6 +1910,19 @@
"message": "επαληθεύστε τα στοιχεία δικτύου",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "Σας προτείνουμε να $1 πριν συνεχίσετε.",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "Σύμφωνα με τις καταχωρήσεις μας, το όνομα δικτύου ίσως δεν ταιριάζει με το αναγνωριστικό αλυσίδας."
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "Το σύμβολο νομίσματος που υποβλήθηκε δεν ταιριάζει με αυτό που αναμενόταν για αυτό το αναγνωριστικό αλυσίδας."
+ },
+ "mismatchedRpcUrl": {
+ "message": "Σύμφωνα με τις καταχωρήσεις μας, η τιμή RPC URL που υποβλήθηκε δεν ταιριάζει με κάποιον γνωστό πάροχο για αυτό το αναγνωριστικό αλυσίδας."
+ },
"missingNFT": {
"message": "Δεν βλέπετε το NFT σας;"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "Δίκτυο:"
},
+ "networkAddedSuccessfully": {
+ "message": "Το δίκτυο προστέθηκε επιτυχώς!"
+ },
"networkDetails": {
"message": "Λεπτομέρειες Δικτύου"
},
@@ -2059,6 +2093,9 @@
"message": "Το Nonce είναι υψηλότερο από το προτεινόμενο nonce του $1",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "Εισάγετε το συλλεκτικό αναγνωριστικό"
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "Μετάβαση στις Ρυθμίσεις",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "Ο βελτιωμένος εντοπισμός token είναι προς το παρόν διαθέσιμος στα δίκτυα Ethereum Mainnet, Polygon, BSC και Avalanche. Περισσότερα προσεχώς!"
@@ -2154,11 +2191,21 @@
"message": "Ενεργοποίηση dark mode"
},
"notifications12Description": {
- "message": "Το Dark Mode (σκουρόχρωμη λειτουργία) θα ενεργοποιηθεί για νέους χρήστες ανάλογα με τις προτιμήσεις του συστήματός τους. Εάν είστε ήδη χρήστης, ενεργοποιήστε τo dark mode με μη αυτόματο τρόπο στην ενότητα Ρυθμίσεις -> Πειραματικές."
+ "message": "Έφτασε επιτέλους το Dark Mode (σκουρόχρωμη λειτουργία)! Για να το ενεργοποιήσετε, μεταβείτε στις Ρυθμίσεις -> Πειραματικό και επιλέξτε μία από τις επιλογές εμφάνισης: Light, Dark, Σύστημα."
},
"notifications12Title": {
"message": "Θέλετε dark mode; Τώρα έχετε dark mode! 🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "Προβολή λίστας προσαρμοσμένων δικτύων"
+ },
+ "notifications13Description": {
+ "message": "Τώρα μπορείτε να προσθέσετε εύκολα τα ακόλουθα δημοφιλή προσαρμοσμένα δίκτυα: Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm and Polygon! Για να ενεργοποιήσετε αυτήν τη λειτουργία, πηγάινετε στις Ρυθμίσεις -> Πειραματικό και ενεργοποιήστε το «Προβολή λίστας προσαρμοσμένων δικτύων»!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "Προσθήκη Δημοφιλών Δικτύων"
+ },
"notifications1Description": {
"message": "Οι χρήστες του MetaMask Mobile μπορούν τώρα να ανταλλάξουν tokens μέσα στο κινητό τους πορτοφόλι. Σαρώστε τον κωδικό QR για να πάρετε την εφαρμογή για κινητά και να αρχίσετε να ανταλλάζετε.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "Μεταβείτε στις Ρυθμίσεις για Προχωρημένους",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "Από τη MetaMask v10.4.0 και μετά, δεν χρειάζεστε πλέον το Ledger Live για να συνδέσετε τη συσκευή Ledger με το MetaMask.",
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "Επισήμανση όλων ως αναγνωσμένων"
},
- "numberOfNewTokensDetected": {
- "message": "$1 νέα token βρέθηκαν σε αυτόν τον λογαριασμό",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "από"
},
@@ -2344,9 +2387,6 @@
"message": "Ανοίξτε το MetaMask σε πλήρη οθόνη για να συνδέσετε το ledger σας μέσω WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Ελέγξτε τον πηγαίο κώδικα"
- },
"optional": {
"message": "Προαιρετικό"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "Προτιμώμενος Τύπος Σύνδεσης Ledger",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Προετοιμασία ανταλλαγής..."
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "Αποκάλυψη φράσης ανάκτησης"
},
+ "revokeAllTokensTitle": {
+ "message": "Ανάκληση άδειας πρόσβασης σε όλα σας τα $1;",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "Με την ανάκληση της άδειας, το ακόλουθο $1 δεν θα έχει πλέον πρόσβαση στο $2 σας",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Δοκιμαστικό Δίκτυο Rinkeby"
},
@@ -2852,12 +2900,23 @@
"message": "Αποστολή $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "Προειδοποίηση: πρόκειται να στείλετε ένα συμβόλαιο token το οποίο ίσως καταλήξει σε απώλεια χρημάτων. $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "Ορίστε ρυθμίσεις απορρήτου για προχωρημένους"
},
"setAdvancedPrivacySettingsDetails": {
"message": "Το MetaMask χρησιμοποιεί αυτές τις αξιόπιστες υπηρεσίες τρίτων για να ενισχύσει τη χρηστικότητα και την ασφάλεια των προϊόντων."
},
+ "setApprovalForAll": {
+ "message": "Ρύθμιση Έγκρισης Όλων"
+ },
+ "setApprovalForAllTitle": {
+ "message": "Έγκριση $1 χωρίς όριο δαπανών",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "Ρυθμίσεις"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "Επιλέξτε αυτό για να εμφανίσετε τις τιμές αερίου και να περιορίσετε τα στοιχεία ελέγχου απευθείας στις οθόνες αποστολής και επιβεβαίωσης."
},
+ "showCustomNetworkList": {
+ "message": "Προβολή Λίστας Προσαρμοσμένων Δικτύων"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "Επιλέξτε αυτό για να εμφανιστεί μια λίστα δικτύων με προσυμπληρωμένα στοιχεία κατά την προσθήκη ενός νέου δικτύου."
+ },
"showFiatConversionInTestnets": {
"message": "Εμφάνιση Μετατροπής σε Δοκιμαστικά Δίκτυα"
},
@@ -2920,7 +2985,7 @@
"message": "Υπογραφή"
},
"signNotice": {
- "message": "Η υπογραφή αυτού του μηνύματος μπορεί να έχει\nεπικίνδυνες παρενέργειες. Υπογράφετε μηνύματα μόνο από\nτοποθεσίες που εμπιστεύεστε πλήρως με ολόκληρο τον λογαριασμό σας.\n Αυτή η επικίνδυνη μέθοδος θα καταργηθεί σε μια μελλοντική έκδοση."
+ "message": "Η υπογραφή αυτού του μηνύματος μπορεί να έχει\nεπικίνδυνες παρενέργειες. Υπογράφετε μηνύματα μόνο από\nτοποθεσίες που εμπιστεύεστε πλήρως με ολόκληρο τον λογαριασμό σας.\n Αυτή η επικίνδυνη μέθοδος θα καταργηθεί σε μια μελλοντική έκδοση."
},
"signatureRequest": {
"message": "Αίτημα Υπογραφής"
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "Για να επιβεβαιώσετε ότι καταλαβαίνετε, επιλέξτε όλα τα πλαίσια ελέγχου."
},
- "snapInstallWarningKeyAccess": {
- "message": "Παρέχετε βασική πρόσβαση στο snap \"$1\". Αυτή η ενέργεια είναι μη αναστρέψιμη και παραχωρεί στο \"$1\" τον έλεγχο των λογαριασμών και των περιουσιακών σας στοιχείων. Βεβαιωθείτε ότι εμπιστεύεστε το \"$1\" πριν συνεχίσετε.",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "Αυτό το snap αιτείται τις παρακάτω άδειες:"
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "Ένα snap θα εκτελεστεί μόνο εάν είναι ενεργοποιημένο"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "Ορισμένα δίκτυα ενδέχεται να ενέχουν κινδύνους για την ασφάλεια ή/και το απόρρητο. Ενημερωθείτε για τους κινδύνους πριν προσθέσετε και χρησιμοποιήσετε ένα δίκτυο."
+ },
"somethingWentWrong": {
"message": "Ουπς! Κάτι πήγε στραβά."
},
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "Αλλαγή Δικτύων"
},
+ "switchToNetwork": {
+ "message": "Εναλλαγή σε $1",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "Εναλλαγή σε αυτόν τον λογαριασμό"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "$1 δοκιμαστικά δίκτυα",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Διακριτικό"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "Ο εντοπισμός token είναι επί του παρόντος διαθέσιμος στο $1. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "Νέο! Η βελτιωμένη ανίχνευση token είναι διαθέσιμη στο Ethereum Mainnet ως πειραματικό χαρακτηριστικό. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "Το token API της ConsenSys δημιουργεί μια λίστα με token από διάφορες λίστες token τρίτων. Εάν το απενεργοποιήσετε, θα σταματήσει ο εντοπισμός νέων token που προστίθενται στο πορτοφόλι σας, αλλά θα διατηρηθεί η επιλογή αναζήτησης token για εισαγωγή."
- },
"tokenId": {
"message": "Αναγνωριστικό token"
},
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "Ουπς! Κάτι πήγε στραβά...."
},
+ "unknownCollection": {
+ "message": "Ανώνυμη συλλογή"
+ },
"unknownNetwork": {
"message": "Άγνωστο Ιδιωτικό Δίκτυο"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "Εμφάνιση μιας προειδοποίησης για τομείς Απάτης Ηλεκτρονικού Ψαρέματος που στοχεύουν χρήστες του Ethereum"
},
- "useTokenDetection": {
- "message": "Χρήση Ανίχνευσης Token"
- },
- "useTokenDetectionDescription": {
- "message": "Χρησιμοποιούμε API τρίτων για να εντοπίσουμε και να εμφανίσουμε νέα tokens που αποστέλλονται στο πορτοφόλι σας. Απενεργοποιήστε αν δεν θέλετε το MetaMask να τραβήξει δεδομένα από αυτές τις υπηρεσίες."
- },
"useTokenDetectionPrivacyDesc": {
"message": "Η αυτόματη εμφάνιση των token που αποστέλλονται στον λογαριασμό σας συνεπάγεται επικοινωνία με διακομιστές τρίτων για τη λήψη εικόνων των token. Αυτοί οι διακομιστές θα έχουν πρόσβαση στη διεύθυνση IP σας."
},
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "Επιτυχής δημιουργία πορτοφολιού"
},
+ "wantToAddThisNetwork": {
+ "message": "Θέλετε να προσθέσετε αυτό το δίκτυο;"
+ },
"warning": {
"message": "Προειδοποίηση"
},
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "Ναι, ας δοκιμάσουμε"
},
+ "youHaveAddedAll": {
+ "message": "Προσθέσατε όλα τα δημοφιλή δίκτυα. Μπορείτε να ανακαλύψετε περισσότερα δίκτυα $1 Ή μπορείτε να \n $2",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "Πρέπει να επιτρέψετε πρόσβαση στην κάμερα για να χρησιμοποιήσετε αυτήν τη λειτουργία."
},
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index a3ef3b742..89123a67c 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -18,13 +18,13 @@
"message": "After you’ve signed with your wallet, click on 'Get Signature' to receive the signature"
},
"QRHardwareSignRequestGetSignature": {
- "message": "Get Signature"
+ "message": "Get signature"
},
"QRHardwareSignRequestSubtitle": {
"message": "Scan the QR code with your wallet"
},
"QRHardwareSignRequestTitle": {
- "message": "Request Signature"
+ "message": "Request signature"
},
"QRHardwareUnknownQRCodeTitle": {
"message": "Error"
@@ -33,16 +33,70 @@
"message": "Invalid QR code. Please scan the sync QR code of the hardware wallet."
},
"QRHardwareWalletImporterTitle": {
- "message": "Scan QR Code"
+ "message": "Scan QR code"
},
"QRHardwareWalletSteps1Description": {
"message": "Connect an airgapped hardware wallet that communicates through QR-codes. Officially supported airgapped hardware wallets include:"
},
"QRHardwareWalletSteps1Title": {
- "message": "QR-based HW Wallet"
+ "message": "QR-based HW wallet"
},
"QRHardwareWalletSteps2Description": {
- "message": "Ngrave (Coming Soon)"
+ "message": "Ngrave (coming soon)"
+ },
+ "SIWEAddressInvalid": {
+ "message": "The address in the sign-in request does not match the address of the account you are using to sign in."
+ },
+ "SIWEDomainInvalid": {
+ "message": "The website you are attempting to sign in to ($1) does not match the domain in the sign-in request. Proceed with caution.",
+ "description": "$1 represents the website domain"
+ },
+ "SIWEDomainWarningBody": {
+ "message": "The website ($1) is asking you to sign in to the wrong domain. This may be a phishing attack.",
+ "description": "$1 represents the website domain"
+ },
+ "SIWELabelChainID": {
+ "message": "Chain ID:"
+ },
+ "SIWELabelExpirationTime": {
+ "message": "Expires At:"
+ },
+ "SIWELabelIssuedAt": {
+ "message": "Issued At:"
+ },
+ "SIWELabelMessage": {
+ "message": "Message:"
+ },
+ "SIWELabelNonce": {
+ "message": "Nonce:"
+ },
+ "SIWELabelNotBefore": {
+ "message": "Not Before:"
+ },
+ "SIWELabelRequestID": {
+ "message": "Request ID:"
+ },
+ "SIWELabelResources": {
+ "message": "Resources: $1",
+ "description": "$1 represents the number of resources"
+ },
+ "SIWELabelURI": {
+ "message": "URI:"
+ },
+ "SIWELabelVersion": {
+ "message": "Version:"
+ },
+ "SIWESiteRequestSubtitle": {
+ "message": "This site is requesting to sign in with"
+ },
+ "SIWESiteRequestTitle": {
+ "message": "Sign-in request"
+ },
+ "SIWEWarningSubtitle": {
+ "message": "To confirm you understand, check:"
+ },
+ "SIWEWarningTitle": {
+ "message": "Are you sure?"
},
"about": {
"message": "About"
@@ -79,17 +133,17 @@
"message": "Account details"
},
"accountIdenticon": {
- "message": "Account Identicon"
+ "message": "Account identicon"
},
"accountName": {
- "message": "Account Name"
+ "message": "Account name"
},
"accountNameDuplicate": {
"message": "This account name already exists",
"description": "This is an error message shown when the user enters a new account name that matches an existing account name"
},
"accountOptions": {
- "message": "Account Options"
+ "message": "Account options"
},
"accountSelectionRequired": {
"message": "You need to select an account!"
@@ -128,7 +182,7 @@
"message": "Add contact"
},
"addCustomToken": {
- "message": "Add Custom Token"
+ "message": "Add custom token"
},
"addCustomTokenByContractAddress": {
"message": "Can’t find a token? You can manually add any token by pasting its address. Token contract addresses can be found on $1.",
@@ -164,17 +218,17 @@
"message": "add more networks manually"
},
"addNetwork": {
- "message": "Add Network"
+ "message": "Add network"
},
"addNetworkTooltipWarning": {
"message": "This network connection relies on third parties. This connection may be less reliable or enable third-parties to track activity. $1",
"description": "$1 is Learn more link"
},
"addSuggestedTokens": {
- "message": "Add Suggested Tokens"
+ "message": "Add suggested tokens"
},
"addToken": {
- "message": "Add Token"
+ "message": "Add token"
},
"address": {
"message": "Address"
@@ -201,13 +255,13 @@
"message": "Gas price"
},
"advancedOptions": {
- "message": "Advanced Options"
+ "message": "Advanced options"
},
"advancedPriorityFeeToolTip": {
"message": "Priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction."
},
"affirmAgree": {
- "message": "I Agree"
+ "message": "I agree"
},
"airgapVault": {
"message": "AirGap Vault"
@@ -278,7 +332,10 @@
"description": "$1 is the symbol of the token for which the user is granting approval"
},
"approveAndInstall": {
- "message": "Approve & Install"
+ "message": "Approve & install"
+ },
+ "approveAndUpdate": {
+ "message": "Approve & update"
},
"approveButtonText": {
"message": "Approve"
@@ -296,8 +353,9 @@
"approvedAsset": {
"message": "Approved asset"
},
- "areYouDeveloper": {
- "message": "Are you a developer?"
+ "approvedOn": {
+ "message": "Approved on $1",
+ "description": "$1 is the approval date for a permission"
},
"areYouSure": {
"message": "Are you sure?"
@@ -311,8 +369,11 @@
"assets": {
"message": "Assets"
},
+ "attemptSendingAssets": {
+ "message": "If you attempt to send assets directly from one network to another, this may result in permanent asset loss. Make sure to use a bridge."
+ },
"attemptToCancel": {
- "message": "Attempt to Cancel?"
+ "message": "Attempt to cancel?"
},
"attemptToCancelDescription": {
"message": "Submitting this attempt does not guarantee your original transaction will be cancelled. If the cancellation attempt is successful, you will be charged the transaction fee above."
@@ -327,7 +388,7 @@
"message": "You have authorized the following permissions"
},
"autoLockTimeLimit": {
- "message": "Auto-Lock Timer (minutes)"
+ "message": "Auto-lock timer (minutes)"
},
"autoLockTimeLimitDescription": {
"message": "Set the idle time in minutes before MetaMask will become locked."
@@ -339,7 +400,10 @@
"message": "Back"
},
"backToAll": {
- "message": "Back to All"
+ "message": "Back to all"
+ },
+ "backup": {
+ "message": "Backup"
},
"backupApprovalInfo": {
"message": "This secret code is required to recover your wallet in case you lose your device, forget your password, have to re-install MetaMask, or want to access your wallet on another device."
@@ -350,6 +414,12 @@
"backupNow": {
"message": "Backup now"
},
+ "backupUserData": {
+ "message": "Backup your data"
+ },
+ "backupUserDataDescription": {
+ "message": "You can backup user settings containing preferences and account addresses into a JSON file."
+ },
"balance": {
"message": "Balance"
},
@@ -362,6 +432,9 @@
"basic": {
"message": "Basic"
},
+ "beCareful": {
+ "message": "Be careful"
+ },
"betaMetamaskDescription": {
"message": "Trusted by millions, MetaMask is a secure wallet making the world of web3 accessible to all."
},
@@ -394,7 +467,7 @@
"description": "This is used with viewOnEtherscan e.g View Swap on Etherscan"
},
"blockExplorerUrl": {
- "message": "Block Explorer URL"
+ "message": "Block explorer URL"
},
"blockExplorerUrlDefinition": {
"message": "The URL used as the block explorer for this network."
@@ -407,7 +480,7 @@
"message": "Blockies"
},
"browserNotSupported": {
- "message": "Your Browser is not supported..."
+ "message": "Your browser is not supported..."
},
"buildContactList": {
"message": "Build your contact list"
@@ -458,13 +531,13 @@
"message": "Bytes"
},
"canToggleInSettings": {
- "message": "You can re-enable this notification in Settings -> Alerts."
+ "message": "You can re-enable this notification in Settings > Alerts."
},
"cancel": {
"message": "Cancel"
},
"cancelEdit": {
- "message": "Cancel Edit"
+ "message": "Cancel edit"
},
"cancelPopoverTitle": {
"message": "Cancel transaction"
@@ -488,7 +561,7 @@
"message": "Cancel swap for free"
},
"cancellationGasFee": {
- "message": "Cancellation Gas Fee"
+ "message": "Cancellation gas fee"
},
"cancelled": {
"message": "Cancelled"
@@ -513,6 +586,9 @@
"message": "Click here to connect your Ledger via WebHID",
"description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid"
},
+ "clickToManuallyAdd": {
+ "message": "Click here to manually add the tokens."
+ },
"clickToRevealSeed": {
"message": "Click here to reveal secret words"
},
@@ -529,6 +605,9 @@
"confirm": {
"message": "Confirm"
},
+ "confirmPageDialogSetApprovalForAll": {
+ "message": "By clicking approve, you are granting access to all of the NFTs you currently own on this contract and any NFTs on this contract you may acquire in the future until you revoke this approval. The party to whom you're granting approval will be able to transfer your NFTs from your wallet without further notice. Proceed with caution."
+ },
"confirmPassword": {
"message": "Confirm password"
},
@@ -560,7 +639,7 @@
"message": "Connect account or create new"
},
"connectHardwareWallet": {
- "message": "Connect Hardware Wallet"
+ "message": "Connect hardware wallet"
},
"connectManually": {
"message": "Manually connect to current site"
@@ -586,7 +665,7 @@
"description": "$1 is the number of accounts to which the web3 site/application is asking to connect; this will substitute $1 in connectToMultiple"
},
"connectWithMetaMask": {
- "message": "Connect With MetaMask"
+ "message": "Connect with MetaMask"
},
"connectedAccountsDescriptionPlural": {
"message": "You have $1 accounts connected to this site.",
@@ -620,19 +699,19 @@
"message": "Connecting to $1"
},
"connectingToGoerli": {
- "message": "Connecting to Goerli Test Network"
+ "message": "Connecting to Goerli test network"
},
"connectingToKovan": {
- "message": "Connecting to Kovan Test Network"
+ "message": "Connecting to Kovan test network"
},
"connectingToMainnet": {
"message": "Connecting to Ethereum Mainnet"
},
"connectingToRinkeby": {
- "message": "Connecting to Rinkeby Test Network"
+ "message": "Connecting to Rinkeby test network"
},
"connectingToRopsten": {
- "message": "Connecting to Ropsten Test Network"
+ "message": "Connecting to Ropsten test network"
},
"contactUs": {
"message": "Contact us"
@@ -665,10 +744,22 @@
"message": "You are sending tokens to the token's contract address. This may result in the loss of these tokens."
},
"contractDeployment": {
- "message": "Contract Deployment"
+ "message": "Contract deployment"
+ },
+ "contractDescription": {
+ "message": "To protect yourself against scammers, take a moment to verify contract details."
},
"contractInteraction": {
- "message": "Contract Interaction"
+ "message": "Contract interaction"
+ },
+ "contractRequestingSpendingCap": {
+ "message": "Contract requesting spending cap"
+ },
+ "contractTitle": {
+ "message": "Contract details"
+ },
+ "contractToken": {
+ "message": "Token contract"
},
"convertTokenToNFTDescription": {
"message": "We've detected that this asset is an NFT. MetaMask now has full native support for NFTs. Would you like to remove it from your token list and add it as an NFT?"
@@ -692,28 +783,28 @@
"message": "Copy to clipboard"
},
"copyTransactionId": {
- "message": "Copy Transaction ID"
+ "message": "Copy transaction ID"
},
"create": {
"message": "Create"
},
"createAWallet": {
- "message": "Create a Wallet"
+ "message": "Create a wallet"
},
"createAccount": {
- "message": "Create Account"
+ "message": "Create account"
},
"createNewWallet": {
"message": "Create a new wallet"
},
"createPassword": {
- "message": "Create Password"
+ "message": "Create password"
},
"currencyConversion": {
- "message": "Currency Conversion"
+ "message": "Currency conversion"
},
"currencySymbol": {
- "message": "Currency Symbol"
+ "message": "Currency symbol"
},
"currencySymbolDefinition": {
"message": "The ticker symbol displayed for this network’s currency."
@@ -725,7 +816,7 @@
"message": "Current extension page"
},
"currentLanguage": {
- "message": "Current Language"
+ "message": "Current language"
},
"currentTitle": {
"message": "Current:"
@@ -749,26 +840,32 @@
"message": "Search for a previously added network"
},
"customGas": {
- "message": "Customize Gas"
+ "message": "Customize gas"
},
"customGasSettingToolTipMessage": {
"message": "Use $1 to customize the gas price. This can be confusing if you aren’t familiar. Interact at your own risk.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Increasing fee may decrease processing times, but it is not guaranteed."
},
"customSpendLimit": {
- "message": "Custom Spend Limit"
+ "message": "Custom spend limit"
+ },
+ "customSpendingCap": {
+ "message": "Custom spending cap"
},
"customToken": {
- "message": "Custom Token"
+ "message": "Custom token"
},
"customTokenWarningInNonTokenDetectionNetwork": {
"message": "Token detection is not available on this network yet. Please import token manually and make sure you trust it. Learn about $1"
},
"customTokenWarningInTokenDetectionNetwork": {
- "message": "Before manually importing a token, make sure you trust it. Learn about $1."
+ "message": "Before manually importing a token, make sure you trust it. Learn about $1"
+ },
+ "customTokenWarningInTokenDetectionNetworkWithTDOFF": {
+ "message": "Make sure you trust a token before you import it. Learn how to avoid $1. You can also enable token detection $2."
},
"customerSupport": {
"message": "customer support"
@@ -800,7 +897,7 @@
"message": "Hex"
},
"decimal": {
- "message": "Token Decimal"
+ "message": "Token decimal"
},
"decimalsMustZerotoTen": {
"message": "Decimals must be at least 0, and not over 36."
@@ -829,17 +926,17 @@
"message": "Delete"
},
"deleteAccount": {
- "message": "Delete Account"
+ "message": "Delete account"
},
"deleteNetwork": {
- "message": "Delete Network?"
+ "message": "Delete network?"
},
"deleteNetworkDescription": {
"message": "Are you sure you want to delete this network?"
},
"depositCrypto": {
"message": "Deposit $1",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Description"
@@ -848,7 +945,7 @@
"message": "Details"
},
"directDepositCrypto": {
- "message": "Directly Deposit $1"
+ "message": "Directly deposit $1"
},
"directDepositCryptoExplainer": {
"message": "If you already have some $1, the quickest way to get $1 in your new wallet by direct deposit."
@@ -900,7 +997,7 @@
"message": "Download this Secret Recovery Phrase and keep it stored safely on an external encrypted hard drive or storage medium."
},
"downloadStateLogs": {
- "message": "Download State Logs"
+ "message": "Download state logs"
},
"dropped": {
"message": "Dropped"
@@ -918,7 +1015,7 @@
"message": "Edit cancellation gas fee"
},
"editContact": {
- "message": "Edit Contact"
+ "message": "Edit contact"
},
"editGasEducationButtonText": {
"message": "How should I choose?"
@@ -1028,28 +1125,28 @@
"message": "This lowers your maximum fee but if network traffic increases your transaction may be delayed or fail."
},
"editNonceField": {
- "message": "Edit Nonce"
+ "message": "Edit nonce"
},
"editNonceMessage": {
"message": "This is an advanced feature, use cautiously."
},
"editPermission": {
- "message": "Edit Permission"
+ "message": "Edit permission"
},
"editSpeedUpEditGasFeeModalTitle": {
"message": "Edit speed up gas fee"
},
"enableAutoDetect": {
- "message": " Enable Autodetect"
+ "message": " Enable autodetect"
},
"enableEIP1559V2": {
- "message": "Enable Enhanced Gas Fee UI"
+ "message": "Enable enhanced gas fee UI"
},
"enableEIP1559V2AlertMessage": {
"message": "We've updated how gas fee estimation and customization works."
},
"enableEIP1559V2ButtonText": {
- "message": "Turn on Enhanced Gas Fee UI in Settings"
+ "message": "Turn on enhanced gas fee UI in Settings"
},
"enableEIP1559V2Description": {
"message": "We've updated how gas estimation and customization works. Turn on if you'd like to use the new gas experience. $1",
@@ -1068,7 +1165,7 @@
"message": "Use OpenSea's API to fetch NFT data. NFT auto-detection relies on OpenSea's API, and will not be available when this is turned off."
},
"enableSmartTransactions": {
- "message": "Enable Smart Transactions"
+ "message": "Enable smart transactions"
},
"enableToken": {
"message": "enable $1",
@@ -1085,7 +1182,7 @@
"message": "You passed the test - keep your Secret Recovery Phrase safe, it's your responsibility!"
},
"endOfFlowMessage10": {
- "message": "All Done"
+ "message": "All done"
},
"endOfFlowMessage2": {
"message": "Tips on storing it safely"
@@ -1100,7 +1197,7 @@
"message": "Be careful of phishing! MetaMask will never spontaneously ask for your Secret Recovery Phrase."
},
"endOfFlowMessage6": {
- "message": "If you need to back up your Secret Recovery Phrase again, you can find it in Settings -> Security."
+ "message": "If you need to back up your Secret Recovery Phrase again, you can find it in Settings > Security."
},
"endOfFlowMessage7": {
"message": "If you ever have questions or see something fishy, contact our support $1.",
@@ -1117,7 +1214,7 @@
"description": "$1 is the return value of eth_chainId from an RPC endpoint"
},
"ensIllegalCharacter": {
- "message": "Illegal Character for ENS."
+ "message": "Illegal character for ENS."
},
"ensNotFoundOnCurrentNetwork": {
"message": "ENS name not found on the current network. Try switching to Ethereum Mainnet."
@@ -1129,10 +1226,10 @@
"message": "Error in ENS name registration"
},
"ensUnknownError": {
- "message": "ENS Lookup failed."
+ "message": "ENS lookup failed."
},
"enterMaxSpendLimit": {
- "message": "Enter Max Spend Limit"
+ "message": "Enter max spend limit"
},
"enterPassword": {
"message": "Enter password"
@@ -1145,7 +1242,7 @@
"description": "Displayed error code for debugging purposes. $1 is the error code"
},
"errorDetails": {
- "message": "Error Details",
+ "message": "Error details",
"description": "Title for collapsible section that displays error details for debugging purposes"
},
"errorMessage": {
@@ -1173,13 +1270,13 @@
"description": "Title for error stack, which is displayed for debugging purposes"
},
"estimatedProcessingTimes": {
- "message": "Estimated Processing Times"
+ "message": "Estimated processing times"
},
"ethGasPriceFetchWarning": {
"message": "Backup gas price is provided as the main gas estimation service is unavailable right now."
},
"ethereumPublicAddress": {
- "message": "Ethereum Public Address"
+ "message": "Ethereum public address"
},
"etherscan": {
"message": "Etherscan"
@@ -1200,10 +1297,10 @@
"message": "Experimental"
},
"exportPrivateKey": {
- "message": "Export Private Key"
+ "message": "Export private key"
},
"externalExtension": {
- "message": "External Extension"
+ "message": "External extension"
},
"failed": {
"message": "Failed"
@@ -1217,9 +1314,6 @@
"failureMessage": {
"message": "Something went wrong, and we were unable to complete the action"
},
- "fakeTokenWarning": {
- "message": "Anyone can create a token, including creating fake versions of existing tokens. Learn more about $1"
- },
"fast": {
"message": "Fast"
},
@@ -1302,7 +1396,7 @@
"message": "Function: SetApprovalForAll"
},
"functionType": {
- "message": "Function Type"
+ "message": "Function type"
},
"gas": {
"message": "Gas"
@@ -1318,10 +1412,10 @@
"message": "Our low, medium and high estimates are not available."
},
"gasFee": {
- "message": "Gas Fee"
+ "message": "Gas fee"
},
"gasLimit": {
- "message": "Gas Limit"
+ "message": "Gas limit"
},
"gasLimitInfoTooltipContent": {
"message": "Gas limit is the maximum amount of units of gas you are willing to spend."
@@ -1343,16 +1437,16 @@
"message": "Gas option"
},
"gasPrice": {
- "message": "Gas Price (GWEI)"
+ "message": "Gas price (GWEI)"
},
"gasPriceExcessive": {
"message": "Your gas fee is set unnecessarily high. Consider lowering the amount."
},
"gasPriceExcessiveInput": {
- "message": "Gas Price Is Excessive"
+ "message": "Gas price is excessive"
},
"gasPriceExtremelyLow": {
- "message": "Gas Price Extremely Low"
+ "message": "Gas price extremely low"
},
"gasPriceFetchFailed": {
"message": "Gas price estimation failed due to network error."
@@ -1393,14 +1487,14 @@
"description": "$1 represents an amount of time"
},
"gasUsed": {
- "message": "Gas Used"
+ "message": "Gas used"
},
"gdprMessage": {
"message": "This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our $1.",
"description": "$1 refers to the gdprMessagePrivacyPolicy message, the translation of which is meant to be used exclusively in the context of gdprMessage"
},
"gdprMessagePrivacyPolicy": {
- "message": "Privacy Policy here",
+ "message": "Privacy policy here",
"description": "this translation is intended to be exclusively used as the replacement for the $1 in the gdprMessage translation"
},
"general": {
@@ -1414,13 +1508,13 @@
"description": "Displays network name for Ether faucet"
},
"getStarted": {
- "message": "Get Started"
+ "message": "Get started"
},
"goBack": {
- "message": "Go Back"
+ "message": "Go back"
},
"goerli": {
- "message": "Goerli Test Network"
+ "message": "Goerli test network"
},
"gotIt": {
"message": "Got it!"
@@ -1458,7 +1552,7 @@
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
},
"hexData": {
- "message": "Hex Data"
+ "message": "Hex data"
},
"hide": {
"message": "Hide"
@@ -1473,21 +1567,21 @@
"message": "Hide token"
},
"hideTokenPrompt": {
- "message": "Hide Token?"
+ "message": "Hide token?"
},
"hideTokenSymbol": {
"message": "Hide $1",
"description": "$1 is the symbol for a token (e.g. 'DAI')"
},
"hideZeroBalanceTokens": {
- "message": "Hide Tokens Without Balance"
+ "message": "Hide tokens without balance"
},
"high": {
"message": "Aggressive"
},
"highGasSettingToolTipMessage": {
"message": "High probability, even in volatile markets. Use $1 to cover surges in network traffic due to things like popular NFT drops.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "high"
@@ -1506,7 +1600,7 @@
"description": "Button to import an account from a selected file"
},
"importAccount": {
- "message": "Import Account"
+ "message": "Import account"
},
"importAccountError": {
"message": "Error importing account."
@@ -1518,7 +1612,7 @@
"message": "Import a wallet with Secret Recovery Phrase"
},
"importMyWallet": {
- "message": "Import My Wallet"
+ "message": "Import my wallet"
},
"importNFT": {
"message": "Import NFT"
@@ -1535,6 +1629,12 @@
"importNFTs": {
"message": "Import NFTs"
},
+ "importSelectedTokens": {
+ "message": "Import selected tokens?"
+ },
+ "importSelectedTokensDescription": {
+ "message": "Only the tokens you've selected will appear in your wallet. You can always import hidden tokens later by searching for them."
+ },
"importTokenQuestion": {
"message": "Import token?"
},
@@ -1545,7 +1645,7 @@
"message": "import tokens"
},
"importTokensCamelCase": {
- "message": "Import Tokens"
+ "message": "Import tokens"
},
"importWallet": {
"message": "Import wallet"
@@ -1561,6 +1661,9 @@
"message": "Imported",
"description": "status showing that an account has been fully loaded into the keyring"
},
+ "inYourSettings": {
+ "message": "in your Settings"
+ },
"infuraBlockedNotification": {
"message": "MetaMask is unable to connect to the blockchain host. Review possible reasons $1.",
"description": "$1 is a clickable link with with text defined by the 'here' key"
@@ -1601,7 +1704,7 @@
"message": "This asset is an NFT and needs to be re-added on the Import NFTs page found under the NFTs tab"
},
"invalidBlockExplorerURL": {
- "message": "Invalid Block Explorer URL"
+ "message": "Invalid block explorer URL"
},
"invalidChainIdTooBig": {
"message": "Invalid chain ID. The chain ID is too big."
@@ -1618,7 +1721,7 @@
"description": "$1 is a link to https://chainid.network"
},
"invalidCustomNetworkAlertTitle": {
- "message": "Invalid Custom Network"
+ "message": "Invalid custom network"
},
"invalidHexNumber": {
"message": "Invalid hexadecimal number."
@@ -1641,6 +1744,9 @@
"invalidSeedPhrase": {
"message": "Invalid Secret Recovery Phrase"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "Invalid input! Secret Recovery Phrase is case sensitive."
+ },
"ipfsGateway": {
"message": "IPFS Gateway"
},
@@ -1673,10 +1779,10 @@
"message": "This action will edit tokens that are already listed in your wallet, which can be used to phish you. Only approve if you are certain that you mean to change what these tokens represent. Learn more about $1"
},
"kovan": {
- "message": "Kovan Test Network"
+ "message": "Kovan test network"
},
"lastConnected": {
- "message": "Last Connected"
+ "message": "Last connected"
},
"learmMoreAboutGas": {
"message": "Want to $1 about gas?"
@@ -1761,7 +1867,7 @@
"message": "Links"
},
"loadMore": {
- "message": "Load More"
+ "message": "Load more"
},
"loading": {
"message": "Loading..."
@@ -1770,7 +1876,7 @@
"message": "Loading NFTs..."
},
"loadingTokens": {
- "message": "Loading Tokens..."
+ "message": "Loading tokens..."
},
"localhost": {
"message": "Localhost 8545"
@@ -1790,7 +1896,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Use $1 to wait for a cheaper price. Time estimates are much less accurate as prices are somewhat unpredictable.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "low"
@@ -1834,7 +1940,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Use $1 for fast processing at current market price.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "memo"
@@ -1948,11 +2054,15 @@
"message": "Must select at least 1 token."
},
"myAccounts": {
- "message": "My Accounts"
+ "message": "My accounts"
},
"name": {
"message": "Name"
},
+ "nativeToken": {
+ "message": "The native token on this network is $1. It is the token used for gas fees.",
+ "description": "$1 represents the name of the native token on the current network"
+ },
"needCryptoInWallet": {
"message": "To interact with decentralized applications using MetaMask, you’ll need $1 in your wallet.",
"description": "$1 represents the cypto symbol to be purchased"
@@ -1962,13 +2072,13 @@
"description": "$1 represents `needHelpLinkText`, the text which goes in the help link"
},
"needHelpFeedback": {
- "message": "Share your Feedback"
+ "message": "Share your feedback"
},
"needHelpLinkText": {
- "message": "MetaMask Support"
+ "message": "MetaMask support"
},
"needHelpSubmitTicket": {
- "message": "Submit a Ticket"
+ "message": "Submit a ticket"
},
"needImportFile": {
"message": "You must select a file to import.",
@@ -1984,13 +2094,13 @@
"message": "Network added successfully!"
},
"networkDetails": {
- "message": "Network Details"
+ "message": "Network details"
},
"networkIsBusy": {
"message": "Network is busy. Gas prices are high and estimates are less accurate."
},
"networkName": {
- "message": "Network Name"
+ "message": "Network name"
},
"networkNameAvalanche": {
"message": "Avalanche"
@@ -2043,7 +2153,7 @@
"message": "Nevermind"
},
"newAccount": {
- "message": "New Account"
+ "message": "New account"
},
"newAccountDetectedDialogMessage": {
"message": "New address detected! Click here to add to your address book."
@@ -2056,10 +2166,10 @@
"message": "Collectible was successfully added!"
},
"newContact": {
- "message": "New Contact"
+ "message": "New contact"
},
"newContract": {
- "message": "New Contract"
+ "message": "New contract"
},
"newNFTDetectedMessage": {
"message": "Allow MetaMask to automatically detect NFTs from Opensea and display in your wallet."
@@ -2084,10 +2194,10 @@
"message": "Token imported"
},
"newTotal": {
- "message": "New Total"
+ "message": "New total"
},
"newTransactionFee": {
- "message": "New Transaction Fee"
+ "message": "New transaction fee"
},
"newValues": {
"message": "new values"
@@ -2103,7 +2213,7 @@
"message": "NFT"
},
"nftTokenIdPlaceholder": {
- "message": "Enter the Token ID"
+ "message": "Enter the token id"
},
"nfts": {
"message": "NFTs"
@@ -2121,10 +2231,10 @@
"message": "No, I already have a Secret Recovery Phrase"
},
"noConversionDateAvailable": {
- "message": "No Currency Conversion Date Available"
+ "message": "No currency conversion date available"
},
"noConversionRateAvailable": {
- "message": "No Conversion Rate Available"
+ "message": "No conversion rate available"
},
"noNFTs": {
"message": "No NFTs yet"
@@ -2133,7 +2243,7 @@
"message": "No Snaps installed"
},
"noThanks": {
- "message": "No Thanks"
+ "message": "No thanks"
},
"noThanksVariant2": {
"message": "No, thanks."
@@ -2157,7 +2267,7 @@
"message": "Turn this on to change the nonce (transaction number) on confirmation screens. This is an advanced feature, use cautiously."
},
"nonceFieldHeading": {
- "message": "Custom Nonce"
+ "message": "Custom nonce"
},
"notBusy": {
"message": "Not busy"
@@ -2166,14 +2276,14 @@
"message": "Is this the correct account? It's different from the currently selected account in your wallet"
},
"notEnoughGas": {
- "message": "Not Enough Gas"
+ "message": "Not enough gas"
},
"notifications": {
"message": "Notifications"
},
"notifications10ActionText": {
- "message": "Visit in settings",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "message": "Visit in Settings",
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "Improved token detection is currently available on Ethereum Mainnet, Polygon, BSC, and Avalanche networks. More to come!"
@@ -2197,11 +2307,21 @@
"message": "Enable dark mode"
},
"notifications12Description": {
- "message": "Dark mode on Extension is finally here! To turn it on, go to Settings -> Experimental and select one of the display options: Light, Dark, System."
+ "message": "Dark mode on Extension is finally here! To turn it on, go to Settings > Experimental and select one of the display options: Light, Dark, System."
},
"notifications12Title": {
"message": "Wen dark mode? Now dark mode! 🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "Show custom network list"
+ },
+ "notifications13Description": {
+ "message": "You can now add the following popular custom networks easily: Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm and Polygon! To enable this feature, go to Settings > Experimental and turn \"Show custom network list\" on!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "Add Popular Networks"
+ },
"notifications1Description": {
"message": "MetaMask Mobile users can now swap tokens inside their mobile wallet. Scan the QR code to get the mobile app and start swapping.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2251,7 +2371,7 @@
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6Title": {
- "message": "Ledger Support Update for Chrome Users",
+ "message": "Ledger support update for Chrome users",
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"notifications7DescriptionOne": {
@@ -2267,15 +2387,15 @@
"description": "Title for a notification in the 'See What's New' popup. Notifies ledger users of the need to update firmware."
},
"notifications8ActionText": {
- "message": "Go to Advanced Settings",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "message": "Go to Settings > Advanced",
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "As of MetaMask v10.4.0, you no longer need Ledger Live to connect your Ledger device to MetaMask.",
"description": "Description of a notification in the 'See What's New' popup. Describes changes for how Ledger Live is no longer needed to connect the device."
},
"notifications8DescriptionTwo": {
- "message": "For an easier and more stable ledger experience, go to the Advanced tab of settings and switch the 'Preferred Ledger Connection Type' to 'WebHID'.",
+ "message": "For an easier and more stable ledger experience, go to Settings > Advanced and switch the 'Preferred Ledger Connection Type' to 'WebHID'.",
"description": "Description of a notification in the 'See What's New' popup. Describes how the user can turn off the Ledger Live setting."
},
"notifications8Title": {
@@ -2304,10 +2424,13 @@
"notificationsMarkAllAsRead": {
"message": "Mark all as read"
},
- "numberOfNewTokensDetected": {
+ "numberOfNewTokensDetectedPlural": {
"message": "$1 new tokens found in this account",
"description": "$1 is the number of new tokens detected"
},
+ "numberOfNewTokensDetectedSingular": {
+ "message": "1 new token found in this account"
+ },
"ofTextNofM": {
"message": "of"
},
@@ -2330,7 +2453,7 @@
"message": "Import an existing wallet"
},
"onboardingPinExtensionBillboardAccess": {
- "message": "Full Access"
+ "message": "Full access"
},
"onboardingPinExtensionBillboardDescription": {
"message": "These extensions can see and change information"
@@ -2387,8 +2510,8 @@
"message": "Open MetaMask in full screen to connect your ledger via WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Check the source code"
+ "openInBlockExplorer": {
+ "message": "Open in block explorer"
},
"optional": {
"message": "Optional"
@@ -2460,10 +2583,16 @@
"message": "Permission request"
},
"permissionRequestCapitalized": {
- "message": "Permission Request"
+ "message": "Permission request"
+ },
+ "permissionRequested": {
+ "message": "Requested now"
+ },
+ "permissionRevoked": {
+ "message": "Revoked in this update"
},
"permission_accessNetwork": {
- "message": "Access the Internet.",
+ "message": "Access the internet.",
"description": "The description of the `endowment:network-access` permission."
},
"permission_accessSnap": {
@@ -2482,6 +2611,10 @@
"message": "Run indefinitely.",
"description": "The description for the `endowment:long-running` permission"
},
+ "permission_manageBip32Keys": {
+ "message": "Control your accounts and assets under $1 ($2).",
+ "description": "The description for the `snap_getBip32Entropy_*` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'."
+ },
"permission_manageBip44Keys": {
"message": "Control your \"$1\" accounts and assets.",
"description": "The description for the `snap_getBip44Entropy_*` permission. $1 is the name of a protocol, e.g. 'Filecoin'."
@@ -2515,8 +2648,8 @@
"message": "Popular custom networks"
},
"preferredLedgerConnectionType": {
- "message": "Preferred Ledger Connection Type",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "message": "Preferred Ledger connection type",
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Preparing swap..."
@@ -2525,7 +2658,7 @@
"message": "Prev"
},
"primaryCurrencySetting": {
- "message": "Primary Currency"
+ "message": "Primary currency"
},
"primaryCurrencySettingDescription": {
"message": "Select native to prioritize displaying values in the native currency of the chain (e.g. ETH). Select Fiat to prioritize displaying values in your selected fiat currency."
@@ -2537,7 +2670,7 @@
"message": "Priority Fee"
},
"privacyMsg": {
- "message": "Privacy Policy"
+ "message": "Privacy policy"
},
"privateKey": {
"message": "Private Key",
@@ -2547,19 +2680,19 @@
"message": "Warning: Never disclose this key. Anyone with your private keys can steal any assets held in your account."
},
"privateNetwork": {
- "message": "Private Network"
+ "message": "Private network"
},
"proceedWithTransaction": {
"message": "I want to proceed anyway"
},
"proposedApprovalLimit": {
- "message": "Proposed Approval Limit"
+ "message": "Proposed approval limit"
},
"provide": {
"message": "Provide"
},
"publicAddress": {
- "message": "Public Address"
+ "message": "Public address"
},
"queue": {
"message": "Queue"
@@ -2619,7 +2752,7 @@
"message": "Reject"
},
"rejectAll": {
- "message": "Reject All"
+ "message": "Reject all"
},
"rejectTxsDescription": {
"message": "You are about to batch reject $1 transactions."
@@ -2671,13 +2804,13 @@
"message": "Reset"
},
"resetAccount": {
- "message": "Reset Account"
+ "message": "Reset account"
},
"resetAccountDescription": {
"message": "Resetting your account will clear your transaction history. This will not change the balances in your accounts or require you to re-enter your Secret Recovery Phrase."
},
"resetWallet": {
- "message": "Reset Wallet"
+ "message": "Reset wallet"
},
"resetWalletSubHeader": {
"message": "MetaMask does not keep a copy of your password. If you’re having trouble unlocking your account, you will need to reset your wallet. You can do this by providing the Secret Recovery Phrase you used when you set up your wallet."
@@ -2694,12 +2827,24 @@
"restore": {
"message": "Restore"
},
+ "restoreFailed": {
+ "message": "Can not restore your data from the file provided"
+ },
+ "restoreSuccessful": {
+ "message": "Your data has been restored successfully"
+ },
+ "restoreUserData": {
+ "message": "Restore user data"
+ },
+ "restoreUserDataDescription": {
+ "message": "You can restore user settings containing preferences and account addresses from a previously backed up JSON file."
+ },
"restoreWalletPreferences": {
"message": "A backup of your data from $1 has been found. Would you like to restore your wallet preferences?",
"description": "$1 is the date at which the data was backed up"
},
"retryTransaction": {
- "message": "Retry Transaction"
+ "message": "Retry transaction"
},
"reusedTokenNameWarning": {
"message": "A token here reuses a symbol from another token you watch, this can be confusing or deceptive."
@@ -2727,29 +2872,32 @@
"message": "By revoking permission, the following $1 will no longer be able to access your $2",
"description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
},
+ "revokeSpendingCapTooltipText": {
+ "message": "This contract will be unable to spend any more of your current or future tokens."
+ },
"rinkeby": {
- "message": "Rinkeby Test Network"
+ "message": "Rinkeby test network"
},
"ropsten": {
- "message": "Ropsten Test Network"
+ "message": "Ropsten test network"
},
"rpcUrl": {
"message": "New RPC URL"
},
"safeTransferFrom": {
- "message": "Safe Transfer From"
+ "message": "Safe transfer from"
},
"save": {
"message": "Save"
},
"saveAsCsvFile": {
- "message": "Save as CSV File"
+ "message": "Save as CSV file"
},
"scanInstructions": {
"message": "Place the QR code in front of your camera"
},
"scanQrCode": {
- "message": "Scan QR Code"
+ "message": "Scan QR code"
},
"scrollDown": {
"message": "Scroll down"
@@ -2758,16 +2906,16 @@
"message": "Search"
},
"searchAccounts": {
- "message": "Search Accounts"
+ "message": "Search accounts"
},
"searchResults": {
- "message": "Search Results"
+ "message": "Search results"
},
"searchSettings": {
- "message": "Search in settings"
+ "message": "Search in Settings"
},
"searchTokens": {
- "message": "Search Tokens"
+ "message": "Search tokens"
},
"secretBackupPhraseDescription": {
"message": "Your Secret Recovery Phrase makes it easy to back up and restore your account."
@@ -2782,10 +2930,10 @@
"message": "Secret Recovery Phrase"
},
"secureWallet": {
- "message": "Secure Wallet"
+ "message": "Secure wallet"
},
"securityAndPrivacy": {
- "message": "Security & Privacy"
+ "message": "Security & privacy"
},
"seedPhraseConfirm": {
"message": "Confirm Secret Recovery Phrase"
@@ -2854,7 +3002,7 @@
"message": "Select all"
},
"selectAnAccount": {
- "message": "Select an Account"
+ "message": "Select an account"
},
"selectAnAccountAlreadyConnected": {
"message": "This account has already been connected to MetaMask"
@@ -2863,7 +3011,7 @@
"message": "Please select each phrase in order to make sure it is correct."
},
"selectHdPath": {
- "message": "Select HD Path"
+ "message": "Select HD path"
},
"selectNFTPrivacyPreference": {
"message": "Turn on NFT detection in Settings"
@@ -2881,7 +3029,7 @@
"message": "Send"
},
"sendAmount": {
- "message": "Send Amount"
+ "message": "Send amount"
},
"sendBugReport": {
"message": "Send us a bug report."
@@ -2894,7 +3042,7 @@
"message": "Send to"
},
"sendTokens": {
- "message": "Send Tokens"
+ "message": "Send tokens"
},
"sendingDisabled": {
"message": "Sending of ERC-1155 NFT assets is not yet supported."
@@ -2914,7 +3062,7 @@
"message": "MetaMask uses these trusted third-party services to enhance product usability and safety."
},
"setApprovalForAll": {
- "message": "Set Approval for All"
+ "message": "Set approval for all"
},
"setApprovalForAllTitle": {
"message": "Approve $1 with no spend limit",
@@ -2940,19 +3088,19 @@
"message": "Select this to show gas price and limit controls directly on the send and confirm screens."
},
"showCustomNetworkList": {
- "message": "Show Custom Network List"
+ "message": "Show custom network list"
},
"showCustomNetworkListDescription": {
"message": "Select this to show a list of networks with prefilled details when adding a new network."
},
"showFiatConversionInTestnets": {
- "message": "Show Conversion on test networks"
+ "message": "Show conversion on test networks"
},
"showFiatConversionInTestnetsDescription": {
"message": "Select this to show fiat conversion on test networks"
},
"showHexData": {
- "message": "Show Hex Data"
+ "message": "Show hex data"
},
"showHexDataDescription": {
"message": "Select this to show the hex data field on the send screen"
@@ -2961,7 +3109,7 @@
"message": "Show/hide"
},
"showIncomingTransactions": {
- "message": "Show Incoming Transactions"
+ "message": "Show incoming transactions"
},
"showIncomingTransactionsDescription": {
"message": "Select this to use Etherscan to show incoming transactions in the transactions list"
@@ -2973,7 +3121,7 @@
"message": "Show Private Keys"
},
"showRecommendations": {
- "message": "Show Recommendations"
+ "message": "Show recommendations"
},
"showTestnetNetworks": {
"message": "Show test networks"
@@ -2982,7 +3130,7 @@
"message": "Select this to show test networks in network list"
},
"sigRequest": {
- "message": "Signature Request"
+ "message": "Signature request"
},
"sign": {
"message": "Sign"
@@ -2991,7 +3139,7 @@
"message": "Signing this message can be dangerous. This signature could potentially perform any operation on your account's behalf, including granting complete control of your account and all of its assets to the requesting site. Only sign this message if you know what you're doing or completely trust the requesting site."
},
"signatureRequest": {
- "message": "Signature Request"
+ "message": "Signature request"
},
"signatureRequest1": {
"message": "Message"
@@ -2999,6 +3147,9 @@
"signed": {
"message": "Signed"
},
+ "signin": {
+ "message": "Sign-In"
+ },
"simulationErrorMessageV2": {
"message": "We were not able to estimate gas. There might be an error in the contract and this transaction may fail."
},
@@ -3006,7 +3157,7 @@
"message": "Skip"
},
"skipAccountSecurity": {
- "message": "Skip Account Security?"
+ "message": "Skip account security?"
},
"skipAccountSecurityDetails": {
"message": "I understand that until I back up my Secret Recovery Phrase, I may lose my accounts and all of their assets."
@@ -3015,7 +3166,7 @@
"message": "Slow"
},
"smartTransaction": {
- "message": "Smart Transaction"
+ "message": "Smart transaction"
},
"snapAccess": {
"message": "$1 snap has access to:",
@@ -3033,15 +3184,25 @@
"message": "Install Snap"
},
"snapInstallWarningCheck": {
- "message": "To confirm you understand, check all."
+ "message": "To confirm that you understand, check the box."
+ },
+ "snapInstallWarningCheckPlural": {
+ "message": "To confirm that you understand, check all the boxes."
},
"snapInstallWarningKeyAccess": {
- "message": "You are granting key access to the snap \"$1\". This is irrevocable and grants \"$1\" control of your accounts and assets. Make sure you trust \"$1\" before proceeding.",
- "description": "The parameter is the name of the snap"
+ "message": "You are granting $2 key access to the snap \"$1\". This is irrevocable and grants \"$1\" control of your $2 accounts and assets. Make sure you trust \"$1\" before proceeding.",
+ "description": "The first parameter is the name of the snap and the second one is the protocol"
},
"snapRequestsPermission": {
"message": "This snap is requesting the following permissions:"
},
+ "snapUpdate": {
+ "message": "Update Snap"
+ },
+ "snapUpdateExplanation": {
+ "message": "$1 needs a newer version of your snap.",
+ "description": "$1 is the dapp that is requesting an update to the snap."
+ },
"snaps": {
"message": "Snaps"
},
@@ -3064,7 +3225,7 @@
"message": "Source"
},
"speedUp": {
- "message": "Speed Up"
+ "message": "Speed up"
},
"speedUpCancellation": {
"message": "Speed up this cancellation"
@@ -3134,10 +3295,10 @@
"message": "Error in retrieving state logs."
},
"stateLogFileName": {
- "message": "MetaMask State Logs"
+ "message": "MetaMask state logs"
},
"stateLogs": {
- "message": "State Logs"
+ "message": "State logs"
},
"stateLogsDescription": {
"message": "State logs contain your public account addresses and sent transactions."
@@ -3207,7 +3368,7 @@
"message": "Swap would have failed"
},
"stxCancelledDescription": {
- "message": "Your transaction would have failed and was canceled to protect you from paying unnecessary gas fees."
+ "message": "Your transaction would have failed and was cancelled to protect you from paying unnecessary gas fees."
},
"stxCancelledSubDescription": {
"message": "Try your swap again. We’ll be here to protect you against similar risks next time."
@@ -3273,10 +3434,10 @@
"message": "A transaction has been successful but we’re unsure what it is. This may be due to submitting another transaction while this swap was processing."
},
"stxUserCancelled": {
- "message": "Swap canceled"
+ "message": "Swap cancelled"
},
"stxUserCancelledDescription": {
- "message": "Your transaction has been canceled and you did not pay any unnecessary gas fees."
+ "message": "Your transaction has been cancelled and you did not pay any unnecessary gas fees."
},
"stxYouCanOptOut": {
"message": "You can opt-out in advanced settings any time."
@@ -3291,7 +3452,7 @@
"message": "Support"
},
"supportCenter": {
- "message": "Visit our Support Center"
+ "message": "Visit our support center"
},
"swap": {
"message": "Swap"
@@ -3493,7 +3654,7 @@
"message": "Request for quotation"
},
"swapReviewSwap": {
- "message": "Review Swap"
+ "message": "Review swap"
},
"swapSearchForAToken": {
"message": "Search for a token"
@@ -3592,13 +3753,13 @@
"message": "0% Slippage"
},
"swapsAdvancedOptions": {
- "message": "Advanced Options"
+ "message": "Advanced options"
},
"swapsExcessiveSlippageWarning": {
"message": "Slippage amount is too high and will result in a bad rate. Please reduce your slippage tolerance to a value below 15%."
},
"swapsMaxSlippage": {
- "message": "Slippage Tolerance"
+ "message": "Slippage tolerance"
},
"swapsNotEnoughForTx": {
"message": "Not enough $1 to complete this transaction",
@@ -3617,7 +3778,7 @@
"message": "Switch network"
},
"switchNetworks": {
- "message": "Switch Networks"
+ "message": "Switch networks"
},
"switchToNetwork": {
"message": "Switch to $1",
@@ -3626,6 +3787,9 @@
"switchToThisAccount": {
"message": "Switch to this account"
},
+ "switchedTo": {
+ "message": "You have switched to"
+ },
"switchingNetworksCancelsPendingConfirmations": {
"message": "Switching networks will cancel all pending confirmations"
},
@@ -3675,13 +3839,13 @@
"message": "10% increase"
},
"terms": {
- "message": "Terms of Use"
+ "message": "Terms of use"
},
"termsOfService": {
- "message": "Terms of Service"
+ "message": "Terms of service"
},
"testFaucet": {
- "message": "Test Faucet"
+ "message": "Test faucet"
},
"testNetworks": {
"message": "Test networks"
@@ -3692,6 +3856,9 @@
"themeDescription": {
"message": "Choose your preferred MetaMask theme."
},
+ "thingsToKeep": {
+ "message": "Things to keep in mind:"
+ },
"thisWillCreate": {
"message": "This will create a new wallet and Secret Recovery Phrase"
},
@@ -3710,7 +3877,7 @@
},
"toggleTestNetworks": {
"message": "$1 test networks",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Token"
@@ -3722,13 +3889,13 @@
"message": "Token has already been added."
},
"tokenContractAddress": {
- "message": "Token Contract Address"
+ "message": "Token contract address"
},
"tokenDecimalFetchFailed": {
"message": "Token decimal required."
},
"tokenDecimalTitle": {
- "message": "Token Decimal:"
+ "message": "Token decimal:"
},
"tokenDetails": {
"message": "Token details"
@@ -3739,11 +3906,11 @@
"tokenDetectionAlertMessage": {
"message": "Token detection is currently available on $1. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "New! Improved token detection is available on Ethereum Mainnet as an experimental feature. $1"
+ "tokenDetectionDescription": {
+ "message": "ConsenSys' token API aggregates a list of tokens from various third party token lists. When turned on, tokens will be automatically detected, and searchable, on Ethereum mainnet, Binance, Polygon and Avalanche. When turned off, you will still be able to search for tokens on Ethereum mainnet using MetaMask's legacy token list."
},
- "tokenDetectionToggleDescription": {
- "message": "ConsenSys’ token API aggregates a list of tokens from various third party token lists. Turning it off will stop detecting new tokens added to your wallet, but will keep the option to search for tokens to import."
+ "tokenFoundTitle": {
+ "message": "1 new token found"
},
"tokenId": {
"message": "Token ID"
@@ -3751,8 +3918,14 @@
"tokenList": {
"message": "Token lists:"
},
+ "tokenScamSecurityRisk": {
+ "message": "token scams and security risks"
+ },
+ "tokenShowUp": {
+ "message": "Your tokens may not automatically show up in your wallet."
+ },
"tokenSymbol": {
- "message": "Token Symbol"
+ "message": "Token symbol"
},
"tokensFoundTitle": {
"message": "$1 new tokens found",
@@ -3825,7 +3998,7 @@
"message": "Transaction dropped at $2."
},
"transactionError": {
- "message": "Transaction Error. Exception thrown in contract code."
+ "message": "Transaction error. Exception thrown in contract code."
},
"transactionErrorNoContract": {
"message": "Trying to call a function on a non-contract address."
@@ -3837,25 +4010,25 @@
"message": "Transaction fee"
},
"transactionHistoryBaseFee": {
- "message": "Base Fee (GWEI)"
+ "message": "Base fee (GWEI)"
},
"transactionHistoryL1GasLabel": {
- "message": "Total L1 Gas Fee"
+ "message": "Total L1 gas fee"
},
"transactionHistoryL2GasLimitLabel": {
- "message": "L2 Gas Limit"
+ "message": "L2 gas limit"
},
"transactionHistoryL2GasPriceLabel": {
- "message": "L2 Gas Price"
+ "message": "L2 gas price"
},
"transactionHistoryMaxFeePerGas": {
- "message": "Max Fee Per Gas"
+ "message": "Max fee per gas"
},
"transactionHistoryPriorityFee": {
- "message": "Priority Fee (GWEI)"
+ "message": "Priority fee (GWEI)"
},
"transactionHistoryTotalGasFee": {
- "message": "Total Gas Fee"
+ "message": "Total gas fee"
},
"transactionResubmitted": {
"message": "Transaction resubmitted with estimated gas fee increased to $1 at $2"
@@ -3873,7 +4046,7 @@
"message": "Transfer between my accounts"
},
"transferFrom": {
- "message": "Transfer From"
+ "message": "Transfer from"
},
"troubleConnectingToWallet": {
"message": "We had trouble connecting to your $1, try reviewing $2 and try again.",
@@ -3930,7 +4103,7 @@
"message": "Unnamed collection"
},
"unknownNetwork": {
- "message": "Unknown Private Network"
+ "message": "Unknown private network"
},
"unknownQrCode": {
"message": "Error: We couldn't identify that QR code"
@@ -3948,6 +4121,10 @@
"message": "This custom network is not recognized",
"description": "$1 is a clickable link with text defined by the 'unrecognizedChanLinkText' key. The link will open to instructions for users to validate custom network details."
},
+ "unrecognizedProtocol": {
+ "message": "$1 (Unrecognized protocol)",
+ "description": "Shown when the protocol is unknown by the extension. $1 is the protocol code."
+ },
"unsendableAsset": {
"message": "Sending collectible (ERC-721) tokens is not currently supported",
"description": "This is an error message we show the user if they attempt to send a collectible asset type, for which currently don't support sending"
@@ -3974,17 +4151,11 @@
"message": "Displaying NFTs media & data may expose your IP address to centralized servers. Third-party APIs (like OpenSea) are used to detect NFTs in your wallet. This exposes your account address with those services. Leave this disabled if you don’t want the app to pull data from those those services."
},
"usePhishingDetection": {
- "message": "Use Phishing Detection"
+ "message": "Use phishing detection"
},
"usePhishingDetectionDescription": {
"message": "Display a warning for phishing domains targeting Ethereum users"
},
- "useTokenDetection": {
- "message": "Use Token Detection"
- },
- "useTokenDetectionDescription": {
- "message": "We use third-party APIs to detect and display new tokens sent to your wallet. Turn off if you don’t want MetaMask to pull data from those services."
- },
"useTokenDetectionPrivacyDesc": {
"message": "Automatically displaying tokens sent to your account involves communication with third party servers to fetch token’s images. Those serves will have access to your IP address."
},
@@ -4007,19 +4178,19 @@
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"viewAccount": {
- "message": "View Account"
+ "message": "View account"
},
"viewAllDetails": {
"message": "View all details"
},
"viewContact": {
- "message": "View Contact"
+ "message": "View contact"
},
"viewFullTransactionDetails": {
"message": "View full transaction details"
},
"viewMore": {
- "message": "View More"
+ "message": "View more"
},
"viewOnBlockExplorer": {
"message": "View on block explorer"
@@ -4036,7 +4207,7 @@
"message": "View on Opensea"
},
"viewinExplorer": {
- "message": "View $1 in Explorer",
+ "message": "View $1 in explorer",
"description": "$1 is the action type. e.g (Account, Transaction, Swap)"
},
"visitWebSite": {
@@ -4071,6 +4242,10 @@
"warning": {
"message": "Warning"
},
+ "warningTooltipText": {
+ "message": "$1 The contract could spend your entire token balance without further notice or consent. Protect yourself by customizing a lower spending cap.",
+ "description": "$1 is a fa-exclamation-circle icon with text 'Be careful' in 'warning' colour"
+ },
"weak": {
"message": "Weak"
},
@@ -4086,7 +4261,7 @@
"message": "Welcome to MetaMask"
},
"welcomeBack": {
- "message": "Welcome Back!"
+ "message": "Welcome back!"
},
"welcomeExploreDescription": {
"message": "Store, send and spend crypto currencies and assets."
diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json
index ffe4d6aeb..ef92cc93a 100644
--- a/app/_locales/es/messages.json
+++ b/app/_locales/es/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "Añadir memo"
},
+ "addMoreNetworks": {
+ "message": "agregar más redes manualmente"
+ },
"addNetwork": {
"message": "Agregar red"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "Alertas"
},
+ "allOfYour": {
+ "message": "Todo su $1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "Permitir que esta extensión externa haga lo siguiente:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "Aprobar límite de gastos"
},
+ "approveAllTokensTitle": {
+ "message": "¿Dar permiso para acceder a todo su $1?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "Aprobar e instalar"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "Activo aprobado"
},
- "areYouDeveloper": {
- "message": "¿Usted es desarrollador?"
- },
"areYouSure": {
"message": "¿Está seguro?"
},
@@ -438,7 +446,7 @@
"message": "Comprar $1 con Wyre"
},
"buyWithWyreDescription": {
- "message": "Acceso fácil a transacciones de $ 1,000 o menos con verificación rápida y efectiva. Aceptamos tarjetas débito, crédito, Apple Pay y transferencias bancarias en más de 100 países. Tokens serán depósitados en su MetaMask."
+ "message": "Acceso fácil a compras de hasta $1,000. Verificación interactiva rápida de compra de límite alto. Acepta tarjeta de débito/crédito, Apple Pay y transferencias bancarias. Disponible para más de 100 países. Los tokens se depositarán en su cuenta MetaMask"
},
"bytes": {
"message": "Bytes"
@@ -466,6 +474,13 @@
"message": "Para $1 una transacción, la tarifa de gas debe aumentar al menos un 10% para que sea reconocida por la red.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "Cancelar el swap por ~$1",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "Cancelar el swap gratuitamente"
+ },
"cancellationGasFee": {
"message": "Cuota de gas por cancelación"
},
@@ -650,7 +665,7 @@
"message": "Interacción con el contrato"
},
"convertTokenToNFTDescription": {
- "message": "Hemos detectado que este activo es un NFT. MetaMask ahora tiene soporte nativo completo para NFTs. ¿Quieres eliminarlo de tu lista de tokens y añadirlo como un NFT?"
+ "message": "Hemos detectado que este activo es un NFT. MetaMask ahora tiene soporte nativo completo para NFT. ¿Quiere eliminarlo de su lista de tokens y añadirlo como un NFT?"
},
"convertTokenToNFTExistDescription": {
"message": "Hemos detectado que este recurso se ha agregado como NFT. ¿Quiere eliminarlo de su lista de tokens?"
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "Use $1 para personalizar el precio de gas. Esto puede ser confuso si no está familiarizado. Interactúe bajo su propio riesgo.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Aumentar la cuota puede disminuir los tiempos de procesamiento, pero no está garantizado."
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "Deposite $1",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Descripción"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "Se produjo un error y no pudimos completar la acción"
},
- "fakeTokenWarning": {
- "message": "Cualquiera puede crear un token, incluso crear versiones falsas de tokens existentes. Aprenda más sobre $1"
- },
"fast": {
"message": "Rápido"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "Función: Aprobar"
},
+ "functionSetApprovalForAll": {
+ "message": "Función: SetApprovalForAll"
+ },
"functionType": {
"message": "Tipo de función"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "Alta probabilidad, incluso en mercados volátiles. Use $1 para cubrir aumentos repentinos en el tráfico de la red debido a cosas como caídas de NFT populares.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "alto"
@@ -1521,7 +1536,7 @@
"message": "agregar activo"
},
"importTokensCamelCase": {
- "message": "AGREGAR TOKENS"
+ "message": "Agregar Tokens"
},
"importWallet": {
"message": "Importar cartera"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "Frase secreta de recuperación no válida"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "¡Entrada inválida! La frase secreta de recuperación distingue entre mayúsculas y minúsculas."
+ },
"ipfsGateway": {
"message": "Puerta de enlace de IPFS"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Utilice $1 para esperar un precio más bajo. Las estimaciones de tiempo son mucho menos precisas ya que los precios son algo imprevisibles.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "bajo"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Utilice $1 para un procesamiento rápido al precio actual del mercado.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "memorándum"
@@ -1892,6 +1910,19 @@
"message": "verifique los detalles de la red",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "Recomendamos que usted $1 antes de proceder.",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "Según nuestros registros, es posible que el nombre de la red no coincida correctamente con este ID de cadena."
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "El símbolo de moneda enviado no coincide con lo que esperamos para este ID de cadena."
+ },
+ "mismatchedRpcUrl": {
+ "message": "Según nuestros registros, el valor de la URL de RPC enviado no coincide con un proveedor conocido para este ID de cadena."
+ },
"missingNFT": {
"message": "¿No ve su NFT?"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "Red:"
},
+ "networkAddedSuccessfully": {
+ "message": "¡Red añadida exitosamente!"
+ },
"networkDetails": {
"message": "Detalles de la red"
},
@@ -2059,6 +2093,9 @@
"message": "El nonce es superior al nonce sugerido de $1",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "Ingresa el ID del token"
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "Vaya a configuración",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "En este momento, la detección mejorada de token está disponible en las redes Ethereum Mainnet, Polygon, BSC y Avalanche. ¡Y habrá más!"
@@ -2154,11 +2191,21 @@
"message": "Habilitar modo oscuro"
},
"notifications12Description": {
- "message": "¡El modo oscuro en Extension finalmente está aquí! Para encenderlo, vaya a Configuración -> Experimental y seleccione una de las opciones de visualización: Claro, Oscuro, Sistema."
+ "message": "¡El modo oscuro en Extensión finalmente está aquí! Para activarlo, vaya a Configuración -> Experimental y seleccione una de las opciones de visualización: Claro, Oscuro, Sistema."
},
"notifications12Title": {
"message": "¿Cuándo estará disponible el modo oscuro? ¡Ahora! 🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "Mostrar lista de redes personalizadas"
+ },
+ "notifications13Description": {
+ "message": "Ahora puede agregar fácilmente las siguientes redes personalizadas populares: Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm y Polygon. Para habilitar esta función, vaya a Configuración -> Experimental y ¡active \"Mostrar lista de redes personalizadas\"!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "Añadir redes populares"
+ },
"notifications1Description": {
"message": "Los usuarios de la aplicación móvil de MetaMask ahora pueden canjear tokens en su cartera móvil. Escanee el código QR para obtener la aplicación móvil y comience a canjear.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "Ir a Configuración Avanzada",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "A partir de MetaMask v10.4.0, ya no necesita Ledger Live para conectar su dispositivo Ledger a MetaMask.",
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "Marcar todo como leído"
},
- "numberOfNewTokensDetected": {
- "message": "Se encontraron tokens nuevos de $1 en esta cuenta",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "de"
},
@@ -2344,9 +2387,6 @@
"message": "Abra MetaMask en pantalla completa para conectar su Ledger a través de WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Compruebe el código fuente"
- },
"optional": {
"message": "Opcional"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "Tipo de conexión de Ledger preferida",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Preparando swap..."
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "Revelar frase semilla"
},
+ "revokeAllTokensTitle": {
+ "message": "¿Revocar permiso para acceder a todo su $1?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "Al revocar permisos, el/la siguiente $1 no tendrá más acceso a su $2",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Red de prueba Rinkeby"
},
@@ -2852,12 +2900,23 @@
"message": "Enviando $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "Advertencia: está a punto de enviar un contrato de token que podría dar lugar a una pérdida de fondos. $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "Configuración avanzada de privacidad"
},
"setAdvancedPrivacySettingsDetails": {
"message": "MetaMask utiliza estos servicios de terceros de confianza para mejorar la usabilidad y la seguridad de los productos."
},
+ "setApprovalForAll": {
+ "message": "Establecer aprobación para todos"
+ },
+ "setApprovalForAllTitle": {
+ "message": "Aprobar $1 sin límite preestablecido",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "Configuración"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "Seleccione esta opción para mostrar el precio del gas y limitar los controles directamente en las pantallas de envío y confirmación."
},
+ "showCustomNetworkList": {
+ "message": "Mostrar lista de redes personalizadas"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "Seleccione esto para mostrar una lista de redes con detalles precargados al agregar una red nueva."
+ },
"showFiatConversionInTestnets": {
"message": "Mostrar conversión en redes de prueba"
},
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "Para confirmar que comprende, marque todo."
},
- "snapInstallWarningKeyAccess": {
- "message": "Está otorgando acceso clave al complemento \"$1\". Esto es irrevocable y le otorga a \"$1\" el control de sus cuentas y activos. Asegúrese de confiar en \"$1\" antes de continuar.",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "Este complemento solicita los siguientes permisos:"
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "Un complemento solo se ejecutará si está habilitado"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "Algunas redes pueden presentar riesgos de seguridad y/o privacidad. Comprenda los riesgos antes de agregar y utilizar una red."
+ },
"somethingWentWrong": {
"message": "Lo lamentamos, se produjo un error."
},
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "Cambiar redes"
},
+ "switchToNetwork": {
+ "message": "Cambiar a $1",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "Cambiar a esta cuenta"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "$1 redes de prueba",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Token"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "La detección de tókens está actualmente disponible en $1. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "¡Nuevo! La detección de tokens mejorada está disponible en la Mainnet de Ethereum como funcionalidad experimental. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "La API de tokens de ConsenSys agrega una lista de tokens de varias listas de tokens de terceros. Al desactivarla se dejará de detectar nuevos tokens agregados a su billetera, pero se mantendrá la opción de buscar tokens para importar."
- },
"tokenId": {
"message": "Identificador de Token"
},
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "Lo lamentamos, se produjo un error…"
},
+ "unknownCollection": {
+ "message": "Colección sin nombre"
+ },
"unknownNetwork": {
"message": "Red privada desconocida"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "Mostrar una advertencia respecto de los dominios de phishing dirigidos a los usuarios de Ethereum"
},
- "useTokenDetection": {
- "message": "Usar detección de token"
- },
- "useTokenDetectionDescription": {
- "message": "Utilizamos API de terceros para detectar y mostrar nuevos tokens enviados a su cartera. Desactive si no desea que MetaMask extraiga datos de esos servicios."
- },
"useTokenDetectionPrivacyDesc": {
"message": "La visualización automática de tokens enviados a su cuenta implica la comunicación con servidores de terceros para obtener imágenes de tokens. Esos servicios tendrán acceso a su dirección IP."
},
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "Creación exitosa de la cartera"
},
+ "wantToAddThisNetwork": {
+ "message": "¿Desea añadir esta red?"
+ },
"warning": {
"message": "Advertencia"
},
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "Sí, intentémoslo"
},
+ "youHaveAddedAll": {
+ "message": "Ha agregado todas las redes populares. Puede descubrir más redes $1 o puede $2",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "Necesita permitir el acceso a la cámara para usar esta función."
},
diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json
index f92e60f3d..e3a617fbe 100644
--- a/app/_locales/es_419/messages.json
+++ b/app/_locales/es_419/messages.json
@@ -1047,9 +1047,6 @@
"failureMessage": {
"message": "Se produjo un error y no pudimos finalizar la acción"
},
- "fakeTokenWarning": {
- "message": "Cualquiera puede crear un token, incluso crear versiones falsas de tokens existentes. Aprenda más sobre $1"
- },
"fast": {
"message": "Rápido"
},
@@ -1533,7 +1530,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Utilice $1 para esperar un precio más bajo. Las estimaciones de tiempo son mucho menos precisas ya que los precios son algo imprevisibles.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "baja"
@@ -1565,7 +1562,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Utilice $1 para un procesamiento rápido al precio actual del mercado.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "nota"
@@ -1911,7 +1908,7 @@
},
"notifications8ActionText": {
"message": "Ir a Configuración Avanzada",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "A partir de MetaMask v10.4.0, ya no necesita Ledger Live para conectar su dispositivo Ledger a MetaMask.",
@@ -2085,7 +2082,7 @@
},
"preferredLedgerConnectionType": {
"message": "Tipo de conexión de Ledger preferida",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"prev": {
"message": "Ant."
@@ -3007,7 +3004,7 @@
},
"toggleTestNetworks": {
"message": "$1 redes de prueba",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Token"
@@ -3021,9 +3018,6 @@
"tokenDecimalFetchFailed": {
"message": "Se requieren los decimales del token."
},
- "tokenDetectionAnnouncement": {
- "message": "¡Nuevo! La detección de tokens mejorada está disponible en la Mainnet de Ethereum como funcionalidad experimental. $1"
- },
"tokenId": {
"message": "ID del token"
},
@@ -3236,12 +3230,6 @@
"usePhishingDetectionDescription": {
"message": "Mostrar una advertencia respecto de los dominios de phishing dirigidos a los usuarios de Ethereum"
},
- "useTokenDetection": {
- "message": "Usar detección de token"
- },
- "useTokenDetectionDescription": {
- "message": "Utilizamos API de terceros para detectar y mostrar nuevos tokens enviados a su cartera. Desactive si no desea que MetaMask extraiga datos de esos servicios."
- },
"usedByClients": {
"message": "Usado por una variedad de clientes distintos"
},
diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json
index 9d2367dae..5befbfd05 100644
--- a/app/_locales/fr/messages.json
+++ b/app/_locales/fr/messages.json
@@ -9,19 +9,19 @@
"message": "Il n’y a plus de comptes. Si vous souhaitez accéder à un autre compte non répertorié ci-dessous, veuillez reconnecter votre portefeuille matériel et le sélectionner."
},
"QRHardwareScanInstructions": {
- "message": "Placez le code QR devant votre caméra. L’écran est flou, mais cela n’affectera pas la lecture."
+ "message": "Placez le code QR devant votre caméra. L’écran est flou, mais cela n’affectera pas la lecture."
},
"QRHardwareSignRequestCancel": {
"message": "Rejeter"
},
"QRHardwareSignRequestDescription": {
- "message": "Après avoir signé avec votre portefeuille, cliquez sur « Obtenir la signature » pour recevoir la signature"
+ "message": "Après avoir signé avec votre portefeuille, cliquez sur « Obtenir la signature » pour recevoir la signature"
},
"QRHardwareSignRequestGetSignature": {
"message": "Obtenir la signature"
},
"QRHardwareSignRequestSubtitle": {
- "message": "Veuillez scanner le code QR avec votre portefeuille"
+ "message": "Veuillez scanner le code QR avec votre portefeuille"
},
"QRHardwareSignRequestTitle": {
"message": "Demander la signature"
@@ -30,16 +30,16 @@
"message": "Erreur"
},
"QRHardwareUnknownWalletQRCode": {
- "message": "Code QR invalide. Scannez le code QR de synchronisation du portefeuille matériel."
+ "message": "Code QR invalide. Scannez le code QR de synchronisation du portefeuille matériel."
},
"QRHardwareWalletImporterTitle": {
"message": "Scannez le code QR"
},
"QRHardwareWalletSteps1Description": {
- "message": "Connectez un portefeuille matériel sécurisé par air gap qui communique via des codes QR. Les portefeuilles de ce type officiellement pris en charge sont les suivants :"
+ "message": "Connectez un portefeuille matériel sécurisé par air gap qui communique via des codes QR. Les portefeuilles de ce type officiellement pris en charge sont les suivants :"
},
"QRHardwareWalletSteps1Title": {
- "message": "Portefeuille matériel basé avec code QR"
+ "message": "Portefeuille matériel basé avec code QR"
},
"QRHardwareWalletSteps2Description": {
"message": "AirGap Vault et Ngrave (bientôt disponible)"
@@ -128,7 +128,7 @@
"message": "Ajouter un jeton personnalisé"
},
"addCustomTokenByContractAddress": {
- "message": "Vous ne trouvez pas de jeton ? Vous pouvez ajouter manuellement n’importe quel jeton avec son adresse par copier-coller. Les adresses des contrats de jetons sont disponibles sur $1.",
+ "message": "Vous ne trouvez pas de jeton ? Vous pouvez ajouter manuellement n’importe quel jeton avec son adresse par copier-coller. Les adresses des contrats de jetons sont disponibles sur $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
@@ -146,7 +146,7 @@
"description": "Link text for the 'addEthereumChainConfirmationRisksLearnMore' translation key"
},
"addEthereumChainConfirmationTitle": {
- "message": "Autoriser ce site à ajouter un réseau ?"
+ "message": "Autoriser ce site à ajouter un réseau ?"
},
"addFriendsAndAddresses": {
"message": "Ajoutez uniquement des amis et des adresses de confiance"
@@ -157,6 +157,9 @@
"addMemo": {
"message": "Ajouter un mémo"
},
+ "addMoreNetworks": {
+ "message": "ajouter manuellement d’autres réseaux"
+ },
"addNetwork": {
"message": "Ajouter un réseau"
},
@@ -180,10 +183,10 @@
"message": "Paramètres avancés"
},
"advancedBaseGasFeeToolTip": {
- "message": "Lorsque votre transaction est intégrée au bloc, toute différence entre vos frais de base maximaux et les frais de base réels vous sera remboursée. Le montant total est calculé comme suit : frais de base maximaux (en GWEI) × limite de carburant."
+ "message": "Lorsque votre transaction est intégrée au bloc, toute différence entre vos frais de base maximaux et les frais de base réels vous sera remboursée. Le montant total est calculé comme suit : frais de base maximaux (en GWEI) × limite de carburant."
},
"advancedGasFeeDefaultOptIn": {
- "message": "Enregistrer ces $1 comme valeur par défaut pour « Avancé »"
+ "message": "Enregistrer ces $1 comme valeur par défaut pour « Avancé »"
},
"advancedGasFeeDefaultOptOut": {
"message": "Toujours utiliser par défaut ces valeurs et les paramètres avancés."
@@ -198,7 +201,7 @@
"message": "Options avancées"
},
"advancedPriorityFeeToolTip": {
- "message": "Les frais de priorité (aussi appelés « pourboire du mineur ») vont directement aux mineurs et les incitent à accorder la priorité à votre transaction."
+ "message": "Les frais de priorité (aussi appelés « pourboire du mineur ») vont directement aux mineurs et les incitent à accorder la priorité à votre transaction."
},
"affirmAgree": {
"message": "Je suis d’accord"
@@ -210,35 +213,39 @@
"message": " (Tutoriels)"
},
"alertDisableTooltip": {
- "message": "Vous pouvez modifier ceci dans « Paramètres > Alertes »"
+ "message": "Vous pouvez modifier ceci dans « Paramètres > Alertes »"
},
"alertSettingsUnconnectedAccount": {
"message": "Navigation sur un site Web avec un compte non connecté sélectionné"
},
"alertSettingsUnconnectedAccountDescription": {
- "message": "Cette alerte s’affiche dans le pop-up lorsque vous naviguez sur un site web3 connecté, mais que le compte actuellement sélectionné n’est pas connecté."
+ "message": "Cette alerte s’affiche dans le pop-up lorsque vous naviguez sur un site web3 connecté, mais que le compte actuellement sélectionné n’est pas connecté."
},
"alertSettingsWeb3ShimUsage": {
- "message": "Lorsqu’un site Web tente d’utiliser l’API window.web3 supprimée"
+ "message": "Lorsqu’un site Web tente d’utiliser l’API window.web3 supprimée"
},
"alertSettingsWeb3ShimUsageDescription": {
- "message": "Cette alerte s’affiche dans le pop-up lorsque vous naviguez sur un site qui tente d’utiliser l’API window.web3 supprimée, et qui peut par conséquent être défaillant."
+ "message": "Cette alerte s’affiche dans le pop-up lorsque vous naviguez sur un site qui tente d’utiliser l’API window.web3 supprimée, et qui peut par conséquent être défaillant."
},
"alerts": {
"message": "Alertes"
},
+ "allOfYour": {
+ "message": "Tous vos $1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
- "message": "Autoriser cette extension externe à :"
+ "message": "Autoriser cette extension externe à :"
},
"allowSpendToken": {
- "message": "Donner l’autorisation d’accéder à votre $1 ?",
+ "message": "Donner l’autorisation d’accéder à votre $1 ?",
"description": "$1 is the symbol of the token that are requesting to spend"
},
"allowThisSiteTo": {
- "message": "Autoriser ce site à :"
+ "message": "Autoriser ce site à :"
},
"allowWithdrawAndSpend": {
- "message": "Permettre à $1 de retirer et de dépenser jusqu’au montant suivant :",
+ "message": "Permettre à $1 de retirer et de dépenser jusqu’au montant suivant :",
"description": "The url of the site that requested permission to 'withdraw and spend'"
},
"amount": {
@@ -263,6 +270,10 @@
"approve": {
"message": "Approuver"
},
+ "approveAllTokensTitle": {
+ "message": "Donner l’autorisation d’accéder à tous vos $1 ?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "Approuver et installer"
},
@@ -277,16 +288,13 @@
"message": "Approuvé"
},
"approvedAmountWithColon": {
- "message": "Montant approuvé :"
+ "message": "Montant approuvé :"
},
"approvedAsset": {
"message": "Actif approuvé"
},
- "areYouDeveloper": {
- "message": "Êtes-vous un(e) développeur(-se) ?"
- },
"areYouSure": {
- "message": "En êtes-vous sûr(e) ?"
+ "message": "En êtes-vous sûr(e) ?"
},
"asset": {
"message": "Actif"
@@ -424,21 +432,21 @@
"description": "$1 represents the cypto symbol to be purchased"
},
"buyCryptoWithMoonPayDescription": {
- "message": "MoonPay prend en charge les moyens de paiement populaires, incluant Visa, Mastercard, Apple / Google / Samsung Pay et les virements bancaires dans plus de 145 pays. Les tokens sont déposés sur votre compte MetaMask."
+ "message": "MoonPay prend en charge les moyens de paiement populaires, incluant Visa, Mastercard, Apple / Google / Samsung Pay et les virements bancaires dans plus de 145 pays. Les tokens sont déposés sur votre compte MetaMask."
},
"buyCryptoWithTransak": {
"message": "Acheter $1 avec Transak",
"description": "$1 represents the cypto symbol to be purchased"
},
"buyCryptoWithTransakDescription": {
- "message": "Transak prend en charge les cartes de crédit et de débit, Apple Pay, MobiKwik et les virements bancaires (selon l’emplacement) dans plus de 100 pays. Les tokens $1 sont directement déposés sur votre compte MetaMask.",
+ "message": "Transak prend en charge les cartes de crédit et de débit, Apple Pay, MobiKwik et les virements bancaires (selon l’emplacement) dans plus de 100 pays. Les tokens $1 sont directement déposés sur votre compte MetaMask.",
"description": "$1 represents the crypto symbol to be purchased"
},
"buyWithWyre": {
"message": "Acheter des $1 avec Wyre"
},
"buyWithWyreDescription": {
- "message": "Wyre vous permet d’utiliser une carte de crédit pour déposer des $1 directement sur votre compte MetaMask."
+ "message": "Intégration facile pour les achats à hauteur de 1000 $. Vérification interactive et rapide des achats pour les comptes qui bénéficient d’un plafond de paiement élevé. Prise en charge des cartes de débit/crédit, d’Apple Pay et des virements bancaires. Disponible dans plus de 100 pays. Dépôt de jetons sur votre compte MetaMask."
},
"bytes": {
"message": "Octets"
@@ -463,9 +471,16 @@
"description": "$1 is text 'replace' in bold"
},
"cancelSpeedUpTransactionTooltip": {
- "message": "Pour $1 la transaction, les gas fees doivent être augmentés d’au moins 10 % pour être reconnus par le réseau.",
+ "message": "Pour $1 la transaction, les gas fees doivent être augmentés d’au moins 10 % pour être reconnus par le réseau.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "Annuler le swap pour ~$1",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "Annuler le swap gratuitement"
+ },
"cancellationGasFee": {
"message": "Frais d’annulation de carburant"
},
@@ -521,7 +536,7 @@
"message": "Confirmé"
},
"confusableUnicode": {
- "message": "« $1 » est similaire à « $2 »."
+ "message": "« $1 » est similaire à « $2 »."
},
"confusableZeroWidthUnicode": {
"message": "Caractère de largeur nulle trouvé."
@@ -561,21 +576,21 @@
"description": "$1 will be replaced by the translation of connectToMultipleNumberOfAccounts"
},
"connectToMultipleNumberOfAccounts": {
- "message": "$1 comptes",
+ "message": "$1 comptes",
"description": "$1 is the number of accounts to which the web3 site/application is asking to connect; this will substitute $1 in connectToMultiple"
},
"connectWithMetaMask": {
"message": "Connectez-vous avec MetaMask"
},
"connectedAccountsDescriptionPlural": {
- "message": "Vous avez $1 comptes connectés à ce site.",
+ "message": "Vous avez $1 comptes connectés à ce site.",
"description": "$1 is the number of accounts"
},
"connectedAccountsDescriptionSingular": {
- "message": "Vous avez 1 compte connecté à ce site."
+ "message": "Vous avez 1 compte connecté à ce site."
},
"connectedAccountsEmptyDescription": {
- "message": "MetaMask n’est pas connecté à ce site. Pour vous connecter à un site web3, cliquez sur le bouton de connexion."
+ "message": "MetaMask n’est pas connecté à ce site. Pour vous connecter à un site web3, cliquez sur le bouton de connexion."
},
"connectedSites": {
"message": "Sites connectés"
@@ -589,7 +604,7 @@
"description": "$1 is the account name"
},
"connectedSnapSites": {
- "message": "Le snap $1 est connecté à ces sites. Ils ont accès aux autorisations énumérées ci-dessus.",
+ "message": "Le snap $1 est connecté à ces sites. Ils ont accès aux autorisations énumérées ci-dessus.",
"description": "$1 represents the name of the snap"
},
"connecting": {
@@ -650,10 +665,10 @@
"message": "Interaction avec un contrat"
},
"convertTokenToNFTDescription": {
- "message": "Nous avons détecté que cet actif est un NFT. MetaMask prend désormais nativement en charge les NFT. Voulez-vous le retirer de votre liste de tokens et l’ajouter comme un NFT ?"
+ "message": "Nous avons détecté que cet actif est un NFT. MetaMask prend désormais nativement en charge les NFT. Voulez-vous le retirer de votre liste de jetons et l’ajouter en tant que NFT ?"
},
"convertTokenToNFTExistDescription": {
- "message": "Nous avons détecté que cet actif a été ajouté en tant que NFT. Souhaitez-vous le retirer de votre liste de tokens ?"
+ "message": "Nous avons détecté que cet actif a été ajouté en tant que NFT. Souhaitez-vous le retirer de votre liste de tokens ?"
},
"copiedExclamation": {
"message": "Copié!"
@@ -707,7 +722,7 @@
"message": "Langue actuelle"
},
"currentTitle": {
- "message": "Actuel :"
+ "message": "Actuel :"
},
"currentlyUnavailable": {
"message": "Indisponible sur ce réseau"
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "Utilisez $1 pour personnaliser le prix du carburant. Cela peut porter à confusion si vous n’en avez pas l’habitude. Agissez avec prudence !",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Augmenter le tarif peut faire baisser le temps de traitement, mais cela n’est pas garanti."
@@ -773,7 +788,7 @@
"message": "Données"
},
"dataBackupFoundInfo": {
- "message": "Certaines données de votre compte ont été sauvegardées lors d’une précédente installation de MetaMask. Il peut s’agir de vos paramètres, contacts et jetons. Souhaitez-vous restaurer ces données maintenant ?"
+ "message": "Certaines données de votre compte ont été sauvegardées lors d’une précédente installation de MetaMask. Il peut s’agir de vos paramètres, contacts et jetons. Souhaitez-vous restaurer ces données maintenant ?"
},
"dataHex": {
"message": "Hex"
@@ -791,7 +806,7 @@
"message": "Copier le message crypté"
},
"decryptInlineError": {
- "message": "Ce message ne peut pas être décrypté à la suite d’une erreur : $1",
+ "message": "Ce message ne peut pas être décrypté à la suite d’une erreur : $1",
"description": "$1 is error message"
},
"decryptMessageNotice": {
@@ -811,14 +826,14 @@
"message": "Supprimer le compte"
},
"deleteNetwork": {
- "message": "Supprimer le réseau ?"
+ "message": "Supprimer le réseau ?"
},
"deleteNetworkDescription": {
"message": "Souhaitez-vous vraiment supprimer ce réseau ?"
},
"depositCrypto": {
"message": "Effectuer un dépôt de $1",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Description"
@@ -833,7 +848,7 @@
"message": "Si vous avez déjà un peu de $1, la façon la plus rapide d’obtenir $1 dans votre nouveau portefeuille est par dépôt direct."
},
"disabledGasOptionToolTipMessage": {
- "message": "« $1 » est désactivé parce qu’il ne correspond pas au minimum d’augmentation de 10 % par rapport aux gas fees initiaux.",
+ "message": "« $1 » est désactivé parce qu’il ne correspond pas au minimum d’augmentation de 10 % par rapport aux gas fees initiaux.",
"description": "$1 is gas estimate type which can be market or aggressive"
},
"disconnect": {
@@ -843,7 +858,7 @@
"message": "Déconnecter tous les comptes"
},
"disconnectAllAccountsConfirmationDescription": {
- "message": "Souhaitez-vous vraiment vous déconnecter ? Vous risquez de perdre certaines fonctionnalités du site."
+ "message": "Souhaitez-vous vraiment vous déconnecter ? Vous risquez de perdre certaines fonctionnalités du site."
},
"disconnectPrompt": {
"message": "Déconnecter $1"
@@ -900,7 +915,7 @@
"message": "Modifier le contact"
},
"editGasEducationButtonText": {
- "message": "Comment puis-je choisir ?"
+ "message": "Comment puis-je choisir ?"
},
"editGasEducationHighExplanation": {
"message": "Ce choix est préférable pour les transactions urgentes (comme les swaps), car cela multiplie les chances de réussite de la transaction. Si le traitement d’un swap prend trop de temps, il peut échouer et vous faire perdre une partie de vos frais de carburant."
@@ -915,7 +930,7 @@
"message": "Le choix du bon prix de carburant dépend du type de transaction et de son importance à vos yeux."
},
"editGasEducationModalTitle": {
- "message": "Comment choisir ?"
+ "message": "Comment choisir ?"
},
"editGasFeeModalTitle": {
"message": "Modifier le prix du carburant"
@@ -931,7 +946,7 @@
"description": "$1 is the minimum limit for gas and $2 is the maximum limit"
},
"editGasLimitTooltip": {
- "message": "La limite de carburant correspond au maximum d’unités de carburant que vous consentez à utiliser. Celles-ci servent de multiplicateur aux « Frais de priorité maximaux » et aux « Frais maximaux »."
+ "message": "La limite de carburant correspond au maximum d’unités de carburant que vous consentez à utiliser. Celles-ci servent de multiplicateur aux « Frais de priorité maximaux » et aux « Frais maximaux »."
},
"editGasLow": {
"message": "Bas"
@@ -958,7 +973,7 @@
"message": "Les frais maximaux correspondent au montant le plus élevé que vous aurez à payer (frais de base + frais de priorité)."
},
"editGasMaxPriorityFeeBelowMinimum": {
- "message": "Les frais de priorité maximaux doivent être supérieurs à 0 GWEI"
+ "message": "Les frais de priorité maximaux doivent être supérieurs à 0 GWEI"
},
"editGasMaxPriorityFeeBelowMinimumV2": {
"message": "Les frais de priorité doivent être supérieurs à 0."
@@ -976,7 +991,7 @@
"message": "Les frais de priorité sont faibles par rapport aux conditions actuelles du réseau"
},
"editGasMaxPriorityFeeTooltip": {
- "message": "Les frais de priorité maximaux (aussi appelés « pourboire du mineur ») vont directement aux mineurs et les incitent à accorder la priorité à votre transaction. Vous paierez le plus souvent votre réglage maximal"
+ "message": "Les frais de priorité maximaux (aussi appelés « pourboire du mineur ») vont directement aux mineurs et les incitent à accorder la priorité à votre transaction. Vous paierez le plus souvent votre réglage maximal"
},
"editGasMedium": {
"message": "Moyen"
@@ -985,14 +1000,14 @@
"message": "Les frais de carburant doivent être supérieurs à 0"
},
"editGasPriceTooltip": {
- "message": "Ce réseau exige un champ « Prix du carburant » lors de la soumission d’une transaction. Le prix du carburant correspond au montant que vous paierez par unité de carburant."
+ "message": "Ce réseau exige un champ « Prix du carburant » lors de la soumission d’une transaction. Le prix du carburant correspond au montant que vous paierez par unité de carburant."
},
"editGasSubTextAmountLabel": {
- "message": "Montant maximal :",
+ "message": "Montant maximal :",
"description": "This is meant to be used as the $1 substitution editGasSubTextAmount"
},
"editGasSubTextFeeLabel": {
- "message": "Frais maximaux :"
+ "message": "Frais maximaux :"
},
"editGasTitle": {
"message": "Modifier la priorité"
@@ -1092,7 +1107,7 @@
"message": "En savoir plus."
},
"endpointReturnedDifferentChainId": {
- "message": "Le point terminal a renvoyé un ID de chaîne différent : $1",
+ "message": "Le point terminal a renvoyé un ID de chaîne différent : $1",
"description": "$1 is the return value of eth_chainId from an RPC endpoint"
},
"ensIllegalCharacter": {
@@ -1120,7 +1135,7 @@
"message": "Entrez votre mot de passe pour continuer"
},
"errorCode": {
- "message": "Code : $1",
+ "message": "Code : $1",
"description": "Displayed error code for debugging purposes. $1 is the error code"
},
"errorDetails": {
@@ -1128,11 +1143,11 @@
"description": "Title for collapsible section that displays error details for debugging purposes"
},
"errorMessage": {
- "message": "Message : $1",
+ "message": "Message : $1",
"description": "Displayed error message for debugging purposes. $1 is the error message"
},
"errorName": {
- "message": "Code : $1",
+ "message": "Code : $1",
"description": "Displayed error name for debugging purposes. $1 is the error name"
},
"errorPageMessage": {
@@ -1148,7 +1163,7 @@
"description": "Title of generic error page"
},
"errorStack": {
- "message": "Stack :",
+ "message": "Stack :",
"description": "Title for error stack, which is displayed for debugging purposes"
},
"estimatedProcessingTimes": {
@@ -1188,7 +1203,7 @@
"message": "Échec"
},
"failedToFetchChainId": {
- "message": "Impossible de récupérer l’ID de la chaîne. Votre URL de RPC est-elle correcte ?"
+ "message": "Impossible de récupérer l’ID de la chaîne. Votre URL de RPC est-elle correcte ?"
},
"failedToFetchTickerSymbolData": {
"message": "Les données de vérification de code mnémonique sont actuellement indisponibles. Assurez-vous que le code que vous avez saisi est correct. Il aura une incidence sur les taux de conversion que vous voyez pour ce réseau"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "Un problème est survenu et nous n’avons pas pu mener à bien l’action"
},
- "fakeTokenWarning": {
- "message": "Tout un chacun peut créer un jeton, y compris créer de fausses copies de jetons existants. En savoir plus sur $1"
- },
"fast": {
"message": "Rapide"
},
@@ -1213,7 +1225,7 @@
"description": "Exchange type"
},
"fileImportFail": {
- "message": "L’importation de fichier ne fonctionne pas ? Cliquez ici !",
+ "message": "L’importation de fichier ne fonctionne pas ? Cliquez ici !",
"description": "Helps user import their account from a JSON file"
},
"flaskSnapSettingsCardButtonCta": {
@@ -1256,26 +1268,29 @@
"message": "Suivez-nous sur Twitter"
},
"forbiddenIpfsGateway": {
- "message": "Passerelle IPFS interdite : veuillez spécifier une passerelle CID"
+ "message": "Passerelle IPFS interdite : veuillez spécifier une passerelle CID"
},
"forgetDevice": {
"message": "Oublier cet appareil"
},
"forgotPassword": {
- "message": "Mot de passe oublié ?"
+ "message": "Mot de passe oublié ?"
},
"from": {
"message": "de"
},
"fromAddress": {
- "message": "De : $1",
+ "message": "De : $1",
"description": "$1 is the address to include in the From label. It is typically shortened first using shortenAddress"
},
"fromTokenLists": {
- "message": "À partir des listes de tokens : $1"
+ "message": "À partir des listes de tokens : $1"
},
"functionApprove": {
- "message": "Fonction : approuver"
+ "message": "Fonction : approuver"
+ },
+ "functionSetApprovalForAll": {
+ "message": "Fonction : SetApprovalForAll"
},
"functionType": {
"message": "Type de fonction"
@@ -1337,15 +1352,15 @@
"message": "Le prix du carburant indique la quantité d’Ether que vous acceptez de payer pour chaque unité de carburant."
},
"gasTimingHoursShort": {
- "message": "$1 h",
+ "message": "$1 h",
"description": "$1 represents a number of hours"
},
"gasTimingMinutes": {
- "message": "$1 minutes",
+ "message": "$1 minutes",
"description": "$1 represents a number of minutes"
},
"gasTimingMinutesShort": {
- "message": "$1 min",
+ "message": "$1 min",
"description": "$1 represents a number of minutes"
},
"gasTimingNegative": {
@@ -1357,11 +1372,11 @@
"description": "$1 represents an amount of time"
},
"gasTimingSeconds": {
- "message": "$1 secondes",
+ "message": "$1 secondes",
"description": "$1 represents a number of seconds"
},
"gasTimingSecondsShort": {
- "message": "$1 s",
+ "message": "$1 s",
"description": "$1 represents a number of seconds"
},
"gasTimingVeryPositive": {
@@ -1372,7 +1387,7 @@
"message": "Carburant utilisé"
},
"gdprMessage": {
- "message": "Ces données sont agrégées et sont donc anonymes aux fins du règlement général sur la protection des données (UE) 2016/679. Pour plus d’informations concernant nos pratiques en matière de confidentialité, veuillez consulter notre $1.",
+ "message": "Ces données sont agrégées et sont donc anonymes aux fins du règlement général sur la protection des données (UE) 2016/679. Pour plus d’informations concernant nos pratiques en matière de confidentialité, veuillez consulter notre $1.",
"description": "$1 refers to the gdprMessagePrivacyPolicy message, the translation of which is meant to be used exclusively in the context of gdprMessage"
},
"gdprMessagePrivacyPolicy": {
@@ -1399,10 +1414,10 @@
"message": "Réseau de test Goerli"
},
"gotIt": {
- "message": "J’ai compris !"
+ "message": "J'ai compris !"
},
"grantedToWithColon": {
- "message": "Accordé à :"
+ "message": "Accordé à :"
},
"gwei": {
"message": "GWEI"
@@ -1449,7 +1464,7 @@
"message": "Masquer le token"
},
"hideTokenPrompt": {
- "message": "Masquer le jeton ?"
+ "message": "Masquer le jeton ?"
},
"hideTokenSymbol": {
"message": "Masquer $1",
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "Utilisez $1 pour couvrir les envolées du trafic réseau dues à des événements tels que les chutes de NFT populaires.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "élevé"
@@ -1500,22 +1515,22 @@
"message": "Importer le NFT"
},
"importNFTAddressToolTip": {
- "message": "Sur OpenSea, par exemple, sur la page des NFT, dans la section Détails, se trouve une valeur avec un hyperlien bleu intitulée « Adresse de contrat ». Si vous cliquez dessus, vous serez redirigé vers l’adresse du contrat sur Etherscan. En haut à gauche de cette page, il devrait y avoir une icône intitulée « Contrat » et à droite, une longue chaîne de lettres et de chiffres. C’est l’adresse du contrat qui a créé votre NFT. Cliquez sur l’icône « Copier » à droite de l’adresse pour la copier dans votre presse-papiers."
+ "message": "Sur OpenSea, par exemple, sur la page des NFT, dans la section Détails, se trouve une valeur avec un hyperlien bleu intitulée « Adresse de contrat ». Si vous cliquez dessus, vous serez redirigé vers l’adresse du contrat sur Etherscan. En haut à gauche de cette page, il devrait y avoir une icône intitulée « Contrat » et à droite, une longue chaîne de lettres et de chiffres. C’est l’adresse du contrat qui a créé votre NFT. Cliquez sur l’icône « Copier » à droite de l’adresse pour la copier dans votre presse-papiers."
},
"importNFTPage": {
"message": "page Importer des NFT"
},
"importNFTTokenIdToolTip": {
- "message": "L’ID d’un collectible est un identifiant unique puisqu’il n’y a pas deux NFT identiques. Encore une fois, sur OpenSea, ce numéro se trouve dans la section « Détails ». Prenez-en note ou copiez-le dans votre presse-papiers."
+ "message": "L’ID d’un collectible est un identifiant unique puisqu’il n’y a pas deux NFT identiques. Encore une fois, sur OpenSea, ce numéro se trouve dans la section « Détails ». Prenez-en note ou copiez-le dans votre presse-papiers."
},
"importNFTs": {
"message": "Importer des NFT"
},
"importTokenQuestion": {
- "message": "Importer un jeton ?"
+ "message": "Importer un jeton ?"
},
"importTokenWarning": {
- "message": "Tout un chacun peut créer un jeton avec n’importe quel nom, y compris de fausses versions de jetons existants. Ajoutez et échangez avec prudence !"
+ "message": "Tout un chacun peut créer un jeton avec n’importe quel nom, y compris de fausses versions de jetons existants. Ajoutez et échangez avec prudence !"
},
"importTokens": {
"message": "importer des jetons"
@@ -1583,7 +1598,7 @@
"message": "ID de chaîne invalide. L’ID de la chaîne est trop grand."
},
"invalidCustomNetworkAlertContent1": {
- "message": "L’ID de la chaîne pour le réseau personnalisé « $1 » doit être saisi à nouveau.",
+ "message": "L’ID de la chaîne pour le réseau personnalisé « $1 » doit être saisi à nouveau.",
"description": "$1 is the name/identifier of the network."
},
"invalidCustomNetworkAlertContent2": {
@@ -1603,10 +1618,10 @@
"message": "Numéro hexadécimal invalide. Supprimez tous les zéros non significatifs."
},
"invalidIpfsGateway": {
- "message": "Passerelle IPFS invalide : la valeur doit être une URL valide"
+ "message": "Passerelle IPFS invalide : la valeur doit être une URL valide"
},
"invalidNumber": {
- "message": "Numéro invalide. Saisissez un nombre décimal ou hexadécimal avec le préfixe « 0x »."
+ "message": "Numéro invalide. Saisissez un nombre décimal ou hexadécimal avec le préfixe « 0x »."
},
"invalidNumberLeadingZeros": {
"message": "Numéro invalide. Supprimez tous les zéros en tête."
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "Phrase secrète de récupération invalide"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "Entrée invalide ! La phrase secrète de récupération est sensible à la casse."
+ },
"ipfsGateway": {
"message": "Passerelle IPFS"
},
@@ -1655,7 +1673,7 @@
"message": "Dernière connexion"
},
"learmMoreAboutGas": {
- "message": "Souhaitez-vous $1 sur le carburant ?"
+ "message": "Souhaitez-vous $1 sur le carburant ?"
},
"learnCancelSpeeedup": {
"message": "Découvrir comment $1",
@@ -1677,13 +1695,13 @@
"message": "Fermez tout autre logiciel connecté à votre appareil, puis cliquez ici pour actualiser."
},
"ledgerConnectionInstructionHeader": {
- "message": "Avant de cliquer sur confirmer :"
+ "message": "Avant de cliquer sur confirmer :"
},
"ledgerConnectionInstructionStepFour": {
- "message": "Activez les « données de contrat intelligent » ou la « signature aveugle » sur votre dispositif Ledger"
+ "message": "Activez les « données de contrat intelligent » ou la « signature aveugle » sur votre dispositif Ledger"
},
"ledgerConnectionInstructionStepOne": {
- "message": "Activez l’option « Utiliser Ledger Live » sous Paramètres > Avancés"
+ "message": "Activez l’option « Utiliser Ledger Live » sous Paramètres > Avancés"
},
"ledgerConnectionInstructionStepThree": {
"message": "Branchez votre dispositif Ledger et sélectionnez l’application Ethereum"
@@ -1692,7 +1710,7 @@
"message": "Ouvrez et déverrouillez l’application Ledger Live"
},
"ledgerConnectionPreferenceDescription": {
- "message": "Personnalisez la façon dont vous souhaitez connecter votre Ledger à MetaMask. $1 est recommandé, mais d’autres options sont disponibles. En savoir plus ici : $2",
+ "message": "Personnalisez la façon dont vous souhaitez connecter votre Ledger à MetaMask. $1 est recommandé, mais d’autres options sont disponibles. En savoir plus ici : $2",
"description": "A description that appears above a dropdown where users can select between up to three options - Ledger Live, U2F or WebHID - depending on what is supported in their browser. $1 is the recommended browser option, it will be either WebHID or U2f. $2 is a link to an article where users can learn more, but will be the translation of the learnMore message."
},
"ledgerDeviceOpenFailureMessage": {
@@ -1715,11 +1733,11 @@
"message": "Si l’application Ledger Live est ouverte, désactivez toute connexion en cours à celle-ci et fermez-la."
},
"ledgerWebHIDNotConnectedErrorMessage": {
- "message": "Le dispositif Ledger n’est pas connecté. Si vous souhaitez le connecter, veuillez cliquer à nouveau sur « Continuer » et approuver la connexion au HID",
+ "message": "Le dispositif Ledger n’est pas connecté. Si vous souhaitez le connecter, veuillez cliquer à nouveau sur « Continuer » et approuver la connexion au HID",
"description": "An error message shown to the user during the hardware connect flow."
},
"letsGoSetUp": {
- "message": "Oui, passons à la configuration !"
+ "message": "Oui, passons à la configuration !"
},
"levelArrow": {
"message": "flèche de niveau"
@@ -1749,7 +1767,7 @@
"message": "Chargement des jetons..."
},
"localhost": {
- "message": "Localhost 8545"
+ "message": "Localhost 8545"
},
"lock": {
"message": "Déconnexion"
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Utilisez $1 pour attendre un prix inférieur. Les estimations de temps sont nettement moins précises, car les prix sont relativement imprévisibles.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "bas"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Utilisez $1 pour un traitement rapide au prix actuel du marché.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "note"
@@ -1850,7 +1868,7 @@
"description": "This string is localized separately from some of the commitments so that we can bold it"
},
"metametricsCommitmentsIntro": {
- "message": "MetaMask :"
+ "message": "MetaMask :"
},
"metametricsCommitmentsNeverCollect": {
"message": "Ne collectera jamais vos clés, adresses, transactions, soldes, hachages ou toute autre information personnelle"
@@ -1867,10 +1885,10 @@
"message": "Ne collectera jamais votre adresse IP complète"
},
"metametricsCommitmentsNeverSell": {
- "message": "Ne vendra jamais de données à des fins lucratives. Jamais !"
+ "message": "Ne vendra jamais de données à des fins lucratives. Jamais !"
},
"metametricsCommitmentsNeverSellDataForProfit": {
- "message": "Ne vendra $1 de données à des fins lucratives. Jamais !",
+ "message": "Ne vendra $1 de données à des fins lucratives. Jamais !",
"description": "The $1 is the bolded word 'Never', from 'metametricsCommitmentsBoldNever'"
},
"metametricsCommitmentsSendAnonymizedEvents": {
@@ -1886,14 +1904,27 @@
"message": "Nous aimerions recueillir des données d’utilisation de base pour améliorer la convivialité de notre produit. Ces indicateurs seront…"
},
"metametricsTitle": {
- "message": "Rejoignez plus de 6 M d’utilisateurs pour améliorer MetaMask"
+ "message": "Rejoignez plus de 6 M d’utilisateurs pour améliorer MetaMask"
},
"mismatchedChainLinkText": {
"message": "vérifier les détails du réseau",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "Nous vous recommandons de $1 avant de continuer.",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "Selon nos informations, le nom du réseau peut ne pas correspondre exactement à l'ID de la chaîne."
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "Le symbole monétaire soumis ne correspond à cet ID de chaîne."
+ },
+ "mismatchedRpcUrl": {
+ "message": "Selon nos informations, la valeur de l'URL RPC soumise ne correspond pas à un fournisseur connu pour cet ID de chaîne."
+ },
"missingNFT": {
- "message": "Vous ne voyez pas votre NFT ?"
+ "message": "Vous ne voyez pas votre NFT ?"
},
"missingSetting": {
"message": "Vous ne trouvez pas un paramètre ?"
@@ -1902,10 +1933,10 @@
"message": "Demandez ici"
},
"missingToken": {
- "message": "Vous ne voyez pas votre jeton ?"
+ "message": "Vous ne voyez pas votre jeton ?"
},
"mobileSyncWarning": {
- "message": "La fonction « Synchronisation avec l’extension » est temporairement désactivée. Si vous souhaitez utiliser votre portefeuille d’extension sur MetaMask mobile : sur votre application mobile, revenez aux options de configuration du portefeuille et sélectionnez l’option « Importation avec la phrase secrète de récupération ». Utilisez la phrase secrète de votre portefeuille d’extension pour importer celui-ci sur votre mobile."
+ "message": "La fonction « Synchronisation avec l’extension » est temporairement désactivée. Si vous souhaitez utiliser votre portefeuille d’extension sur MetaMask mobile : sur votre application mobile, revenez aux options de configuration du portefeuille et sélectionnez l’option « Importation avec la phrase secrète de récupération ». Utilisez la phrase secrète de votre portefeuille d’extension pour importer celui-ci sur votre mobile."
},
"mustSelectOne": {
"message": "Vous devez sélectionner au moins 1 jeton."
@@ -1921,7 +1952,7 @@
"description": "$1 represents the cypto symbol to be purchased"
},
"needHelp": {
- "message": "Vous avez besoin d’aide ? Contactez $1",
+ "message": "Vous avez besoin d’aide ? Contactez $1",
"description": "$1 represents `needHelpLinkText`, the text which goes in the help link"
},
"needHelpFeedback": {
@@ -1941,7 +1972,10 @@
"message": "Vous ne pouvez envoyer des montants négatifs d’ETH."
},
"network": {
- "message": "Réseau :"
+ "message": "Réseau :"
+ },
+ "networkAddedSuccessfully": {
+ "message": "Réseau ajouté avec succès !"
},
"networkDetails": {
"message": "Détails du réseau"
@@ -1974,20 +2008,20 @@
"message": "Testnet"
},
"networkSettingsChainIdDescription": {
- "message": "L’ID de la chaîne est utilisé pour la signature des transactions. Il doit correspondre à l’ID de la chaîne renvoyé par le réseau. Vous pouvez saisir un numéro décimal ou hexadécimal avec le préfixe « 0x », mais nous afficherons le numéro en décimal."
+ "message": "L’ID de la chaîne est utilisé pour la signature des transactions. Il doit correspondre à l’ID de la chaîne renvoyé par le réseau. Vous pouvez saisir un numéro décimal ou hexadécimal avec le préfixe « 0x », mais nous afficherons le numéro en décimal."
},
"networkStatus": {
"message": "Statut du réseau"
},
"networkStatusBaseFeeTooltip": {
- "message": "Les frais de base sont fixés par le réseau et varient toutes les 13-14 secondes. Nos options $1 et $2 tiennent compte des augmentations soudaines.",
+ "message": "Les frais de base sont fixés par le réseau et varient toutes les 13-14 secondes. Nos options $1 et $2 tiennent compte des augmentations soudaines.",
"description": "$1 and $2 are bold text for Medium and Aggressive respectively."
},
"networkStatusPriorityFeeTooltip": {
- "message": "Éventail de frais de priorité (aussi appelés « pourboire du mineur »). Ils sont versés aux mineurs et les incitent à accorder la priorité à votre transaction."
+ "message": "Éventail de frais de priorité (aussi appelés « pourboire du mineur »). Ils sont versés aux mineurs et les incitent à accorder la priorité à votre transaction."
},
"networkStatusStabilityFeeTooltip": {
- "message": "Le prix du carburant est de $1 au regard des 72 dernières heures.",
+ "message": "Le prix du carburant est de $1 au regard des 72 dernières heures.",
"description": "$1 is networks stability value - stable, low, high"
},
"networkURL": {
@@ -2013,7 +2047,7 @@
"description": "Default name of next account to be created on create account screen"
},
"newCollectibleAddedMessage": {
- "message": "Le collectible a été ajouté avec succès !"
+ "message": "Le collectible a été ajouté avec succès !"
},
"newContact": {
"message": "Nouveau contact"
@@ -2025,10 +2059,10 @@
"message": "Cela permet à MetaMask de détecter automatiquement les NFT d’OpenSea et de les afficher dans votre portefeuille."
},
"newNFTsDetected": {
- "message": "Nouveau ! Détection de NFT"
+ "message": "Nouveau ! Détection de NFT"
},
"newNetworkAdded": {
- "message": "« $1 » a été ajouté avec succès !"
+ "message": "« $1 » a été ajouté avec succès !"
},
"newPassword": {
"message": "Nouveau mot de passe (min 8 caractères)"
@@ -2059,6 +2093,9 @@
"message": "Le nonce est supérieur au nonce suggéré de $1",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "Saisissez l’ID du collectible"
},
@@ -2120,7 +2157,7 @@
"message": "Pas occupé"
},
"notCurrentAccount": {
- "message": "S’agit-il du bon compte ? Il est différent de celui actuellement sélectionné dans votre portefeuille"
+ "message": "S’agit-il du bon compte ? Il est différent de celui actuellement sélectionné dans votre portefeuille"
},
"notEnoughGas": {
"message": "Pas assez de carburant"
@@ -2130,10 +2167,10 @@
},
"notifications10ActionText": {
"message": "Ouvrir les paramètres",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
- "message": "Une détection améliorée des tokens est actuellement disponible sur les réseaux Ethereum Mainnet, Polygon, BSC et Avalanche. Il y aura bientôt d’autres nouveautés !"
+ "message": "Une détection améliorée des tokens est actuellement disponible sur les réseaux Ethereum Mainnet, Polygon, BSC et Avalanche. Il y aura bientôt d’autres nouveautés !"
},
"notifications10DescriptionThree": {
"message": "La fonction de détection des tokens est ACTIVÉE par défaut. Mais vous pouvez la désactiver dans les Paramètres."
@@ -2154,17 +2191,27 @@
"message": "Activer le mode sombre"
},
"notifications12Description": {
- "message": "Le mode sombre sera activé pour les nouveaux utilisateurs en fonction de leurs préférences système. Les utilisateurs existants peuvent activer manuellement le mode sombre dans Paramètres -> Expérimental."
+ "message": "Le mode sombre est enfin disponible pour l’extension ! Pour changer le mode d'affichage, allez dans « Paramètres » -> « Expérimental » et sélectionnez l’une des options suivantes : Clair, Sombre, Système."
},
"notifications12Title": {
- "message": "Mode sombre, quand ? Mode sombre maintenant ! 🕶️🦊"
+ "message": "Mode sombre, quand ? Mode sombre maintenant ! 🕶️🦊"
+ },
+ "notifications13ActionText": {
+ "message": "Afficher la liste des réseaux personnalisés"
+ },
+ "notifications13Description": {
+ "message": "Vous pouvez maintenant ajouter facilement les réseaux personnalisés populaires suivants : Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm et Polygon ! Pour activer cette fonctionnalité, allez dans Paramètres -> Expérimental et activez « Afficher la liste des réseaux personnalisés » !",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "Ajouter des réseaux populaires"
},
"notifications1Description": {
"message": "Les utilisateurs de MetaMask Mobile peuvent désormais échanger des jetons dans leur portefeuille mobile. Scannez le code QR pour obtenir l’application mobile et commencez à échanger.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
},
"notifications1Title": {
- "message": "Les swaps sur mobile sont enfin possibles !",
+ "message": "Les swaps sur mobile sont enfin possibles !",
"description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile."
},
"notifications3ActionText": {
@@ -2192,15 +2239,15 @@
"description": "Title for a notification in the 'See What's New' popup. Encourages users to do swaps on Binance Smart Chain."
},
"notifications5Description": {
- "message": "Votre « phrase mnémonique » devient votre « phrase secrète de récupération ».",
+ "message": "Votre « phrase mnémonique » devient votre « phrase secrète de récupération ».",
"description": "Description of a notification in the 'See What's New' popup. Describes the seed phrase wording update."
},
"notifications6DescriptionOne": {
- "message": "Depuis la version 91 de Chrome, l’API qui permettait la prise en charge de notre Ledger (U2F) ne gère plus les portefeuilles matériels. MetaMask a donc mis en place un nouveau système de prise en charge de Ledger Live qui vous permet de continuer à vous connecter à votre appareil Ledger via l’application Ledger Live.",
+ "message": "Depuis la version 91 de Chrome, l’API qui permettait la prise en charge de notre Ledger (U2F) ne gère plus les portefeuilles matériels. MetaMask a donc mis en place un nouveau système de prise en charge de Ledger Live qui vous permet de continuer à vous connecter à votre appareil Ledger via l’application Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionThree": {
- "message": "Lorsque vous interagissez avec votre compte Ledger dans MetaMask, un nouvel onglet s’ouvre et vous invite à ouvrir l’application Ledger Live. Une fois celle-ci lancée, vous devrez autoriser une connexion WebSocket à votre compte MetaMask. Et le tour est joué !",
+ "message": "Lorsque vous interagissez avec votre compte Ledger dans MetaMask, un nouvel onglet s’ouvre et vous invite à ouvrir l’application Ledger Live. Une fois celle-ci lancée, vous devrez autoriser une connexion WebSocket à votre compte MetaMask. Et le tour est joué !",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionTwo": {
@@ -2212,7 +2259,7 @@
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"notifications7DescriptionOne": {
- "message": "MetaMask v10.1.0 inclut une nouvelle prise en charge des transactions EIP-1559 en cas d’utilisation d’appareils Ledger.",
+ "message": "MetaMask v10.1.0 inclut une nouvelle prise en charge des transactions EIP-1559 en cas d’utilisation d’appareils Ledger.",
"description": "Description of a notification in the 'See What's New' popup. Describes changes for ledger and EIP1559 in v10.1.0"
},
"notifications7DescriptionTwo": {
@@ -2225,14 +2272,14 @@
},
"notifications8ActionText": {
"message": "Accédez aux Paramètres avancés",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
- "message": "Depuis MetaMask v10.4.0, vous n’avez plus besoin de Ledger Live pour connecter votre appareil Ledger à MetaMask.",
+ "message": "Depuis MetaMask v10.4.0, vous n’avez plus besoin de Ledger Live pour connecter votre appareil Ledger à MetaMask.",
"description": "Description of a notification in the 'See What's New' popup. Describes changes for how Ledger Live is no longer needed to connect the device."
},
"notifications8DescriptionTwo": {
- "message": "Pour une expérience plus conviviale et plus stable, allez dans l’onglet des paramètres Avancés et changez « Type de connexion Ledger préféré » en « WebHID ».",
+ "message": "Pour une expérience plus conviviale et plus stable, allez dans l’onglet des paramètres Avancés et changez « Type de connexion Ledger préféré » en « WebHID ».",
"description": "Description of a notification in the 'See What's New' popup. Describes how the user can turn off the Ledger Live setting."
},
"notifications8Title": {
@@ -2240,7 +2287,7 @@
"description": "Title for a notification in the 'See What's New' popup. Notifies ledger users that there is an improvement in how they can connect their device."
},
"notifications9DescriptionOne": {
- "message": "Nous vous proposons désormais plus d’informations dans l’onglet « Données » lors de la confirmation des transactions de contrats intelligents."
+ "message": "Nous vous proposons désormais plus d’informations dans l’onglet « Données » lors de la confirmation des transactions de contrats intelligents."
},
"notifications9DescriptionTwo": {
"message": "Vous pouvez à présent mieux cerner les détails de votre transaction avant de la confirmer, et ajouter plus aisément les adresses des transactions à votre carnet d’adresses, ce qui vous aidera à prendre des décisions sûres et éclairées."
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "Marquer tout comme lu"
},
- "numberOfNewTokensDetected": {
- "message": "$1 nouveaux jetons trouvés dans ce compte",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "de"
},
@@ -2320,18 +2363,18 @@
"message": "2"
},
"onboardingPinExtensionTitle": {
- "message": "Votre installation de MetaMask est terminée !"
+ "message": "Votre installation de MetaMask est terminée !"
},
"onboardingReturnNotice": {
- "message": "« $1 » va fermer cet onglet et vous rediriger vers $2",
+ "message": "« $1 » va fermer cet onglet et vous rediriger vers $2",
"description": "Return the user to the site that initiated onboarding"
},
"onboardingShowIncomingTransactionsDescription": {
- "message": "L’affichage des transactions entrantes dans votre portefeuille repose sur la communication avec $1. Etherscan aura accès à votre adresse Ethereum et à votre adresse IP. Voir $2.",
+ "message": "L’affichage des transactions entrantes dans votre portefeuille repose sur la communication avec $1. Etherscan aura accès à votre adresse Ethereum et à votre adresse IP. Voir $2.",
"description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key."
},
"onboardingUsePhishingDetectionDescription": {
- "message": "Les alertes de détection d’hameçonnage reposent sur la communication avec $1. jsDeliver aura accès à votre adresse IP. Voir $2.",
+ "message": "Les alertes de détection d’hameçonnage reposent sur la communication avec $1. jsDeliver aura accès à votre adresse IP. Voir $2.",
"description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link"
},
"onlyAddTrustedNetworks": {
@@ -2344,9 +2387,6 @@
"message": "Ouvrez MetaMask en mode plein écran pour connecter votre Ledger via WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Vérifier le code source"
- },
"optional": {
"message": "Facultatif"
},
@@ -2384,7 +2424,7 @@
"message": "Ce mot de passe permet de déverrouiller votre portefeuille MetaMask uniquement sur cet appareil. MetaMask ne peut pas récupérer ce mot de passe."
},
"passwordStrength": {
- "message": "Robustesse du mot de passe : $1",
+ "message": "Robustesse du mot de passe : $1",
"description": "Return password strength to the user when user wants to create password."
},
"passwordStrengthDescription": {
@@ -2407,10 +2447,10 @@
"message": "Cette transaction ne sera pas traitée tant que la précédente ne sera pas terminée."
},
"pendingTransactionMultiple": {
- "message": "Vous avez ($1) transactions en attente."
+ "message": "Vous avez ($1) transactions en attente."
},
"pendingTransactionSingle": {
- "message": "Vous avez (1) transaction en attente.",
+ "message": "Vous avez (1) transaction en attente.",
"description": "$1 is count of pending transactions"
},
"permissionRequest": {
@@ -2440,7 +2480,7 @@
"description": "The description for the `endowment:long-running` permission"
},
"permission_manageBip44Keys": {
- "message": "Contrôlez vos comptes et actifs « $1 ».",
+ "message": "Contrôlez vos comptes et actifs « $1 ».",
"description": "The description for the `snap_getBip44Entropy_*` permission. $1 is the name of a protocol, e.g. 'Filecoin'."
},
"permission_manageState": {
@@ -2452,7 +2492,7 @@
"description": "The description for the `snap_notify` permission"
},
"permission_unknown": {
- "message": "Autorisation inconnue : $1",
+ "message": "Autorisation inconnue : $1",
"description": "$1 is the name of a requested permission that is not recognized."
},
"permissions": {
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "Type de connexion Ledger préféré",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Préparation du swap..."
@@ -2531,7 +2571,7 @@
"message": "ajouté à nouveau"
},
"readdToken": {
- "message": "Vous pourrez ajouter à nouveau ce jeton en allant sur « Ajouter un jeton » dans le menu des options de votre compte."
+ "message": "Vous pourrez ajouter à nouveau ce jeton en allant sur « Ajouter un jeton » dans le menu des options de votre compte."
},
"receive": {
"message": "Recevoir"
@@ -2549,13 +2589,13 @@
"message": "Commencez ici"
},
"recoveryPhraseReminderConfirm": {
- "message": "C’est compris !"
+ "message": "C’est compris !"
},
"recoveryPhraseReminderHasBackedUp": {
"message": "Conservez toujours votre phrase secrète de récupération dans un endroit sûr et secret"
},
"recoveryPhraseReminderHasNotBackedUp": {
- "message": "Vous avez besoin de sauvegarder à nouveau votre phrase secrète de récupération ?"
+ "message": "Vous avez besoin de sauvegarder à nouveau votre phrase secrète de récupération ?"
},
"recoveryPhraseReminderItemOne": {
"message": "Ne partagez jamais votre phrase secrète de récupération avec qui que ce soit"
@@ -2588,7 +2628,7 @@
"message": "Rejeté"
},
"remember": {
- "message": "Rappel :"
+ "message": "Rappel :"
},
"remindMeLater": {
"message": "Rappelez-moi plus tard"
@@ -2609,7 +2649,7 @@
"message": "Supprimer le Snap"
},
"removeSnapConfirmation": {
- "message": "Voulez-vous vraiment supprimer $1 ?",
+ "message": "Voulez-vous vraiment supprimer $1 ?",
"description": "$1 represents the name of the snap"
},
"removeSnapDescription": {
@@ -2652,7 +2692,7 @@
"message": "Restaurer"
},
"restoreWalletPreferences": {
- "message": "Une sauvegarde de vos données de $1 a été trouvée. Voulez-vous restaurer vos préférences de portefeuille ?",
+ "message": "Une sauvegarde de vos données de $1 a été trouvée. Voulez-vous restaurer vos préférences de portefeuille ?",
"description": "$1 is the date at which the data was backed up"
},
"retryTransaction": {
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "Révéler la phrase mnémonique"
},
+ "revokeAllTokensTitle": {
+ "message": "Révoquer l'autorisation d'accéder à tous vos $1 ?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "En révoquant cette autorisation, les $1 suivants ne pourront plus accéder à vos $2. ",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Réseau de test Rinkeby"
},
@@ -2761,22 +2809,22 @@
"message": "Stocker dans un coffre-fort de banque."
},
"seedPhraseIntroSidebarCopyOne": {
- "message": "Votre phrase secrète de récupération est une formule de 12 mots qui constitue la « clé maîtresse » de votre portefeuille et de vos fonds"
+ "message": "Votre phrase secrète de récupération est une formule de 12 mots qui constitue la « clé maîtresse » de votre portefeuille et de vos fonds"
},
"seedPhraseIntroSidebarCopyThree": {
"message": "Si quelqu’un vous demande votre phrase de récupération, il est probable qu’il essaie de vous arnaquer pour dérober les fonds de votre portefeuille"
},
"seedPhraseIntroSidebarCopyTwo": {
- "message": "Ne partagez jamais, au grand jamais, votre phrase secrète de récupération, pas même avec MetaMask !"
+ "message": "Ne partagez jamais, au grand jamais, votre phrase secrète de récupération, pas même avec MetaMask !"
},
"seedPhraseIntroSidebarTitleOne": {
- "message": "Qu’est-ce qu’une phrase secrète de récupération ?"
+ "message": "Qu’est-ce qu’une phrase secrète de récupération ?"
},
"seedPhraseIntroSidebarTitleThree": {
- "message": "Dois-je partager ma phrase secrète de récupération ?"
+ "message": "Dois-je partager ma phrase secrète de récupération ?"
},
"seedPhraseIntroSidebarTitleTwo": {
- "message": "Comment puis-je sauvegarder ma phrase secrète de récupération ?"
+ "message": "Comment puis-je sauvegarder ma phrase secrète de récupération ?"
},
"seedPhraseIntroTitle": {
"message": "Sécuriser votre portefeuille"
@@ -2788,7 +2836,7 @@
"message": "Les phrases secrètes de récupération sont composées de 12, 15, 18, 21 ou 24 mots"
},
"seedPhraseWriteDownDetails": {
- "message": "Notez cette phrase secrète de récupération de 12 mots et stockez-la dans un endroit de confiance où vous seul avez accès."
+ "message": "Notez cette phrase secrète de récupération de 12 mots et stockez-la dans un endroit de confiance où vous seul avez accès."
},
"seedPhraseWriteDownHeader": {
"message": "Notez votre phrase secrète de récupération"
@@ -2852,12 +2900,23 @@
"message": "Envoi de $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "Attention : vous êtes sur le point d'envoyer des jetons à l'adresse d'un contrat de jetons qui pourrait entraîner une perte de fonds. $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "Définir des paramètres de confidentialité avancés"
},
"setAdvancedPrivacySettingsDetails": {
"message": "MetaMask utilise ces services tiers de confiance pour améliorer la convivialité et la sécurité des produits."
},
+ "setApprovalForAll": {
+ "message": "Définir l'approbation pour tous"
+ },
+ "setApprovalForAllTitle": {
+ "message": "Approuver $1 sans limite de dépenses",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "Paramètres"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "Sélectionnez cette option pour afficher le prix du carburant et les contrôles des limites directement sur les écrans d’envoi et de confirmation."
},
+ "showCustomNetworkList": {
+ "message": "Afficher la liste des réseaux personnalisés"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "Sélectionnez cette option pour afficher une liste de réseaux dont l’adresse est préremplie lorsque vous ajoutez un nouveau réseau."
+ },
"showFiatConversionInTestnets": {
"message": "Afficher la conversion sur Testnets"
},
@@ -2938,7 +3003,7 @@
"message": "Ignorer"
},
"skipAccountSecurity": {
- "message": "Renoncer à la sécurité des comptes ?"
+ "message": "Renoncer à la sécurité des comptes ?"
},
"skipAccountSecurityDetails": {
"message": "Je suis conscient(e) que tant que je n’aurai pas sauvegardé ma phrase secrète de récupération, je risque de perdre mes comptes et tous leurs actifs."
@@ -2950,7 +3015,7 @@
"message": "Transaction intelligente"
},
"snapAccess": {
- "message": "Le snap $1 peut accéder à :",
+ "message": "Le snap $1 peut accéder à :",
"description": "$1 represents the name of the snap"
},
"snapAdded": {
@@ -2958,7 +3023,7 @@
"description": "$1 represents the date the snap was installed, $2 represents which origin installed the snap."
},
"snapError": {
- "message": "Erreur de snap : « $1 ». Code d’erreur : « $2 »",
+ "message": "Erreur de snap : « $1 ». Code d’erreur : « $2 »",
"description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code."
},
"snapInstall": {
@@ -2967,12 +3032,8 @@
"snapInstallWarningCheck": {
"message": "Pour confirmer que vous comprenez, cochez tout."
},
- "snapInstallWarningKeyAccess": {
- "message": "Vous accordez un accès de clé au snap « $1 ». Cette action est irrévocable et accorde à « $1 » le contrôle de vos comptes et actifs. Vérifiez que vous pouvez faire confiance à « $1 » avant de continuer.",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
- "message": "Ce snap demande les autorisations suivantes :"
+ "message": "Ce snap demande les autorisations suivantes :"
},
"snaps": {
"message": "Snaps"
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "Un snap ne s’exécute que s’il est activé"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "Certains réseaux peuvent présenter des risques pour la sécurité et/ou la vie privée. Informez-vous sur les risques avant d’ajouter et d’utiliser un réseau."
+ },
"somethingWentWrong": {
"message": "Oups ! Quelque chose a mal tourné. "
},
@@ -2999,7 +3063,7 @@
"message": "Accélérer cette annulation"
},
"speedUpExplanation": {
- "message": "Nous avons mis à jour le prix du carburant selon les conditions actuelles du réseau et l’avons augmenté d’au moins 10 % (requis par le réseau)."
+ "message": "Nous avons mis à jour le prix du carburant selon les conditions actuelles du réseau et l’avons augmenté d’au moins 10 % (requis par le réseau)."
},
"speedUpPopoverTitle": {
"message": "Accélérer la transaction"
@@ -3017,7 +3081,7 @@
"message": "Limite de dépenses insuffisante"
},
"spendLimitInvalid": {
- "message": "Limite de dépenses invalide ; cela doit être une valeur positive"
+ "message": "Limite de dépenses invalide ; cela doit être une valeur positive"
},
"spendLimitPermission": {
"message": "Autorisation de limite de dépenses"
@@ -3030,11 +3094,11 @@
"message": "Limite de dépenses trop élevée"
},
"srpInputNumberOfWords": {
- "message": "J’ai une phrase de $1 mots",
+ "message": "J’ai une phrase de $1 mots",
"description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)."
},
"srpPasteFailedTooManyWords": {
- "message": "Le collage a échoué parce que la phrase contenait plus de 24 mots. Une phrase secrète de récupération peut contenir un maximum de 24 mots.",
+ "message": "Le collage a échoué parce que la phrase contenait plus de 24 mots. Une phrase secrète de récupération peut contenir un maximum de 24 mots.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
},
"srpPasteTip": {
@@ -3084,7 +3148,7 @@
"message": "Assurez-vous que votre Lattice1 est prêt à se connecter"
},
"step1LatticeWalletMsg": {
- "message": "Vous pouvez connecter MetaMask à votre dispositif Lattice1 une fois qu’il est configuré et en ligne. Déverrouillez votre appareil et munissez-vous de son ID. Pour en savoir plus sur l’utilisation des portefeuilles matériels, $1",
+ "message": "Vous pouvez connecter MetaMask à votre dispositif Lattice1 une fois qu’il est configuré et en ligne. Déverrouillez votre appareil et munissez-vous de son ID. Pour en savoir plus sur l’utilisation des portefeuilles matériels, $1",
"description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key"
},
"step1LedgerWallet": {
@@ -3118,7 +3182,7 @@
"message": "Robuste"
},
"stxAreHere": {
- "message": "Les transactions intelligentes sont là !"
+ "message": "Les transactions intelligentes sont là !"
},
"stxBenefit1": {
"message": "Minimise les frais de transaction"
@@ -3142,7 +3206,7 @@
"message": "Réessayez le swap. Nous serons là pour vous protéger contre des risques similaires la prochaine fois."
},
"stxDescription": {
- "message": "MetaMask Swaps vient de devenir beaucoup plus intelligent ! Si vous activez les transactions intelligentes, MetaMask pourra optimiser programmatiquement votre swap pour vous aider à :"
+ "message": "MetaMask Swaps vient de devenir beaucoup plus intelligent ! Si vous activez les transactions intelligentes, MetaMask pourra optimiser programmatiquement votre swap pour vous aider à :"
},
"stxErrorNotEnoughFunds": {
"message": "Fonds insuffisants pour une transaction intelligente."
@@ -3173,7 +3237,7 @@
"message": "* Avec les transactions intelligentes, votre transaction sera soumise plusieurs fois en privé. Si toutes les tentatives échouent, la transaction sera diffusée publiquement pour s’assurer de la réussite de votre swap."
},
"stxSuccess": {
- "message": "Swap terminé !"
+ "message": "Swap terminé !"
},
"stxSuccessDescription": {
"message": "Votre $1 est maintenant disponible.",
@@ -3226,7 +3290,7 @@
"message": "Swap"
},
"swapAdvancedSlippageInfo": {
- "message": "Si le prix fluctue entre le passage de votre ordre et sa confirmation, on parle alors d’un « effet de glissement » (slippage). Votre swap sera automatiquement annulé si ce phénomène dépasse votre paramètre de « glissement maximal »."
+ "message": "Si le prix fluctue entre le passage de votre ordre et sa confirmation, on parle alors d’un « effet de glissement » (slippage). Votre swap sera automatiquement annulé si ce phénomène dépasse votre paramètre de « glissement maximal »."
},
"swapAggregator": {
"message": "Agrégateur"
@@ -3246,7 +3310,7 @@
"description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved."
},
"swapApproveNeedMoreTokens": {
- "message": "Vous avez besoin de $1 $2 de plus pour effectuer ce swap",
+ "message": "Vous avez besoin de $1 $2 de plus pour effectuer ce swap",
"description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol."
},
"swapApproveNeedMoreTokensSmartTransactions": {
@@ -3265,7 +3329,7 @@
"message": "Confirmez avec votre portefeuille matériel"
},
"swapContractDataDisabledErrorDescription": {
- "message": "Dans l’application Ethereum de votre Ledger, allez dans « Paramètres » et autorisez les données de contrat. Ensuite, retentez votre swap."
+ "message": "Dans l’application Ethereum de votre Ledger, allez dans « Paramètres » et autorisez les données de contrat. Ensuite, retentez votre swap."
},
"swapContractDataDisabledErrorTitle": {
"message": "Les données de contrat ne sont pas activées sur votre Ledger"
@@ -3340,7 +3404,7 @@
"message": "Le montant du glissement est très élevé."
},
"swapIncludesMMFee": {
- "message": "Comprend des frais MetaMask à hauteur de $1 %.",
+ "message": "Comprend des frais MetaMask à hauteur de $1 %.",
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number."
},
"swapLowSlippageError": {
@@ -3353,11 +3417,11 @@
"message": "Frais MetaMask"
},
"swapMetaMaskFeeDescription": {
- "message": "Nous recherchons systématiquement le meilleur prix auprès des meilleures sources de liquidité. Une commission de $1 % est automatiquement incluse dans cette cotation.",
+ "message": "Nous recherchons systématiquement le meilleur prix auprès des meilleures sources de liquidité. Une commission de $1 % est automatiquement incluse dans cette cotation.",
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number."
},
"swapNQuotesWithDot": {
- "message": "$1 cotations.",
+ "message": "$1 cotations.",
"description": "$1 is the number of quotes that the user can select from when opening the list of quotes on the 'view quote' screen"
},
"swapNewQuoteIn": {
@@ -3369,7 +3433,7 @@
"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": "Vous êtes sur le point d’effectuer un swap de $1 $2 (~$3) contre $4 $5 (~$6).",
+ "message": "Vous êtes sur le point d’effectuer un swap de $1 $2 (~$3) contre $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": {
@@ -3392,7 +3456,7 @@
"message": "Détails de la cotation"
},
"swapQuoteDetailsSlippageInfo": {
- "message": "Si le prix fluctue entre le passage de votre ordre et sa confirmation, on parle alors d’un « effet de glissement » (slippage). Votre swap sera automatiquement annulé si ce phénomène dépasse votre paramètre de « tolérance de glissement »."
+ "message": "Si le prix fluctue entre le passage de votre ordre et sa confirmation, on parle alors d’un « effet de glissement » (slippage). Votre swap sera automatiquement annulé si ce phénomène dépasse votre paramètre de « tolérance de glissement »."
},
"swapQuoteSource": {
"message": "Origine de la cotation"
@@ -3490,21 +3554,21 @@
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
},
"swapTokenVerificationOnlyOneSource": {
- "message": "Vérification effectuée uniquement sur 1 source."
+ "message": "Vérification effectuée uniquement sur 1 source."
},
"swapTokenVerificationSources": {
- "message": "Vérification effectuée sur $1 sources.",
+ "message": "Vérification effectuée sur $1 sources.",
"description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number."
},
"swapTooManyDecimalsError": {
- "message": "$1 accepte jusqu’à $2 décimales",
+ "message": "$1 accepte jusqu’à $2 décimales",
"description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token"
},
"swapTransactionComplete": {
"message": "Transaction terminée"
},
"swapTwoTransactions": {
- "message": "2 transactions"
+ "message": "2 transactions"
},
"swapUnknown": {
"message": "Inconnu"
@@ -3514,17 +3578,17 @@
"description": "This appears in a tooltip next to the verifyThisTokenOn message. It gives the user more information about why they should check the token on a block explorer. $1 will be the name or url of the block explorer, which will be the translation of 'etherscan' or a block explorer url specified for a custom network."
},
"swapYourTokenBalance": {
- "message": "$1 $2 disponibles pour un swap",
+ "message": "$1 $2 disponibles pour un swap",
"description": "Tells the user how much of a token they have in their balance. $1 is a decimal number amount of tokens, and $2 is a token symbol"
},
"swapZeroSlippage": {
- "message": "0 % de glissement"
+ "message": "0 % de glissement"
},
"swapsAdvancedOptions": {
"message": "Options avancées"
},
"swapsExcessiveSlippageWarning": {
- "message": "Le montant du glissement est trop élevé et donnera lieu à un mauvais taux. Veuillez réduire votre tolérance de glissement à une valeur inférieure à 15 %."
+ "message": "Le montant du glissement est trop élevé et donnera lieu à un mauvais taux. Veuillez réduire votre tolérance de glissement à une valeur inférieure à 15 %."
},
"swapsMaxSlippage": {
"message": "Tolérance de glissement"
@@ -3537,10 +3601,10 @@
"message": "Afficher dans l’activité"
},
"switchEthereumChainConfirmationDescription": {
- "message": "Ceci permet de remplacer le réseau sélectionné dans MetaMask par un réseau précédemment ajouté :"
+ "message": "Ceci permet de remplacer le réseau sélectionné dans MetaMask par un réseau précédemment ajouté :"
},
"switchEthereumChainConfirmationTitle": {
- "message": "Autoriser ce site à changer de réseau ?"
+ "message": "Autoriser ce site à changer de réseau ?"
},
"switchNetwork": {
"message": "Changer de réseau"
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "Changer de réseau"
},
+ "switchToNetwork": {
+ "message": "Passer à $1",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "Basculer vers ce compte"
},
@@ -3576,7 +3644,7 @@
"message": "Vos données ont été synchronisées avec succès. Profitez de l’application mobile MetaMask !"
},
"syncWithMobileDesc": {
- "message": "Vous pouvez synchroniser vos comptes et vos informations avec votre appareil mobile. Ouvrez l’application mobile MetaMask, allez dans « Paramètres » et appuyez sur « Synchroniser depuis l’extension de navigateur »"
+ "message": "Vous pouvez synchroniser vos comptes et vos informations avec votre appareil mobile. Ouvrez l’application mobile MetaMask, allez dans « Paramètres » et appuyez sur « Synchroniser depuis l’extension de navigateur »"
},
"syncWithMobileDescNewUsers": {
"message": "Si vous ouvrez l’application MetaMask Mobile pour la première fois, suivez simplement les étapes dans votre téléphone."
@@ -3591,13 +3659,13 @@
"message": "Synchronisation des données avec 3Box (expérimental)"
},
"syncWithThreeBoxDescription": {
- "message": "Activez cette fonction pour que vos paramètres soient sauvegardés avec 3Box. Cette fonction est actuellement expérimentale ; utilisez-la avec prudence."
+ "message": "Activez cette fonction pour que vos paramètres soient sauvegardés avec 3Box. Cette fonction est actuellement expérimentale ; utilisez-la avec prudence."
},
"syncWithThreeBoxDisabled": {
"message": "3Box a été désactivé en raison d’une erreur lors de la synchronisation initiale"
},
"tenPercentIncreased": {
- "message": "Augmentation de 10 %"
+ "message": "Augmentation de 10 %"
},
"terms": {
"message": "Conditions d’utilisation"
@@ -3630,12 +3698,12 @@
"message": "Destinataire"
},
"toAddress": {
- "message": "Vers : $1",
+ "message": "Vers : $1",
"description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress"
},
"toggleTestNetworks": {
"message": "$1 réseaux de test",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Jeton"
@@ -3653,7 +3721,7 @@
"message": "Décimale de jeton requise."
},
"tokenDecimalTitle": {
- "message": "Décimales du token :"
+ "message": "Décimales du token :"
},
"tokenDetails": {
"message": "Détails du token"
@@ -3664,17 +3732,11 @@
"tokenDetectionAlertMessage": {
"message": "La détection du token est actuellement disponible sur $1. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "Nouveau ! Une détection améliorée des jetons est disponible sur le Mainnet d’Ethereum en tant que fonctionnalité expérimentale. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "L’API des tokens de ConsenSys regroupe une liste de tokens provenant de diverses listes de tokens externes. Sa désactivation arrêtera la détection de nouveaux tokens ajoutés à votre portefeuille, mais conservera l’option de recherche de tokens à importer."
- },
"tokenId": {
"message": "ID de token"
},
"tokenList": {
- "message": "Listes de tokens :"
+ "message": "Listes de tokens :"
},
"tokenSymbol": {
"message": "Symbole du jeton"
@@ -3741,7 +3803,7 @@
"message": "Montant + frais de carburant"
},
"transactionDetailLayer2GasHeading": {
- "message": "Frais de carburant de couche 2 (L2)"
+ "message": "Frais de carburant de couche 2 (L2)"
},
"transactionDetailMultiLayerTotalSubtitle": {
"message": "Montant + frais"
@@ -3765,13 +3827,13 @@
"message": "Frais de base (GWEI)"
},
"transactionHistoryL1GasLabel": {
- "message": "Total des frais de carburant L 1"
+ "message": "Total des frais de carburant L 1"
},
"transactionHistoryL2GasLimitLabel": {
- "message": "Limite de carburant L2"
+ "message": "Limite de carburant L2"
},
"transactionHistoryL2GasPriceLabel": {
- "message": "Prix de carburant L2"
+ "message": "Prix de carburant L2"
},
"transactionHistoryMaxFeePerGas": {
"message": "Frais maximaux par carburant"
@@ -3821,7 +3883,7 @@
"message": "Activer la détection améliorée des jetons"
},
"twelveHrTitle": {
- "message": "12 h :"
+ "message": "12 h :"
},
"txInsightsNotSupported": {
"message": "Les aperçus de transaction ne sont pas pris en charge pour ce contrat à l’heure actuelle."
@@ -3851,11 +3913,14 @@
"unknownCameraErrorTitle": {
"message": "Oups ! Il y a eu un problème...."
},
+ "unknownCollection": {
+ "message": "Collection sans nom"
+ },
"unknownNetwork": {
"message": "Réseau privé inconnu"
},
"unknownQrCode": {
- "message": "Erreur : nous n’avons pas pu identifier le code QR"
+ "message": "Erreur : nous n’avons pas pu identifier le code QR"
},
"unlimited": {
"message": "Illimité"
@@ -3893,7 +3958,7 @@
"message": "Détection automatique des NFT"
},
"useCollectibleDetectionDescription": {
- "message": "L’affichage des médias et des données des NFT peut exposer votre adresse IP à des serveurs centralisés. Des API tierces (comme OpenSea) sont utilisées pour détecter les NFT dans votre portefeuille. Cela expose donc l’adresse de votre compte à ces services. Désactivez cette option si vous ne souhaitez pas que l’application récupère des données auprès de ces services."
+ "message": "L’affichage des médias et des données des NFT peut exposer votre adresse IP à des serveurs centralisés. Des API tierces (comme OpenSea) sont utilisées pour détecter les NFT dans votre portefeuille. Cela expose donc l’adresse de votre compte à ces services. Désactivez cette option si vous ne souhaitez pas que l’application récupère des données auprès de ces services."
},
"usePhishingDetection": {
"message": "Utiliser la fonction antihameçonnage"
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "Cela permet d’afficher un avertissement pour les domaines d’hameçonnage ciblant les utilisateurs d’Ethereum"
},
- "useTokenDetection": {
- "message": "Utiliser la détection des jetons"
- },
- "useTokenDetectionDescription": {
- "message": "Nous utilisons des API tierces pour détecter et afficher les nouveaux jetons envoyés à votre portefeuille. Désactivez cette option si vous ne souhaitez pas que MetaMask récupère les données de ces services."
- },
"useTokenDetectionPrivacyDesc": {
"message": "L’affichage automatique des tokens envoyés sur votre compte implique une communication avec des serveurs externes afin de récupérer les images des tokens. Ces serveurs auront accès à votre adresse IP."
},
@@ -3968,7 +4027,7 @@
"message": "notre guide de connexion des portefeuilles matériels"
},
"walletCreationSuccessDetail": {
- "message": "Votre portefeuille est bien protégé. Conservez votre phrase secrète de récupération en sécurité et en toute discrétion. C’est votre responsabilité !"
+ "message": "Votre portefeuille est bien protégé. Conservez votre phrase secrète de récupération en sécurité et en toute discrétion. C’est votre responsabilité !"
},
"walletCreationSuccessReminder1": {
"message": "MetaMask ne peut pas restaurer votre phrase secrète de récupération."
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "Portefeuille créé avec succès"
},
+ "wantToAddThisNetwork": {
+ "message": "Voulez-vous ajouter ce réseau ?"
+ },
"warning": {
"message": "Avertissement"
},
@@ -3994,7 +4056,7 @@
"message": "Faible"
},
"web3ShimUsageNotification": {
- "message": "Nous avons remarqué que ce site Web a essayé d’utiliser l’API window.web3 supprimée. Si le site semble être défectueux, veuillez cliquer sur $1 pour plus d’informations.",
+ "message": "Nous avons remarqué que ce site Web a essayé d’utiliser l’API window.web3 supprimée. Si le site semble être défectueux, veuillez cliquer sur $1 pour plus d’informations.",
"description": "$1 is a clickable link."
},
"webhid": {
@@ -4014,13 +4076,13 @@
"message": "Explorer des applications décentralisées"
},
"welcomeLoginDescription": {
- "message": "Utilisez votre MetaMask pour vous connecter à des applications décentralisées. Nul besoin de vous inscrire !"
+ "message": "Utilisez votre MetaMask pour vous connecter à des applications décentralisées. Nul besoin de vous inscrire !"
},
"welcomeLoginTitle": {
"message": "Dites bonjour à votre portefeuille"
},
"welcomeToMetaMask": {
- "message": "C’est parti !"
+ "message": "C’est parti !"
},
"welcomeToMetaMaskIntro": {
"message": "MetaMask est un portefeuille sécurisé utilisé par des millions de personnes qui rend l’univers du web3 accessible à toutes et à tous."
@@ -4030,7 +4092,7 @@
"description": "This is the title of a popup that gives users notifications about new features and updates to MetaMask."
},
"whatsThis": {
- "message": "Qu’est-ce que c’est ?"
+ "message": "Qu’est-ce que c’est ?"
},
"writePhrase": {
"message": "Écrivez cette phrase sur une feuille de papier et rangez-la dans un endroit sûr. Si vous recherchez plus de sécurité, notez-la sur plusieurs feuilles de papier et rangez-les dans deux ou trois endroits différents."
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "Oui, essayons"
},
+ "youHaveAddedAll": {
+ "message": "Vous avez ajouté tous les réseaux populaires. Vous pouvez découvrir d’autres réseaux $1 ou $2",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "Vous devez autoriser l’accès à votre appareil pour utiliser cette fonctionnalité."
},
diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json
index 2d643f1aa..e4855ee24 100644
--- a/app/_locales/hi/messages.json
+++ b/app/_locales/hi/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "मेमो जोड़ें"
},
+ "addMoreNetworks": {
+ "message": "मैन्युअल रूप से अधिक नेटवर्क जोड़ें"
+ },
"addNetwork": {
"message": "नेटवर्क जोड़ें"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "चेतावनियां"
},
+ "allOfYour": {
+ "message": "आपके सभी $1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "इस बाहरी एक्सटेंशन को इसकी अनुमति दें:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "खर्च सीमा अनुमोदित करें"
},
+ "approveAllTokensTitle": {
+ "message": "आपके सभी $1 को एक्सेस करने के लिए अनुमति दें",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "स्वीकृति दें और इंस्टॉल करें"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "स्वीकृत एसेट"
},
- "areYouDeveloper": {
- "message": "क्या आप एक डेवलपर हैं?"
- },
"areYouSure": {
"message": "क्या आप सुनिश्चित हैं?"
},
@@ -435,10 +443,10 @@
"description": "$1 represents the crypto symbol to be purchased"
},
"buyWithWyre": {
- "message": "Wyre के जरिए $1 खरीदें"
+ "message": "Wyre के साथ $1 खरीदें"
},
"buyWithWyreDescription": {
- "message": "Wyre आपको अपने MetaMask खाते में $1 जमा करने के लिए डेबिट कार्ड का उपयोग करने की सुविधा देता है।"
+ "message": "$1000 तक की खरीदारी के लिए आसान ऑनबोर्डिंग। तेज़ इंटरैक्टिव उच्च सीमा खरीद सत्यापन। डेबिट / क्रेडिट कार्ड, ऐप्पल पे, बैंक ट्रांसफर का समर्थन करता है। 100+ देशों में उपलब्ध है। टोकन आपके मेटामास्क खाते में जमा होते हैं"
},
"bytes": {
"message": "बाइट"
@@ -466,6 +474,13 @@
"message": "किसी लेनदेन को $1 करने के लिए गैस शुल्क में कम से कम 10% की वृद्धि की जानी चाहिए ताकि उसे नेटवर्क द्वारा मान्यता मिल सके।",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "~$1 में स्वैप रद्द करें",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "मुफ्त में स्वैप रद्द करें"
+ },
"cancellationGasFee": {
"message": "रद्दीकरण गैस शुल्क"
},
@@ -650,7 +665,7 @@
"message": "अनुबंध इंटरैक्शन"
},
"convertTokenToNFTDescription": {
- "message": "हमने पाया है कि ये एसेट एक एनएफटी है। MetaMask के पास अब एनएफटी के लिए पूर्ण स्थानीय सपोर्ट है। क्या आप इसे अपनी टोकन सूची से हटाना चाहते हैं और इसे एनएफटी के रूप में जोड़ना चाहते हैं?"
+ "message": "हमने पाया है कि यह संपत्ति एक एनएफटी है। मेटामास्क के पास अब एनएफटी के लिए पूर्ण देशी समर्थन है। क्या आप इसे अपनी टोकन सूची से हटाना चाहते हैं और इसे एनएफटी के रूप में जोड़ना चाहते हैं?"
},
"convertTokenToNFTExistDescription": {
"message": "हमने पाया है कि इस एसेट को एक एनएफटी के रूप में जोड़ा गया है। क्या आप इसे अपनी टोकन सूची से हटाना चाहते हैं?"
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "गैस की कीमत को अनुकूलित करने के लिए $1 का उपयोग करें। यदि आप परिचित नहीं हैं तो ये भ्रामक हो सकता है। अपनी ज़िम्मेदारी पर बातचीत करें।",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "शुल्क बढ़ाने से प्रसंस्करण समय में कमी हो सकती है, लेकिन इसकी गारंटी नहीं होती है।"
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "$1 जमा करें",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "विवरण"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "कुछ गलत हुआ और हम कार्रवाई को पूरा करने में असमर्थ रहे"
},
- "fakeTokenWarning": {
- "message": "कोई भी टोकन बना सकता है, जिसमें मौजूदा टोकन के नकली संस्करण को बनाना शामिल है। $1 के बारे में और अधिक जानें"
- },
"fast": {
"message": "तेज"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "फंक्शन: अनुमोदित करें"
},
+ "functionSetApprovalForAll": {
+ "message": "कार्य: सभी के लिए स्वीकृति सेट करें"
+ },
"functionType": {
"message": "फंक्शन का प्रकार"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "लोकप्रिय NFT ड्रॉप जैसी चीज़ों की वजह से नेटवर्क ट्रैफिक में वृद्धि को कवर करने के लिए $1 का उपयोग करें।",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "उच्च"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "अमान्य गुप्त रिकवरी फ्रेज"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "अमान्य निवेश! गुप्त पुनर्प्राप्ति वाक्यांश केस संवेदी है।"
+ },
"ipfsGateway": {
"message": "IPFS गेटवे"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "एक सस्ती कीमत की प्रतीक्षा के लिए $1 का उपयोग करें। समय का अनुमान बहुत कम सही होता है क्योंकि कीमतें कुछ हद तक अप्रत्याशित होती हैं।",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "निम्न"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "मौजूदा बाजार मूल्य पर तेजी से प्रोसेस करने के लिए $1का उपयोग करें।",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "मेमो"
@@ -1892,6 +1910,19 @@
"message": "नेटवर्क विवरण सत्यापित करें",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "हम सिफ़ारिश करते हैं कि आगे बढ़ने से पहले आप $1 करें।",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "हमारे रिकॉर्ड के अनुसार, नेटवर्क का नाम इस चेन आईडी से ठीक से मेल नहीं खा सकता है।"
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "सबमिट किया गया मुद्रा संकेत इस चेन आईडी के लिए हमारी अपेक्षा से मेल नहीं खाता।"
+ },
+ "mismatchedRpcUrl": {
+ "message": "हमारे रिकॉर्ड के अनुसार, सबमिट किया गया RPC URL मान इस चेन आईडी के किसी ज्ञात प्रोवाइडर से मेल नहीं खाता।"
+ },
"missingNFT": {
"message": "अपना NFT नहीं देख रहे हैं?"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "नेटवर्क:"
},
+ "networkAddedSuccessfully": {
+ "message": "नेटवर्क सफलतापूर्वक जोड़ा गया!"
+ },
"networkDetails": {
"message": "नेटवर्क विवरण"
},
@@ -2059,6 +2093,9 @@
"message": "नॉन्स $1 के सुझाए गए नॉन्स से अधिक है",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "एनएफटी"
+ },
"nftTokenIdPlaceholder": {
"message": "संग्रहणीय ID दर्ज करें"
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "सेटिंग्स में जाएं",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "बेहतर टोकन डिटेक्शन वर्तमान में Ethereum Mainnet, Polygon, BSC और Avalanche के नेटवर्कों पर उपलब्ध है। और भी आने वाला है!"
@@ -2154,11 +2191,21 @@
"message": "डार्क मोड सक्षम करें"
},
"notifications12Description": {
- "message": "नए उपयोगकर्ताओं के लिए उनकी सिस्टम प्राथमिकताओं के आधार पर डार्क मोड सक्षम किया जाएगा। मौजूदा उपयोगकर्ताओं के लिए, सेटिंग->एक्सपेरिमेंटल के नीचे मैन्युअल रूप से डार्क मोड सक्षम करें।"
+ "message": "एक्सटेंशन पर डार्क मोड आखिरकार आ गया है! इसे चालू करने के लिए, सेटिंग -> प्रायोगिक पर जाएं और प्रदर्शन विकल्पों में से एक का चयन करें: लाइट, डार्क, सिस्टम।"
},
"notifications12Title": {
"message": "वेन डार्क मोड? अब डार्क मोड! 🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "कस्टम नेटवर्क सूची दिखाएं"
+ },
+ "notifications13Description": {
+ "message": "अब आप निम्नलिखित लोकप्रिय कस्टम नेटवर्क्स आसानी से जोड़ सकते हैं: आर्बिट्रम, एवलांश, बिनेंस स्मार्ट चेन, फैंटम, हार्मनी, ऑप्टिमिज़्म, पाम एंड पॉलीगॉन! इस फ़ीचर को एनेबल करने के लिए, सेटिंग्स ->एक्सपेरिमेंटल पर जाएं और \"शो कस्टम नेटवर्क लिस्ट\" ऑन करें!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "लोकप्रिय नेटवर्क्स जोड़ें"
+ },
"notifications1Description": {
"message": "MetaMask Mobile उपयोगकर्ता अब अपने मोबाइल वॉलेट के अंदर टोकन स्वैप कर सकते हैं। मोबाइल ऐप प्राप्त करने के लिए QR कोड को स्कैन करें और स्वैप करना शुरू करें।",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "एडवांस सेटिंग्स पर जाएं",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "MetaMask v10.4.0 के अनुसार, अब आपको अपने लेजर डिवाइस को मेटामास्क से कनेक्ट करने के लिए लेजर लाइव की आवश्यकता नहीं है।",
@@ -2255,15 +2302,11 @@
"message": "सूचनाएं"
},
"notificationsInfos": {
- "message": "$1 से $2",
+ "message": "$1 को $2 से",
"description": "$1 is the date at which the notification has been dispatched and $2 is the link to the snap that dispatched the notification."
},
"notificationsMarkAllAsRead": {
- "message": "सभी को पढ़ा हुआ मार्क करें"
- },
- "numberOfNewTokensDetected": {
- "message": "इस खाते में $1 के नए टोकन पाए गए",
- "description": "$1 is the number of new tokens detected"
+ "message": "सभी को पढ़ा हुआ चिन्हित करें"
},
"ofTextNofM": {
"message": "का"
@@ -2344,9 +2387,6 @@
"message": "अपने लेजर को WebHID के माध्यम से कनेक्ट करने के लिए MetaMask को पूर्ण स्क्रीन में खोलें।",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "सोर्स कोड जांचें"
- },
"optional": {
"message": "वैकल्पिक"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "वरीयता वाले लेजर कनेक्शन के प्रकार",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "स्वैप की तैयारी कर रहा है..."
@@ -2646,7 +2686,7 @@
"message": "आगे बढ़ने से पहले ये सुनिश्चित करें कि आप सही सीक्रेट रिकवरी फ़्रेज़ का इस्तेमाल कर रहे हैं। इसे आप अनडू नहीं कर पाएंगे।"
},
"restartMetamask": {
- "message": "मेटामास्क को पुनरारंभ करें"
+ "message": "MetaMask को फिर से शुरू करें"
},
"restore": {
"message": "पुनर्स्थापित करें"
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "सीड फ़्रेज़ दिखाएं"
},
+ "revokeAllTokensTitle": {
+ "message": "अपने सभी $1 को एक्सेस करने की अनुमति निरस्त करें?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "अनुमति निरस्त करने से, निम्नलिखित $1 अब आपके $2 को एक्सेस नहीं कर सकेगा",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Rinkeby टेस्ट नेटवर्क"
},
@@ -2852,12 +2900,23 @@
"message": "$1 भेजा जा रहा है",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "चेतावनी: आप एक टोकन अनुबंध को भेजने वाले हैं जिसके परिणामस्वरूप धन की हानि हो सकती है। $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "एडवांस गोपनीयता सेटिंग्स निर्धारित करें"
},
"setAdvancedPrivacySettingsDetails": {
"message": "MetaMask उत्पाद की उपयोगिता और सुरक्षा को बढ़ाने के लिए इन विश्वसनीय तीसरे-पक्ष की सेवाओं का उपयोग करता है।"
},
+ "setApprovalForAll": {
+ "message": "सभी के लिए स्वीकृति सेट करें"
+ },
+ "setApprovalForAllTitle": {
+ "message": "बिना किसी खर्च सीमा के $1 स्वीकृत करें",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "सेटिंग"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "गैस मूल्य और सीमा नियंत्रण को सीधे भेजने और पुष्टि करने की स्क्रीन पर दिखाने के लिए इसका चयन करें।"
},
+ "showCustomNetworkList": {
+ "message": "कस्टम नेटवर्क की सूची दिखाएं"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "नया नेटवर्क जोड़ते समय पहले से भरे हुए विवरण वाले नेटवर्कों की सूची दिखाने के लिए इसे चुनें।"
+ },
"showFiatConversionInTestnets": {
"message": "टेस्ट नेटवर्क पर रूपांतरण दिखाएं"
},
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "ये पुष्टि करने के लिए कि आप समझते हैं, सभी पर सही का निशान लगाएं।"
},
- "snapInstallWarningKeyAccess": {
- "message": "आप स्नैप \"$1\" को महत्वपूर्ण एक्सेस प्रदान कर रहे हैं। यह अपरिवर्तनीय है और आपके अकाउंट्स और एसेट्स पर \"$1\" को नियंत्रण प्रदान करता है। आगे बढ़ने से पहले सुनिश्चित करें कि आप \"$1\" पर भरोसा करते हैं।",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "ये स्नैप निम्नलिखित अनुमतियों हेतु अनुरोध कर रहा है:"
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "कोई स्नैप तभी चलेगा जब उसे सक्षम किया गया हो"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "कुछ नेटवर्क सुरक्षा और/या गोपनीयता संबंधी जोखिम पैदा कर सकते हैं। नेटवर्क जोड़ने और उपयोग करने से पहले जोखिमों को समझें।"
+ },
"somethingWentWrong": {
"message": "ओह! कुछ गलत हो गया।"
},
@@ -3164,10 +3228,10 @@
"message": "स्मार्ट लेनदेन अनुपलब्ध होने पर भी आप अपने टोकनों को स्वैप कर सकते हैं।"
},
"stxPendingPrivatelySubmittingSwap": {
- "message": "आपका स्वैप निजी रूप से भेजा जा रहा है..."
+ "message": "आपका स्वैप निजी रूप से सबमिट किया जा रहा है..."
},
"stxPendingPubliclySubmittingSwap": {
- "message": "आपका स्वैप सार्वजनिक रूप से भेजा जा रहा है..."
+ "message": "आपका स्वैप सार्वजनिक रूप से सबमिट किया जा रहा है..."
},
"stxSubDescription": {
"message": "* स्मार्ट लेनदेन आपके लेनदेन को निजी तौर पर, अनेक बार जमा करने का प्रयास करेंगे। यदि सभी प्रयास विफल हो जाते हैं, तो लेनदेन को सार्वजनिक रूप से प्रसारित किया जाएगा ताकि यह सुनिश्चित हो सके कि आपका स्वैप सफलतापूर्वक पूरा हो।"
@@ -3180,7 +3244,7 @@
"description": "$1 is a token symbol, e.g. ETH"
},
"stxSwapCompleteIn": {
- "message": "स्वैप < में पूरा होगा",
+ "message": "स्वैप पूरा होने में शेष समय <",
"description": "'<' means 'less than', e.g. Swap will complete in < 2:59"
},
"stxTooltip": {
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "नेटवर्क स्विच करें"
},
+ "switchToNetwork": {
+ "message": "$1 पर स्विच करें",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "इस खाते पर स्विच करें"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "$1 परीक्षण नेटवर्क",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "टोकन"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "फिलहाल टोकन डिटेक्शन $1 पर उपलब्ध है। $2"
},
- "tokenDetectionAnnouncement": {
- "message": "नया! प्रायोगिक फीचर के रूप में Ethereum Mainnet पर बेहतर टोकन डिटेक्शन उपलब्ध है। $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "ConsenSys के टोकन का एपीआई विभिन्न थर्ड पार्टी टोकन सूचियों में से टोकन की एक सूची एकत्र करता है। इसे बंद करने से आपके वॉलेट में जोड़े गए नए टोकन का पता चलना बंद हो जाएगा, लेकिन इंपोर्ट करने के लिए टोकन खोजने का विकल्प बना रहेगा।"
- },
"tokenId": {
"message": "टोकन आइडी"
},
@@ -3805,7 +3867,7 @@
"description": "$1 is the wallet device name; $2 is a link to wallet connection guide"
},
"troubleStarting": {
- "message": "मेटामास्क को शुरू करने में दिक्कत हुई। यह त्रुटि रुक-रुक कर हो सकती है, इसलिए एक्सटेंशन को पुनरारंभ करने का प्रयास करें।"
+ "message": "MetaMask को शुरू करने में परेशानी आई। यह त्रुटि रुक-रुक कर हो सकती है, इसलिए एक्सटेंशन को फिर से शुरू करके देखें।"
},
"troubleTokenBalances": {
"message": "हमें आपके टोकन की शेषराशि लोड करने में परेशानी हुई। आप उन्हें देख सकते हैं ",
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "ओह! कुछ गलत हो गया...."
},
+ "unknownCollection": {
+ "message": "अनाम संग्रह"
+ },
"unknownNetwork": {
"message": "अज्ञात निजी नेटवर्क"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "Ethereum उपयोगकर्ताओं को लक्षित करने वाले फिशिंग डोमेन के लिए एक चेतावनी प्रदर्शित करें"
},
- "useTokenDetection": {
- "message": "टोकन डिटेक्शन का उपयोग करें"
- },
- "useTokenDetectionDescription": {
- "message": "हम आपके वॉलेट में भेजे गए नए टोकन का पता लगाने और प्रदर्शित करने के लिए तीसरे-पक्ष API का उपयोग करते हैं। बंद करें यदि आप नहीं चाहते कि MetaMask उन सेवाओं से डेटा पुल करे।"
- },
"useTokenDetectionPrivacyDesc": {
"message": "आपके खाते में भेजे गए टोकन को स्वचालित रूप से प्रदर्शित करने में थर्ड पार्टी के सर्वर्स के साथ संचार शामिल रहेगा, जो टोकन के चित्रों को लाने का काम करते हैं। वे सर्वर्स आपके IP एड्रेस को एक्सेस कर पाएंगे।"
},
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "वॉलेट का निर्माण सफल हुआ"
},
+ "wantToAddThisNetwork": {
+ "message": "इस नेटवर्क को जोड़ना चाहते हैं?"
+ },
"warning": {
"message": "चेतावनी"
},
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "हां, आइए आजमाते हैं"
},
+ "youHaveAddedAll": {
+ "message": "आपने सभी लोकप्रिय नेटवर्क जोड़ लिए हैं। आप अधिक नेटवर्क खोज सकते हैं $1 या आप $2 कर सकते हैं",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "आपको इस सुविधा का उपयोग करने के लिए कैमरे तक पहुंच की अनुमति देने की आवश्यकता है।"
},
diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json
index 83c6ea82d..5c7b61784 100644
--- a/app/_locales/id/messages.json
+++ b/app/_locales/id/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "Tambahkan memo"
},
+ "addMoreNetworks": {
+ "message": "tambahkan jaringan secara manual"
+ },
"addNetwork": {
"message": "Tambahkan Jaringan"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "Peringatan"
},
+ "allOfYour": {
+ "message": "Seluruh $1 Anda",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "Izinkan ekstensi eksternal ini untuk:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "Setujui batas penggunaan"
},
+ "approveAllTokensTitle": {
+ "message": "Berikan izin untuk mengakses seluruh $1 Anda?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "Setujui & Instal"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "Aset yang disetujui"
},
- "areYouDeveloper": {
- "message": "Anda seorang pengembang?"
- },
"areYouSure": {
"message": "Anda yakin?"
},
@@ -438,7 +446,7 @@
"message": "Beli $1 dengan Wyre"
},
"buyWithWyreDescription": {
- "message": "Wyre memungkinkan Anda menggunakan kartu debit untuk menyetorkan ETH langsung di akun MetaMask Anda."
+ "message": "Orientasi mudah untuk pembelian hingga $ 1000. Verifikasi pembelian limit tinggi interaktif yang cepat. Mendukung Kartu Debit/Kredit, Apple Pay, Transfer Bank. Tersedia di 100+ negara. Token disetor ke Akun MetaMask Anda"
},
"bytes": {
"message": "Byte"
@@ -466,6 +474,13 @@
"message": "Untuk $1 suatu transaksi, biaya gas harus dinaikkan minimal 10% agar dapat dikenali oleh jaringan.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "Batalkan swap untuk ~$1",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "Batalkan swap gratis"
+ },
"cancellationGasFee": {
"message": "Biaya Pembatalan Gas"
},
@@ -650,7 +665,7 @@
"message": "Interaksi Kontrak"
},
"convertTokenToNFTDescription": {
- "message": "Kami mendeteksi bahwa aset ini merupakan NFT. Kini MetaMask memiliki dukungan asli penuh untuk NFT. Anda ingin menghapusnya dari daftar token dan menambahkannya sebagai NFT?"
+ "message": "Kami mendeteksi bahwa aset ini merupakan NFT. Kini MetaMask memiliki dukungan asli penuh untuk NFT. Ingin menghapusnya dari daftar token dan menambahkannya sebagai NFT?"
},
"convertTokenToNFTExistDescription": {
"message": "Kami mendeteksi bahwa aset ini telah ditambahkan sebagai NFT. Anda ingin menghapusnya dari daftar token?"
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "Gunakan $1 untuk menyesuaikan harga gas. Anda akan bingung jika tidak terbiasa. Berinteraksi dengan risiko Anda sendiri.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Menaikkan biaya dapat mengurangi waktu pemrosesan, namun tidak ada jaminan."
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "Deposit $1",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Deskripsi"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "Ada yang salah, dan kami tidak dapat menyelesaikan tindakan"
},
- "fakeTokenWarning": {
- "message": "Siapa pun dapat membuat token, termasuk membuat versi palsu dari token yang ada. Pelajari selengkapnya seputar $1"
- },
"fast": {
"message": "Cepat"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "Fungsi: Setujui"
},
+ "functionSetApprovalForAll": {
+ "message": "Fungsi: SetApprovalForAll"
+ },
"functionType": {
"message": "Jenis Fungsi"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "Gunakan $1 untuk menutupi lonjakan lalu lintas jaringan karena hal-hal seperti penurunan NFT populer.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "tinggi"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "Frasa Pemulihan Rahasia Tidak Valid"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "Masukan tidak valid! Frasa Pemulihan Rahasia peka terhadap huruf besar/kecil."
+ },
"ipfsGateway": {
"message": "Gateway IPFS"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Gunakan $1 untuk menunggu harga yang lebih murah. Estimasi waktu kurang akurat karena harga sedang tidak dapat diprediksi.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "rendah"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Gunakan $1 untuk pemrosesan cepat dengan harga pasar saat ini.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "memo"
@@ -1892,6 +1910,19 @@
"message": "memverifikasi detail jaringan",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "Kami menyarankan agar Anda $1 sebelum melanjutkan.",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "Menurut catatan kami, nama jaringan mungkin tidak sepenuhnya sesuai dengan ID rantai ini."
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "Simbol mata uang yang dikirimkan tidak sesuai dengan yang kami harapkan untuk ID rantai ini."
+ },
+ "mismatchedRpcUrl": {
+ "message": "Menurut catatan kami, nilai URL RPC yang dikirimkan tidak sesuai dengan penyedia yang dikenal untuk ID rantai ini."
+ },
"missingNFT": {
"message": "Tidak melihat NFT Anda?"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "Jaringan:"
},
+ "networkAddedSuccessfully": {
+ "message": "Jaringan berhasil ditambahkan!"
+ },
"networkDetails": {
"message": "Detail Jaringan"
},
@@ -2059,6 +2093,9 @@
"message": "Nonce lebih tinggi dari nonce $1 yang disarankan",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "Masukkan ID koleksi"
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "Lihat di pengaturan",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "Deteksi token yang ditingkatkan saat ini tersedia di jaringan Ethereum Mainnet, Polygon, BSC, dan Avalanche. Nantikan selengkapnya!"
@@ -2154,11 +2191,21 @@
"message": "Aktifkan mode gelap"
},
"notifications12Description": {
- "message": "Mode Gelap akan diaktifkan untuk pengguna baru tergantung preferensi sistem mereka. Untuk pengguna lama, aktifkan Mode Gelap secara manual di bawah Pengaturan -> Eksperimental."
+ "message": "Mode gelap pada Ekstensi akhirnya hadir! Untuk menyalakannya, buka Pengaturan -> Eksperimental dan pilih salah satu opsi tampilan: Terang, Gelap, Sistem."
},
"notifications12Title": {
"message": "Kapan mode gelap? Ini saatnya mode gelap! ️🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "Tampilkan daftar jaringan khusus"
+ },
+ "notifications13Description": {
+ "message": "Kini Anda dapat menambahkan jaringan khusus populer berikut dengan mudah: Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm, dan Polygon! Untuk mengaktifkan fitur ini, buka Pengaturan -> Eksperimental dan aktifkan \"Tampilkan daftar jaringan khusus\"!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "Tambahkan Jaringan Populer"
+ },
"notifications1Description": {
"message": "Pengguna MetaMask Mobile kini bisa menukar token di dalam dompet seluler mereka. Pindai kode QR untuk mendapatkan aplikasi seluler dan mulai menukar.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "Buka Pengaturan Lanjutan",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "Pada MetaMask v10.4.0, Anda tidak lagi memerlukan Ledger Live untuk menghubungkan perangkat Ledger Anda ke MetaMask.",
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "Tandai semua telah dibaca"
},
- "numberOfNewTokensDetected": {
- "message": "$1 token baru ditemukan di akun ini",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "dari"
},
@@ -2344,9 +2387,6 @@
"message": "Buka MetaMask dalam layar penuh untuk menghubungkan ledger Anda melalui WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Periksa kode sumbernya"
- },
"optional": {
"message": "Opsional"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "Jenis Koneksi Ledger Pilihan",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Mempersiapkan pertukaran..."
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "Ungkap frasa seed"
},
+ "revokeAllTokensTitle": {
+ "message": "Cabut izin untuk mengakses seluruh $1 Anda?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "Dengan mencabut izin, $1 berikut tidak lagi dapat mengakses $2 Anda",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Jaringan Uji Rinkeby"
},
@@ -2852,12 +2900,23 @@
"message": "Mengirim $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "Peringatan: Anda akan mengirim kontrak token yang berpotensi mengakibatkan hilangnya dana. $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "Atur pengaturan privasi lanjutan"
},
"setAdvancedPrivacySettingsDetails": {
"message": "MetaMask menggunakan layanan pihak ketiga tepercaya ini untuk meningkatkan kegunaan dan keamanan produk."
},
+ "setApprovalForAll": {
+ "message": "Atur Persetujuan untuk Semua"
+ },
+ "setApprovalForAllTitle": {
+ "message": "Setujui $1 tanpa batas penggunaan",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "Pengaturan"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "Pilih ini untuk menampilkan biaya gas dan kontrol batas secara langsung di layar kirim dan konfirmasi."
},
+ "showCustomNetworkList": {
+ "message": "Tampilkan Daftar Jaringan Khusus"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "Pilih ini untuk menampilkan daftar jaringan dengan detail yang telah diisi saat menambahkan jaringan baru."
+ },
"showFiatConversionInTestnets": {
"message": "Tampilkan Konversi di Testnet"
},
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "Untuk mengonfirmasikan Anda sudah paham, centang semua."
},
- "snapInstallWarningKeyAccess": {
- "message": "Anda memberikan akses kunci ke snap \"$1\". Tindakan ini tidak dapat dibatalkan dan memberikan kendali \"$1\" atas akun dan aset Anda. Sebelum melanjutkan, pastikan \"$1\" aman.",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "Snap ini meminta izin berikut:"
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "Snap hanya akan beroperasi jika diaktifkan"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "Beberapa jaringan dapat menimbulkan risiko keamanan dan/atau privasi. Pahami risikonya sebelum menambahkan & menggunakan jaringan."
+ },
"somethingWentWrong": {
"message": "Ups! Ada yang salah."
},
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "Beralih Jaringan"
},
+ "switchToNetwork": {
+ "message": "Beralih ke $1",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "Beralih ke akun ini"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "$1 jaringan pengujian",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Token"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "Saat ini deteksi token tersedia di $1. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "Baru! Deteksi token yang ditingkatkan tersedia di Ethereum Mainnet sebagai fitur eksperimental. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "API token ConsenSys mengumpulkan daftar token dari berbagai daftar token pihak ketiga. Menonaktifkannya akan menghentikan deteksi token baru yang ditambahkan ke dompet Anda, tetapi Anda akan tetap memiliki opsi untuk mencari token yang akan diimpor."
- },
"tokenId": {
"message": "ID token"
},
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "Ups! Ada yang salah..."
},
+ "unknownCollection": {
+ "message": "Koleksi tanpa nama"
+ },
"unknownNetwork": {
"message": "Jaringan Privat Tidak Dikenal"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "Menampilkan peringatan untuk domain pengelabuan yang menargetkan pengguna Ethereum"
},
- "useTokenDetection": {
- "message": "Gunakan Deteksi Token"
- },
- "useTokenDetectionDescription": {
- "message": "Kami menggunakan API pihak ketiga untuk mendeteksi dan menampilkan token baru yang dikirim ke dompet Anda. Matikan jika Anda tidak ingin MetaMask memakai data dari layanan tersebut."
- },
"useTokenDetectionPrivacyDesc": {
"message": "Menampilkan token yang dikirim ke akun Anda secara otomatis yang melibatkan komunikasi dengan server pihak ketiga untuk mengambil gambar token. Server tersebut akan memiliki akses ke alamat IP Anda."
},
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "Dompet berhasil dibuat"
},
+ "wantToAddThisNetwork": {
+ "message": "Ingin menambahkan jaringan ini?"
+ },
"warning": {
"message": "Peringatan"
},
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "Ya, mari kita coba"
},
+ "youHaveAddedAll": {
+ "message": "Anda telah menambahkan semua jaringan populer. Anda dapat menemukan lebih banyak jaringan $1 atau dapat $2",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "Anda harus mengizinkan akses kamera untuk menggunakan fitur ini."
},
diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json
index bf140ebfd..6e24b8903 100644
--- a/app/_locales/ja/messages.json
+++ b/app/_locales/ja/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "メモを追加"
},
+ "addMoreNetworks": {
+ "message": "他のネットワークを手動で追加"
+ },
"addNetwork": {
"message": "ネットワークを追加"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "アラート"
},
+ "allOfYour": {
+ "message": "すべての $1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "この外部拡張機能に次の操作を許可します"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "使用限度額の承認"
},
+ "approveAllTokensTitle": {
+ "message": "すべての $1 へのアクセスを許可しますか?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "承認してインストール"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "承認済みのアセット"
},
- "areYouDeveloper": {
- "message": "開発者の方ですか?"
- },
"areYouSure": {
"message": "よろしいですか?"
},
@@ -435,10 +443,10 @@
"description": "$1 represents the crypto symbol to be purchased"
},
"buyWithWyre": {
- "message": "Wyreで $1 を購入"
+ "message": "Wyreで$1を購入"
},
"buyWithWyreDescription": {
- "message": "Wyreを使用すると、デビット カードを使用して、$1 をMetaMaskアカウントに直接デポジットできます。"
+ "message": "簡単なオンボーディングプロセスで最高 $ 1000 購入可能。迅速かつインタラクティブな高限度額の購入検証。デビット・クレジットカード、Apple Pay、銀行送金に対応。100か国以上で利用可能。トークンは MetaMask アカウントに入金されます。"
},
"bytes": {
"message": "バイト"
@@ -466,6 +474,13 @@
"message": "トランザクションを$1するには、ネットワークに認識されるようにガス代を 10% 以上増額する必要があります。",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "$1 以下でスワップをキャンセル",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "無料でスワップをキャンセル"
+ },
"cancellationGasFee": {
"message": "キャンセルのガス代"
},
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "ガス代をカスタマイズするには$1を使用します。慣れていない場合はわかりにくい可能性があります。自己責任で操作してください。",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "手数料を増やすと処理時間は短くなる可能性がありますが、必ずそうなるとは限りません。"
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "$1 を入金",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "説明"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "問題が発生しました。アクションを完了させることができません"
},
- "fakeTokenWarning": {
- "message": "既存のトークンの偽のバージョンの作成を含め、誰でもトークンを作成できます。$1に関する詳細をご覧ください"
- },
"fast": {
"message": "高速"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "機能: 承認"
},
+ "functionSetApprovalForAll": {
+ "message": "関数: SetApprovalForAll"
+ },
"functionType": {
"message": "機能の種類"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "人気のNFTドロップなどによるネットワークトラフィックの急増に備えるため、$1を使用してください。",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "高"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "無効なシークレットリカバリーフレーズ"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "入力値が無効です!秘密のリカバリーフレーズは大文字・小文字が区別されます。"
+ },
"ipfsGateway": {
"message": "IPFSゲートウェイ"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "値下がりを待つには$1を使用してください。価格がやや予測不能なため、予想時間はあまり正確ではありません。",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "低"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "現在の市場価格での迅速な処理には、$1を使用してください。",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "メモ"
@@ -1892,6 +1910,19 @@
"message": "ネットワークの詳細の確認",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "先に進む前に$1をお勧めします。",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "弊社の記録によると、ネットワーク名がこのチェーン ID と正しく一致していない可能性があります。"
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "送信された通貨記号がこのチェーン ID に関して予想されるものと一致していません。"
+ },
+ "mismatchedRpcUrl": {
+ "message": "弊社の記録によると、送信された RPC URL の値がこのチェーン ID の既知のプロバイダーと一致しません。"
+ },
"missingNFT": {
"message": "NFTが見当たりませんか?"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "ネットワーク:"
},
+ "networkAddedSuccessfully": {
+ "message": "ネットワークが追加されました!"
+ },
"networkDetails": {
"message": "ネットワークの詳細"
},
@@ -2059,6 +2093,9 @@
"message": "ナンスが提案され$1よりも大きいです",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "コレクティブルIDを入力してください"
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "設定に移動",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "改善されたトークン検出は、現在 Ethereum Mainnet、Polygon、BSC、Avalanche ネットワークで利用できます。他のネットワークも追加される予定です!"
@@ -2154,11 +2191,21 @@
"message": "ダークモードを有効にする"
},
"notifications12Description": {
- "message": "新規ユーザーの場合、システム設定に従ってダークモードが有効になります。既存のユーザーは、設定 -> 実験的機能で、ダークモードを手動で有効にできます。"
+ "message": "拡張機能のダークモードがついに追加されました!オンにするには、設定 - 実験的機能の順に移動し、ライト、ダーク、システムの表示オプションから一つを選択してください。"
},
"notifications12Title": {
"message": "いつダークモードに?今ダークモードです!🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "カスタムネットワークリストを表示"
+ },
+ "notifications13Description": {
+ "message": "人気のカスタムネットワーク(Arbitrum、Avalanche、Binance Smart Chain、Fantom、Harmony、Optimism、Palm、Polygon)が簡単に追加できるようになりました!この機能を有効にするには、設定 -> 実験的機能に移動し、「カスタムネットワークリストを表示」をオンにしてください!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "人気のネットワークを追加"
+ },
"notifications1Description": {
"message": "MetaMask Mobileのユーザーが、モバイルウォレット内でトークンを交換できるようになりました。QRコードをスキャンしてモバイルアプリを取得し、スワップを開始します。",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "高度な設定に移動",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "MetaMask v10.4.0以降では、LedgerデバイスのMetaMaskへの接続にLedger Liveが不要になりました。",
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "すべて既読にする"
},
- "numberOfNewTokensDetected": {
- "message": "$1 の新しいトークンがこのアカウントで見つかりました",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "中の"
},
@@ -2344,9 +2387,6 @@
"message": "WebHIDでLedgerを接続するには、MetaMaskを全画面モードで開いてください。",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "ソースコードを確認"
- },
"optional": {
"message": "任意"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "優先Ledger接続タイプ",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "スワップを準備しています..."
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "シードフレーズを表示"
},
+ "revokeAllTokensTitle": {
+ "message": "すべての $1 へのアクセス許可を取り消しますか?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "アクセス許可を取り消すと、次の $1 が今後 $2 にアクセスできなくなります",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Rinkebyテストネットワーク"
},
@@ -2852,12 +2900,23 @@
"message": "$1を送信中",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "警告: 資金の喪失に繋がる可能性のあるトークンコントラクトに送信しようとしています。$1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "高度なプライバシー設定を設定"
},
"setAdvancedPrivacySettingsDetails": {
"message": "MetaMaskはこれらの信頼できるサードパーティーサービスを使用して、製品の使いやすさと安全性を向上させています。"
},
+ "setApprovalForAll": {
+ "message": "すべてを承認に設定"
+ },
+ "setApprovalForAllTitle": {
+ "message": "使用限度額なしで $1 を承認",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "設定"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "これを選択すると、ガス代と限度額のコントロールが送金画面と確認画面に直接表示されます。"
},
+ "showCustomNetworkList": {
+ "message": "カスタムネットワークリストを表示"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "新規ネットワークの追加時に事前に情報が入力済みのネットワークのリストを表示するには、これを選択します。"
+ },
"showFiatConversionInTestnets": {
"message": "テストネット上に変換を表示"
},
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "理解したことを確認するために、すべての項目にチェックを入れてください。"
},
- "snapInstallWarningKeyAccess": {
- "message": "スナップ「$1」に重要なアクセス権を付与しようとしています。これは取り消し不可能で、「$1」によるアカウントとアセットのコントロールが可能になります。続行する前に、「$1」が信頼できることを確認してください。",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "このスナップが次のパーミッションをリクエストしています:"
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "スナップは有効になっている場合にのみ実行されます"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "ネットワークによっては、セキュリティやプライバシーの面でリスクが伴う可能性があります。ネットワークを追加・使用する前にリスクを理解するようにしてください。"
+ },
"somethingWentWrong": {
"message": "申し訳ありません。問題が発生しました。"
},
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "ネットワークを切り替える"
},
+ "switchToNetwork": {
+ "message": "$1 に切り替える",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "このアカウントに切り替える"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "$1テストネットワーク",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "トークン"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "トークン検出は現在 $1 で利用可能です。$2"
},
- "tokenDetectionAnnouncement": {
- "message": "新機能! 実験的な機能として、Ethereum Mainnetでのトークン検出が改善されました。$1"
- },
- "tokenDetectionToggleDescription": {
- "message": "ConsenSys のトークン API は、さまざまなサードパーティのトークンリストからトークンのリストを集積します。これをオフにすると、ウォレットに追加された新しいトークンは検出されなくなりますが、引き続きトークンを検索してインポートすることは可能です。"
- },
"tokenId": {
"message": "トークン ID"
},
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "申し訳ありません。問題が発生しました..."
},
+ "unknownCollection": {
+ "message": "無名のコレクション"
+ },
"unknownNetwork": {
"message": "不明なプライベートネットワーク"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "イーサリアムユーザーを対象としたドメインのフィッシングに対して警告を表示します"
},
- "useTokenDetection": {
- "message": "トークン検出を使用"
- },
- "useTokenDetectionDescription": {
- "message": "弊社はユーザーのウォレットに送信された新しいトークンを検出して表示するために、サードパーティーAPIを使用します。MetaMaskにこれらのサービスからデータを取得させたくない場合は、この機能をオフにしてください。"
- },
"useTokenDetectionPrivacyDesc": {
"message": "アカウントに送られたトークンを自動的に表示するには、サードパーティーサーバーと通信し、トークンの画像を取得する必要があります。これらのサーバーはユーザーの IP アドレスにアクセスできます。"
},
@@ -3962,7 +4021,7 @@
"description": "$1 is the action type. e.g (Account, Transaction, Swap)"
},
"visitWebSite": {
- "message": "弊社Webサイトにアクセス"
+ "message": "弊社 Web サイトにアクセス"
},
"walletConnectionGuide": {
"message": "弊社のハードウェアウォレット接続ガイド"
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "ウォレットが作成されました"
},
+ "wantToAddThisNetwork": {
+ "message": "このネットワークを追加しますか?"
+ },
"warning": {
"message": "警告"
},
@@ -4049,11 +4111,15 @@
"yesLetsTry": {
"message": "はい、やってみます"
},
+ "youHaveAddedAll": {
+ "message": "すべての人気ネットワークを追加しました。$1で他のネットワークを発見するか、$2できます。",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "この機能を使用するには、カメラへのアクセスを許可する必要があります。"
},
"youSign": {
- "message": "署名しています"
+ "message": "著名しています"
},
"yourPrivateSeedPhrase": {
"message": "秘密のシークレットリカバリーフレーズ"
diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json
index 8a5ba0a7b..c5e1cb03b 100644
--- a/app/_locales/ko/messages.json
+++ b/app/_locales/ko/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "메모 추가"
},
+ "addMoreNetworks": {
+ "message": "네트워크 직접 추가"
+ },
"addNetwork": {
"message": "네트워크 추가"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "경고"
},
+ "allOfYour": {
+ "message": "내 $1 모두",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "이 외부 확장을 통해 다음을 허용:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "지출 한도 승인"
},
+ "approveAllTokensTitle": {
+ "message": "내 모든 $1에 액세스할 수 있는 권한을 부여할까요?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "승인 및 설치"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "승인된 자산"
},
- "areYouDeveloper": {
- "message": "개발자이신가요?"
- },
"areYouSure": {
"message": "확실한가요?"
},
@@ -438,7 +446,7 @@
"message": "Wyre로 $1 구매"
},
"buyWithWyreDescription": {
- "message": "Wyre를 사용하면 체크카드를 이용하여 $1 를 MetaMask 계정에 바로 예치할 수 있습니다."
+ "message": "최대 $ 1000 구매까지 간편한 온보딩. 신속한 대화형 상한 구매 확인. 직불/신용 카드, Apple Pay, 은행 송금 지원. 100여국 이상에서 사용 가능. MetaMask 계정으로 토큰 입금"
},
"bytes": {
"message": "바이트"
@@ -466,6 +474,13 @@
"message": "거래를 $1하려면 가스비를 최소 10%를 인상해야 네트워크에서 인식될 수 있습니다.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "~$1 비용으로 스왑 취소",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "무료로 스왑 취소"
+ },
"cancellationGasFee": {
"message": "가스 수수료 취소"
},
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "$1을(를) 사용하여 가스 가격을 맞춤설정하세요. 익숙하지 않은 경우 혼동될 수 있습니다. 자신의 책임하에 상호 작용하세요.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "수수료를 올리면 처리 시간이 단축되기도 하지만 항상 그렇지는 않습니다."
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "$1 입금",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "설명"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "문제가 발생했습니다. 작업을 완료할 수 없습니다."
},
- "fakeTokenWarning": {
- "message": "기존 토큰의 가짜 버전 생성을 포함하여 누구나 토큰을 생성할 수 있습니다. $1에 대해 자세히 알아보기"
- },
"fast": {
"message": "빠름"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "기능: 승인"
},
+ "functionSetApprovalForAll": {
+ "message": "기능: 모두승인설정"
+ },
"functionType": {
"message": "기능 유형"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "인기 있는 NFT의 하락 등으로 인한 네트워크 트래픽 급증을 커버하려면 $1을 사용하세요.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "높음"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "잘못된 비밀 복구 구문"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "입력 오류! 비밀 복구 구문은 대소문자를 구분해야 합니다."
+ },
"ipfsGateway": {
"message": "IPFS 게이트웨이"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "$1 사용을 통해 더 저렴한 가격을 기다리세요. 가격 예측이 힘들기 때문에 시간 추정은 더욱 부정확합니다.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "낮음"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "현재 시장 가격으로 빠르게 처리할 수 있도록 $1을(를) 사용하세요.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "메모"
@@ -1892,6 +1910,19 @@
"message": "네트워크 세부 정보 검증",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "계속 진행하기 전에 $1 확인을 권합니다.",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "기록에 따르면 네트워크 이름이 이 체인 ID와 일치하지 않는 것 같습니다."
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "제출하신 환율 기호가 이 체인 ID의 환율과 일치하지 않습니다."
+ },
+ "mismatchedRpcUrl": {
+ "message": "기록에 따르면 제출하신 RPC URL 값이 이 체인 ID에 대해 알려진 공급업체와 일치하지 않습니다."
+ },
"missingNFT": {
"message": "NFT가 보이지 않나요?"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "네트워크:"
},
+ "networkAddedSuccessfully": {
+ "message": "성공적으로 네트워크를 추가했습니다!"
+ },
"networkDetails": {
"message": "네트워크 세부 정보"
},
@@ -2059,6 +2093,9 @@
"message": "임시값이 권장 임시값인 $1보다 큽니다.",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT\n"
+ },
"nftTokenIdPlaceholder": {
"message": "수집 가능한 ID를 입력하세요."
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "설정으로 이동하기",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "향상된 토큰 감지 기능을 현재 이더리움 메인넷과 Polygon, BSC, Avalanche 네트워크에서 이용할 수 있습니다. 더 많은 기능이 준비 중입니다!"
@@ -2154,11 +2191,21 @@
"message": "다크모드 활성화"
},
"notifications12Description": {
- "message": "신규 사용자의 경우 시스템 설정에 따라 다크모드가 활성화됩니다. 기존의 사용자는 설정 -> 실험에서 직접 다크 모드를 활성화해야 합니다."
+ "message": "다크모드가 마침내 활성화되었습니다! 설정(Settings) -> 시험 기능(Experimental)으로 이동하여 라이트, 다크, 시스템 중 선택하세요."
},
"notifications12Title": {
"message": "다크모드를 원하세요? 이제 다크모드를 사용하세요! 🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "사용자 정의 네트워크 목록 보기"
+ },
+ "notifications13Description": {
+ "message": "이제 Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm 및 Polygon과 같은 인기 있는 사용자 정의 네트워크를 쉽게 추가할 수 있습니다! 이 기능을 활성화하려면 설정 -> 실험으로 이동하여 \"사용자 지정 네트워크 목록 표시\"를 켜세요!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "인기 네트워크 추가"
+ },
"notifications1Description": {
"message": "MetaMask 모바일 사용자는 이제 모바일 지갑에서 토큰을 스왑할 수 있습니다. QR 코드를 스캔하여 모바일 앱을 설치하고 스왑을 시작하세요.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "고급 설정으로 이동하기",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "MetaMask v10.4.0부터는 Ledger 장치를 MetaMask에 연결할 때 더 이상 Ledger Live를 사용하지 않아도 됩니다.",
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "모두 읽음으로 표시"
},
- "numberOfNewTokensDetected": {
- "message": "계정에서 $1개의 새로운 토큰이 발견됨",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "/"
},
@@ -2344,9 +2387,6 @@
"message": "전체 화면에서 MetaMask를 열어 WebHID를 통해 Ledger를 연결합니다.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "소스 코드를 확인하세요"
- },
"optional": {
"message": "옵션"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "선호하는 Ledger 연결 유형",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "스왑 준비 중..."
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "시드 구문 보기"
},
+ "revokeAllTokensTitle": {
+ "message": "내 모든 $1에 액세스할 수 있는 권한을 취소할까요?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "권한을 취소하면 다음 $1의 $2 권한은 더 이상 유효하지 않습니다",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Rinkeby 테스트 네트워크"
},
@@ -2852,12 +2900,23 @@
"message": "$1 보내기",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "경고: 토큰 주소를 전송하면 토큰이 손실될 수 있습니다. $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "개인정보 설정 고급 지정"
},
"setAdvancedPrivacySettingsDetails": {
"message": "이와 같이 MetaMask는 신용있는 타사의 서비스를 사용하여 제품 가용성과 안전성을 향상합니다."
},
+ "setApprovalForAll": {
+ "message": "모두 승인 설정"
+ },
+ "setApprovalForAllTitle": {
+ "message": "$1 무제한 지출 승인",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "설정"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "이 항목을 선택하면 보내기 및 확인 화면에서 바로 가스 가격과 한도 조절을 확인할 수 있습니다."
},
+ "showCustomNetworkList": {
+ "message": "사용자 정의 네트워크 보기"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "이를 선택하면 새로 네트워크를 추가할 때 네트워크 목록에 상세 설명이 함께 나타납니다."
+ },
"showFiatConversionInTestnets": {
"message": "테스트넷에 전환 표시"
},
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "이해하셨으면 모두 체크해 주세요."
},
- "snapInstallWarningKeyAccess": {
- "message": "'$1' 스냅 이용에 필요한 키 액세스 권한을 부여하고 있습니다. 이 작업은 사용자의 계정과 자산에 '$1' 제어 권한을 부여하며 취소가 불가능합니다. '$1의 신뢰성을 확인한 후에 진행하세요.",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "이 스냅이 다음 권한을 요청하고 있습니다."
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "스냅은 활성화된 상태에서만 작동합니다."
},
+ "someNetworksMayPoseSecurity": {
+ "message": "네트워크에 따라 보안이나 개인 정보 유출의 위험이 있을 수 있습니다. 네트워크 추가 및 사용 이전에 위험 요소를 파악하세요."
+ },
"somethingWentWrong": {
"message": "죄송합니다! 문제가 생겼습니다."
},
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "네트워크 전환"
},
+ "switchToNetwork": {
+ "message": "$1 네트워크로 전환",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "이 계정으로 전환"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "$1 테스트 네트워크",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "토큰"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "토큰 감지 기능은 현재 $1. $2에서 사용할 수 있습니다."
},
- "tokenDetectionAnnouncement": {
- "message": "신규! 개선된 토큰 감지는 실험적 기능으로 이더리움 메인넷에서 사용할 수 있습니다. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "ConsenSys의 토큰 API는 타사의 다양한 목록을 모아 하나의 토큰 목록을 작성합니다. 이 API를 끄면 신규 토큰을 감지해서 지갑에 추가하는 기능은 중단되지만 토큰을 검색해서 가져오는 기능은 계속 사용할 수 있습니다."
- },
"tokenId": {
"message": "토큰 ID"
},
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "죄송합니다! 문제가 생겼습니다...."
},
+ "unknownCollection": {
+ "message": "제목 미지정 콜렉션"
+ },
"unknownNetwork": {
"message": "알 수 없는 비공개 네트워크"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "이더리움 사용자를 노리는 피싱 도메인에 대한 경고를 표시합니다"
},
- "useTokenDetection": {
- "message": "토큰 감지 사용"
- },
- "useTokenDetectionDescription": {
- "message": "당사는 타사 API를 사용하여 지갑으로 전송된 새 토큰을 감지하고 표시합니다. MetaMask가 해당 서비스에서 데이터를 가져오는 것을 원하지 않으면 이 기능을 사용하지 마세요."
- },
"useTokenDetectionPrivacyDesc": {
"message": "계정으로 전송된 토큰이 자동으로 표시되도록 하려면 타사 서버와의 통신을 통해 토큰 이미지를 불러와야 합니다. 이를 위해 타사 서버는 사용자의 IP 주소에 액세스하게 됩니다."
},
@@ -3962,7 +4021,7 @@
"description": "$1 is the action type. e.g (Account, Transaction, Swap)"
},
"visitWebSite": {
- "message": "당사 웹사이트 방문하기"
+ "message": "웹사이트를 방문하세요"
},
"walletConnectionGuide": {
"message": "당사의 하드웨어 지갑 연결 가이드"
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "지갑 생성 성공"
},
+ "wantToAddThisNetwork": {
+ "message": "이 네트워크를 추가할까요?"
+ },
"warning": {
"message": "경고"
},
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "예, 시도하겠습니다."
},
+ "youHaveAddedAll": {
+ "message": "모든 인기 네트워크를 추가했습니다. $1에서 더 많은 네트워크를 확인하거나 $2 할 수 있습니다.",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "이 기능을 사용하려면 카메라 액세스를 허용해야 합니다."
},
diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json
index 9d64198d3..af0a3c7a6 100644
--- a/app/_locales/ph/messages.json
+++ b/app/_locales/ph/messages.json
@@ -2216,7 +2216,7 @@
"message": "Welcome sa MetaMask"
},
"welcomeBack": {
- "message": "Welcome Back!"
+ "message": "Welcome back!"
},
"whatsNew": {
"message": "Ano'ng bago",
diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json
index b8b40ee7a..3e6e965b2 100644
--- a/app/_locales/pt/messages.json
+++ b/app/_locales/pt/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "Adicionar observação"
},
+ "addMoreNetworks": {
+ "message": "adicionar mais redes manualmente"
+ },
"addNetwork": {
"message": "Adicionar rede"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "Alertas"
},
+ "allOfYour": {
+ "message": "Todos os seus $1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "Permitir que essa extensão externa:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "Aprovar"
},
+ "approveAllTokensTitle": {
+ "message": "Dar permissão para acessar todos os seus $1?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "Aprovar e instalar"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "Ativo aprovado"
},
- "areYouDeveloper": {
- "message": "Você é desenvolvedor?"
- },
"areYouSure": {
"message": "Tem certeza?"
},
@@ -435,10 +443,10 @@
"description": "$1 represents the crypto symbol to be purchased"
},
"buyWithWyre": {
- "message": "Comprar $1 com Wyre"
+ "message": "Comprar $1 com o Wyre"
},
"buyWithWyreDescription": {
- "message": "Com o Wyre, você pode usar um cartão de débito para depositar $1 diretamente na sua conta da MetaMask."
+ "message": "Integração fácil para compras de até US$ 1.000. Verificação de compra de alto limite rápida e interativa. Aceita cartão de crédito/débito, Apple Pay, transferências bancárias. Disponível em mais de 100 países. Depósito de tokens em sua conta na MetaMask"
},
"bytes": {
"message": "Bytes"
@@ -466,6 +474,13 @@
"message": "Para $1 uma transação, a taxa de gás deve ser aumentada em pelo menos 10% para que seja reconhecida pela rede.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "Cancelar swap por ~$1",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "Cancelar swap gratuitamente"
+ },
"cancellationGasFee": {
"message": "Taxa de gás por cancelamento"
},
@@ -650,7 +665,7 @@
"message": "Interação com contrato"
},
"convertTokenToNFTDescription": {
- "message": "Detectamos que esse ativo é um NFT. A MetaMask agora oferece suporte nativo a NFTs. Gostaria de removê-lo da sua lista de tokens e adicioná-lo como NFT?"
+ "message": "Detectamos que esse ativo é um NFT. A MetaMask agora oferece suporte nativo a NFTs. Gostaria de removê-lo de sua lista de tokens e adicioná-lo como NFT?"
},
"convertTokenToNFTExistDescription": {
"message": "Detectamos que esse ativo foi adicionado como NFT. Deseja removê-lo da sua lista de tokens?"
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "Use $1 para personalizar o preço do gás. Isso pode parecer confuso se você não estiver familiarizado. Interaja por sua conta e risco.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Aumentar a taxa pode diminuir o tempo de processamento, mas isso não é garantido."
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "Depositar $1",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Descrição"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "Ocorreu algum erro e não conseguimos concluir a ação"
},
- "fakeTokenWarning": {
- "message": "Qualquer um pode criar um token, incluindo versões falsas de tokens existentes. Saiba mais sobre $1"
- },
"fast": {
"message": "Rápido"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "Função: aprovar"
},
+ "functionSetApprovalForAll": {
+ "message": "Função: SetApprovalForAll"
+ },
"functionType": {
"message": "Tipo de função"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "Alta probabilidade, mesmo em mercados voláteis. Use $1 para cobrir picos no tráfego de rede em razão de coisas como quedas em NFTs populares.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "alta"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "Frase Secreta de Recuperação inválida"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "Entrada inválida! A frase secreta de recuperação diferencia maiúsculas e minúsculas."
+ },
"ipfsGateway": {
"message": "Gateway IPFS"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Use $1 para aguardar um preço mais baixo. As estimativas de tempo são muito menos exatas, pois os preços são relativamente imprevisíveis.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "baixa"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Use $1 para um processamento rápido pelo preço atual de mercado.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "memorando"
@@ -1892,6 +1910,19 @@
"message": "verifique os detalhes da rede",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "Recomendamos que você $1 antes de prosseguir.",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "De acordo com os nossos registros, o nome da rede pode não corresponder à ID desta cadeia."
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "O símbolo de moeda enviado não corresponde ao esperado para a ID desta cadeia."
+ },
+ "mismatchedRpcUrl": {
+ "message": "De acordo com os nossos registros, o valor da URL da RPC enviado não corresponde a um provedor conhecido da ID desta cadeia."
+ },
"missingNFT": {
"message": "Não está vendo o seu NFT?"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "Ethereum:"
},
+ "networkAddedSuccessfully": {
+ "message": "Rede adicionada com sucesso!"
+ },
"networkDetails": {
"message": "Detalhes da rede"
},
@@ -2059,6 +2093,9 @@
"message": "Nonce é maior que o nonce sugerido de $1",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "Insira o ID do token"
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "Veja em configurações",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "A detecção aperfeiçoada de tokens está atualmente disponível nas redes Ethereum Mainnet, Polygon, BSC e Avalanche. Há outras por vir!"
@@ -2154,11 +2191,21 @@
"message": "Ativar o modo escuro"
},
"notifications12Description": {
- "message": "O Modo Escuro será ativado para novos usuários de acordo com suas preferências de sistema. Para usuários antigos, ative o Modo Escuro manualmente em Configurações -> Experimental."
+ "message": "O modo escuro na extensão finalmente chegou! Para ativá-lo, acesse Configurações -> Experimental e selecione uma das opções de exibição: claro, escuro, sistema."
},
"notifications12Title": {
"message": "Modo escuro quando? Modo escuro agora! 🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "Exibir lista de redes personalizadas"
+ },
+ "notifications13Description": {
+ "message": "Agora você pode adicionar facilmente as seguintes redes personalizadas que são populares: Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm e Polygon. Para ativar esse recurso, acesse Configurações -> Experimentais e ative \"Exibir lista de redes personalizadas\".",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "Adicionar redes populares"
+ },
"notifications1Description": {
"message": "Usuários da MetaMask Mobile agora podem trocar tokens dentro de sua carteira mobile. Leia o QR code para obter o aplicativo para dispositivos móveis e comece a trocar.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "Ir para Configurações Avançadas",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "A partir da MetaMask v10.4.0, não é mais necessário o Ledger Live para conectar o seu dispositivo Ledger à MetaMask.",
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "Marcar todas como lidas"
},
- "numberOfNewTokensDetected": {
- "message": "$1 novos tokens encontrados nesta conta",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "de"
},
@@ -2344,9 +2387,6 @@
"message": "Abra a MetaMask em tela cheia para conectar sua ledger por meio do WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Verifique o código-fonte"
- },
"optional": {
"message": "Opcional"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "Tipo de conexão preferencial com o Ledger",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Preparando swap..."
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "Revelar a frase de recuperação"
},
+ "revokeAllTokensTitle": {
+ "message": "Revogar permissão de acesso a todos os seus $1?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "Ao revogar a permissão, o $1 a seguir não terá mais acesso ao seu $2",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Rede de Teste Rinkeby"
},
@@ -2852,12 +2900,23 @@
"message": "Enviando $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "Aviso: você está prestes a enviar a um contrato de token que pode resultar em perda de fundos. $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "Definir configurações avançadas de privacidade"
},
"setAdvancedPrivacySettingsDetails": {
"message": "A MetaMask utiliza esses serviços terceirizados de confiança para aumentar a usabilidade e a segurança dos produtos."
},
+ "setApprovalForAll": {
+ "message": "Definir aprovação para todos"
+ },
+ "setApprovalForAllTitle": {
+ "message": "Aprovar $1 sem limite de gastos",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "Definições"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "Selecione isso para mostrar o preço do gás e limitar os controles diretamente nas telas de envio e de confirmação."
},
+ "showCustomNetworkList": {
+ "message": "Exibir lista de redes personalizadas"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "Selecione esta opção para exibir uma lista de redes com as informações pré-preenchidas ao adicionar uma nova rede."
+ },
"showFiatConversionInTestnets": {
"message": "Mostrar conversão nas redes de teste"
},
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "Para confirmar que você entende, marque todas."
},
- "snapInstallWarningKeyAccess": {
- "message": "Você está concedendo ao snap \"$1\" acesso à sua chave. Isso é irrevogável e concede a \"$1\" controle sobre suas contas e ativos. Certifique-se de que confia em \"$1\" antes de prosseguir.",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "Esse snap está solicitando as seguintes permissões:"
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "O snap só será executado se estiver ativado"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "Algumas redes podem representar riscos de segurança e/ou privacidade. Tenha os riscos em mente antes de adicionar e usar uma rede."
+ },
"somethingWentWrong": {
"message": "Ops! Algo deu errado."
},
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "Trocar redes"
},
+ "switchToNetwork": {
+ "message": "Trocar para $1",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "Trocar para esta conta"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "$1 redes de teste",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Token"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "A detecção de tokens está atualmente disponível em $1. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "Novidade! A detecção aprimorada de token está disponível na Mainnet do Ethereum como uma funcionalidade experimental. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "A API de token da ConsenSys agrega uma lista de tokens de várias listas de tokens de terceiros. Se você desativá-la, não haverá detecção de novos tokens adicionados à sua carteira, mas continuará com a opção de procurar tokens para importar."
- },
"tokenId": {
"message": "ID do token"
},
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "Ops! Algo deu errado...."
},
+ "unknownCollection": {
+ "message": "Coleção sem nome"
+ },
"unknownNetwork": {
"message": "Rede Privada Desconhecida"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "Exibir uma advertência para os domínios de phishing destinados a usuários do Ethereum"
},
- "useTokenDetection": {
- "message": "Usar detecção de tokens"
- },
- "useTokenDetectionDescription": {
- "message": "Utilizamos APIs terceirizadas para detectar e exibir novos tokens enviados à sua carteira. Desative essa opção se não deseja que a MetaMask extraia dados desses serviços."
- },
"useTokenDetectionPrivacyDesc": {
"message": "A exibição automática de tokens enviados para a sua conta envolve a comunicação com servidores de terceiros para buscar as imagens dos tokens. Esses servidores terão acesso ao seu endereço IP."
},
@@ -3962,7 +4021,7 @@
"description": "$1 is the action type. e.g (Account, Transaction, Swap)"
},
"visitWebSite": {
- "message": "Visite o nosso site"
+ "message": "Visite nosso site"
},
"walletConnectionGuide": {
"message": "nosso guia de conexão com a carteira de hardware"
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "Carteira criada com sucesso"
},
+ "wantToAddThisNetwork": {
+ "message": "Desejar adicionar esta rede?"
+ },
"warning": {
"message": "Atenção"
},
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "Sim, vamos tentar"
},
+ "youHaveAddedAll": {
+ "message": "Você adicionou todas as redes populares. Você pode descobrir mais redes $1 Ou você pode $2",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "Você precisa permitir o acesso à câmera para usar esse recurso."
},
diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json
index 35a8c9b30..5b2c10459 100644
--- a/app/_locales/pt_BR/messages.json
+++ b/app/_locales/pt_BR/messages.json
@@ -1031,9 +1031,6 @@
"failureMessage": {
"message": "Ocorreu algum erro e não conseguimos concluir a ação"
},
- "fakeTokenWarning": {
- "message": "Qualquer um pode criar um token, incluindo versões falsas de tokens existentes. Saiba mais sobre $1"
- },
"fast": {
"message": "Rápido"
},
@@ -1517,7 +1514,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Use $1 para aguardar um preço mais baixo. As estimativas de tempo são muito menos exatas, pois os preços são relativamente imprevisíveis.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "baixa"
@@ -1549,7 +1546,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Use $1 para um processamento rápido pelo preço atual de mercado.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "nota"
@@ -1895,7 +1892,7 @@
},
"notifications8ActionText": {
"message": "Ir para Configurações Avançadas",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "A partir da MetaMask v10.4.0, não é mais necessário o Ledger Live para conectar o seu dispositivo Ledger à MetaMask.",
@@ -2069,7 +2066,7 @@
},
"preferredLedgerConnectionType": {
"message": "Tipo de conexão preferencial com o Ledger",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"prev": {
"message": "Anterior"
@@ -2991,7 +2988,7 @@
},
"toggleTestNetworks": {
"message": "$1 redes de teste",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Token"
@@ -3005,9 +3002,6 @@
"tokenDecimalFetchFailed": {
"message": "A casa decimal do token é necessária."
},
- "tokenDetectionAnnouncement": {
- "message": "Novidade! A detecção aprimorada de token está disponível na Mainnet do Ethereum como uma funcionalidade experimental. $1"
- },
"tokenId": {
"message": "ID do token"
},
@@ -3220,12 +3214,6 @@
"usePhishingDetectionDescription": {
"message": "Exibir uma advertência para os domínios de phishing destinados a usuários do Ethereum"
},
- "useTokenDetection": {
- "message": "Usar detecção de tokens"
- },
- "useTokenDetectionDescription": {
- "message": "Utilizamos APIs terceirizadas para detectar e exibir novos tokens enviados à sua carteira. Desative essa opção se não deseja que a MetaMask extraia dados desses serviços."
- },
"usedByClients": {
"message": "Usado por diversos clientes diferentes"
},
diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json
index 16fa10c05..46090715e 100644
--- a/app/_locales/ru/messages.json
+++ b/app/_locales/ru/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "Добавить примечание"
},
+ "addMoreNetworks": {
+ "message": "добавить другие сети вручную"
+ },
"addNetwork": {
"message": "Добавить сеть"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "Предупреждения"
},
+ "allOfYour": {
+ "message": "Все ваши $1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "Разрешить этому внешнему расширению:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "Одобрить лимит расходов"
},
+ "approveAllTokensTitle": {
+ "message": "Разрешить доступ к всем вашим $1?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "Одобрить и установить"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "Одобренный актив"
},
- "areYouDeveloper": {
- "message": "Вы разработчик?"
- },
"areYouSure": {
"message": "Вы уверены?"
},
@@ -435,10 +443,10 @@
"description": "$1 represents the crypto symbol to be purchased"
},
"buyWithWyre": {
- "message": "Купить $1 с помощью Wyre"
+ "message": "Купить 1 $ с помощью Wyre"
},
"buyWithWyreDescription": {
- "message": "Wyre позволяет использовать дебетовую карту для внесения $1 прямо на ваш счет MetaMask."
+ "message": "Простая регистрация для покупок на сумму до 1000 $. Быстрая интерактивная проверка покупки с высоким лимитом. Поддерживает дебетовые/кредитные карты, Apple Pay, банковские переводы. Доступно в более чем 100 странах. Токены зачисляются на ваш счет MetaMask"
},
"bytes": {
"message": "Байты"
@@ -466,6 +474,13 @@
"message": "Чтобы $1 транзакции плата за газ должна быть увеличена как минимум на 10%. Это позволит обеспечить прием транзакции сетью.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "Отменить обмен на ~$1",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "Отменить обмен бесплатно"
+ },
"cancellationGasFee": {
"message": "Плата за газ при отмене"
},
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "Использовать $1, чтобы настроить цену на газ. Это может сбивать с толку, если вы не знакомы с этим. Взаимодействуйте на свой страх и риск.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Увеличение комиссии может сократить время обработки, но это не гарантируется."
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "Внесите $1",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Описание"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "Что-то пошло не так, и мы не смогли завершить действие"
},
- "fakeTokenWarning": {
- "message": "Кто угодно может создать токен, в том числе создать поддельные версии существующих токенов. Узнайте подробнее о $1"
- },
"fast": {
"message": "Быстрый"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "Функция: Одобрить"
},
+ "functionSetApprovalForAll": {
+ "message": "Функция: SetApprovalForAll"
+ },
"functionType": {
"message": "Тип функции"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "Используйте $1, чтобы компенсировать скачки сетевого трафика из-за таких событий, как дропы популярных NFT.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "высокая"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "Неверная секретная фраза для восстановления"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "Неправильный ввод! Секретная фраза для восстановления чувствительна к регистру."
+ },
"ipfsGateway": {
"message": "Шлюз IPFS"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Используйте $1, чтобы дождаться более низкой цены. Оценки времени намного менее точны, поскольку цены в некоторой степени непредсказуемы.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "низкая"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Используйте $1 для быстрой обработки по текущей рыночной цене.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "заметка"
@@ -1892,6 +1910,19 @@
"message": "проверить сведения о сети",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "Мы рекомендуем вам $1, прежде чем продолжить.",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "Согласно нашим данным, имя сети может не соответствовать этому идентификатору блокчейна."
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "Представленный символ валюты не соответствует тому, что мы ожидаем для этого идентификатора блокчейна."
+ },
+ "mismatchedRpcUrl": {
+ "message": "Согласно нашим записям, отправленное значение URL-адреса RPC не соответствует известному поставщику для этого идентификатора блокчейна."
+ },
"missingNFT": {
"message": "Не видите свои NFT?"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "Сеть:"
},
+ "networkAddedSuccessfully": {
+ "message": "Сеть успешно добавлена!"
+ },
"networkDetails": {
"message": "Сведения о сети"
},
@@ -2059,6 +2093,9 @@
"message": "Одноразовый номер больше, чем предложенный одноразовый номер $1",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "Введите ид. коллекционного актива"
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "Смотреть в настройках",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "Улучшенное обнаружение токенов в настоящее время доступно в сетях Ethereum Mainnet, Polygon, BSC и Avalanche. Это еще не все!"
@@ -2159,6 +2196,16 @@
"notifications12Title": {
"message": "Когда появится темный режим? Он уже появился! ️"
},
+ "notifications13ActionText": {
+ "message": "Показать пользовательский список сетей"
+ },
+ "notifications13Description": {
+ "message": "Теперь вы можете легко добавить следующие популярные пользовательские сети: Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm и Polygon! Чтобы включить эту функцию, перейдите в «Настройки» -> «Экспериментальная версия» и включите «Показать пользовательский список сетей»!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "Добавить популярные сети"
+ },
"notifications1Description": {
"message": "Теперь пользователи MetaMask Mobile могут обменивать токены в своем мобильном кошельке. Отсканируйте QR-код, чтобы скачать мобильное приложение и начать обмен.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "Перейти в Дополнительные настройки",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "Начиная с версии MetaMask 10.4.0, вам больше не требуется Ledger Live для подключения леджера к MetaMask.",
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "Отметить все как прочитанные"
},
- "numberOfNewTokensDetected": {
- "message": "$1 новых токена(-ов) найдены в этом аккаунте",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "из"
},
@@ -2344,9 +2387,6 @@
"message": "Откройте MetaMask в полноэкранном режиме, чтобы подключить свой леджер через WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Проверьте исходный код"
- },
"optional": {
"message": "Необязательно"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "Предпочтительный тип подключения к леджеру",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Подготовка обмена..."
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "Показать сид-фразу"
},
+ "revokeAllTokensTitle": {
+ "message": "Отозвать разрешение на доступ ко всем вашим $1?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "При отмене разрешения следующий $1 больше не сможет получить доступ к вашему $2",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Тестовая сеть Rinkeby"
},
@@ -2852,12 +2900,23 @@
"message": "Отправка $1...",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "Предупреждение: вы собираетесь отправить токен-контракт, что может привести к потере средств. $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "Задать дополнительные настройки конфиденциальности"
},
"setAdvancedPrivacySettingsDetails": {
"message": "MetaMask использует эти доверенные сторонние сервисы для повышения удобства использования и безопасности продукта."
},
+ "setApprovalForAll": {
+ "message": "Установить одобрение для всех"
+ },
+ "setApprovalForAllTitle": {
+ "message": "Одобрить $1 без ограничений по расходам",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "Настройки"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "Выберите это, чтобы отображать цену газа и управление лимитами непосредственно на экранах отправки и подтверждения."
},
+ "showCustomNetworkList": {
+ "message": "Показать пользовательский список сетей"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "Выберите это, чтобы отобразить список сетей с предварительно заполненными данными при добавлении новой сети."
+ },
"showFiatConversionInTestnets": {
"message": "Показывать конвертацию в тестовых сетях"
},
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "Чтобы подтвердить, что вы понимаете, отметьте все."
},
- "snapInstallWarningKeyAccess": {
- "message": "Вы предоставляете ключевой доступ к снапу «$1». Это действие является безотзывным и предоставляет «$1» контроль над вашими счетами и активами. Прежде чем продолжить, убедитесь, что вы доверяете «$1».",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "Этот снап запрашивает следующие разрешения:"
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "Снап будет работать только в том случае, если он включен"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "Некоторые сети могут представлять угрозу безопасности и/или конфиденциальности. Прежде чем добавлять и использовать сеть, ознакомьтесь с рисками."
+ },
"somethingWentWrong": {
"message": "Ой! Что-то пошло не так."
},
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "Сменить сети"
},
+ "switchToNetwork": {
+ "message": "Переключиться на $1",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "Переключиться на этот счет"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "$1 тестовые сети",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Токен"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "Обнаружение токена в настоящее время доступно на $1. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "Новинка! Улучшенное обнаружение токенов доступно в сети Ethereum Mainnet в качестве экспериментальной функции. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "API токенов ConsenSys объединяет список токенов из различных списков сторонних токенов. Отключение этого параметра прекратит обнаружение новых токенов, добавляемых в ваш кошелек, но сохранит возможность поиска токенов для импорта."
- },
"tokenId": {
"message": "Ид. токена"
},
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "Ой! Что-то пошло не так...."
},
+ "unknownCollection": {
+ "message": "Безымянная коллекция"
+ },
"unknownNetwork": {
"message": "Неизвестная частная сеть"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "Показывать предупреждение для фишинговых доменов, нацеленных на пользователей Ethereum"
},
- "useTokenDetection": {
- "message": "Использовать обнаружение токенов"
- },
- "useTokenDetectionDescription": {
- "message": "Мы используем сторонние API для обнаружения и отображения новых токенов, отправленных в ваш кошелек. Отключите, если не хотите, чтобы MetaMask получал данные от этих служб."
- },
"useTokenDetectionPrivacyDesc": {
"message": "Автоматическое отображение токенов, отправленных на ваш счет, требует обмена данными со сторонними серверами для получения изображений токенов. Эти серверы получат доступ к вашему IP-адресу."
},
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "Кошелек создан"
},
+ "wantToAddThisNetwork": {
+ "message": "Хотите добавить эту сеть?"
+ },
"warning": {
"message": "Предупреждение"
},
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "Да, давайте попробуем"
},
+ "youHaveAddedAll": {
+ "message": "Вы добавили все популярные сети. Вы можете открыть для себя больше сетей $1 или можете $2",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "Для использования этой функции вам необходимо предоставить доступ к камере."
},
diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json
index 5ed22eaba..72098a20a 100644
--- a/app/_locales/tl/messages.json
+++ b/app/_locales/tl/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "Magdagdag ng memo"
},
+ "addMoreNetworks": {
+ "message": "magdagdag pa ng mga network nang mano-mano"
+ },
"addNetwork": {
"message": "Magdagdag ng Network"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "Mga Alerto"
},
+ "allOfYour": {
+ "message": "Lahat ng iyong $1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "Payagan ang external extension na ito na:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "Aprubahan ang limitasyon sa paggastos"
},
+ "approveAllTokensTitle": {
+ "message": "Magbigay ng pahintulot na i-access ang lahat ng iyong $1?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "Aprubahan at I-install"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "Aprubadong asset"
},
- "areYouDeveloper": {
- "message": "Isa ka bang developer?"
- },
"areYouSure": {
"message": "Sigurado ka ba?"
},
@@ -466,6 +474,13 @@
"message": "Sa $1 na transaksyon ang singil sa gas ay dapat tumaas nang hindi bababa sa 10% para ito ay makilala ng network.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "Kanselahin ang swap sa halagang ~$1",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "Kanselahin ang swap nang libre"
+ },
"cancellationGasFee": {
"message": "Bayarin sa Gasolina para sa Pagkansela"
},
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "Gamitin ang $1 para i-customize ang presyo ng gas. Ito ay maaaring nakakalito kung hindi ka pamilyar. Harapin ang sarili mong panganib.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Kapag dinagdagan ang bayarin, mababawasan ang mga oras ng pagproseso, pero hindi ito garantisado."
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "Magdeposito ng $1",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Deskripsyon"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "Nagkaproblema, at hindi namin makumpleto ang aksyon"
},
- "fakeTokenWarning": {
- "message": "Sinuman ay maaaring gumawa ng token, kabilang ang paggawa ng mga pekeng bersyon ng mga umiiral na token. Alamin pa ang tungkol sa $1"
- },
"fast": {
"message": "Mabilis"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "Function: Aprubahan"
},
+ "functionSetApprovalForAll": {
+ "message": "Function: ItakdaAngPag-aprubaParaSaLahat"
+ },
"functionType": {
"message": "Uri ng Function"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "Gamitin ang $1 upang pagtakpan ang mga surge sa network traffic dahil sa mga bagay tulad ng popular na pagbagsak ng NFT.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "mataas"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "Hindi valid ang Secret Recovery Phrase"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "Di-wastong input! Ang Secret Recovery Phrase ay case sensitive."
+ },
"ipfsGateway": {
"message": "Gateway na IPFS"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Gamitin ang $1 para maghintay ng mas murang presyo. Ang mga pagtatantya sa oras ay hindi gaanong tumpak dahil ang mga presyo ay medyo hindi mahuhulaan.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "mababa"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Gamitin ang $1 para sa pagproseso sa kasalukuyang market price.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "memo"
@@ -1892,6 +1910,19 @@
"message": "i-verify ang mga detalye ng network",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "Inirerekomenda namin na $1 ka bago magpatuloy.",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "Ayon sa aming talaan, ang pangalan ng network ay maaaring hindi tumugma nang tama sa chain ID na ito."
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "Ang isinumiteng simbolo ng currency ay hindi tumutugma sa inaasahan namin para sa chain ID na ito."
+ },
+ "mismatchedRpcUrl": {
+ "message": "Ayon sa aming mga talaan, ang isinumiteng RPC URL value ay hindi tumutugma sa isang kilalang provider para sa chain ID na ito."
+ },
"missingNFT": {
"message": "Hindi makita ang NFT mo?"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "Network:"
},
+ "networkAddedSuccessfully": {
+ "message": "Matagumpay na naidagdag ang network!"
+ },
"networkDetails": {
"message": "Mga Detalye ng Network"
},
@@ -2059,6 +2093,9 @@
"message": "Mas mataas ang noncesa iminumungkahing nonce na $1",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "Ilagay ang collectible ID"
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "Bisitahin sa mga setting",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "Ang pinahusay na pagtukoy ng token ay kasalukuyang magagamit sa Ethereum Mainnet, Polygon, BSC, at mga Avalanche network. Marami pang darating!"
@@ -2159,6 +2196,16 @@
"notifications12Title": {
"message": "Wen dark mode? Ngayon dark mode! 🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "Ipakita ang listahan ng custom na network"
+ },
+ "notifications13Description": {
+ "message": "Madali mo na ngayong maidagdag ang mga sumusunod na sikat na custom na network: Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm at Polygon! Para i-enable ang feature na ito, pumunta sa Mga Setting -> Experimental at i-on ang \"Ipakita ang listahan ng custom na network\"!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "Magdagdag ng mga Sikat na Network"
+ },
"notifications1Description": {
"message": "Ang mga user ng MetaMask Mobile ay maaari na ngayong mag-swap ng mga token sa loob ng kanilang mobile wallet. I-scan ang QR code para makuha ang mobile app at magsimulang mag-swap.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "Magpunta sa Advanced Settings",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "Para sa MetaMask v10.4.0, hindi mo na kailangang ikonekta ang Ledger Live sa iyong Ledger device sa MetaMask.",
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "Markahan ang lahat bilang nabasa na"
},
- "numberOfNewTokensDetected": {
- "message": "$1 bagong token ang nakita sa account na ito",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "ng"
},
@@ -2344,9 +2387,6 @@
"message": "Buksan ang MetaMask sa buong screen para ikonekta ang ledger mo sa pamamagitan ng WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Suriin ang code ng pinagmulan"
- },
"optional": {
"message": "Opsyonal"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "Napiling Uri ng Ledger Connection",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Inihahanda ang pagpapalit..."
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "Ipakita ang seed phrase"
},
+ "revokeAllTokensTitle": {
+ "message": "Bawiin ang pahintulot na i-access ang lahat ng iyong $1?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "Sa pamamagitan ng pagbawi ng pahintulot, hindi na maa-access ng sumusunod na $1 ang iyong $2",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Rinkeby Test Network"
},
@@ -2852,12 +2900,23 @@
"message": "Nagpapadala ng $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "Babala: magpapadala ka sa isang kontrata ng token na maaaring magresulta sa pagkawala ng mga pondo. $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "Magtakda ng advanced privacy settings"
},
"setAdvancedPrivacySettingsDetails": {
"message": "Ginagamit ng MetaMask ang mga pinagkakatiwalaang serbisyo ng third-party na ito para mapahusay ang kakayahang magamit at kaligtasan ng produkto."
},
+ "setApprovalForAll": {
+ "message": "Itakda ang Pag-apruba para sa Lahat"
+ },
+ "setApprovalForAllTitle": {
+ "message": "Aprubahan ang $1 nang walang limitasyon sa paggastos",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "Mga Setting"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "Piliin ito para direktang maipakita ang presyo ng gas at mga kontrol sa limitasyon sa mga screen ng pagpapadala at pagkumpirma."
},
+ "showCustomNetworkList": {
+ "message": "Ipakita ang Listahan ng Custom na Network"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "Piliin ito para magpakita ng listahan ng mga network na may prefilled na mga detalye kapag nagdaragdag ng bagong network."
+ },
"showFiatConversionInTestnets": {
"message": "Ipakita ang Conversion sa Testnets"
},
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "Para kumpirmahing naunawaan mo, tsekan lahat."
},
- "snapInstallWarningKeyAccess": {
- "message": "Ipinagkakaloob mo ang key access sa snap \"$1\". Hindi ito maaaring bawiin at ipinagkakaloob sa \"$1\" ang kontrol sa iyong mga account at mga asset. Tiyaking pinagkakatiwalaan mo ang \"$1\" bago magpatuloy.",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "Hinihiling ng snap na ito ang mga sumusunod na pahintulot:"
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "Tatakbo lamang ang snap kapag pinagana ito"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "Maaaring magdulot ang ilang network ng mga panganib sa seguridad at/o pagkapribado. Unawain ang mga panganib bago idagdag o gamitin ang isang network."
+ },
"somethingWentWrong": {
"message": "Oops! Nagkaproblema."
},
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "Lumipat ng Network"
},
+ "switchToNetwork": {
+ "message": "Lumipat sa $1",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "Lumipat sa account na ito"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "$1 na test network",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Token"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "Ang pagtukoy ng token ay kasalukuyang magagamit sa $1. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "Bago! Ang pinahusay na pagtukoy ng token ay magagamit sa Ethereum Mainnet bilang isang pang-eksperimentong feature. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "Pinagsasama-sama ng ConsenSys’ token API ang listahan ng mga token mula sa maraming listahan ng token ng third party. Ang pag-off dito ay magpapahinto sa pagtuklas ng mga bagong token na madadagdag sa iyong wallet, ngunit pananatilihin ang opsyon sa paghahanap ng mga token para i-import."
- },
"tokenId": {
"message": "Token ID"
},
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "Ooops! Nagkaproblema...."
},
+ "unknownCollection": {
+ "message": "Walang pangalang koleksyon"
+ },
"unknownNetwork": {
"message": "Hindi Alam na Pribadong Network"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "Magpakita ng babala para sa mga phishing domain na nagta-target sa mga user ng Ethereum"
},
- "useTokenDetection": {
- "message": "Gamitin ang Pag-detect ng Token"
- },
- "useTokenDetectionDescription": {
- "message": "Gumagamit kami ng mga third-party na API para makita at magpakita ng mga bagong token na ipinadala sa iyong wallet. I-off kung ayaw mong makuha ng MetaMask ang data mula sa mga serbisyong iyon."
- },
"useTokenDetectionPrivacyDesc": {
"message": "Awtomatikong ipinapakita ang mga token na ipinadala sa iyong account na nakapaloob sa komunikasyon ng mga server ng third party para makuha ang mga larawan ng token. Ang mga server na iyon ay magkakaroon ng access sa iyong IP address."
},
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "Matagumpay ang paggawa ng wallet"
},
+ "wantToAddThisNetwork": {
+ "message": "Gusto mo bang idagdag ang network na ito?"
+ },
"warning": {
"message": "Babala"
},
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "Oo, subukan natin"
},
+ "youHaveAddedAll": {
+ "message": "Idinagdag mo ang lahat ng sikat na network. Maaari kang makatuklas ng higit pang mga network $1 O maaari kang",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "Kailangan mong payagan ang pag-access sa camera para magamit ang feature na ito."
},
diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json
index 05719bd53..0954b697f 100644
--- a/app/_locales/tr/messages.json
+++ b/app/_locales/tr/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "Not ekleyin"
},
+ "addMoreNetworks": {
+ "message": "manuel olarak daha fazla ağ ekleyin"
+ },
"addNetwork": {
"message": "Ağ ekle"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "Uyarılar"
},
+ "allOfYour": {
+ "message": "Sahip olduğunuz tüm $1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "Bu harici uzantının şunu yapmasına izin ver:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "Harcama limitini onayla"
},
+ "approveAllTokensTitle": {
+ "message": "Sahip olduğunuz tüm $1 için erişim izni verilsin mi?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "Onayla ve Yükle"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "Onaylanan varlık"
},
- "areYouDeveloper": {
- "message": "Geliştirici misin?"
- },
"areYouSure": {
"message": "Emin misin?"
},
@@ -438,7 +446,7 @@
"message": "Wyre ile $1 satın al"
},
"buyWithWyreDescription": {
- "message": "Wyre, doğrudan MetaMask hesabınıza $1 yatırma işlemleri için banka kartı kullanmanıza izin verir."
+ "message": "1000$'a kadar satın alma işlemlerinde kolay oryantasyon. Banka Kartı/Kredi Kartı, Apple Pay, Banka Transferlerini destekler. +100 ülkede kullanılabilir. Token'lar MetaMask Hesabına yatırılır"
},
"bytes": {
"message": "Bayt"
@@ -466,6 +474,13 @@
"message": "İşlemi $1 için, gaz ücretinin ağ tarafından tanınması amacıyla en az %10 oranında artırılması gerekir.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "~$1 için swap işlemini iptal edin",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "Swap işlemini ücretsiz iptal edin"
+ },
"cancellationGasFee": {
"message": "İptal İşlemi Gaz Ücreti"
},
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "Gaz fiyatını özelleştirmek için $1 kullanın. Bu, bilgi sahibi değilseniz kafa karıştırıcı olabilir. Riski size ait olmak üzere kullanın.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Ücretin artırılması işlem süresini kısaltabilir ancak bu garanti edilmez."
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "$1 yatır",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Açıklama"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "Bir şeyler ters gitti ve işlemi tamamlayamadık"
},
- "fakeTokenWarning": {
- "message": "Mevcut tokenlerin sahteleri de dahil olmak üzere herkes bir token oluşturabilir. $1 hakkında daha fazla bilgi edinin"
- },
"fast": {
"message": "Hızlı"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "İşlev: Onayla"
},
+ "functionSetApprovalForAll": {
+ "message": "İşlev: TümüİçinOnayVer"
+ },
"functionType": {
"message": "İşlev Türü"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "Popüler NFT düşüşleri gibi şeyler nedeniyle ağ trafiğindeki dalgalanmaları kapsayacak şekilde $1 kullanın.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "yüksek"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "Gizli Kurtarma İfadesi geçersiz"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "Giriş geçersiz! Gizli Kurtarma İfadesi büyük/küçük harf duyarlıdır."
+ },
"ipfsGateway": {
"message": "IPFS Ağ Geçidi"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Daha ucuz bir fiyat için beklemek için $1 kullanın. Fiyatlar bir şekilde öngörülemez oldukları için süre tahminleri çok daha az kesindir.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "düşük"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Mevcut piyasa fiyatında hızlı işleme almak için $1 kullanın.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "not"
@@ -1892,6 +1910,19 @@
"message": "ağ bilgilerini doğrula",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "İlerlemeden önce şunu öneririz: $1.",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "Kayıtlarımıza göre, ağ adı bu zincir kimliği ile doğru bir şekilde uyumlu olmayabilir."
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "Sunulan para birimi sembolü bu zincir kimliği için beklediğimiz sembolle uyumlu değil."
+ },
+ "mismatchedRpcUrl": {
+ "message": "Kayıtlarımıza göre, sunulan RPC URL adresi değeri bu zincir kimliğinin bilinen bir sağlayıcısı ile uyumlu değil."
+ },
"missingNFT": {
"message": "NFT'nizi görmüyor musunuz?"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "Ağ:"
},
+ "networkAddedSuccessfully": {
+ "message": "Ağ başarılı bir şekilde eklendi!"
+ },
"networkDetails": {
"message": "Ağ Bilgileri"
},
@@ -2059,6 +2093,9 @@
"message": "Geçici anahtar, önerilen $1 geçici anahtarından daha büyük",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "Toplanabilir kimliğini girin"
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "Ayarlarda ziyaret et",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "Geliştirilmiş token algılama şu anda Ethereum Mainnet, Polygon, BSC ve Avalanche ağlarında mevcut. Daha fazlası gelecek!"
@@ -2154,11 +2191,21 @@
"message": "Karanlık modu etkinleştir"
},
"notifications12Description": {
- "message": "Karanlık Mod, sistem tercihlerine bağlı olarak yeni kullanıcılar için etkinleştirilecektir. Mevcut kullanıcılar için, Ayarlar -> Deneysel altında Karanlık Modu manuel olarak etkinleştir."
+ "message": "Uzantıda karanlık mod sonunda burada! Bunu açmak için Ayarlar -> Deneysel kısmına git ve ekran seçeneklerinden birini seç: Aydınlık, Karanlık, Sistem."
},
"notifications12Title": {
"message": "Karanlık mod mu? Şimdi karanlık mod! 🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "Özel ağ listesini göster"
+ },
+ "notifications13Description": {
+ "message": "Artık şu popüler özel ağları kolayca ekleyebilirsiniz: Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm ve Polygon! Bu özelliği etkinleştirmek için Ayarlar -> Deneysel kısmına gidip \"Özel ağ listesini göster\" seçeneğini açın!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "Popüler Ağları Ekle"
+ },
"notifications1Description": {
"message": "MetaMask Mobil kullanıcıları artık mobil cüzdanları içinde token takas edebilirler. Mobil uygulamayı edinmek ve takas yapmaya başlamak için QR kodunu tarayın.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "Gelişmiş ayarlara git",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "MetaMask 10.4.0 sürümü itibariyle Kayıt Defteri cihazınızı Metamask'e bağlamak için artık Ledger Live'e ihtiyacınız yok.",
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "Tümünü okundu olarak işaretle"
},
- "numberOfNewTokensDetected": {
- "message": "Bu hesapta $1 yeni token bulundu",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "/"
},
@@ -2344,9 +2387,6 @@
"message": "Kayıt defterinizi WebHID üzerinden bağlamak için MetaMask'i tam ekran açın.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Kaynak kodunu kontrol et"
- },
"optional": {
"message": "İsteğe bağlı"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "Tercih Edilen Kayıt Defteri Bağlantı Türü",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Takas hazırlanıyor..."
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "Tohum ifadesini ortaya çıkar"
},
+ "revokeAllTokensTitle": {
+ "message": "Sahip olduğunuz tüm $1 için izin geri çekilsin mi?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "İzni geri çektiğinizde aşağıdaki $1 artık $2 alanınıza erişim sağlayamayacak",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Rinkeby Test Ağı"
},
@@ -2852,12 +2900,23 @@
"message": "$1 Gönderiliyor",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "Uyarı: Para kaybı ile sonuçlanabilecek bir token sözleşmesi göndermek üzeresiniz. $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "Gelişmiş gizlilik ayarlarını yapın"
},
"setAdvancedPrivacySettingsDetails": {
"message": "MetaMask, ürünün kullanılabilirliğini ve güvenliğini iyileştirmek amacıyla bu güvenilir üçüncü taraf hizmetlerini kullanır."
},
+ "setApprovalForAll": {
+ "message": "Tümüne Onay Ver"
+ },
+ "setApprovalForAllTitle": {
+ "message": "$1 için harcama limiti olmadan onay ver",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "Ayarlar"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "Gaz fiyatı ve limit kontrollerini doğrudan gönder ve onayla ekranlarında göstermek için bunu seçin."
},
+ "showCustomNetworkList": {
+ "message": "Özel Ağ Listesini Göster"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "Yeni bir ağ eklerken önceden doldurulan bilgilerle ağ listesini görüntülenmesi için bunu seçin."
+ },
"showFiatConversionInTestnets": {
"message": "Test ağlarında Dönüşümü göster"
},
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "Anladığını doğrulamak için hepsini kontrol et."
},
- "snapInstallWarningKeyAccess": {
- "message": "\"$1\" snap'ine anahtar erişimi veriyorsun. Bu geri alınamaz ve hesapların ve varlıkların üzerinde \"1$\" kontrol sağlar. Devam etmeden önce \"$1\" öğesine güvendiğinden emin ol.",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "Bu ek, aşağıdaki izinleri istiyor:"
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "Bir snap yalnızca etkinleştirilmişse çalışır"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "Bazı ağlar güvenlik ve/veya gizlilik riskleri teşkil edebilir. Bir ağ eklemeden ve kullanmadan önce riskleri anlayın."
+ },
"somethingWentWrong": {
"message": "Eyvah! Bir şeyler ters gitti."
},
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "Ağları Değiştir"
},
+ "switchToNetwork": {
+ "message": "$1 ağına geçin",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "Bu hesaba geç"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "Test ağlarını $1",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Token"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "Token algılama şu anda $1 üzerinden kullanılabilir. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "Yeni! Deney aşamasında olan bir özellik olarak Ethereum Mainnet'te gelişmiş token algılama mevcut. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "ConsenSys'in token API'si, çeşitli üçüncü taraf token listelerinden token'ların bir listesini toplar. Kapatmak, cüzdanına eklenen yeni token'ları algılamayı durduracak, ancak içe aktarılacak token'ları arama seçeneğini koruyacaktır."
- },
"tokenId": {
"message": "Token Kimliği"
},
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "Eyvah! Bir şeyler ters gitti...."
},
+ "unknownCollection": {
+ "message": "İsimsiz koleksiyon"
+ },
"unknownNetwork": {
"message": "Bilinmeyen Özel Ağ"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "Ethereum kullanıcılarını hedefleyen kimlik avı alanları için bir uyarı görüntüler"
},
- "useTokenDetection": {
- "message": "Token Algılama Kullan"
- },
- "useTokenDetectionDescription": {
- "message": "Cüzdanınıza gönderilen yeni tokenleri algılamak ve görüntülemek için üçüncü taraf API'leri kullanıyoruz. MetaMask tarafından bu hizmetlerden veri çekilmesini istemiyorsanız bunu kapatın."
- },
"useTokenDetectionPrivacyDesc": {
"message": "Hesabına gönderilen token'ların otomatik olarak görüntülenmesi, token'ın görüntülerini almak için üçüncü taraf sunucularla iletişimi içerir. Bu servislerin IP adresine erişimi olacaktır."
},
@@ -3962,7 +4021,7 @@
"description": "$1 is the action type. e.g (Account, Transaction, Swap)"
},
"visitWebSite": {
- "message": "Web sitemizi ziyaret edin"
+ "message": "Web sitemizi ziyaret et"
},
"walletConnectionGuide": {
"message": "donanım cüzdanı bağlantı kılavuzumuz"
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "Cüzdan oluşturma başarılı"
},
+ "wantToAddThisNetwork": {
+ "message": "Bu ağı eklemek istiyor musunuz?"
+ },
"warning": {
"message": "Uyarı"
},
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "Evet, deneyelim"
},
+ "youHaveAddedAll": {
+ "message": "Tüm popüler ağları eklediniz. $1 daha fazla ağ gekşefedebilir veya $2 seçeneğini seçebilirsiniz",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "Bu özelliği kullanmak için kamera erişimine izin vermeniz gerekir."
},
diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json
index a38862ed5..6a40728d3 100644
--- a/app/_locales/vi/messages.json
+++ b/app/_locales/vi/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "Thêm bản ghi nhớ"
},
+ "addMoreNetworks": {
+ "message": "thêm thủ công các mạng khác"
+ },
"addNetwork": {
"message": "Thêm mạng"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "Cảnh báo"
},
+ "allOfYour": {
+ "message": "Tất cả $1 của bạn",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "Cho phép tiện ích bên ngoài này:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "Phê duyệt giới hạn chi tiêu"
},
+ "approveAllTokensTitle": {
+ "message": "Cấp quyền truy cập vào tất cả $1 của bạn?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "Chấp nhận và cài đặt"
},
@@ -282,9 +293,6 @@
"approvedAsset": {
"message": "Tài sản được chấp nhận"
},
- "areYouDeveloper": {
- "message": "Bạn có phải là lập trình viên không?"
- },
"areYouSure": {
"message": "Bạn có chắc chắn không?"
},
@@ -438,7 +446,7 @@
"message": "Mua $1 qua Wyre"
},
"buyWithWyreDescription": {
- "message": "Wyre cho phép bạn dùng thẻ ghi nợ để nạp $1 trực tiếp vào tài khoản MetaMask của mình."
+ "message": "Dễ dàng tham gia đối với các giao dịch mua lên đến $1.000. Xác minh mua hàng giới hạn cao và tương tác nhanh. Hỗ trợ Thẻ Tín dụng/Ghi nợ, Apple Pay, Chuyển khoản Ngân hàng. Hiện có tại hơn 100 quốc gia. Nạp token vào Tài khoản MetaMask của bạn"
},
"bytes": {
"message": "Byte"
@@ -466,6 +474,13 @@
"message": "Để $1 một giao dịch, phí gas phải tăng tối thiểu 10% để mạng nhận ra giao dịch này.",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "Hủy hoán đổi với giá ~$1",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "Hủy hoán đổi miễn phí"
+ },
"cancellationGasFee": {
"message": "Phí gas hủy"
},
@@ -650,7 +665,7 @@
"message": "Tương tác với hợp đồng"
},
"convertTokenToNFTDescription": {
- "message": "Chúng tôi phát hiện tài sản này là một NFT. MetaMask hiện đã hỗ trợ gốc đầy đủ cho NFT. Bạn có muốn xóa tài sản khỏi danh sách token và thêm tài sản dưới dạng NFT không?"
+ "message": "Chúng tôi phát hiện tài sản này là một NFT. MetaMask hiện đã hỗ trợ toàn diện và đầy đủ cho NFT. Bạn có muốn xóa tài sản khỏi danh sách token và thêm tài sản dưới dạng NFT không?"
},
"convertTokenToNFTExistDescription": {
"message": "Chúng tôi phát hiện tài sản này đã được thêm dưới dạng NFT. Bạn có muốn xóa tài sản khỏi danh sách token không?"
@@ -732,7 +747,7 @@
},
"customGasSettingToolTipMessage": {
"message": "Sử dụng $1 để tùy chỉnh giá gas. Việc này có thể gây nhầm lẫn nếu bạn không quen thuộc. Bạn phải tự chịu trách nhiệm nếu thực hiện.",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "Việc tăng phí có thể giúp giảm thời gian xử lý, nhưng điều này không được đảm bảo."
@@ -818,7 +833,7 @@
},
"depositCrypto": {
"message": "Nạp $1",
- "description": "$1 represents the cypto symbol to be purchased"
+ "description": "$1 represents the crypto symbol to be purchased"
},
"description": {
"message": "Mô tả"
@@ -1196,9 +1211,6 @@
"failureMessage": {
"message": "Đã xảy ra sự cố và chúng tôi không thể hoàn tất hành động"
},
- "fakeTokenWarning": {
- "message": "Bất kỳ ai cũng có thể tạo token, bao gồm cả phiên bản giả mạo của các token hiện tại. Tìm hiểu thêm về $1"
- },
"fast": {
"message": "Nhanh"
},
@@ -1277,6 +1289,9 @@
"functionApprove": {
"message": "Chức năng: Phê duyệt"
},
+ "functionSetApprovalForAll": {
+ "message": "Chức năng: SetApprovalForAll"
+ },
"functionType": {
"message": "Loại chức năng"
},
@@ -1463,7 +1478,7 @@
},
"highGasSettingToolTipMessage": {
"message": "Sử dụng $1 để bù đắp khi lưu lượng mạng lưới tăng vọt trong những trường hợp như phát hành NFT nổi tiếng.",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "cao"
@@ -1617,6 +1632,9 @@
"invalidSeedPhrase": {
"message": "Cụm mật khẩu khôi phục bí mật không hợp lệ"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "Nội dung nhập không hợp lệ! Cụm từ khôi phục bí mật phân biệt chữ hoa và chữ thường."
+ },
"ipfsGateway": {
"message": "Cổng kết nối IPFS"
},
@@ -1766,7 +1784,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "Sử dụng $1 để chờ mức giá rẻ hơn. Thời gian dự kiến sẽ kém chính xác hơn nhiều do mức giá tương đối khó dự đoán.",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "thấp"
@@ -1810,7 +1828,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "Sử dụng $1 để xử lý nhanh theo giá thị trường hiện tại.",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "bản ghi nhớ"
@@ -1892,6 +1910,19 @@
"message": "xác minh thông tin về mạng",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "Bạn nên $1 trước khi tiếp tục.",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "Theo hồ sơ của chúng tôi, tên mạng có thể không khớp hoàn toàn với ID chuỗi này."
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "Ký hiệu đơn vị tiền tệ đã gửi không khớp với những gì chúng tôi mong đợi cho ID chuỗi này."
+ },
+ "mismatchedRpcUrl": {
+ "message": "Theo hồ sơ của chúng tôi, giá trị RPC URL đã gửi không khớp với một nhà cung cấp đã biết cho ID chuỗi này."
+ },
"missingNFT": {
"message": "Không thấy NFT của mình?"
},
@@ -1943,6 +1974,9 @@
"network": {
"message": "Mạng:"
},
+ "networkAddedSuccessfully": {
+ "message": "Đã thêm mạng thành công!"
+ },
"networkDetails": {
"message": "Thông tin về mạng"
},
@@ -2059,6 +2093,9 @@
"message": "Số chỉ dùng một lần lớn hơn số chỉ dùng một lần gợi ý là $1",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "NFT"
+ },
"nftTokenIdPlaceholder": {
"message": "Nhập ID bộ sưu tập"
},
@@ -2130,7 +2167,7 @@
},
"notifications10ActionText": {
"message": "Xem trong cài đặt",
- "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
+ "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page."
},
"notifications10DescriptionOne": {
"message": "Tính năng phát hiện token cải tiến hiện đã có sẵn trên Mạng chính thức của Ethereum, mạng Polygon, BSC và Avalanche. Sẽ sớm có thêm nhiều mạng khác!"
@@ -2154,11 +2191,21 @@
"message": "Bật chế độ tối"
},
"notifications12Description": {
- "message": "Chế độ tối sẽ được kích hoạt dành cho người dùng mới tùy theo tùy chọn hệ thống của họ. Đối với người dùng hiện tại, có thể bật Chế độ tối theo cách thủ công trong phần Cài đặt -> Thử nghiệm."
+ "message": "Tiện ích Chế độ tối hiện đã ra mắt! Để bật, hãy vào Cài đặt -> Thử nghiệm và chọn một trong các tùy chọn hiển thị: Sáng, Tối, Hệ thống."
},
"notifications12Title": {
"message": "Dùng chế độ tối khi nào? Ngay bây giờ! 🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "Hiển thị danh sách mạng tùy chỉnh"
+ },
+ "notifications13Description": {
+ "message": "Giờ đây, bạn có thể dễ dàng thêm các mạng tùy chỉnh phổ biến sau: Arbitrum, Avalanche, Binance Smart Chain, Fantom, Harmony, Optimism, Palm và Polygon! Để bật tính năng này, hãy chuyển đến Cài đặt -> Thử nghiệm và bật \"Hiển thị danh sách mạng tùy chỉnh\"!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "Thêm các mạng phổ biến"
+ },
"notifications1Description": {
"message": "Giờ đây, người dùng MetaMask trên điện thoại di động có thể hoán đổi token trong ví di động của họ. Quét mã QR để tải ứng dụng di động và bắt đầu hoán đổi.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2225,7 +2272,7 @@
},
"notifications8ActionText": {
"message": "Đến Cài Đặt Nâng Cao",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "Kể từ phiên bản MetaMask v10.4.0, bạn không cần phần mềm Ledger Live để kết nối thiết bị Ledger của mình với MetaMask nữa.",
@@ -2261,10 +2308,6 @@
"notificationsMarkAllAsRead": {
"message": "Đánh dấu đã đọc tất cả"
},
- "numberOfNewTokensDetected": {
- "message": "Tìm thấy $1 token mới trong tài khoản này",
- "description": "$1 is the number of new tokens detected"
- },
"ofTextNofM": {
"message": "trên"
},
@@ -2344,9 +2387,6 @@
"message": "Mở MetaMask ở chế độ toàn màn hình để kết nối thiết bị Ledger của bạn qua WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
- "openSourceCode": {
- "message": "Kiểm tra mã nguồn"
- },
"optional": {
"message": "Không bắt buộc"
},
@@ -2473,7 +2513,7 @@
},
"preferredLedgerConnectionType": {
"message": "Dạng Kết Nối Ledger Ưu Tiên",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"preparingSwap": {
"message": "Đang chuẩn bị hoán đổi..."
@@ -2676,6 +2716,14 @@
"revealTheSeedPhrase": {
"message": "Hiện cụm từ khôi phục bí mật"
},
+ "revokeAllTokensTitle": {
+ "message": "Thu hồi quyền truy cập vào tất cả $1 của bạn?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "Bằng cách thu hồi quyền truy cập, $1 sau đây sẽ không thể truy cập vào $2 của bạn nữa",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Mạng thử nghiệm Rinkeby"
},
@@ -2852,12 +2900,23 @@
"message": "Gửi $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "Cảnh báo: bạn sắp gửi đến một hợp đồng token và điều này có thể dẫn đến nguy cơ mất tiền. $1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "Thiết lập cài đặt quyền riêng tư nâng cao"
},
"setAdvancedPrivacySettingsDetails": {
"message": "MetaMask sử dụng các dịch vụ của bên thứ ba đáng tin cậy này để nâng cao sự hữu ích và an toàn của sản phẩm."
},
+ "setApprovalForAll": {
+ "message": "Cài đặt phê duyệt tất cả"
+ },
+ "setApprovalForAllTitle": {
+ "message": "Phê duyệt $1 không có giới hạn chi tiêu",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "Cài đặt"
},
@@ -2877,6 +2936,12 @@
"showAdvancedGasInlineDescription": {
"message": "Chọn tùy chọn này để hiển thị các quyền kiểm soát giá gas và giới hạn ngay trên màn hình gửi và xác nhận."
},
+ "showCustomNetworkList": {
+ "message": "Hiển thị danh sách mạng tùy chỉnh"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "Chọn tùy chọn này để hiển thị danh sách các mạng có thông tin được điền sẵn khi thêm mạng mới."
+ },
"showFiatConversionInTestnets": {
"message": "Hiển thị tỷ lệ quy đổi trên các mạng thử nghiệm"
},
@@ -2967,10 +3032,6 @@
"snapInstallWarningCheck": {
"message": "Để xác nhận rằng bạn hiểu, hãy đánh dấu vào tất cả."
},
- "snapInstallWarningKeyAccess": {
- "message": "Bạn đang cấp quyền truy cập khóa cho Snap \"$1\". Hành động này không thể hủy bỏ và sẽ cấp quyền kiểm soát tài khoản và tài sản của bạn cho \"$1\". Đảm bảo bạn tin tưởng \"$1\" trước khi tiếp tục.",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "Snap này đang yêu cầu các quyền sau:"
},
@@ -2986,6 +3047,9 @@
"snapsToggle": {
"message": "Snap chỉ hoạt động khi đã bật"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "Một số mạng có thể gây ra rủi ro về bảo mật và/hoặc quyền riêng tư. Bạn cần hiểu rõ các rủi ro này trước khi thêm và sử dụng mạng."
+ },
"somethingWentWrong": {
"message": "Rất tiếc! Đã xảy ra sự cố."
},
@@ -3548,6 +3612,10 @@
"switchNetworks": {
"message": "Chuyển mạng"
},
+ "switchToNetwork": {
+ "message": "Chuyển sang $1",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "Chuyển sang tài khoản này"
},
@@ -3635,7 +3703,7 @@
},
"toggleTestNetworks": {
"message": "$1 mạng thử nghiệm",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "Token"
@@ -3664,12 +3732,6 @@
"tokenDetectionAlertMessage": {
"message": "Tính năng phát hiện token hiện có sẵn trên $1. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "Mới! Tính năng phát hiện token được cải tiến hiện đã có sẵn trên Mạng chính thức của Ethereum dưới dạng một tính năng thử nghiệm. $1"
- },
- "tokenDetectionToggleDescription": {
- "message": "API token của ConsenSys sẽ tổng hợp danh sách token từ các danh sách token của nhiều bên thứ ba khác nhau. Tắt tính năng này sẽ ngừng phát hiện token mới được thêm vào ví của bạn, nhưng sẽ giữ lại tùy chọn tìm kiếm token để nhập."
- },
"tokenId": {
"message": "ID Token"
},
@@ -3851,6 +3913,9 @@
"unknownCameraErrorTitle": {
"message": "Rất tiếc! Đã xảy ra sự cố...."
},
+ "unknownCollection": {
+ "message": "Bộ sưu tập chưa có tên"
+ },
"unknownNetwork": {
"message": "Mạng riêng không xác định"
},
@@ -3901,12 +3966,6 @@
"usePhishingDetectionDescription": {
"message": "Hiển thị cảnh báo đối với các tên miền lừa đảo nhắm đến người dùng Ethereum"
},
- "useTokenDetection": {
- "message": "Sử Dụng Phát Hiện Token"
- },
- "useTokenDetectionDescription": {
- "message": "Chúng tôi sử dụng API của bên thứ ba để phát hiện và hiển thị các token mới được gửi vào ví của bạn. Hãy tắt tính năng này nếu bạn không muốn MetaMask lấy dữ liệu từ các dịch vụ đó."
- },
"useTokenDetectionPrivacyDesc": {
"message": "Tự động hiển thị các token được gửi vào tài khoản của bạn có liên quan đến hoạt động trao đổi thông tin với các máy chủ bên thứ ba để tìm nạp hình ảnh của token. Các máy chủ đó sẽ có quyền truy cập vào địa chỉ IP của bạn."
},
@@ -3987,6 +4046,9 @@
"walletCreationSuccessTitle": {
"message": "Tạo ví thành công"
},
+ "wantToAddThisNetwork": {
+ "message": "Bạn muốn thêm mạng này?"
+ },
"warning": {
"message": "Cảnh báo"
},
@@ -4049,6 +4111,10 @@
"yesLetsTry": {
"message": "Có, hãy thử"
},
+ "youHaveAddedAll": {
+ "message": "Bạn đã thêm tất cả các mạng phổ biến. Bạn có thể khám phá thêm nhiều mạng khác $1 Hoặc bạn có thể $2",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "Bạn cần cho phép truy cập vào máy ảnh để sử dụng tính năng này."
},
diff --git a/app/_locales/zh/messages.json b/app/_locales/zh/messages.json
index 66984cbd4..37d0db906 100644
--- a/app/_locales/zh/messages.json
+++ b/app/_locales/zh/messages.json
@@ -157,6 +157,9 @@
"addMemo": {
"message": "添加备忘录"
},
+ "addMoreNetworks": {
+ "message": "手动添加更多网络"
+ },
"addNetwork": {
"message": "添加网络"
},
@@ -227,6 +230,10 @@
"alerts": {
"message": "提醒"
},
+ "allOfYour": {
+ "message": "您的所有$1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "允许此外部扩展程序:"
},
@@ -263,6 +270,10 @@
"approve": {
"message": "批准消费限额"
},
+ "approveAllTokensTitle": {
+ "message": "是否允许访问您的所有$1?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "批准并安装"
},
@@ -435,10 +446,10 @@
"description": "$1 represents the crypto symbol to be purchased"
},
"buyWithWyre": {
- "message": "使用 Wyre 购买 $1"
+ "message": "使用Wyre购买$1"
},
"buyWithWyreDescription": {
- "message": "您可以通过 Wyre 使用借记卡将 $1 存入您的 MetaMask 账户。"
+ "message": "购买不超过$1000可以轻松开通。快速交互式上限购买验证。支持借记卡/信用卡、Apple Pay、银行转账。适用于100多个国家。代币存入您的MetaMask账户"
},
"bytes": {
"message": "字节"
@@ -466,6 +477,13 @@
"message": "若要$1交易,燃料费用必须增加至少10%才能被网络认可。",
"description": "$1 is string 'cancel' or 'speed up'"
},
+ "cancelSwapForFee": {
+ "message": "以~$1取消兑换",
+ "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
+ },
+ "cancelSwapForFree": {
+ "message": "免费取消兑换"
+ },
"cancellationGasFee": {
"message": "取消燃料费用"
},
@@ -1196,9 +1214,6 @@
"failureMessage": {
"message": "出了点问题,我们无法完成此操作"
},
- "fakeTokenWarning": {
- "message": "任何人都可以创建代币,包括创建现有代币的虚假版本。了解关于 $ 的更多详情"
- },
"fast": {
"message": "快"
},
@@ -1277,6 +1292,9 @@
"functionApprove": {
"message": "功能:批准"
},
+ "functionSetApprovalForAll": {
+ "message": "功能:SetApprovalForAll"
+ },
"functionType": {
"message": "功能类型"
},
@@ -1617,6 +1635,9 @@
"invalidSeedPhrase": {
"message": "助记词无效"
},
+ "invalidSeedPhraseCaseSensitive": {
+ "message": "输入无效!助记词须区分大小写。"
+ },
"ipfsGateway": {
"message": "IPFS 网关"
},
@@ -1646,7 +1667,7 @@
"message": "已知合约地址。"
},
"knownTokenWarning": {
- "message": "此操作将编辑已经在您的钱包中列出的代币,有可能被用来欺骗您。只有确定要更改这些代币的内容时,才通过此操作。了解更多关于 $1"
+ "message": "此操作将编辑已经在您的钱包中列出的代币,有肯能被用来欺骗您。只有确定要更改这些代币的内容时,才通过此操作。了解更多关于 $1"
},
"kovan": {
"message": "Kovan 测试网络"
@@ -1892,6 +1913,19 @@
"message": "验证网络信息",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
+ "mismatchedChainRecommendation": {
+ "message": "我们建议您在继续之前$1。",
+ "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key. The link will open to instructions for users to validate custom network details."
+ },
+ "mismatchedNetworkName": {
+ "message": "根据我们的记录,该网络名称可能与此链ID不匹配。"
+ },
+ "mismatchedNetworkSymbol": {
+ "message": "所提交的货币符号与我们对此链ID的预期不匹配。"
+ },
+ "mismatchedRpcUrl": {
+ "message": "根据我们的记录,所提交的RPC URL值与此链ID的已知提供者不匹配。"
+ },
"missingNFT": {
"message": "找不到您的 NFT?"
},
@@ -1943,6 +1977,9 @@
"network": {
"message": "网络: "
},
+ "networkAddedSuccessfully": {
+ "message": "网络添加成功!"
+ },
"networkDetails": {
"message": "网络详情"
},
@@ -2059,6 +2096,9 @@
"message": "Nonce 高于建议的 nouce 值 $1",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nft": {
+ "message": "非同质化代币(NFT)"
+ },
"nftTokenIdPlaceholder": {
"message": "输入代币ID"
},
@@ -2154,11 +2194,21 @@
"message": "启用黑暗模式"
},
"notifications12Description": {
- "message": "将根据新用户的系统偏好设置为其启用黑暗模式。对于现有用户,请在“设置 -> 实验项”下手动启用黑暗模式。"
+ "message": "扩展程序的深色模式终于来了!若要开启,请前往“设置 -> 实验项”,然后选择一个显示选项:浅色、深色、系统。"
},
"notifications12Title": {
"message": "何时启用黑暗模式?现在启用黑暗模式! 🕶️🦊"
},
+ "notifications13ActionText": {
+ "message": "显示自定义网络列表"
+ },
+ "notifications13Description": {
+ "message": "您现在可以轻松添加以下热门自定义网络:Arbitrum、Avalanche、Binance Smart Chain、Fantom、Harmony、Optimism、Palm和Polygon!如需启用此功能,请转到“设置” -> “实验”,然后打开“显示自定义网络列表”!",
+ "description": "Description of a notification in the 'See What's New' popup. Describes popular network feature."
+ },
+ "notifications13Title": {
+ "message": "添加热门网络"
+ },
"notifications1Description": {
"message": "MetaMask Mobile 用户现在可以在他们的移动钱包中交换代币。扫描二维码以获取移动应用程序并开始交换。",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@@ -2676,6 +2726,14 @@
"revealTheSeedPhrase": {
"message": "显示助记词"
},
+ "revokeAllTokensTitle": {
+ "message": "撤销访问您的所有$1的权限?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "通过撤销权限,以下$1将无法再访问您的$2",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Rinkeby 测试网络"
},
@@ -2852,12 +2910,23 @@
"message": "正在发送 $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
+ "sendingToTokenContractWarning": {
+ "message": "警告:您将要发送到代币合约,这可能会导致资金损失。$1",
+ "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning"
+ },
"setAdvancedPrivacySettings": {
"message": "设置高级隐私设置"
},
"setAdvancedPrivacySettingsDetails": {
"message": "MetaMask 使用这些可信的第三方服务来提高产品可用性和安全性。"
},
+ "setApprovalForAll": {
+ "message": "设置批准所有"
+ },
+ "setApprovalForAllTitle": {
+ "message": "批准$1,且无消费限制",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "设置"
},
@@ -2877,6 +2946,12 @@
"showAdvancedGasInlineDescription": {
"message": "选择此项可直接在发送和确认界面显示燃料价格和上限控制。"
},
+ "showCustomNetworkList": {
+ "message": "显示自定义网络列表"
+ },
+ "showCustomNetworkListDescription": {
+ "message": "选择此项,在添加新网络时就会显示附有预填详细信息的网络列表。"
+ },
"showFiatConversionInTestnets": {
"message": "在测试网络上显示转换"
},
@@ -2967,10 +3042,6 @@
"snapInstallWarningCheck": {
"message": "请勾选全部以确认您理解。"
},
- "snapInstallWarningKeyAccess": {
- "message": "您正在向snap \"$1\"授予密钥访问权限。此操作不可撤销,并会向\"$1\"授予对您的账户和资产的控制权。在继续之前,请确保您信任\"$1\"。",
- "description": "The parameter is the name of the snap"
- },
"snapRequestsPermission": {
"message": "此Snap正在请求以下权限:"
},
@@ -2986,6 +3057,9 @@
"snapsToggle": {
"message": "Snap仅在启用后才会运行"
},
+ "someNetworksMayPoseSecurity": {
+ "message": "某些网络可能会带来安全和/或隐私风险。在添加和使用网络之前,请先了解风险。"
+ },
"somethingWentWrong": {
"message": "哎呀!出了点问题。"
},
@@ -3548,6 +3622,10 @@
"switchNetworks": {
"message": "切换网络"
},
+ "switchToNetwork": {
+ "message": "切换至$1",
+ "description": "$1 represents the custom network that has previously been added"
+ },
"switchToThisAccount": {
"message": "切换到该账户"
},
@@ -3664,9 +3742,6 @@
"tokenDetectionAlertMessage": {
"message": "代币检测目前适用于 $1. $2"
},
- "tokenDetectionAnnouncement": {
- "message": "新功能!以太坊主网上提供了经过改进的代币检测作为实验功能。$1"
- },
"tokenDetectionToggleDescription": {
"message": "ConsenSys的代币API使用来自各种第三方的代币列表,汇总成一个代币列表。关闭它将会停止检测添加到您钱包中的新代币,但会保留搜索代币以导入的选项。"
},
@@ -3851,6 +3926,9 @@
"unknownCameraErrorTitle": {
"message": "糟糕!出问题了...."
},
+ "unknownCollection": {
+ "message": "未命名的收藏"
+ },
"unknownNetwork": {
"message": "未知的私有网络"
},
@@ -3901,12 +3979,6 @@
"usePhishingDetectionDescription": {
"message": "显示针对 Ethereum 用户的网络钓鱼域名警告"
},
- "useTokenDetection": {
- "message": "使用代币检测"
- },
- "useTokenDetectionDescription": {
- "message": "我们使用第三方 API 来检测和显示发送到您钱包的新代币。如果您不希望 MetaMask 从这些服务中提取数据,请关闭。"
- },
"useTokenDetectionPrivacyDesc": {
"message": "要自动显示发送到您账户的代币,需要与第三方服务器通信以获取代币的图像。这些服务器将拥有您的IP地址的访问权限。"
},
@@ -3987,6 +4059,9 @@
"walletCreationSuccessTitle": {
"message": "钱包创建成功"
},
+ "wantToAddThisNetwork": {
+ "message": "想要添加此网络吗?"
+ },
"warning": {
"message": "警告"
},
@@ -4049,6 +4124,10 @@
"yesLetsTry": {
"message": "是的,我们试一下"
},
+ "youHaveAddedAll": {
+ "message": "您已经添加了所有热门网络。您可以探索更多网络$1,或者您可以$2",
+ "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
+ },
"youNeedToAllowCameraAccess": {
"message": "需要开启相机访问权限,才能使用该功能。"
},
diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json
index 64633eb4b..147485e81 100644
--- a/app/_locales/zh_CN/messages.json
+++ b/app/_locales/zh_CN/messages.json
@@ -613,7 +613,7 @@
},
"customGasSettingToolTipMessage": {
"message": "使用$1来定制燃料价格。如果您不熟悉这可能会引起混淆。操作风险自付。",
- "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customGasSubTitle": {
"message": "提升费用可能会缩短处理时间,但不保证绝对有效。"
@@ -1017,9 +1017,6 @@
"failureMessage": {
"message": "出了点问题,我们无法完成这个操作。"
},
- "fakeTokenWarning": {
- "message": "任何人都可以创建代币,包括创建现有代币的假版本。了解更多关于 $1"
- },
"fast": {
"message": "快"
},
@@ -1233,7 +1230,7 @@
},
"highGasSettingToolTipMessage": {
"message": "使用$1来覆盖网络流量因像流行的 NFT 丢弃而出现的剧增。",
- "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold font-weight"
},
"highLowercase": {
"message": "高"
@@ -1484,7 +1481,7 @@
},
"lowGasSettingToolTipMessage": {
"message": "使用$1等待较便宜的价格。时间估计远不准确,因为价格有些难以预测。",
- "description": "$1 is key 'low' separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight"
},
"lowLowercase": {
"message": "低"
@@ -1519,7 +1516,7 @@
},
"mediumGasSettingToolTipMessage": {
"message": "使用$1按当前市场价格快速处理。",
- "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold fontweight"
+ "description": "$1 is key 'medium' (text: 'Market') separated here so that it can be passed in with bold font-weight"
},
"memo": {
"message": "备忘"
@@ -1865,7 +1862,7 @@
},
"notifications8ActionText": {
"message": "转到高级设置",
- "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced Settings page."
+ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page."
},
"notifications8DescriptionOne": {
"message": "从MetaMaskv10.4.0开始,您不再需要Ledger Live连接您的Ledger设备到Metamask。",
@@ -2039,7 +2036,7 @@
},
"preferredLedgerConnectionType": {
"message": "首选Ledger连接类型",
- "description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
+ "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
},
"prev": {
"message": "上一个"
@@ -2952,7 +2949,7 @@
},
"toggleTestNetworks": {
"message": "$1 测试网络",
- "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open to the advanced settings where users can enable the display of test networks in the network dropdown."
+ "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown."
},
"token": {
"message": "代币"
@@ -2966,9 +2963,6 @@
"tokenDecimalFetchFailed": {
"message": "需要代币十进制。"
},
- "tokenDetectionAnnouncement": {
- "message": "新功能!改进的代币检测可以作为实验功能在Ethereum Mainnet上进行。$1"
- },
"tokenSymbol": {
"message": "代币符号"
},
@@ -3178,12 +3172,6 @@
"usePhishingDetectionDescription": {
"message": "显示针对 Ethereum 用户钓鱼域名的警告。"
},
- "useTokenDetection": {
- "message": "使用代币检测"
- },
- "useTokenDetectionDescription": {
- "message": "我们使用第三方API来检测和显示发送到您钱包的新代币。 如果您不想从这些服务中拉取数据,请关闭"
- },
"usedByClients": {
"message": "可用于各种不同的客户端"
},
diff --git a/app/fonts/Euclid/EuclidCircularB-Medium.ttf b/app/fonts/Euclid/EuclidCircularB-Medium.ttf
new file mode 100644
index 000000000..7a2ae44a1
Binary files /dev/null and b/app/fonts/Euclid/EuclidCircularB-Medium.ttf differ
diff --git a/app/home.html b/app/home.html
index 46bbb857e..4a4df3f4e 100644
--- a/app/home.html
+++ b/app/home.html
@@ -10,7 +10,7 @@
-
+
diff --git a/app/images/eth_badge.svg b/app/images/eth_badge.svg
new file mode 100644
index 000000000..90063426f
--- /dev/null
+++ b/app/images/eth_badge.svg
@@ -0,0 +1,17 @@
+
diff --git a/app/manifest/v3/_base.json b/app/manifest/v3/_base.json
index 3980afdc5..7b28cd2ed 100644
--- a/app/manifest/v3/_base.json
+++ b/app/manifest/v3/_base.json
@@ -47,6 +47,7 @@
],
"default_locale": "en",
"description": "__MSG_appDescription__",
+ "host_permissions": ["file://*/*", "http://*/*", "https://*/*"],
"icons": {
"16": "images/icon-16.png",
"19": "images/icon-19.png",
@@ -61,6 +62,7 @@
"name": "__MSG_appName__",
"permissions": [
"storage",
+ "scripting",
"unlimitedStorage",
"clipboardWrite",
"http://localhost:8545/",
@@ -72,11 +74,5 @@
"*://*.eth/",
"notifications"
],
- "short_name": "__MSG_appName__",
- "web_accessible_resources": [
- {
- "resources": ["inpage.js", "phishing.html"],
- "matches": ["http://*/*", "https://*/*"]
- }
- ]
+ "short_name": "__MSG_appName__"
}
diff --git a/app/scripts/app-init.js b/app/scripts/app-init.js
index 7b9c1dfd1..4ae247c5b 100644
--- a/app/scripts/app-init.js
+++ b/app/scripts/app-init.js
@@ -1,42 +1,87 @@
+/* global chrome */
+// This file is used only for manifest version 3
+
+// Represents if importAllScripts has been run
+// eslint-disable-next-line
+let scriptsLoadInitiated = false;
+
+const testMode = process.env.IN_TEST;
+
+const loadTimeLogs = [];
+
// eslint-disable-next-line import/unambiguous
function tryImport(...fileNames) {
try {
+ const startTime = new Date().getTime();
// eslint-disable-next-line
importScripts(...fileNames);
+ const endTime = new Date().getTime();
+ loadTimeLogs.push({
+ name: fileNames[0],
+ value: endTime - startTime,
+ children: [],
+ startTime,
+ endTime,
+ });
+
return true;
} catch (e) {
console.error(e);
- return false;
}
+
+ return false;
}
function importAllScripts() {
+ // Bail if we've already imported scripts
+ if (scriptsLoadInitiated) {
+ return;
+ }
+ scriptsLoadInitiated = true;
+ const files = [];
+
+ // In testMode individual files are imported, this is to help capture load time stats
+ const loadFile = (fileName) => {
+ if (testMode) {
+ tryImport(fileName);
+ } else {
+ files.push(fileName);
+ }
+ };
+
const startImportScriptsTime = Date.now();
+
// value of applyLavaMoat below is dynamically replaced at build time with actual value
- const applyLavaMoat = true;
+ const applyLavaMoat = process.env.APPLY_LAVAMOAT;
+ if (typeof applyLavaMoat !== 'boolean') {
+ throw new Error('Missing APPLY_LAVAMOAT environment variable');
+ }
- tryImport('./globalthis.js');
- tryImport('./sentry-install.js');
+ loadFile('./globalthis.js');
+ loadFile('./sentry-install.js');
- if (applyLavaMoat) {
- tryImport('./runtime-lavamoat.js');
- tryImport('./lockdown-more.js');
- tryImport('./policy-load.js');
+ // Always apply LavaMoat in e2e test builds, so that we can capture initialization stats
+ if (testMode || applyLavaMoat) {
+ loadFile('./runtime-lavamoat.js');
+ loadFile('./lockdown-more.js');
+ loadFile('./policy-load.js');
} else {
- tryImport('./init-globals.js');
- tryImport('./lockdown-install.js');
- tryImport('./lockdown-run.js');
- tryImport('./lockdown-more.js');
- tryImport('./runtime-cjs.js');
+ loadFile('./init-globals.js');
+ loadFile('./lockdown-install.js');
+ loadFile('./lockdown-run.js');
+ loadFile('./lockdown-more.js');
+ loadFile('./runtime-cjs.js');
}
- const fileList = [
- // The list of files is injected at build time by replacing comment below with comma separated strings of file names
- // https://github.com/MetaMask/metamask-extension/blob/496d9d81c3367931031edc11402552690c771acf/development/build/scripts.js#L406
- /** FILE NAMES */
- ];
+ // This environment variable is set to a string of comma-separated relative file paths.
+ const rawFileList = process.env.FILE_NAMES;
+ const fileList = rawFileList.split(',');
+ fileList.forEach((fileName) => loadFile(fileName));
+
+ // Import all required resources
+ tryImport(...files);
- fileList.forEach((fileName) => tryImport(fileName));
+ const endImportScriptsTime = Date.now();
// for performance metrics/reference
console.log(
@@ -44,7 +89,55 @@ function importAllScripts() {
(Date.now() - startImportScriptsTime) / 1000
}`,
);
+
+ // In testMode load time logs are output to console
+ if (testMode) {
+ console.log(
+ `Time for each import: ${JSON.stringify(
+ {
+ name: 'Total',
+ children: loadTimeLogs,
+ startTime: startImportScriptsTime,
+ endTime: endImportScriptsTime,
+ value: endImportScriptsTime - startImportScriptsTime,
+ version: 1,
+ },
+ undefined,
+ ' ',
+ )}`,
+ );
+ }
}
-// Placing script import call here ensures that scripts are inported each time service worker is activated.
-importAllScripts();
+// Ref: https://stackoverflow.com/questions/66406672/chrome-extension-mv3-modularize-service-worker-js-file
+// eslint-disable-next-line no-undef
+self.addEventListener('install', importAllScripts);
+
+/*
+ * A keepalive message listener to prevent Service Worker getting shut down due to inactivity.
+ * UI sends the message periodically, in a setInterval.
+ * Chrome will revive the service worker if it was shut down, whenever a new message is sent, but only if a listener was defined here.
+ *
+ * chrome below needs to be replaced by cross-browser object,
+ * but there is issue in importing webextension-polyfill into service worker.
+ * chrome does seems to work in at-least all chromium based browsers
+ */
+chrome.runtime.onMessage.addListener(() => {
+ importAllScripts();
+ return false;
+});
+
+/*
+ * This content script is injected programmatically because
+ * MAIN world injection does not work properly via manifest
+ * https://bugs.chromium.org/p/chromium/issues/detail?id=634381
+ */
+chrome.scripting.registerContentScripts([
+ {
+ id: 'inpage',
+ matches: ['file://*/*', 'http://*/*', 'https://*/*'],
+ js: ['inpage.js'],
+ runAt: 'document_start',
+ world: 'MAIN',
+ },
+]);
diff --git a/app/scripts/background.js b/app/scripts/background.js
index e0caed345..084a0f0e6 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -22,6 +22,9 @@ import { SECOND } from '../../shared/constants/time';
import {
REJECT_NOTFICIATION_CLOSE,
REJECT_NOTFICIATION_CLOSE_SIG,
+ EVENT,
+ EVENT_NAMES,
+ TRAITS,
} from '../../shared/constants/metametrics';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
import { maskObject } from '../../shared/modules/object.utils';
@@ -69,6 +72,7 @@ let notificationIsOpen = false;
let uiIsTriggering = false;
const openMetamaskTabsIDs = {};
const requestAccountTabIds = {};
+let controller;
// state persistence
const inTest = process.env.IN_TEST;
@@ -96,7 +100,7 @@ const initApp = async (remotePort) => {
log.info('MetaMask initialization complete.');
};
-if (isManifestV3()) {
+if (isManifestV3) {
browser.runtime.onConnect.addListener(initApp);
} else {
// initialization flow
@@ -314,7 +318,7 @@ function setupController(initState, initLangCode, remoteSourcePort) {
// MetaMask Controller
//
- const controller = new MetamaskController({
+ controller = new MetamaskController({
infuraProjectId: process.env.INFURA_PROJECT_ID,
// User confirmation callbacks:
showUserConfirmation: triggerUi,
@@ -398,7 +402,7 @@ function setupController(initState, initLangCode, remoteSourcePort) {
//
// connect to other contexts
//
- if (isManifestV3() && remoteSourcePort) {
+ if (isManifestV3 && remoteSourcePort) {
connectRemote(remoteSourcePort);
}
@@ -472,7 +476,7 @@ function setupController(initState, initLangCode, remoteSourcePort) {
controller.isClientOpen = true;
controller.setupTrustedCommunication(portStream, remotePort.sender);
- if (isManifestV3()) {
+ if (isManifestV3) {
// Message below if captured by UI code in app/scripts/ui.js which will trigger UI initialisation
// This ensures that UI is initialised only after background is ready
// It fixes the issue of blank screen coming when extension is loaded, the issue is very frequent in MV3
@@ -601,7 +605,7 @@ function setupController(initState, initLangCode, remoteSourcePort) {
label = String(count);
}
// browserAction has been replaced by action in MV3
- if (isManifestV3()) {
+ if (isManifestV3) {
browser.action.setBadgeText({ text: label });
browser.action.setBadgeBackgroundColor({ color: '#037DD6' });
} else {
@@ -615,11 +619,11 @@ function setupController(initState, initLangCode, remoteSourcePort) {
const { unapprovedMsgCount } = controller.messageManager;
const { unapprovedPersonalMsgCount } = controller.personalMessageManager;
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager;
- const {
- unapprovedEncryptionPublicKeyMsgCount,
- } = controller.encryptionPublicKeyManager;
+ const { unapprovedEncryptionPublicKeyMsgCount } =
+ controller.encryptionPublicKeyManager;
const { unapprovedTypedMessagesCount } = controller.typedMessageManager;
- const pendingApprovalCount = controller.approvalController.getTotalApprovalCount();
+ const pendingApprovalCount =
+ controller.approvalController.getTotalApprovalCount();
const waitingForUnlockCount =
controller.appStateController.waitingForUnlock.length;
return (
@@ -751,12 +755,32 @@ async function openPopup() {
});
}
+// It adds the "App Installed" event into a queue of events, which will be tracked only after a user opts into metrics.
+const addAppInstalledEvent = () => {
+ if (controller) {
+ controller.metaMetricsController.updateTraits({
+ [TRAITS.INSTALL_DATE_EXT]: new Date().toISOString().split('T')[0], // yyyy-mm-dd
+ });
+ controller.metaMetricsController.addEventBeforeMetricsOptIn({
+ category: EVENT.CATEGORIES.APP,
+ event: EVENT_NAMES.APP_INSTALLED,
+ properties: {},
+ });
+ return;
+ }
+ setTimeout(() => {
+ // If the controller is not set yet, we wait and try to add the "App Installed" event again.
+ addAppInstalledEvent();
+ }, 1000);
+};
+
// On first install, open a new tab with MetaMask
browser.runtime.onInstalled.addListener(({ reason }) => {
if (
reason === 'install' &&
!(process.env.METAMASK_DEBUG || process.env.IN_TEST)
) {
+ addAppInstalledEvent();
platform.openExtensionInBrowser();
}
});
diff --git a/app/scripts/constants/contracts.js b/app/scripts/constants/contracts.js
index 90df403c2..e822ccc7e 100644
--- a/app/scripts/constants/contracts.js
+++ b/app/scripts/constants/contracts.js
@@ -6,3 +6,17 @@ export const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN =
'0xb8e671734ce5c8d7dfbbea5574fa4cf39f7a54a4';
export const SINGLE_CALL_BALANCES_ADDRESS_KOVAN =
'0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc';
+export const SINGLE_CALL_BALANCES_ADDRESS_GOERLI =
+ '0x9788C4E93f9002a7ad8e72633b11E8d1ecd51f9b';
+export const SINGLE_CALL_BALANCES_ADDRESS_BSC =
+ '0x2352c63A83f9Fd126af8676146721Fa00924d7e4';
+export const SINGLE_CALL_BALANCES_ADDRESS_OPTIMISM =
+ '0xB1c568e9C3E6bdaf755A60c7418C269eb11524FC';
+export const SINGLE_CALL_BALANCES_ADDRESS_POLYGON =
+ '0x2352c63A83f9Fd126af8676146721Fa00924d7e4';
+export const SINGLE_CALL_BALANCES_ADDRESS_AVALANCHE =
+ '0xD023D153a0DFa485130ECFdE2FAA7e612EF94818';
+export const SINGLE_CALL_BALANCES_ADDRESS_FANTOM =
+ '0x07f697424ABe762bB808c109860c04eA488ff92B';
+export const SINGLE_CALL_BALANCES_ADDRESS_ARBITRUM =
+ '0x151E24A486D7258dd7C33Fb67E4bB01919B7B32c';
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 5250a010e..a6fb3bcd1 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -1,4 +1,3 @@
-import querystring from 'querystring';
import pump from 'pump';
import { WindowPostMessageStream } from '@metamask/post-message-stream';
import ObjectMultiplex from 'obj-multiplex';
@@ -7,6 +6,7 @@ import PortStream from 'extension-port-stream';
import { obj as createThoughStream } from 'through2';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
+import shouldInjectProvider from '../../shared/modules/provider-injection';
// These require calls need to use require to be statically recognized by browserify
const fs = require('fs');
@@ -28,6 +28,8 @@ const PHISHING_WARNING_PAGE = 'metamask-phishing-warning-page';
const PHISHING_SAFELIST = 'metamask-phishing-safelist';
const PROVIDER = 'metamask-provider';
+// For more information about these legacy streams, see here:
+// https://github.com/MetaMask/metamask-extension/issues/15491
// TODO:LegacyProvider: Delete
const LEGACY_CONTENT_SCRIPT = 'contentscript';
const LEGACY_INPAGE = 'inpage';
@@ -42,7 +44,9 @@ if (
) {
setupPhishingStream();
} else if (shouldInjectProvider()) {
- injectScript(inpageBundle);
+ if (!isManifestV3) {
+ injectScript(inpageBundle);
+ }
setupStreams();
}
@@ -56,12 +60,7 @@ function injectScript(content) {
const container = document.head || document.documentElement;
const scriptTag = document.createElement('script');
scriptTag.setAttribute('async', 'false');
- // Inline scripts do not work in MV3 due to more strict security policy
- if (isManifestV3()) {
- scriptTag.setAttribute('src', browser.runtime.getURL('inpage.js'));
- } else {
- scriptTag.textContent = content;
- }
+ scriptTag.textContent = content;
container.insertBefore(scriptTag, container.children[0]);
container.removeChild(scriptTag);
} catch (error) {
@@ -263,108 +262,17 @@ function notifyInpageOfStreamFailure() {
);
}
-/**
- * Determines if the provider should be injected
- *
- * @returns {boolean} {@code true} Whether the provider should be injected
- */
-function shouldInjectProvider() {
- return (
- doctypeCheck() &&
- suffixCheck() &&
- documentElementCheck() &&
- !blockedDomainCheck()
- );
-}
-
-/**
- * Checks the doctype of the current document if it exists
- *
- * @returns {boolean} {@code true} if the doctype is html or if none exists
- */
-function doctypeCheck() {
- const { doctype } = window.document;
- if (doctype) {
- return doctype.name === 'html';
- }
- return true;
-}
-
-/**
- * Returns whether or not the extension (suffix) of the current document is prohibited
- *
- * This checks {@code window.location.pathname} against a set of file extensions
- * that we should not inject the provider into. This check is indifferent of
- * query parameters in the location.
- *
- * @returns {boolean} whether or not the extension of the current document is prohibited
- */
-function suffixCheck() {
- const prohibitedTypes = [/\.xml$/u, /\.pdf$/u];
- const currentUrl = window.location.pathname;
- for (let i = 0; i < prohibitedTypes.length; i++) {
- if (prohibitedTypes[i].test(currentUrl)) {
- return false;
- }
- }
- return true;
-}
-
-/**
- * Checks the documentElement of the current document
- *
- * @returns {boolean} {@code true} if the documentElement is an html node or if none exists
- */
-function documentElementCheck() {
- const documentElement = document.documentElement.nodeName;
- if (documentElement) {
- return documentElement.toLowerCase() === 'html';
- }
- return true;
-}
-
-/**
- * Checks if the current domain is blocked
- *
- * @returns {boolean} {@code true} if the current domain is blocked
- */
-function blockedDomainCheck() {
- const blockedDomains = [
- 'adyen.com',
- 'ani.gamer.com.tw',
- 'blueskybooking.com',
- 'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
- 'docs.google.com',
- 'dropbox.com',
- 'gravityforms.com',
- 'harbourair.com',
- 'sharefile.com',
- 'uscourts.gov',
- 'webbyawards.com',
- ];
- const currentUrl = window.location.href;
- let currentRegex;
- for (let i = 0; i < blockedDomains.length; i++) {
- const blockedDomain = blockedDomains[i].replace('.', '\\.');
- currentRegex = new RegExp(
- `(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`,
- 'u',
- );
- if (!currentRegex.test(currentUrl)) {
- return true;
- }
- }
- return false;
-}
-
/**
* Redirects the current page to a phishing information page
+ *
+ * @param data
*/
-function redirectToPhishingWarning() {
+function redirectToPhishingWarning(data = {}) {
console.debug('MetaMask: Routing to Phishing Warning page.');
+ const { hostname, href } = window.location;
+ const { newIssueUrl } = data;
const baseUrl = process.env.PHISHING_WARNING_PAGE_URL;
- window.location.href = `${baseUrl}#${querystring.stringify({
- hostname: window.location.hostname,
- href: window.location.href,
- })}`;
+
+ const querystring = new URLSearchParams({ hostname, href, newIssueUrl });
+ window.location.href = `${baseUrl}#${querystring}`;
}
diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js
index 5bf237e31..4dee8a8c3 100644
--- a/app/scripts/controllers/app-state.js
+++ b/app/scripts/controllers/app-state.js
@@ -37,6 +37,14 @@ export default class AppStateController extends EventEmitter {
...initState,
qrHardware: {},
collectiblesDropdownState: {},
+ usedNetworks: {
+ '0x1': true,
+ '0x2a': true,
+ '0x3': true,
+ '0x4': true,
+ '0x5': true,
+ '0x539': true,
+ },
});
this.timer = null;
@@ -294,4 +302,18 @@ export default class AppStateController extends EventEmitter {
collectiblesDropdownState,
});
}
+
+ /**
+ * Updates the array of the first time used networks
+ *
+ * @param chainId
+ * @returns {void}
+ */
+ setFirstTimeUsedNetwork(chainId) {
+ const currentState = this.store.getState();
+ const { usedNetworks } = currentState;
+ usedNetworks[chainId] = true;
+
+ this.store.updateState({ usedNetworks });
+ }
}
diff --git a/app/scripts/controllers/backup.js b/app/scripts/controllers/backup.js
new file mode 100644
index 000000000..4d7b5c371
--- /dev/null
+++ b/app/scripts/controllers/backup.js
@@ -0,0 +1,77 @@
+import { exportAsFile } from '../../../shared/modules/export-utils';
+import { prependZero } from '../../../shared/modules/string-utils';
+
+export default class BackupController {
+ constructor(opts = {}) {
+ const {
+ preferencesController,
+ addressBookController,
+ trackMetaMetricsEvent,
+ } = opts;
+
+ this.preferencesController = preferencesController;
+ this.addressBookController = addressBookController;
+ this._trackMetaMetricsEvent = trackMetaMetricsEvent;
+ }
+
+ async restoreUserData(jsonString) {
+ const existingPreferences = this.preferencesController.store.getState();
+ const { preferences, addressBook } = JSON.parse(jsonString);
+ if (preferences) {
+ preferences.identities = existingPreferences.identities;
+ preferences.lostIdentities = existingPreferences.lostIdentities;
+ preferences.selectedAddress = existingPreferences.selectedAddress;
+
+ this.preferencesController.store.updateState(preferences);
+ }
+
+ if (addressBook) {
+ this.addressBookController.update(addressBook, true);
+ }
+
+ if (preferences && addressBook) {
+ this._trackMetaMetricsEvent({
+ event: 'User Data Imported',
+ category: 'Backup',
+ });
+ }
+ }
+
+ async backupUserData() {
+ const userData = {
+ preferences: { ...this.preferencesController.store.getState() },
+ addressBook: { ...this.addressBookController.state },
+ };
+
+ /**
+ * We can remove these properties since we will won't be restoring identities from backup
+ */
+ delete userData.preferences.identities;
+ delete userData.preferences.lostIdentities;
+ delete userData.preferences.selectedAddress;
+
+ const result = JSON.stringify(userData);
+
+ const date = new Date();
+
+ const prefixZero = (num) => prependZero(num, 2);
+
+ /*
+ * userData.YYYY_MM_DD_HH_mm_SS e.g userData.2022_01_13_13_45_56
+ * */
+ const userDataFileName = `MetaMaskUserData.${date.getFullYear()}_${prefixZero(
+ date.getMonth() + 1,
+ )}_${prefixZero(date.getDay())}_${prefixZero(date.getHours())}_${prefixZero(
+ date.getMinutes(),
+ )}_${prefixZero(date.getDay())}.json`;
+
+ exportAsFile(userDataFileName, result);
+
+ this._trackMetaMetricsEvent({
+ event: 'User Data Exported',
+ category: 'Backup',
+ });
+
+ return result;
+ }
+}
diff --git a/app/scripts/controllers/backup.test.js b/app/scripts/controllers/backup.test.js
new file mode 100644
index 000000000..b4e740bcf
--- /dev/null
+++ b/app/scripts/controllers/backup.test.js
@@ -0,0 +1,118 @@
+import { strict as assert } from 'assert';
+import sinon from 'sinon';
+import BackupController from './backup';
+
+function getMockController() {
+ const mcState = {
+ getSelectedAddress: sinon.stub().returns('0x01'),
+ selectedAddress: '0x01',
+ identities: {
+ '0x295e26495CEF6F69dFA69911d9D8e4F3bBadB89B': {
+ address: '0x295e26495CEF6F69dFA69911d9D8e4F3bBadB89B',
+ lastSelected: 1655380342907,
+ name: 'Account 3',
+ },
+ },
+ lostIdentities: {
+ '0xfd59bbe569376e3d3e4430297c3c69ea93f77435': {
+ address: '0xfd59bbe569376e3d3e4430297c3c69ea93f77435',
+ lastSelected: 1655379648197,
+ name: 'Ledger 1',
+ },
+ },
+ update: (store) => (mcState.store = store),
+ };
+
+ mcState.store = {
+ getState: sinon.stub().returns(mcState),
+ updateState: (store) => (mcState.store = store),
+ };
+
+ return mcState;
+}
+
+const jsonData = `{"preferences":{"frequentRpcListDetail":[{"chainId":"0x539","nickname":"Localhost 8545","rpcPrefs":{},"rpcUrl":"http://localhost:8545","ticker":"ETH"},{"chainId":"0x38","nickname":"Binance Smart Chain Mainnet","rpcPrefs":{"blockExplorerUrl":"https://bscscan.com"},"rpcUrl":"https://bsc-dataseed1.binance.org","ticker":"BNB"},{"chainId":"0x61","nickname":"Binance Smart Chain Testnet","rpcPrefs":{"blockExplorerUrl":"https://testnet.bscscan.com"},"rpcUrl":"https://data-seed-prebsc-1-s1.binance.org:8545","ticker":"tBNB"},{"chainId":"0x89","nickname":"Polygon Mainnet","rpcPrefs":{"blockExplorerUrl":"https://polygonscan.com"},"rpcUrl":"https://polygon-rpc.com","ticker":"MATIC"}],"useBlockie":false,"useNonceField":false,"usePhishDetect":true,"dismissSeedBackUpReminder":false,"useTokenDetection":false,"useCollectibleDetection":false,"openSeaEnabled":false,"advancedGasFee":null,"featureFlags":{"sendHexData":true,"showIncomingTransactions":true},"knownMethodData":{},"currentLocale":"en","forgottenPassword":false,"preferences":{"hideZeroBalanceTokens":false,"showFiatInTestnets":false,"showTestNetworks":true,"useNativeCurrencyAsPrimaryCurrency":true},"ipfsGateway":"dweb.link","infuraBlocked":false,"ledgerTransportType":"webhid","theme":"light","customNetworkListEnabled":false,"textDirection":"auto"},"addressBook":{"addressBook":{"0x61":{"0x42EB768f2244C8811C63729A21A3569731535f06":{"address":"0x42EB768f2244C8811C63729A21A3569731535f06","chainId":"0x61","isEns":false,"memo":"","name":""}}}}}`;
+
+describe('BackupController', function () {
+ const getBackupController = () => {
+ return new BackupController({
+ preferencesController: getMockController(),
+ addressBookController: getMockController(),
+ trackMetaMetricsEvent: sinon.stub(),
+ });
+ };
+
+ describe('constructor', function () {
+ it('should setup correctly', async function () {
+ const backupController = getBackupController();
+ const selectedAddress =
+ backupController.preferencesController.getSelectedAddress();
+ assert.equal(selectedAddress, '0x01');
+ });
+
+ it('should restore backup', async function () {
+ const backupController = getBackupController();
+ backupController.restoreUserData(jsonData);
+ // check Preferences backup
+ assert.equal(
+ backupController.preferencesController.store.frequentRpcListDetail[0]
+ .chainId,
+ '0x539',
+ );
+ assert.equal(
+ backupController.preferencesController.store.frequentRpcListDetail[1]
+ .chainId,
+ '0x38',
+ );
+ // make sure identities are not lost after restore
+ assert.equal(
+ backupController.preferencesController.store.identities[
+ '0x295e26495CEF6F69dFA69911d9D8e4F3bBadB89B'
+ ].lastSelected,
+ 1655380342907,
+ );
+ assert.equal(
+ backupController.preferencesController.store.identities[
+ '0x295e26495CEF6F69dFA69911d9D8e4F3bBadB89B'
+ ].name,
+ 'Account 3',
+ );
+ assert.equal(
+ backupController.preferencesController.store.lostIdentities[
+ '0xfd59bbe569376e3d3e4430297c3c69ea93f77435'
+ ].lastSelected,
+ 1655379648197,
+ );
+ assert.equal(
+ backupController.preferencesController.store.lostIdentities[
+ '0xfd59bbe569376e3d3e4430297c3c69ea93f77435'
+ ].name,
+ 'Ledger 1',
+ );
+ // make sure selected address is not lost after restore
+ assert.equal(
+ backupController.preferencesController.store.selectedAddress,
+ '0x01',
+ );
+ // check address book backup
+ assert.equal(
+ backupController.addressBookController.store.addressBook['0x61'][
+ '0x42EB768f2244C8811C63729A21A3569731535f06'
+ ].chainId,
+ '0x61',
+ );
+ assert.equal(
+ backupController.addressBookController.store.addressBook['0x61'][
+ '0x42EB768f2244C8811C63729A21A3569731535f06'
+ ].address,
+ '0x42EB768f2244C8811C63729A21A3569731535f06',
+ );
+ assert.equal(
+ backupController.addressBookController.store.addressBook['0x61'][
+ '0x42EB768f2244C8811C63729A21A3569731535f06'
+ ].isEns,
+ false,
+ );
+ });
+ });
+});
diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js
index 697887c2f..daef5c7ac 100644
--- a/app/scripts/controllers/detect-tokens.js
+++ b/app/scripts/controllers/detect-tokens.js
@@ -1,13 +1,14 @@
import Web3 from 'web3';
import { warn } from 'loglevel';
-import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi';
-import { SINGLE_CALL_BALANCES_ADDRESS } from '../constants/contracts';
import { MINUTE } from '../../../shared/constants/time';
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
+import { STATIC_MAINNET_TOKEN_LIST } from '../../../shared/constants/tokens';
import { isTokenDetectionEnabledForNetwork } from '../../../shared/modules/network.utils';
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
-import { TOKEN_STANDARDS } from '../../../ui/helpers/constants/common';
-import { ASSET_TYPES } from '../../../shared/constants/transaction';
+import {
+ ASSET_TYPES,
+ TOKEN_STANDARDS,
+} from '../../../shared/constants/transaction';
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
// By default, poll every 3 minutes
@@ -48,14 +49,15 @@ export default class DetectTokensController {
this.network = network;
this.keyringMemStore = keyringMemStore;
this.tokenList = tokenList;
+ this.useTokenDetection =
+ this.preferences?.store.getState().useTokenDetection;
this.selectedAddress = this.preferences?.store.getState().selectedAddress;
this.tokenAddresses = this.tokensController?.state.tokens.map((token) => {
return token.address;
});
this.hiddenTokens = this.tokensController?.state.ignoredTokens;
- this.detectedTokens = process.env.TOKEN_DETECTION_V2
- ? this.tokensController?.state.detectedTokens
- : [];
+ this.detectedTokens = this.tokensController?.state.detectedTokens;
+ this.chainId = this.getChainIdFromNetworkStore(network);
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
preferences?.store.subscribe(({ selectedAddress, useTokenDetection }) => {
@@ -74,32 +76,11 @@ export default class DetectTokensController {
return token.address;
});
this.hiddenTokens = ignoredTokens;
- this.detectedTokens = process.env.TOKEN_DETECTION_V2
- ? detectedTokens
- : [];
+ this.detectedTokens = detectedTokens;
},
);
}
- /**
- * TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up
- *
- * @param tokens
- */
- async _getTokenBalances(tokens) {
- const ethContract = this.web3.eth
- .contract(SINGLE_CALL_BALANCES_ABI)
- .at(SINGLE_CALL_BALANCES_ADDRESS);
- return new Promise((resolve, reject) => {
- ethContract.balances([this.selectedAddress], tokens, (error, result) => {
- if (error) {
- return reject(error);
- }
- return resolve(result);
- });
- });
- }
-
/**
* For each token in the tokenlist provided by the TokenListController, check selectedAddress balance.
*/
@@ -108,31 +89,33 @@ export default class DetectTokensController {
return;
}
if (
- process.env.TOKEN_DETECTION_V2 &&
- (!this.useTokenDetection ||
- !isTokenDetectionEnabledForNetwork(
- this._network.store.getState().provider.chainId,
- ))
+ !isTokenDetectionEnabledForNetwork(
+ this.getChainIdFromNetworkStore(this._network),
+ )
) {
return;
}
- const { tokenList } = this._tokenList.state;
- // since the token detection is currently enabled only on Mainnet
- // we can use the chainId check to ensure token detection is not triggered for any other network
- // but once the balance check contract for other networks are deploayed and ready to use, we need to update this check.
if (
- !process.env.TOKEN_DETECTION_V2 &&
- (this._network.store.getState().provider.chainId !== MAINNET_CHAIN_ID ||
- Object.keys(tokenList).length === 0)
+ !this.useTokenDetection &&
+ this.getChainIdFromNetworkStore(this._network) !== MAINNET_CHAIN_ID
) {
return;
}
+ const isTokenDetectionInactiveInMainnet =
+ !this.useTokenDetection &&
+ this.getChainIdFromNetworkStore(this._network) === MAINNET_CHAIN_ID;
+ const { tokenList } = this._tokenList.state;
+
+ const tokenListUsed = isTokenDetectionInactiveInMainnet
+ ? STATIC_MAINNET_TOKEN_LIST
+ : tokenList;
+
const tokensToDetect = [];
this.web3.setProvider(this._network._provider);
- for (const tokenAddress in tokenList) {
+ for (const tokenAddress in tokenListUsed) {
if (
- !this.tokenAddresses.find((address) =>
+ !this.tokenAddresses.find(({ address }) =>
isEqualCaseInsensitive(address, tokenAddress),
) &&
!this.hiddenTokens.find((address) =>
@@ -152,12 +135,10 @@ export default class DetectTokensController {
for (const tokensSlice of sliceOfTokensToDetect) {
let result;
try {
- result = process.env.TOKEN_DETECTION_V2
- ? await this.assetsContractController.getBalancesInSingleCall(
- this.selectedAddress,
- tokensSlice,
- )
- : await this._getTokenBalances(tokensSlice);
+ result = await this.assetsContractController.getBalancesInSingleCall(
+ this.selectedAddress,
+ tokensSlice,
+ );
} catch (error) {
warn(
`MetaMask - DetectTokensController single call balance fetch failed`,
@@ -166,58 +147,36 @@ export default class DetectTokensController {
return;
}
- let tokensWithBalance = [];
- if (process.env.TOKEN_DETECTION_V2) {
- const eventTokensDetails = [];
- if (result) {
- const nonZeroTokenAddresses = Object.keys(result);
- for (const nonZeroTokenAddress of nonZeroTokenAddresses) {
- const {
- address,
- symbol,
- decimals,
- iconUrl,
- aggregators,
- } = tokenList[nonZeroTokenAddress];
+ const tokensWithBalance = [];
+ const eventTokensDetails = [];
+ if (result) {
+ const nonZeroTokenAddresses = Object.keys(result);
+ for (const nonZeroTokenAddress of nonZeroTokenAddresses) {
+ const { address, symbol, decimals, aggregators } =
+ tokenListUsed[nonZeroTokenAddress];
- eventTokensDetails.push(`${symbol} - ${address}`);
+ eventTokensDetails.push(`${symbol} - ${address}`);
- tokensWithBalance.push({
- address,
- symbol,
- decimals,
- image: iconUrl,
- aggregators,
- });
- }
+ tokensWithBalance.push({
+ address,
+ symbol,
+ decimals,
+ aggregators,
+ });
+ }
- if (tokensWithBalance.length > 0) {
- this._trackMetaMetricsEvent({
- event: EVENT_NAMES.TOKEN_DETECTED,
- category: EVENT.CATEGORIES.WALLET,
- properties: {
- tokens: eventTokensDetails,
- token_standard: TOKEN_STANDARDS.ERC20,
- asset_type: ASSET_TYPES.TOKEN,
- },
- });
- await this.tokensController.addDetectedTokens(tokensWithBalance);
- }
+ if (tokensWithBalance.length > 0) {
+ this._trackMetaMetricsEvent({
+ event: EVENT_NAMES.TOKEN_DETECTED,
+ category: EVENT.CATEGORIES.WALLET,
+ properties: {
+ tokens: eventTokensDetails,
+ token_standard: TOKEN_STANDARDS.ERC20,
+ asset_type: ASSET_TYPES.TOKEN,
+ },
+ });
+ await this.tokensController.addDetectedTokens(tokensWithBalance);
}
- } else {
- tokensWithBalance = tokensSlice.filter((_, index) => {
- const balance = result[index];
- return balance && !balance.isZero();
- });
- await Promise.all(
- tokensWithBalance.map((tokenAddress) => {
- return this.tokensController.addToken(
- tokenAddress,
- tokenList[tokenAddress].symbol,
- tokenList[tokenAddress].decimals,
- );
- }),
- );
}
}
}
@@ -235,6 +194,10 @@ export default class DetectTokensController {
this.interval = DEFAULT_INTERVAL;
}
+ getChainIdFromNetworkStore(network) {
+ return network?.store.getState().provider.chainId;
+ }
+
/* eslint-disable accessor-pairs */
/**
* @type {number}
@@ -258,6 +221,12 @@ export default class DetectTokensController {
}
this._network = network;
this.web3 = new Web3(network._provider);
+ this._network.store.subscribe(() => {
+ if (this.chainId !== this.getChainIdFromNetworkStore(network)) {
+ this.restartTokenDetection();
+ this.chainId = this.getChainIdFromNetworkStore(network);
+ }
+ });
}
/**
diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js
index 4fd5605b0..4f1e0334a 100644
--- a/app/scripts/controllers/detect-tokens.test.js
+++ b/app/scripts/controllers/detect-tokens.test.js
@@ -7,6 +7,7 @@ import {
ControllerMessenger,
TokenListController,
TokensController,
+ AssetsContractController,
} from '@metamask/controllers';
import { MAINNET, ROPSTEN } from '../../../shared/constants/network';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
@@ -15,9 +16,14 @@ import NetworkController from './network';
import PreferencesController from './preferences';
describe('DetectTokensController', function () {
- let tokenListController;
const sandbox = sinon.createSandbox();
- let keyringMemStore, network, preferences, provider, tokensController;
+ let assetsContractController,
+ keyringMemStore,
+ network,
+ preferences,
+ provider,
+ tokensController,
+ tokenListController;
const noop = () => undefined;
@@ -31,18 +37,44 @@ describe('DetectTokensController', function () {
network.setInfuraProjectId('foo');
network.initializeProvider(networkControllerProviderConfig);
provider = network.getProviderAndBlockTracker().provider;
- preferences = new PreferencesController({ network, provider });
- tokensController = new TokensController({
- onPreferencesStateChange: preferences.store.subscribe.bind(
- preferences.store,
- ),
- onNetworkStateChange: network.store.subscribe.bind(network.store),
+
+ const tokenListMessenger = new ControllerMessenger().getRestricted({
+ name: 'TokenListController',
+ });
+ tokenListController = new TokenListController({
+ chainId: '1',
+ preventPollingOnNetworkRestart: false,
+ onNetworkStateChange: sinon.spy(),
+ onPreferencesStateChange: sinon.spy(),
+ messenger: tokenListMessenger,
+ });
+ await tokenListController.start();
+
+ preferences = new PreferencesController({
+ network,
+ provider,
+ tokenListController,
});
preferences.setAddresses([
'0x7e57e2',
'0xbc86727e770de68b1060c91f6bb6945c73e10388',
]);
preferences.setUseTokenDetection(true);
+
+ tokensController = new TokensController({
+ onPreferencesStateChange: preferences.store.subscribe.bind(
+ preferences.store,
+ ),
+ onNetworkStateChange: network.store.subscribe.bind(network.store),
+ });
+
+ assetsContractController = new AssetsContractController({
+ onPreferencesStateChange: preferences.store.subscribe.bind(
+ preferences.store,
+ ),
+ onNetworkStateChange: network.store.subscribe.bind(network.store),
+ });
+
sandbox
.stub(network, 'getLatestBlock')
.callsFake(() => Promise.resolve({}));
@@ -122,17 +154,6 @@ describe('DetectTokensController', function () {
.get(`/tokens/3`)
.reply(200, { error: 'ChainId 3 is not supported' })
.persist();
- const tokenListMessenger = new ControllerMessenger().getRestricted({
- name: 'TokenListController',
- });
- tokenListController = new TokenListController({
- chainId: '1',
- useStaticTokenList: false,
- onNetworkStateChange: sinon.spy(),
- onPreferencesStateChange: sinon.spy(),
- messenger: tokenListMessenger,
- });
- await tokenListController.start();
});
after(function () {
@@ -155,6 +176,7 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
+ assetsContractController,
});
controller.isOpen = true;
controller.isUnlocked = true;
@@ -171,7 +193,7 @@ describe('DetectTokensController', function () {
sandbox.assert.calledThrice(stub);
});
- it('should not check tokens while on test network', async function () {
+ it('should not check and add tokens while on unsupported networks', async function () {
sandbox.useFakeTimers();
network.setProviderType(ROPSTEN);
const tokenListMessengerRopsten = new ControllerMessenger().getRestricted({
@@ -179,7 +201,6 @@ describe('DetectTokensController', function () {
});
tokenListController = new TokenListController({
chainId: '3',
- useStaticTokenList: false,
onNetworkStateChange: sinon.spy(),
onPreferencesStateChange: sinon.spy(),
messenger: tokenListMessengerRopsten,
@@ -191,17 +212,21 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
+ assetsContractController,
});
controller.isOpen = true;
controller.isUnlocked = true;
- const stub = sandbox.stub(controller, '_getTokenBalances');
+ const stub = sandbox.stub(
+ assetsContractController,
+ 'getBalancesInSingleCall',
+ );
await controller.detectNewTokens();
sandbox.assert.notCalled(stub);
});
- it('should skip adding tokens listed in hiddenTokens array', async function () {
+ it('should skip adding tokens listed in ignoredTokens array', async function () {
sandbox.useFakeTimers();
network.setProviderType(MAINNET);
const controller = new DetectTokensController({
@@ -210,52 +235,49 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
+ assetsContractController,
+ trackMetaMetricsEvent: noop,
});
controller.isOpen = true;
controller.isUnlocked = true;
const { tokenList } = tokenListController.state;
- const erc20ContractAddresses = Object.keys(tokenList);
-
- const existingTokenAddress = erc20ContractAddresses[0];
- const existingToken = tokenList[existingTokenAddress];
- await tokensController.addToken(
- existingTokenAddress,
- existingToken.symbol,
- existingToken.decimals,
- );
+ const tokenValues = Object.values(tokenList);
- const tokenAddressToSkip = erc20ContractAddresses[1];
- const tokenToSkip = tokenList[tokenAddressToSkip];
- await tokensController.addToken(
- tokenAddressToSkip,
- tokenToSkip.symbol,
- tokenToSkip.decimals,
- );
+ await tokensController.addDetectedTokens([
+ {
+ address: tokenValues[0].address,
+ symbol: tokenValues[0].symbol,
+ decimals: tokenValues[0].decimals,
+ aggregators: tokenValues[0].aggregators,
+ image: undefined,
+ isERC721: undefined,
+ },
+ ]);
sandbox
- .stub(controller, '_getTokenBalances')
+ .stub(assetsContractController, 'getBalancesInSingleCall')
.callsFake((tokensToDetect) =>
tokensToDetect.map((token) =>
- token === tokenAddressToSkip ? new BigNumber(10) : 0,
+ token.address === tokenValues[1].address ? new BigNumber(10) : 0,
),
);
+ await tokensController.ignoreTokens([tokenValues[1].address]);
- await tokensController.removeAndIgnoreToken(tokenAddressToSkip);
await controller.detectNewTokens();
-
- assert.deepEqual(tokensController.state.tokens, [
+ assert.deepEqual(tokensController.state.detectedTokens, [
{
- address: toChecksumHexAddress(existingTokenAddress),
- decimals: existingToken.decimals,
- symbol: existingToken.symbol,
+ address: toChecksumHexAddress(tokenValues[0].address),
+ decimals: tokenValues[0].decimals,
+ symbol: tokenValues[0].symbol,
+ aggregators: tokenValues[0].aggregators,
image: undefined,
- isERC721: false,
+ isERC721: undefined,
},
]);
});
- it('should check and add tokens while on main network', async function () {
+ it('should check and add tokens while on supported networks', async function () {
sandbox.useFakeTimers();
network.setProviderType(MAINNET);
const controller = new DetectTokensController({
@@ -264,6 +286,8 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
+ assetsContractController,
+ trackMetaMetricsEvent: noop,
});
controller.isOpen = true;
controller.isUnlocked = true;
@@ -273,105 +297,41 @@ describe('DetectTokensController', function () {
const existingTokenAddress = erc20ContractAddresses[0];
const existingToken = tokenList[existingTokenAddress];
- await tokensController.addToken(
- existingTokenAddress,
- existingToken.symbol,
- existingToken.decimals,
- );
-
- const tokenAddressToAdd = erc20ContractAddresses[1];
- const tokenToAdd = tokenList[tokenAddressToAdd];
- const contractAddressesToDetect = erc20ContractAddresses.filter(
- (address) => address !== existingTokenAddress,
- );
- const indexOfTokenToAdd = contractAddressesToDetect.indexOf(
- tokenAddressToAdd,
- );
- const balances = new Array(contractAddressesToDetect.length);
-
- balances[indexOfTokenToAdd] = new BigNumber(10);
-
- sandbox
- .stub(controller, '_getTokenBalances')
- .returns(Promise.resolve(balances));
-
- await controller.detectNewTokens();
- assert.deepEqual(tokensController.state.tokens, [
+ await tokensController.addDetectedTokens([
{
- address: toChecksumHexAddress(existingTokenAddress),
- decimals: existingToken.decimals,
+ address: existingToken.address,
symbol: existingToken.symbol,
- isERC721: false,
- image: undefined,
- },
- {
- address: toChecksumHexAddress(tokenAddressToAdd),
- decimals: tokenToAdd.decimals,
- symbol: tokenToAdd.symbol,
+ decimals: existingToken.decimals,
+ aggregators: existingToken.aggregators,
image: undefined,
- isERC721: false,
+ isERC721: undefined,
},
]);
- });
-
- it('should check and add tokens while on non-default Mainnet', async function () {
- sandbox.useFakeTimers();
- network.setRpcTarget('https://some-fake-RPC-endpoint.metamask.io', '0x1');
- const controller = new DetectTokensController({
- preferences,
- network,
- keyringMemStore,
- tokenList: tokenListController,
- tokensController,
- });
- controller.isOpen = true;
- controller.isUnlocked = true;
-
- const { tokenList } = tokenListController.state;
- const erc20ContractAddresses = Object.keys(tokenList);
-
- const existingTokenAddress = erc20ContractAddresses[0];
- const existingToken = tokenList[existingTokenAddress];
- await tokensController.addToken(
- existingTokenAddress,
- existingToken.symbol,
- existingToken.decimals,
- );
-
const tokenAddressToAdd = erc20ContractAddresses[1];
const tokenToAdd = tokenList[tokenAddressToAdd];
-
- const contractAddressesToDetect = erc20ContractAddresses.filter(
- (address) => address !== existingTokenAddress,
- );
- const indexOfTokenToAdd = contractAddressesToDetect.indexOf(
- tokenAddressToAdd,
- );
-
- const balances = new Array(contractAddressesToDetect.length);
- balances[indexOfTokenToAdd] = new BigNumber(10);
-
sandbox
- .stub(controller, '_getTokenBalances')
- .returns(Promise.resolve(balances));
-
+ .stub(assetsContractController, 'getBalancesInSingleCall')
+ .callsFake(() =>
+ Promise.resolve({ [tokenAddressToAdd]: new BigNumber(10) }),
+ );
await controller.detectNewTokens();
-
- assert.deepEqual(tokensController.state.tokens, [
+ assert.deepEqual(tokensController.state.detectedTokens, [
{
address: toChecksumHexAddress(existingTokenAddress),
decimals: existingToken.decimals,
symbol: existingToken.symbol,
+ aggregators: existingToken.aggregators,
image: undefined,
- isERC721: false,
+ isERC721: undefined,
},
{
address: toChecksumHexAddress(tokenAddressToAdd),
decimals: tokenToAdd.decimals,
symbol: tokenToAdd.symbol,
+ aggregators: tokenToAdd.aggregators,
image: undefined,
- isERC721: false,
+ isERC721: undefined,
},
]);
});
@@ -384,6 +344,7 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
+ assetsContractController,
});
controller.isOpen = true;
controller.isUnlocked = true;
@@ -402,6 +363,7 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
+ assetsContractController,
});
controller.isOpen = true;
controller.selectedAddress = '0x0';
@@ -419,10 +381,14 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
+ assetsContractController,
});
controller.isOpen = true;
controller.isUnlocked = false;
- const stub = sandbox.stub(controller, '_getTokenBalances');
+ const stub = sandbox.stub(
+ assetsContractController,
+ 'getBalancesInSingleCall',
+ );
clock.tick(180000);
sandbox.assert.notCalled(stub);
});
@@ -435,6 +401,7 @@ describe('DetectTokensController', function () {
network,
keyringMemStore,
tokensController,
+ assetsContractController,
});
// trigger state update from preferences controller
await preferences.setSelectedAddress(
@@ -442,7 +409,10 @@ describe('DetectTokensController', function () {
);
controller.isOpen = false;
controller.isUnlocked = true;
- const stub = sandbox.stub(controller, '_getTokenBalances');
+ const stub = sandbox.stub(
+ assetsContractController,
+ 'getBalancesInSingleCall',
+ );
clock.tick(180000);
sandbox.assert.notCalled(stub);
});
diff --git a/app/scripts/controllers/ens/ens.js b/app/scripts/controllers/ens/ens.js
index 0a629dd40..2242a2b8c 100644
--- a/app/scripts/controllers/ens/ens.js
+++ b/app/scripts/controllers/ens/ens.js
@@ -1,5 +1,6 @@
-import EthJsEns from 'ethjs-ens';
+import { ethers } from 'ethers';
import ensNetworkMap from 'ethereum-ens-network-map';
+import { NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP } from '../../../../shared/constants/network';
export default class Ens {
static getNetworkEnsSupport(network) {
@@ -7,17 +8,21 @@ export default class Ens {
}
constructor({ network, provider } = {}) {
- this._ethJsEns = new EthJsEns({
- network,
- provider,
+ const networkName = NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP[network];
+ const ensAddress = ensNetworkMap[network];
+ const ethProvider = new ethers.providers.Web3Provider(provider, {
+ chainId: parseInt(network, 10),
+ name: networkName,
+ ensAddress,
});
+ this._ethProvider = ethProvider;
}
lookup(ensName) {
- return this._ethJsEns.lookup(ensName);
+ return this._ethProvider.resolveName(ensName);
}
reverse(address) {
- return this._ethJsEns.reverse(address);
+ return this._ethProvider.lookupAddress(address);
}
}
diff --git a/app/scripts/controllers/ens/index.test.js b/app/scripts/controllers/ens/index.test.js
index c94f73a5d..d325e2885 100644
--- a/app/scripts/controllers/ens/index.test.js
+++ b/app/scripts/controllers/ens/index.test.js
@@ -20,7 +20,7 @@ describe('EnsController', function () {
describe('#constructor', function () {
it('should construct the controller given a provider and a network', async function () {
const ens = new EnsController({
- provider: {},
+ provider: sinon.fake(),
getCurrentChainId,
onNetworkDidChange,
});
diff --git a/app/scripts/controllers/incoming-transactions.js b/app/scripts/controllers/incoming-transactions.js
index 8b07f4a1f..1c264b97d 100644
--- a/app/scripts/controllers/incoming-transactions.js
+++ b/app/scripts/controllers/incoming-transactions.js
@@ -18,9 +18,8 @@ import {
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
} from '../../../shared/constants/network';
-import { SECOND } from '../../../shared/constants/time';
-const fetchWithTimeout = getFetchWithTimeout(SECOND * 30);
+const fetchWithTimeout = getFetchWithTimeout();
/**
* @typedef {import('../../../shared/constants/transaction').TransactionMeta} TransactionMeta
diff --git a/app/scripts/controllers/incoming-transactions.test.js b/app/scripts/controllers/incoming-transactions.test.js
index fd5f9d9cf..ee6f559e9 100644
--- a/app/scripts/controllers/incoming-transactions.test.js
+++ b/app/scripts/controllers/incoming-transactions.test.js
@@ -190,9 +190,8 @@ describe('IncomingTransactionsController', function () {
);
assert(mockedNetworkMethods.onNetworkDidChange.calledOnce);
- const networkControllerListenerCallback = mockedNetworkMethods.onNetworkDidChange.getCall(
- 0,
- ).args[0];
+ const networkControllerListenerCallback =
+ mockedNetworkMethods.onNetworkDidChange.getCall(0).args[0];
assert.strictEqual(incomingTransactionsController._update.callCount, 0);
networkControllerListenerCallback('testNetworkType');
assert.strictEqual(incomingTransactionsController._update.callCount, 1);
@@ -253,8 +252,10 @@ describe('IncomingTransactionsController', function () {
initState: getNonEmptyInitState(),
},
);
- const startBlock = getNonEmptyInitState()
- .incomingTxLastFetchedBlockByChainId[ROPSTEN_CHAIN_ID];
+ const startBlock =
+ getNonEmptyInitState().incomingTxLastFetchedBlockByChainId[
+ ROPSTEN_CHAIN_ID
+ ];
nock('https://api-ropsten.etherscan.io')
.get(
`/api?module=account&action=txlist&address=${MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`,
@@ -546,8 +547,10 @@ describe('IncomingTransactionsController', function () {
},
);
const NEW_MOCK_SELECTED_ADDRESS = `${MOCK_SELECTED_ADDRESS}9`;
- const startBlock = getNonEmptyInitState()
- .incomingTxLastFetchedBlockByChainId[ROPSTEN_CHAIN_ID];
+ const startBlock =
+ getNonEmptyInitState().incomingTxLastFetchedBlockByChainId[
+ ROPSTEN_CHAIN_ID
+ ];
nock('https://api-ropsten.etherscan.io')
.get(
`/api?module=account&action=txlist&address=${NEW_MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`,
@@ -572,9 +575,10 @@ describe('IncomingTransactionsController', function () {
incomingTransactionsController.store,
);
- const subscription = incomingTransactionsController.preferencesController.store.subscribe.getCall(
- 1,
- ).args[0];
+ const subscription =
+ incomingTransactionsController.preferencesController.store.subscribe.getCall(
+ 1,
+ ).args[0];
// The incoming transactions controller will always skip the first event
// We need to call subscription twice to test the event handling
// TODO: stop skipping the first event
@@ -658,9 +662,10 @@ describe('IncomingTransactionsController', function () {
incomingTransactionsController.store,
);
- const subscription = incomingTransactionsController.preferencesController.store.subscribe.getCall(
- 1,
- ).args[0];
+ const subscription =
+ incomingTransactionsController.preferencesController.store.subscribe.getCall(
+ 1,
+ ).args[0];
// The incoming transactions controller will always skip the first event
// We need to call subscription twice to test the event handling
// TODO: stop skipping the first event
@@ -682,9 +687,8 @@ describe('IncomingTransactionsController', function () {
});
it('should update when switching to a supported network', async function () {
- const mockedNetworkMethods = getMockNetworkControllerMethods(
- ROPSTEN_CHAIN_ID,
- );
+ const mockedNetworkMethods =
+ getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID);
const incomingTransactionsController = new IncomingTransactionsController(
{
blockTracker: getMockBlockTracker(),
@@ -693,8 +697,10 @@ describe('IncomingTransactionsController', function () {
initState: getNonEmptyInitState(),
},
);
- const startBlock = getNonEmptyInitState()
- .incomingTxLastFetchedBlockByChainId[ROPSTEN_CHAIN_ID];
+ const startBlock =
+ getNonEmptyInitState().incomingTxLastFetchedBlockByChainId[
+ ROPSTEN_CHAIN_ID
+ ];
nock('https://api-ropsten.etherscan.io')
.get(
`/api?module=account&action=txlist&address=${MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`,
@@ -715,8 +721,8 @@ describe('IncomingTransactionsController', function () {
incomingTransactionsController.store,
);
- const subscription = mockedNetworkMethods.onNetworkDidChange.getCall(0)
- .args[0];
+ const subscription =
+ mockedNetworkMethods.onNetworkDidChange.getCall(0).args[0];
await subscription(ROPSTEN_CHAIN_ID);
await updateStateCalled();
@@ -763,9 +769,8 @@ describe('IncomingTransactionsController', function () {
});
it('should not update when switching to an unsupported network', async function () {
- const mockedNetworkMethods = getMockNetworkControllerMethods(
- ROPSTEN_CHAIN_ID,
- );
+ const mockedNetworkMethods =
+ getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID);
const incomingTransactionsController = new IncomingTransactionsController(
{
blockTracker: getMockBlockTracker(),
@@ -796,8 +801,8 @@ describe('IncomingTransactionsController', function () {
incomingTransactionsController.store,
);
- const subscription = mockedNetworkMethods.onNetworkDidChange.getCall(0)
- .args[0];
+ const subscription =
+ mockedNetworkMethods.onNetworkDidChange.getCall(0).args[0];
incomingTransactionsController.getCurrentChainId = () => FAKE_CHAIN_ID;
await subscription();
@@ -820,15 +825,14 @@ describe('IncomingTransactionsController', function () {
describe('_update', function () {
describe('when state is empty (initialized)', function () {
it('should use provided block number and update the latest block seen', async function () {
- const incomingTransactionsController = new IncomingTransactionsController(
- {
+ const incomingTransactionsController =
+ new IncomingTransactionsController({
blockTracker: getMockBlockTracker(),
...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(),
initState: getEmptyInitState(),
getCurrentChainId: () => ROPSTEN_CHAIN_ID,
- },
- );
+ });
sinon.spy(incomingTransactionsController.store, 'updateState');
incomingTransactionsController._getNewIncomingTransactions = sinon
@@ -857,15 +861,14 @@ describe('IncomingTransactionsController', function () {
});
it('should update the last fetched block for network to highest block seen in incoming txs', async function () {
- const incomingTransactionsController = new IncomingTransactionsController(
- {
+ const incomingTransactionsController =
+ new IncomingTransactionsController({
blockTracker: getMockBlockTracker(),
...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(),
initState: getEmptyInitState(),
getCurrentChainId: () => ROPSTEN_CHAIN_ID,
- },
- );
+ });
const NEW_TRANSACTION_ONE = {
id: 555,
@@ -911,15 +914,14 @@ describe('IncomingTransactionsController', function () {
describe('when state is populated with prior data for network', function () {
it('should use the last fetched block for the current network and increment by 1 in state', async function () {
- const incomingTransactionsController = new IncomingTransactionsController(
- {
+ const incomingTransactionsController =
+ new IncomingTransactionsController({
blockTracker: getMockBlockTracker(),
...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(),
getCurrentChainId: () => ROPSTEN_CHAIN_ID,
- },
- );
+ });
sinon.spy(incomingTransactionsController.store, 'updateState');
incomingTransactionsController._getNewIncomingTransactions = sinon
.stub()
@@ -1105,11 +1107,12 @@ describe('IncomingTransactionsController', function () {
},
);
- const result = await incomingTransactionsController._getNewIncomingTransactions(
- ADDRESS_TO_FETCH_FOR,
- '789',
- ROPSTEN_CHAIN_ID,
- );
+ const result =
+ await incomingTransactionsController._getNewIncomingTransactions(
+ ADDRESS_TO_FETCH_FOR,
+ '789',
+ ROPSTEN_CHAIN_ID,
+ );
assert(mockFetch.calledOnce);
assert.deepStrictEqual(result, [
@@ -1137,11 +1140,12 @@ describe('IncomingTransactionsController', function () {
},
);
- const result = await incomingTransactionsController._getNewIncomingTransactions(
- ADDRESS_TO_FETCH_FOR,
- '789',
- ROPSTEN_CHAIN_ID,
- );
+ const result =
+ await incomingTransactionsController._getNewIncomingTransactions(
+ ADDRESS_TO_FETCH_FOR,
+ '789',
+ ROPSTEN_CHAIN_ID,
+ );
assert.deepStrictEqual(result, []);
window.fetch = tempFetchStatusZero;
mockFetchStatusZero.reset();
@@ -1164,11 +1168,12 @@ describe('IncomingTransactionsController', function () {
},
);
- const result = await incomingTransactionsController._getNewIncomingTransactions(
- ADDRESS_TO_FETCH_FOR,
- '789',
- ROPSTEN_CHAIN_ID,
- );
+ const result =
+ await incomingTransactionsController._getNewIncomingTransactions(
+ ADDRESS_TO_FETCH_FOR,
+ '789',
+ ROPSTEN_CHAIN_ID,
+ );
assert.deepStrictEqual(result, []);
window.fetch = tempFetchEmptyResult;
mockFetchEmptyResult.reset();
diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js
index 67c879c94..56d757a7c 100644
--- a/app/scripts/controllers/metametrics.js
+++ b/app/scripts/controllers/metametrics.js
@@ -19,6 +19,8 @@ import {
} from '../../../shared/constants/metametrics';
import { SECOND } from '../../../shared/constants/time';
+const EXTENSION_UNINSTALL_URL = 'https://metamask.io/uninstalled';
+
const defaultCaptureException = (err) => {
// throw error on clean stack so its captured by platform integrations (eg sentry)
// but does not interrupt the call stack
@@ -52,6 +54,9 @@ const exceptionsToFilter = {
* whether or not events are tracked
* @property {{[string]: MetaMetricsEventFragment}} [fragments] - Object keyed
* by UUID with stored fragments as values.
+ * @property {Array} [eventsBeforeMetricsOptIn] - Array of queued events added before
+ * a user opts into metrics.
+ * @property {object} [traits] - Traits that are not derived from other state keys.
*/
export default class MetaMetricsController {
@@ -69,6 +74,7 @@ export default class MetaMetricsController {
* identifier from the network controller
* @param {string} options.version - The version of the extension
* @param {string} options.environment - The environment the extension is running in
+ * @param {string} options.extension - webextension-polyfill
* @param {MetaMetricsControllerState} options.initState - State to initialized with
* @param options.captureException
*/
@@ -81,6 +87,7 @@ export default class MetaMetricsController {
version,
environment,
initState,
+ extension,
captureException = defaultCaptureException,
}) {
this._captureException = (err) => {
@@ -96,12 +103,16 @@ export default class MetaMetricsController {
this.locale = prefState.currentLocale.replace('_', '-');
this.version =
environment === 'production' ? version : `${version}-${environment}`;
+ this.extension = extension;
+ this.environment = environment;
const abandonedFragments = omitBy(initState?.fragments, 'persist');
this.store = new ObservableStore({
participateInMetaMetrics: null,
metaMetricsId: null,
+ eventsBeforeMetricsOptIn: [],
+ traits: {},
...initState,
fragments: {
...initState?.fragments,
@@ -315,6 +326,26 @@ export default class MetaMetricsController {
this._identify(allValidTraits);
}
+ // It sets an uninstall URL ("Sorry to see you go!" page),
+ // which is opened if a user uninstalls the extension.
+ updateExtensionUninstallUrl(participateInMetaMetrics, metaMetricsId) {
+ const query = {};
+ if (participateInMetaMetrics) {
+ // We only want to track these things if a user opted into metrics.
+ query.mmi = Buffer.from(metaMetricsId).toString('base64');
+ query.env = this.environment;
+ query.av = this.version;
+ }
+ const queryString = new URLSearchParams(query);
+
+ // this.extension not currently defined in tests
+ if (this.extension && this.extension.runtime) {
+ this.extension.runtime.setUninstallURL(
+ `${EXTENSION_UNINSTALL_URL}?${queryString}`,
+ );
+ }
+ }
+
/**
* Setter for the `participateInMetaMetrics` property
*
@@ -331,6 +362,12 @@ export default class MetaMetricsController {
metaMetricsId = null;
}
this.store.updateState({ participateInMetaMetrics, metaMetricsId });
+ if (participateInMetaMetrics) {
+ this.trackEventsAfterMetricsOptIn();
+ this.clearEventsAfterMetricsOptIn();
+ }
+
+ this.updateExtensionUninstallUrl(participateInMetaMetrics, metaMetricsId);
return metaMetricsId;
}
@@ -472,6 +509,37 @@ export default class MetaMetricsController {
}
}
+ // Track all queued events after a user opted into metrics.
+ trackEventsAfterMetricsOptIn() {
+ const { eventsBeforeMetricsOptIn } = this.store.getState();
+ eventsBeforeMetricsOptIn.forEach((eventBeforeMetricsOptIn) => {
+ this.trackEvent(eventBeforeMetricsOptIn);
+ });
+ }
+
+ // Once we track queued events after a user opts into metrics, we want to clear the event queue.
+ clearEventsAfterMetricsOptIn() {
+ this.store.updateState({
+ eventsBeforeMetricsOptIn: [],
+ });
+ }
+
+ // It adds an event into a queue, which is only tracked if a user opts into metrics.
+ addEventBeforeMetricsOptIn(event) {
+ const prevState = this.store.getState().eventsBeforeMetricsOptIn;
+ this.store.updateState({
+ eventsBeforeMetricsOptIn: [...prevState, event],
+ });
+ }
+
+ // Add or update traits for tracking.
+ updateTraits(newTraits) {
+ const { traits } = this.store.getState();
+ this.store.updateState({
+ traits: { ...traits, ...newTraits },
+ });
+ }
+
/** PRIVATE METHODS */
/**
@@ -549,24 +617,27 @@ export default class MetaMetricsController {
* @returns {MetaMetricsTraits | null} traits that have changed since last update
*/
_buildUserTraitsObject(metamaskState) {
+ const { traits } = this.store.getState();
/** @type {MetaMetricsTraits} */
const currentTraits = {
[TRAITS.ADDRESS_BOOK_ENTRIES]: sum(
Object.values(metamaskState.addressBook).map(size),
),
+ [TRAITS.INSTALL_DATE_EXT]: traits[TRAITS.INSTALL_DATE_EXT] || '',
[TRAITS.LEDGER_CONNECTION_TYPE]: metamaskState.ledgerTransportType,
[TRAITS.NETWORKS_ADDED]: metamaskState.frequentRpcListDetail.map(
(rpc) => rpc.chainId,
),
- [TRAITS.NETWORKS_WITHOUT_TICKER]: metamaskState.frequentRpcListDetail.reduce(
- (networkList, currentNetwork) => {
- if (!currentNetwork.ticker) {
- networkList.push(currentNetwork.chainId);
- }
- return networkList;
- },
- [],
- ),
+ [TRAITS.NETWORKS_WITHOUT_TICKER]:
+ metamaskState.frequentRpcListDetail.reduce(
+ (networkList, currentNetwork) => {
+ if (!currentNetwork.ticker) {
+ networkList.push(currentNetwork.chainId);
+ }
+ return networkList;
+ },
+ [],
+ ),
[TRAITS.NFT_AUTODETECTION_ENABLED]: metamaskState.useCollectibleDetection,
[TRAITS.NUMBER_OF_ACCOUNTS]: Object.values(metamaskState.identities)
.length,
diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js
index 804132295..c5449b90e 100644
--- a/app/scripts/controllers/metametrics.test.js
+++ b/app/scripts/controllers/metametrics.test.js
@@ -132,12 +132,10 @@ function getMetaMetricsController({
} = {}) {
return new MetaMetricsController({
segment,
- getNetworkIdentifier: networkController.getNetworkIdentifier.bind(
- networkController,
- ),
- getCurrentChainId: networkController.getCurrentChainId.bind(
- networkController,
- ),
+ getNetworkIdentifier:
+ networkController.getNetworkIdentifier.bind(networkController),
+ getCurrentChainId:
+ networkController.getCurrentChainId.bind(networkController),
onNetworkDidChange: networkController.on.bind(
networkController,
NETWORK_EVENTS.NETWORK_DID_CHANGE,
@@ -690,6 +688,7 @@ describe('MetaMetricsController', function () {
assert.deepEqual(traits, {
[TRAITS.ADDRESS_BOOK_ENTRIES]: 3,
+ [TRAITS.INSTALL_DATE_EXT]: '',
[TRAITS.LEDGER_CONNECTION_TYPE]: 'web-hid',
[TRAITS.NETWORKS_ADDED]: [MAINNET_CHAIN_ID, ROPSTEN_CHAIN_ID, '0xaf'],
[TRAITS.NETWORKS_WITHOUT_TICKER]: ['0xaf'],
diff --git a/app/scripts/controllers/network/network-controller.test.js b/app/scripts/controllers/network/network-controller.test.js
index 260d30187..0ff8f938f 100644
--- a/app/scripts/controllers/network/network-controller.test.js
+++ b/app/scripts/controllers/network/network-controller.test.js
@@ -34,8 +34,8 @@ describe('NetworkController', () => {
describe('#provider', () => {
it('provider should be updatable without reassignment', () => {
networkController.initializeProvider(networkControllerProviderConfig);
- const providerProxy = networkController.getProviderAndBlockTracker()
- .provider;
+ const providerProxy =
+ networkController.getProviderAndBlockTracker().provider;
expect(providerProxy.test).toBeUndefined();
providerProxy.setTarget({ test: true });
expect(providerProxy.test).toStrictEqual(true);
@@ -79,7 +79,8 @@ describe('NetworkController', () => {
describe('#getEIP1559Compatibility', () => {
it('should return false when baseFeePerGas is not in the block header', async () => {
networkController.initializeProvider(networkControllerProviderConfig);
- const supportsEIP1559 = await networkController.getEIP1559Compatibility();
+ const supportsEIP1559 =
+ await networkController.getEIP1559Compatibility();
expect(supportsEIP1559).toStrictEqual(false);
});
@@ -88,7 +89,8 @@ describe('NetworkController', () => {
getLatestBlockStub.callsFake(() =>
Promise.resolve({ baseFeePerGas: '0xa ' }),
);
- const supportsEIP1559 = await networkController.getEIP1559Compatibility();
+ const supportsEIP1559 =
+ await networkController.getEIP1559Compatibility();
expect(supportsEIP1559).toStrictEqual(true);
});
@@ -98,7 +100,8 @@ describe('NetworkController', () => {
Promise.resolve({ baseFeePerGas: '0xa ' }),
);
await networkController.getEIP1559Compatibility();
- const supportsEIP1559 = await networkController.getEIP1559Compatibility();
+ const supportsEIP1559 =
+ await networkController.getEIP1559Compatibility();
expect(getLatestBlockStub.calledOnce).toStrictEqual(true);
expect(supportsEIP1559).toStrictEqual(true);
});
diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js
index 9cdad5aed..a079307ab 100644
--- a/app/scripts/controllers/network/network.js
+++ b/app/scripts/controllers/network/network.js
@@ -20,7 +20,6 @@ import {
INFURA_BLOCKED_KEY,
TEST_NETWORK_TICKER_MAP,
} from '../../../../shared/constants/network';
-import { SECOND } from '../../../../shared/constants/time';
import {
isPrefixedFormattedHexString,
isSafeChainId,
@@ -31,7 +30,7 @@ import createInfuraClient from './createInfuraClient';
import createJsonRpcClient from './createJsonRpcClient';
const env = process.env.METAMASK_ENV;
-const fetchWithTimeout = getFetchWithTimeout(SECOND * 30);
+const fetchWithTimeout = getFetchWithTimeout();
let defaultProviderConfigOpts;
if (process.env.IN_TEST) {
diff --git a/app/scripts/controllers/network/pending-middleware.test.js b/app/scripts/controllers/network/pending-middleware.test.js
index be0289648..9544dc792 100644
--- a/app/scripts/controllers/network/pending-middleware.test.js
+++ b/app/scripts/controllers/network/pending-middleware.test.js
@@ -68,8 +68,7 @@ describe('PendingNonceMiddleware', () => {
from: '0xf231d46dd78806e1dd93442cf33c7671f8538748',
gas: GAS_LIMITS.SIMPLE,
gasPrice: '0x1e8480',
- hash:
- '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09',
+ hash: '0x2cc5a25744486f7383edebbf32003e5a66e18135799593d6b5cdd2bb43674f09',
input: '0x',
nonce: '0x4',
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
diff --git a/app/scripts/controllers/network/util.test.js b/app/scripts/controllers/network/util.test.js
index 4e650f3db..4ca1768cb 100644
--- a/app/scripts/controllers/network/util.test.js
+++ b/app/scripts/controllers/network/util.test.js
@@ -25,8 +25,7 @@ describe('network utils', () => {
chainId: '0x3',
time: 1624408066355,
metamaskNetworkId: '3',
- hash:
- '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
+ hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e',
s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff',
v: '0x29',
@@ -38,8 +37,7 @@ describe('network utils', () => {
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
gas: '0x7b0d',
gasPrice: '0x77359400',
- hash:
- '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
+ hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
input: '0x',
maxFeePerGas: '0x77359400',
maxPriorityFeePerGas: '0x77359400',
@@ -72,8 +70,7 @@ describe('network utils', () => {
chainId: '0x3',
time: 1624408066355,
metamaskNetworkId: '3',
- hash:
- '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
+ hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e',
s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff',
v: '0x29',
@@ -84,8 +81,7 @@ describe('network utils', () => {
blockNumber: null,
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
gas: '0x7b0d',
- hash:
- '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
+ hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
input: '0x',
gasPrice: '0x77359400',
nonce: '0x4b',
diff --git a/app/scripts/controllers/permissions/caveat-mutators.test.js b/app/scripts/controllers/permissions/caveat-mutators.test.js
index 476b3e1f6..23b140163 100644
--- a/app/scripts/controllers/permissions/caveat-mutators.test.js
+++ b/app/scripts/controllers/permissions/caveat-mutators.test.js
@@ -4,9 +4,8 @@ import { CaveatMutatorFactories } from './caveat-mutators';
describe('caveat mutators', () => {
describe('restrictReturnedAccounts', () => {
- const { removeAccount } = CaveatMutatorFactories[
- CaveatTypes.restrictReturnedAccounts
- ];
+ const { removeAccount } =
+ CaveatMutatorFactories[CaveatTypes.restrictReturnedAccounts];
describe('removeAccount', () => {
it('returns the no-op operation if the target account is not permitted', () => {
diff --git a/app/scripts/controllers/permissions/flask/snap-permissions.test.js b/app/scripts/controllers/permissions/flask/snap-permissions.test.js
index c1f5dcce6..a6ee60f78 100644
--- a/app/scripts/controllers/permissions/flask/snap-permissions.test.js
+++ b/app/scripts/controllers/permissions/flask/snap-permissions.test.js
@@ -31,7 +31,6 @@ describe('buildSnapRestrictedMethodSpecifications', () => {
expect(specification).toMatchObject({
targetKey: expect.stringMatching(/^(snap_|wallet_)/u),
methodImplementation: expect.any(Function),
- allowedCaveats: null,
});
});
});
diff --git a/app/scripts/controllers/permissions/specifications.js b/app/scripts/controllers/permissions/specifications.js
index ac52af06c..de6b1bdad 100644
--- a/app/scripts/controllers/permissions/specifications.js
+++ b/app/scripts/controllers/permissions/specifications.js
@@ -1,4 +1,7 @@
import { constructPermission, PermissionType } from '@metamask/controllers';
+///: BEGIN:ONLY_INCLUDE_IN(flask)
+import { caveatSpecifications as snapsCaveatsSpecifications } from '@metamask/rpc-methods';
+///: END:ONLY_INCLUDE_IN
import {
CaveatTypes,
RestrictedMethods,
@@ -63,6 +66,10 @@ export const getCaveatSpecifications = ({ getIdentities }) => {
validator: (caveat, _origin, _target) =>
validateCaveatAccounts(caveat.value, getIdentities),
},
+
+ ///: BEGIN:ONLY_INCLUDE_IN(flask)
+ ...snapsCaveatsSpecifications,
+ ///: END:ONLY_INCLUDE_IN
};
};
diff --git a/app/scripts/controllers/permissions/specifications.test.js b/app/scripts/controllers/permissions/specifications.test.js
index dcbb69c64..3d668b2f4 100644
--- a/app/scripts/controllers/permissions/specifications.test.js
+++ b/app/scripts/controllers/permissions/specifications.test.js
@@ -15,10 +15,15 @@ describe('PermissionController specifications', () => {
describe('caveat specifications', () => {
it('getCaveatSpecifications returns the expected specifications object', () => {
const caveatSpecifications = getCaveatSpecifications({});
- expect(Object.keys(caveatSpecifications)).toHaveLength(1);
+ expect(Object.keys(caveatSpecifications)).toHaveLength(2);
expect(
caveatSpecifications[CaveatTypes.restrictReturnedAccounts].type,
).toStrictEqual(CaveatTypes.restrictReturnedAccounts);
+
+ // TODO: Use `SnapCaveatType` from `rpc-methods` when it's exported.
+ expect(caveatSpecifications.permittedDerivationPaths.type).toStrictEqual(
+ 'permittedDerivationPaths',
+ );
});
describe('restrictReturnedAccounts', () => {
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index c1a1d2c7b..18ac909a7 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -38,7 +38,7 @@ export default class PreferencesController {
// set to true means the dynamic list from the API is being used
// set to false will be using the static list from contract-metadata
- useTokenDetection: Boolean(process.env.TOKEN_DETECTION_V2),
+ useTokenDetection: false,
useCollectibleDetection: false,
openSeaEnabled: false,
advancedGasFee: null,
@@ -79,6 +79,7 @@ export default class PreferencesController {
this.store.setMaxListeners(12);
this.openPopup = opts.openPopup;
this.migrateAddressBookState = opts.migrateAddressBookState;
+ this.tokenListController = opts.tokenListController;
this._subscribeToInfuraAvailability();
@@ -131,6 +132,13 @@ export default class PreferencesController {
*/
setUseTokenDetection(val) {
this.store.updateState({ useTokenDetection: val });
+ this.tokenListController.updatePreventPollingOnNetworkRestart(!val);
+ if (val) {
+ this.tokenListController.start();
+ } else {
+ this.tokenListController.clearingTokenListData();
+ this.tokenListController.stop();
+ }
}
/**
diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js
index 407b95b0e..8696f7997 100644
--- a/app/scripts/controllers/preferences.test.js
+++ b/app/scripts/controllers/preferences.test.js
@@ -1,5 +1,9 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
+import {
+ ControllerMessenger,
+ TokenListController,
+} from '@metamask/controllers';
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
import PreferencesController from './preferences';
import NetworkController from './network';
@@ -9,6 +13,7 @@ describe('preferences controller', function () {
let network;
let currentChainId;
let provider;
+ let tokenListController;
const migrateAddressBookState = sinon.stub();
beforeEach(function () {
@@ -21,6 +26,16 @@ describe('preferences controller', function () {
network.setInfuraProjectId('foo');
network.initializeProvider(networkControllerProviderConfig);
provider = network.getProviderAndBlockTracker().provider;
+ const tokenListMessenger = new ControllerMessenger().getRestricted({
+ name: 'TokenListController',
+ });
+ tokenListController = new TokenListController({
+ chainId: '1',
+ preventPollingOnNetworkRestart: false,
+ onNetworkStateChange: sinon.spy(),
+ onPreferencesStateChange: sinon.spy(),
+ messenger: tokenListMessenger,
+ });
sandbox
.stub(network, 'getLatestBlock')
@@ -35,6 +50,7 @@ describe('preferences controller', function () {
migrateAddressBookState,
network,
provider,
+ tokenListController,
});
});
diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js
index 01ccf0b38..e49eff3c5 100644
--- a/app/scripts/controllers/swaps.js
+++ b/app/scripts/controllers/swaps.js
@@ -90,7 +90,8 @@ const initialState = {
swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
swapsQuotePrefetchingRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
- swapsStxGetTransactionsRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
+ swapsStxGetTransactionsRefreshTime:
+ FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
swapsStxMaxFeeMultiplier: FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER,
swapsFeatureFlags: {},
},
@@ -336,10 +337,8 @@ export default class SwapsController {
if (Object.values(newQuotes).length === 0) {
this.setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR);
} else {
- const [
- _topAggId,
- quotesWithSavingsAndFeeData,
- ] = await this._findTopQuoteAndCalculateSavings(newQuotes);
+ const [_topAggId, quotesWithSavingsAndFeeData] =
+ await this._findTopQuoteAndCalculateSavings(newQuotes);
topAggId = _topAggId;
newQuotes = quotesWithSavingsAndFeeData;
}
@@ -486,10 +485,8 @@ export default class SwapsController {
const quoteToUpdate = { ...swapsState.quotes[initialAggId] };
- const {
- gasLimit: newGasEstimate,
- simulationFails,
- } = await this.timedoutGasReturn(quoteToUpdate.trade);
+ const { gasLimit: newGasEstimate, simulationFails } =
+ await this.timedoutGasReturn(quoteToUpdate.trade);
if (newGasEstimate && !simulationFails) {
const gasEstimateWithRefund = calculateGasEstimateWithRefund(
@@ -637,9 +634,8 @@ export default class SwapsController {
}
async _findTopQuoteAndCalculateSavings(quotes = {}) {
- const {
- contractExchangeRates: tokenConversionRates,
- } = this.getTokenRatesState();
+ const { contractExchangeRates: tokenConversionRates } =
+ this.getTokenRatesState();
const {
swapsState: { customGasPrice, customMaxPriorityFeePerGas },
} = this.store.getState();
@@ -652,10 +648,8 @@ export default class SwapsController {
const newQuotes = cloneDeep(quotes);
- const {
- gasFeeEstimates,
- gasEstimateType,
- } = await this._getEIP1559GasFeeEstimates();
+ const { gasFeeEstimates, gasEstimateType } =
+ await this._getEIP1559GasFeeEstimates();
let usedGasPrice = '0x0';
@@ -756,9 +750,8 @@ export default class SwapsController {
const tokenPercentageOfPreFeeDestAmount = new BigNumber(100, 10)
.minus(metaMaskFee, 10)
.div(100);
- const destinationAmountBeforeMetaMaskFee = decimalAdjustedDestinationAmount.div(
- tokenPercentageOfPreFeeDestAmount,
- );
+ const destinationAmountBeforeMetaMaskFee =
+ decimalAdjustedDestinationAmount.div(tokenPercentageOfPreFeeDestAmount);
const metaMaskFeeInTokens = destinationAmountBeforeMetaMaskFee.minus(
decimalAdjustedDestinationAmount,
);
diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps.test.js
index f8f580c4d..e34dff016 100644
--- a/app/scripts/controllers/swaps.test.js
+++ b/app/scripts/controllers/swaps.test.js
@@ -42,8 +42,7 @@ const TEST_AGG_ID_APPROVAL = 'TEST_AGG_APPROVAL';
const POLLING_TIMEOUT = SECOND * 1000;
const MOCK_APPROVAL_NEEDED = {
- data:
- '0x095ea7b300000000000000000000000095e6f48254609a6ee006f7d493c8e5fb97094cef0000000000000000000000000000000000000000004a817c7ffffffdabf41c00',
+ data: '0x095ea7b300000000000000000000000095e6f48254609a6ee006f7d493c8e5fb97094cef0000000000000000000000000000000000000000004a817c7ffffffdabf41c00',
to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
amount: '0',
from: '0x2369267687A84ac7B494daE2f1542C40E37f4455',
@@ -139,7 +138,8 @@ const EMPTY_INIT_STATE = {
swapsQuoteRefreshTime: 60000,
swapsQuotePrefetchingRefreshTime: 60000,
swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
- swapsStxGetTransactionsRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
+ swapsStxGetTransactionsRefreshTime:
+ FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
swapsStxMaxFeeMultiplier: FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER,
swapsUserFeeLevel: '',
saveFetchedQuotes: false,
@@ -370,13 +370,10 @@ describe('SwapsController', function () {
baseGasEstimate,
);
- const {
- gasLimit: bufferedGasLimit,
- } = await swapsController.getBufferedGasLimit();
- const {
- gasEstimate,
- gasEstimateWithRefund,
- } = swapsController.store.getState().swapsState.quotes[initialAggId];
+ const { gasLimit: bufferedGasLimit } =
+ await swapsController.getBufferedGasLimit();
+ const { gasEstimate, gasEstimateWithRefund } =
+ swapsController.store.getState().swapsState.quotes[initialAggId];
assert.strictEqual(gasEstimate, bufferedGasLimit);
assert.strictEqual(
gasEstimateWithRefund,
@@ -416,12 +413,10 @@ describe('SwapsController', function () {
});
it('returns the top aggId and quotes with savings and fee values if passed necessary data and an even number of quotes', async function () {
- const [
- topAggId,
- resultQuotes,
- ] = await swapsController._findTopQuoteAndCalculateSavings(
- getTopQuoteAndSavingsMockQuotes(),
- );
+ const [topAggId, resultQuotes] =
+ await swapsController._findTopQuoteAndCalculateSavings(
+ getTopQuoteAndSavingsMockQuotes(),
+ );
assert.equal(topAggId, TEST_AGG_ID_1);
assert.deepStrictEqual(
resultQuotes,
@@ -442,10 +437,8 @@ describe('SwapsController', function () {
medianMetaMaskFee: '0.0202',
};
- const [
- topAggId,
- resultQuotes,
- ] = await swapsController._findTopQuoteAndCalculateSavings(testInput);
+ const [topAggId, resultQuotes] =
+ await swapsController._findTopQuoteAndCalculateSavings(testInput);
assert.equal(topAggId, TEST_AGG_ID_1);
assert.deepStrictEqual(resultQuotes, expectedResultQuotes);
});
@@ -485,10 +478,8 @@ describe('SwapsController', function () {
},
};
- const [
- topAggId,
- resultQuotes,
- ] = await swapsController._findTopQuoteAndCalculateSavings(testInput);
+ const [topAggId, resultQuotes] =
+ await swapsController._findTopQuoteAndCalculateSavings(testInput);
assert.equal(topAggId, TEST_AGG_ID_1);
assert.deepStrictEqual(resultQuotes, expectedResultQuotes);
});
@@ -503,7 +494,8 @@ describe('SwapsController', function () {
trade: { value: '0x8ac7230489e80000' },
}),
);
- const baseExpectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults();
+ const baseExpectedResultQuotes =
+ getTopQuoteAndSavingsBaseExpectedResults();
const expectedResultQuotes = {
[TEST_AGG_ID_1]: {
...baseExpectedResultQuotes[TEST_AGG_ID_1],
@@ -549,10 +541,8 @@ describe('SwapsController', function () {
},
};
- const [
- topAggId,
- resultQuotes,
- ] = await swapsController._findTopQuoteAndCalculateSavings(testInput);
+ const [topAggId, resultQuotes] =
+ await swapsController._findTopQuoteAndCalculateSavings(testInput);
assert.equal(topAggId, TEST_AGG_ID_1);
assert.deepStrictEqual(resultQuotes, expectedResultQuotes);
});
@@ -569,7 +559,8 @@ describe('SwapsController', function () {
);
// 0.04 ETH fee included in trade value
testInput[TEST_AGG_ID_1].trade.value = '0x8b553ece48ec0000';
- const baseExpectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults();
+ const baseExpectedResultQuotes =
+ getTopQuoteAndSavingsBaseExpectedResults();
const expectedResultQuotes = {
[TEST_AGG_ID_1]: {
...baseExpectedResultQuotes[TEST_AGG_ID_1],
@@ -626,10 +617,8 @@ describe('SwapsController', function () {
delete expectedResultQuotes[TEST_AGG_ID_1].isBestQuote;
delete expectedResultQuotes[TEST_AGG_ID_1].savings;
- const [
- topAggId,
- resultQuotes,
- ] = await swapsController._findTopQuoteAndCalculateSavings(testInput);
+ const [topAggId, resultQuotes] =
+ await swapsController._findTopQuoteAndCalculateSavings(testInput);
assert.equal(topAggId, TEST_AGG_ID_2);
assert.deepStrictEqual(resultQuotes, expectedResultQuotes);
});
@@ -638,7 +627,8 @@ describe('SwapsController', function () {
const testInput = getTopQuoteAndSavingsMockQuotes();
// 0.04 ETH fee included in trade value
testInput[TEST_AGG_ID_1].trade.value = '0x8e1bc9bf040000';
- const baseExpectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults();
+ const baseExpectedResultQuotes =
+ getTopQuoteAndSavingsBaseExpectedResults();
const expectedResultQuotes = {
...baseExpectedResultQuotes,
[TEST_AGG_ID_1]: {
@@ -662,10 +652,8 @@ describe('SwapsController', function () {
delete expectedResultQuotes[TEST_AGG_ID_1].isBestQuote;
delete expectedResultQuotes[TEST_AGG_ID_1].savings;
- const [
- topAggId,
- resultQuotes,
- ] = await swapsController._findTopQuoteAndCalculateSavings(testInput);
+ const [topAggId, resultQuotes] =
+ await swapsController._findTopQuoteAndCalculateSavings(testInput);
assert.equal(topAggId, TEST_AGG_ID_2);
assert.deepStrictEqual(resultQuotes, expectedResultQuotes);
});
diff --git a/app/scripts/controllers/threebox.js b/app/scripts/controllers/threebox.js
index fc278d0b2..6af4b9711 100644
--- a/app/scripts/controllers/threebox.js
+++ b/app/scripts/controllers/threebox.js
@@ -225,9 +225,8 @@ export default class ThreeBoxController {
PreferencesController: preferences,
AddressBookController: addressBook,
};
- const initialMigrationState = migrator.generateInitialState(
- formattedStateBackup,
- );
+ const initialMigrationState =
+ migrator.generateInitialState(formattedStateBackup);
const migratedState = await migrator.migrateData(initialMigrationState);
return {
preferences: migratedState.data.PreferencesController,
diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js
index a616ce357..8103b0767 100644
--- a/app/scripts/controllers/transactions/index.js
+++ b/app/scripts/controllers/transactions/index.js
@@ -27,6 +27,8 @@ import {
import {
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
+ TRANSACTION_APPROVAL_AMOUNT_TYPE,
+ TOKEN_STANDARDS,
TRANSACTION_ENVELOPE_TYPES,
TRANSACTION_EVENTS,
} from '../../../../shared/constants/transaction';
@@ -174,9 +176,8 @@ export default class TransactionController extends EventEmitter {
);
return [...pendingTransactions, ...externalPendingTransactions];
},
- getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(
- this.txStateManager,
- ),
+ getConfirmedTransactions:
+ this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
});
this.pendingTxTracker = new PendingTransactionTracker({
@@ -189,9 +190,8 @@ export default class TransactionController extends EventEmitter {
return [...pending, ...approved];
},
approveTransaction: this.approveTransaction.bind(this),
- getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(
- this.txStateManager,
- ),
+ getCompletedTransactions:
+ this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
});
this.txStateManager.store.subscribe(() =>
@@ -227,10 +227,10 @@ export default class TransactionController extends EventEmitter {
}
async getEIP1559Compatibility(fromAddress) {
- const currentNetworkIsCompatible = await this._getCurrentNetworkEIP1559Compatibility();
- const fromAccountIsCompatible = await this._getCurrentAccountEIP1559Compatibility(
- fromAddress,
- );
+ const currentNetworkIsCompatible =
+ await this._getCurrentNetworkEIP1559Compatibility();
+ const fromAccountIsCompatible =
+ await this._getCurrentAccountEIP1559Compatibility(fromAddress);
return currentNetworkIsCompatible && fromAccountIsCompatible;
}
@@ -815,10 +815,8 @@ export default class TransactionController extends EventEmitter {
maxFeePerGas: defaultMaxFeePerGas,
maxPriorityFeePerGas: defaultMaxPriorityFeePerGas,
} = await this._getDefaultGasFees(txMeta, eip1559Compatibility);
- const {
- gasLimit: defaultGasLimit,
- simulationFails,
- } = await this._getDefaultGasLimit(txMeta, getCodeResponse);
+ const { gasLimit: defaultGasLimit, simulationFails } =
+ await this._getDefaultGasLimit(txMeta, getCodeResponse);
// eslint-disable-next-line no-param-reassign
txMeta = this.txStateManager.getTransaction(txMeta.id);
@@ -960,10 +958,8 @@ export default class TransactionController extends EventEmitter {
}
try {
- const {
- gasFeeEstimates,
- gasEstimateType,
- } = await this._getEIP1559GasFeeEstimates();
+ const { gasFeeEstimates, gasEstimateType } =
+ await this._getEIP1559GasFeeEstimates();
if (
eip1559Compatibility &&
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET
@@ -1025,11 +1021,8 @@ export default class TransactionController extends EventEmitter {
return { gasLimit: GAS_LIMITS.SIMPLE };
}
- const {
- blockGasLimit,
- estimatedGasHex,
- simulationFails,
- } = await this.txGasUtil.analyzeGasUsage(txMeta);
+ const { blockGasLimit, estimatedGasHex, simulationFails } =
+ await this.txGasUtil.analyzeGasUsage(txMeta);
// add additional gas buffer to our estimation for safety
const gasLimit = this.txGasUtil.addGasBuffer(
@@ -1203,6 +1196,7 @@ export default class TransactionController extends EventEmitter {
loadingDefaults: false,
status: TRANSACTION_STATUSES.APPROVED,
type: TRANSACTION_TYPES.RETRY,
+ originalType: originalTxMeta.type,
});
if (estimatedBaseFee) {
@@ -1531,9 +1525,8 @@ export default class TransactionController extends EventEmitter {
const metricsParams = { gas_used: gasUsed };
if (submittedTime) {
- metricsParams.completion_time = this._getTransactionCompletionTime(
- submittedTime,
- );
+ metricsParams.completion_time =
+ this._getTransactionCompletionTime(submittedTime);
}
if (txReceipt.status === '0x0') {
@@ -1592,9 +1585,8 @@ export default class TransactionController extends EventEmitter {
const metricsParams = { gas_used: gasUsed };
if (submittedTime) {
- metricsParams.completion_time = this._getTransactionCompletionTime(
- submittedTime,
- );
+ metricsParams.completion_time =
+ this._getTransactionCompletionTime(submittedTime);
}
if (txReceipt.status === '0x0') {
@@ -1659,10 +1651,8 @@ export default class TransactionController extends EventEmitter {
*/
async createTransactionEventFragment(transactionId, event) {
const txMeta = this.txStateManager.getTransaction(transactionId);
- const {
- properties,
- sensitiveProperties,
- } = await this._buildEventFragmentProperties(txMeta);
+ const { properties, sensitiveProperties } =
+ await this._buildEventFragmentProperties(txMeta);
this._createTransactionEventFragment(
txMeta,
event,
@@ -1840,6 +1830,7 @@ export default class TransactionController extends EventEmitter {
return;
}
otherTxMeta.replacedBy = txMeta.hash;
+ otherTxMeta.replacedById = txMeta.id;
this.txStateManager.updateTransaction(
txMeta,
'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce',
@@ -1976,8 +1967,64 @@ export default class TransactionController extends EventEmitter {
}
}
+ /**
+ * The allowance amount in relation to the dapp proposed amount for specific token
+ *
+ * @param {string} transactionApprovalAmountType - The transaction approval amount type
+ * @param {string} originalApprovalAmount - The original approval amount is the originally dapp proposed token amount
+ * @param {string} finalApprovalAmount - The final approval amount is the chosen amount which will be the same as the
+ * originally dapp proposed token amount if the user does not edit the amount or will be a custom token amount set by the user
+ */
+ _allowanceAmountInRelationToDappProposedValue(
+ transactionApprovalAmountType,
+ originalApprovalAmount,
+ finalApprovalAmount,
+ ) {
+ if (
+ transactionApprovalAmountType ===
+ TRANSACTION_APPROVAL_AMOUNT_TYPE.CUSTOM &&
+ originalApprovalAmount &&
+ finalApprovalAmount
+ ) {
+ return `${new BigNumber(originalApprovalAmount, 10)
+ .div(finalApprovalAmount, 10)
+ .times(100)
+ .round(2)}`;
+ }
+ return null;
+ }
+
+ /**
+ * The allowance amount in relation to the balance for that specific token
+ *
+ * @param {string} transactionApprovalAmountType - The transaction approval amount type
+ * @param {string} dappProposedTokenAmount - The dapp proposed token amount
+ * @param {string} currentTokenBalance - The balance of the token that is being send
+ */
+ _allowanceAmountInRelationToTokenBalance(
+ transactionApprovalAmountType,
+ dappProposedTokenAmount,
+ currentTokenBalance,
+ ) {
+ if (
+ (transactionApprovalAmountType ===
+ TRANSACTION_APPROVAL_AMOUNT_TYPE.CUSTOM ||
+ transactionApprovalAmountType ===
+ TRANSACTION_APPROVAL_AMOUNT_TYPE.DAPP_PROPOSED) &&
+ dappProposedTokenAmount &&
+ currentTokenBalance
+ ) {
+ return `${new BigNumber(dappProposedTokenAmount, 16)
+ .div(currentTokenBalance, 10)
+ .times(100)
+ .round(2)}`;
+ }
+ return null;
+ }
+
async _buildEventFragmentProperties(txMeta, extraParams) {
const {
+ id,
type,
time,
status,
@@ -1992,8 +2039,11 @@ export default class TransactionController extends EventEmitter {
estimateUsed,
},
defaultGasEstimates,
+ originalType,
+ replacedById,
metamaskNetworkId: network,
} = txMeta;
+ const { transactions } = this.store.getState();
const source = referrer === ORIGIN_METAMASK ? 'user' : 'dapp';
const { assetType, tokenStandard } = await determineTransactionAssetType(
@@ -2035,7 +2085,8 @@ export default class TransactionController extends EventEmitter {
if (gasFeeEstimates?.[estimateType]?.suggestedMaxPriorityFeePerGas) {
defaultMaxPriorityFeePerGas =
gasFeeEstimates[estimateType]?.suggestedMaxPriorityFeePerGas;
- gasParams.default_max_priority_fee_per_gas = defaultMaxPriorityFeePerGas;
+ gasParams.default_max_priority_fee_per_gas =
+ defaultMaxPriorityFeePerGas;
}
}
}
@@ -2068,12 +2119,93 @@ export default class TransactionController extends EventEmitter {
eip1559Version = eip1559V2Enabled ? '2' : '1';
}
- const properties = {
+ const contractInteractionTypes = [
+ TRANSACTION_TYPES.CONTRACT_INTERACTION,
+ TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
+ TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM,
+ TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL,
+ TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
+ TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
+ TRANSACTION_TYPES.SMART,
+ TRANSACTION_TYPES.SWAP,
+ TRANSACTION_TYPES.SWAP_APPROVAL,
+ ].includes(type);
+
+ const contractMethodNames = {
+ APPROVE: 'Approve',
+ };
+
+ const customTokenAmount = transactions[id]?.customTokenAmount;
+ const dappProposedTokenAmount = transactions[id]?.dappProposedTokenAmount;
+ const currentTokenBalance = transactions[id]?.currentTokenBalance;
+ const originalApprovalAmount = transactions[id]?.originalApprovalAmount;
+ const finalApprovalAmount = transactions[id]?.finalApprovalAmount;
+ let transactionApprovalAmountType;
+ let transactionContractMethod;
+ let transactionApprovalAmountVsProposedRatio;
+ let transactionApprovalAmountVsBalanceRatio;
+ let transactionType = TRANSACTION_TYPES.SIMPLE_SEND;
+ if (type === TRANSACTION_TYPES.CANCEL) {
+ transactionType = TRANSACTION_TYPES.CANCEL;
+ } else if (type === TRANSACTION_TYPES.RETRY) {
+ transactionType = originalType;
+ } else if (type === TRANSACTION_TYPES.DEPLOY_CONTRACT) {
+ transactionType = TRANSACTION_TYPES.DEPLOY_CONTRACT;
+ } else if (contractInteractionTypes) {
+ transactionType = TRANSACTION_TYPES.CONTRACT_INTERACTION;
+ transactionContractMethod = transactions[id]?.contractMethodName;
+ if (
+ transactionContractMethod === contractMethodNames.APPROVE &&
+ tokenStandard === TOKEN_STANDARDS.ERC20
+ ) {
+ if (dappProposedTokenAmount === '0' || customTokenAmount === '0') {
+ transactionApprovalAmountType =
+ TRANSACTION_APPROVAL_AMOUNT_TYPE.REVOKE;
+ } else if (customTokenAmount) {
+ transactionApprovalAmountType =
+ TRANSACTION_APPROVAL_AMOUNT_TYPE.CUSTOM;
+ } else if (dappProposedTokenAmount) {
+ transactionApprovalAmountType =
+ TRANSACTION_APPROVAL_AMOUNT_TYPE.DAPP_PROPOSED;
+ }
+ transactionApprovalAmountVsProposedRatio =
+ this._allowanceAmountInRelationToDappProposedValue(
+ transactionApprovalAmountType,
+ originalApprovalAmount,
+ finalApprovalAmount,
+ );
+ transactionApprovalAmountVsBalanceRatio =
+ this._allowanceAmountInRelationToTokenBalance(
+ transactionApprovalAmountType,
+ dappProposedTokenAmount,
+ currentTokenBalance,
+ );
+ }
+ }
+
+ const replacedTxMeta = this._getTransaction(replacedById);
+
+ const TRANSACTION_REPLACEMENT_METHODS = {
+ RETRY: TRANSACTION_TYPES.RETRY,
+ CANCEL: TRANSACTION_TYPES.CANCEL,
+ SAME_NONCE: 'other',
+ };
+
+ let transactionReplaced;
+ if (extraParams?.dropped) {
+ transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.SAME_NONCE;
+ if (replacedTxMeta?.type === TRANSACTION_TYPES.CANCEL) {
+ transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.CANCEL;
+ } else if (replacedTxMeta?.type === TRANSACTION_TYPES.RETRY) {
+ transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.RETRY;
+ }
+ }
+
+ let properties = {
chain_id: chainId,
referrer,
source,
network,
- type,
eip_1559_version: eip1559Version,
gas_edit_type: 'none',
gas_edit_attempted: 'none',
@@ -2081,19 +2213,40 @@ export default class TransactionController extends EventEmitter {
device_model: await this.getDeviceModel(this.getSelectedAddress()),
asset_type: assetType,
token_standard: tokenStandard,
+ transaction_type: transactionType,
+ transaction_speed_up: type === TRANSACTION_TYPES.RETRY,
};
- const sensitiveProperties = {
+ if (transactionContractMethod === contractMethodNames.APPROVE) {
+ properties = {
+ ...properties,
+ transaction_approval_amount_type: transactionApprovalAmountType,
+ };
+ }
+
+ let sensitiveProperties = {
status,
transaction_envelope_type: isEIP1559Transaction(txMeta)
? TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET
: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
first_seen: time,
gas_limit: gasLimit,
+ transaction_contract_method: transactionContractMethod,
+ transaction_replaced: transactionReplaced,
...extraParams,
...gasParamsInGwei,
};
+ if (transactionContractMethod === contractMethodNames.APPROVE) {
+ sensitiveProperties = {
+ ...sensitiveProperties,
+ transaction_approval_amount_vs_balance_ratio:
+ transactionApprovalAmountVsBalanceRatio,
+ transaction_approval_amount_vs_proposed_ratio:
+ transactionApprovalAmountVsProposedRatio,
+ };
+ }
+
return { properties, sensitiveProperties };
}
@@ -2228,10 +2381,8 @@ export default class TransactionController extends EventEmitter {
if (!txMeta) {
return;
}
- const {
- properties,
- sensitiveProperties,
- } = await this._buildEventFragmentProperties(txMeta, extraParams);
+ const { properties, sensitiveProperties } =
+ await this._buildEventFragmentProperties(txMeta, extraParams);
// Create event fragments for event types that spawn fragments, and ensure
// existence of fragments for event types that act upon them.
@@ -2300,6 +2451,8 @@ export default class TransactionController extends EventEmitter {
_dropTransaction(txId) {
this.txStateManager.setTxStatusDropped(txId);
const txMeta = this.txStateManager.getTransaction(txId);
- this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.FINALIZED);
+ this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.FINALIZED, {
+ dropped: true,
+ });
}
}
diff --git a/app/scripts/controllers/transactions/index.test.js b/app/scripts/controllers/transactions/index.test.js
index c8adf4191..eb386706e 100644
--- a/app/scripts/controllers/transactions/index.test.js
+++ b/app/scripts/controllers/transactions/index.test.js
@@ -17,6 +17,7 @@ import {
TRANSACTION_ENVELOPE_TYPES,
TRANSACTION_EVENTS,
ASSET_TYPES,
+ TOKEN_STANDARDS,
} from '../../../../shared/constants/transaction';
import { SECOND } from '../../../../shared/constants/time';
@@ -26,7 +27,6 @@ import {
} from '../../../../shared/constants/gas';
import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../ui/helpers/constants/transactions';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
-import { TOKEN_STANDARDS } from '../../../../ui/helpers/constants/common';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
import TransactionController from '.';
@@ -1471,17 +1471,20 @@ describe('Transaction Controller', function () {
network: '42',
referrer: ORIGIN_METAMASK,
source: EVENT.SOURCE.TRANSACTION.USER,
- type: TRANSACTION_TYPES.SIMPLE_SEND,
+ transaction_type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
token_standard: TOKEN_STANDARDS.NONE,
device_model: 'N/A',
+ transaction_speed_up: false,
},
sensitiveProperties: {
default_gas: '0.000031501',
default_gas_price: '2',
gas_price: '2',
gas_limit: '0x7b0d',
+ transaction_contract_method: undefined,
+ transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
@@ -1550,17 +1553,20 @@ describe('Transaction Controller', function () {
network: '42',
referrer: ORIGIN_METAMASK,
source: EVENT.SOURCE.TRANSACTION.USER,
- type: TRANSACTION_TYPES.SIMPLE_SEND,
+ transaction_type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
token_standard: TOKEN_STANDARDS.NONE,
device_model: 'N/A',
+ transaction_speed_up: false,
},
sensitiveProperties: {
default_gas: '0.000031501',
default_gas_price: '2',
gas_price: '2',
gas_limit: '0x7b0d',
+ transaction_contract_method: undefined,
+ transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
@@ -1639,17 +1645,20 @@ describe('Transaction Controller', function () {
network: '42',
referrer: 'other',
source: EVENT.SOURCE.TRANSACTION.DAPP,
- type: TRANSACTION_TYPES.SIMPLE_SEND,
+ transaction_type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
token_standard: TOKEN_STANDARDS.NONE,
device_model: 'N/A',
+ transaction_speed_up: false,
},
sensitiveProperties: {
default_gas: '0.000031501',
default_gas_price: '2',
gas_price: '2',
gas_limit: '0x7b0d',
+ transaction_contract_method: undefined,
+ transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
@@ -1720,17 +1729,20 @@ describe('Transaction Controller', function () {
network: '42',
referrer: 'other',
source: EVENT.SOURCE.TRANSACTION.DAPP,
- type: TRANSACTION_TYPES.SIMPLE_SEND,
+ transaction_type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
token_standard: TOKEN_STANDARDS.NONE,
device_model: 'N/A',
+ transaction_speed_up: false,
},
sensitiveProperties: {
default_gas: '0.000031501',
default_gas_price: '2',
gas_price: '2',
gas_limit: '0x7b0d',
+ transaction_contract_method: undefined,
+ transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
@@ -1801,15 +1813,18 @@ describe('Transaction Controller', function () {
network: '42',
referrer: 'other',
source: EVENT.SOURCE.TRANSACTION.DAPP,
- type: TRANSACTION_TYPES.SIMPLE_SEND,
+ transaction_type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
token_standard: TOKEN_STANDARDS.NONE,
device_model: 'N/A',
+ transaction_speed_up: false,
},
sensitiveProperties: {
gas_price: '2',
gas_limit: '0x7b0d',
+ transaction_contract_method: undefined,
+ transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
@@ -1860,7 +1875,7 @@ describe('Transaction Controller', function () {
network: '42',
referrer: 'other',
source: EVENT.SOURCE.TRANSACTION.DAPP,
- type: TRANSACTION_TYPES.SIMPLE_SEND,
+ transaction_type: TRANSACTION_TYPES.SIMPLE_SEND,
chain_id: '0x2a',
eip_1559_version: '0',
gas_edit_attempted: 'none',
@@ -1869,12 +1884,15 @@ describe('Transaction Controller', function () {
asset_type: ASSET_TYPES.NATIVE,
token_standard: TOKEN_STANDARDS.NONE,
device_model: 'N/A',
+ transaction_speed_up: false,
},
sensitiveProperties: {
baz: 3.0,
foo: 'bar',
gas_price: '2',
gas_limit: '0x7b0d',
+ transaction_contract_method: undefined,
+ transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
@@ -1937,11 +1955,12 @@ describe('Transaction Controller', function () {
network: '42',
referrer: 'other',
source: EVENT.SOURCE.TRANSACTION.DAPP,
- type: TRANSACTION_TYPES.SIMPLE_SEND,
+ transaction_type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
token_standard: TOKEN_STANDARDS.NONE,
device_model: 'N/A',
+ transaction_speed_up: false,
},
sensitiveProperties: {
baz: 3.0,
@@ -1949,6 +1968,8 @@ describe('Transaction Controller', function () {
max_fee_per_gas: '2',
max_priority_fee_per_gas: '2',
gas_limit: '0x7b0d',
+ transaction_contract_method: undefined,
+ transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET,
status: 'unapproved',
@@ -2277,8 +2298,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getCode = '0xab';
// test update gasFees
await txController.updateEditableParams('1', {
- data:
- '0xa9059cbb000000000000000000000000e18035bf8712672935fdb4e5e431b1a0183d2dfc0000000000000000000000000000000000000000000000000de0b6b3a7640000',
+ data: '0xa9059cbb000000000000000000000000e18035bf8712672935fdb4e5e431b1a0183d2dfc0000000000000000000000000000000000000000000000000de0b6b3a7640000',
});
const result = txStateManager.getTransaction('1');
assert.equal(
@@ -2302,8 +2322,7 @@ describe('Transaction Controller', function () {
// maxFeePerGas: '0x004',
to: VALID_ADDRESS,
from: VALID_ADDRESS,
- data:
- '0xa9059cbb000000000000000000000000e18035bf8712672935fdb4e5e431b1a0183d2dfc0000000000000000000000000000000000000000000000000de0b6b3a7640000',
+ data: '0xa9059cbb000000000000000000000000e18035bf8712672935fdb4e5e431b1a0183d2dfc0000000000000000000000000000000000000000000000000de0b6b3a7640000',
},
estimateUsed: '0x005',
estimatedBaseFee: '0x006',
diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js
index 7fbaca8ce..e5a56b8ab 100644
--- a/app/scripts/controllers/transactions/pending-tx-tracker.js
+++ b/app/scripts/controllers/transactions/pending-tx-tracker.js
@@ -198,13 +198,8 @@ export default class PendingTransactionTracker extends EventEmitter {
try {
const transactionReceipt = await this.query.getTransactionReceipt(txHash);
if (transactionReceipt?.blockNumber) {
- const {
- baseFeePerGas,
- timestamp: blockTimestamp,
- } = await this.query.getBlockByHash(
- transactionReceipt?.blockHash,
- false,
- );
+ const { baseFeePerGas, timestamp: blockTimestamp } =
+ await this.query.getBlockByHash(transactionReceipt?.blockHash, false);
this.emit(
'tx:confirmed',
diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.test.js b/app/scripts/controllers/transactions/pending-tx-tracker.test.js
index b359b25f9..572837fe7 100644
--- a/app/scripts/controllers/transactions/pending-tx-tracker.test.js
+++ b/app/scripts/controllers/transactions/pending-tx-tracker.test.js
@@ -154,8 +154,7 @@ describe('PendingTransactionTracker', function () {
it('should call _checkPendingTx for each pending transaction', async function () {
const txMeta = {
id: 1,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.SIGNED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -212,8 +211,7 @@ describe('PendingTransactionTracker', function () {
it('should publish a new transaction', async function () {
const txMeta = {
id: 1,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.SIGNED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -257,8 +255,7 @@ describe('PendingTransactionTracker', function () {
it('should publish the given transaction if more than 2**retryCount blocks have passed', async function () {
const txMeta = {
id: 1,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.SIGNED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -304,8 +301,7 @@ describe('PendingTransactionTracker', function () {
it('should NOT publish the given transaction if fewer than 2**retryCount blocks have passed', async function () {
const txMeta = {
id: 1,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.SIGNED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -404,8 +400,7 @@ describe('PendingTransactionTracker', function () {
assert.ok(
await pendingTxTracker._checkIfTxWasDropped({
id: 1,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.SUBMITTED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -437,8 +432,7 @@ describe('PendingTransactionTracker', function () {
const dropped = await pendingTxTracker._checkIfTxWasDropped({
id: 1,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.SUBMITTED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -458,8 +452,7 @@ describe('PendingTransactionTracker', function () {
const confirmedTxList = [
{
id: 1,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.CONFIRMED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -471,8 +464,7 @@ describe('PendingTransactionTracker', function () {
},
{
id: 2,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.CONFIRMED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -517,8 +509,7 @@ describe('PendingTransactionTracker', function () {
const confirmedTxList = [
{
id: 1,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.CONFIRMED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -530,8 +521,7 @@ describe('PendingTransactionTracker', function () {
},
{
id: 2,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.CONFIRMED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -577,8 +567,7 @@ describe('PendingTransactionTracker', function () {
it("should emit 'tx:warning' if getTransactionReceipt rejects", async function () {
const txMeta = {
id: 1,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.SUBMITTED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
@@ -730,8 +719,7 @@ describe('PendingTransactionTracker', function () {
txParams: { nonce: '0x1' },
id: '123',
value: '0x02',
- hash:
- '0x2a919d2512ec963f524bfd9730fb66b6d5a2e399d1dd957abb5e2b544a12644b',
+ hash: '0x2a919d2512ec963f524bfd9730fb66b6d5a2e399d1dd957abb5e2b544a12644b',
},
];
const pendingTxTracker = new PendingTransactionTracker({
@@ -774,8 +762,7 @@ describe('PendingTransactionTracker', function () {
const nonceBN = new BN(2);
const txMeta = {
id: 1,
- hash:
- '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: TRANSACTION_STATUSES.SUBMITTED,
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js
index 350cb1eaa..cddcfc946 100644
--- a/app/scripts/controllers/transactions/tx-gas-utils.js
+++ b/app/scripts/controllers/transactions/tx-gas-utils.js
@@ -102,11 +102,8 @@ export default class TxGasUtil {
}
async getBufferedGasLimit(txMeta, multiplier) {
- const {
- blockGasLimit,
- estimatedGasHex,
- simulationFails,
- } = await this.analyzeGasUsage(txMeta);
+ const { blockGasLimit, estimatedGasHex, simulationFails } =
+ await this.analyzeGasUsage(txMeta);
// add additional gas buffer to our estimation for safety
const gasLimit = this.addGasBuffer(
diff --git a/app/scripts/disable-console.js b/app/scripts/disable-console.js
index d1ba36d57..ff6fa46ed 100644
--- a/app/scripts/disable-console.js
+++ b/app/scripts/disable-console.js
@@ -2,7 +2,7 @@
// eslint-disable-next-line import/unambiguous
if (
!(typeof process !== 'undefined' && process.env.METAMASK_DEBUG) &&
- typeof console !== undefined
+ typeof console !== 'undefined'
) {
console.log = noop;
console.info = noop;
diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js
index 9b4952a5e..95fce4fc7 100644
--- a/app/scripts/inpage.js
+++ b/app/scripts/inpage.js
@@ -34,6 +34,7 @@ cleanContextForImports();
import log from 'loglevel';
import { WindowPostMessageStream } from '@metamask/post-message-stream';
import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider';
+import shouldInjectProvider from '../../shared/modules/provider-injection';
restoreContextAfterImports();
@@ -43,14 +44,16 @@ log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn');
// setup plugin communication
//
-// setup background connection
-const metamaskStream = new WindowPostMessageStream({
- name: 'metamask-inpage',
- target: 'metamask-contentscript',
-});
-
-initializeProvider({
- connectionStream: metamaskStream,
- logger: log,
- shouldShimWeb3: true,
-});
+if (shouldInjectProvider()) {
+ // setup background connection
+ const metamaskStream = new WindowPostMessageStream({
+ name: 'metamask-inpage',
+ target: 'metamask-contentscript',
+ });
+
+ initializeProvider({
+ connectionStream: metamaskStream,
+ logger: log,
+ shouldShimWeb3: true,
+ });
+}
diff --git a/app/scripts/lib/ComposableObservableStore.js b/app/scripts/lib/ComposableObservableStore.js
index 6d156ab29..cbb346f5c 100644
--- a/app/scripts/lib/ComposableObservableStore.js
+++ b/app/scripts/lib/ComposableObservableStore.js
@@ -16,7 +16,7 @@ export default class ComposableObservableStore extends ObservableStore {
* extends one of the two base controllers in the `@metamask/controllers`
* package.
*
- * @type {Record}
+ * @type {Record}
*/
config = {};
@@ -43,7 +43,7 @@ export default class ComposableObservableStore extends ObservableStore {
/**
* Composes a new internal store subscription structure
*
- * @param {Record} config - Describes which stores are being
+ * @param {Record} config - Describes which stores are being
* composed. The key is the name of the store, and the value is either an
* ObserableStore, or a controller that extends one of the two base
* controllers in the `@metamask/controllers` package.
diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js
index d8c90588c..8dea01fc9 100644
--- a/app/scripts/lib/account-tracker.js
+++ b/app/scripts/lib/account-tracker.js
@@ -19,6 +19,13 @@ import {
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
KOVAN_CHAIN_ID,
+ GOERLI_CHAIN_ID,
+ BSC_CHAIN_ID,
+ OPTIMISM_CHAIN_ID,
+ POLYGON_CHAIN_ID,
+ AVALANCHE_CHAIN_ID,
+ FANTOM_CHAIN_ID,
+ ARBITRUM_CHAIN_ID,
} from '../../../shared/constants/network';
import {
@@ -26,6 +33,13 @@ import {
SINGLE_CALL_BALANCES_ADDRESS_RINKEBY,
SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN,
SINGLE_CALL_BALANCES_ADDRESS_KOVAN,
+ SINGLE_CALL_BALANCES_ADDRESS_GOERLI,
+ SINGLE_CALL_BALANCES_ADDRESS_BSC,
+ SINGLE_CALL_BALANCES_ADDRESS_OPTIMISM,
+ SINGLE_CALL_BALANCES_ADDRESS_POLYGON,
+ SINGLE_CALL_BALANCES_ADDRESS_AVALANCHE,
+ SINGLE_CALL_BALANCES_ADDRESS_FANTOM,
+ SINGLE_CALL_BALANCES_ADDRESS_ARBITRUM,
} from '../constants/contracts';
import { bnToHex } from './util';
@@ -230,6 +244,55 @@ export default class AccountTracker {
);
break;
+ case GOERLI_CHAIN_ID:
+ await this._updateAccountsViaBalanceChecker(
+ addresses,
+ SINGLE_CALL_BALANCES_ADDRESS_GOERLI,
+ );
+ break;
+
+ case BSC_CHAIN_ID:
+ await this._updateAccountsViaBalanceChecker(
+ addresses,
+ SINGLE_CALL_BALANCES_ADDRESS_BSC,
+ );
+ break;
+
+ case OPTIMISM_CHAIN_ID:
+ await this._updateAccountsViaBalanceChecker(
+ addresses,
+ SINGLE_CALL_BALANCES_ADDRESS_OPTIMISM,
+ );
+ break;
+
+ case POLYGON_CHAIN_ID:
+ await this._updateAccountsViaBalanceChecker(
+ addresses,
+ SINGLE_CALL_BALANCES_ADDRESS_POLYGON,
+ );
+ break;
+
+ case AVALANCHE_CHAIN_ID:
+ await this._updateAccountsViaBalanceChecker(
+ addresses,
+ SINGLE_CALL_BALANCES_ADDRESS_AVALANCHE,
+ );
+ break;
+
+ case FANTOM_CHAIN_ID:
+ await this._updateAccountsViaBalanceChecker(
+ addresses,
+ SINGLE_CALL_BALANCES_ADDRESS_FANTOM,
+ );
+ break;
+
+ case ARBITRUM_CHAIN_ID:
+ await this._updateAccountsViaBalanceChecker(
+ addresses,
+ SINGLE_CALL_BALANCES_ADDRESS_ARBITRUM,
+ );
+ break;
+
default:
await Promise.all(addresses.map(this._updateAccount.bind(this)));
}
@@ -243,8 +306,17 @@ export default class AccountTracker {
* @returns {Promise} after the account balance is updated
*/
async _updateAccount(address) {
+ let balance = '0x0';
+
// query balance
- const balance = await this._query.getBalance(address);
+ try {
+ balance = await this._query.getBalance(address);
+ } catch (error) {
+ if (error.data?.request?.method !== 'eth_getBalance') {
+ throw error;
+ }
+ }
+
const result = { address, balance };
// update accounts state
const { accounts } = this.store.getState();
diff --git a/app/scripts/lib/buy-url.js b/app/scripts/lib/buy-url.js
index 7cd885a66..579e0c51c 100644
--- a/app/scripts/lib/buy-url.js
+++ b/app/scripts/lib/buy-url.js
@@ -9,7 +9,6 @@ import {
ROPSTEN_CHAIN_ID,
BUYABLE_CHAINS_MAP,
} from '../../../shared/constants/network';
-import { SECOND } from '../../../shared/constants/time';
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
import {
TRANSAK_API_KEY,
@@ -17,7 +16,7 @@ import {
COINBASEPAY_API_KEY,
} from '../constants/on-ramp';
-const fetchWithTimeout = getFetchWithTimeout(SECOND * 30);
+const fetchWithTimeout = getFetchWithTimeout();
/**
* Create a Wyre purchase URL.
@@ -83,9 +82,8 @@ const createTransakUrl = (walletAddress, chainId) => {
* @returns String
*/
const createMoonPayUrl = async (walletAddress, chainId) => {
- const {
- moonPay: { defaultCurrencyCode, showOnlyCurrencies } = {},
- } = BUYABLE_CHAINS_MAP[chainId];
+ const { moonPay: { defaultCurrencyCode, showOnlyCurrencies } = {} } =
+ BUYABLE_CHAINS_MAP[chainId];
const moonPayQueryParams = new URLSearchParams({
apiKey: MOONPAY_API_KEY,
walletAddress,
diff --git a/app/scripts/lib/buy-url.test.js b/app/scripts/lib/buy-url.test.js
index b582bae19..21c3a3498 100644
--- a/app/scripts/lib/buy-url.test.js
+++ b/app/scripts/lib/buy-url.test.js
@@ -116,9 +116,8 @@ describe('buy-url', () => {
});
it('returns a MoonPay url with a prefilled wallet address for the Ethereum network', async () => {
- const {
- moonPay: { defaultCurrencyCode, showOnlyCurrencies } = {},
- } = BUYABLE_CHAINS_MAP[MAINNET.chainId];
+ const { moonPay: { defaultCurrencyCode, showOnlyCurrencies } = {} } =
+ BUYABLE_CHAINS_MAP[MAINNET.chainId];
const moonPayQueryParams = new URLSearchParams({
apiKey: MOONPAY_API_KEY,
walletAddress: MAINNET.address,
diff --git a/app/scripts/lib/ens-ipfs/resolver.js b/app/scripts/lib/ens-ipfs/resolver.js
index 95073b908..297b3e555 100644
--- a/app/scripts/lib/ens-ipfs/resolver.js
+++ b/app/scripts/lib/ens-ipfs/resolver.js
@@ -35,9 +35,8 @@ export default async function resolveEnsToIpfsContentId({ provider, name }) {
const type = contentHash.getCodec(rawContentHash);
if (type === 'ipfs-ns' || type === 'ipns-ns') {
- decodedContentHash = contentHash.helpers.cidV0ToV1Base32(
- decodedContentHash,
- );
+ decodedContentHash =
+ contentHash.helpers.cidV0ToV1Base32(decodedContentHash);
}
return { type, hash: decodedContentHash };
diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js
index 2d19811c3..942b888cc 100644
--- a/app/scripts/lib/ens-ipfs/setup.js
+++ b/app/scripts/lib/ens-ipfs/setup.js
@@ -2,11 +2,10 @@ import base32Encode from 'base32-encode';
import base64 from 'base64-js';
import browser from 'webextension-polyfill';
-import { SECOND } from '../../../../shared/constants/time';
import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout';
import resolveEnsToIpfsContentId from './resolver';
-const fetchWithTimeout = getFetchWithTimeout(SECOND * 30);
+const fetchWithTimeout = getFetchWithTimeout();
const supportedTopLevelDomains = ['eth'];
diff --git a/app/scripts/lib/metaRPCClientFactory.js b/app/scripts/lib/metaRPCClientFactory.js
index 1dfe572d2..c69651c01 100644
--- a/app/scripts/lib/metaRPCClientFactory.js
+++ b/app/scripts/lib/metaRPCClientFactory.js
@@ -3,6 +3,8 @@ import SafeEventEmitter from 'safe-event-emitter';
import createRandomId from '../../../shared/modules/random-id';
import { TEN_SECONDS_IN_MILLISECONDS } from '../../../ui/helpers/constants/critical-error';
+class DisconnectError extends Error {}
+
class MetaRPCClient {
constructor(connectionStream) {
this.connectionStream = connectionStream;
@@ -12,6 +14,7 @@ class MetaRPCClient {
this.connectionStream.on('data', this.handleResponse.bind(this));
this.connectionStream.on('end', this.close.bind(this));
this.responseHandled = {};
+ this.DisconnectError = DisconnectError;
}
send(id, payload, cb) {
@@ -47,6 +50,13 @@ class MetaRPCClient {
close() {
this.notificationChannel.removeAllListeners();
this.uncaughtErrorChannel.removeAllListeners();
+ // fail all unfinished requests
+ for (const [id, handler] of this.requests) {
+ if (!this.responseHandled[id]) {
+ this.responseHandled[id] = true;
+ handler(new DisconnectError('disconnected'));
+ }
+ }
}
handleResponse(data) {
diff --git a/app/scripts/lib/metaRPCClientFactory.test.js b/app/scripts/lib/metaRPCClientFactory.test.js
index d8fbf6fe3..ccf547bdd 100644
--- a/app/scripts/lib/metaRPCClientFactory.test.js
+++ b/app/scripts/lib/metaRPCClientFactory.test.js
@@ -1,3 +1,4 @@
+/* eslint-disable jest/no-done-callback */
import { obj as createThoughStream } from 'through2';
import metaRPCClientFactory from './metaRPCClientFactory';
@@ -9,13 +10,14 @@ describe('metaRPCClientFactory', () => {
const metaRPCClient = metaRPCClientFactory(streamTest);
metaRPCClient.foo();
});
- it('should be able to make an rpc request/response with the method and params and node-style callback', () => {
+ it('should be able to make an rpc request/response with the method and params and node-style callback', (done) => {
const streamTest = createThoughStream();
const metaRPCClient = metaRPCClientFactory(streamTest);
// make a "foo" method call
metaRPCClient.foo('bar', (_, result) => {
expect(result).toStrictEqual('foobarbaz');
+ done();
});
// fake a response
@@ -27,7 +29,7 @@ describe('metaRPCClientFactory', () => {
});
});
});
- it('should be able to make an rpc request/error with the method and params and node-style callback', () => {
+ it('should be able to make an rpc request/error with the method and params and node-style callback', (done) => {
const streamTest = createThoughStream();
const metaRPCClient = metaRPCClientFactory(streamTest);
@@ -35,6 +37,7 @@ describe('metaRPCClientFactory', () => {
metaRPCClient.foo('bar', (err) => {
expect(err.message).toStrictEqual('foo-message');
expect(err.code).toStrictEqual(1);
+ done();
});
metaRPCClient.requests.forEach((_, key) => {
@@ -49,7 +52,7 @@ describe('metaRPCClientFactory', () => {
});
});
- it('should be able to make an rpc request/response with the method and params and node-style callback with multiple instances of metaRPCClientFactory and the same connectionStream', () => {
+ it('should be able to make an rpc request/response with the method and params and node-style callback with multiple instances of metaRPCClientFactory and the same connectionStream', (done) => {
const streamTest = createThoughStream();
const metaRPCClient = metaRPCClientFactory(streamTest);
const metaRPCClient2 = metaRPCClientFactory(streamTest);
@@ -59,6 +62,7 @@ describe('metaRPCClientFactory', () => {
expect(result).toStrictEqual('foobarbaz');
metaRPCClient2.baz('bar', (err) => {
expect(err).toBeNull();
+ done();
});
});
@@ -81,12 +85,13 @@ describe('metaRPCClientFactory', () => {
});
});
- it('should be able to handle notifications', () => {
+ it('should be able to handle notifications', (done) => {
const streamTest = createThoughStream();
const metaRPCClient = metaRPCClientFactory(streamTest);
metaRPCClient.onNotification((notification) => {
expect(notification.method).toStrictEqual('foobarbaz');
+ done();
});
// send a notification
@@ -97,12 +102,13 @@ describe('metaRPCClientFactory', () => {
});
});
- it('should be able to handle errors with no id', () => {
+ it('should be able to handle errors with no id', (done) => {
const streamTest = createThoughStream();
const metaRPCClient = metaRPCClientFactory(streamTest);
metaRPCClient.onUncaughtError((error) => {
expect(error.code).toStrictEqual(1);
+ done();
});
streamTest.write({
@@ -114,12 +120,13 @@ describe('metaRPCClientFactory', () => {
});
});
- it('should be able to handle errors with null id', () => {
+ it('should be able to handle errors with null id', (done) => {
const streamTest = createThoughStream();
const metaRPCClient = metaRPCClientFactory(streamTest);
metaRPCClient.onUncaughtError((error) => {
expect(error.code).toStrictEqual(1);
+ done();
});
streamTest.write({
@@ -132,7 +139,7 @@ describe('metaRPCClientFactory', () => {
});
});
- it('should be able to handle no message within TIMEOUT secs', async () => {
+ it('should be able to handle no message within TIMEOUT secs for getState', async () => {
jest.useFakeTimers();
const streamTest = createThoughStream();
const metaRPCClient = metaRPCClientFactory(streamTest);
@@ -148,4 +155,17 @@ describe('metaRPCClientFactory', () => {
jest.useRealTimers();
});
+
+ it('should fail all pending actions with a DisconnectError when the stream ends', (done) => {
+ const streamTest = createThoughStream();
+ const metaRPCClient = metaRPCClientFactory(streamTest);
+
+ metaRPCClient.foo('bar', (err) => {
+ expect(err).toBeInstanceOf(metaRPCClient.DisconnectError);
+ expect(err.message).toStrictEqual('disconnected');
+ done();
+ });
+
+ streamTest.emit('end');
+ });
});
diff --git a/app/scripts/lib/network-store.js b/app/scripts/lib/network-store.js
index 75f7796a0..abee778c1 100644
--- a/app/scripts/lib/network-store.js
+++ b/app/scripts/lib/network-store.js
@@ -1,8 +1,7 @@
import log from 'loglevel';
-import { SECOND } from '../../../shared/constants/time';
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
-const fetchWithTimeout = getFetchWithTimeout(SECOND * 30);
+const fetchWithTimeout = getFetchWithTimeout();
const FIXTURE_SERVER_HOST = 'localhost';
const FIXTURE_SERVER_PORT = 12345;
diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js
index 00017bc37..ffc45575b 100644
--- a/app/scripts/lib/personal-message-manager.js
+++ b/app/scripts/lib/personal-message-manager.js
@@ -7,6 +7,7 @@ import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import createId from '../../../shared/modules/random-id';
import { EVENT } from '../../../shared/constants/metametrics';
+import { detectSIWE } from '../../../shared/modules/siwe';
import { addHexPrefix } from './util';
const hexRe = /^[0-9A-Fa-f]+$/gu;
@@ -135,6 +136,11 @@ export default class PersonalMessageManager extends EventEmitter {
msgParams.origin = req.origin;
}
msgParams.data = this.normalizeMsgData(msgParams.data);
+
+ // check for SIWE message
+ const siwe = detectSIWE(msgParams);
+ msgParams.siwe = siwe;
+
// create txData obj with parameters and meta data
const time = new Date().getTime();
const msgId = createId();
@@ -314,8 +320,9 @@ export default class PersonalMessageManager extends EventEmitter {
*/
_saveMsgList() {
const unapprovedPersonalMsgs = this.getUnapprovedMsgs();
- const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs)
- .length;
+ const unapprovedPersonalMsgCount = Object.keys(
+ unapprovedPersonalMsgs,
+ ).length;
this.memStore.updateState({
unapprovedPersonalMsgs,
unapprovedPersonalMsgCount,
diff --git a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js
index 642fdb1ce..dfae4b8d9 100644
--- a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js
+++ b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js
@@ -2,7 +2,7 @@
import { handlers as permittedSnapMethods } from '@metamask/rpc-methods/dist/permitted';
///: END:ONLY_INCLUDE_IN
import { permissionRpcMethods } from '@metamask/controllers';
-import { selectHooks } from '@metamask/rpc-methods';
+import { selectHooks } from '@metamask/rpc-methods/dist/utils';
import { ethErrors } from 'eth-rpc-errors';
import { flatten } from 'lodash';
import { UNSUPPORTED_RPC_METHODS } from '../../../../shared/constants/network';
@@ -31,7 +31,7 @@ const expectedHookNames = Array.from(
*
* @param {Record} hooks - Required "hooks" into our
* controllers.
- * @returns {(req: Object, res: Object, next: Function, end: Function) => void}
+ * @returns {(req: object, res: object, next: Function, end: Function) => void}
*/
export function createMethodMiddleware(hooks) {
// Fail immediately if we forgot to provide any expected hooks.
diff --git a/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js b/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js
index 223743a92..d31302966 100644
--- a/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js
+++ b/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js
@@ -289,25 +289,34 @@ async function addEthereumChainHandler(
}),
);
+ let rpcUrlOrigin;
+ try {
+ rpcUrlOrigin = new URL(firstValidRPCUrl).origin;
+ } catch {
+ // ignore
+ }
+
sendMetrics({
event: 'Custom Network Added',
category: EVENT.CATEGORIES.NETWORK,
referrer: {
url: origin,
},
- sensitiveProperties: {
+ properties: {
chain_id: _chainId,
- rpc_url: firstValidRPCUrl,
network_name: _chainName,
// Including network to override the default network
// property included in all events. For RPC type networks
// the MetaMetrics controller uses the rpcUrl for the network
// property.
- network: firstValidRPCUrl,
+ network: rpcUrlOrigin,
symbol: ticker,
block_explorer_url: firstValidBlockExplorerUrl,
source: EVENT.SOURCE.TRANSACTION.DAPP,
},
+ sensitiveProperties: {
+ rpc_url: rpcUrlOrigin,
+ },
});
// Once the network has been added, the requested is considered successful
diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js
index 8d5562c47..60356ba8a 100644
--- a/app/scripts/lib/typed-message-manager.js
+++ b/app/scripts/lib/typed-message-manager.js
@@ -391,8 +391,9 @@ export default class TypedMessageManager extends EventEmitter {
*/
_saveMsgList() {
const unapprovedTypedMessages = this.getUnapprovedMsgs();
- const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages)
- .length;
+ const unapprovedTypedMessagesCount = Object.keys(
+ unapprovedTypedMessages,
+ ).length;
this.memStore.updateState({
unapprovedTypedMessages,
unapprovedTypedMessagesCount,
diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js
new file mode 100644
index 000000000..62902e5cb
--- /dev/null
+++ b/app/scripts/metamask-controller.actions.test.js
@@ -0,0 +1,212 @@
+import { strict as assert } from 'assert';
+import sinon from 'sinon';
+import proxyquire from 'proxyquire';
+
+const Ganache = require('../../test/e2e/ganache');
+
+const ganacheServer = new Ganache();
+
+const browserPolyfillMock = {
+ runtime: {
+ id: 'fake-extension-id',
+ onInstalled: {
+ addListener: () => undefined,
+ },
+ onMessageExternal: {
+ addListener: () => undefined,
+ },
+ getPlatformInfo: async () => 'mac',
+ },
+};
+
+let loggerMiddlewareMock;
+const createLoggerMiddlewareMock = () => (req, res, next) => {
+ if (loggerMiddlewareMock) {
+ loggerMiddlewareMock.requests.push(req);
+ next((cb) => {
+ loggerMiddlewareMock.responses.push(res);
+ cb();
+ });
+ return;
+ }
+ next();
+};
+
+const TEST_SEED =
+ 'debris dizzy just program just float decrease vacant alarm reduce speak stadium';
+
+const MetaMaskController = proxyquire('./metamask-controller', {
+ './lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock },
+}).default;
+
+describe('MetaMaskController', function () {
+ let metamaskController;
+ const sandbox = sinon.createSandbox();
+ const noop = () => undefined;
+
+ before(async function () {
+ await ganacheServer.start();
+ });
+
+ beforeEach(function () {
+ metamaskController = new MetaMaskController({
+ showUserConfirmation: noop,
+ encryptor: {
+ encrypt(_, object) {
+ this.object = object;
+ return Promise.resolve('mock-encrypted');
+ },
+ decrypt() {
+ return Promise.resolve(this.object);
+ },
+ },
+ initLangCode: 'en_US',
+ platform: {
+ showTransactionNotification: () => undefined,
+ getVersion: () => 'foo',
+ },
+ browser: browserPolyfillMock,
+ infuraProjectId: 'foo',
+ });
+ });
+
+ afterEach(function () {
+ sandbox.restore();
+ });
+
+ after(async function () {
+ await ganacheServer.quit();
+ });
+
+ describe('#addNewAccount', function () {
+ it('two parallel calls with same accountCount give same result', async function () {
+ await metamaskController.createNewVaultAndKeychain('test@123');
+ const [addNewAccountResult1, addNewAccountResult2] = await Promise.all([
+ metamaskController.addNewAccount(1),
+ metamaskController.addNewAccount(1),
+ ]);
+ assert.deepEqual(
+ Object.keys(addNewAccountResult1.identities),
+ Object.keys(addNewAccountResult2.identities),
+ );
+ });
+
+ it('two successive calls with same accountCount give same result', async function () {
+ await metamaskController.createNewVaultAndKeychain('test@123');
+ const addNewAccountResult1 = await metamaskController.addNewAccount(1);
+ const addNewAccountResult2 = await metamaskController.addNewAccount(1);
+ assert.deepEqual(
+ Object.keys(addNewAccountResult1.identities),
+ Object.keys(addNewAccountResult2.identities),
+ );
+ });
+
+ it('two successive calls with different accountCount give different results', async function () {
+ await metamaskController.createNewVaultAndKeychain('test@123');
+ const addNewAccountResult1 = await metamaskController.addNewAccount(1);
+ const addNewAccountResult2 = await metamaskController.addNewAccount(2);
+ assert.notDeepEqual(addNewAccountResult1, addNewAccountResult2);
+ });
+ });
+
+ describe('#importAccountWithStrategy', function () {
+ it('two sequential calls with same strategy give same result', async function () {
+ let keyringControllerState1;
+ let keyringControllerState2;
+ const importPrivkey =
+ '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553';
+
+ await metamaskController.createNewVaultAndKeychain('test@123');
+ await Promise.all([
+ metamaskController.importAccountWithStrategy('Private Key', [
+ importPrivkey,
+ ]),
+ Promise.resolve(1).then(() => {
+ keyringControllerState1 = JSON.stringify(
+ metamaskController.keyringController.memStore.getState(),
+ );
+ metamaskController.importAccountWithStrategy('Private Key', [
+ importPrivkey,
+ ]);
+ }),
+ Promise.resolve(2).then(() => {
+ keyringControllerState2 = JSON.stringify(
+ metamaskController.keyringController.memStore.getState(),
+ );
+ }),
+ ]);
+ assert.deepEqual(keyringControllerState1, keyringControllerState2);
+ });
+ });
+
+ describe('#createNewVaultAndRestore', function () {
+ it('two successive calls with same inputs give same result', async function () {
+ const result1 = await metamaskController.createNewVaultAndRestore(
+ 'test@123',
+ TEST_SEED,
+ );
+ const result2 = await metamaskController.createNewVaultAndRestore(
+ 'test@123',
+ TEST_SEED,
+ );
+ assert.deepEqual(result1, result2);
+ });
+ });
+
+ describe('#createNewVaultAndKeychain', function () {
+ it('two successive calls with same inputs give same result', async function () {
+ const result1 = await metamaskController.createNewVaultAndKeychain(
+ 'test@123',
+ );
+ const result2 = await metamaskController.createNewVaultAndKeychain(
+ 'test@123',
+ );
+ assert.notEqual(result1, undefined);
+ assert.deepEqual(result1, result2);
+ });
+ });
+
+ describe('#addToken', function () {
+ const address = '0x514910771af9ca656af840dff83e8264ecf986ca';
+ const symbol = 'LINK';
+ const decimals = 18;
+
+ it('two parallel calls with same token details give same result', async function () {
+ const supportsInterfaceStub = sinon
+ .stub()
+ .returns(Promise.resolve(false));
+ sinon
+ .stub(metamaskController.tokensController, '_createEthersContract')
+ .callsFake(() =>
+ Promise.resolve({ supportsInterface: supportsInterfaceStub }),
+ );
+
+ const [token1, token2] = await Promise.all([
+ metamaskController.getApi().addToken(address, symbol, decimals),
+ metamaskController.getApi().addToken(address, symbol, decimals),
+ ]);
+ assert.deepEqual(token1, token2);
+ });
+ });
+
+ describe('#addCustomNetwork', function () {
+ const customRpc = {
+ chainId: '0x1',
+ chainName: 'DUMMY_CHAIN_NAME',
+ rpcUrl: 'DUMMY_RPCURL',
+ ticker: 'DUMMY_TICKER',
+ blockExplorerUrl: 'DUMMY_EXPLORER',
+ };
+ it('two successive calls with custom RPC details give same result', async function () {
+ await metamaskController.addCustomNetwork(customRpc);
+ const rpcList1Length =
+ metamaskController.preferencesController.store.getState()
+ .frequentRpcListDetail.length;
+ await metamaskController.addCustomNetwork(customRpc);
+ const rpcList2Length =
+ metamaskController.preferencesController.store.getState()
+ .frequentRpcListDetail.length;
+ assert.equal(rpcList1Length, rpcList2Length);
+ });
+ });
+});
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index d7159d2f2..1a915ac78 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -51,12 +51,15 @@ import {
SnapController,
IframeExecutionService,
} from '@metamask/snap-controllers';
+import { satisfies as satisfiesSemver } from 'semver';
///: END:ONLY_INCLUDE_IN
import {
+ ASSET_TYPES,
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
} from '../../shared/constants/transaction';
+import { PHISHING_NEW_ISSUE_URLS } from '../../shared/constants/phishing';
import {
GAS_API_BASE_URL,
GAS_DEV_API_BASE_URL,
@@ -85,7 +88,7 @@ import {
POLLING_TOKEN_ENVIRONMENT_TYPES,
SUBJECT_TYPES,
} from '../../shared/constants/app';
-import { EVENT } from '../../shared/constants/metametrics';
+import { EVENT, EVENT_NAMES } from '../../shared/constants/metametrics';
import { hexToDecimal } from '../../ui/helpers/utils/conversions.util';
import {
@@ -94,6 +97,7 @@ import {
} from '../../ui/helpers/utils/token-util';
import { isEqualCaseInsensitive } from '../../shared/modules/string-utils';
import { parseStandardTokenTransactionData } from '../../shared/modules/transaction.utils';
+import { STATIC_MAINNET_TOKEN_LIST } from '../../shared/constants/tokens';
import {
onMessageReceived,
checkForMultipleVersionsRunning,
@@ -119,6 +123,7 @@ import CachedBalancesController from './controllers/cached-balances';
import AlertController from './controllers/alert';
import OnboardingController from './controllers/onboarding';
import ThreeBoxController from './controllers/threebox';
+import BackupController from './controllers/backup';
import IncomingTransactionsController from './controllers/incoming-transactions';
import MessageManager, { normalizeMsgData } from './lib/message-manager';
import DecryptMessageManager from './lib/decrypt-message-manager';
@@ -226,14 +231,40 @@ export default class MetamaskController extends EventEmitter {
// now we can initialize the RPC provider, which other controllers require
this.initializeProvider();
- this.provider = this.networkController.getProviderAndBlockTracker().provider;
- this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker;
+ this.provider =
+ this.networkController.getProviderAndBlockTracker().provider;
+ this.blockTracker =
+ this.networkController.getProviderAndBlockTracker().blockTracker;
+
+ const tokenListMessenger = this.controllerMessenger.getRestricted({
+ name: 'TokenListController',
+ });
+
+ this.tokenListController = new TokenListController({
+ chainId: hexToDecimal(this.networkController.getCurrentChainId()),
+ preventPollingOnNetworkRestart: true,
+ onNetworkStateChange: (cb) => {
+ this.networkController.store.subscribe((networkState) => {
+ const modifiedNetworkState = {
+ ...networkState,
+ provider: {
+ ...networkState.provider,
+ chainId: hexToDecimal(networkState.provider.chainId),
+ },
+ };
+ return cb(modifiedNetworkState);
+ });
+ },
+ messenger: tokenListMessenger,
+ state: initState.TokenListController,
+ });
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
initLangCode: opts.initLangCode,
openPopup: opts.openPopup,
network: this.networkController,
+ tokenListController: this.tokenListController,
provider: this.provider,
migrateAddressBookState: this.migrateAddressBookState.bind(this),
});
@@ -248,62 +279,75 @@ export default class MetamaskController extends EventEmitter {
config: { provider: this.provider },
state: initState.TokensController,
});
- process.env.TOKEN_DETECTION_V2
- ? (this.assetsContractController = new AssetsContractController({
- onPreferencesStateChange: (listener) =>
- this.preferencesController.store.subscribe(listener),
- onNetworkStateChange: (cb) =>
- this.networkController.store.subscribe((networkState) => {
- const modifiedNetworkState = {
- ...networkState,
- provider: {
- ...networkState.provider,
- chainId: hexToDecimal(networkState.provider.chainId),
- },
- };
- return cb(modifiedNetworkState);
- }),
- config: {
- provider: this.provider,
- },
- state: initState.AssetsContractController,
- }))
- : (this.assetsContractController = new AssetsContractController(
- {
- onPreferencesStateChange: (listener) =>
- this.preferencesController.store.subscribe(listener),
- },
- {
- provider: this.provider,
- },
- ));
+
+ this.assetsContractController = new AssetsContractController(
+ {
+ onPreferencesStateChange: (listener) =>
+ this.preferencesController.store.subscribe(listener),
+ onNetworkStateChange: (cb) =>
+ this.networkController.store.subscribe((networkState) => {
+ const modifiedNetworkState = {
+ ...networkState,
+ provider: {
+ ...networkState.provider,
+ chainId: hexToDecimal(networkState.provider.chainId),
+ },
+ };
+ return cb(modifiedNetworkState);
+ }),
+ },
+ {
+ provider: this.provider,
+ },
+ initState.AssetsContractController,
+ );
this.collectiblesController = new CollectiblesController(
{
- onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
- this.preferencesController.store,
- ),
+ onPreferencesStateChange:
+ this.preferencesController.store.subscribe.bind(
+ this.preferencesController.store,
+ ),
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
),
- getERC721AssetName: this.assetsContractController.getERC721AssetName.bind(
- this.assetsContractController,
- ),
- getERC721AssetSymbol: this.assetsContractController.getERC721AssetSymbol.bind(
- this.assetsContractController,
- ),
+ getERC721AssetName:
+ this.assetsContractController.getERC721AssetName.bind(
+ this.assetsContractController,
+ ),
+ getERC721AssetSymbol:
+ this.assetsContractController.getERC721AssetSymbol.bind(
+ this.assetsContractController,
+ ),
getERC721TokenURI: this.assetsContractController.getERC721TokenURI.bind(
this.assetsContractController,
),
getERC721OwnerOf: this.assetsContractController.getERC721OwnerOf.bind(
this.assetsContractController,
),
- getERC1155BalanceOf: this.assetsContractController.getERC1155BalanceOf.bind(
- this.assetsContractController,
- ),
- getERC1155TokenURI: this.assetsContractController.getERC1155TokenURI.bind(
- this.assetsContractController,
- ),
+ getERC1155BalanceOf:
+ this.assetsContractController.getERC1155BalanceOf.bind(
+ this.assetsContractController,
+ ),
+ getERC1155TokenURI:
+ this.assetsContractController.getERC1155TokenURI.bind(
+ this.assetsContractController,
+ ),
+ onCollectibleAdded: ({ address, symbol, tokenId, standard, source }) =>
+ this.metaMetricsController.trackEvent({
+ event: EVENT_NAMES.NFT_ADDED,
+ category: EVENT.CATEGORIES.WALLET,
+ properties: {
+ token_contract_address: address,
+ token_symbol: symbol,
+ asset_type: ASSET_TYPES.COLLECTIBLE,
+ token_standard: standard,
+ source,
+ },
+ sensitiveProperties: {
+ tokenId,
+ },
+ }),
},
{},
initState.CollectiblesController,
@@ -316,16 +360,18 @@ export default class MetamaskController extends EventEmitter {
{
onCollectiblesStateChange: (listener) =>
this.collectiblesController.subscribe(listener),
- onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
- this.preferencesController.store,
- ),
+ onPreferencesStateChange:
+ this.preferencesController.store.subscribe.bind(
+ this.preferencesController.store,
+ ),
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
),
getOpenSeaApiKey: () => this.collectiblesController.openSeaApiKey,
- getBalancesInSingleCall: this.assetsContractController.getBalancesInSingleCall.bind(
- this.assetsContractController,
- ),
+ getBalancesInSingleCall:
+ this.assetsContractController.getBalancesInSingleCall.bind(
+ this.assetsContractController,
+ ),
addCollectible: this.collectiblesController.addCollectible.bind(
this.collectiblesController,
),
@@ -348,6 +394,7 @@ export default class MetamaskController extends EventEmitter {
),
version: this.platform.getVersion(),
environment: process.env.METAMASK_ENVIRONMENT,
+ extension: this.extension,
initState: initState.MetaMetricsController,
captureException,
});
@@ -374,12 +421,12 @@ export default class MetamaskController extends EventEmitter {
this.networkController,
NETWORK_EVENTS.NETWORK_DID_CHANGE,
),
- getCurrentNetworkEIP1559Compatibility: this.networkController.getEIP1559Compatibility.bind(
- this.networkController,
- ),
- getCurrentAccountEIP1559Compatibility: this.getCurrentAccountEIP1559Compatibility.bind(
- this,
- ),
+ getCurrentNetworkEIP1559Compatibility:
+ this.networkController.getEIP1559Compatibility.bind(
+ this.networkController,
+ ),
+ getCurrentAccountEIP1559Compatibility:
+ this.getCurrentAccountEIP1559Compatibility.bind(this),
legacyAPIEndpoint: `${gasApiBaseUrl}/networks//gasPrices`,
EIP1559APIEndpoint: `${gasApiBaseUrl}/networks//suggestedGasFees`,
getCurrentNetworkLegacyGasAPICompatibility: () => {
@@ -417,54 +464,6 @@ export default class MetamaskController extends EventEmitter {
},
});
- const tokenListMessenger = this.controllerMessenger.getRestricted({
- name: 'TokenListController',
- });
- process.env.TOKEN_DETECTION_V2
- ? (this.tokenListController = new TokenListController({
- chainId: hexToDecimal(this.networkController.getCurrentChainId()),
- onNetworkStateChange: (cb) =>
- this.networkController.store.subscribe((networkState) => {
- const modifiedNetworkState = {
- ...networkState,
- provider: {
- ...networkState.provider,
- chainId: hexToDecimal(networkState.provider.chainId),
- },
- };
- return cb(modifiedNetworkState);
- }),
- messenger: tokenListMessenger,
- state: initState.TokenListController,
- }))
- : (this.tokenListController = new TokenListController({
- chainId: hexToDecimal(this.networkController.getCurrentChainId()),
- useStaticTokenList: !this.preferencesController.store.getState()
- .useTokenDetection,
- onNetworkStateChange: (cb) =>
- this.networkController.store.subscribe((networkState) => {
- const modifiedNetworkState = {
- ...networkState,
- provider: {
- ...networkState.provider,
- chainId: hexToDecimal(networkState.provider.chainId),
- },
- };
- return cb(modifiedNetworkState);
- }),
- onPreferencesStateChange: (cb) =>
- this.preferencesController.store.subscribe((preferencesState) => {
- const modifiedPreferencesState = {
- ...preferencesState,
- useStaticTokenList: !this.preferencesController.store.getState()
- .useTokenDetection,
- };
- return cb(modifiedPreferencesState);
- }),
- messenger: tokenListMessenger,
- state: initState.TokenListController,
- }));
-
this.phishingController = new PhishingController();
this.announcementController = new AnnouncementController(
@@ -533,12 +532,16 @@ export default class MetamaskController extends EventEmitter {
this.accountTracker.start();
this.incomingTransactionsController.start();
this.currencyRateController.start();
- this.tokenListController.start();
+ if (this.preferencesController.store.getState().useTokenDetection) {
+ this.tokenListController.start();
+ }
} else {
this.accountTracker.stop();
this.incomingTransactionsController.stop();
this.currencyRateController.stop();
- this.tokenListController.stop();
+ if (this.preferencesController.store.getState().useTokenDetection) {
+ this.tokenListController.stop();
+ }
}
});
@@ -603,10 +606,11 @@ export default class MetamaskController extends EventEmitter {
const accountsMissingIdentities = accounts.filter(
(address) => !identities[address],
);
- const keyringTypesWithMissingIdentities = accountsMissingIdentities.map(
- (address) =>
- this.keyringController.getKeyringForAccount(address)?.type,
- );
+ const keyringTypesWithMissingIdentities =
+ accountsMissingIdentities.map(
+ (address) =>
+ this.keyringController.getKeyringForAccount(address)?.type,
+ );
const identitiesCount = Object.keys(identities || {}).length;
@@ -645,7 +649,7 @@ export default class MetamaskController extends EventEmitter {
///: BEGIN:ONLY_INCLUDE_IN(flask)
this.snapExecutionService = new IframeExecutionService({
iframeUrl: new URL(
- 'https://metamask.github.io/iframe-execution-environment/0.5.0',
+ 'https://metamask.github.io/iframe-execution-environment/0.6.0',
),
messenger: this.controllerMessenger.getRestricted({
name: 'ExecutionService',
@@ -657,7 +661,8 @@ export default class MetamaskController extends EventEmitter {
name: 'SnapController',
allowedEvents: [
'ExecutionService:unhandledError',
- 'ExecutionService:unresponsive',
+ 'ExecutionService:outboundRequest',
+ 'ExecutionService:outboundResponse',
],
allowedActions: [
`${this.permissionController.name}:getEndowments`,
@@ -666,32 +671,59 @@ export default class MetamaskController extends EventEmitter {
`${this.permissionController.name}:hasPermissions`,
`${this.permissionController.name}:requestPermissions`,
`${this.permissionController.name}:revokeAllPermissions`,
+ `${this.permissionController.name}:revokePermissions`,
`${this.permissionController.name}:revokePermissionForAllSubjects`,
+ `${this.approvalController.name}:addRequest`,
+ `${this.permissionController.name}:grantPermissions`,
+ 'ExecutionService:executeSnap',
+ 'ExecutionService:getRpcRequestHandler',
+ 'ExecutionService:terminateSnap',
+ 'ExecutionService:terminateAllSnaps',
+ 'ExecutionService:handleRpcRequest',
],
});
+ const SNAP_BLOCKLIST = [
+ {
+ id: 'npm:@consensys/starknet-snap',
+ versionRange: '<0.1.11',
+ },
+ ];
+
this.snapController = new SnapController({
environmentEndowmentPermissions: Object.values(EndowmentPermissions),
- terminateAllSnaps: this.snapExecutionService.terminateAllSnaps.bind(
- this.snapExecutionService,
- ),
- terminateSnap: this.snapExecutionService.terminateSnap.bind(
- this.snapExecutionService,
- ),
- executeSnap: this.snapExecutionService.executeSnap.bind(
- this.snapExecutionService,
- ),
- getRpcMessageHandler: this.snapExecutionService.getRpcMessageHandler.bind(
- this.snapExecutionService,
- ),
closeAllConnections: this.removeAllConnections.bind(this),
// Prefix subject with appKeyType to generate separate keys for separate uses
getAppKey: async (subject, appKeyType) => {
await this.appStateController.getUnlockPromise(true);
return this.getAppKeyForSubject(`${appKeyType}:${subject}`);
},
+ checkBlockList: async (snapsToCheck) => {
+ return Object.entries(snapsToCheck).reduce(
+ (acc, [snapId, snapVersion]) => {
+ const blockInfo = SNAP_BLOCKLIST.find(
+ (blocked) =>
+ blocked.id === snapId &&
+ satisfiesSemver(snapVersion, blocked.versionRange, {
+ includePrerelease: true,
+ }),
+ );
+
+ const cur = blockInfo
+ ? {
+ blocked: true,
+ reason: blockInfo.reason,
+ infoUrl: blockInfo.infoUrl,
+ }
+ : { blocked: false };
+ return { ...acc, [snapId]: cur };
+ },
+ {},
+ );
+ },
state: initState.SnapController,
messenger: snapControllerMessenger,
+ featureFlags: { dappsCanUpdateSnaps: true },
});
this.notificationController = new NotificationController({
@@ -731,26 +763,17 @@ export default class MetamaskController extends EventEmitter {
},
});
///: END:ONLY_INCLUDE_IN
-
- process.env.TOKEN_DETECTION_V2
- ? (this.detectTokensController = new DetectTokensController({
- preferences: this.preferencesController,
- tokensController: this.tokensController,
- assetsContractController: this.assetsContractController,
- network: this.networkController,
- keyringMemStore: this.keyringController.memStore,
- tokenList: this.tokenListController,
- trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
- this.metaMetricsController,
- ),
- }))
- : (this.detectTokensController = new DetectTokensController({
- preferences: this.preferencesController,
- tokensController: this.tokensController,
- network: this.networkController,
- keyringMemStore: this.keyringController.memStore,
- tokenList: this.tokenListController,
- }));
+ this.detectTokensController = new DetectTokensController({
+ preferences: this.preferencesController,
+ tokensController: this.tokensController,
+ assetsContractController: this.assetsContractController,
+ network: this.networkController,
+ keyringMemStore: this.keyringController.memStore,
+ tokenList: this.tokenListController,
+ trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
+ this.metaMetricsController,
+ ),
+ });
this.addressBookController = new AddressBookController(
undefined,
@@ -776,6 +799,14 @@ export default class MetamaskController extends EventEmitter {
),
});
+ this.backupController = new BackupController({
+ preferencesController: this.preferencesController,
+ addressBookController: this.addressBookController,
+ trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
+ this.metaMetricsController,
+ ),
+ });
+
this.txController = new TransactionController({
initState:
initState.TransactionController || initState.TransactionManager,
@@ -783,12 +814,12 @@ export default class MetamaskController extends EventEmitter {
getProviderConfig: this.networkController.getProviderConfig.bind(
this.networkController,
),
- getCurrentNetworkEIP1559Compatibility: this.networkController.getEIP1559Compatibility.bind(
- this.networkController,
- ),
- getCurrentAccountEIP1559Compatibility: this.getCurrentAccountEIP1559Compatibility.bind(
- this,
- ),
+ getCurrentNetworkEIP1559Compatibility:
+ this.networkController.getEIP1559Compatibility.bind(
+ this.networkController,
+ ),
+ getCurrentAccountEIP1559Compatibility:
+ this.getCurrentAccountEIP1559Compatibility.bind(this),
networkStore: this.networkController.networkStore,
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
@@ -806,28 +837,29 @@ export default class MetamaskController extends EventEmitter {
updateEventFragment: this.metaMetricsController.updateEventFragment.bind(
this.metaMetricsController,
),
- finalizeEventFragment: this.metaMetricsController.finalizeEventFragment.bind(
- this.metaMetricsController,
- ),
- getEventFragmentById: this.metaMetricsController.getEventFragmentById.bind(
- this.metaMetricsController,
- ),
+ finalizeEventFragment:
+ this.metaMetricsController.finalizeEventFragment.bind(
+ this.metaMetricsController,
+ ),
+ getEventFragmentById:
+ this.metaMetricsController.getEventFragmentById.bind(
+ this.metaMetricsController,
+ ),
trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
this.metaMetricsController,
),
getParticipateInMetrics: () =>
this.metaMetricsController.state.participateInMetaMetrics,
- getEIP1559GasFeeEstimates: this.gasFeeController.fetchGasFeeEstimates.bind(
- this.gasFeeController,
- ),
- getExternalPendingTransactions: this.getExternalPendingTransactions.bind(
- this,
- ),
+ getEIP1559GasFeeEstimates:
+ this.gasFeeController.fetchGasFeeEstimates.bind(this.gasFeeController),
+ getExternalPendingTransactions:
+ this.getExternalPendingTransactions.bind(this),
getAccountType: this.getAccountType.bind(this),
getDeviceModel: this.getDeviceModel.bind(this),
- getTokenStandardAndDetails: this.assetsContractController.getTokenStandardAndDetails.bind(
- this.assetsContractController,
- ),
+ getTokenStandardAndDetails:
+ this.assetsContractController.getTokenStandardAndDetails.bind(
+ this.assetsContractController,
+ ),
});
this.txController.on('newUnapprovedTx', () => opts.showUserConfirmation());
@@ -837,7 +869,8 @@ export default class MetamaskController extends EventEmitter {
status === TRANSACTION_STATUSES.FAILED
) {
const txMeta = this.txController.txStateManager.getTransaction(txId);
- const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail();
+ const frequentRpcListDetail =
+ this.preferencesController.getFrequentRpcListDetail();
let rpcPrefs = {};
if (txMeta.chainId) {
const rpcSettings = frequentRpcListDetail.find(
@@ -964,9 +997,8 @@ export default class MetamaskController extends EventEmitter {
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
),
- getEIP1559GasFeeEstimates: this.gasFeeController.fetchGasFeeEstimates.bind(
- this.gasFeeController,
- ),
+ getEIP1559GasFeeEstimates:
+ this.gasFeeController.fetchGasFeeEstimates.bind(this.gasFeeController),
});
this.smartTransactionsController = new SmartTransactionsController(
{
@@ -979,9 +1011,8 @@ export default class MetamaskController extends EventEmitter {
getNonceLock: this.txController.nonceTracker.getNonceLock.bind(
this.txController.nonceTracker,
),
- confirmExternalTransaction: this.txController.confirmExternalTransaction.bind(
- this.txController,
- ),
+ confirmExternalTransaction:
+ this.txController.confirmExternalTransaction.bind(this.txController),
provider: this.provider,
trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
this.metaMetricsController,
@@ -1026,6 +1057,7 @@ export default class MetamaskController extends EventEmitter {
PermissionLogController: this.permissionLogController.store,
SubjectMetadataController: this.subjectMetadataController,
ThreeBoxController: this.threeBoxController.store,
+ BackupController: this.backupController,
AnnouncementController: this.announcementController,
GasFeeController: this.gasFeeController,
TokenListController: this.tokenListController,
@@ -1058,12 +1090,13 @@ export default class MetamaskController extends EventEmitter {
CurrencyController: this.currencyRateController,
AlertController: this.alertController.store,
OnboardingController: this.onboardingController.store,
- IncomingTransactionsController: this.incomingTransactionsController
- .store,
+ IncomingTransactionsController:
+ this.incomingTransactionsController.store,
PermissionController: this.permissionController,
PermissionLogController: this.permissionLogController.store,
SubjectMetadataController: this.subjectMetadataController,
ThreeBoxController: this.threeBoxController.store,
+ BackupController: this.backupController,
SwapsController: this.swapsController.store,
EnsController: this.ensController.store,
ApprovalController: this.approvalController,
@@ -1104,6 +1137,8 @@ export default class MetamaskController extends EventEmitter {
this.setupControllerEventSubscriptions();
+ // For more information about these legacy streams, see here:
+ // https://github.com/MetaMask/metamask-extension/issues/15491
// TODO:LegacyProvider: Delete
this.publicConfigStore = this.createPublicConfigStore();
@@ -1137,9 +1172,9 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger,
'SnapController:get',
),
- getSnapRpcHandler: this.controllerMessenger.call.bind(
+ handleSnapRpcRequest: this.controllerMessenger.call.bind(
this.controllerMessenger,
- 'SnapController:getRpcMessageHandler',
+ 'SnapController:handleRpcRequest',
),
getSnapState: this.controllerMessenger.call.bind(
this.controllerMessenger,
@@ -1263,7 +1298,7 @@ export default class MetamaskController extends EventEmitter {
// Record Snap metadata whenever a Snap is added to state.
this.controllerMessenger.subscribe(
`${this.snapController.name}:snapAdded`,
- (snapId, snap, svgIcon = null) => {
+ (snap, svgIcon = null) => {
const {
manifest: { proposedName },
version,
@@ -1271,7 +1306,7 @@ export default class MetamaskController extends EventEmitter {
this.subjectMetadataController.addSubjectMetadata({
subjectType: SUBJECT_TYPES.SNAP,
name: proposedName,
- origin: snapId,
+ origin: snap.id,
version,
svgIcon,
});
@@ -1280,12 +1315,28 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger.subscribe(
`${this.snapController.name}:snapInstalled`,
- (snapId) => {
+ (truncatedSnap) => {
this.metaMetricsController.trackEvent({
event: 'Snap Installed',
category: EVENT.CATEGORIES.SNAPS,
properties: {
- snap_id: snapId,
+ snap_id: truncatedSnap.id,
+ version: truncatedSnap.version,
+ },
+ });
+ },
+ );
+
+ this.controllerMessenger.subscribe(
+ `${this.snapController.name}:snapUpdated`,
+ (newSnap, oldVersion) => {
+ this.metaMetricsController.trackEvent({
+ event: 'Snap Updated',
+ category: EVENT.CATEGORIES.SNAPS,
+ properties: {
+ snap_id: newSnap.id,
+ old_version: oldVersion,
+ new_version: newSnap.version,
},
});
},
@@ -1293,12 +1344,12 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger.subscribe(
`${this.snapController.name}:snapTerminated`,
- (snapId) => {
+ (truncatedSnap) => {
const approvals = Object.values(
this.approvalController.state.pendingApprovals,
).filter(
(approval) =>
- approval.origin === snapId &&
+ approval.origin === truncatedSnap.id &&
approval.type === MESSAGE_TYPE.SNAP_CONFIRM,
);
for (const approval of approvals) {
@@ -1330,7 +1381,8 @@ export default class MetamaskController extends EventEmitter {
{ suppressUnauthorizedError = true } = {},
) => {
if (origin === ORIGIN_METAMASK) {
- const selectedAddress = this.preferencesController.getSelectedAddress();
+ const selectedAddress =
+ this.preferencesController.getSelectedAddress();
return selectedAddress ? [selectedAddress] : [];
} else if (this.isUnlocked()) {
return await this.getPermittedAccounts(origin, {
@@ -1358,9 +1410,8 @@ export default class MetamaskController extends EventEmitter {
},
})[0],
};
- const providerProxy = this.networkController.initializeProvider(
- providerOpts,
- );
+ const providerProxy =
+ this.networkController.initializeProvider(providerOpts);
return providerProxy;
}
@@ -1482,6 +1533,7 @@ export default class MetamaskController extends EventEmitter {
smartTransactionsController,
txController,
assetsContractController,
+ backupController,
} = this;
return {
@@ -1502,18 +1554,20 @@ export default class MetamaskController extends EventEmitter {
setUseTokenDetection: preferencesController.setUseTokenDetection.bind(
preferencesController,
),
- setUseCollectibleDetection: preferencesController.setUseCollectibleDetection.bind(
- preferencesController,
- ),
+ setUseCollectibleDetection:
+ preferencesController.setUseCollectibleDetection.bind(
+ preferencesController,
+ ),
setOpenSeaEnabled: preferencesController.setOpenSeaEnabled.bind(
preferencesController,
),
setIpfsGateway: preferencesController.setIpfsGateway.bind(
preferencesController,
),
- setParticipateInMetaMetrics: metaMetricsController.setParticipateInMetaMetrics.bind(
- metaMetricsController,
- ),
+ setParticipateInMetaMetrics:
+ metaMetricsController.setParticipateInMetaMetrics.bind(
+ metaMetricsController,
+ ),
setCurrentLocale: preferencesController.setCurrentLocale.bind(
preferencesController,
),
@@ -1536,32 +1590,24 @@ export default class MetamaskController extends EventEmitter {
forgetDevice: this.forgetDevice.bind(this),
checkHardwareStatus: this.checkHardwareStatus.bind(this),
unlockHardwareWalletAccount: this.unlockHardwareWalletAccount.bind(this),
- setLedgerTransportPreference: this.setLedgerTransportPreference.bind(
- this,
- ),
- attemptLedgerTransportCreation: this.attemptLedgerTransportCreation.bind(
- this,
- ),
- establishLedgerTransportPreference: this.establishLedgerTransportPreference.bind(
- this,
- ),
+ setLedgerTransportPreference:
+ this.setLedgerTransportPreference.bind(this),
+ attemptLedgerTransportCreation:
+ this.attemptLedgerTransportCreation.bind(this),
+ establishLedgerTransportPreference:
+ this.establishLedgerTransportPreference.bind(this),
// qr hardware devices
- submitQRHardwareCryptoHDKey: qrHardwareKeyring.submitCryptoHDKey.bind(
- qrHardwareKeyring,
- ),
- submitQRHardwareCryptoAccount: qrHardwareKeyring.submitCryptoAccount.bind(
- qrHardwareKeyring,
- ),
- cancelSyncQRHardware: qrHardwareKeyring.cancelSync.bind(
- qrHardwareKeyring,
- ),
- submitQRHardwareSignature: qrHardwareKeyring.submitSignature.bind(
- qrHardwareKeyring,
- ),
- cancelQRHardwareSignRequest: qrHardwareKeyring.cancelSignRequest.bind(
- qrHardwareKeyring,
- ),
+ submitQRHardwareCryptoHDKey:
+ qrHardwareKeyring.submitCryptoHDKey.bind(qrHardwareKeyring),
+ submitQRHardwareCryptoAccount:
+ qrHardwareKeyring.submitCryptoAccount.bind(qrHardwareKeyring),
+ cancelSyncQRHardware:
+ qrHardwareKeyring.cancelSync.bind(qrHardwareKeyring),
+ submitQRHardwareSignature:
+ qrHardwareKeyring.submitSignature.bind(qrHardwareKeyring),
+ cancelQRHardwareSignRequest:
+ qrHardwareKeyring.cancelSignRequest.bind(qrHardwareKeyring),
// mobile
fetchInfoToSync: this.fetchInfoToSync.bind(this),
@@ -1571,30 +1617,25 @@ export default class MetamaskController extends EventEmitter {
verifyPassword: this.verifyPassword.bind(this),
// network management
- setProviderType: networkController.setProviderType.bind(
- networkController,
- ),
- rollbackToPreviousProvider: networkController.rollbackToPreviousProvider.bind(
- networkController,
- ),
+ setProviderType:
+ networkController.setProviderType.bind(networkController),
+ rollbackToPreviousProvider:
+ networkController.rollbackToPreviousProvider.bind(networkController),
setCustomRpc: this.setCustomRpc.bind(this),
updateAndSetCustomRpc: this.updateAndSetCustomRpc.bind(this),
delCustomRpc: this.delCustomRpc.bind(this),
addCustomNetwork: this.addCustomNetwork.bind(this),
- requestUserApproval: this.requestUserApproval.bind(this),
+ requestAddNetworkApproval: this.requestAddNetworkApproval.bind(this),
// PreferencesController
setSelectedAddress: preferencesController.setSelectedAddress.bind(
preferencesController,
),
addToken: tokensController.addToken.bind(tokensController),
- rejectWatchAsset: tokensController.rejectWatchAsset.bind(
- tokensController,
- ),
- acceptWatchAsset: tokensController.acceptWatchAsset.bind(
- tokensController,
- ),
+ rejectWatchAsset:
+ tokensController.rejectWatchAsset.bind(tokensController),
+ acceptWatchAsset:
+ tokensController.acceptWatchAsset.bind(tokensController),
updateTokenType: tokensController.updateTokenType.bind(tokensController),
- removeToken: tokensController.removeAndIgnoreToken.bind(tokensController),
setAccountLabel: preferencesController.setAccountLabel.bind(
preferencesController,
),
@@ -1608,9 +1649,10 @@ export default class MetamaskController extends EventEmitter {
addKnownMethodData: preferencesController.addKnownMethodData.bind(
preferencesController,
),
- setDismissSeedBackUpReminder: preferencesController.setDismissSeedBackUpReminder.bind(
- preferencesController,
- ),
+ setDismissSeedBackUpReminder:
+ preferencesController.setDismissSeedBackUpReminder.bind(
+ preferencesController,
+ ),
setAdvancedGasFee: preferencesController.setAdvancedGasFee.bind(
preferencesController,
),
@@ -1618,9 +1660,10 @@ export default class MetamaskController extends EventEmitter {
preferencesController,
),
setTheme: preferencesController.setTheme.bind(preferencesController),
- setCustomNetworkListEnabled: preferencesController.setCustomNetworkListEnabled.bind(
- preferencesController,
- ),
+ setCustomNetworkListEnabled:
+ preferencesController.setCustomNetworkListEnabled.bind(
+ preferencesController,
+ ),
// AssetsContractController
getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this),
@@ -1629,25 +1672,29 @@ export default class MetamaskController extends EventEmitter {
collectiblesController,
),
- addCollectibleVerifyOwnership: collectiblesController.addCollectibleVerifyOwnership.bind(
- collectiblesController,
- ),
+ addCollectibleVerifyOwnership:
+ collectiblesController.addCollectibleVerifyOwnership.bind(
+ collectiblesController,
+ ),
- removeAndIgnoreCollectible: collectiblesController.removeAndIgnoreCollectible.bind(
- collectiblesController,
- ),
+ removeAndIgnoreCollectible:
+ collectiblesController.removeAndIgnoreCollectible.bind(
+ collectiblesController,
+ ),
removeCollectible: collectiblesController.removeCollectible.bind(
collectiblesController,
),
- checkAndUpdateAllCollectiblesOwnershipStatus: collectiblesController.checkAndUpdateAllCollectiblesOwnershipStatus.bind(
- collectiblesController,
- ),
+ checkAndUpdateAllCollectiblesOwnershipStatus:
+ collectiblesController.checkAndUpdateAllCollectiblesOwnershipStatus.bind(
+ collectiblesController,
+ ),
- checkAndUpdateSingleCollectibleOwnershipStatus: collectiblesController.checkAndUpdateSingleCollectibleOwnershipStatus.bind(
- collectiblesController,
- ),
+ checkAndUpdateSingleCollectibleOwnershipStatus:
+ collectiblesController.checkAndUpdateSingleCollectibleOwnershipStatus.bind(
+ collectiblesController,
+ ),
isCollectibleOwner: collectiblesController.isCollectibleOwner.bind(
collectiblesController,
@@ -1660,37 +1707,43 @@ export default class MetamaskController extends EventEmitter {
),
// AppStateController
- setLastActiveTime: appStateController.setLastActiveTime.bind(
- appStateController,
- ),
- setDefaultHomeActiveTabName: appStateController.setDefaultHomeActiveTabName.bind(
- appStateController,
- ),
- setConnectedStatusPopoverHasBeenShown: appStateController.setConnectedStatusPopoverHasBeenShown.bind(
- appStateController,
- ),
- setRecoveryPhraseReminderHasBeenShown: appStateController.setRecoveryPhraseReminderHasBeenShown.bind(
- appStateController,
- ),
- setRecoveryPhraseReminderLastShown: appStateController.setRecoveryPhraseReminderLastShown.bind(
- appStateController,
- ),
- setShowTestnetMessageInDropdown: appStateController.setShowTestnetMessageInDropdown.bind(
- appStateController,
- ),
- setCollectiblesDetectionNoticeDismissed: appStateController.setCollectiblesDetectionNoticeDismissed.bind(
- appStateController,
- ),
- setEnableEIP1559V2NoticeDismissed: appStateController.setEnableEIP1559V2NoticeDismissed.bind(
- appStateController,
- ),
- updateCollectibleDropDownState: appStateController.updateCollectibleDropDownState.bind(
- appStateController,
- ),
+ setLastActiveTime:
+ appStateController.setLastActiveTime.bind(appStateController),
+ setDefaultHomeActiveTabName:
+ appStateController.setDefaultHomeActiveTabName.bind(appStateController),
+ setConnectedStatusPopoverHasBeenShown:
+ appStateController.setConnectedStatusPopoverHasBeenShown.bind(
+ appStateController,
+ ),
+ setRecoveryPhraseReminderHasBeenShown:
+ appStateController.setRecoveryPhraseReminderHasBeenShown.bind(
+ appStateController,
+ ),
+ setRecoveryPhraseReminderLastShown:
+ appStateController.setRecoveryPhraseReminderLastShown.bind(
+ appStateController,
+ ),
+ setShowTestnetMessageInDropdown:
+ appStateController.setShowTestnetMessageInDropdown.bind(
+ appStateController,
+ ),
+ setCollectiblesDetectionNoticeDismissed:
+ appStateController.setCollectiblesDetectionNoticeDismissed.bind(
+ appStateController,
+ ),
+ setEnableEIP1559V2NoticeDismissed:
+ appStateController.setEnableEIP1559V2NoticeDismissed.bind(
+ appStateController,
+ ),
+ updateCollectibleDropDownState:
+ appStateController.updateCollectibleDropDownState.bind(
+ appStateController,
+ ),
+ setFirstTimeUsedNetwork:
+ appStateController.setFirstTimeUsedNetwork.bind(appStateController),
// EnsController
- tryReverseResolveAddress: ensController.reverseResolveAddress.bind(
- ensController,
- ),
+ tryReverseResolveAddress:
+ ensController.reverseResolveAddress.bind(ensController),
// KeyringController
setLocked: this.setLocked.bind(this),
@@ -1701,44 +1754,34 @@ export default class MetamaskController extends EventEmitter {
// txController
cancelTransaction: txController.cancelTransaction.bind(txController),
updateTransaction: txController.updateTransaction.bind(txController),
- updateAndApproveTransaction: txController.updateAndApproveTransaction.bind(
- txController,
- ),
- approveTransactionsWithSameNonce: txController.approveTransactionsWithSameNonce.bind(
- txController,
- ),
+ updateAndApproveTransaction:
+ txController.updateAndApproveTransaction.bind(txController),
+ approveTransactionsWithSameNonce:
+ txController.approveTransactionsWithSameNonce.bind(txController),
createCancelTransaction: this.createCancelTransaction.bind(this),
createSpeedUpTransaction: this.createSpeedUpTransaction.bind(this),
estimateGas: this.estimateGas.bind(this),
getNextNonce: this.getNextNonce.bind(this),
- addUnapprovedTransaction: txController.addUnapprovedTransaction.bind(
- txController,
- ),
- createTransactionEventFragment: txController.createTransactionEventFragment.bind(
- txController,
- ),
+ addUnapprovedTransaction:
+ txController.addUnapprovedTransaction.bind(txController),
+ createTransactionEventFragment:
+ txController.createTransactionEventFragment.bind(txController),
getTransactions: txController.getTransactions.bind(txController),
- updateEditableParams: txController.updateEditableParams.bind(
- txController,
- ),
- updateTransactionGasFees: txController.updateTransactionGasFees.bind(
- txController,
- ),
- updateTransactionSendFlowHistory: txController.updateTransactionSendFlowHistory.bind(
- txController,
- ),
+ updateEditableParams:
+ txController.updateEditableParams.bind(txController),
+ updateTransactionGasFees:
+ txController.updateTransactionGasFees.bind(txController),
+ updateTransactionSendFlowHistory:
+ txController.updateTransactionSendFlowHistory.bind(txController),
- updateSwapApprovalTransaction: txController.updateSwapApprovalTransaction.bind(
- txController,
- ),
- updateSwapTransaction: txController.updateSwapTransaction.bind(
- txController,
- ),
+ updateSwapApprovalTransaction:
+ txController.updateSwapApprovalTransaction.bind(txController),
+ updateSwapTransaction:
+ txController.updateSwapTransaction.bind(txController),
- updatePreviousGasParams: txController.updatePreviousGasParams.bind(
- txController,
- ),
+ updatePreviousGasParams:
+ txController.updatePreviousGasParams.bind(txController),
// messageManager
signMessage: this.signMessage.bind(this),
cancelMessage: this.cancelMessage.bind(this),
@@ -1761,55 +1804,47 @@ export default class MetamaskController extends EventEmitter {
cancelEncryptionPublicKey: this.cancelEncryptionPublicKey.bind(this),
// onboarding controller
- setSeedPhraseBackedUp: onboardingController.setSeedPhraseBackedUp.bind(
- onboardingController,
- ),
- completeOnboarding: onboardingController.completeOnboarding.bind(
- onboardingController,
- ),
- setFirstTimeFlowType: onboardingController.setFirstTimeFlowType.bind(
- onboardingController,
- ),
+ setSeedPhraseBackedUp:
+ onboardingController.setSeedPhraseBackedUp.bind(onboardingController),
+ completeOnboarding:
+ onboardingController.completeOnboarding.bind(onboardingController),
+ setFirstTimeFlowType:
+ onboardingController.setFirstTimeFlowType.bind(onboardingController),
// alert controller
- setAlertEnabledness: alertController.setAlertEnabledness.bind(
- alertController,
- ),
- setUnconnectedAccountAlertShown: alertController.setUnconnectedAccountAlertShown.bind(
- alertController,
- ),
- setWeb3ShimUsageAlertDismissed: alertController.setWeb3ShimUsageAlertDismissed.bind(
- alertController,
- ),
+ setAlertEnabledness:
+ alertController.setAlertEnabledness.bind(alertController),
+ setUnconnectedAccountAlertShown:
+ alertController.setUnconnectedAccountAlertShown.bind(alertController),
+ setWeb3ShimUsageAlertDismissed:
+ alertController.setWeb3ShimUsageAlertDismissed.bind(alertController),
// 3Box
- setThreeBoxSyncingPermission: threeBoxController.setThreeBoxSyncingPermission.bind(
- threeBoxController,
- ),
- restoreFromThreeBox: threeBoxController.restoreFromThreeBox.bind(
- threeBoxController,
- ),
- setShowRestorePromptToFalse: threeBoxController.setShowRestorePromptToFalse.bind(
- threeBoxController,
- ),
- getThreeBoxLastUpdated: threeBoxController.getLastUpdated.bind(
- threeBoxController,
- ),
- turnThreeBoxSyncingOn: threeBoxController.turnThreeBoxSyncingOn.bind(
- threeBoxController,
- ),
+ setThreeBoxSyncingPermission:
+ threeBoxController.setThreeBoxSyncingPermission.bind(
+ threeBoxController,
+ ),
+ restoreFromThreeBox:
+ threeBoxController.restoreFromThreeBox.bind(threeBoxController),
+ setShowRestorePromptToFalse:
+ threeBoxController.setShowRestorePromptToFalse.bind(threeBoxController),
+ getThreeBoxLastUpdated:
+ threeBoxController.getLastUpdated.bind(threeBoxController),
+ turnThreeBoxSyncingOn:
+ threeBoxController.turnThreeBoxSyncingOn.bind(threeBoxController),
initializeThreeBox: this.initializeThreeBox.bind(this),
// permissions
- removePermissionsFor: permissionController.revokePermissions.bind(
- permissionController,
- ),
- approvePermissionsRequest: permissionController.acceptPermissionsRequest.bind(
- permissionController,
- ),
- rejectPermissionsRequest: permissionController.rejectPermissionsRequest.bind(
- permissionController,
- ),
+ removePermissionsFor:
+ permissionController.revokePermissions.bind(permissionController),
+ approvePermissionsRequest:
+ permissionController.acceptPermissionsRequest.bind(
+ permissionController,
+ ),
+ rejectPermissionsRequest:
+ permissionController.rejectPermissionsRequest.bind(
+ permissionController,
+ ),
...getPermissionBackgroundApiMethods(permissionController),
///: BEGIN:ONLY_INCLUDE_IN(flask)
@@ -1825,81 +1860,75 @@ export default class MetamaskController extends EventEmitter {
///: END:ONLY_INCLUDE_IN
// swaps
- fetchAndSetQuotes: swapsController.fetchAndSetQuotes.bind(
- swapsController,
- ),
- setSelectedQuoteAggId: swapsController.setSelectedQuoteAggId.bind(
- swapsController,
- ),
+ fetchAndSetQuotes:
+ swapsController.fetchAndSetQuotes.bind(swapsController),
+ setSelectedQuoteAggId:
+ swapsController.setSelectedQuoteAggId.bind(swapsController),
resetSwapsState: swapsController.resetSwapsState.bind(swapsController),
setSwapsTokens: swapsController.setSwapsTokens.bind(swapsController),
clearSwapsQuotes: swapsController.clearSwapsQuotes.bind(swapsController),
setApproveTxId: swapsController.setApproveTxId.bind(swapsController),
setTradeTxId: swapsController.setTradeTxId.bind(swapsController),
- setSwapsTxGasPrice: swapsController.setSwapsTxGasPrice.bind(
- swapsController,
- ),
- setSwapsTxGasLimit: swapsController.setSwapsTxGasLimit.bind(
- swapsController,
- ),
- setSwapsTxMaxFeePerGas: swapsController.setSwapsTxMaxFeePerGas.bind(
- swapsController,
- ),
- setSwapsTxMaxFeePriorityPerGas: swapsController.setSwapsTxMaxFeePriorityPerGas.bind(
- swapsController,
- ),
- safeRefetchQuotes: swapsController.safeRefetchQuotes.bind(
- swapsController,
- ),
- stopPollingForQuotes: swapsController.stopPollingForQuotes.bind(
- swapsController,
- ),
- setBackgroundSwapRouteState: swapsController.setBackgroundSwapRouteState.bind(
- swapsController,
- ),
- resetPostFetchState: swapsController.resetPostFetchState.bind(
- swapsController,
- ),
+ setSwapsTxGasPrice:
+ swapsController.setSwapsTxGasPrice.bind(swapsController),
+ setSwapsTxGasLimit:
+ swapsController.setSwapsTxGasLimit.bind(swapsController),
+ setSwapsTxMaxFeePerGas:
+ swapsController.setSwapsTxMaxFeePerGas.bind(swapsController),
+ setSwapsTxMaxFeePriorityPerGas:
+ swapsController.setSwapsTxMaxFeePriorityPerGas.bind(swapsController),
+ safeRefetchQuotes:
+ swapsController.safeRefetchQuotes.bind(swapsController),
+ stopPollingForQuotes:
+ swapsController.stopPollingForQuotes.bind(swapsController),
+ setBackgroundSwapRouteState:
+ swapsController.setBackgroundSwapRouteState.bind(swapsController),
+ resetPostFetchState:
+ swapsController.resetPostFetchState.bind(swapsController),
setSwapsErrorKey: swapsController.setSwapsErrorKey.bind(swapsController),
- setInitialGasEstimate: swapsController.setInitialGasEstimate.bind(
- swapsController,
- ),
- setCustomApproveTxData: swapsController.setCustomApproveTxData.bind(
- swapsController,
- ),
+ setInitialGasEstimate:
+ swapsController.setInitialGasEstimate.bind(swapsController),
+ setCustomApproveTxData:
+ swapsController.setCustomApproveTxData.bind(swapsController),
setSwapsLiveness: swapsController.setSwapsLiveness.bind(swapsController),
- setSwapsFeatureFlags: swapsController.setSwapsFeatureFlags.bind(
- swapsController,
- ),
- setSwapsUserFeeLevel: swapsController.setSwapsUserFeeLevel.bind(
- swapsController,
- ),
- setSwapsQuotesPollingLimitEnabled: swapsController.setSwapsQuotesPollingLimitEnabled.bind(
- swapsController,
- ),
+ setSwapsFeatureFlags:
+ swapsController.setSwapsFeatureFlags.bind(swapsController),
+ setSwapsUserFeeLevel:
+ swapsController.setSwapsUserFeeLevel.bind(swapsController),
+ setSwapsQuotesPollingLimitEnabled:
+ swapsController.setSwapsQuotesPollingLimitEnabled.bind(swapsController),
// Smart Transactions
- setSmartTransactionsOptInStatus: smartTransactionsController.setOptInState.bind(
- smartTransactionsController,
- ),
+ setSmartTransactionsOptInStatus:
+ smartTransactionsController.setOptInState.bind(
+ smartTransactionsController,
+ ),
fetchSmartTransactionFees: smartTransactionsController.getFees.bind(
smartTransactionsController,
),
- submitSignedTransactions: smartTransactionsController.submitSignedTransactions.bind(
- smartTransactionsController,
- ),
- cancelSmartTransaction: smartTransactionsController.cancelSmartTransaction.bind(
- smartTransactionsController,
- ),
- fetchSmartTransactionsLiveness: smartTransactionsController.fetchLiveness.bind(
- smartTransactionsController,
- ),
- updateSmartTransaction: smartTransactionsController.updateSmartTransaction.bind(
- smartTransactionsController,
- ),
- setStatusRefreshInterval: smartTransactionsController.setStatusRefreshInterval.bind(
+ clearSmartTransactionFees: smartTransactionsController.clearFees.bind(
smartTransactionsController,
),
+ submitSignedTransactions:
+ smartTransactionsController.submitSignedTransactions.bind(
+ smartTransactionsController,
+ ),
+ cancelSmartTransaction:
+ smartTransactionsController.cancelSmartTransaction.bind(
+ smartTransactionsController,
+ ),
+ fetchSmartTransactionsLiveness:
+ smartTransactionsController.fetchLiveness.bind(
+ smartTransactionsController,
+ ),
+ updateSmartTransaction:
+ smartTransactionsController.updateSmartTransaction.bind(
+ smartTransactionsController,
+ ),
+ setStatusRefreshInterval:
+ smartTransactionsController.setStatusRefreshInterval.bind(
+ smartTransactionsController,
+ ),
// MetaMetrics
trackMetaMetricsEvent: metaMetricsController.trackEvent.bind(
@@ -1919,9 +1948,8 @@ export default class MetamaskController extends EventEmitter {
),
// approval controller
- resolvePendingApproval: approvalController.accept.bind(
- approvalController,
- ),
+ resolvePendingApproval:
+ approvalController.accept.bind(approvalController),
rejectPendingApproval: async (id, error) => {
approvalController.reject(
id,
@@ -1935,25 +1963,26 @@ export default class MetamaskController extends EventEmitter {
),
// GasFeeController
- getGasFeeEstimatesAndStartPolling: gasFeeController.getGasFeeEstimatesAndStartPolling.bind(
- gasFeeController,
- ),
+ getGasFeeEstimatesAndStartPolling:
+ gasFeeController.getGasFeeEstimatesAndStartPolling.bind(
+ gasFeeController,
+ ),
- disconnectGasFeeEstimatePoller: gasFeeController.disconnectPoller.bind(
- gasFeeController,
- ),
+ disconnectGasFeeEstimatePoller:
+ gasFeeController.disconnectPoller.bind(gasFeeController),
- getGasFeeTimeEstimate: gasFeeController.getTimeEstimate.bind(
- gasFeeController,
- ),
+ getGasFeeTimeEstimate:
+ gasFeeController.getTimeEstimate.bind(gasFeeController),
- addPollingTokenToAppState: appStateController.addPollingToken.bind(
- appStateController,
- ),
+ addPollingTokenToAppState:
+ appStateController.addPollingToken.bind(appStateController),
- removePollingTokenFromAppState: appStateController.removePollingToken.bind(
- appStateController,
- ),
+ removePollingTokenFromAppState:
+ appStateController.removePollingToken.bind(appStateController),
+
+ // BackupController
+ backupUserData: backupController.backupUserData.bind(backupController),
+ restoreUserData: backupController.restoreUserData.bind(backupController),
// DetectTokenController
detectNewTokens: detectTokensController.detectNewTokens.bind(
@@ -1968,29 +1997,24 @@ export default class MetamaskController extends EventEmitter {
: null,
/** Token Detection V2 */
- addDetectedTokens: process.env.TOKEN_DETECTION_V2
- ? tokensController.addDetectedTokens.bind(tokensController)
- : null,
- importTokens: process.env.TOKEN_DETECTION_V2
- ? tokensController.importTokens.bind(tokensController)
- : null,
- ignoreTokens: process.env.TOKEN_DETECTION_V2
- ? tokensController.ignoreTokens.bind(tokensController)
- : null,
- getBalancesInSingleCall: process.env.TOKEN_DETECTION_V2
- ? assetsContractController.getBalancesInSingleCall.bind(
- assetsContractController,
- )
- : null,
+ addDetectedTokens:
+ tokensController.addDetectedTokens.bind(tokensController),
+ addImportedTokens: tokensController.addTokens.bind(tokensController),
+ ignoreTokens: tokensController.ignoreTokens.bind(tokensController),
+ getBalancesInSingleCall:
+ assetsContractController.getBalancesInSingleCall.bind(
+ assetsContractController,
+ ),
};
}
async getTokenStandardAndDetails(address, userAddress, tokenId) {
- const details = await this.assetsContractController.getTokenStandardAndDetails(
- address,
- userAddress,
- tokenId,
- );
+ const details =
+ await this.assetsContractController.getTokenStandardAndDetails(
+ address,
+ userAddress,
+ tokenId,
+ );
return {
...details,
decimals: details?.decimals?.toString(10),
@@ -2037,7 +2061,7 @@ export default class MetamaskController extends EventEmitter {
}
}
- async requestUserApproval(customRpc, originIsMetaMask) {
+ async requestAddNetworkApproval(customRpc, originIsMetaMask) {
try {
await this.approvalController.addAndShowApprovalRequest({
origin: 'metamask',
@@ -2072,6 +2096,31 @@ export default class MetamaskController extends EventEmitter {
blockExplorerUrl,
},
);
+
+ let rpcUrlOrigin;
+ try {
+ rpcUrlOrigin = new URL(rpcUrl).origin;
+ } catch {
+ // ignore
+ }
+ this.metaMetricsController.trackEvent({
+ event: 'Custom Network Added',
+ category: EVENT.CATEGORIES.NETWORK,
+ referrer: {
+ url: rpcUrlOrigin,
+ },
+ properties: {
+ chain_id: chainId,
+ network_name: chainName,
+ network: rpcUrlOrigin,
+ symbol: ticker,
+ block_explorer_url: blockExplorerUrl,
+ source: EVENT.SOURCE.NETWORK.POPULAR_NETWORK_LIST,
+ },
+ sensitiveProperties: {
+ rpc_url: rpcUrlOrigin,
+ },
+ });
}
/**
@@ -2118,9 +2167,8 @@ export default class MetamaskController extends EventEmitter {
ethQuery,
);
- const primaryKeyring = keyringController.getKeyringsByType(
- 'HD Key Tree',
- )[0];
+ const primaryKeyring =
+ keyringController.getKeyringsByType('HD Key Tree')[0];
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found');
}
@@ -2145,7 +2193,8 @@ export default class MetamaskController extends EventEmitter {
// keyring's iframe and have the setting initialized properly
// Optimistically called to not block MetaMask login due to
// Ledger Keyring GitHub downtime
- const transportPreference = this.preferencesController.getLedgerTransportPreference();
+ const transportPreference =
+ this.preferencesController.getLedgerTransportPreference();
this.setLedgerTransportPreference(transportPreference);
// set new identities
@@ -2198,7 +2247,14 @@ export default class MetamaskController extends EventEmitter {
useTokenDetection,
} = this.preferencesController.store.getState();
+ const isTokenDetectionInactiveInMainnet =
+ !useTokenDetection &&
+ this.networkController.store.getState().provider.chainId ===
+ MAINNET_CHAIN_ID;
const { tokenList } = this.tokenListController.state;
+ const caseInSensitiveTokenList = isTokenDetectionInactiveInMainnet
+ ? STATIC_MAINNET_TOKEN_LIST
+ : tokenList;
const preferences = {
currentLocale,
@@ -2221,13 +2277,11 @@ export default class MetamaskController extends EventEmitter {
checksummedAccountAddress
].filter((asset) => {
if (asset.isERC721 === undefined) {
- // since the token.address from allTokens is checksumaddress
- // asset.address have to be changed to lowercase when we are using dynamic list
- const address = useTokenDetection
- ? asset.address.toLowerCase()
- : asset.address;
// the tokenList will be holding only erc20 tokens
- if (tokenList[address] !== undefined) {
+ if (
+ caseInSensitiveTokenList[asset.address?.toLowerCase()] !==
+ undefined
+ ) {
return true;
}
} else if (asset.isERC721 === false) {
@@ -2239,12 +2293,10 @@ export default class MetamaskController extends EventEmitter {
});
// Accounts
- const hdKeyring = this.keyringController.getKeyringsByType(
- 'HD Key Tree',
- )[0];
- const simpleKeyPairKeyrings = this.keyringController.getKeyringsByType(
- 'Simple Key Pair',
- );
+ const hdKeyring =
+ this.keyringController.getKeyringsByType('HD Key Tree')[0];
+ const simpleKeyPairKeyrings =
+ this.keyringController.getKeyringsByType('Simple Key Pair');
const hdAccounts = await hdKeyring.getAccounts();
const simpleKeyPairKeyringAccounts = await Promise.all(
simpleKeyPairKeyrings.map((keyring) => keyring.getAccounts()),
@@ -2301,7 +2353,8 @@ export default class MetamaskController extends EventEmitter {
}
try {
- const threeBoxSyncingAllowed = this.threeBoxController.getThreeBoxSyncingState();
+ const threeBoxSyncingAllowed =
+ this.threeBoxController.getThreeBoxSyncingState();
if (threeBoxSyncingAllowed && !this.threeBoxController.box) {
// 'await' intentionally omitted to avoid waiting for initialization
this.threeBoxController.init();
@@ -2317,7 +2370,8 @@ export default class MetamaskController extends EventEmitter {
// keyring's iframe and have the setting initialized properly
// Optimistically called to not block MetaMask login due to
// Ledger Keyring GitHub downtime
- const transportPreference = this.preferencesController.getLedgerTransportPreference();
+ const transportPreference =
+ this.preferencesController.getLedgerTransportPreference();
this.setLedgerTransportPreference(transportPreference);
@@ -2413,7 +2467,8 @@ export default class MetamaskController extends EventEmitter {
}
async establishLedgerTransportPreference() {
- const transportPreference = this.preferencesController.getLedgerTransportPreference();
+ const transportPreference =
+ this.preferencesController.getLedgerTransportPreference();
return await this.setLedgerTransportPreference(transportPreference);
}
@@ -2582,31 +2637,41 @@ export default class MetamaskController extends EventEmitter {
/**
* Adds a new account to the default (first) HD seed phrase Keyring.
*
+ * @param accountCount
* @returns {} keyState
*/
- async addNewAccount() {
- const primaryKeyring = this.keyringController.getKeyringsByType(
- 'HD Key Tree',
- )[0];
+ async addNewAccount(accountCount) {
+ const primaryKeyring =
+ this.keyringController.getKeyringsByType('HD Key Tree')[0];
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found');
}
const { keyringController } = this;
- const oldAccounts = await keyringController.getAccounts();
- const keyState = await keyringController.addNewAccount(primaryKeyring);
- const newAccounts = await keyringController.getAccounts();
+ const { identities: oldIdentities } =
+ this.preferencesController.store.getState();
- await this.verifySeedPhrase();
+ if (Object.keys(oldIdentities).length === accountCount) {
+ const oldAccounts = await keyringController.getAccounts();
+ const keyState = await keyringController.addNewAccount(primaryKeyring);
+ const newAccounts = await keyringController.getAccounts();
- this.preferencesController.setAddresses(newAccounts);
- newAccounts.forEach((address) => {
- if (!oldAccounts.includes(address)) {
- this.preferencesController.setSelectedAddress(address);
- }
- });
+ await this.verifySeedPhrase();
- const { identities } = this.preferencesController.store.getState();
- return { ...keyState, identities };
+ this.preferencesController.setAddresses(newAccounts);
+ newAccounts.forEach((address) => {
+ if (!oldAccounts.includes(address)) {
+ this.preferencesController.setSelectedAddress(address);
+ }
+ });
+
+ const { identities } = this.preferencesController.store.getState();
+ return { ...keyState, identities };
+ }
+
+ return {
+ ...keyringController.memStore.getState(),
+ identities: oldIdentities,
+ };
}
/**
@@ -2620,9 +2685,8 @@ export default class MetamaskController extends EventEmitter {
* encoded as an array of UTF-8 bytes.
*/
async verifySeedPhrase() {
- const primaryKeyring = this.keyringController.getKeyringsByType(
- 'HD Key Tree',
- )[0];
+ const primaryKeyring =
+ this.keyringController.getKeyringsByType('HD Key Tree')[0];
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found');
}
@@ -3057,10 +3121,11 @@ export default class MetamaskController extends EventEmitter {
}
default: {
- const promise = this.encryptionPublicKeyManager.addUnapprovedMessageAsync(
- msgParams,
- req,
- );
+ const promise =
+ this.encryptionPublicKeyManager.addUnapprovedMessageAsync(
+ msgParams,
+ req,
+ );
this.sendUpdate();
this.opts.showUserConfirmation();
return promise;
@@ -3325,9 +3390,13 @@ export default class MetamaskController extends EventEmitter {
if (sender.url) {
const { hostname } = new URL(sender.url);
// Check if new connection is blocked if phishing detection is on
- if (usePhishDetect && this.phishingController.test(hostname)) {
- log.debug('MetaMask - sending phishing warning for', hostname);
- this.sendPhishingWarning(connectionStream, hostname);
+ const phishingTestResponse = this.phishingController.test(hostname);
+ if (usePhishDetect && phishingTestResponse?.result) {
+ this.sendPhishingWarning(
+ connectionStream,
+ hostname,
+ phishingTestResponse,
+ );
return;
}
}
@@ -3405,11 +3474,15 @@ export default class MetamaskController extends EventEmitter {
* @param {*} connectionStream - The duplex stream to the per-page script,
* for sending the reload attempt to.
* @param {string} hostname - The hostname that triggered the suspicion.
+ * @param {object} phishingTestResponse - Result of calling `phishingController.test`,
+ * which is the result of calling eth-phishing-detects detector.check method https://github.com/MetaMask/eth-phishing-detect/blob/master/src/detector.js#L55-L112
*/
- sendPhishingWarning(connectionStream, hostname) {
+ sendPhishingWarning(connectionStream, hostname, phishingTestResponse) {
+ const newIssueUrl = PHISHING_NEW_ISSUE_URLS[phishingTestResponse?.name];
+
const mux = setupMultiplex(connectionStream);
const phishingStream = mux.createStream('phishing');
- phishingStream.write({ hostname });
+ phishingStream.write({ hostname, newIssueUrl });
}
/**
@@ -3591,9 +3664,10 @@ export default class MetamaskController extends EventEmitter {
subjectType,
// Miscellaneous
- addSubjectMetadata: this.subjectMetadataController.addSubjectMetadata.bind(
- this.subjectMetadataController,
- ),
+ addSubjectMetadata:
+ this.subjectMetadataController.addSubjectMetadata.bind(
+ this.subjectMetadataController,
+ ),
getProviderState: this.getProviderState.bind(this),
getUnlockPromise: this.appStateController.getUnlockPromise.bind(
this.appStateController,
@@ -3601,9 +3675,10 @@ export default class MetamaskController extends EventEmitter {
handleWatchAssetRequest: this.tokensController.watchAsset.bind(
this.tokensController,
),
- requestUserApproval: this.approvalController.addAndShowApprovalRequest.bind(
- this.approvalController,
- ),
+ requestUserApproval:
+ this.approvalController.addAndShowApprovalRequest.bind(
+ this.approvalController,
+ ),
sendMetrics: this.metaMetricsController.trackEvent.bind(
this.metaMetricsController,
),
@@ -3618,15 +3693,17 @@ export default class MetamaskController extends EventEmitter {
this.permissionController,
origin,
),
- requestAccountsPermission: this.permissionController.requestPermissions.bind(
- this.permissionController,
- { origin },
- { eth_accounts: {} },
- ),
- requestPermissionsForOrigin: this.permissionController.requestPermissions.bind(
- this.permissionController,
- { origin },
- ),
+ requestAccountsPermission:
+ this.permissionController.requestPermissions.bind(
+ this.permissionController,
+ { origin },
+ { eth_accounts: {} },
+ ),
+ requestPermissionsForOrigin:
+ this.permissionController.requestPermissions.bind(
+ this.permissionController,
+ { origin },
+ ),
// Custom RPC-related
addCustomRpc: async ({
@@ -3669,9 +3746,10 @@ export default class MetamaskController extends EventEmitter {
getWeb3ShimUsageState: this.alertController.getWeb3ShimUsageState.bind(
this.alertController,
),
- setWeb3ShimUsageRecorded: this.alertController.setWeb3ShimUsageRecorded.bind(
- this.alertController,
- ),
+ setWeb3ShimUsageRecorded:
+ this.alertController.setWeb3ShimUsageRecorded.bind(
+ this.alertController,
+ ),
}),
);
@@ -3679,17 +3757,19 @@ export default class MetamaskController extends EventEmitter {
engine.push(
createSnapMethodMiddleware(subjectType === SUBJECT_TYPES.SNAP, {
getAppKey: this.getAppKeyForSubject.bind(this, origin),
+ getUnlockPromise: this.appStateController.getUnlockPromise.bind(
+ this.appStateController,
+ ),
getSnaps: this.snapController.getPermittedSnaps.bind(
this.snapController,
origin,
),
requestPermissions: async (requestedPermissions) => {
- const [
- approvedPermissions,
- ] = await this.permissionController.requestPermissions(
- { origin },
- requestedPermissions,
- );
+ const [approvedPermissions] =
+ await this.permissionController.requestPermissions(
+ { origin },
+ requestedPermissions,
+ );
return Object.values(approvedPermissions);
},
@@ -3979,10 +4059,8 @@ export default class MetamaskController extends EventEmitter {
* @returns {Promise}
*/
async getPendingNonce(address) {
- const {
- nonceDetails,
- releaseLock,
- } = await this.txController.nonceTracker.getNonceLock(address);
+ const { nonceDetails, releaseLock } =
+ await this.txController.nonceTracker.getNonceLock(address);
const pendingNonce = nonceDetails.params.highestSuggested;
releaseLock();
@@ -4098,7 +4176,8 @@ export default class MetamaskController extends EventEmitter {
nickname = '',
rpcPrefs = {},
) {
- const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail();
+ const frequentRpcListDetail =
+ this.preferencesController.getFrequentRpcListDetail();
const rpcSettings = frequentRpcListDetail.find(
(rpc) => rpcUrl === rpc.rpcUrl,
);
@@ -4147,7 +4226,8 @@ export default class MetamaskController extends EventEmitter {
* @returns {object} rpcInfo found in the frequentRpcList
*/
findCustomRpcBy(rpcInfo) {
- const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail();
+ const frequentRpcListDetail =
+ this.preferencesController.getFrequentRpcListDetail();
for (const existingRpcInfo of frequentRpcListDetail) {
for (const key of Object.keys(rpcInfo)) {
if (existingRpcInfo[key] === rpcInfo[key]) {
@@ -4168,10 +4248,10 @@ export default class MetamaskController extends EventEmitter {
* @param {string} transportType - The Ledger transport type.
*/
async setLedgerTransportPreference(transportType) {
- const currentValue = this.preferencesController.getLedgerTransportPreference();
- const newValue = this.preferencesController.setLedgerTransportPreference(
- transportType,
- );
+ const currentValue =
+ this.preferencesController.getLedgerTransportPreference();
+ const newValue =
+ this.preferencesController.setLedgerTransportPreference(transportType);
const keyring = await this.getKeyringForDevice(DEVICE_NAMES.LEDGER);
if (keyring?.updateTransportMethod) {
@@ -4237,9 +4317,8 @@ export default class MetamaskController extends EventEmitter {
onEnvironmentTypeClosed(environmentType) {
const appStatePollingTokenType =
POLLING_TOKEN_ENVIRONMENT_TYPES[environmentType];
- const pollingTokensToDisconnect = this.appStateController.store.getState()[
- appStatePollingTokenType
- ];
+ const pollingTokensToDisconnect =
+ this.appStateController.store.getState()[appStatePollingTokenType];
pollingTokensToDisconnect.forEach((pollingToken) => {
this.gasFeeController.disconnectPoller(pollingToken);
this.appStateController.removePollingToken(
diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js
index c0e1a08b9..21b697fd2 100644
--- a/app/scripts/metamask-controller.test.js
+++ b/app/scripts/metamask-controller.test.js
@@ -221,9 +221,10 @@ describe('MetaMaskController', function () {
});
it('adds private key to keyrings in KeyringController', async function () {
- const simpleKeyrings = metamaskController.keyringController.getKeyringsByType(
- 'Simple Key Pair',
- );
+ const simpleKeyrings =
+ metamaskController.keyringController.getKeyringsByType(
+ 'Simple Key Pair',
+ );
const privKeyBuffer = simpleKeyrings[0].wallets[0].privateKey;
const pubKeyBuffer = simpleKeyrings[0].wallets[0].publicKey;
const addressBuffer = pubToAddress(pubKeyBuffer);
@@ -234,7 +235,8 @@ describe('MetaMaskController', function () {
});
it('adds 1 account', async function () {
- const keyringAccounts = await metamaskController.keyringController.getAccounts();
+ const keyringAccounts =
+ await metamaskController.keyringController.getAccounts();
assert.equal(
keyringAccounts[keyringAccounts.length - 1],
'0xe18035bf8712672935fdb4e5e431b1a0183d2dfc',
@@ -259,7 +261,8 @@ describe('MetaMaskController', function () {
const identities = Object.keys(
metamaskController.preferencesController.store.getState().identities,
);
- const addresses = await metamaskController.keyringController.getAccounts();
+ const addresses =
+ await metamaskController.keyringController.getAccounts();
identities.forEach((identity) => {
assert.ok(
@@ -486,7 +489,8 @@ describe('MetaMaskController', function () {
});
it('changes preferences controller select address', function () {
- const preferenceControllerState = metamaskController.preferencesController.store.getState();
+ const preferenceControllerState =
+ metamaskController.preferencesController.store.getState();
assert.equal(preferenceControllerState.selectedAddress, address);
});
@@ -517,9 +521,10 @@ describe('MetaMaskController', function () {
await metamaskController
.connectHardware(DEVICE_NAMES.TREZOR, 0)
.catch(() => null);
- const keyrings = await metamaskController.keyringController.getKeyringsByType(
- KEYRING_TYPES.TREZOR,
- );
+ const keyrings =
+ await metamaskController.keyringController.getKeyringsByType(
+ KEYRING_TYPES.TREZOR,
+ );
assert.deepEqual(
metamaskController.keyringController.addNewKeyring.getCall(0).args,
[KEYRING_TYPES.TREZOR],
@@ -532,9 +537,10 @@ describe('MetaMaskController', function () {
await metamaskController
.connectHardware(DEVICE_NAMES.LEDGER, 0)
.catch(() => null);
- const keyrings = await metamaskController.keyringController.getKeyringsByType(
- KEYRING_TYPES.LEDGER,
- );
+ const keyrings =
+ await metamaskController.keyringController.getKeyringsByType(
+ KEYRING_TYPES.LEDGER,
+ );
assert.deepEqual(
metamaskController.keyringController.addNewKeyring.getCall(0).args,
[KEYRING_TYPES.LEDGER],
@@ -586,9 +592,10 @@ describe('MetaMaskController', function () {
.connectHardware(DEVICE_NAMES.TREZOR, 0)
.catch(() => null);
await metamaskController.forgetDevice(DEVICE_NAMES.TREZOR);
- const keyrings = await metamaskController.keyringController.getKeyringsByType(
- KEYRING_TYPES.TREZOR,
- );
+ const keyrings =
+ await metamaskController.keyringController.getKeyringsByType(
+ KEYRING_TYPES.TREZOR,
+ );
assert.deepEqual(keyrings[0].accounts, []);
assert.deepEqual(keyrings[0].page, 0);
@@ -645,9 +652,10 @@ describe('MetaMaskController', function () {
});
it('should set unlockedAccount in the keyring', async function () {
- const keyrings = await metamaskController.keyringController.getKeyringsByType(
- KEYRING_TYPES.TREZOR,
- );
+ const keyrings =
+ await metamaskController.keyringController.getKeyringsByType(
+ KEYRING_TYPES.TREZOR,
+ );
assert.equal(keyrings[0].unlockedAccount, accountToUnlock);
});
@@ -690,7 +698,8 @@ describe('MetaMaskController', function () {
CUSTOM_RPC_URL,
CUSTOM_RPC_CHAIN_ID,
);
- const networkControllerState = metamaskController.networkController.store.getState();
+ const networkControllerState =
+ metamaskController.networkController.store.getState();
assert.equal(networkControllerState.provider.rpcUrl, CUSTOM_RPC_URL);
});
});
@@ -725,8 +734,9 @@ describe('MetaMaskController', function () {
});
it('#addNewAccount', async function () {
- await metamaskController.addNewAccount();
- const getAccounts = await metamaskController.keyringController.getAccounts();
+ await metamaskController.addNewAccount(1);
+ const getAccounts =
+ await metamaskController.keyringController.getAccounts();
assert.equal(getAccounts.length, 2);
});
});
@@ -959,7 +969,8 @@ describe('MetaMaskController', function () {
// handle the promise so it doesn't throw an unhandledRejection
promise.then(noop).catch(noop);
- metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs();
+ metamaskPersonalMsgs =
+ metamaskController.personalMessageManager.getUnapprovedMsgs();
personalMessages = metamaskController.personalMessageManager.messages;
msgId = Object.keys(metamaskPersonalMsgs)[0];
personalMessages[0].msgParams.metamaskId = parseInt(msgId, 10);
@@ -1250,9 +1261,8 @@ describe('MetaMaskController', function () {
describe('markNotificationsAsRead', function () {
it('marks the notification as read', function () {
metamaskController.markNotificationsAsRead([NOTIFICATION_ID]);
- const readNotification = metamaskController.getState().notifications[
- NOTIFICATION_ID
- ];
+ const readNotification =
+ metamaskController.getState().notifications[NOTIFICATION_ID];
assert.notEqual(readNotification.readDate, null);
});
});
diff --git a/app/scripts/migrations/022.test.js b/app/scripts/migrations/022.test.js
index c4e91aac5..c2950b0ac 100644
--- a/app/scripts/migrations/022.test.js
+++ b/app/scripts/migrations/022.test.js
@@ -18,11 +18,8 @@ const storage = {
describe('storage is migrated successfully where transactions that are submitted have submittedTimes', () => {
it('should add submittedTime key on the txMeta if appropriate', async () => {
const migratedData = await migration22.migrate(storage);
- const [
- txMeta1,
- txMeta2,
- txMeta3,
- ] = migratedData.data.TransactionController.transactions;
+ const [txMeta1, txMeta2, txMeta3] =
+ migratedData.data.TransactionController.transactions;
expect(migratedData.meta.version).toStrictEqual(22);
// should have written a submitted time
diff --git a/app/scripts/migrations/030.js b/app/scripts/migrations/030.js
index 31dc1cf6e..41f00bf66 100644
--- a/app/scripts/migrations/030.js
+++ b/app/scripts/migrations/030.js
@@ -33,7 +33,8 @@ function transformState(state) {
delete frequentRpcListDetail[index].chainId;
}
});
- newState.PreferencesController.frequentRpcListDetail = frequentRpcListDetail;
+ newState.PreferencesController.frequentRpcListDetail =
+ frequentRpcListDetail;
}
}
if (state.NetworkController) {
diff --git a/app/scripts/migrations/047.js b/app/scripts/migrations/047.js
index b3d2a6fd7..a08da313a 100644
--- a/app/scripts/migrations/047.js
+++ b/app/scripts/migrations/047.js
@@ -21,7 +21,8 @@ function transformState(state) {
if (Array.isArray(transactions)) {
transactions.forEach((transaction) => {
if (typeof transaction.metamaskNetworkId === 'number') {
- transaction.metamaskNetworkId = transaction.metamaskNetworkId.toString();
+ transaction.metamaskNetworkId =
+ transaction.metamaskNetworkId.toString();
}
});
}
diff --git a/app/scripts/migrations/049.js b/app/scripts/migrations/049.js
index 6b3013d82..a420886e0 100644
--- a/app/scripts/migrations/049.js
+++ b/app/scripts/migrations/049.js
@@ -18,11 +18,8 @@ export default {
function transformState(state = {}) {
if (state.PreferencesController) {
- const {
- metaMetricsId,
- participateInMetaMetrics,
- metaMetricsSendCount,
- } = state.PreferencesController;
+ const { metaMetricsId, participateInMetaMetrics, metaMetricsSendCount } =
+ state.PreferencesController;
state.MetaMetricsController = state.MetaMetricsController ?? {};
if (metaMetricsId !== undefined) {
@@ -31,7 +28,8 @@ function transformState(state = {}) {
}
if (participateInMetaMetrics !== undefined) {
- state.MetaMetricsController.participateInMetaMetrics = participateInMetaMetrics;
+ state.MetaMetricsController.participateInMetaMetrics =
+ participateInMetaMetrics;
delete state.PreferencesController.participateInMetaMetrics;
}
diff --git a/app/scripts/migrations/052.js b/app/scripts/migrations/052.js
index 4b5de7d22..f2469106e 100644
--- a/app/scripts/migrations/052.js
+++ b/app/scripts/migrations/052.js
@@ -34,11 +34,8 @@ export default {
function transformState(state = {}) {
if (state.PreferencesController) {
- const {
- accountTokens,
- accountHiddenTokens,
- frequentRpcListDetail,
- } = state.PreferencesController;
+ const { accountTokens, accountHiddenTokens, frequentRpcListDetail } =
+ state.PreferencesController;
const newAccountTokens = {};
const newAccountHiddenTokens = {};
diff --git a/app/scripts/migrations/055.js b/app/scripts/migrations/055.js
index 98676bc63..b0ab2a844 100644
--- a/app/scripts/migrations/055.js
+++ b/app/scripts/migrations/055.js
@@ -23,13 +23,16 @@ function transformState(state) {
if (
state?.IncomingTransactionsController?.incomingTxLastFetchedBlocksByNetwork
) {
- state.IncomingTransactionsController.incomingTxLastFetchedBlockByChainId = mapKeys(
- state.IncomingTransactionsController.incomingTxLastFetchedBlocksByNetwork,
- // using optional chaining in case user's state has fetched blocks for
- // RPC network types (which don't map to a single chainId). This should
- // not be possible, but it's safer
- (_, key) => NETWORK_TYPE_TO_ID_MAP[key]?.chainId ?? UNKNOWN_CHAIN_ID_KEY,
- );
+ state.IncomingTransactionsController.incomingTxLastFetchedBlockByChainId =
+ mapKeys(
+ state.IncomingTransactionsController
+ .incomingTxLastFetchedBlocksByNetwork,
+ // using optional chaining in case user's state has fetched blocks for
+ // RPC network types (which don't map to a single chainId). This should
+ // not be possible, but it's safer
+ (_, key) =>
+ NETWORK_TYPE_TO_ID_MAP[key]?.chainId ?? UNKNOWN_CHAIN_ID_KEY,
+ );
// Now that mainnet and test net last fetched blocks are keyed by their
// respective chainIds, we can safely delete anything we had for custom
// networks. Any custom network that shares a chainId with one of the
diff --git a/app/scripts/migrations/056.js b/app/scripts/migrations/056.js
index 70300f363..48e75eaa4 100644
--- a/app/scripts/migrations/056.js
+++ b/app/scripts/migrations/056.js
@@ -30,11 +30,10 @@ export default {
PreferencesController.accountTokens[account],
);
chains.forEach((chain) => {
- PreferencesController.accountTokens[account][
- chain
- ] = PreferencesController.accountTokens[account][chain].filter(
- ({ address }) => address,
- );
+ PreferencesController.accountTokens[account][chain] =
+ PreferencesController.accountTokens[account][chain].filter(
+ ({ address }) => address,
+ );
});
});
}
diff --git a/app/scripts/migrations/064.test.js b/app/scripts/migrations/064.test.js
index 94228ad9b..227d4d11d 100644
--- a/app/scripts/migrations/064.test.js
+++ b/app/scripts/migrations/064.test.js
@@ -95,8 +95,7 @@ describe('migration #64', () => {
s: '0x49f74084dd8c517b305a2e60b39ae9002176a5244cb06de8f9ea3757811f5ec6',
status: 'confirmed',
estimatedBaseFee: 'b',
- hash:
- '0x4d8543f12afd3795b94d723dcd0e20bfc3740e1af668e5e90a0c5ec49f36ba12',
+ hash: '0x4d8543f12afd3795b94d723dcd0e20bfc3740e1af668e5e90a0c5ec49f36ba12',
},
1: {
type: SENT_ETHER,
@@ -118,8 +117,7 @@ describe('migration #64', () => {
status: 'unapproved',
time: 1631118004776,
txParams: {
- data:
- '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029',
+ data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029',
from: '0x0f002c95c041f003be01c3e4f52cae1f6ab3ba6e',
gas: '0x31413',
value: '0x0',
@@ -159,8 +157,7 @@ describe('migration #64', () => {
gas: '0xa9fe',
},
estimatedBaseFee: 'b',
- hash:
- '0x19ffab8a9467df9afbef82d8907f9e39f0696c7a774ed5473ecf7337adcc674b',
+ hash: '0x19ffab8a9467df9afbef82d8907f9e39f0696c7a774ed5473ecf7337adcc674b',
origin: 'https://metamask.github.io',
r: '0xc2b2901f3593536d21e9b136c469b9b8f91a944f18a29a3cdf3a2eaadf660e71',
rawTx:
@@ -277,8 +274,7 @@ describe('migration #64', () => {
s: '0x49f74084dd8c517b305a2e60b39ae9002176a5244cb06de8f9ea3757811f5ec6',
status: 'confirmed',
estimatedBaseFee: 'b',
- hash:
- '0x4d8543f12afd3795b94d723dcd0e20bfc3740e1af668e5e90a0c5ec49f36ba12',
+ hash: '0x4d8543f12afd3795b94d723dcd0e20bfc3740e1af668e5e90a0c5ec49f36ba12',
},
1: {
type: TRANSACTION_TYPES.SIMPLE_SEND,
@@ -300,8 +296,7 @@ describe('migration #64', () => {
status: 'unapproved',
time: 1631118004776,
txParams: {
- data:
- '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029',
+ data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029',
from: '0x0f002c95c041f003be01c3e4f52cae1f6ab3ba6e',
gas: '0x31413',
value: '0x0',
@@ -341,8 +336,7 @@ describe('migration #64', () => {
gas: '0xa9fe',
},
estimatedBaseFee: 'b',
- hash:
- '0x19ffab8a9467df9afbef82d8907f9e39f0696c7a774ed5473ecf7337adcc674b',
+ hash: '0x19ffab8a9467df9afbef82d8907f9e39f0696c7a774ed5473ecf7337adcc674b',
origin: 'https://metamask.github.io',
r: '0xc2b2901f3593536d21e9b136c469b9b8f91a944f18a29a3cdf3a2eaadf660e71',
rawTx:
diff --git a/app/scripts/migrations/065.js b/app/scripts/migrations/065.js
index 96e9820e1..c1422d8ae 100644
--- a/app/scripts/migrations/065.js
+++ b/app/scripts/migrations/065.js
@@ -19,10 +19,8 @@ export default {
function transformState(state) {
if (state.PreferencesController) {
- const {
- completedOnboarding,
- firstTimeFlowType,
- } = state.PreferencesController;
+ const { completedOnboarding, firstTimeFlowType } =
+ state.PreferencesController;
state.OnboardingController = state.OnboardingController ?? {};
if (completedOnboarding !== undefined) {
diff --git a/app/scripts/migrations/067.js b/app/scripts/migrations/067.js
index 98150edf7..b635a0643 100644
--- a/app/scripts/migrations/067.js
+++ b/app/scripts/migrations/067.js
@@ -36,14 +36,13 @@ function transformState(state) {
const cachedBalances = state.CachedBalancesController?.cachedBalances || {};
const userIsCurrentlyOnATestNet = TEST_CHAINS.includes(provider?.chainId);
- const userHasMadeATestNetTransaction = Object.values(
- transactions,
- ).some(({ chainId }) => TEST_CHAINS.includes(chainId));
+ const userHasMadeATestNetTransaction = Object.values(transactions).some(
+ ({ chainId }) => TEST_CHAINS.includes(chainId),
+ );
const userHasACachedBalanceOnATestnet = TEST_CHAINS.some((chainId) => {
const cachedBalancesForChain = Object.values(cachedBalances[chainId] || {});
- const userHasABalanceGreaterThanZeroOnThisChain = cachedBalancesForChain.some(
- hexNumberIsGreaterThanZero,
- );
+ const userHasABalanceGreaterThanZeroOnThisChain =
+ cachedBalancesForChain.some(hexNumberIsGreaterThanZero);
return userHasABalanceGreaterThanZeroOnThisChain;
});
const userHasUsedATestnet =
diff --git a/app/scripts/migrations/068.js b/app/scripts/migrations/068.js
index c2abbef76..a08dbcaea 100644
--- a/app/scripts/migrations/068.js
+++ b/app/scripts/migrations/068.js
@@ -38,9 +38,8 @@ function transformState(state) {
permissionActivityLog: permissionsLog,
permissionHistory: permissionsHistory,
},
- SubjectMetadataController: getSubjectMetadataControllerState(
- domainMetadata,
- ),
+ SubjectMetadataController:
+ getSubjectMetadataControllerState(domainMetadata),
};
}
diff --git a/app/scripts/migrations/068.test.js b/app/scripts/migrations/068.test.js
index 0540c5b48..b5aad374a 100644
--- a/app/scripts/migrations/068.test.js
+++ b/app/scripts/migrations/068.test.js
@@ -119,10 +119,8 @@ describe('migration #68', () => {
};
const newStorage = await migration68.migrate(oldStorage);
- const {
- PermissionLogController,
- SubjectMetadataController,
- } = newStorage.data;
+ const { PermissionLogController, SubjectMetadataController } =
+ newStorage.data;
const expected = getOldState().PermissionsMetadata;
expect(PermissionLogController.permissionHistory).toStrictEqual(
diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js
index 8f1d49600..7381fab9d 100644
--- a/app/scripts/platforms/extension.js
+++ b/app/scripts/platforms/extension.js
@@ -80,10 +80,8 @@ export default class ExtensionPlatform {
}
getVersion() {
- const {
- version,
- version_name: versionName,
- } = browser.runtime.getManifest();
+ const { version, version_name: versionName } =
+ browser.runtime.getManifest();
const versionParts = version.split('.');
if (versionName) {
diff --git a/app/scripts/ui.js b/app/scripts/ui.js
index 93f06143d..5d7a0c181 100644
--- a/app/scripts/ui.js
+++ b/app/scripts/ui.js
@@ -11,7 +11,7 @@ import Eth from 'ethjs';
import EthQuery from 'eth-query';
import StreamProvider from 'web3-stream-provider';
import log from 'loglevel';
-import launchMetaMaskUi from '../../ui';
+import launchMetaMaskUi, { updateBackgroundConnection } from '../../ui';
import {
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_POPUP,
@@ -24,34 +24,37 @@ import { setupMultiplex } from './lib/stream-utils';
import { getEnvironmentType } from './lib/util';
import metaRPCClientFactory from './lib/metaRPCClientFactory';
-start().catch(log.error);
-
-async function start() {
- async function displayCriticalError(container, err, metamaskState) {
- const html = await getErrorHtml(SUPPORT_LINK, metamaskState);
-
- container.innerHTML = html;
+const container = document.getElementById('app-content');
- const button = document.getElementById('critical-error-button');
+const WORKER_KEEP_ALIVE_INTERVAL = 1000;
+const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE';
- button.addEventListener('click', (_) => {
- browser.runtime.reload();
- });
+/*
+ * As long as UI is open it will keep sending messages to service worker
+ * In service worker as this message is received
+ * if service worker is inactive it is reactivated and script re-loaded
+ * Time has been kept to 1000ms but can be reduced for even faster re-activation of service worker
+ */
+if (isManifestV3) {
+ setInterval(() => {
+ browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE });
+ }, WORKER_KEEP_ALIVE_INTERVAL);
+}
- log.error(err.stack);
- throw err;
- }
+start().catch(log.error);
+async function start() {
// create platform global
global.platform = new ExtensionPlatform();
// identify window type (popup, notification)
const windowType = getEnvironmentType();
- // setup stream to background
- const extensionPort = browser.runtime.connect({ name: windowType });
+ let isUIInitialised = false;
- const connectionStream = new PortStream(extensionPort);
+ // setup stream to background
+ let extensionPort = browser.runtime.connect({ name: windowType });
+ let connectionStream = new PortStream(extensionPort);
const activeTab = await queryCurrentActiveTab(windowType);
@@ -59,24 +62,53 @@ async function start() {
* In case of MV3 the issue of blank screen was very frequent, it is caused by UI initialising before background is ready to send state.
* Code below ensures that UI is rendered only after background is ready.
*/
- if (isManifestV3()) {
- extensionPort.onMessage.addListener((message) => {
+ if (isManifestV3) {
+ /*
+ * In case of MV3 the issue of blank screen was very frequent, it is caused by UI initialising before background is ready to send state.
+ * Code below ensures that UI is rendered only after CONNECTION_READY message is received thus background is ready.
+ * In case the UI is already rendered, only update the streams.
+ */
+ const messageListener = (message) => {
if (message?.name === 'CONNECTION_READY') {
- initializeUiWithTab(activeTab);
+ if (isUIInitialised) {
+ updateUiStreams();
+ } else {
+ initializeUiWithTab(activeTab);
+ }
}
- });
+ };
+
+ // resetExtensionStreamAndListeners takes care to remove listeners from closed streams
+ // it also creates new streams and attach event listeners to them
+ const resetExtensionStreamAndListeners = () => {
+ extensionPort.onMessage.removeListener(messageListener);
+ extensionPort.onDisconnect.removeListener(
+ resetExtensionStreamAndListeners,
+ );
+ // message below will try to activate service worker
+ // in MV3 is likely that reason of stream closing is service worker going in-active
+ browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE });
+
+ extensionPort = browser.runtime.connect({ name: windowType });
+ connectionStream = new PortStream(extensionPort);
+ extensionPort.onMessage.addListener(messageListener);
+ extensionPort.onDisconnect.addListener(resetExtensionStreamAndListeners);
+ };
+
+ extensionPort.onMessage.addListener(messageListener);
+ extensionPort.onDisconnect.addListener(resetExtensionStreamAndListeners);
} else {
initializeUiWithTab(activeTab);
}
function initializeUiWithTab(tab) {
- const container = document.getElementById('app-content');
- initializeUi(tab, container, connectionStream, (err, store) => {
+ initializeUi(tab, connectionStream, (err, store) => {
if (err) {
// if there's an error, store will be = metamaskState
- displayCriticalError(container, err, store);
+ displayCriticalError(err, store);
return;
}
+ isUIInitialised = true;
const state = store.getState();
const { metamask: { completedOnboarding } = {} } = state;
@@ -86,6 +118,18 @@ async function start() {
}
});
}
+
+ // Function to update new backgroundConnection in the UI
+ function updateUiStreams() {
+ connectToAccountManager(connectionStream, (err, backgroundConnection) => {
+ if (err) {
+ displayCriticalError(err);
+ return;
+ }
+
+ updateBackgroundConnection(backgroundConnection);
+ });
+ }
}
async function queryCurrentActiveTab(windowType) {
@@ -112,7 +156,7 @@ async function queryCurrentActiveTab(windowType) {
});
}
-function initializeUi(activeTab, container, connectionStream, cb) {
+function initializeUi(activeTab, connectionStream, cb) {
connectToAccountManager(connectionStream, (err, backgroundConnection) => {
if (err) {
cb(err, null);
@@ -130,6 +174,21 @@ function initializeUi(activeTab, container, connectionStream, cb) {
});
}
+async function displayCriticalError(err, metamaskState) {
+ const html = await getErrorHtml(SUPPORT_LINK, metamaskState);
+
+ container.innerHTML = html;
+
+ const button = document.getElementById('critical-error-button');
+
+ button.addEventListener('click', (_) => {
+ browser.runtime.reload();
+ });
+
+ log.error(err.stack);
+ throw err;
+}
+
/**
* Establishes a connection to the background and a Web3 provider
*
@@ -138,7 +197,8 @@ function initializeUi(activeTab, container, connectionStream, cb) {
*/
function connectToAccountManager(connectionStream, cb) {
const mx = setupMultiplex(connectionStream);
- setupControllerConnection(mx.createStream('controller'), cb);
+ const controllerConnectionStream = mx.createStream('controller');
+ setupControllerConnection(controllerConnectionStream, cb);
setupWeb3Connection(mx.createStream('provider'));
}
@@ -160,10 +220,10 @@ function setupWeb3Connection(connectionStream) {
/**
* Establishes a streamed connection to the background account manager
*
- * @param {PortDuplexStream} connectionStream - PortStream instance establishing a background connection
+ * @param {PortDuplexStream} controllerConnectionStream - PortStream instance establishing a background connection
* @param {Function} cb - Called when the remote account manager connection is established
*/
-function setupControllerConnection(connectionStream, cb) {
- const backgroundRPC = metaRPCClientFactory(connectionStream);
+function setupControllerConnection(controllerConnectionStream, cb) {
+ const backgroundRPC = metaRPCClientFactory(controllerConnectionStream);
cb(null, backgroundRPC);
}
diff --git a/app/trezor-usb-permissions.html b/app/trezor-usb-permissions.html
index 16f28e5e1..8e35d19ca 100644
--- a/app/trezor-usb-permissions.html
+++ b/app/trezor-usb-permissions.html
@@ -30,4 +30,4 @@
-
\ No newline at end of file
+
diff --git a/babel.config.js b/babel.config.js
index 31e829dec..c7bbde0ba 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -4,24 +4,13 @@ module.exports = function (api) {
parserOpts: {
strictMode: true,
},
+ targets: {
+ browsers: ['chrome >= 66', 'firefox >= 68'],
+ },
presets: [
'@babel/preset-typescript',
- [
- '@babel/preset-env',
- {
- targets: {
- browsers: ['chrome >= 66', 'firefox >= 68'],
- },
- },
- ],
+ '@babel/preset-env',
'@babel/preset-react',
],
- plugins: [
- '@babel/plugin-transform-runtime',
- '@babel/plugin-proposal-class-properties',
- '@babel/plugin-proposal-object-rest-spread',
- '@babel/plugin-proposal-optional-chaining',
- '@babel/plugin-proposal-nullish-coalescing-operator',
- ],
};
};
diff --git a/development/README.md b/development/README.md
index d0c890813..c53c91fc0 100644
--- a/development/README.md
+++ b/development/README.md
@@ -46,7 +46,7 @@ Events triggered whilst using the extension will be displayed in Segment's Debug
To opt in to MetaMetrics;
- Unlock the extension
-- Open the Account menu
+- Open the Account menu
- Click the `Settings` menu item
- Click the `Security & Privacy` menu item
- Toggle the `Participate in MetaMetrics` menu option to the `ON` position
diff --git a/development/announcer.js b/development/announcer.js
old mode 100644
new mode 100755
index a3fc459a9..2c23cf9f8
--- a/development/announcer.js
+++ b/development/announcer.js
@@ -1,3 +1,4 @@
+#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { version } = require('../package.json');
diff --git a/development/build/config.js b/development/build/config.js
new file mode 100644
index 000000000..a907b64f5
--- /dev/null
+++ b/development/build/config.js
@@ -0,0 +1,101 @@
+const path = require('path');
+const { readFile } = require('fs/promises');
+const ini = require('ini');
+const { BuildType } = require('../lib/build-type');
+
+/**
+ * Get configuration for non-production builds.
+ *
+ * @returns {object} The production configuration.
+ */
+async function getConfig() {
+ const configPath = path.resolve(__dirname, '..', '..', '.metamaskrc');
+ let configContents = '';
+ try {
+ configContents = await readFile(configPath, {
+ encoding: 'utf8',
+ });
+ } catch (error) {
+ if (error.code !== 'ENOENT') {
+ throw error;
+ }
+ }
+ return {
+ COLLECTIBLES_V1: process.env.COLLECTIBLES_V1,
+ INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID,
+ ONBOARDING_V2: process.env.ONBOARDING_V2,
+ PHISHING_WARNING_PAGE_URL: process.env.PHISHING_WARNING_PAGE_URL,
+ PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY,
+ PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY,
+ SEGMENT_HOST: process.env.SEGMENT_HOST,
+ SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY,
+ SENTRY_DSN_DEV:
+ process.env.SENTRY_DSN_DEV ??
+ 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496',
+ SIWE_V1: process.env.SIWE_V1,
+ SWAPS_USE_DEV_APIS: process.env.SWAPS_USE_DEV_APIS,
+ ...ini.parse(configContents),
+ };
+}
+
+/**
+ * Get configuration for production builds and perform validation.
+ *
+ * This function validates that all required variables are present, and that
+ * the production configuration file doesn't include any extraneous entries.
+ *
+ * @param {BuildType} buildType - The current build type (e.g. "main", "flask",
+ * etc.).
+ * @returns {object} The production configuration.
+ */
+async function getProductionConfig(buildType) {
+ const prodConfigPath = path.resolve(__dirname, '..', '..', '.metamaskprodrc');
+ let prodConfigContents = '';
+ try {
+ prodConfigContents = await readFile(prodConfigPath, {
+ encoding: 'utf8',
+ });
+ } catch (error) {
+ if (error.code !== 'ENOENT') {
+ throw error;
+ }
+ }
+ const prodConfig = {
+ INFURA_BETA_PROJECT_ID: process.env.INFURA_BETA_PROJECT_ID,
+ INFURA_FLASK_PROJECT_ID: process.env.INFURA_FLASK_PROJECT_ID,
+ INFURA_PROD_PROJECT_ID: process.env.INFURA_PROD_PROJECT_ID,
+ PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY,
+ PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY,
+ SEGMENT_BETA_WRITE_KEY: process.env.SEGMENT_BETA_WRITE_KEY,
+ SEGMENT_FLASK_WRITE_KEY: process.env.SEGMENT_FLASK_WRITE_KEY,
+ SEGMENT_PROD_WRITE_KEY: process.env.SEGMENT_PROD_WRITE_KEY,
+ SENTRY_DSN: process.env.SENTRY_DSN,
+ ...ini.parse(prodConfigContents),
+ };
+
+ const requiredEnvironmentVariables = {
+ all: ['PUBNUB_PUB_KEY', 'PUBNUB_SUB_KEY', 'SENTRY_DSN'],
+ [BuildType.beta]: ['INFURA_BETA_PROJECT_ID', 'SEGMENT_BETA_WRITE_KEY'],
+ [BuildType.flask]: ['INFURA_FLASK_PROJECT_ID', 'SEGMENT_FLASK_WRITE_KEY'],
+ [BuildType.main]: ['INFURA_PROD_PROJECT_ID', 'SEGMENT_PROD_WRITE_KEY'],
+ };
+
+ for (const required of [
+ ...requiredEnvironmentVariables.all,
+ ...requiredEnvironmentVariables[buildType],
+ ]) {
+ if (!prodConfig[required]) {
+ throw new Error(`Missing '${required}' environment variable`);
+ }
+ }
+
+ const allValid = Object.values(requiredEnvironmentVariables).flat();
+ for (const environmentVariable of Object.keys(prodConfig)) {
+ if (!allValid.includes(environmentVariable)) {
+ throw new Error(`Invalid environment variable: '${environmentVariable}'`);
+ }
+ }
+ return prodConfig;
+}
+
+module.exports = { getConfig, getProductionConfig };
diff --git a/development/build/constants.js b/development/build/constants.js
index 15b87e984..c91ce0a93 100644
--- a/development/build/constants.js
+++ b/development/build/constants.js
@@ -1,20 +1,51 @@
+/**
+ * The build target. This descrbes the overall purpose of the build.
+ *
+ * These constants also act as the high-level tasks for the build system (i.e.
+ * the usual tasks invoked directly via the CLI rather than internally).
+ */
+const BUILD_TARGETS = {
+ DEV: 'dev',
+ DIST: 'dist',
+ PROD: 'prod',
+ TEST: 'test',
+ TEST_DEV: 'testDev',
+};
+
+/**
+ * The build environment. This describes the environment this build was produced in.
+ */
+const ENVIRONMENT = {
+ DEVELOPMENT: 'development',
+ PRODUCTION: 'production',
+ OTHER: 'other',
+ PULL_REQUEST: 'pull-request',
+ RELEASE_CANDIDATE: 'release-candidate',
+ STAGING: 'staging',
+ TESTING: 'testing',
+};
+
const TASKS = {
+ ...BUILD_TARGETS,
CLEAN: 'clean',
- DEV: 'dev',
LINT_SCSS: 'lint-scss',
MANIFEST_DEV: 'manifest:dev',
MANIFEST_PROD: 'manifest:prod',
MANIFEST_TEST: 'manifest:test',
MANIFEST_TEST_DEV: 'manifest:testDev',
- PROD: 'prod',
RELOAD: 'reload',
- SCRIPTS_PROD: 'scripts:prod',
SCRIPTS_CORE_DEV_STANDARD_ENTRY_POINTS:
'scripts:core:dev:standardEntryPoints',
SCRIPTS_CORE_DEV_CONTENTSCRIPT: 'scripts:core:dev:contentscript',
SCRIPTS_CORE_DEV_DISABLE_CONSOLE: 'scripts:core:dev:disable-console',
SCRIPTS_CORE_DEV_SENTRY: 'scripts:core:dev:sentry',
SCRIPTS_CORE_DEV_PHISHING_DETECT: 'scripts:core:dev:phishing-detect',
+ SCRIPTS_CORE_DIST_STANDARD_ENTRY_POINTS:
+ 'scripts:core:dist:standardEntryPoints',
+ SCRIPTS_CORE_DIST_CONTENTSCRIPT: 'scripts:core:dist:contentscript',
+ SCRIPTS_CORE_DIST_DISABLE_CONSOLE: 'scripts:core:dist:disable-console',
+ SCRIPTS_CORE_DIST_SENTRY: 'scripts:core:dist:sentry',
+ SCRIPTS_CORE_DIST_PHISHING_DETECT: 'scripts:core:dist:phishing-detect',
SCRIPTS_CORE_PROD_STANDARD_ENTRY_POINTS:
'scripts:core:prod:standardEntryPoints',
SCRIPTS_CORE_PROD_CONTENTSCRIPT: 'scripts:core:prod:contentscript',
@@ -35,14 +66,13 @@ const TASKS = {
SCRIPTS_CORE_TEST_DISABLE_CONSOLE: 'scripts:core:test:disable-console',
SCRIPTS_CORE_TEST_SENTRY: 'scripts:core:test:sentry',
SCRIPTS_CORE_TEST_PHISHING_DETECT: 'scripts:core:test:phishing-detect',
+ SCRIPTS_DIST: 'scripts:dist',
STATIC_DEV: 'static:dev',
STATIC_PROD: 'static:prod',
STYLES: 'styles',
STYLES_DEV: 'styles:dev',
STYLES_PROD: 'styles:prod',
- TEST: 'test',
- TEST_DEV: 'testDev',
ZIP: 'zip',
};
-module.exports = { TASKS };
+module.exports = { BUILD_TARGETS, ENVIRONMENT, TASKS };
diff --git a/development/build/etc.js b/development/build/etc.js
index a75cd650f..3da86409f 100644
--- a/development/build/etc.js
+++ b/development/build/etc.js
@@ -49,7 +49,10 @@ function createZipTask(platform, buildType, version) {
gulp.src(`dist/${platform}/**`),
// sort files and set `mtime` to epoch to ensure zip build is deterministic
sort(),
- gulpZip(`${path}.zip`, { modifiedTime: new Date(0) }),
+ // Modified time set to an arbitrary static date to ensure build the is reproducible.
+ // The date chosen is MetaMask's birthday. Originally we chose the Unix epoch, but this
+ // resulted in invalid dates on certain timezones/operating systems.
+ gulpZip(`${path}.zip`, { modifiedTime: new Date('2016-07-14T00:00:00') }),
gulp.dest('builds'),
);
};
diff --git a/development/build/index.js b/development/build/index.js
index d9924f86b..628cbbbac 100755
--- a/development/build/index.js
+++ b/development/build/index.js
@@ -1,3 +1,4 @@
+#!/usr/bin/env node
//
// build task definitions
//
@@ -10,7 +11,7 @@ const { hideBin } = require('yargs/helpers');
const { sync: globby } = require('globby');
const { getVersion } = require('../lib/get-version');
const { BuildType } = require('../lib/build-type');
-const { TASKS } = require('./constants');
+const { TASKS, ENVIRONMENT } = require('./constants');
const {
createTask,
composeSeries,
@@ -22,17 +23,14 @@ const createScriptTasks = require('./scripts');
const createStyleTasks = require('./styles');
const createStaticAssetTasks = require('./static');
const createEtcTasks = require('./etc');
-const { getBrowserVersionMap } = require('./utils');
+const { getBrowserVersionMap, getEnvironment } = require('./utils');
+const { getConfig, getProductionConfig } = require('./config');
+const { BUILD_TARGETS } = require('./constants');
// Packages required dynamically via browserify configuration in dependencies
// Required for LavaMoat policy generation
require('loose-envify');
require('globalthis');
-require('@babel/plugin-proposal-object-rest-spread');
-require('@babel/plugin-transform-runtime');
-require('@babel/plugin-proposal-class-properties');
-require('@babel/plugin-proposal-optional-chaining');
-require('@babel/plugin-proposal-nullish-coalescing-operator');
require('@babel/preset-env');
require('@babel/preset-react');
require('@babel/preset-typescript');
@@ -55,9 +53,12 @@ require('eslint-plugin-react');
require('eslint-plugin-react-hooks');
require('eslint-plugin-jest');
-defineAndRunBuildTasks();
+defineAndRunBuildTasks().catch((error) => {
+ console.error(error.stack || error);
+ process.exitCode = 1;
+});
-function defineAndRunBuildTasks() {
+async function defineAndRunBuildTasks() {
const {
applyLavaMoat,
buildType,
@@ -68,7 +69,7 @@ function defineAndRunBuildTasks() {
shouldLintFenceFiles,
skipStats,
version,
- } = parseArgv();
+ } = await parseArgv();
const browserPlatforms = ['firefox', 'chrome', 'brave', 'opera'];
@@ -140,6 +141,17 @@ function defineAndRunBuildTasks() {
),
);
+ // build production-like distributable build
+ createTask(
+ TASKS.DIST,
+ composeSeries(
+ clean,
+ styleTasks.prod,
+ composeParallel(scriptTasks.dist, staticTasks.prod, manifestTasks.prod),
+ zip,
+ ),
+ );
+
// build for prod release
createTask(
TASKS.PROD,
@@ -152,7 +164,7 @@ function defineAndRunBuildTasks() {
);
// build just production scripts, for LavaMoat policy generation purposes
- createTask(TASKS.SCRIPTS_PROD, scriptTasks.prod);
+ createTask(TASKS.SCRIPTS_DIST, scriptTasks.dist);
// build for CI testing
createTask(
@@ -169,20 +181,22 @@ function defineAndRunBuildTasks() {
createTask(TASKS.styles, styleTasks.prod);
// Finally, start the build process by running the entry task.
- runTask(entryTask, { skipStats });
+ await runTask(entryTask, { skipStats });
}
-function parseArgv() {
+async function parseArgv() {
const { argv } = yargs(hideBin(process.argv))
.usage('$0 [options]', 'Build the MetaMask extension.', (_yargs) =>
_yargs
.positional('task', {
description: `The task to run. There are a number of main tasks, each of which calls other tasks internally. The main tasks are:
-prod: Create an optimized build for a production environment.
-
dev: Create an unoptimized, live-reloading build for local development.
+dist: Create an optimized production-like for a non-production environment.
+
+prod: Create an optimized build for a production environment.
+
test: Create an optimized build for running e2e tests.
testDev: Create an unoptimized, live-reloading build for debugging e2e tests.`,
@@ -259,6 +273,18 @@ testDev: Create an unoptimized, live-reloading build for debugging e2e tests.`,
const version = getVersion(buildType, buildVersion);
+ const highLevelTasks = Object.values(BUILD_TARGETS);
+ if (highLevelTasks.includes(task)) {
+ const environment = getEnvironment({ buildTarget: task });
+ if (environment === ENVIRONMENT.PRODUCTION) {
+ // Output ignored, this is only called to ensure config is validated
+ await getProductionConfig(buildType);
+ } else {
+ // Output ignored, this is only called to ensure config is validated
+ await getConfig();
+ }
+ }
+
return {
applyLavaMoat,
buildType,
diff --git a/development/build/scripts.js b/development/build/scripts.js
index 95833b6ed..86a324ad4 100644
--- a/development/build/scripts.js
+++ b/development/build/scripts.js
@@ -27,28 +27,16 @@ const terser = require('terser');
const bifyModuleGroups = require('bify-module-groups');
-const metamaskrc = require('rc')('metamask', {
- INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID,
- INFURA_BETA_PROJECT_ID: process.env.INFURA_BETA_PROJECT_ID,
- INFURA_FLASK_PROJECT_ID: process.env.INFURA_FLASK_PROJECT_ID,
- INFURA_PROD_PROJECT_ID: process.env.INFURA_PROD_PROJECT_ID,
- ONBOARDING_V2: process.env.ONBOARDING_V2,
- COLLECTIBLES_V1: process.env.COLLECTIBLES_V1,
- PHISHING_WARNING_PAGE_URL: process.env.PHISHING_WARNING_PAGE_URL,
- TOKEN_DETECTION_V2: process.env.TOKEN_DETECTION_V2,
- ADD_POPULAR_NETWORKS: process.env.ADD_POPULAR_NETWORKS,
- SEGMENT_HOST: process.env.SEGMENT_HOST,
- SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY,
- SEGMENT_BETA_WRITE_KEY: process.env.SEGMENT_BETA_WRITE_KEY,
- SEGMENT_FLASK_WRITE_KEY: process.env.SEGMENT_FLASK_WRITE_KEY,
- SEGMENT_PROD_WRITE_KEY: process.env.SEGMENT_PROD_WRITE_KEY,
- SENTRY_DSN_DEV:
- process.env.SENTRY_DSN_DEV ||
- 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496',
-});
-
-const { streamFlatMap } = require('../stream-flat-map.js');
+const { streamFlatMap } = require('../stream-flat-map');
const { BuildType } = require('../lib/build-type');
+const { BUILD_TARGETS, ENVIRONMENT } = require('./constants');
+const { getConfig, getProductionConfig } = require('./config');
+const {
+ isDevBuild,
+ isTestBuild,
+ getEnvironment,
+ logError,
+} = require('./utils');
const {
createTask,
@@ -60,55 +48,28 @@ const {
createRemoveFencedCodeTransform,
} = require('./transforms/remove-fenced-code');
-/**
- * The build environment. This describes the environment this build was produced in.
- */
-const ENVIRONMENT = {
- DEVELOPMENT: 'development',
- PRODUCTION: 'production',
- OTHER: 'other',
- PULL_REQUEST: 'pull-request',
- RELEASE_CANDIDATE: 'release-candidate',
- STAGING: 'staging',
- TESTING: 'testing',
-};
-
-/**
- * Get a value from the configuration, and confirm that it is set.
- *
- * @param {string} key - The configuration key to retrieve.
- * @returns {string} The config entry requested.
- * @throws {Error} Throws if the requested key is missing.
- */
-function getConfigValue(key) {
- const value = metamaskrc[key];
- if (!value) {
- throw new Error(`Missing config entry for '${key}'`);
- }
- return value;
-}
-
/**
* Get the appropriate Infura project ID.
*
* @param {object} options - The Infura project ID options.
* @param {BuildType} options.buildType - The current build type.
+ * @param {object} options.config - The environment variable configuration.
* @param {ENVIRONMENT[keyof ENVIRONMENT]} options.environment - The build environment.
- * @param {boolean} options.testing - Whether the current build is a test build or not.
+ * @param {boolean} options.testing - Whether this is a test build or not.
* @returns {string} The Infura project ID.
*/
-function getInfuraProjectId({ buildType, environment, testing }) {
+function getInfuraProjectId({ buildType, config, environment, testing }) {
if (testing) {
return '00000000000000000000000000000000';
} else if (environment !== ENVIRONMENT.PRODUCTION) {
// Skip validation because this is unset on PRs from forks.
- return metamaskrc.INFURA_PROJECT_ID;
+ return config.INFURA_PROJECT_ID;
} else if (buildType === BuildType.main) {
- return getConfigValue('INFURA_PROD_PROJECT_ID');
+ return config.INFURA_PROD_PROJECT_ID;
} else if (buildType === BuildType.beta) {
- return getConfigValue('INFURA_BETA_PROJECT_ID');
+ return config.INFURA_BETA_PROJECT_ID;
} else if (buildType === BuildType.flask) {
- return getConfigValue('INFURA_FLASK_PROJECT_ID');
+ return config.INFURA_FLASK_PROJECT_ID;
}
throw new Error(`Invalid build type: '${buildType}'`);
}
@@ -118,19 +79,20 @@ function getInfuraProjectId({ buildType, environment, testing }) {
*
* @param {object} options - The Segment write key options.
* @param {BuildType} options.buildType - The current build type.
+ * @param {object} options.config - The environment variable configuration.
* @param {keyof ENVIRONMENT} options.environment - The current build environment.
* @returns {string} The Segment write key.
*/
-function getSegmentWriteKey({ buildType, environment }) {
+function getSegmentWriteKey({ buildType, config, environment }) {
if (environment !== ENVIRONMENT.PRODUCTION) {
// Skip validation because this is unset on PRs from forks, and isn't necessary for development builds.
- return metamaskrc.SEGMENT_WRITE_KEY;
+ return config.SEGMENT_WRITE_KEY;
} else if (buildType === BuildType.main) {
- return getConfigValue('SEGMENT_PROD_WRITE_KEY');
+ return config.SEGMENT_PROD_WRITE_KEY;
} else if (buildType === BuildType.beta) {
- return getConfigValue('SEGMENT_BETA_WRITE_KEY');
+ return config.SEGMENT_BETA_WRITE_KEY;
} else if (buildType === BuildType.flask) {
- return getConfigValue('SEGMENT_FLASK_WRITE_KEY');
+ return config.SEGMENT_FLASK_WRITE_KEY;
}
throw new Error(`Invalid build type: '${buildType}'`);
}
@@ -139,11 +101,12 @@ function getSegmentWriteKey({ buildType, environment }) {
* Get the URL for the phishing warning page, if it has been set.
*
* @param {object} options - The phishing warning page options.
+ * @param {object} options.config - The environment variable configuration.
* @param {boolean} options.testing - Whether this is a test build or not.
* @returns {string} The URL for the phishing warning page, or `undefined` if no URL is set.
*/
-function getPhishingWarningPageUrl({ testing }) {
- let phishingWarningPageUrl = metamaskrc.PHISHING_WARNING_PAGE_URL;
+function getPhishingWarningPageUrl({ config, testing }) {
+ let phishingWarningPageUrl = config.PHISHING_WARNING_PAGE_URL;
if (!phishingWarningPageUrl) {
phishingWarningPageUrl = testing
@@ -183,6 +146,31 @@ const noopWriteStream = through.obj((_file, _fileEncoding, callback) =>
module.exports = createScriptTasks;
+/**
+ * Create tasks for building JavaScript bundles and templates. One
+ * task is returned for each build target. These build target tasks are
+ * each composed of smaller tasks.
+ *
+ * @param {object} options - Build options.
+ * @param {boolean} options.applyLavaMoat - Whether the build should use
+ * LavaMoat at runtime or not.
+ * @param {string[]} options.browserPlatforms - A list of browser platforms to
+ * build bundles for.
+ * @param {BuildType} options.buildType - The current build type (e.g. "main",
+ * "flask", etc.).
+ * @param {string[] | null} options.ignoredFiles - A list of files to exclude
+ * from the current build.
+ * @param {boolean} options.isLavaMoat - Whether this build script is being run
+ * using LavaMoat or not.
+ * @param {object} options.livereload - The "gulp-livereload" server instance.
+ * @param {boolean} options.policyOnly - Whether to stop the build after
+ * generating the LavaMoat policy, skipping any writes to disk other than the
+ * LavaMoat policy itself.
+ * @param {boolean} options.shouldLintFenceFiles - Whether files with code
+ * fences should be linted after fences have been removed.
+ * @param {string} options.version - The current version of the extension.
+ * @returns {object} A set of tasks, one for each build target.
+ */
function createScriptTasks({
applyLavaMoat,
browserPlatforms,
@@ -190,45 +178,59 @@ function createScriptTasks({
ignoredFiles,
isLavaMoat,
livereload,
- shouldLintFenceFiles,
policyOnly,
+ shouldLintFenceFiles,
version,
}) {
- // internal tasks
- const core = {
+ // high level tasks
+ return {
// dev tasks (live reload)
- dev: createTasksForBuildJsExtension({
+ dev: createTasksForScriptBundles({
+ buildTarget: BUILD_TARGETS.DEV,
taskPrefix: 'scripts:core:dev',
- devMode: true,
}),
- testDev: createTasksForBuildJsExtension({
- taskPrefix: 'scripts:core:test-live',
- devMode: true,
- testing: true,
+ // production-like distributable build
+ dist: createTasksForScriptBundles({
+ buildTarget: BUILD_TARGETS.DIST,
+ taskPrefix: 'scripts:core:dist',
+ }),
+ // production
+ prod: createTasksForScriptBundles({
+ buildTarget: BUILD_TARGETS.PROD,
+ taskPrefix: 'scripts:core:prod',
}),
// built for CI tests
- test: createTasksForBuildJsExtension({
+ test: createTasksForScriptBundles({
+ buildTarget: BUILD_TARGETS.TEST,
taskPrefix: 'scripts:core:test',
- testing: true,
}),
- // production
- prod: createTasksForBuildJsExtension({ taskPrefix: 'scripts:core:prod' }),
+ // built for CI test debugging
+ testDev: createTasksForScriptBundles({
+ buildTarget: BUILD_TARGETS.TEST_DEV,
+ taskPrefix: 'scripts:core:test-live',
+ }),
};
- // high level tasks
-
- const { dev, test, testDev, prod } = core;
- return { dev, test, testDev, prod };
-
- function createTasksForBuildJsExtension({ taskPrefix, devMode, testing }) {
+ /**
+ * Define tasks for building the JavaScript modules used by the extension.
+ * This function returns a single task that builds JavaScript modules in
+ * parallel for a single type of build (e.g. dev, testing, production).
+ *
+ * @param {object} options - The build options.
+ * @param {BUILD_TARGETS} options.buildTarget - The build target that these
+ * JavaScript modules are intended for.
+ * @param {string} options.taskPrefix - The prefix to use for the name of
+ * each defined task.
+ */
+ function createTasksForScriptBundles({ buildTarget, taskPrefix }) {
const standardEntryPoints = ['background', 'ui', 'content-script'];
const standardSubtask = createTask(
`${taskPrefix}:standardEntryPoints`,
createFactoredBuild({
applyLavaMoat,
browserPlatforms,
+ buildTarget,
buildType,
- devMode,
entryFiles: standardEntryPoints.map((label) => {
if (label === 'content-script') {
return './app/vendor/trezor/content-script.js';
@@ -238,7 +240,6 @@ function createScriptTasks({
ignoredFiles,
policyOnly,
shouldLintFenceFiles,
- testing,
version,
}),
);
@@ -247,24 +248,24 @@ function createScriptTasks({
// because inpage bundle result is included inside contentscript
const contentscriptSubtask = createTask(
`${taskPrefix}:contentscript`,
- createTaskForBundleContentscript({ devMode, testing }),
+ createContentscriptBundle({ buildTarget }),
);
// this can run whenever
const disableConsoleSubtask = createTask(
`${taskPrefix}:disable-console`,
- createTaskForBundleDisableConsole({ devMode, testing }),
+ createDisableConsoleBundle({ buildTarget }),
);
// this can run whenever
const installSentrySubtask = createTask(
`${taskPrefix}:sentry`,
- createTaskForBundleSentry({ devMode, testing }),
+ createSentryBundle({ buildTarget }),
);
// task for initiating browser livereload
const initiateLiveReload = async () => {
- if (devMode) {
+ if (isDevBuild(buildTarget)) {
// trigger live reload when the bundles are updated
// this is not ideal, but overcomes the limitations:
// - run from the main process (not child process tasks)
@@ -297,153 +298,214 @@ function createScriptTasks({
return composeParallel(initiateLiveReload, ...allSubtasks);
}
- function createTaskForBundleDisableConsole({ devMode, testing }) {
+ /**
+ * Create a bundle for the "disable-console" module.
+ *
+ * @param {object} options - The build options.
+ * @param {BUILD_TARGETS} options.buildTarget - The current build target.
+ * @returns {Function} A function that creates the bundle.
+ */
+ function createDisableConsoleBundle({ buildTarget }) {
const label = 'disable-console';
return createNormalBundle({
browserPlatforms,
+ buildTarget,
buildType,
destFilepath: `${label}.js`,
- devMode,
entryFilepath: `./app/scripts/${label}.js`,
ignoredFiles,
label,
- testing,
policyOnly,
shouldLintFenceFiles,
version,
});
}
- function createTaskForBundleSentry({ devMode, testing }) {
+ /**
+ * Create a bundle for the "sentry-install" module.
+ *
+ * @param {object} options - The build options.
+ * @param {BUILD_TARGETS} options.buildTarget - The current build target.
+ * @returns {Function} A function that creates the bundle.
+ */
+ function createSentryBundle({ buildTarget }) {
const label = 'sentry-install';
return createNormalBundle({
browserPlatforms,
+ buildTarget,
buildType,
destFilepath: `${label}.js`,
- devMode,
entryFilepath: `./app/scripts/${label}.js`,
ignoredFiles,
label,
- testing,
policyOnly,
shouldLintFenceFiles,
version,
});
}
- // the "contentscript" bundle contains the "inpage" bundle
- function createTaskForBundleContentscript({ devMode, testing }) {
+ /**
+ * Create bundles for the "contentscript" and "inpage" modules. The inpage
+ * module is created first because it gets embedded in the contentscript
+ * module.
+ *
+ * @param {object} options - The build options.
+ * @param {BUILD_TARGETS} options.buildTarget - The current build target.
+ * @returns {Function} A function that creates the bundles.
+ */
+ function createContentscriptBundle({ buildTarget }) {
const inpage = 'inpage';
const contentscript = 'contentscript';
return composeSeries(
createNormalBundle({
+ buildTarget,
buildType,
browserPlatforms,
destFilepath: `${inpage}.js`,
- devMode,
entryFilepath: `./app/scripts/${inpage}.js`,
label: inpage,
ignoredFiles,
policyOnly,
shouldLintFenceFiles,
- testing,
version,
}),
createNormalBundle({
+ buildTarget,
buildType,
browserPlatforms,
destFilepath: `${contentscript}.js`,
- devMode,
entryFilepath: `./app/scripts/${contentscript}.js`,
label: contentscript,
ignoredFiles,
policyOnly,
shouldLintFenceFiles,
- testing,
version,
}),
);
}
}
-const postProcessServiceWorker = (
- mv3BrowserPlatforms,
- fileList,
+/**
+ * Create the bundle for the app initialization module used in manifest v3
+ * builds.
+ *
+ * This must be called after the "background" bundles have been created, so
+ * that the list of all background bundles can be injected into this bundle.
+ *
+ * @param {object} options - Build options.
+ * @param {boolean} options.applyLavaMoat - Whether the build should use
+ * LavaMoat at runtime or not.
+ * @param {string[]} options.browserPlatforms - A list of browser platforms to
+ * build bundles for.
+ * @param {BUILD_TARGETS} options.buildTarget - The current build target.
+ * @param {BuildType} options.buildType - The current build type (e.g. "main",
+ * "flask", etc.).
+ * @param {string[] | null} options.ignoredFiles - A list of files to exclude
+ * from the current build.
+ * @param {string[]} options.jsBundles - A list of JavaScript bundles to be
+ * injected into this bundle.
+ * @param {boolean} options.policyOnly - Whether to stop the build after
+ * generating the LavaMoat policy, skipping any writes to disk other than the
+ * LavaMoat policy itself.
+ * @param {boolean} options.shouldLintFenceFiles - Whether files with code
+ * fences should be linted after fences have been removed.
+ * @param {string} options.version - The current version of the extension.
+ * @returns {Function} A function that creates the set of bundles.
+ */
+async function createManifestV3AppInitializationBundle({
applyLavaMoat,
-) => {
- mv3BrowserPlatforms.forEach((browser) => {
- const appInitFile = `./dist/${browser}/app-init.js`;
- const fileContent = readFileSync('./app/scripts/app-init.js', 'utf8');
- const fileOutput = fileContent
- .replace('/** FILE NAMES */', fileList)
- .replace(
- 'const applyLavaMoat = true;',
- `const applyLavaMoat = ${applyLavaMoat};`,
- );
- writeFileSync(appInitFile, fileOutput);
- });
-};
-
-// Function generates app-init.js for browsers chrome, brave and opera.
-// It dynamically injects list of files generated in the build.
-async function bundleMV3AppInitialiser({
- jsBundles,
browserPlatforms,
+ buildTarget,
buildType,
- devMode,
ignoredFiles,
- testing,
+ jsBundles,
policyOnly,
shouldLintFenceFiles,
- applyLavaMoat,
+ version,
}) {
const label = 'app-init';
// TODO: remove this filter for firefox once MV3 is supported in it
const mv3BrowserPlatforms = browserPlatforms.filter(
(platform) => platform !== 'firefox',
);
- const fileList = jsBundles.reduce(
- (result, file) => `${result}'${file}',\n `,
- '',
- );
+
+ for (const filename of jsBundles) {
+ if (filename.includes(',')) {
+ throw new Error(
+ `Invalid filename "${filename}", not allowed to contain comma.`,
+ );
+ }
+ }
+
+ const extraEnvironmentVariables = {
+ APPLY_LAVAMOAT: applyLavaMoat,
+ FILE_NAMES: jsBundles.join(','),
+ };
await createNormalBundle({
browserPlatforms: mv3BrowserPlatforms,
+ buildTarget,
buildType,
destFilepath: 'app-init.js',
- devMode,
entryFilepath: './app/scripts/app-init.js',
+ extraEnvironmentVariables,
ignoredFiles,
label,
- testing,
policyOnly,
shouldLintFenceFiles,
+ version,
})();
- postProcessServiceWorker(mv3BrowserPlatforms, fileList, applyLavaMoat);
-
- let prevChromeFileContent;
- watch('./dist/chrome/app-init.js', () => {
- const chromeFileContent = readFileSync('./dist/chrome/app-init.js', 'utf8');
- if (chromeFileContent !== prevChromeFileContent) {
- prevChromeFileContent = chromeFileContent;
- postProcessServiceWorker(mv3BrowserPlatforms, fileList, applyLavaMoat);
- }
- });
+ // Code below is used to set statsMode to true when testing in MV3
+ // This is used to capture module initialisation stats using lavamoat.
+ if (isTestBuild(buildTarget)) {
+ const content = readFileSync('./dist/chrome/runtime-lavamoat.js', 'utf8');
+ const fileOutput = content.replace('statsMode = false', 'statsMode = true');
+ writeFileSync('./dist/chrome/runtime-lavamoat.js', fileOutput);
+ }
console.log(`Bundle end: service worker app-init.js`);
}
+/**
+ * Return a function that creates a set of factored bundles.
+ *
+ * For each entry point, a series of one or more bundles is created. These are
+ * split up roughly by size, to ensure no single bundle exceeds the maximum
+ * JavaScript file size imposed by Firefox.
+ *
+ * Modules that are common between all entry points are bundled separately, as
+ * a set of one or more "common" bundles.
+ *
+ * @param {object} options - Build options.
+ * @param {boolean} options.applyLavaMoat - Whether the build should use
+ * LavaMoat at runtime or not.
+ * @param {string[]} options.browserPlatforms - A list of browser platforms to
+ * build bundles for.
+ * @param {BUILD_TARGETS} options.buildTarget - The current build target.
+ * @param {BuildType} options.buildType - The current build type (e.g. "main",
+ * "flask", etc.).
+ * @param {string[]} options.entryFiles - A list of entry point file paths,
+ * relative to the repository root directory.
+ * @param {string[] | null} options.ignoredFiles - A list of files to exclude
+ * from the current build.
+ * @param {boolean} options.policyOnly - Whether to stop the build after
+ * generating the LavaMoat policy, skipping any writes to disk other than the
+ * LavaMoat policy itself.
+ * @param {boolean} options.shouldLintFenceFiles - Whether files with code
+ * fences should be linted after fences have been removed.
+ * @param {string} options.version - The current version of the extension.
+ * @returns {Function} A function that creates the set of bundles.
+ */
function createFactoredBuild({
applyLavaMoat,
browserPlatforms,
+ buildTarget,
buildType,
- devMode,
entryFiles,
ignoredFiles,
policyOnly,
shouldLintFenceFiles,
- testing,
version,
}) {
return async function () {
@@ -453,25 +515,23 @@ function createFactoredBuild({
const { bundlerOpts, events } = buildConfiguration;
// devMode options
- const reloadOnChange = Boolean(devMode);
- const minify = Boolean(devMode) === false;
+ const reloadOnChange = isDevBuild(buildTarget);
+ const minify = !isDevBuild(buildTarget);
- const envVars = getEnvironmentVariables({
+ const envVars = await getEnvironmentVariables({
+ buildTarget,
buildType,
- devMode,
- testing,
version,
});
setupBundlerDefaults(buildConfiguration, {
+ buildTarget,
buildType,
- devMode,
envVars,
ignoredFiles,
policyOnly,
minify,
reloadOnChange,
shouldLintFenceFiles,
- testing,
});
// set bundle entries
@@ -597,16 +657,16 @@ function createFactoredBuild({
...commonSet.values(),
...groupSet.values(),
].map((label) => `./${label}.js`);
- await bundleMV3AppInitialiser({
- jsBundles,
+ await createManifestV3AppInitializationBundle({
+ applyLavaMoat,
browserPlatforms,
+ buildTarget,
buildType,
- devMode,
ignoredFiles,
- testing,
+ jsBundles,
policyOnly,
shouldLintFenceFiles,
- applyLavaMoat,
+ version,
});
}
break;
@@ -630,23 +690,48 @@ function createFactoredBuild({
}
});
- await bundleIt(buildConfiguration, { reloadOnChange });
+ await createBundle(buildConfiguration, { reloadOnChange });
};
}
+/**
+ * Return a function that creates a single JavaScript bundle.
+ *
+ * @param {object} options - Build options.
+ * @param {string[]} options.browserPlatforms - A list of browser platforms to
+ * build the bundle for.
+ * @param {BUILD_TARGETS} options.buildTarget - The current build target.
+ * @param {BuildType} options.buildType - The current build type (e.g. "main",
+ * "flask", etc.).
+ * @param {string} options.destFilepath - The file path the bundle should be
+ * written to.
+ * @param {string[]} options.entryFilepath - The entry point file path,
+ * relative to the repository root directory.
+ * @param {Record} options.extraEnvironmentVariables - Extra
+ * environment variables to inject just into this bundle.
+ * @param {string[] | null} options.ignoredFiles - A list of files to exclude
+ * from the current build.
+ * @param {string} options.label - A label used to describe this bundle in any
+ * diagnostic messages.
+ * @param {boolean} options.policyOnly - Whether to stop the build after
+ * generating the LavaMoat policy, skipping any writes to disk other than the
+ * LavaMoat policy itself.
+ * @param {boolean} options.shouldLintFenceFiles - Whether files with code
+ * fences should be linted after fences have been removed.
+ * @param {string} options.version - The current version of the extension.
+ * @returns {Function} A function that creates the bundle.
+ */
function createNormalBundle({
browserPlatforms,
+ buildTarget,
buildType,
destFilepath,
- devMode,
entryFilepath,
- extraEntries = [],
+ extraEnvironmentVariables,
ignoredFiles,
label,
policyOnly,
- modulesToExpose,
shouldLintFenceFiles,
- testing,
version,
}) {
return async function () {
@@ -656,36 +741,30 @@ function createNormalBundle({
const { bundlerOpts, events } = buildConfiguration;
// devMode options
+ const devMode = isDevBuild(buildTarget);
const reloadOnChange = Boolean(devMode);
const minify = Boolean(devMode) === false;
- const envVars = getEnvironmentVariables({
- buildType,
- devMode,
- testing,
- version,
- });
+ const envVars = {
+ ...(await getEnvironmentVariables({
+ buildTarget,
+ buildType,
+ version,
+ })),
+ ...extraEnvironmentVariables,
+ };
setupBundlerDefaults(buildConfiguration, {
buildType,
- devMode,
envVars,
ignoredFiles,
policyOnly,
minify,
reloadOnChange,
shouldLintFenceFiles,
- testing,
});
// set bundle entries
- bundlerOpts.entries = [...extraEntries];
- if (entryFilepath) {
- bundlerOpts.entries.push(entryFilepath);
- }
-
- if (modulesToExpose) {
- bundlerOpts.require = bundlerOpts.require.concat(modulesToExpose);
- }
+ bundlerOpts.entries = [entryFilepath];
// instrument pipeline
events.on('configurePipeline', ({ pipeline }) => {
@@ -701,7 +780,7 @@ function createNormalBundle({
});
});
- await bundleIt(buildConfiguration, { reloadOnChange });
+ await createBundle(buildConfiguration, { reloadOnChange });
};
}
@@ -723,15 +802,14 @@ function createBuildConfiguration() {
function setupBundlerDefaults(
buildConfiguration,
{
+ buildTarget,
buildType,
- devMode,
envVars,
ignoredFiles,
policyOnly,
minify,
reloadOnChange,
shouldLintFenceFiles,
- testing,
},
) {
const { bundlerOpts } = buildConfiguration;
@@ -740,7 +818,7 @@ function setupBundlerDefaults(
Object.assign(bundlerOpts, {
// Source transforms
transform: [
- // Remove code that should be excluded from builds of the current type
+ // // Remove code that should be excluded from builds of the current type
createRemoveFencedCodeTransform(buildType, shouldLintFenceFiles),
// Transpile top-level code
[
@@ -754,13 +832,13 @@ function setupBundlerDefaults(
// Look for TypeScript files when walking the dependency tree
extensions,
// Use entryFilepath for moduleIds, easier to determine origin file
- fullPaths: devMode,
+ fullPaths: isDevBuild(buildTarget) || isTestBuild(buildTarget),
// For sourcemaps
debug: true,
});
- // Ensure react-devtools are not included in non-dev builds
- if (!devMode || testing) {
+ // Ensure react-devtools is only included in dev builds
+ if (buildTarget !== BUILD_TARGETS.DEV) {
bundlerOpts.manualIgnore.push('react-devtools');
bundlerOpts.manualIgnore.push('remote-redux-devtools');
}
@@ -786,7 +864,7 @@ function setupBundlerDefaults(
}
// Setup source maps
- setupSourcemaps(buildConfiguration, { devMode });
+ setupSourcemaps(buildConfiguration, { buildTarget });
}
}
@@ -840,7 +918,7 @@ function setupMinification(buildConfiguration) {
});
}
-function setupSourcemaps(buildConfiguration, { devMode }) {
+function setupSourcemaps(buildConfiguration, { buildTarget }) {
const { events } = buildConfiguration;
events.on('configurePipeline', ({ pipeline }) => {
pipeline.get('sourcemaps:init').push(sourcemaps.init({ loadMaps: true }));
@@ -849,14 +927,14 @@ function setupSourcemaps(buildConfiguration, { devMode }) {
// Use inline source maps for development due to Chrome DevTools bug
// https://bugs.chromium.org/p/chromium/issues/detail?id=931675
.push(
- devMode
+ isDevBuild(buildTarget)
? sourcemaps.write()
: sourcemaps.write('../sourcemaps', { addComment: false }),
);
});
}
-async function bundleIt(buildConfiguration, { reloadOnChange }) {
+async function createBundle(buildConfiguration, { reloadOnChange }) {
const { label, bundlerOpts, events } = buildConfiguration;
const bundler = browserify(bundlerOpts);
@@ -898,7 +976,7 @@ async function bundleIt(buildConfiguration, { reloadOnChange }) {
if (!reloadOnChange) {
bundleStream.on('error', (error) => {
console.error('Bundling failed! See details below.');
- console.error(error.stack || error);
+ logError(error);
process.exit(1);
});
}
@@ -916,55 +994,54 @@ async function bundleIt(buildConfiguration, { reloadOnChange }) {
}
}
-function getEnvironmentVariables({ buildType, devMode, testing, version }) {
- const environment = getEnvironment({ devMode, testing });
- if (environment === ENVIRONMENT.PRODUCTION && !process.env.SENTRY_DSN) {
- throw new Error('Missing SENTRY_DSN environment variable');
- }
+/**
+ * Get environment variables to inject in the current build.
+ *
+ * @param {object} options - Build options.
+ * @param {BUILD_TARGETS} options.buildTarget - The current build target.
+ * @param {BuildType} options.buildType - The current build type (e.g. "main",
+ * "flask", etc.).
+ * @param {string} options.version - The current version of the extension.
+ * @returns {object} A map of environment variables to inject.
+ */
+async function getEnvironmentVariables({ buildTarget, buildType, version }) {
+ const environment = getEnvironment({ buildTarget });
+ const config =
+ environment === ENVIRONMENT.PRODUCTION
+ ? await getProductionConfig(buildType)
+ : await getConfig();
+
+ const devMode = isDevBuild(buildTarget);
+ const testing = isTestBuild(buildTarget);
return {
+ COLLECTIBLES_V1: config.COLLECTIBLES_V1 === '1',
+ CONF: devMode ? config : {},
+ IN_TEST: testing,
+ INFURA_PROJECT_ID: getInfuraProjectId({
+ buildType,
+ config,
+ environment,
+ testing,
+ }),
METAMASK_DEBUG: devMode,
METAMASK_ENVIRONMENT: environment,
METAMASK_VERSION: version,
METAMASK_BUILD_TYPE: buildType,
NODE_ENV: devMode ? ENVIRONMENT.DEVELOPMENT : ENVIRONMENT.PRODUCTION,
- IN_TEST: testing,
- PHISHING_WARNING_PAGE_URL: getPhishingWarningPageUrl({ testing }),
- PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
- PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
- CONF: devMode ? metamaskrc : {},
- SENTRY_DSN: process.env.SENTRY_DSN,
- SENTRY_DSN_DEV: metamaskrc.SENTRY_DSN_DEV,
- INFURA_PROJECT_ID: getInfuraProjectId({ buildType, environment, testing }),
- SEGMENT_HOST: metamaskrc.SEGMENT_HOST,
- SEGMENT_WRITE_KEY: getSegmentWriteKey({ buildType, environment }),
- SWAPS_USE_DEV_APIS: process.env.SWAPS_USE_DEV_APIS === '1',
- ONBOARDING_V2: metamaskrc.ONBOARDING_V2 === '1',
- COLLECTIBLES_V1: metamaskrc.COLLECTIBLES_V1 === '1',
- TOKEN_DETECTION_V2: metamaskrc.TOKEN_DETECTION_V2 === '1',
- ADD_POPULAR_NETWORKS: metamaskrc.ADD_POPULAR_NETWORKS === '1',
+ ONBOARDING_V2: config.ONBOARDING_V2 === '1',
+ PHISHING_WARNING_PAGE_URL: getPhishingWarningPageUrl({ config, testing }),
+ PUBNUB_PUB_KEY: config.PUBNUB_PUB_KEY || '',
+ PUBNUB_SUB_KEY: config.PUBNUB_SUB_KEY || '',
+ SEGMENT_HOST: config.SEGMENT_HOST,
+ SEGMENT_WRITE_KEY: getSegmentWriteKey({ buildType, config, environment }),
+ SENTRY_DSN: config.SENTRY_DSN,
+ SENTRY_DSN_DEV: config.SENTRY_DSN_DEV,
+ SIWE_V1: config.SIWE_V1 === '1',
+ SWAPS_USE_DEV_APIS: config.SWAPS_USE_DEV_APIS === '1',
+ TOKEN_ALLOWANCE_IMPROVEMENTS: config.TOKEN_ALLOWANCE_IMPROVEMENTS === '1',
};
}
-function getEnvironment({ devMode, testing }) {
- // get environment slug
- if (devMode) {
- return ENVIRONMENT.DEVELOPMENT;
- } else if (testing) {
- return ENVIRONMENT.TESTING;
- } else if (process.env.CIRCLE_BRANCH === 'master') {
- return ENVIRONMENT.PRODUCTION;
- } else if (
- /^Version-v(\d+)[.](\d+)[.](\d+)/u.test(process.env.CIRCLE_BRANCH)
- ) {
- return ENVIRONMENT.RELEASE_CANDIDATE;
- } else if (process.env.CIRCLE_BRANCH === 'develop') {
- return ENVIRONMENT.STAGING;
- } else if (process.env.CIRCLE_PULL_REQUEST) {
- return ENVIRONMENT.PULL_REQUEST;
- }
- return ENVIRONMENT.OTHER;
-}
-
function renderHtmlFile({
htmlName,
groupSet,
diff --git a/development/build/styles.js b/development/build/styles.js
index 926609dff..47c5e5c66 100644
--- a/development/build/styles.js
+++ b/development/build/styles.js
@@ -73,7 +73,7 @@ async function buildScssPipeline(src, dest, devMode, rtl) {
// use our own compiler which runs sass in its own process
// in order to not pollute the intrinsics
// eslint-disable-next-line node/global-require
- sass.compiler = require('./sass-compiler.js');
+ sass.compiler = require('./sass-compiler');
}
await pump(
...[
diff --git a/development/build/task.js b/development/build/task.js
index 26df8bf85..328f65e64 100644
--- a/development/build/task.js
+++ b/development/build/task.js
@@ -15,6 +15,7 @@ module.exports = {
};
const { setupTaskDisplay } = require('./display');
+const { logError } = require('./utils');
async function runTask(taskName, { skipStats } = {}) {
if (!(taskName in tasks)) {
@@ -30,7 +31,7 @@ async function runTask(taskName, { skipStats } = {}) {
console.error(
`MetaMask build: Encountered an error while running task "${taskName}".`,
);
- console.error(err);
+ logError(err);
process.exit(1);
}
taskEvents.emit('complete');
diff --git a/development/build/transforms/remove-fenced-code.js b/development/build/transforms/remove-fenced-code.js
index 90d49972e..6a95adf17 100644
--- a/development/build/transforms/remove-fenced-code.js
+++ b/development/build/transforms/remove-fenced-code.js
@@ -320,9 +320,8 @@ function removeFencedCode(filePath, typeOfCurrentBuild, fileContent) {
let currentCommand;
for (let i = 0; i < parsedDirectives.length; i++) {
- const { line, indices, terminus, command, parameters } = parsedDirectives[
- i
- ];
+ const { line, indices, terminus, command, parameters } =
+ parsedDirectives[i];
if (i % 2 === 0) {
if (terminus !== DirectiveTerminuses.BEGIN) {
throw new Error(
@@ -368,9 +367,8 @@ function removeFencedCode(filePath, typeOfCurrentBuild, fileContent) {
}
// Forbid empty fences
- const { line: previousLine, indices: previousIndices } = parsedDirectives[
- i - 1
- ];
+ const { line: previousLine, indices: previousIndices } =
+ parsedDirectives[i - 1];
if (fileContent.substring(previousIndices[1], indices[0]).trim() === '') {
throw new Error(
`Empty fence found in file "${filePath}":\n${previousLine}\n${line}\n`,
diff --git a/development/build/transforms/remove-fenced-code.test.js b/development/build/transforms/remove-fenced-code.test.js
index 3be3e401a..d185ee6f5 100644
--- a/development/build/transforms/remove-fenced-code.test.js
+++ b/development/build/transforms/remove-fenced-code.test.js
@@ -289,9 +289,8 @@ describe('build/transforms/remove-fenced-code', () => {
),
).toStrictEqual([ignoredLine, true]);
- const modifiedInputWithoutFences = testData.validInputs.withoutFences.concat(
- ignoredLine,
- );
+ const modifiedInputWithoutFences =
+ testData.validInputs.withoutFences.concat(ignoredLine);
// These inputs will not be transformed
expect(
diff --git a/development/build/transforms/utils.js b/development/build/transforms/utils.js
index fb7f49c32..0f8172cd3 100644
--- a/development/build/transforms/utils.js
+++ b/development/build/transforms/utils.js
@@ -1,5 +1,5 @@
const { ESLint } = require('eslint');
-const eslintrc = require('../../../.eslintrc.js');
+const eslintrc = require('../../../.eslintrc');
eslintrc.overrides.forEach((override) => {
const rules = override.rules ?? {};
diff --git a/development/build/utils.js b/development/build/utils.js
index 9ca4f5c17..f783c6d40 100644
--- a/development/build/utils.js
+++ b/development/build/utils.js
@@ -1,5 +1,30 @@
const semver = require('semver');
const { BuildType } = require('../lib/build-type');
+const { BUILD_TARGETS, ENVIRONMENT } = require('./constants');
+
+/**
+ * Returns whether the current build is a development build or not.
+ *
+ * @param {BUILD_TARGETS} buildTarget - The current build target.
+ * @returns Whether the current build is a development build.
+ */
+function isDevBuild(buildTarget) {
+ return (
+ buildTarget === BUILD_TARGETS.DEV || buildTarget === BUILD_TARGETS.TEST_DEV
+ );
+}
+
+/**
+ * Returns whether the current build is an e2e test build or not.
+ *
+ * @param {BUILD_TARGETS} buildTarget - The current build target.
+ * @returns Whether the current build is an e2e test build.
+ */
+function isTestBuild(buildTarget) {
+ return (
+ buildTarget === BUILD_TARGETS.TEST || buildTarget === BUILD_TARGETS.TEST_DEV
+ );
+}
/**
* Map the current version to a format that is compatible with each browser.
@@ -51,6 +76,51 @@ function getBrowserVersionMap(platforms, version) {
}, {});
}
+/**
+ * Get the environment of the current build.
+ *
+ * @param {object} options - Build options.
+ * @param {BUILD_TARGETS} options.buildTarget - The target of the current build.
+ * @returns {ENVIRONMENT} The current build environment.
+ */
+function getEnvironment({ buildTarget }) {
+ // get environment slug
+ if (buildTarget === BUILD_TARGETS.PROD) {
+ return ENVIRONMENT.PRODUCTION;
+ } else if (isDevBuild(buildTarget)) {
+ return ENVIRONMENT.DEVELOPMENT;
+ } else if (isTestBuild(buildTarget)) {
+ return ENVIRONMENT.TESTING;
+ } else if (
+ /^Version-v(\d+)[.](\d+)[.](\d+)/u.test(process.env.CIRCLE_BRANCH)
+ ) {
+ return ENVIRONMENT.RELEASE_CANDIDATE;
+ } else if (process.env.CIRCLE_BRANCH === 'develop') {
+ return ENVIRONMENT.STAGING;
+ } else if (process.env.CIRCLE_PULL_REQUEST) {
+ return ENVIRONMENT.PULL_REQUEST;
+ }
+ return ENVIRONMENT.OTHER;
+}
+
+/**
+ * Log an error to the console.
+ *
+ * This function includes a workaround for a SES bug that results in errors
+ * being printed to the console as `{}`. The workaround is to print the stack
+ * instead, which does work correctly.
+ *
+ * @see {@link https://github.com/endojs/endo/issues/944}
+ * @param {Error} error - The error to print
+ */
+function logError(error) {
+ console.error(error.stack || error);
+}
+
module.exports = {
getBrowserVersionMap,
+ getEnvironment,
+ isDevBuild,
+ isTestBuild,
+ logError,
};
diff --git a/development/charts/flamegraph/chart/index.html b/development/charts/flamegraph/chart/index.html
new file mode 100644
index 000000000..7afe9f9d0
--- /dev/null
+++ b/development/charts/flamegraph/chart/index.html
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Performance Measurements
+
+
+
+
+
+
+
+
+
d3-flame-graph
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/development/charts/flamegraph/lib/d3-flamegraph-tooltip.js b/development/charts/flamegraph/lib/d3-flamegraph-tooltip.js
new file mode 100644
index 000000000..cc042a0f2
--- /dev/null
+++ b/development/charts/flamegraph/lib/d3-flamegraph-tooltip.js
@@ -0,0 +1,3117 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+ if(typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory();
+ else if(typeof define === 'function' && define.amd)
+ define([], factory);
+ else if(typeof exports === 'object')
+ exports["flamegraph"] = factory();
+ else
+ root["flamegraph"] = root["flamegraph"] || {}, root["flamegraph"]["tooltip"] = factory();
+})(self, function() {
+return /******/ (() => { // webpackBootstrap
+/******/ "use strict";
+/******/ // The require scope
+/******/ var __webpack_require__ = {};
+/******/
+/************************************************************************/
+/******/ /* webpack/runtime/define property getters */
+/******/ (() => {
+/******/ // define getter functions for harmony exports
+/******/ __webpack_require__.d = (exports, definition) => {
+/******/ for(var key in definition) {
+/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
+/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
+/******/ }
+/******/ }
+/******/ };
+/******/ })();
+/******/
+/******/ /* webpack/runtime/hasOwnProperty shorthand */
+/******/ (() => {
+/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
+/******/ })();
+/******/
+/******/ /* webpack/runtime/make namespace object */
+/******/ (() => {
+/******/ // define __esModule on exports
+/******/ __webpack_require__.r = (exports) => {
+/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ }
+/******/ Object.defineProperty(exports, '__esModule', { value: true });
+/******/ };
+/******/ })();
+/******/
+/************************************************************************/
+var __webpack_exports__ = {};
+// ESM COMPAT FLAG
+__webpack_require__.r(__webpack_exports__);
+
+// EXPORTS
+__webpack_require__.d(__webpack_exports__, {
+ "defaultFlamegraphTooltip": () => (/* binding */ defaultFlamegraphTooltip)
+});
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selector.js
+function none() {}
+
+/* harmony default export */ function selector(selector) {
+ return selector == null ? none : function() {
+ return this.querySelector(selector);
+ };
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/select.js
+
+
+
+/* harmony default export */ function selection_select(select) {
+ if (typeof select !== "function") select = selector(select);
+
+ for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
+ if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
+ if ("__data__" in node) subnode.__data__ = node.__data__;
+ subgroup[i] = subnode;
+ }
+ }
+ }
+
+ return new Selection(subgroups, this._parents);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/array.js
+// Given something array like (or null), returns something that is strictly an
+// array. This is used to ensure that array-like objects passed to d3.selectAll
+// or selection.selectAll are converted into proper arrays when creating a
+// selection; we don’t ever want to create a selection backed by a live
+// HTMLCollection or NodeList. However, note that selection.selectAll will use a
+// static NodeList as a group, since it safely derived from querySelectorAll.
+function array(x) {
+ return x == null ? [] : Array.isArray(x) ? x : Array.from(x);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selectorAll.js
+function empty() {
+ return [];
+}
+
+/* harmony default export */ function selectorAll(selector) {
+ return selector == null ? empty : function() {
+ return this.querySelectorAll(selector);
+ };
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectAll.js
+
+
+
+
+function arrayAll(select) {
+ return function() {
+ return array(select.apply(this, arguments));
+ };
+}
+
+/* harmony default export */ function selectAll(select) {
+ if (typeof select === "function") select = arrayAll(select);
+ else select = selectorAll(select);
+
+ for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
+ if (node = group[i]) {
+ subgroups.push(select.call(node, node.__data__, i, group));
+ parents.push(node);
+ }
+ }
+ }
+
+ return new Selection(subgroups, parents);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/matcher.js
+/* harmony default export */ function matcher(selector) {
+ return function() {
+ return this.matches(selector);
+ };
+}
+
+function childMatcher(selector) {
+ return function(node) {
+ return node.matches(selector);
+ };
+}
+
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChild.js
+
+
+var find = Array.prototype.find;
+
+function childFind(match) {
+ return function() {
+ return find.call(this.children, match);
+ };
+}
+
+function childFirst() {
+ return this.firstElementChild;
+}
+
+/* harmony default export */ function selectChild(match) {
+ return this.select(match == null ? childFirst
+ : childFind(typeof match === "function" ? match : childMatcher(match)));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChildren.js
+
+
+var filter = Array.prototype.filter;
+
+function children() {
+ return Array.from(this.children);
+}
+
+function childrenFilter(match) {
+ return function() {
+ return filter.call(this.children, match);
+ };
+}
+
+/* harmony default export */ function selectChildren(match) {
+ return this.selectAll(match == null ? children
+ : childrenFilter(typeof match === "function" ? match : childMatcher(match)));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/filter.js
+
+
+
+/* harmony default export */ function selection_filter(match) {
+ if (typeof match !== "function") match = matcher(match);
+
+ for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
+ if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
+ subgroup.push(node);
+ }
+ }
+ }
+
+ return new Selection(subgroups, this._parents);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sparse.js
+/* harmony default export */ function sparse(update) {
+ return new Array(update.length);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/enter.js
+
+
+
+/* harmony default export */ function enter() {
+ return new Selection(this._enter || this._groups.map(sparse), this._parents);
+}
+
+function EnterNode(parent, datum) {
+ this.ownerDocument = parent.ownerDocument;
+ this.namespaceURI = parent.namespaceURI;
+ this._next = null;
+ this._parent = parent;
+ this.__data__ = datum;
+}
+
+EnterNode.prototype = {
+ constructor: EnterNode,
+ appendChild: function(child) { return this._parent.insertBefore(child, this._next); },
+ insertBefore: function(child, next) { return this._parent.insertBefore(child, next); },
+ querySelector: function(selector) { return this._parent.querySelector(selector); },
+ querySelectorAll: function(selector) { return this._parent.querySelectorAll(selector); }
+};
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/constant.js
+/* harmony default export */ function src_constant(x) {
+ return function() {
+ return x;
+ };
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/data.js
+
+
+
+
+function bindIndex(parent, group, enter, update, exit, data) {
+ var i = 0,
+ node,
+ groupLength = group.length,
+ dataLength = data.length;
+
+ // Put any non-null nodes that fit into update.
+ // Put any null nodes into enter.
+ // Put any remaining data into enter.
+ for (; i < dataLength; ++i) {
+ if (node = group[i]) {
+ node.__data__ = data[i];
+ update[i] = node;
+ } else {
+ enter[i] = new EnterNode(parent, data[i]);
+ }
+ }
+
+ // Put any non-null nodes that don’t fit into exit.
+ for (; i < groupLength; ++i) {
+ if (node = group[i]) {
+ exit[i] = node;
+ }
+ }
+}
+
+function bindKey(parent, group, enter, update, exit, data, key) {
+ var i,
+ node,
+ nodeByKeyValue = new Map,
+ groupLength = group.length,
+ dataLength = data.length,
+ keyValues = new Array(groupLength),
+ keyValue;
+
+ // Compute the key for each node.
+ // If multiple nodes have the same key, the duplicates are added to exit.
+ for (i = 0; i < groupLength; ++i) {
+ if (node = group[i]) {
+ keyValues[i] = keyValue = key.call(node, node.__data__, i, group) + "";
+ if (nodeByKeyValue.has(keyValue)) {
+ exit[i] = node;
+ } else {
+ nodeByKeyValue.set(keyValue, node);
+ }
+ }
+ }
+
+ // Compute the key for each datum.
+ // If there a node associated with this key, join and add it to update.
+ // If there is not (or the key is a duplicate), add it to enter.
+ for (i = 0; i < dataLength; ++i) {
+ keyValue = key.call(parent, data[i], i, data) + "";
+ if (node = nodeByKeyValue.get(keyValue)) {
+ update[i] = node;
+ node.__data__ = data[i];
+ nodeByKeyValue.delete(keyValue);
+ } else {
+ enter[i] = new EnterNode(parent, data[i]);
+ }
+ }
+
+ // Add any remaining nodes that were not bound to data to exit.
+ for (i = 0; i < groupLength; ++i) {
+ if ((node = group[i]) && (nodeByKeyValue.get(keyValues[i]) === node)) {
+ exit[i] = node;
+ }
+ }
+}
+
+function datum(node) {
+ return node.__data__;
+}
+
+/* harmony default export */ function data(value, key) {
+ if (!arguments.length) return Array.from(this, datum);
+
+ var bind = key ? bindKey : bindIndex,
+ parents = this._parents,
+ groups = this._groups;
+
+ if (typeof value !== "function") value = src_constant(value);
+
+ for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
+ var parent = parents[j],
+ group = groups[j],
+ groupLength = group.length,
+ data = arraylike(value.call(parent, parent && parent.__data__, j, parents)),
+ dataLength = data.length,
+ enterGroup = enter[j] = new Array(dataLength),
+ updateGroup = update[j] = new Array(dataLength),
+ exitGroup = exit[j] = new Array(groupLength);
+
+ bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);
+
+ // Now connect the enter nodes to their following update node, such that
+ // appendChild can insert the materialized enter node before this node,
+ // rather than at the end of the parent node.
+ for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
+ if (previous = enterGroup[i0]) {
+ if (i0 >= i1) i1 = i0 + 1;
+ while (!(next = updateGroup[i1]) && ++i1 < dataLength);
+ previous._next = next || null;
+ }
+ }
+ }
+
+ update = new Selection(update, parents);
+ update._enter = enter;
+ update._exit = exit;
+ return update;
+}
+
+// Given some data, this returns an array-like view of it: an object that
+// exposes a length property and allows numeric indexing. Note that unlike
+// selectAll, this isn’t worried about “live” collections because the resulting
+// array will only be used briefly while data is being bound. (It is possible to
+// cause the data to change while iterating by using a key function, but please
+// don’t; we’d rather avoid a gratuitous copy.)
+function arraylike(data) {
+ return typeof data === "object" && "length" in data
+ ? data // Array, TypedArray, NodeList, array-like
+ : Array.from(data); // Map, Set, iterable, string, or anything else
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/exit.js
+
+
+
+/* harmony default export */ function exit() {
+ return new Selection(this._exit || this._groups.map(sparse), this._parents);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/join.js
+/* harmony default export */ function join(onenter, onupdate, onexit) {
+ var enter = this.enter(), update = this, exit = this.exit();
+ if (typeof onenter === "function") {
+ enter = onenter(enter);
+ if (enter) enter = enter.selection();
+ } else {
+ enter = enter.append(onenter + "");
+ }
+ if (onupdate != null) {
+ update = onupdate(update);
+ if (update) update = update.selection();
+ }
+ if (onexit == null) exit.remove(); else onexit(exit);
+ return enter && update ? enter.merge(update).order() : update;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/merge.js
+
+
+/* harmony default export */ function merge(context) {
+ var selection = context.selection ? context.selection() : context;
+
+ for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {
+ for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {
+ if (node = group0[i] || group1[i]) {
+ merge[i] = node;
+ }
+ }
+ }
+
+ for (; j < m0; ++j) {
+ merges[j] = groups0[j];
+ }
+
+ return new Selection(merges, this._parents);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/order.js
+/* harmony default export */ function order() {
+
+ for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) {
+ for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) {
+ if (node = group[i]) {
+ if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next);
+ next = node;
+ }
+ }
+ }
+
+ return this;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sort.js
+
+
+/* harmony default export */ function sort(compare) {
+ if (!compare) compare = ascending;
+
+ function compareNode(a, b) {
+ return a && b ? compare(a.__data__, b.__data__) : !a - !b;
+ }
+
+ for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) {
+ if (node = group[i]) {
+ sortgroup[i] = node;
+ }
+ }
+ sortgroup.sort(compareNode);
+ }
+
+ return new Selection(sortgroups, this._parents).order();
+}
+
+function ascending(a, b) {
+ return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/call.js
+/* harmony default export */ function call() {
+ var callback = arguments[0];
+ arguments[0] = this;
+ callback.apply(null, arguments);
+ return this;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/nodes.js
+/* harmony default export */ function nodes() {
+ return Array.from(this);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/node.js
+/* harmony default export */ function node() {
+
+ for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
+ for (var group = groups[j], i = 0, n = group.length; i < n; ++i) {
+ var node = group[i];
+ if (node) return node;
+ }
+ }
+
+ return null;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/size.js
+/* harmony default export */ function size() {
+ let size = 0;
+ for (const node of this) ++size; // eslint-disable-line no-unused-vars
+ return size;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/empty.js
+/* harmony default export */ function selection_empty() {
+ return !this.node();
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/each.js
+/* harmony default export */ function each(callback) {
+
+ for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
+ for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
+ if (node = group[i]) callback.call(node, node.__data__, i, group);
+ }
+ }
+
+ return this;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/namespaces.js
+var xhtml = "http://www.w3.org/1999/xhtml";
+
+/* harmony default export */ const namespaces = ({
+ svg: "http://www.w3.org/2000/svg",
+ xhtml: xhtml,
+ xlink: "http://www.w3.org/1999/xlink",
+ xml: "http://www.w3.org/XML/1998/namespace",
+ xmlns: "http://www.w3.org/2000/xmlns/"
+});
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/namespace.js
+
+
+/* harmony default export */ function namespace(name) {
+ var prefix = name += "", i = prefix.indexOf(":");
+ if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
+ return namespaces.hasOwnProperty(prefix) ? {space: namespaces[prefix], local: name} : name; // eslint-disable-line no-prototype-builtins
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/attr.js
+
+
+function attrRemove(name) {
+ return function() {
+ this.removeAttribute(name);
+ };
+}
+
+function attrRemoveNS(fullname) {
+ return function() {
+ this.removeAttributeNS(fullname.space, fullname.local);
+ };
+}
+
+function attrConstant(name, value) {
+ return function() {
+ this.setAttribute(name, value);
+ };
+}
+
+function attrConstantNS(fullname, value) {
+ return function() {
+ this.setAttributeNS(fullname.space, fullname.local, value);
+ };
+}
+
+function attrFunction(name, value) {
+ return function() {
+ var v = value.apply(this, arguments);
+ if (v == null) this.removeAttribute(name);
+ else this.setAttribute(name, v);
+ };
+}
+
+function attrFunctionNS(fullname, value) {
+ return function() {
+ var v = value.apply(this, arguments);
+ if (v == null) this.removeAttributeNS(fullname.space, fullname.local);
+ else this.setAttributeNS(fullname.space, fullname.local, v);
+ };
+}
+
+/* harmony default export */ function attr(name, value) {
+ var fullname = namespace(name);
+
+ if (arguments.length < 2) {
+ var node = this.node();
+ return fullname.local
+ ? node.getAttributeNS(fullname.space, fullname.local)
+ : node.getAttribute(fullname);
+ }
+
+ return this.each((value == null
+ ? (fullname.local ? attrRemoveNS : attrRemove) : (typeof value === "function"
+ ? (fullname.local ? attrFunctionNS : attrFunction)
+ : (fullname.local ? attrConstantNS : attrConstant)))(fullname, value));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/window.js
+/* harmony default export */ function src_window(node) {
+ return (node.ownerDocument && node.ownerDocument.defaultView) // node is a Node
+ || (node.document && node) // node is a Window
+ || node.defaultView; // node is a Document
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/style.js
+
+
+function styleRemove(name) {
+ return function() {
+ this.style.removeProperty(name);
+ };
+}
+
+function styleConstant(name, value, priority) {
+ return function() {
+ this.style.setProperty(name, value, priority);
+ };
+}
+
+function styleFunction(name, value, priority) {
+ return function() {
+ var v = value.apply(this, arguments);
+ if (v == null) this.style.removeProperty(name);
+ else this.style.setProperty(name, v, priority);
+ };
+}
+
+/* harmony default export */ function style(name, value, priority) {
+ return arguments.length > 1
+ ? this.each((value == null
+ ? styleRemove : typeof value === "function"
+ ? styleFunction
+ : styleConstant)(name, value, priority == null ? "" : priority))
+ : styleValue(this.node(), name);
+}
+
+function styleValue(node, name) {
+ return node.style.getPropertyValue(name)
+ || src_window(node).getComputedStyle(node, null).getPropertyValue(name);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/property.js
+function propertyRemove(name) {
+ return function() {
+ delete this[name];
+ };
+}
+
+function propertyConstant(name, value) {
+ return function() {
+ this[name] = value;
+ };
+}
+
+function propertyFunction(name, value) {
+ return function() {
+ var v = value.apply(this, arguments);
+ if (v == null) delete this[name];
+ else this[name] = v;
+ };
+}
+
+/* harmony default export */ function property(name, value) {
+ return arguments.length > 1
+ ? this.each((value == null
+ ? propertyRemove : typeof value === "function"
+ ? propertyFunction
+ : propertyConstant)(name, value))
+ : this.node()[name];
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/classed.js
+function classArray(string) {
+ return string.trim().split(/^|\s+/);
+}
+
+function classList(node) {
+ return node.classList || new ClassList(node);
+}
+
+function ClassList(node) {
+ this._node = node;
+ this._names = classArray(node.getAttribute("class") || "");
+}
+
+ClassList.prototype = {
+ add: function(name) {
+ var i = this._names.indexOf(name);
+ if (i < 0) {
+ this._names.push(name);
+ this._node.setAttribute("class", this._names.join(" "));
+ }
+ },
+ remove: function(name) {
+ var i = this._names.indexOf(name);
+ if (i >= 0) {
+ this._names.splice(i, 1);
+ this._node.setAttribute("class", this._names.join(" "));
+ }
+ },
+ contains: function(name) {
+ return this._names.indexOf(name) >= 0;
+ }
+};
+
+function classedAdd(node, names) {
+ var list = classList(node), i = -1, n = names.length;
+ while (++i < n) list.add(names[i]);
+}
+
+function classedRemove(node, names) {
+ var list = classList(node), i = -1, n = names.length;
+ while (++i < n) list.remove(names[i]);
+}
+
+function classedTrue(names) {
+ return function() {
+ classedAdd(this, names);
+ };
+}
+
+function classedFalse(names) {
+ return function() {
+ classedRemove(this, names);
+ };
+}
+
+function classedFunction(names, value) {
+ return function() {
+ (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names);
+ };
+}
+
+/* harmony default export */ function classed(name, value) {
+ var names = classArray(name + "");
+
+ if (arguments.length < 2) {
+ var list = classList(this.node()), i = -1, n = names.length;
+ while (++i < n) if (!list.contains(names[i])) return false;
+ return true;
+ }
+
+ return this.each((typeof value === "function"
+ ? classedFunction : value
+ ? classedTrue
+ : classedFalse)(names, value));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/text.js
+function textRemove() {
+ this.textContent = "";
+}
+
+function textConstant(value) {
+ return function() {
+ this.textContent = value;
+ };
+}
+
+function textFunction(value) {
+ return function() {
+ var v = value.apply(this, arguments);
+ this.textContent = v == null ? "" : v;
+ };
+}
+
+/* harmony default export */ function selection_text(value) {
+ return arguments.length
+ ? this.each(value == null
+ ? textRemove : (typeof value === "function"
+ ? textFunction
+ : textConstant)(value))
+ : this.node().textContent;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/html.js
+function htmlRemove() {
+ this.innerHTML = "";
+}
+
+function htmlConstant(value) {
+ return function() {
+ this.innerHTML = value;
+ };
+}
+
+function htmlFunction(value) {
+ return function() {
+ var v = value.apply(this, arguments);
+ this.innerHTML = v == null ? "" : v;
+ };
+}
+
+/* harmony default export */ function html(value) {
+ return arguments.length
+ ? this.each(value == null
+ ? htmlRemove : (typeof value === "function"
+ ? htmlFunction
+ : htmlConstant)(value))
+ : this.node().innerHTML;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/raise.js
+function raise() {
+ if (this.nextSibling) this.parentNode.appendChild(this);
+}
+
+/* harmony default export */ function selection_raise() {
+ return this.each(raise);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/lower.js
+function lower() {
+ if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild);
+}
+
+/* harmony default export */ function selection_lower() {
+ return this.each(lower);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/creator.js
+
+
+
+function creatorInherit(name) {
+ return function() {
+ var document = this.ownerDocument,
+ uri = this.namespaceURI;
+ return uri === xhtml && document.documentElement.namespaceURI === xhtml
+ ? document.createElement(name)
+ : document.createElementNS(uri, name);
+ };
+}
+
+function creatorFixed(fullname) {
+ return function() {
+ return this.ownerDocument.createElementNS(fullname.space, fullname.local);
+ };
+}
+
+/* harmony default export */ function creator(name) {
+ var fullname = namespace(name);
+ return (fullname.local
+ ? creatorFixed
+ : creatorInherit)(fullname);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/append.js
+
+
+/* harmony default export */ function append(name) {
+ var create = typeof name === "function" ? name : creator(name);
+ return this.select(function() {
+ return this.appendChild(create.apply(this, arguments));
+ });
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/insert.js
+
+
+
+function constantNull() {
+ return null;
+}
+
+/* harmony default export */ function insert(name, before) {
+ var create = typeof name === "function" ? name : creator(name),
+ select = before == null ? constantNull : typeof before === "function" ? before : selector(before);
+ return this.select(function() {
+ return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null);
+ });
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/remove.js
+function remove() {
+ var parent = this.parentNode;
+ if (parent) parent.removeChild(this);
+}
+
+/* harmony default export */ function selection_remove() {
+ return this.each(remove);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/clone.js
+function selection_cloneShallow() {
+ var clone = this.cloneNode(false), parent = this.parentNode;
+ return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
+}
+
+function selection_cloneDeep() {
+ var clone = this.cloneNode(true), parent = this.parentNode;
+ return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
+}
+
+/* harmony default export */ function clone(deep) {
+ return this.select(deep ? selection_cloneDeep : selection_cloneShallow);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/datum.js
+/* harmony default export */ function selection_datum(value) {
+ return arguments.length
+ ? this.property("__data__", value)
+ : this.node().__data__;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/on.js
+function contextListener(listener) {
+ return function(event) {
+ listener.call(this, event, this.__data__);
+ };
+}
+
+function parseTypenames(typenames) {
+ return typenames.trim().split(/^|\s+/).map(function(t) {
+ var name = "", i = t.indexOf(".");
+ if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
+ return {type: t, name: name};
+ });
+}
+
+function onRemove(typename) {
+ return function() {
+ var on = this.__on;
+ if (!on) return;
+ for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {
+ if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) {
+ this.removeEventListener(o.type, o.listener, o.options);
+ } else {
+ on[++i] = o;
+ }
+ }
+ if (++i) on.length = i;
+ else delete this.__on;
+ };
+}
+
+function onAdd(typename, value, options) {
+ return function() {
+ var on = this.__on, o, listener = contextListener(value);
+ if (on) for (var j = 0, m = on.length; j < m; ++j) {
+ if ((o = on[j]).type === typename.type && o.name === typename.name) {
+ this.removeEventListener(o.type, o.listener, o.options);
+ this.addEventListener(o.type, o.listener = listener, o.options = options);
+ o.value = value;
+ return;
+ }
+ }
+ this.addEventListener(typename.type, listener, options);
+ o = {type: typename.type, name: typename.name, value: value, listener: listener, options: options};
+ if (!on) this.__on = [o];
+ else on.push(o);
+ };
+}
+
+/* harmony default export */ function on(typename, value, options) {
+ var typenames = parseTypenames(typename + ""), i, n = typenames.length, t;
+
+ if (arguments.length < 2) {
+ var on = this.node().__on;
+ if (on) for (var j = 0, m = on.length, o; j < m; ++j) {
+ for (i = 0, o = on[j]; i < n; ++i) {
+ if ((t = typenames[i]).type === o.type && t.name === o.name) {
+ return o.value;
+ }
+ }
+ }
+ return;
+ }
+
+ on = value ? onAdd : onRemove;
+ for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options));
+ return this;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/dispatch.js
+
+
+function dispatchEvent(node, type, params) {
+ var window = src_window(node),
+ event = window.CustomEvent;
+
+ if (typeof event === "function") {
+ event = new event(type, params);
+ } else {
+ event = window.document.createEvent("Event");
+ if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail;
+ else event.initEvent(type, false, false);
+ }
+
+ node.dispatchEvent(event);
+}
+
+function dispatchConstant(type, params) {
+ return function() {
+ return dispatchEvent(this, type, params);
+ };
+}
+
+function dispatchFunction(type, params) {
+ return function() {
+ return dispatchEvent(this, type, params.apply(this, arguments));
+ };
+}
+
+/* harmony default export */ function dispatch(type, params) {
+ return this.each((typeof params === "function"
+ ? dispatchFunction
+ : dispatchConstant)(type, params));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/iterator.js
+/* harmony default export */ function* iterator() {
+ for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
+ for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
+ if (node = group[i]) yield node;
+ }
+ }
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/index.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+var root = [null];
+
+function Selection(groups, parents) {
+ this._groups = groups;
+ this._parents = parents;
+}
+
+function selection() {
+ return new Selection([[document.documentElement]], root);
+}
+
+function selection_selection() {
+ return this;
+}
+
+Selection.prototype = selection.prototype = {
+ constructor: Selection,
+ select: selection_select,
+ selectAll: selectAll,
+ selectChild: selectChild,
+ selectChildren: selectChildren,
+ filter: selection_filter,
+ data: data,
+ enter: enter,
+ exit: exit,
+ join: join,
+ merge: merge,
+ selection: selection_selection,
+ order: order,
+ sort: sort,
+ call: call,
+ nodes: nodes,
+ node: node,
+ size: size,
+ empty: selection_empty,
+ each: each,
+ attr: attr,
+ style: style,
+ property: property,
+ classed: classed,
+ text: selection_text,
+ html: html,
+ raise: selection_raise,
+ lower: selection_lower,
+ append: append,
+ insert: insert,
+ remove: selection_remove,
+ clone: clone,
+ datum: selection_datum,
+ on: on,
+ dispatch: dispatch,
+ [Symbol.iterator]: iterator
+};
+
+/* harmony default export */ const src_selection = (selection);
+
+;// CONCATENATED MODULE: ../node_modules/d3-selection/src/select.js
+
+
+/* harmony default export */ function src_select(selector) {
+ return typeof selector === "string"
+ ? new Selection([[document.querySelector(selector)]], [document.documentElement])
+ : new Selection([[selector]], root);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-dispatch/src/dispatch.js
+var noop = {value: () => {}};
+
+function dispatch_dispatch() {
+ for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
+ if (!(t = arguments[i] + "") || (t in _) || /[\s.]/.test(t)) throw new Error("illegal type: " + t);
+ _[t] = [];
+ }
+ return new Dispatch(_);
+}
+
+function Dispatch(_) {
+ this._ = _;
+}
+
+function dispatch_parseTypenames(typenames, types) {
+ return typenames.trim().split(/^|\s+/).map(function(t) {
+ var name = "", i = t.indexOf(".");
+ if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
+ if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
+ return {type: t, name: name};
+ });
+}
+
+Dispatch.prototype = dispatch_dispatch.prototype = {
+ constructor: Dispatch,
+ on: function(typename, callback) {
+ var _ = this._,
+ T = dispatch_parseTypenames(typename + "", _),
+ t,
+ i = -1,
+ n = T.length;
+
+ // If no callback was specified, return the callback of the given type and name.
+ if (arguments.length < 2) {
+ while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;
+ return;
+ }
+
+ // If a type was specified, set the callback for the given type and name.
+ // Otherwise, if a null callback was specified, remove callbacks of the given name.
+ if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);
+ while (++i < n) {
+ if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);
+ else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null);
+ }
+
+ return this;
+ },
+ copy: function() {
+ var copy = {}, _ = this._;
+ for (var t in _) copy[t] = _[t].slice();
+ return new Dispatch(copy);
+ },
+ call: function(type, that) {
+ if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
+ if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
+ for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
+ },
+ apply: function(type, that, args) {
+ if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
+ for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
+ }
+};
+
+function get(type, name) {
+ for (var i = 0, n = type.length, c; i < n; ++i) {
+ if ((c = type[i]).name === name) {
+ return c.value;
+ }
+ }
+}
+
+function set(type, name, callback) {
+ for (var i = 0, n = type.length; i < n; ++i) {
+ if (type[i].name === name) {
+ type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1));
+ break;
+ }
+ }
+ if (callback != null) type.push({name: name, value: callback});
+ return type;
+}
+
+/* harmony default export */ const src_dispatch = (dispatch_dispatch);
+
+;// CONCATENATED MODULE: ../node_modules/d3-timer/src/timer.js
+var timer_frame = 0, // is an animation frame pending?
+ timeout = 0, // is a timeout pending?
+ interval = 0, // are any timers active?
+ pokeDelay = 1000, // how frequently we check for clock skew
+ taskHead,
+ taskTail,
+ clockLast = 0,
+ clockNow = 0,
+ clockSkew = 0,
+ clock = typeof performance === "object" && performance.now ? performance : Date,
+ setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); };
+
+function now() {
+ return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew);
+}
+
+function clearNow() {
+ clockNow = 0;
+}
+
+function Timer() {
+ this._call =
+ this._time =
+ this._next = null;
+}
+
+Timer.prototype = timer.prototype = {
+ constructor: Timer,
+ restart: function(callback, delay, time) {
+ if (typeof callback !== "function") throw new TypeError("callback is not a function");
+ time = (time == null ? now() : +time) + (delay == null ? 0 : +delay);
+ if (!this._next && taskTail !== this) {
+ if (taskTail) taskTail._next = this;
+ else taskHead = this;
+ taskTail = this;
+ }
+ this._call = callback;
+ this._time = time;
+ sleep();
+ },
+ stop: function() {
+ if (this._call) {
+ this._call = null;
+ this._time = Infinity;
+ sleep();
+ }
+ }
+};
+
+function timer(callback, delay, time) {
+ var t = new Timer;
+ t.restart(callback, delay, time);
+ return t;
+}
+
+function timerFlush() {
+ now(); // Get the current time, if not already set.
+ ++timer_frame; // Pretend we’ve set an alarm, if we haven’t already.
+ var t = taskHead, e;
+ while (t) {
+ if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e);
+ t = t._next;
+ }
+ --timer_frame;
+}
+
+function wake() {
+ clockNow = (clockLast = clock.now()) + clockSkew;
+ timer_frame = timeout = 0;
+ try {
+ timerFlush();
+ } finally {
+ timer_frame = 0;
+ nap();
+ clockNow = 0;
+ }
+}
+
+function poke() {
+ var now = clock.now(), delay = now - clockLast;
+ if (delay > pokeDelay) clockSkew -= delay, clockLast = now;
+}
+
+function nap() {
+ var t0, t1 = taskHead, t2, time = Infinity;
+ while (t1) {
+ if (t1._call) {
+ if (time > t1._time) time = t1._time;
+ t0 = t1, t1 = t1._next;
+ } else {
+ t2 = t1._next, t1._next = null;
+ t1 = t0 ? t0._next = t2 : taskHead = t2;
+ }
+ }
+ taskTail = t0;
+ sleep(time);
+}
+
+function sleep(time) {
+ if (timer_frame) return; // Soonest alarm already set, or will be.
+ if (timeout) timeout = clearTimeout(timeout);
+ var delay = time - clockNow; // Strictly less than if we recomputed clockNow.
+ if (delay > 24) {
+ if (time < Infinity) timeout = setTimeout(wake, time - clock.now() - clockSkew);
+ if (interval) interval = clearInterval(interval);
+ } else {
+ if (!interval) clockLast = clock.now(), interval = setInterval(poke, pokeDelay);
+ timer_frame = 1, setFrame(wake);
+ }
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-timer/src/timeout.js
+
+
+/* harmony default export */ function src_timeout(callback, delay, time) {
+ var t = new Timer;
+ delay = delay == null ? 0 : +delay;
+ t.restart(elapsed => {
+ t.stop();
+ callback(elapsed + delay);
+ }, delay, time);
+ return t;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/schedule.js
+
+
+
+var emptyOn = src_dispatch("start", "end", "cancel", "interrupt");
+var emptyTween = [];
+
+var CREATED = 0;
+var SCHEDULED = 1;
+var STARTING = 2;
+var STARTED = 3;
+var RUNNING = 4;
+var ENDING = 5;
+var ENDED = 6;
+
+/* harmony default export */ function schedule(node, name, id, index, group, timing) {
+ var schedules = node.__transition;
+ if (!schedules) node.__transition = {};
+ else if (id in schedules) return;
+ create(node, id, {
+ name: name,
+ index: index, // For context during callback.
+ group: group, // For context during callback.
+ on: emptyOn,
+ tween: emptyTween,
+ time: timing.time,
+ delay: timing.delay,
+ duration: timing.duration,
+ ease: timing.ease,
+ timer: null,
+ state: CREATED
+ });
+}
+
+function init(node, id) {
+ var schedule = schedule_get(node, id);
+ if (schedule.state > CREATED) throw new Error("too late; already scheduled");
+ return schedule;
+}
+
+function schedule_set(node, id) {
+ var schedule = schedule_get(node, id);
+ if (schedule.state > STARTED) throw new Error("too late; already running");
+ return schedule;
+}
+
+function schedule_get(node, id) {
+ var schedule = node.__transition;
+ if (!schedule || !(schedule = schedule[id])) throw new Error("transition not found");
+ return schedule;
+}
+
+function create(node, id, self) {
+ var schedules = node.__transition,
+ tween;
+
+ // Initialize the self timer when the transition is created.
+ // Note the actual delay is not known until the first callback!
+ schedules[id] = self;
+ self.timer = timer(schedule, 0, self.time);
+
+ function schedule(elapsed) {
+ self.state = SCHEDULED;
+ self.timer.restart(start, self.delay, self.time);
+
+ // If the elapsed delay is less than our first sleep, start immediately.
+ if (self.delay <= elapsed) start(elapsed - self.delay);
+ }
+
+ function start(elapsed) {
+ var i, j, n, o;
+
+ // If the state is not SCHEDULED, then we previously errored on start.
+ if (self.state !== SCHEDULED) return stop();
+
+ for (i in schedules) {
+ o = schedules[i];
+ if (o.name !== self.name) continue;
+
+ // While this element already has a starting transition during this frame,
+ // defer starting an interrupting transition until that transition has a
+ // chance to tick (and possibly end); see d3/d3-transition#54!
+ if (o.state === STARTED) return src_timeout(start);
+
+ // Interrupt the active transition, if any.
+ if (o.state === RUNNING) {
+ o.state = ENDED;
+ o.timer.stop();
+ o.on.call("interrupt", node, node.__data__, o.index, o.group);
+ delete schedules[i];
+ }
+
+ // Cancel any pre-empted transitions.
+ else if (+i < id) {
+ o.state = ENDED;
+ o.timer.stop();
+ o.on.call("cancel", node, node.__data__, o.index, o.group);
+ delete schedules[i];
+ }
+ }
+
+ // Defer the first tick to end of the current frame; see d3/d3#1576.
+ // Note the transition may be canceled after start and before the first tick!
+ // Note this must be scheduled before the start event; see d3/d3-transition#16!
+ // Assuming this is successful, subsequent callbacks go straight to tick.
+ src_timeout(function() {
+ if (self.state === STARTED) {
+ self.state = RUNNING;
+ self.timer.restart(tick, self.delay, self.time);
+ tick(elapsed);
+ }
+ });
+
+ // Dispatch the start event.
+ // Note this must be done before the tween are initialized.
+ self.state = STARTING;
+ self.on.call("start", node, node.__data__, self.index, self.group);
+ if (self.state !== STARTING) return; // interrupted
+ self.state = STARTED;
+
+ // Initialize the tween, deleting null tween.
+ tween = new Array(n = self.tween.length);
+ for (i = 0, j = -1; i < n; ++i) {
+ if (o = self.tween[i].value.call(node, node.__data__, self.index, self.group)) {
+ tween[++j] = o;
+ }
+ }
+ tween.length = j + 1;
+ }
+
+ function tick(elapsed) {
+ var t = elapsed < self.duration ? self.ease.call(null, elapsed / self.duration) : (self.timer.restart(stop), self.state = ENDING, 1),
+ i = -1,
+ n = tween.length;
+
+ while (++i < n) {
+ tween[i].call(node, t);
+ }
+
+ // Dispatch the end event.
+ if (self.state === ENDING) {
+ self.on.call("end", node, node.__data__, self.index, self.group);
+ stop();
+ }
+ }
+
+ function stop() {
+ self.state = ENDED;
+ self.timer.stop();
+ delete schedules[id];
+ for (var i in schedules) return; // eslint-disable-line no-unused-vars
+ delete node.__transition;
+ }
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/interrupt.js
+
+
+/* harmony default export */ function interrupt(node, name) {
+ var schedules = node.__transition,
+ schedule,
+ active,
+ empty = true,
+ i;
+
+ if (!schedules) return;
+
+ name = name == null ? null : name + "";
+
+ for (i in schedules) {
+ if ((schedule = schedules[i]).name !== name) { empty = false; continue; }
+ active = schedule.state > STARTING && schedule.state < ENDING;
+ schedule.state = ENDED;
+ schedule.timer.stop();
+ schedule.on.call(active ? "interrupt" : "cancel", node, node.__data__, schedule.index, schedule.group);
+ delete schedules[i];
+ }
+
+ if (empty) delete node.__transition;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/interrupt.js
+
+
+/* harmony default export */ function selection_interrupt(name) {
+ return this.each(function() {
+ interrupt(this, name);
+ });
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/number.js
+/* harmony default export */ function number(a, b) {
+ return a = +a, b = +b, function(t) {
+ return a * (1 - t) + b * t;
+ };
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/decompose.js
+var degrees = 180 / Math.PI;
+
+var identity = {
+ translateX: 0,
+ translateY: 0,
+ rotate: 0,
+ skewX: 0,
+ scaleX: 1,
+ scaleY: 1
+};
+
+/* harmony default export */ function decompose(a, b, c, d, e, f) {
+ var scaleX, scaleY, skewX;
+ if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX;
+ if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX;
+ if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY;
+ if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX;
+ return {
+ translateX: e,
+ translateY: f,
+ rotate: Math.atan2(b, a) * degrees,
+ skewX: Math.atan(skewX) * degrees,
+ scaleX: scaleX,
+ scaleY: scaleY
+ };
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/parse.js
+
+
+var svgNode;
+
+/* eslint-disable no-undef */
+function parseCss(value) {
+ const m = new (typeof DOMMatrix === "function" ? DOMMatrix : WebKitCSSMatrix)(value + "");
+ return m.isIdentity ? identity : decompose(m.a, m.b, m.c, m.d, m.e, m.f);
+}
+
+function parseSvg(value) {
+ if (value == null) return identity;
+ if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g");
+ svgNode.setAttribute("transform", value);
+ if (!(value = svgNode.transform.baseVal.consolidate())) return identity;
+ value = value.matrix;
+ return decompose(value.a, value.b, value.c, value.d, value.e, value.f);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/index.js
+
+
+
+function interpolateTransform(parse, pxComma, pxParen, degParen) {
+
+ function pop(s) {
+ return s.length ? s.pop() + " " : "";
+ }
+
+ function translate(xa, ya, xb, yb, s, q) {
+ if (xa !== xb || ya !== yb) {
+ var i = s.push("translate(", null, pxComma, null, pxParen);
+ q.push({i: i - 4, x: number(xa, xb)}, {i: i - 2, x: number(ya, yb)});
+ } else if (xb || yb) {
+ s.push("translate(" + xb + pxComma + yb + pxParen);
+ }
+ }
+
+ function rotate(a, b, s, q) {
+ if (a !== b) {
+ if (a - b > 180) b += 360; else if (b - a > 180) a += 360; // shortest path
+ q.push({i: s.push(pop(s) + "rotate(", null, degParen) - 2, x: number(a, b)});
+ } else if (b) {
+ s.push(pop(s) + "rotate(" + b + degParen);
+ }
+ }
+
+ function skewX(a, b, s, q) {
+ if (a !== b) {
+ q.push({i: s.push(pop(s) + "skewX(", null, degParen) - 2, x: number(a, b)});
+ } else if (b) {
+ s.push(pop(s) + "skewX(" + b + degParen);
+ }
+ }
+
+ function scale(xa, ya, xb, yb, s, q) {
+ if (xa !== xb || ya !== yb) {
+ var i = s.push(pop(s) + "scale(", null, ",", null, ")");
+ q.push({i: i - 4, x: number(xa, xb)}, {i: i - 2, x: number(ya, yb)});
+ } else if (xb !== 1 || yb !== 1) {
+ s.push(pop(s) + "scale(" + xb + "," + yb + ")");
+ }
+ }
+
+ return function(a, b) {
+ var s = [], // string constants and placeholders
+ q = []; // number interpolators
+ a = parse(a), b = parse(b);
+ translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q);
+ rotate(a.rotate, b.rotate, s, q);
+ skewX(a.skewX, b.skewX, s, q);
+ scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q);
+ a = b = null; // gc
+ return function(t) {
+ var i = -1, n = q.length, o;
+ while (++i < n) s[(o = q[i]).i] = o.x(t);
+ return s.join("");
+ };
+ };
+}
+
+var interpolateTransformCss = interpolateTransform(parseCss, "px, ", "px)", "deg)");
+var interpolateTransformSvg = interpolateTransform(parseSvg, ", ", ")", ")");
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/tween.js
+
+
+function tweenRemove(id, name) {
+ var tween0, tween1;
+ return function() {
+ var schedule = schedule_set(this, id),
+ tween = schedule.tween;
+
+ // If this node shared tween with the previous node,
+ // just assign the updated shared tween and we’re done!
+ // Otherwise, copy-on-write.
+ if (tween !== tween0) {
+ tween1 = tween0 = tween;
+ for (var i = 0, n = tween1.length; i < n; ++i) {
+ if (tween1[i].name === name) {
+ tween1 = tween1.slice();
+ tween1.splice(i, 1);
+ break;
+ }
+ }
+ }
+
+ schedule.tween = tween1;
+ };
+}
+
+function tweenFunction(id, name, value) {
+ var tween0, tween1;
+ if (typeof value !== "function") throw new Error;
+ return function() {
+ var schedule = schedule_set(this, id),
+ tween = schedule.tween;
+
+ // If this node shared tween with the previous node,
+ // just assign the updated shared tween and we’re done!
+ // Otherwise, copy-on-write.
+ if (tween !== tween0) {
+ tween1 = (tween0 = tween).slice();
+ for (var t = {name: name, value: value}, i = 0, n = tween1.length; i < n; ++i) {
+ if (tween1[i].name === name) {
+ tween1[i] = t;
+ break;
+ }
+ }
+ if (i === n) tween1.push(t);
+ }
+
+ schedule.tween = tween1;
+ };
+}
+
+/* harmony default export */ function tween(name, value) {
+ var id = this._id;
+
+ name += "";
+
+ if (arguments.length < 2) {
+ var tween = schedule_get(this.node(), id).tween;
+ for (var i = 0, n = tween.length, t; i < n; ++i) {
+ if ((t = tween[i]).name === name) {
+ return t.value;
+ }
+ }
+ return null;
+ }
+
+ return this.each((value == null ? tweenRemove : tweenFunction)(id, name, value));
+}
+
+function tweenValue(transition, name, value) {
+ var id = transition._id;
+
+ transition.each(function() {
+ var schedule = schedule_set(this, id);
+ (schedule.value || (schedule.value = {}))[name] = value.apply(this, arguments);
+ });
+
+ return function(node) {
+ return schedule_get(node, id).value[name];
+ };
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-color/src/define.js
+/* harmony default export */ function src_define(constructor, factory, prototype) {
+ constructor.prototype = factory.prototype = prototype;
+ prototype.constructor = constructor;
+}
+
+function extend(parent, definition) {
+ var prototype = Object.create(parent.prototype);
+ for (var key in definition) prototype[key] = definition[key];
+ return prototype;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-color/src/color.js
+
+
+function Color() {}
+
+var darker = 0.7;
+var brighter = 1 / darker;
+
+var reI = "\\s*([+-]?\\d+)\\s*",
+ reN = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",
+ reP = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",
+ reHex = /^#([0-9a-f]{3,8})$/,
+ reRgbInteger = new RegExp("^rgb\\(" + [reI, reI, reI] + "\\)$"),
+ reRgbPercent = new RegExp("^rgb\\(" + [reP, reP, reP] + "\\)$"),
+ reRgbaInteger = new RegExp("^rgba\\(" + [reI, reI, reI, reN] + "\\)$"),
+ reRgbaPercent = new RegExp("^rgba\\(" + [reP, reP, reP, reN] + "\\)$"),
+ reHslPercent = new RegExp("^hsl\\(" + [reN, reP, reP] + "\\)$"),
+ reHslaPercent = new RegExp("^hsla\\(" + [reN, reP, reP, reN] + "\\)$");
+
+var named = {
+ aliceblue: 0xf0f8ff,
+ antiquewhite: 0xfaebd7,
+ aqua: 0x00ffff,
+ aquamarine: 0x7fffd4,
+ azure: 0xf0ffff,
+ beige: 0xf5f5dc,
+ bisque: 0xffe4c4,
+ black: 0x000000,
+ blanchedalmond: 0xffebcd,
+ blue: 0x0000ff,
+ blueviolet: 0x8a2be2,
+ brown: 0xa52a2a,
+ burlywood: 0xdeb887,
+ cadetblue: 0x5f9ea0,
+ chartreuse: 0x7fff00,
+ chocolate: 0xd2691e,
+ coral: 0xff7f50,
+ cornflowerblue: 0x6495ed,
+ cornsilk: 0xfff8dc,
+ crimson: 0xdc143c,
+ cyan: 0x00ffff,
+ darkblue: 0x00008b,
+ darkcyan: 0x008b8b,
+ darkgoldenrod: 0xb8860b,
+ darkgray: 0xa9a9a9,
+ darkgreen: 0x006400,
+ darkgrey: 0xa9a9a9,
+ darkkhaki: 0xbdb76b,
+ darkmagenta: 0x8b008b,
+ darkolivegreen: 0x556b2f,
+ darkorange: 0xff8c00,
+ darkorchid: 0x9932cc,
+ darkred: 0x8b0000,
+ darksalmon: 0xe9967a,
+ darkseagreen: 0x8fbc8f,
+ darkslateblue: 0x483d8b,
+ darkslategray: 0x2f4f4f,
+ darkslategrey: 0x2f4f4f,
+ darkturquoise: 0x00ced1,
+ darkviolet: 0x9400d3,
+ deeppink: 0xff1493,
+ deepskyblue: 0x00bfff,
+ dimgray: 0x696969,
+ dimgrey: 0x696969,
+ dodgerblue: 0x1e90ff,
+ firebrick: 0xb22222,
+ floralwhite: 0xfffaf0,
+ forestgreen: 0x228b22,
+ fuchsia: 0xff00ff,
+ gainsboro: 0xdcdcdc,
+ ghostwhite: 0xf8f8ff,
+ gold: 0xffd700,
+ goldenrod: 0xdaa520,
+ gray: 0x808080,
+ green: 0x008000,
+ greenyellow: 0xadff2f,
+ grey: 0x808080,
+ honeydew: 0xf0fff0,
+ hotpink: 0xff69b4,
+ indianred: 0xcd5c5c,
+ indigo: 0x4b0082,
+ ivory: 0xfffff0,
+ khaki: 0xf0e68c,
+ lavender: 0xe6e6fa,
+ lavenderblush: 0xfff0f5,
+ lawngreen: 0x7cfc00,
+ lemonchiffon: 0xfffacd,
+ lightblue: 0xadd8e6,
+ lightcoral: 0xf08080,
+ lightcyan: 0xe0ffff,
+ lightgoldenrodyellow: 0xfafad2,
+ lightgray: 0xd3d3d3,
+ lightgreen: 0x90ee90,
+ lightgrey: 0xd3d3d3,
+ lightpink: 0xffb6c1,
+ lightsalmon: 0xffa07a,
+ lightseagreen: 0x20b2aa,
+ lightskyblue: 0x87cefa,
+ lightslategray: 0x778899,
+ lightslategrey: 0x778899,
+ lightsteelblue: 0xb0c4de,
+ lightyellow: 0xffffe0,
+ lime: 0x00ff00,
+ limegreen: 0x32cd32,
+ linen: 0xfaf0e6,
+ magenta: 0xff00ff,
+ maroon: 0x800000,
+ mediumaquamarine: 0x66cdaa,
+ mediumblue: 0x0000cd,
+ mediumorchid: 0xba55d3,
+ mediumpurple: 0x9370db,
+ mediumseagreen: 0x3cb371,
+ mediumslateblue: 0x7b68ee,
+ mediumspringgreen: 0x00fa9a,
+ mediumturquoise: 0x48d1cc,
+ mediumvioletred: 0xc71585,
+ midnightblue: 0x191970,
+ mintcream: 0xf5fffa,
+ mistyrose: 0xffe4e1,
+ moccasin: 0xffe4b5,
+ navajowhite: 0xffdead,
+ navy: 0x000080,
+ oldlace: 0xfdf5e6,
+ olive: 0x808000,
+ olivedrab: 0x6b8e23,
+ orange: 0xffa500,
+ orangered: 0xff4500,
+ orchid: 0xda70d6,
+ palegoldenrod: 0xeee8aa,
+ palegreen: 0x98fb98,
+ paleturquoise: 0xafeeee,
+ palevioletred: 0xdb7093,
+ papayawhip: 0xffefd5,
+ peachpuff: 0xffdab9,
+ peru: 0xcd853f,
+ pink: 0xffc0cb,
+ plum: 0xdda0dd,
+ powderblue: 0xb0e0e6,
+ purple: 0x800080,
+ rebeccapurple: 0x663399,
+ red: 0xff0000,
+ rosybrown: 0xbc8f8f,
+ royalblue: 0x4169e1,
+ saddlebrown: 0x8b4513,
+ salmon: 0xfa8072,
+ sandybrown: 0xf4a460,
+ seagreen: 0x2e8b57,
+ seashell: 0xfff5ee,
+ sienna: 0xa0522d,
+ silver: 0xc0c0c0,
+ skyblue: 0x87ceeb,
+ slateblue: 0x6a5acd,
+ slategray: 0x708090,
+ slategrey: 0x708090,
+ snow: 0xfffafa,
+ springgreen: 0x00ff7f,
+ steelblue: 0x4682b4,
+ tan: 0xd2b48c,
+ teal: 0x008080,
+ thistle: 0xd8bfd8,
+ tomato: 0xff6347,
+ turquoise: 0x40e0d0,
+ violet: 0xee82ee,
+ wheat: 0xf5deb3,
+ white: 0xffffff,
+ whitesmoke: 0xf5f5f5,
+ yellow: 0xffff00,
+ yellowgreen: 0x9acd32
+};
+
+src_define(Color, color, {
+ copy: function(channels) {
+ return Object.assign(new this.constructor, this, channels);
+ },
+ displayable: function() {
+ return this.rgb().displayable();
+ },
+ hex: color_formatHex, // Deprecated! Use color.formatHex.
+ formatHex: color_formatHex,
+ formatHsl: color_formatHsl,
+ formatRgb: color_formatRgb,
+ toString: color_formatRgb
+});
+
+function color_formatHex() {
+ return this.rgb().formatHex();
+}
+
+function color_formatHsl() {
+ return hslConvert(this).formatHsl();
+}
+
+function color_formatRgb() {
+ return this.rgb().formatRgb();
+}
+
+function color(format) {
+ var m, l;
+ format = (format + "").trim().toLowerCase();
+ return (m = reHex.exec(format)) ? (l = m[1].length, m = parseInt(m[1], 16), l === 6 ? rgbn(m) // #ff0000
+ : l === 3 ? new Rgb((m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1) // #f00
+ : l === 8 ? rgba(m >> 24 & 0xff, m >> 16 & 0xff, m >> 8 & 0xff, (m & 0xff) / 0xff) // #ff000000
+ : l === 4 ? rgba((m >> 12 & 0xf) | (m >> 8 & 0xf0), (m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), (((m & 0xf) << 4) | (m & 0xf)) / 0xff) // #f000
+ : null) // invalid hex
+ : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0)
+ : (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%)
+ : (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1)
+ : (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1)
+ : (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%)
+ : (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1)
+ : named.hasOwnProperty(format) ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins
+ : format === "transparent" ? new Rgb(NaN, NaN, NaN, 0)
+ : null;
+}
+
+function rgbn(n) {
+ return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1);
+}
+
+function rgba(r, g, b, a) {
+ if (a <= 0) r = g = b = NaN;
+ return new Rgb(r, g, b, a);
+}
+
+function rgbConvert(o) {
+ if (!(o instanceof Color)) o = color(o);
+ if (!o) return new Rgb;
+ o = o.rgb();
+ return new Rgb(o.r, o.g, o.b, o.opacity);
+}
+
+function color_rgb(r, g, b, opacity) {
+ return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity);
+}
+
+function Rgb(r, g, b, opacity) {
+ this.r = +r;
+ this.g = +g;
+ this.b = +b;
+ this.opacity = +opacity;
+}
+
+src_define(Rgb, color_rgb, extend(Color, {
+ brighter: function(k) {
+ k = k == null ? brighter : Math.pow(brighter, k);
+ return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
+ },
+ darker: function(k) {
+ k = k == null ? darker : Math.pow(darker, k);
+ return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
+ },
+ rgb: function() {
+ return this;
+ },
+ displayable: function() {
+ return (-0.5 <= this.r && this.r < 255.5)
+ && (-0.5 <= this.g && this.g < 255.5)
+ && (-0.5 <= this.b && this.b < 255.5)
+ && (0 <= this.opacity && this.opacity <= 1);
+ },
+ hex: rgb_formatHex, // Deprecated! Use color.formatHex.
+ formatHex: rgb_formatHex,
+ formatRgb: rgb_formatRgb,
+ toString: rgb_formatRgb
+}));
+
+function rgb_formatHex() {
+ return "#" + hex(this.r) + hex(this.g) + hex(this.b);
+}
+
+function rgb_formatRgb() {
+ var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
+ return (a === 1 ? "rgb(" : "rgba(")
+ + Math.max(0, Math.min(255, Math.round(this.r) || 0)) + ", "
+ + Math.max(0, Math.min(255, Math.round(this.g) || 0)) + ", "
+ + Math.max(0, Math.min(255, Math.round(this.b) || 0))
+ + (a === 1 ? ")" : ", " + a + ")");
+}
+
+function hex(value) {
+ value = Math.max(0, Math.min(255, Math.round(value) || 0));
+ return (value < 16 ? "0" : "") + value.toString(16);
+}
+
+function hsla(h, s, l, a) {
+ if (a <= 0) h = s = l = NaN;
+ else if (l <= 0 || l >= 1) h = s = NaN;
+ else if (s <= 0) h = NaN;
+ return new Hsl(h, s, l, a);
+}
+
+function hslConvert(o) {
+ if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity);
+ if (!(o instanceof Color)) o = color(o);
+ if (!o) return new Hsl;
+ if (o instanceof Hsl) return o;
+ o = o.rgb();
+ var r = o.r / 255,
+ g = o.g / 255,
+ b = o.b / 255,
+ min = Math.min(r, g, b),
+ max = Math.max(r, g, b),
+ h = NaN,
+ s = max - min,
+ l = (max + min) / 2;
+ if (s) {
+ if (r === max) h = (g - b) / s + (g < b) * 6;
+ else if (g === max) h = (b - r) / s + 2;
+ else h = (r - g) / s + 4;
+ s /= l < 0.5 ? max + min : 2 - max - min;
+ h *= 60;
+ } else {
+ s = l > 0 && l < 1 ? 0 : h;
+ }
+ return new Hsl(h, s, l, o.opacity);
+}
+
+function hsl(h, s, l, opacity) {
+ return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity);
+}
+
+function Hsl(h, s, l, opacity) {
+ this.h = +h;
+ this.s = +s;
+ this.l = +l;
+ this.opacity = +opacity;
+}
+
+src_define(Hsl, hsl, extend(Color, {
+ brighter: function(k) {
+ k = k == null ? brighter : Math.pow(brighter, k);
+ return new Hsl(this.h, this.s, this.l * k, this.opacity);
+ },
+ darker: function(k) {
+ k = k == null ? darker : Math.pow(darker, k);
+ return new Hsl(this.h, this.s, this.l * k, this.opacity);
+ },
+ rgb: function() {
+ var h = this.h % 360 + (this.h < 0) * 360,
+ s = isNaN(h) || isNaN(this.s) ? 0 : this.s,
+ l = this.l,
+ m2 = l + (l < 0.5 ? l : 1 - l) * s,
+ m1 = 2 * l - m2;
+ return new Rgb(
+ hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2),
+ hsl2rgb(h, m1, m2),
+ hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2),
+ this.opacity
+ );
+ },
+ displayable: function() {
+ return (0 <= this.s && this.s <= 1 || isNaN(this.s))
+ && (0 <= this.l && this.l <= 1)
+ && (0 <= this.opacity && this.opacity <= 1);
+ },
+ formatHsl: function() {
+ var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
+ return (a === 1 ? "hsl(" : "hsla(")
+ + (this.h || 0) + ", "
+ + (this.s || 0) * 100 + "%, "
+ + (this.l || 0) * 100 + "%"
+ + (a === 1 ? ")" : ", " + a + ")");
+ }
+}));
+
+/* From FvD 13.37, CSS Color Module Level 3 */
+function hsl2rgb(h, m1, m2) {
+ return (h < 60 ? m1 + (m2 - m1) * h / 60
+ : h < 180 ? m2
+ : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60
+ : m1) * 255;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basis.js
+function basis(t1, v0, v1, v2, v3) {
+ var t2 = t1 * t1, t3 = t2 * t1;
+ return ((1 - 3 * t1 + 3 * t2 - t3) * v0
+ + (4 - 6 * t2 + 3 * t3) * v1
+ + (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2
+ + t3 * v3) / 6;
+}
+
+/* harmony default export */ function src_basis(values) {
+ var n = values.length - 1;
+ return function(t) {
+ var i = t <= 0 ? (t = 0) : t >= 1 ? (t = 1, n - 1) : Math.floor(t * n),
+ v1 = values[i],
+ v2 = values[i + 1],
+ v0 = i > 0 ? values[i - 1] : 2 * v1 - v2,
+ v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1;
+ return basis((t - i / n) * n, v0, v1, v2, v3);
+ };
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basisClosed.js
+
+
+/* harmony default export */ function basisClosed(values) {
+ var n = values.length;
+ return function(t) {
+ var i = Math.floor(((t %= 1) < 0 ? ++t : t) * n),
+ v0 = values[(i + n - 1) % n],
+ v1 = values[i % n],
+ v2 = values[(i + 1) % n],
+ v3 = values[(i + 2) % n];
+ return basis((t - i / n) * n, v0, v1, v2, v3);
+ };
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/constant.js
+/* harmony default export */ const d3_interpolate_src_constant = (x => () => x);
+
+;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/color.js
+
+
+function linear(a, d) {
+ return function(t) {
+ return a + t * d;
+ };
+}
+
+function exponential(a, b, y) {
+ return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function(t) {
+ return Math.pow(a + t * b, y);
+ };
+}
+
+function hue(a, b) {
+ var d = b - a;
+ return d ? linear(a, d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d) : constant(isNaN(a) ? b : a);
+}
+
+function gamma(y) {
+ return (y = +y) === 1 ? nogamma : function(a, b) {
+ return b - a ? exponential(a, b, y) : d3_interpolate_src_constant(isNaN(a) ? b : a);
+ };
+}
+
+function nogamma(a, b) {
+ var d = b - a;
+ return d ? linear(a, d) : d3_interpolate_src_constant(isNaN(a) ? b : a);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/rgb.js
+
+
+
+
+
+/* harmony default export */ const rgb = ((function rgbGamma(y) {
+ var color = gamma(y);
+
+ function rgb(start, end) {
+ var r = color((start = color_rgb(start)).r, (end = color_rgb(end)).r),
+ g = color(start.g, end.g),
+ b = color(start.b, end.b),
+ opacity = nogamma(start.opacity, end.opacity);
+ return function(t) {
+ start.r = r(t);
+ start.g = g(t);
+ start.b = b(t);
+ start.opacity = opacity(t);
+ return start + "";
+ };
+ }
+
+ rgb.gamma = rgbGamma;
+
+ return rgb;
+})(1));
+
+function rgbSpline(spline) {
+ return function(colors) {
+ var n = colors.length,
+ r = new Array(n),
+ g = new Array(n),
+ b = new Array(n),
+ i, color;
+ for (i = 0; i < n; ++i) {
+ color = color_rgb(colors[i]);
+ r[i] = color.r || 0;
+ g[i] = color.g || 0;
+ b[i] = color.b || 0;
+ }
+ r = spline(r);
+ g = spline(g);
+ b = spline(b);
+ color.opacity = 1;
+ return function(t) {
+ color.r = r(t);
+ color.g = g(t);
+ color.b = b(t);
+ return color + "";
+ };
+ };
+}
+
+var rgbBasis = rgbSpline(src_basis);
+var rgbBasisClosed = rgbSpline(basisClosed);
+
+;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/string.js
+
+
+var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,
+ reB = new RegExp(reA.source, "g");
+
+function zero(b) {
+ return function() {
+ return b;
+ };
+}
+
+function one(b) {
+ return function(t) {
+ return b(t) + "";
+ };
+}
+
+/* harmony default export */ function string(a, b) {
+ var bi = reA.lastIndex = reB.lastIndex = 0, // scan index for next number in b
+ am, // current match in a
+ bm, // current match in b
+ bs, // string preceding current number in b, if any
+ i = -1, // index in s
+ s = [], // string constants and placeholders
+ q = []; // number interpolators
+
+ // Coerce inputs to strings.
+ a = a + "", b = b + "";
+
+ // Interpolate pairs of numbers in a & b.
+ while ((am = reA.exec(a))
+ && (bm = reB.exec(b))) {
+ if ((bs = bm.index) > bi) { // a string precedes the next number in b
+ bs = b.slice(bi, bs);
+ if (s[i]) s[i] += bs; // coalesce with previous string
+ else s[++i] = bs;
+ }
+ if ((am = am[0]) === (bm = bm[0])) { // numbers in a & b match
+ if (s[i]) s[i] += bm; // coalesce with previous string
+ else s[++i] = bm;
+ } else { // interpolate non-matching numbers
+ s[++i] = null;
+ q.push({i: i, x: number(am, bm)});
+ }
+ bi = reB.lastIndex;
+ }
+
+ // Add remains of b.
+ if (bi < b.length) {
+ bs = b.slice(bi);
+ if (s[i]) s[i] += bs; // coalesce with previous string
+ else s[++i] = bs;
+ }
+
+ // Special optimization for only a single match.
+ // Otherwise, interpolate each of the numbers and rejoin the string.
+ return s.length < 2 ? (q[0]
+ ? one(q[0].x)
+ : zero(b))
+ : (b = q.length, function(t) {
+ for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
+ return s.join("");
+ });
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/interpolate.js
+
+
+
+/* harmony default export */ function interpolate(a, b) {
+ var c;
+ return (typeof b === "number" ? number
+ : b instanceof color ? rgb
+ : (c = color(b)) ? (b = c, rgb)
+ : string)(a, b);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attr.js
+
+
+
+
+
+function attr_attrRemove(name) {
+ return function() {
+ this.removeAttribute(name);
+ };
+}
+
+function attr_attrRemoveNS(fullname) {
+ return function() {
+ this.removeAttributeNS(fullname.space, fullname.local);
+ };
+}
+
+function attr_attrConstant(name, interpolate, value1) {
+ var string00,
+ string1 = value1 + "",
+ interpolate0;
+ return function() {
+ var string0 = this.getAttribute(name);
+ return string0 === string1 ? null
+ : string0 === string00 ? interpolate0
+ : interpolate0 = interpolate(string00 = string0, value1);
+ };
+}
+
+function attr_attrConstantNS(fullname, interpolate, value1) {
+ var string00,
+ string1 = value1 + "",
+ interpolate0;
+ return function() {
+ var string0 = this.getAttributeNS(fullname.space, fullname.local);
+ return string0 === string1 ? null
+ : string0 === string00 ? interpolate0
+ : interpolate0 = interpolate(string00 = string0, value1);
+ };
+}
+
+function attr_attrFunction(name, interpolate, value) {
+ var string00,
+ string10,
+ interpolate0;
+ return function() {
+ var string0, value1 = value(this), string1;
+ if (value1 == null) return void this.removeAttribute(name);
+ string0 = this.getAttribute(name);
+ string1 = value1 + "";
+ return string0 === string1 ? null
+ : string0 === string00 && string1 === string10 ? interpolate0
+ : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1));
+ };
+}
+
+function attr_attrFunctionNS(fullname, interpolate, value) {
+ var string00,
+ string10,
+ interpolate0;
+ return function() {
+ var string0, value1 = value(this), string1;
+ if (value1 == null) return void this.removeAttributeNS(fullname.space, fullname.local);
+ string0 = this.getAttributeNS(fullname.space, fullname.local);
+ string1 = value1 + "";
+ return string0 === string1 ? null
+ : string0 === string00 && string1 === string10 ? interpolate0
+ : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1));
+ };
+}
+
+/* harmony default export */ function transition_attr(name, value) {
+ var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate;
+ return this.attrTween(name, typeof value === "function"
+ ? (fullname.local ? attr_attrFunctionNS : attr_attrFunction)(fullname, i, tweenValue(this, "attr." + name, value))
+ : value == null ? (fullname.local ? attr_attrRemoveNS : attr_attrRemove)(fullname)
+ : (fullname.local ? attr_attrConstantNS : attr_attrConstant)(fullname, i, value));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attrTween.js
+
+
+function attrInterpolate(name, i) {
+ return function(t) {
+ this.setAttribute(name, i.call(this, t));
+ };
+}
+
+function attrInterpolateNS(fullname, i) {
+ return function(t) {
+ this.setAttributeNS(fullname.space, fullname.local, i.call(this, t));
+ };
+}
+
+function attrTweenNS(fullname, value) {
+ var t0, i0;
+ function tween() {
+ var i = value.apply(this, arguments);
+ if (i !== i0) t0 = (i0 = i) && attrInterpolateNS(fullname, i);
+ return t0;
+ }
+ tween._value = value;
+ return tween;
+}
+
+function attrTween(name, value) {
+ var t0, i0;
+ function tween() {
+ var i = value.apply(this, arguments);
+ if (i !== i0) t0 = (i0 = i) && attrInterpolate(name, i);
+ return t0;
+ }
+ tween._value = value;
+ return tween;
+}
+
+/* harmony default export */ function transition_attrTween(name, value) {
+ var key = "attr." + name;
+ if (arguments.length < 2) return (key = this.tween(key)) && key._value;
+ if (value == null) return this.tween(key, null);
+ if (typeof value !== "function") throw new Error;
+ var fullname = namespace(name);
+ return this.tween(key, (fullname.local ? attrTweenNS : attrTween)(fullname, value));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/delay.js
+
+
+function delayFunction(id, value) {
+ return function() {
+ init(this, id).delay = +value.apply(this, arguments);
+ };
+}
+
+function delayConstant(id, value) {
+ return value = +value, function() {
+ init(this, id).delay = value;
+ };
+}
+
+/* harmony default export */ function delay(value) {
+ var id = this._id;
+
+ return arguments.length
+ ? this.each((typeof value === "function"
+ ? delayFunction
+ : delayConstant)(id, value))
+ : schedule_get(this.node(), id).delay;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/duration.js
+
+
+function durationFunction(id, value) {
+ return function() {
+ schedule_set(this, id).duration = +value.apply(this, arguments);
+ };
+}
+
+function durationConstant(id, value) {
+ return value = +value, function() {
+ schedule_set(this, id).duration = value;
+ };
+}
+
+/* harmony default export */ function duration(value) {
+ var id = this._id;
+
+ return arguments.length
+ ? this.each((typeof value === "function"
+ ? durationFunction
+ : durationConstant)(id, value))
+ : schedule_get(this.node(), id).duration;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/ease.js
+
+
+function easeConstant(id, value) {
+ if (typeof value !== "function") throw new Error;
+ return function() {
+ schedule_set(this, id).ease = value;
+ };
+}
+
+/* harmony default export */ function ease(value) {
+ var id = this._id;
+
+ return arguments.length
+ ? this.each(easeConstant(id, value))
+ : schedule_get(this.node(), id).ease;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/easeVarying.js
+
+
+function easeVarying(id, value) {
+ return function() {
+ var v = value.apply(this, arguments);
+ if (typeof v !== "function") throw new Error;
+ schedule_set(this, id).ease = v;
+ };
+}
+
+/* harmony default export */ function transition_easeVarying(value) {
+ if (typeof value !== "function") throw new Error;
+ return this.each(easeVarying(this._id, value));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/filter.js
+
+
+
+/* harmony default export */ function transition_filter(match) {
+ if (typeof match !== "function") match = matcher(match);
+
+ for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
+ if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
+ subgroup.push(node);
+ }
+ }
+ }
+
+ return new Transition(subgroups, this._parents, this._name, this._id);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/merge.js
+
+
+/* harmony default export */ function transition_merge(transition) {
+ if (transition._id !== this._id) throw new Error;
+
+ for (var groups0 = this._groups, groups1 = transition._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {
+ for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {
+ if (node = group0[i] || group1[i]) {
+ merge[i] = node;
+ }
+ }
+ }
+
+ for (; j < m0; ++j) {
+ merges[j] = groups0[j];
+ }
+
+ return new Transition(merges, this._parents, this._name, this._id);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/on.js
+
+
+function start(name) {
+ return (name + "").trim().split(/^|\s+/).every(function(t) {
+ var i = t.indexOf(".");
+ if (i >= 0) t = t.slice(0, i);
+ return !t || t === "start";
+ });
+}
+
+function onFunction(id, name, listener) {
+ var on0, on1, sit = start(name) ? init : schedule_set;
+ return function() {
+ var schedule = sit(this, id),
+ on = schedule.on;
+
+ // If this node shared a dispatch with the previous node,
+ // just assign the updated shared dispatch and we’re done!
+ // Otherwise, copy-on-write.
+ if (on !== on0) (on1 = (on0 = on).copy()).on(name, listener);
+
+ schedule.on = on1;
+ };
+}
+
+/* harmony default export */ function transition_on(name, listener) {
+ var id = this._id;
+
+ return arguments.length < 2
+ ? schedule_get(this.node(), id).on.on(name)
+ : this.each(onFunction(id, name, listener));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/remove.js
+function removeFunction(id) {
+ return function() {
+ var parent = this.parentNode;
+ for (var i in this.__transition) if (+i !== id) return;
+ if (parent) parent.removeChild(this);
+ };
+}
+
+/* harmony default export */ function transition_remove() {
+ return this.on("end.remove", removeFunction(this._id));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/select.js
+
+
+
+
+/* harmony default export */ function transition_select(select) {
+ var name = this._name,
+ id = this._id;
+
+ if (typeof select !== "function") select = selector(select);
+
+ for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
+ if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
+ if ("__data__" in node) subnode.__data__ = node.__data__;
+ subgroup[i] = subnode;
+ schedule(subgroup[i], name, id, i, subgroup, schedule_get(node, id));
+ }
+ }
+ }
+
+ return new Transition(subgroups, this._parents, name, id);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selectAll.js
+
+
+
+
+/* harmony default export */ function transition_selectAll(select) {
+ var name = this._name,
+ id = this._id;
+
+ if (typeof select !== "function") select = selectorAll(select);
+
+ for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
+ if (node = group[i]) {
+ for (var children = select.call(node, node.__data__, i, group), child, inherit = schedule_get(node, id), k = 0, l = children.length; k < l; ++k) {
+ if (child = children[k]) {
+ schedule(child, name, id, k, children, inherit);
+ }
+ }
+ subgroups.push(children);
+ parents.push(node);
+ }
+ }
+ }
+
+ return new Transition(subgroups, parents, name, id);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selection.js
+
+
+var selection_Selection = src_selection.prototype.constructor;
+
+/* harmony default export */ function transition_selection() {
+ return new selection_Selection(this._groups, this._parents);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/style.js
+
+
+
+
+
+
+function styleNull(name, interpolate) {
+ var string00,
+ string10,
+ interpolate0;
+ return function() {
+ var string0 = styleValue(this, name),
+ string1 = (this.style.removeProperty(name), styleValue(this, name));
+ return string0 === string1 ? null
+ : string0 === string00 && string1 === string10 ? interpolate0
+ : interpolate0 = interpolate(string00 = string0, string10 = string1);
+ };
+}
+
+function style_styleRemove(name) {
+ return function() {
+ this.style.removeProperty(name);
+ };
+}
+
+function style_styleConstant(name, interpolate, value1) {
+ var string00,
+ string1 = value1 + "",
+ interpolate0;
+ return function() {
+ var string0 = styleValue(this, name);
+ return string0 === string1 ? null
+ : string0 === string00 ? interpolate0
+ : interpolate0 = interpolate(string00 = string0, value1);
+ };
+}
+
+function style_styleFunction(name, interpolate, value) {
+ var string00,
+ string10,
+ interpolate0;
+ return function() {
+ var string0 = styleValue(this, name),
+ value1 = value(this),
+ string1 = value1 + "";
+ if (value1 == null) string1 = value1 = (this.style.removeProperty(name), styleValue(this, name));
+ return string0 === string1 ? null
+ : string0 === string00 && string1 === string10 ? interpolate0
+ : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1));
+ };
+}
+
+function styleMaybeRemove(id, name) {
+ var on0, on1, listener0, key = "style." + name, event = "end." + key, remove;
+ return function() {
+ var schedule = schedule_set(this, id),
+ on = schedule.on,
+ listener = schedule.value[key] == null ? remove || (remove = style_styleRemove(name)) : undefined;
+
+ // If this node shared a dispatch with the previous node,
+ // just assign the updated shared dispatch and we’re done!
+ // Otherwise, copy-on-write.
+ if (on !== on0 || listener0 !== listener) (on1 = (on0 = on).copy()).on(event, listener0 = listener);
+
+ schedule.on = on1;
+ };
+}
+
+/* harmony default export */ function transition_style(name, value, priority) {
+ var i = (name += "") === "transform" ? interpolateTransformCss : interpolate;
+ return value == null ? this
+ .styleTween(name, styleNull(name, i))
+ .on("end.style." + name, style_styleRemove(name))
+ : typeof value === "function" ? this
+ .styleTween(name, style_styleFunction(name, i, tweenValue(this, "style." + name, value)))
+ .each(styleMaybeRemove(this._id, name))
+ : this
+ .styleTween(name, style_styleConstant(name, i, value), priority)
+ .on("end.style." + name, null);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/styleTween.js
+function styleInterpolate(name, i, priority) {
+ return function(t) {
+ this.style.setProperty(name, i.call(this, t), priority);
+ };
+}
+
+function styleTween(name, value, priority) {
+ var t, i0;
+ function tween() {
+ var i = value.apply(this, arguments);
+ if (i !== i0) t = (i0 = i) && styleInterpolate(name, i, priority);
+ return t;
+ }
+ tween._value = value;
+ return tween;
+}
+
+/* harmony default export */ function transition_styleTween(name, value, priority) {
+ var key = "style." + (name += "");
+ if (arguments.length < 2) return (key = this.tween(key)) && key._value;
+ if (value == null) return this.tween(key, null);
+ if (typeof value !== "function") throw new Error;
+ return this.tween(key, styleTween(name, value, priority == null ? "" : priority));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/text.js
+
+
+function text_textConstant(value) {
+ return function() {
+ this.textContent = value;
+ };
+}
+
+function text_textFunction(value) {
+ return function() {
+ var value1 = value(this);
+ this.textContent = value1 == null ? "" : value1;
+ };
+}
+
+/* harmony default export */ function transition_text(value) {
+ return this.tween("text", typeof value === "function"
+ ? text_textFunction(tweenValue(this, "text", value))
+ : text_textConstant(value == null ? "" : value + ""));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/textTween.js
+function textInterpolate(i) {
+ return function(t) {
+ this.textContent = i.call(this, t);
+ };
+}
+
+function textTween(value) {
+ var t0, i0;
+ function tween() {
+ var i = value.apply(this, arguments);
+ if (i !== i0) t0 = (i0 = i) && textInterpolate(i);
+ return t0;
+ }
+ tween._value = value;
+ return tween;
+}
+
+/* harmony default export */ function transition_textTween(value) {
+ var key = "text";
+ if (arguments.length < 1) return (key = this.tween(key)) && key._value;
+ if (value == null) return this.tween(key, null);
+ if (typeof value !== "function") throw new Error;
+ return this.tween(key, textTween(value));
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/transition.js
+
+
+
+/* harmony default export */ function transition() {
+ var name = this._name,
+ id0 = this._id,
+ id1 = newId();
+
+ for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
+ if (node = group[i]) {
+ var inherit = schedule_get(node, id0);
+ schedule(node, name, id1, i, group, {
+ time: inherit.time + inherit.delay + inherit.duration,
+ delay: 0,
+ duration: inherit.duration,
+ ease: inherit.ease
+ });
+ }
+ }
+ }
+
+ return new Transition(groups, this._parents, name, id1);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/end.js
+
+
+/* harmony default export */ function end() {
+ var on0, on1, that = this, id = that._id, size = that.size();
+ return new Promise(function(resolve, reject) {
+ var cancel = {value: reject},
+ end = {value: function() { if (--size === 0) resolve(); }};
+
+ that.each(function() {
+ var schedule = schedule_set(this, id),
+ on = schedule.on;
+
+ // If this node shared a dispatch with the previous node,
+ // just assign the updated shared dispatch and we’re done!
+ // Otherwise, copy-on-write.
+ if (on !== on0) {
+ on1 = (on0 = on).copy();
+ on1._.cancel.push(cancel);
+ on1._.interrupt.push(cancel);
+ on1._.end.push(end);
+ }
+
+ schedule.on = on1;
+ });
+
+ // The selection was empty, resolve end immediately
+ if (size === 0) resolve();
+ });
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/index.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+var id = 0;
+
+function Transition(groups, parents, name, id) {
+ this._groups = groups;
+ this._parents = parents;
+ this._name = name;
+ this._id = id;
+}
+
+function transition_transition(name) {
+ return src_selection().transition(name);
+}
+
+function newId() {
+ return ++id;
+}
+
+var selection_prototype = src_selection.prototype;
+
+Transition.prototype = transition_transition.prototype = {
+ constructor: Transition,
+ select: transition_select,
+ selectAll: transition_selectAll,
+ selectChild: selection_prototype.selectChild,
+ selectChildren: selection_prototype.selectChildren,
+ filter: transition_filter,
+ merge: transition_merge,
+ selection: transition_selection,
+ transition: transition,
+ call: selection_prototype.call,
+ nodes: selection_prototype.nodes,
+ node: selection_prototype.node,
+ size: selection_prototype.size,
+ empty: selection_prototype.empty,
+ each: selection_prototype.each,
+ on: transition_on,
+ attr: transition_attr,
+ attrTween: transition_attrTween,
+ style: transition_style,
+ styleTween: transition_styleTween,
+ text: transition_text,
+ textTween: transition_textTween,
+ remove: transition_remove,
+ tween: tween,
+ delay: delay,
+ duration: duration,
+ ease: ease,
+ easeVarying: transition_easeVarying,
+ end: end,
+ [Symbol.iterator]: selection_prototype[Symbol.iterator]
+};
+
+;// CONCATENATED MODULE: ../node_modules/d3-ease/src/cubic.js
+function cubicIn(t) {
+ return t * t * t;
+}
+
+function cubicOut(t) {
+ return --t * t * t + 1;
+}
+
+function cubicInOut(t) {
+ return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2;
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/transition.js
+
+
+
+
+
+var defaultTiming = {
+ time: null, // Set on use.
+ delay: 0,
+ duration: 250,
+ ease: cubicInOut
+};
+
+function inherit(node, id) {
+ var timing;
+ while (!(timing = node.__transition) || !(timing = timing[id])) {
+ if (!(node = node.parentNode)) {
+ throw new Error(`transition ${id} not found`);
+ }
+ }
+ return timing;
+}
+
+/* harmony default export */ function selection_transition(name) {
+ var id,
+ timing;
+
+ if (name instanceof Transition) {
+ id = name._id, name = name._name;
+ } else {
+ id = newId(), (timing = defaultTiming).time = now(), name = name == null ? null : name + "";
+ }
+
+ for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
+ if (node = group[i]) {
+ schedule(node, name, id, i, group, timing || inherit(node, id));
+ }
+ }
+ }
+
+ return new Transition(groups, this._parents, name, id);
+}
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/index.js
+
+
+
+
+src_selection.prototype.interrupt = selection_interrupt;
+src_selection.prototype.transition = selection_transition;
+
+;// CONCATENATED MODULE: ../node_modules/d3-transition/src/index.js
+
+
+
+
+
+;// CONCATENATED MODULE: ./tooltip.js
+/* global event */
+
+
+
+
+
+
+function defaultLabel (d) {
+ return d.data.name
+}
+
+function defaultFlamegraphTooltip () {
+ var rootElement = src_select('body')
+ var tooltip = null
+ // Function to get HTML content from data.
+ var html = defaultLabel
+ // Function to get text content from data.
+ var text = defaultLabel
+ // Whether to use d3's .html() to set content, otherwise use .text().
+ var contentIsHTML = false
+
+ function tip () {
+ tooltip = rootElement
+ .append('div')
+ .style('display', 'none')
+ .style('position', 'absolute')
+ .style('opacity', 0)
+ .style('pointer-events', 'none')
+ .attr('class', 'd3-flame-graph-tip')
+ }
+
+ tip.show = function (d) {
+ tooltip
+ .style('display', 'block')
+ .style('left', event.pageX + 5 + 'px')
+ .style('top', event.pageY + 5 + 'px')
+ .transition()
+ .duration(200)
+ .style('opacity', 1)
+ .style('pointer-events', 'all')
+
+ if (contentIsHTML) {
+ tooltip.html(html(d))
+ } else {
+ tooltip.text(text(d))
+ }
+
+ return tip
+ }
+
+ tip.hide = function () {
+ tooltip
+ .style('display', 'none')
+ .transition()
+ .duration(200)
+ .style('opacity', 0)
+ .style('pointer-events', 'none')
+
+ return tip
+ }
+
+ /**
+ * Gets/sets a function converting the d3 data into the tooltip's textContent.
+ *
+ * Cannot be combined with tip.html().
+ */
+ tip.text = function (_) {
+ if (!arguments.length) return text
+ text = _
+ contentIsHTML = false
+ return tip
+ }
+
+ /**
+ * Gets/sets a function converting the d3 data into the tooltip's innerHTML.
+ *
+ * Cannot be combined with tip.text().
+ *
+ * @deprecated prefer tip.text().
+ */
+ tip.html = function (_) {
+ if (!arguments.length) return html
+ html = _
+ contentIsHTML = true
+ return tip
+ }
+
+ tip.destroy = function () {
+ tooltip.remove()
+ }
+
+ return tip
+}
+
+/******/ return __webpack_exports__;
+/******/ })()
+;
+});
\ No newline at end of file
diff --git a/development/charts/flamegraph/lib/d3-flamegraph.css b/development/charts/flamegraph/lib/d3-flamegraph.css
new file mode 100644
index 000000000..fa6f345ff
--- /dev/null
+++ b/development/charts/flamegraph/lib/d3-flamegraph.css
@@ -0,0 +1,46 @@
+.d3-flame-graph rect {
+ stroke: #EEEEEE;
+ fill-opacity: .8;
+}
+
+.d3-flame-graph rect:hover {
+ stroke: #474747;
+ stroke-width: 0.5;
+ cursor: pointer;
+}
+
+.d3-flame-graph-label {
+ pointer-events: none;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ font-size: 12px;
+ font-family: Verdana;
+ margin-left: 4px;
+ margin-right: 4px;
+ line-height: 1.5;
+ padding: 0 0 0;
+ font-weight: 400;
+ color: black;
+ text-align: left;
+}
+
+.d3-flame-graph .fade {
+ opacity: 0.6 !important;
+}
+
+.d3-flame-graph .title {
+ font-size: 20px;
+ font-family: Verdana;
+}
+
+.d3-flame-graph-tip {
+ background-color: black;
+ border: none;
+ border-radius: 3px;
+ padding: 5px 10px 5px 10px;
+ min-width: 250px;
+ text-align: left;
+ color: white;
+ z-index: 10;
+}
\ No newline at end of file
diff --git a/development/charts/flamegraph/lib/d3-flamegraph.js b/development/charts/flamegraph/lib/d3-flamegraph.js
new file mode 100644
index 000000000..eabb2c449
--- /dev/null
+++ b/development/charts/flamegraph/lib/d3-flamegraph.js
@@ -0,0 +1,5719 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+ if (typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory();
+ else if (typeof define === 'function' && define.amd) define([], factory);
+ else if (typeof exports === 'object') exports['flamegraph'] = factory();
+ else root['flamegraph'] = factory();
+})(self, function () {
+ return /******/ (() => {
+ // webpackBootstrap
+ /******/ 'use strict'; // The require scope
+ /******/ /******/ var __webpack_require__ = {}; /* webpack/runtime/define property getters */
+ /******/
+ /************************************************************************/
+ /******/ /******/ (() => {
+ /******/ // define getter functions for harmony exports
+ /******/ __webpack_require__.d = (exports, definition) => {
+ /******/ for (var key in definition) {
+ /******/ if (
+ __webpack_require__.o(definition, key) &&
+ !__webpack_require__.o(exports, key)
+ ) {
+ /******/ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: definition[key],
+ });
+ /******/
+ }
+ /******/
+ }
+ /******/
+ };
+ /******/
+ })(); /* webpack/runtime/hasOwnProperty shorthand */
+ /******/
+ /******/ /******/ (() => {
+ /******/ __webpack_require__.o = (obj, prop) =>
+ Object.prototype.hasOwnProperty.call(obj, prop);
+ /******/
+ })();
+ /******/
+ /************************************************************************/
+ var __webpack_exports__ = {};
+
+ // EXPORTS
+ __webpack_require__.d(__webpack_exports__, {
+ default: () => /* binding */ flamegraph,
+ }); // CONCATENATED MODULE: ../node_modules/d3-selection/src/selector.js
+
+ function none() {}
+
+ /* harmony default export */ function selector(selector) {
+ return selector == null
+ ? none
+ : function () {
+ return this.querySelector(selector);
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/select.js
+
+ /* harmony default export */ function selection_select(select) {
+ if (typeof select !== 'function') select = selector(select);
+
+ for (
+ var groups = this._groups,
+ m = groups.length,
+ subgroups = new Array(m),
+ j = 0;
+ j < m;
+ ++j
+ ) {
+ for (
+ var group = groups[j],
+ n = group.length,
+ subgroup = (subgroups[j] = new Array(n)),
+ node,
+ subnode,
+ i = 0;
+ i < n;
+ ++i
+ ) {
+ if (
+ (node = group[i]) &&
+ (subnode = select.call(node, node.__data__, i, group))
+ ) {
+ if ('__data__' in node) subnode.__data__ = node.__data__;
+ subgroup[i] = subnode;
+ }
+ }
+ }
+
+ return new Selection(subgroups, this._parents);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/array.js
+
+ // Given something array like (or null), returns something that is strictly an
+ // array. This is used to ensure that array-like objects passed to d3.selectAll
+ // or selection.selectAll are converted into proper arrays when creating a
+ // selection; we don’t ever want to create a selection backed by a live
+ // HTMLCollection or NodeList. However, note that selection.selectAll will use a
+ // static NodeList as a group, since it safely derived from querySelectorAll.
+ function array(x) {
+ return x == null ? [] : Array.isArray(x) ? x : Array.from(x);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selectorAll.js
+
+ function empty() {
+ return [];
+ }
+
+ /* harmony default export */ function selectorAll(selector) {
+ return selector == null
+ ? empty
+ : function () {
+ return this.querySelectorAll(selector);
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectAll.js
+
+ function arrayAll(select) {
+ return function () {
+ return array(select.apply(this, arguments));
+ };
+ }
+
+ /* harmony default export */ function selectAll(select) {
+ if (typeof select === 'function') select = arrayAll(select);
+ else select = selectorAll(select);
+
+ for (
+ var groups = this._groups,
+ m = groups.length,
+ subgroups = [],
+ parents = [],
+ j = 0;
+ j < m;
+ ++j
+ ) {
+ for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
+ if ((node = group[i])) {
+ subgroups.push(select.call(node, node.__data__, i, group));
+ parents.push(node);
+ }
+ }
+ }
+
+ return new Selection(subgroups, parents);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/matcher.js
+
+ /* harmony default export */ function matcher(selector) {
+ return function () {
+ return this.matches(selector);
+ };
+ }
+
+ function childMatcher(selector) {
+ return function (node) {
+ return node.matches(selector);
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChild.js
+
+ var find = Array.prototype.find;
+
+ function childFind(match) {
+ return function () {
+ return find.call(this.children, match);
+ };
+ }
+
+ function childFirst() {
+ return this.firstElementChild;
+ }
+
+ /* harmony default export */ function selectChild(match) {
+ return this.select(
+ match == null
+ ? childFirst
+ : childFind(
+ typeof match === 'function' ? match : childMatcher(match),
+ ),
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChildren.js
+
+ var filter = Array.prototype.filter;
+
+ function children() {
+ return Array.from(this.children);
+ }
+
+ function childrenFilter(match) {
+ return function () {
+ return filter.call(this.children, match);
+ };
+ }
+
+ /* harmony default export */ function selectChildren(match) {
+ return this.selectAll(
+ match == null
+ ? children
+ : childrenFilter(
+ typeof match === 'function' ? match : childMatcher(match),
+ ),
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/filter.js
+
+ /* harmony default export */ function selection_filter(match) {
+ if (typeof match !== 'function') match = matcher(match);
+
+ for (
+ var groups = this._groups,
+ m = groups.length,
+ subgroups = new Array(m),
+ j = 0;
+ j < m;
+ ++j
+ ) {
+ for (
+ var group = groups[j],
+ n = group.length,
+ subgroup = (subgroups[j] = []),
+ node,
+ i = 0;
+ i < n;
+ ++i
+ ) {
+ if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
+ subgroup.push(node);
+ }
+ }
+ }
+
+ return new Selection(subgroups, this._parents);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sparse.js
+
+ /* harmony default export */ function sparse(update) {
+ return new Array(update.length);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/enter.js
+
+ /* harmony default export */ function enter() {
+ return new Selection(
+ this._enter || this._groups.map(sparse),
+ this._parents,
+ );
+ }
+
+ function EnterNode(parent, datum) {
+ this.ownerDocument = parent.ownerDocument;
+ this.namespaceURI = parent.namespaceURI;
+ this._next = null;
+ this._parent = parent;
+ this.__data__ = datum;
+ }
+
+ EnterNode.prototype = {
+ constructor: EnterNode,
+ appendChild: function (child) {
+ return this._parent.insertBefore(child, this._next);
+ },
+ insertBefore: function (child, next) {
+ return this._parent.insertBefore(child, next);
+ },
+ querySelector: function (selector) {
+ return this._parent.querySelector(selector);
+ },
+ querySelectorAll: function (selector) {
+ return this._parent.querySelectorAll(selector);
+ },
+ }; // CONCATENATED MODULE: ../node_modules/d3-selection/src/constant.js
+
+ /* harmony default export */ function src_constant(x) {
+ return function () {
+ return x;
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/data.js
+
+ function bindIndex(parent, group, enter, update, exit, data) {
+ var i = 0,
+ node,
+ groupLength = group.length,
+ dataLength = data.length;
+
+ // Put any non-null nodes that fit into update.
+ // Put any null nodes into enter.
+ // Put any remaining data into enter.
+ for (; i < dataLength; ++i) {
+ if ((node = group[i])) {
+ node.__data__ = data[i];
+ update[i] = node;
+ } else {
+ enter[i] = new EnterNode(parent, data[i]);
+ }
+ }
+
+ // Put any non-null nodes that don’t fit into exit.
+ for (; i < groupLength; ++i) {
+ if ((node = group[i])) {
+ exit[i] = node;
+ }
+ }
+ }
+
+ function bindKey(parent, group, enter, update, exit, data, key) {
+ var i,
+ node,
+ nodeByKeyValue = new Map(),
+ groupLength = group.length,
+ dataLength = data.length,
+ keyValues = new Array(groupLength),
+ keyValue;
+
+ // Compute the key for each node.
+ // If multiple nodes have the same key, the duplicates are added to exit.
+ for (i = 0; i < groupLength; ++i) {
+ if ((node = group[i])) {
+ keyValues[i] = keyValue =
+ key.call(node, node.__data__, i, group) + '';
+ if (nodeByKeyValue.has(keyValue)) {
+ exit[i] = node;
+ } else {
+ nodeByKeyValue.set(keyValue, node);
+ }
+ }
+ }
+
+ // Compute the key for each datum.
+ // If there a node associated with this key, join and add it to update.
+ // If there is not (or the key is a duplicate), add it to enter.
+ for (i = 0; i < dataLength; ++i) {
+ keyValue = key.call(parent, data[i], i, data) + '';
+ if ((node = nodeByKeyValue.get(keyValue))) {
+ update[i] = node;
+ node.__data__ = data[i];
+ nodeByKeyValue.delete(keyValue);
+ } else {
+ enter[i] = new EnterNode(parent, data[i]);
+ }
+ }
+
+ // Add any remaining nodes that were not bound to data to exit.
+ for (i = 0; i < groupLength; ++i) {
+ if ((node = group[i]) && nodeByKeyValue.get(keyValues[i]) === node) {
+ exit[i] = node;
+ }
+ }
+ }
+
+ function datum(node) {
+ return node.__data__;
+ }
+
+ /* harmony default export */ function data(value, key) {
+ if (!arguments.length) return Array.from(this, datum);
+
+ var bind = key ? bindKey : bindIndex,
+ parents = this._parents,
+ groups = this._groups;
+
+ if (typeof value !== 'function') value = src_constant(value);
+
+ for (
+ var m = groups.length,
+ update = new Array(m),
+ enter = new Array(m),
+ exit = new Array(m),
+ j = 0;
+ j < m;
+ ++j
+ ) {
+ var parent = parents[j],
+ group = groups[j],
+ groupLength = group.length,
+ data = arraylike(
+ value.call(parent, parent && parent.__data__, j, parents),
+ ),
+ dataLength = data.length,
+ enterGroup = (enter[j] = new Array(dataLength)),
+ updateGroup = (update[j] = new Array(dataLength)),
+ exitGroup = (exit[j] = new Array(groupLength));
+
+ bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);
+
+ // Now connect the enter nodes to their following update node, such that
+ // appendChild can insert the materialized enter node before this node,
+ // rather than at the end of the parent node.
+ for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
+ if ((previous = enterGroup[i0])) {
+ if (i0 >= i1) i1 = i0 + 1;
+ while (!(next = updateGroup[i1]) && ++i1 < dataLength);
+ previous._next = next || null;
+ }
+ }
+ }
+
+ update = new Selection(update, parents);
+ update._enter = enter;
+ update._exit = exit;
+ return update;
+ }
+
+ // Given some data, this returns an array-like view of it: an object that
+ // exposes a length property and allows numeric indexing. Note that unlike
+ // selectAll, this isn’t worried about “live” collections because the resulting
+ // array will only be used briefly while data is being bound. (It is possible to
+ // cause the data to change while iterating by using a key function, but please
+ // don’t; we’d rather avoid a gratuitous copy.)
+ function arraylike(data) {
+ return typeof data === 'object' && 'length' in data
+ ? data // Array, TypedArray, NodeList, array-like
+ : Array.from(data); // Map, Set, iterable, string, or anything else
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/exit.js
+
+ /* harmony default export */ function exit() {
+ return new Selection(
+ this._exit || this._groups.map(sparse),
+ this._parents,
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/join.js
+
+ /* harmony default export */ function join(onenter, onupdate, onexit) {
+ var enter = this.enter(),
+ update = this,
+ exit = this.exit();
+ if (typeof onenter === 'function') {
+ enter = onenter(enter);
+ if (enter) enter = enter.selection();
+ } else {
+ enter = enter.append(onenter + '');
+ }
+ if (onupdate != null) {
+ update = onupdate(update);
+ if (update) update = update.selection();
+ }
+ if (onexit == null) exit.remove();
+ else onexit(exit);
+ return enter && update ? enter.merge(update).order() : update;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/merge.js
+
+ /* harmony default export */ function merge(context) {
+ var selection = context.selection ? context.selection() : context;
+
+ for (
+ var groups0 = this._groups,
+ groups1 = selection._groups,
+ m0 = groups0.length,
+ m1 = groups1.length,
+ m = Math.min(m0, m1),
+ merges = new Array(m0),
+ j = 0;
+ j < m;
+ ++j
+ ) {
+ for (
+ var group0 = groups0[j],
+ group1 = groups1[j],
+ n = group0.length,
+ merge = (merges[j] = new Array(n)),
+ node,
+ i = 0;
+ i < n;
+ ++i
+ ) {
+ if ((node = group0[i] || group1[i])) {
+ merge[i] = node;
+ }
+ }
+ }
+
+ for (; j < m0; ++j) {
+ merges[j] = groups0[j];
+ }
+
+ return new Selection(merges, this._parents);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/order.js
+
+ /* harmony default export */ function order() {
+ for (var groups = this._groups, j = -1, m = groups.length; ++j < m; ) {
+ for (
+ var group = groups[j], i = group.length - 1, next = group[i], node;
+ --i >= 0;
+
+ ) {
+ if ((node = group[i])) {
+ if (next && node.compareDocumentPosition(next) ^ 4)
+ next.parentNode.insertBefore(node, next);
+ next = node;
+ }
+ }
+ }
+
+ return this;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sort.js
+
+ /* harmony default export */ function sort(compare) {
+ if (!compare) compare = ascending;
+
+ function compareNode(a, b) {
+ return a && b ? compare(a.__data__, b.__data__) : !a - !b;
+ }
+
+ for (
+ var groups = this._groups,
+ m = groups.length,
+ sortgroups = new Array(m),
+ j = 0;
+ j < m;
+ ++j
+ ) {
+ for (
+ var group = groups[j],
+ n = group.length,
+ sortgroup = (sortgroups[j] = new Array(n)),
+ node,
+ i = 0;
+ i < n;
+ ++i
+ ) {
+ if ((node = group[i])) {
+ sortgroup[i] = node;
+ }
+ }
+ sortgroup.sort(compareNode);
+ }
+
+ return new Selection(sortgroups, this._parents).order();
+ }
+
+ function ascending(a, b) {
+ return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/call.js
+
+ /* harmony default export */ function call() {
+ var callback = arguments[0];
+ arguments[0] = this;
+ callback.apply(null, arguments);
+ return this;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/nodes.js
+
+ /* harmony default export */ function nodes() {
+ return Array.from(this);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/node.js
+
+ /* harmony default export */ function node() {
+ for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
+ for (var group = groups[j], i = 0, n = group.length; i < n; ++i) {
+ var node = group[i];
+ if (node) return node;
+ }
+ }
+
+ return null;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/size.js
+
+ /* harmony default export */ function size() {
+ let size = 0;
+ for (const node of this) ++size; // eslint-disable-line no-unused-vars
+ return size;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/empty.js
+
+ /* harmony default export */ function selection_empty() {
+ return !this.node();
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/each.js
+
+ /* harmony default export */ function each(callback) {
+ for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
+ for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
+ if ((node = group[i])) callback.call(node, node.__data__, i, group);
+ }
+ }
+
+ return this;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/namespaces.js
+
+ var xhtml = 'http://www.w3.org/1999/xhtml';
+
+ /* harmony default export */ const namespaces = {
+ svg: 'http://www.w3.org/2000/svg',
+ xhtml: xhtml,
+ xlink: 'http://www.w3.org/1999/xlink',
+ xml: 'http://www.w3.org/XML/1998/namespace',
+ xmlns: 'http://www.w3.org/2000/xmlns/',
+ }; // CONCATENATED MODULE: ../node_modules/d3-selection/src/namespace.js
+
+ /* harmony default export */ function namespace(name) {
+ var prefix = (name += ''),
+ i = prefix.indexOf(':');
+ if (i >= 0 && (prefix = name.slice(0, i)) !== 'xmlns')
+ name = name.slice(i + 1);
+ return namespaces.hasOwnProperty(prefix)
+ ? { space: namespaces[prefix], local: name }
+ : name; // eslint-disable-line no-prototype-builtins
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/attr.js
+
+ function attrRemove(name) {
+ return function () {
+ this.removeAttribute(name);
+ };
+ }
+
+ function attrRemoveNS(fullname) {
+ return function () {
+ this.removeAttributeNS(fullname.space, fullname.local);
+ };
+ }
+
+ function attrConstant(name, value) {
+ return function () {
+ this.setAttribute(name, value);
+ };
+ }
+
+ function attrConstantNS(fullname, value) {
+ return function () {
+ this.setAttributeNS(fullname.space, fullname.local, value);
+ };
+ }
+
+ function attrFunction(name, value) {
+ return function () {
+ var v = value.apply(this, arguments);
+ if (v == null) this.removeAttribute(name);
+ else this.setAttribute(name, v);
+ };
+ }
+
+ function attrFunctionNS(fullname, value) {
+ return function () {
+ var v = value.apply(this, arguments);
+ if (v == null) this.removeAttributeNS(fullname.space, fullname.local);
+ else this.setAttributeNS(fullname.space, fullname.local, v);
+ };
+ }
+
+ /* harmony default export */ function attr(name, value) {
+ var fullname = namespace(name);
+
+ if (arguments.length < 2) {
+ var node = this.node();
+ return fullname.local
+ ? node.getAttributeNS(fullname.space, fullname.local)
+ : node.getAttribute(fullname);
+ }
+
+ return this.each(
+ (value == null
+ ? fullname.local
+ ? attrRemoveNS
+ : attrRemove
+ : typeof value === 'function'
+ ? fullname.local
+ ? attrFunctionNS
+ : attrFunction
+ : fullname.local
+ ? attrConstantNS
+ : attrConstant)(fullname, value),
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/window.js
+
+ /* harmony default export */ function src_window(node) {
+ return (
+ (node.ownerDocument && node.ownerDocument.defaultView) || // node is a Node
+ (node.document && node) || // node is a Window
+ node.defaultView
+ ); // node is a Document
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/style.js
+
+ function styleRemove(name) {
+ return function () {
+ this.style.removeProperty(name);
+ };
+ }
+
+ function styleConstant(name, value, priority) {
+ return function () {
+ this.style.setProperty(name, value, priority);
+ };
+ }
+
+ function styleFunction(name, value, priority) {
+ return function () {
+ var v = value.apply(this, arguments);
+ if (v == null) this.style.removeProperty(name);
+ else this.style.setProperty(name, v, priority);
+ };
+ }
+
+ /* harmony default export */ function style(name, value, priority) {
+ return arguments.length > 1
+ ? this.each(
+ (value == null
+ ? styleRemove
+ : typeof value === 'function'
+ ? styleFunction
+ : styleConstant)(name, value, priority == null ? '' : priority),
+ )
+ : styleValue(this.node(), name);
+ }
+
+ function styleValue(node, name) {
+ return (
+ node.style.getPropertyValue(name) ||
+ src_window(node).getComputedStyle(node, null).getPropertyValue(name)
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/property.js
+
+ function propertyRemove(name) {
+ return function () {
+ delete this[name];
+ };
+ }
+
+ function propertyConstant(name, value) {
+ return function () {
+ this[name] = value;
+ };
+ }
+
+ function propertyFunction(name, value) {
+ return function () {
+ var v = value.apply(this, arguments);
+ if (v == null) delete this[name];
+ else this[name] = v;
+ };
+ }
+
+ /* harmony default export */ function property(name, value) {
+ return arguments.length > 1
+ ? this.each(
+ (value == null
+ ? propertyRemove
+ : typeof value === 'function'
+ ? propertyFunction
+ : propertyConstant)(name, value),
+ )
+ : this.node()[name];
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/classed.js
+
+ function classArray(string) {
+ return string.trim().split(/^|\s+/);
+ }
+
+ function classList(node) {
+ return node.classList || new ClassList(node);
+ }
+
+ function ClassList(node) {
+ this._node = node;
+ this._names = classArray(node.getAttribute('class') || '');
+ }
+
+ ClassList.prototype = {
+ add: function (name) {
+ var i = this._names.indexOf(name);
+ if (i < 0) {
+ this._names.push(name);
+ this._node.setAttribute('class', this._names.join(' '));
+ }
+ },
+ remove: function (name) {
+ var i = this._names.indexOf(name);
+ if (i >= 0) {
+ this._names.splice(i, 1);
+ this._node.setAttribute('class', this._names.join(' '));
+ }
+ },
+ contains: function (name) {
+ return this._names.indexOf(name) >= 0;
+ },
+ };
+
+ function classedAdd(node, names) {
+ var list = classList(node),
+ i = -1,
+ n = names.length;
+ while (++i < n) list.add(names[i]);
+ }
+
+ function classedRemove(node, names) {
+ var list = classList(node),
+ i = -1,
+ n = names.length;
+ while (++i < n) list.remove(names[i]);
+ }
+
+ function classedTrue(names) {
+ return function () {
+ classedAdd(this, names);
+ };
+ }
+
+ function classedFalse(names) {
+ return function () {
+ classedRemove(this, names);
+ };
+ }
+
+ function classedFunction(names, value) {
+ return function () {
+ (value.apply(this, arguments) ? classedAdd : classedRemove)(
+ this,
+ names,
+ );
+ };
+ }
+
+ /* harmony default export */ function classed(name, value) {
+ var names = classArray(name + '');
+
+ if (arguments.length < 2) {
+ var list = classList(this.node()),
+ i = -1,
+ n = names.length;
+ while (++i < n) if (!list.contains(names[i])) return false;
+ return true;
+ }
+
+ return this.each(
+ (typeof value === 'function'
+ ? classedFunction
+ : value
+ ? classedTrue
+ : classedFalse)(names, value),
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/text.js
+
+ function textRemove() {
+ this.textContent = '';
+ }
+
+ function textConstant(value) {
+ return function () {
+ this.textContent = value;
+ };
+ }
+
+ function textFunction(value) {
+ return function () {
+ var v = value.apply(this, arguments);
+ this.textContent = v == null ? '' : v;
+ };
+ }
+
+ /* harmony default export */ function selection_text(value) {
+ return arguments.length
+ ? this.each(
+ value == null
+ ? textRemove
+ : (typeof value === 'function' ? textFunction : textConstant)(
+ value,
+ ),
+ )
+ : this.node().textContent;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/html.js
+
+ function htmlRemove() {
+ this.innerHTML = '';
+ }
+
+ function htmlConstant(value) {
+ return function () {
+ this.innerHTML = value;
+ };
+ }
+
+ function htmlFunction(value) {
+ return function () {
+ var v = value.apply(this, arguments);
+ this.innerHTML = v == null ? '' : v;
+ };
+ }
+
+ /* harmony default export */ function html(value) {
+ return arguments.length
+ ? this.each(
+ value == null
+ ? htmlRemove
+ : (typeof value === 'function' ? htmlFunction : htmlConstant)(
+ value,
+ ),
+ )
+ : this.node().innerHTML;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/raise.js
+
+ function raise() {
+ if (this.nextSibling) this.parentNode.appendChild(this);
+ }
+
+ /* harmony default export */ function selection_raise() {
+ return this.each(raise);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/lower.js
+
+ function lower() {
+ if (this.previousSibling)
+ this.parentNode.insertBefore(this, this.parentNode.firstChild);
+ }
+
+ /* harmony default export */ function selection_lower() {
+ return this.each(lower);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/creator.js
+
+ function creatorInherit(name) {
+ return function () {
+ var document = this.ownerDocument,
+ uri = this.namespaceURI;
+ return uri === xhtml && document.documentElement.namespaceURI === xhtml
+ ? document.createElement(name)
+ : document.createElementNS(uri, name);
+ };
+ }
+
+ function creatorFixed(fullname) {
+ return function () {
+ return this.ownerDocument.createElementNS(
+ fullname.space,
+ fullname.local,
+ );
+ };
+ }
+
+ /* harmony default export */ function creator(name) {
+ var fullname = namespace(name);
+ return (fullname.local ? creatorFixed : creatorInherit)(fullname);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/append.js
+
+ /* harmony default export */ function append(name) {
+ var create = typeof name === 'function' ? name : creator(name);
+ return this.select(function () {
+ return this.appendChild(create.apply(this, arguments));
+ });
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/insert.js
+
+ function constantNull() {
+ return null;
+ }
+
+ /* harmony default export */ function insert(name, before) {
+ var create = typeof name === 'function' ? name : creator(name),
+ select =
+ before == null
+ ? constantNull
+ : typeof before === 'function'
+ ? before
+ : selector(before);
+ return this.select(function () {
+ return this.insertBefore(
+ create.apply(this, arguments),
+ select.apply(this, arguments) || null,
+ );
+ });
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/remove.js
+
+ function remove() {
+ var parent = this.parentNode;
+ if (parent) parent.removeChild(this);
+ }
+
+ /* harmony default export */ function selection_remove() {
+ return this.each(remove);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/clone.js
+
+ function selection_cloneShallow() {
+ var clone = this.cloneNode(false),
+ parent = this.parentNode;
+ return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
+ }
+
+ function selection_cloneDeep() {
+ var clone = this.cloneNode(true),
+ parent = this.parentNode;
+ return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
+ }
+
+ /* harmony default export */ function clone(deep) {
+ return this.select(deep ? selection_cloneDeep : selection_cloneShallow);
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/datum.js
+
+ /* harmony default export */ function selection_datum(value) {
+ return arguments.length
+ ? this.property('__data__', value)
+ : this.node().__data__;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/on.js
+
+ function contextListener(listener) {
+ return function (event) {
+ listener.call(this, event, this.__data__);
+ };
+ }
+
+ function parseTypenames(typenames) {
+ return typenames
+ .trim()
+ .split(/^|\s+/)
+ .map(function (t) {
+ var name = '',
+ i = t.indexOf('.');
+ if (i >= 0) (name = t.slice(i + 1)), (t = t.slice(0, i));
+ return { type: t, name: name };
+ });
+ }
+
+ function onRemove(typename) {
+ return function () {
+ var on = this.__on;
+ if (!on) return;
+ for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {
+ if (
+ ((o = on[j]),
+ (!typename.type || o.type === typename.type) &&
+ o.name === typename.name)
+ ) {
+ this.removeEventListener(o.type, o.listener, o.options);
+ } else {
+ on[++i] = o;
+ }
+ }
+ if (++i) on.length = i;
+ else delete this.__on;
+ };
+ }
+
+ function onAdd(typename, value, options) {
+ return function () {
+ var on = this.__on,
+ o,
+ listener = contextListener(value);
+ if (on)
+ for (var j = 0, m = on.length; j < m; ++j) {
+ if (
+ (o = on[j]).type === typename.type &&
+ o.name === typename.name
+ ) {
+ this.removeEventListener(o.type, o.listener, o.options);
+ this.addEventListener(
+ o.type,
+ (o.listener = listener),
+ (o.options = options),
+ );
+ o.value = value;
+ return;
+ }
+ }
+ this.addEventListener(typename.type, listener, options);
+ o = {
+ type: typename.type,
+ name: typename.name,
+ value: value,
+ listener: listener,
+ options: options,
+ };
+ if (!on) this.__on = [o];
+ else on.push(o);
+ };
+ }
+
+ /* harmony default export */ function on(typename, value, options) {
+ var typenames = parseTypenames(typename + ''),
+ i,
+ n = typenames.length,
+ t;
+
+ if (arguments.length < 2) {
+ var on = this.node().__on;
+ if (on)
+ for (var j = 0, m = on.length, o; j < m; ++j) {
+ for (i = 0, o = on[j]; i < n; ++i) {
+ if ((t = typenames[i]).type === o.type && t.name === o.name) {
+ return o.value;
+ }
+ }
+ }
+ return;
+ }
+
+ on = value ? onAdd : onRemove;
+ for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options));
+ return this;
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/dispatch.js
+
+ function dispatchEvent(node, type, params) {
+ var window = src_window(node),
+ event = window.CustomEvent;
+
+ if (typeof event === 'function') {
+ event = new event(type, params);
+ } else {
+ event = window.document.createEvent('Event');
+ if (params)
+ event.initEvent(type, params.bubbles, params.cancelable),
+ (event.detail = params.detail);
+ else event.initEvent(type, false, false);
+ }
+
+ node.dispatchEvent(event);
+ }
+
+ function dispatchConstant(type, params) {
+ return function () {
+ return dispatchEvent(this, type, params);
+ };
+ }
+
+ function dispatchFunction(type, params) {
+ return function () {
+ return dispatchEvent(this, type, params.apply(this, arguments));
+ };
+ }
+
+ /* harmony default export */ function dispatch(type, params) {
+ return this.each(
+ (typeof params === 'function' ? dispatchFunction : dispatchConstant)(
+ type,
+ params,
+ ),
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/iterator.js
+
+ /* harmony default export */ function* iterator() {
+ for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
+ for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
+ if ((node = group[i])) yield node;
+ }
+ }
+ } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/index.js
+
+ var root = [null];
+
+ function Selection(groups, parents) {
+ this._groups = groups;
+ this._parents = parents;
+ }
+
+ function selection() {
+ return new Selection([[document.documentElement]], root);
+ }
+
+ function selection_selection() {
+ return this;
+ }
+
+ Selection.prototype = selection.prototype = {
+ constructor: Selection,
+ select: selection_select,
+ selectAll: selectAll,
+ selectChild: selectChild,
+ selectChildren: selectChildren,
+ filter: selection_filter,
+ data: data,
+ enter: enter,
+ exit: exit,
+ join: join,
+ merge: merge,
+ selection: selection_selection,
+ order: order,
+ sort: sort,
+ call: call,
+ nodes: nodes,
+ node: node,
+ size: size,
+ empty: selection_empty,
+ each: each,
+ attr: attr,
+ style: style,
+ property: property,
+ classed: classed,
+ text: selection_text,
+ html: html,
+ raise: selection_raise,
+ lower: selection_lower,
+ append: append,
+ insert: insert,
+ remove: selection_remove,
+ clone: clone,
+ datum: selection_datum,
+ on: on,
+ dispatch: dispatch,
+ [Symbol.iterator]: iterator,
+ };
+
+ /* harmony default export */ const src_selection = selection; // CONCATENATED MODULE: ../node_modules/d3-selection/src/select.js
+
+ /* harmony default export */ function src_select(selector) {
+ return typeof selector === 'string'
+ ? new Selection(
+ [[document.querySelector(selector)]],
+ [document.documentElement],
+ )
+ : new Selection([[selector]], root);
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatDecimal.js
+
+ /* harmony default export */ function formatDecimal(x) {
+ return Math.abs((x = Math.round(x))) >= 1e21
+ ? x.toLocaleString('en').replace(/,/g, '')
+ : x.toString(10);
+ }
+
+ // Computes the decimal coefficient and exponent of the specified number x with
+ // significant digits p, where x is positive and p is in [1, 21] or undefined.
+ // For example, formatDecimalParts(1.23) returns ["123", 0].
+ function formatDecimalParts(x, p) {
+ if (
+ (i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf(
+ 'e',
+ )) < 0
+ )
+ return null; // NaN, ±Infinity
+ var i,
+ coefficient = x.slice(0, i);
+
+ // The string returned by toExponential either has the form \d\.\d+e[-+]\d+
+ // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3).
+ return [
+ coefficient.length > 1
+ ? coefficient[0] + coefficient.slice(2)
+ : coefficient,
+ +x.slice(i + 1),
+ ];
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/exponent.js
+
+ /* harmony default export */ function exponent(x) {
+ return (x = formatDecimalParts(Math.abs(x))), x ? x[1] : NaN;
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatGroup.js
+
+ /* harmony default export */ function formatGroup(grouping, thousands) {
+ return function (value, width) {
+ var i = value.length,
+ t = [],
+ j = 0,
+ g = grouping[0],
+ length = 0;
+
+ while (i > 0 && g > 0) {
+ if (length + g + 1 > width) g = Math.max(1, width - length);
+ t.push(value.substring((i -= g), i + g));
+ if ((length += g + 1) > width) break;
+ g = grouping[(j = (j + 1) % grouping.length)];
+ }
+
+ return t.reverse().join(thousands);
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatNumerals.js
+
+ /* harmony default export */ function formatNumerals(numerals) {
+ return function (value) {
+ return value.replace(/[0-9]/g, function (i) {
+ return numerals[+i];
+ });
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatSpecifier.js
+
+ // [[fill]align][sign][symbol][0][width][,][.precision][~][type]
+ var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;
+
+ function formatSpecifier(specifier) {
+ if (!(match = re.exec(specifier)))
+ throw new Error('invalid format: ' + specifier);
+ var match;
+ return new FormatSpecifier({
+ fill: match[1],
+ align: match[2],
+ sign: match[3],
+ symbol: match[4],
+ zero: match[5],
+ width: match[6],
+ comma: match[7],
+ precision: match[8] && match[8].slice(1),
+ trim: match[9],
+ type: match[10],
+ });
+ }
+
+ formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof
+
+ function FormatSpecifier(specifier) {
+ this.fill = specifier.fill === undefined ? ' ' : specifier.fill + '';
+ this.align = specifier.align === undefined ? '>' : specifier.align + '';
+ this.sign = specifier.sign === undefined ? '-' : specifier.sign + '';
+ this.symbol = specifier.symbol === undefined ? '' : specifier.symbol + '';
+ this.zero = !!specifier.zero;
+ this.width = specifier.width === undefined ? undefined : +specifier.width;
+ this.comma = !!specifier.comma;
+ this.precision =
+ specifier.precision === undefined ? undefined : +specifier.precision;
+ this.trim = !!specifier.trim;
+ this.type = specifier.type === undefined ? '' : specifier.type + '';
+ }
+
+ FormatSpecifier.prototype.toString = function () {
+ return (
+ this.fill +
+ this.align +
+ this.sign +
+ this.symbol +
+ (this.zero ? '0' : '') +
+ (this.width === undefined ? '' : Math.max(1, this.width | 0)) +
+ (this.comma ? ',' : '') +
+ (this.precision === undefined
+ ? ''
+ : '.' + Math.max(0, this.precision | 0)) +
+ (this.trim ? '~' : '') +
+ this.type
+ );
+ }; // CONCATENATED MODULE: ../node_modules/d3-format/src/formatTrim.js
+
+ // Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k.
+ /* harmony default export */ function formatTrim(s) {
+ out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) {
+ switch (s[i]) {
+ case '.':
+ i0 = i1 = i;
+ break;
+ case '0':
+ if (i0 === 0) i0 = i;
+ i1 = i;
+ break;
+ default:
+ if (!+s[i]) break out;
+ if (i0 > 0) i0 = 0;
+ break;
+ }
+ }
+ return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s;
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatPrefixAuto.js
+
+ var prefixExponent;
+
+ /* harmony default export */ function formatPrefixAuto(x, p) {
+ var d = formatDecimalParts(x, p);
+ if (!d) return x + '';
+ var coefficient = d[0],
+ exponent = d[1],
+ i =
+ exponent -
+ (prefixExponent =
+ Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) +
+ 1,
+ n = coefficient.length;
+ return i === n
+ ? coefficient
+ : i > n
+ ? coefficient + new Array(i - n + 1).join('0')
+ : i > 0
+ ? coefficient.slice(0, i) + '.' + coefficient.slice(i)
+ : '0.' +
+ new Array(1 - i).join('0') +
+ formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y!
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatRounded.js
+
+ /* harmony default export */ function formatRounded(x, p) {
+ var d = formatDecimalParts(x, p);
+ if (!d) return x + '';
+ var coefficient = d[0],
+ exponent = d[1];
+ return exponent < 0
+ ? '0.' + new Array(-exponent).join('0') + coefficient
+ : coefficient.length > exponent + 1
+ ? coefficient.slice(0, exponent + 1) +
+ '.' +
+ coefficient.slice(exponent + 1)
+ : coefficient + new Array(exponent - coefficient.length + 2).join('0');
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatTypes.js
+
+ /* harmony default export */ const formatTypes = {
+ '%': (x, p) => (x * 100).toFixed(p),
+ b: (x) => Math.round(x).toString(2),
+ c: (x) => x + '',
+ d: formatDecimal,
+ e: (x, p) => x.toExponential(p),
+ f: (x, p) => x.toFixed(p),
+ g: (x, p) => x.toPrecision(p),
+ o: (x) => Math.round(x).toString(8),
+ p: (x, p) => formatRounded(x * 100, p),
+ r: formatRounded,
+ s: formatPrefixAuto,
+ X: (x) => Math.round(x).toString(16).toUpperCase(),
+ x: (x) => Math.round(x).toString(16),
+ }; // CONCATENATED MODULE: ../node_modules/d3-format/src/identity.js
+
+ /* harmony default export */ function identity(x) {
+ return x;
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/locale.js
+
+ var map = Array.prototype.map,
+ prefixes = [
+ 'y',
+ 'z',
+ 'a',
+ 'f',
+ 'p',
+ 'n',
+ 'µ',
+ 'm',
+ '',
+ 'k',
+ 'M',
+ 'G',
+ 'T',
+ 'P',
+ 'E',
+ 'Z',
+ 'Y',
+ ];
+
+ /* harmony default export */ function locale(locale) {
+ var group =
+ locale.grouping === undefined || locale.thousands === undefined
+ ? identity
+ : formatGroup(
+ map.call(locale.grouping, Number),
+ locale.thousands + '',
+ ),
+ currencyPrefix =
+ locale.currency === undefined ? '' : locale.currency[0] + '',
+ currencySuffix =
+ locale.currency === undefined ? '' : locale.currency[1] + '',
+ decimal = locale.decimal === undefined ? '.' : locale.decimal + '',
+ numerals =
+ locale.numerals === undefined
+ ? identity
+ : formatNumerals(map.call(locale.numerals, String)),
+ percent = locale.percent === undefined ? '%' : locale.percent + '',
+ minus = locale.minus === undefined ? '−' : locale.minus + '',
+ nan = locale.nan === undefined ? 'NaN' : locale.nan + '';
+
+ function newFormat(specifier) {
+ specifier = formatSpecifier(specifier);
+
+ var fill = specifier.fill,
+ align = specifier.align,
+ sign = specifier.sign,
+ symbol = specifier.symbol,
+ zero = specifier.zero,
+ width = specifier.width,
+ comma = specifier.comma,
+ precision = specifier.precision,
+ trim = specifier.trim,
+ type = specifier.type;
+
+ // The "n" type is an alias for ",g".
+ if (type === 'n') (comma = true), (type = 'g');
+ // The "" type, and any invalid type, is an alias for ".12~g".
+ else if (!formatTypes[type])
+ precision === undefined && (precision = 12),
+ (trim = true),
+ (type = 'g');
+
+ // If zero fill is specified, padding goes after sign and before digits.
+ if (zero || (fill === '0' && align === '='))
+ (zero = true), (fill = '0'), (align = '=');
+
+ // Compute the prefix and suffix.
+ // For SI-prefix, the suffix is lazily computed.
+ var prefix =
+ symbol === '$'
+ ? currencyPrefix
+ : symbol === '#' && /[boxX]/.test(type)
+ ? '0' + type.toLowerCase()
+ : '',
+ suffix =
+ symbol === '$' ? currencySuffix : /[%p]/.test(type) ? percent : '';
+
+ // What format function should we use?
+ // Is this an integer type?
+ // Can this type generate exponential notation?
+ var formatType = formatTypes[type],
+ maybeSuffix = /[defgprs%]/.test(type);
+
+ // Set the default precision if not specified,
+ // or clamp the specified precision to the supported range.
+ // For significant precision, it must be in [1, 21].
+ // For fixed precision, it must be in [0, 20].
+ precision =
+ precision === undefined
+ ? 6
+ : /[gprs]/.test(type)
+ ? Math.max(1, Math.min(21, precision))
+ : Math.max(0, Math.min(20, precision));
+
+ function format(value) {
+ var valuePrefix = prefix,
+ valueSuffix = suffix,
+ i,
+ n,
+ c;
+
+ if (type === 'c') {
+ valueSuffix = formatType(value) + valueSuffix;
+ value = '';
+ } else {
+ value = +value;
+
+ // Determine the sign. -0 is not less than 0, but 1 / -0 is!
+ var valueNegative = value < 0 || 1 / value < 0;
+
+ // Perform the initial formatting.
+ value = isNaN(value) ? nan : formatType(Math.abs(value), precision);
+
+ // Trim insignificant zeros.
+ if (trim) value = formatTrim(value);
+
+ // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign.
+ if (valueNegative && +value === 0 && sign !== '+')
+ valueNegative = false;
+
+ // Compute the prefix and suffix.
+ valuePrefix =
+ (valueNegative
+ ? sign === '('
+ ? sign
+ : minus
+ : sign === '-' || sign === '('
+ ? ''
+ : sign) + valuePrefix;
+ valueSuffix =
+ (type === 's' ? prefixes[8 + prefixExponent / 3] : '') +
+ valueSuffix +
+ (valueNegative && sign === '(' ? ')' : '');
+
+ // Break the formatted value into the integer “value” part that can be
+ // grouped, and fractional or exponential “suffix” part that is not.
+ if (maybeSuffix) {
+ (i = -1), (n = value.length);
+ while (++i < n) {
+ if (((c = value.charCodeAt(i)), 48 > c || c > 57)) {
+ valueSuffix =
+ (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) +
+ valueSuffix;
+ value = value.slice(0, i);
+ break;
+ }
+ }
+ }
+ }
+
+ // If the fill character is not "0", grouping is applied before padding.
+ if (comma && !zero) value = group(value, Infinity);
+
+ // Compute the padding.
+ var length = valuePrefix.length + value.length + valueSuffix.length,
+ padding =
+ length < width ? new Array(width - length + 1).join(fill) : '';
+
+ // If the fill character is "0", grouping is applied after padding.
+ if (comma && zero)
+ (value = group(
+ padding + value,
+ padding.length ? width - valueSuffix.length : Infinity,
+ )),
+ (padding = '');
+
+ // Reconstruct the final output based on the desired alignment.
+ switch (align) {
+ case '<':
+ value = valuePrefix + value + valueSuffix + padding;
+ break;
+ case '=':
+ value = valuePrefix + padding + value + valueSuffix;
+ break;
+ case '^':
+ value =
+ padding.slice(0, (length = padding.length >> 1)) +
+ valuePrefix +
+ value +
+ valueSuffix +
+ padding.slice(length);
+ break;
+ default:
+ value = padding + valuePrefix + value + valueSuffix;
+ break;
+ }
+
+ return numerals(value);
+ }
+
+ format.toString = function () {
+ return specifier + '';
+ };
+
+ return format;
+ }
+
+ function formatPrefix(specifier, value) {
+ var f = newFormat(
+ ((specifier = formatSpecifier(specifier)),
+ (specifier.type = 'f'),
+ specifier),
+ ),
+ e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3,
+ k = Math.pow(10, -e),
+ prefix = prefixes[8 + e / 3];
+ return function (value) {
+ return f(k * value) + prefix;
+ };
+ }
+
+ return {
+ format: newFormat,
+ formatPrefix: formatPrefix,
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/defaultLocale.js
+
+ var defaultLocale_locale;
+ var format;
+ var formatPrefix;
+
+ defaultLocale({
+ thousands: ',',
+ grouping: [3],
+ currency: ['$', ''],
+ });
+
+ function defaultLocale(definition) {
+ defaultLocale_locale = locale(definition);
+ format = defaultLocale_locale.format;
+ formatPrefix = defaultLocale_locale.formatPrefix;
+ return defaultLocale_locale;
+ } // CONCATENATED MODULE: ../node_modules/d3-array/src/ascending.js
+
+ function ascending_ascending(a, b) {
+ return a == null || b == null
+ ? NaN
+ : a < b
+ ? -1
+ : a > b
+ ? 1
+ : a >= b
+ ? 0
+ : NaN;
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/treemap/round.js
+
+ /* harmony default export */ function treemap_round(node) {
+ node.x0 = Math.round(node.x0);
+ node.y0 = Math.round(node.y0);
+ node.x1 = Math.round(node.x1);
+ node.y1 = Math.round(node.y1);
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/treemap/dice.js
+
+ /* harmony default export */ function dice(parent, x0, y0, x1, y1) {
+ var nodes = parent.children,
+ node,
+ i = -1,
+ n = nodes.length,
+ k = parent.value && (x1 - x0) / parent.value;
+
+ while (++i < n) {
+ (node = nodes[i]), (node.y0 = y0), (node.y1 = y1);
+ (node.x0 = x0), (node.x1 = x0 += node.value * k);
+ }
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/partition.js
+
+ /* harmony default export */ function partition() {
+ var dx = 1,
+ dy = 1,
+ padding = 0,
+ round = false;
+
+ function partition(root) {
+ var n = root.height + 1;
+ root.x0 = root.y0 = padding;
+ root.x1 = dx;
+ root.y1 = dy / n;
+ root.eachBefore(positionNode(dy, n));
+ if (round) root.eachBefore(treemap_round);
+ return root;
+ }
+
+ function positionNode(dy, n) {
+ return function (node) {
+ if (node.children) {
+ dice(
+ node,
+ node.x0,
+ (dy * (node.depth + 1)) / n,
+ node.x1,
+ (dy * (node.depth + 2)) / n,
+ );
+ }
+ var x0 = node.x0,
+ y0 = node.y0,
+ x1 = node.x1 - padding,
+ y1 = node.y1 - padding;
+ if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
+ if (y1 < y0) y0 = y1 = (y0 + y1) / 2;
+ node.x0 = x0;
+ node.y0 = y0;
+ node.x1 = x1;
+ node.y1 = y1;
+ };
+ }
+
+ partition.round = function (x) {
+ return arguments.length ? ((round = !!x), partition) : round;
+ };
+
+ partition.size = function (x) {
+ return arguments.length
+ ? ((dx = +x[0]), (dy = +x[1]), partition)
+ : [dx, dy];
+ };
+
+ partition.padding = function (x) {
+ return arguments.length ? ((padding = +x), partition) : padding;
+ };
+
+ return partition;
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/count.js
+
+ function count(node) {
+ var sum = 0,
+ children = node.children,
+ i = children && children.length;
+ if (!i) sum = 1;
+ else while (--i >= 0) sum += children[i].value;
+ node.value = sum;
+ }
+
+ /* harmony default export */ function hierarchy_count() {
+ return this.eachAfter(count);
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/each.js
+
+ /* harmony default export */ function hierarchy_each(callback, that) {
+ let index = -1;
+ for (const node of this) {
+ callback.call(that, node, ++index, this);
+ }
+ return this;
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/eachBefore.js
+
+ /* harmony default export */ function eachBefore(callback, that) {
+ var node = this,
+ nodes = [node],
+ children,
+ i,
+ index = -1;
+ while ((node = nodes.pop())) {
+ callback.call(that, node, ++index, this);
+ if ((children = node.children)) {
+ for (i = children.length - 1; i >= 0; --i) {
+ nodes.push(children[i]);
+ }
+ }
+ }
+ return this;
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/eachAfter.js
+
+ /* harmony default export */ function eachAfter(callback, that) {
+ var node = this,
+ nodes = [node],
+ next = [],
+ children,
+ i,
+ n,
+ index = -1;
+ while ((node = nodes.pop())) {
+ next.push(node);
+ if ((children = node.children)) {
+ for (i = 0, n = children.length; i < n; ++i) {
+ nodes.push(children[i]);
+ }
+ }
+ }
+ while ((node = next.pop())) {
+ callback.call(that, node, ++index, this);
+ }
+ return this;
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/find.js
+
+ /* harmony default export */ function hierarchy_find(callback, that) {
+ let index = -1;
+ for (const node of this) {
+ if (callback.call(that, node, ++index, this)) {
+ return node;
+ }
+ }
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/sum.js
+
+ /* harmony default export */ function sum(value) {
+ return this.eachAfter(function (node) {
+ var sum = +value(node.data) || 0,
+ children = node.children,
+ i = children && children.length;
+ while (--i >= 0) sum += children[i].value;
+ node.value = sum;
+ });
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/sort.js
+
+ /* harmony default export */ function hierarchy_sort(compare) {
+ return this.eachBefore(function (node) {
+ if (node.children) {
+ node.children.sort(compare);
+ }
+ });
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/path.js
+
+ /* harmony default export */ function path(end) {
+ var start = this,
+ ancestor = leastCommonAncestor(start, end),
+ nodes = [start];
+ while (start !== ancestor) {
+ start = start.parent;
+ nodes.push(start);
+ }
+ var k = nodes.length;
+ while (end !== ancestor) {
+ nodes.splice(k, 0, end);
+ end = end.parent;
+ }
+ return nodes;
+ }
+
+ function leastCommonAncestor(a, b) {
+ if (a === b) return a;
+ var aNodes = a.ancestors(),
+ bNodes = b.ancestors(),
+ c = null;
+ a = aNodes.pop();
+ b = bNodes.pop();
+ while (a === b) {
+ c = a;
+ a = aNodes.pop();
+ b = bNodes.pop();
+ }
+ return c;
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/ancestors.js
+
+ /* harmony default export */ function ancestors() {
+ var node = this,
+ nodes = [node];
+ while ((node = node.parent)) {
+ nodes.push(node);
+ }
+ return nodes;
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/descendants.js
+
+ /* harmony default export */ function descendants() {
+ return Array.from(this);
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/leaves.js
+
+ /* harmony default export */ function leaves() {
+ var leaves = [];
+ this.eachBefore(function (node) {
+ if (!node.children) {
+ leaves.push(node);
+ }
+ });
+ return leaves;
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/links.js
+
+ /* harmony default export */ function links() {
+ var root = this,
+ links = [];
+ root.each(function (node) {
+ if (node !== root) {
+ // Don’t include the root’s parent, if any.
+ links.push({ source: node.parent, target: node });
+ }
+ });
+ return links;
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/iterator.js
+
+ /* harmony default export */ function* hierarchy_iterator() {
+ var node = this,
+ current,
+ next = [node],
+ children,
+ i,
+ n;
+ do {
+ (current = next.reverse()), (next = []);
+ while ((node = current.pop())) {
+ yield node;
+ if ((children = node.children)) {
+ for (i = 0, n = children.length; i < n; ++i) {
+ next.push(children[i]);
+ }
+ }
+ }
+ } while (next.length);
+ } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/index.js
+
+ function hierarchy(data, children) {
+ if (data instanceof Map) {
+ data = [undefined, data];
+ if (children === undefined) children = mapChildren;
+ } else if (children === undefined) {
+ children = objectChildren;
+ }
+
+ var root = new Node(data),
+ node,
+ nodes = [root],
+ child,
+ childs,
+ i,
+ n;
+
+ while ((node = nodes.pop())) {
+ if (
+ (childs = children(node.data)) &&
+ (n = (childs = Array.from(childs)).length)
+ ) {
+ node.children = childs;
+ for (i = n - 1; i >= 0; --i) {
+ nodes.push((child = childs[i] = new Node(childs[i])));
+ child.parent = node;
+ child.depth = node.depth + 1;
+ }
+ }
+ }
+
+ return root.eachBefore(computeHeight);
+ }
+
+ function node_copy() {
+ return hierarchy(this).eachBefore(copyData);
+ }
+
+ function objectChildren(d) {
+ return d.children;
+ }
+
+ function mapChildren(d) {
+ return Array.isArray(d) ? d[1] : null;
+ }
+
+ function copyData(node) {
+ if (node.data.value !== undefined) node.value = node.data.value;
+ node.data = node.data.data;
+ }
+
+ function computeHeight(node) {
+ var height = 0;
+ do node.height = height;
+ while ((node = node.parent) && node.height < ++height);
+ }
+
+ function Node(data) {
+ this.data = data;
+ this.depth = this.height = 0;
+ this.parent = null;
+ }
+
+ Node.prototype = hierarchy.prototype = {
+ constructor: Node,
+ count: hierarchy_count,
+ each: hierarchy_each,
+ eachAfter: eachAfter,
+ eachBefore: eachBefore,
+ find: hierarchy_find,
+ sum: sum,
+ sort: hierarchy_sort,
+ path: path,
+ ancestors: ancestors,
+ descendants: descendants,
+ leaves: leaves,
+ links: links,
+ copy: node_copy,
+ [Symbol.iterator]: hierarchy_iterator,
+ }; // CONCATENATED MODULE: ../node_modules/d3-array/src/ticks.js
+
+ var e10 = Math.sqrt(50),
+ e5 = Math.sqrt(10),
+ e2 = Math.sqrt(2);
+
+ function ticks(start, stop, count) {
+ var reverse,
+ i = -1,
+ n,
+ ticks,
+ step;
+
+ (stop = +stop), (start = +start), (count = +count);
+ if (start === stop && count > 0) return [start];
+ if ((reverse = stop < start)) (n = start), (start = stop), (stop = n);
+ if ((step = tickIncrement(start, stop, count)) === 0 || !isFinite(step))
+ return [];
+
+ if (step > 0) {
+ let r0 = Math.round(start / step),
+ r1 = Math.round(stop / step);
+ if (r0 * step < start) ++r0;
+ if (r1 * step > stop) --r1;
+ ticks = new Array((n = r1 - r0 + 1));
+ while (++i < n) ticks[i] = (r0 + i) * step;
+ } else {
+ step = -step;
+ let r0 = Math.round(start * step),
+ r1 = Math.round(stop * step);
+ if (r0 / step < start) ++r0;
+ if (r1 / step > stop) --r1;
+ ticks = new Array((n = r1 - r0 + 1));
+ while (++i < n) ticks[i] = (r0 + i) / step;
+ }
+
+ if (reverse) ticks.reverse();
+
+ return ticks;
+ }
+
+ function tickIncrement(start, stop, count) {
+ var step = (stop - start) / Math.max(0, count),
+ power = Math.floor(Math.log(step) / Math.LN10),
+ error = step / Math.pow(10, power);
+ return power >= 0
+ ? (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1) *
+ Math.pow(10, power)
+ : -Math.pow(10, -power) /
+ (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1);
+ }
+
+ function tickStep(start, stop, count) {
+ var step0 = Math.abs(stop - start) / Math.max(0, count),
+ step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)),
+ error = step0 / step1;
+ if (error >= e10) step1 *= 10;
+ else if (error >= e5) step1 *= 5;
+ else if (error >= e2) step1 *= 2;
+ return stop < start ? -step1 : step1;
+ } // CONCATENATED MODULE: ../node_modules/d3-array/src/bisector.js
+
+ function bisector(f) {
+ let delta = f;
+ let compare1 = f;
+ let compare2 = f;
+
+ if (f.length !== 2) {
+ delta = (d, x) => f(d) - x;
+ compare1 = ascending_ascending;
+ compare2 = (d, x) => ascending_ascending(f(d), x);
+ }
+
+ function left(a, x, lo = 0, hi = a.length) {
+ if (lo < hi) {
+ if (compare1(x, x) !== 0) return hi;
+ do {
+ const mid = (lo + hi) >>> 1;
+ if (compare2(a[mid], x) < 0) lo = mid + 1;
+ else hi = mid;
+ } while (lo < hi);
+ }
+ return lo;
+ }
+
+ function right(a, x, lo = 0, hi = a.length) {
+ if (lo < hi) {
+ if (compare1(x, x) !== 0) return hi;
+ do {
+ const mid = (lo + hi) >>> 1;
+ if (compare2(a[mid], x) <= 0) lo = mid + 1;
+ else hi = mid;
+ } while (lo < hi);
+ }
+ return lo;
+ }
+
+ function center(a, x, lo = 0, hi = a.length) {
+ const i = left(a, x, lo, hi - 1);
+ return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i;
+ }
+
+ return { left, center, right };
+ } // CONCATENATED MODULE: ../node_modules/d3-array/src/number.js
+
+ function number(x) {
+ return x === null ? NaN : +x;
+ }
+
+ function* numbers(values, valueof) {
+ if (valueof === undefined) {
+ for (let value of values) {
+ if (value != null && (value = +value) >= value) {
+ yield value;
+ }
+ }
+ } else {
+ let index = -1;
+ for (let value of values) {
+ if (
+ (value = valueof(value, ++index, values)) != null &&
+ (value = +value) >= value
+ ) {
+ yield value;
+ }
+ }
+ }
+ } // CONCATENATED MODULE: ../node_modules/d3-array/src/bisect.js
+
+ const ascendingBisect = bisector(ascending_ascending);
+ const bisectRight = ascendingBisect.right;
+ const bisectLeft = ascendingBisect.left;
+ const bisectCenter = bisector(number).center;
+ /* harmony default export */ const bisect = bisectRight; // CONCATENATED MODULE: ../node_modules/d3-color/src/define.js
+
+ /* harmony default export */ function src_define(
+ constructor,
+ factory,
+ prototype,
+ ) {
+ constructor.prototype = factory.prototype = prototype;
+ prototype.constructor = constructor;
+ }
+
+ function extend(parent, definition) {
+ var prototype = Object.create(parent.prototype);
+ for (var key in definition) prototype[key] = definition[key];
+ return prototype;
+ } // CONCATENATED MODULE: ../node_modules/d3-color/src/color.js
+
+ function Color() {}
+
+ var darker = 0.7;
+ var brighter = 1 / darker;
+
+ var reI = '\\s*([+-]?\\d+)\\s*',
+ reN = '\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*',
+ reP = '\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*',
+ reHex = /^#([0-9a-f]{3,8})$/,
+ reRgbInteger = new RegExp('^rgb\\(' + [reI, reI, reI] + '\\)$'),
+ reRgbPercent = new RegExp('^rgb\\(' + [reP, reP, reP] + '\\)$'),
+ reRgbaInteger = new RegExp('^rgba\\(' + [reI, reI, reI, reN] + '\\)$'),
+ reRgbaPercent = new RegExp('^rgba\\(' + [reP, reP, reP, reN] + '\\)$'),
+ reHslPercent = new RegExp('^hsl\\(' + [reN, reP, reP] + '\\)$'),
+ reHslaPercent = new RegExp('^hsla\\(' + [reN, reP, reP, reN] + '\\)$');
+
+ var named = {
+ aliceblue: 0xf0f8ff,
+ antiquewhite: 0xfaebd7,
+ aqua: 0x00ffff,
+ aquamarine: 0x7fffd4,
+ azure: 0xf0ffff,
+ beige: 0xf5f5dc,
+ bisque: 0xffe4c4,
+ black: 0x000000,
+ blanchedalmond: 0xffebcd,
+ blue: 0x0000ff,
+ blueviolet: 0x8a2be2,
+ brown: 0xa52a2a,
+ burlywood: 0xdeb887,
+ cadetblue: 0x5f9ea0,
+ chartreuse: 0x7fff00,
+ chocolate: 0xd2691e,
+ coral: 0xff7f50,
+ cornflowerblue: 0x6495ed,
+ cornsilk: 0xfff8dc,
+ crimson: 0xdc143c,
+ cyan: 0x00ffff,
+ darkblue: 0x00008b,
+ darkcyan: 0x008b8b,
+ darkgoldenrod: 0xb8860b,
+ darkgray: 0xa9a9a9,
+ darkgreen: 0x006400,
+ darkgrey: 0xa9a9a9,
+ darkkhaki: 0xbdb76b,
+ darkmagenta: 0x8b008b,
+ darkolivegreen: 0x556b2f,
+ darkorange: 0xff8c00,
+ darkorchid: 0x9932cc,
+ darkred: 0x8b0000,
+ darksalmon: 0xe9967a,
+ darkseagreen: 0x8fbc8f,
+ darkslateblue: 0x483d8b,
+ darkslategray: 0x2f4f4f,
+ darkslategrey: 0x2f4f4f,
+ darkturquoise: 0x00ced1,
+ darkviolet: 0x9400d3,
+ deeppink: 0xff1493,
+ deepskyblue: 0x00bfff,
+ dimgray: 0x696969,
+ dimgrey: 0x696969,
+ dodgerblue: 0x1e90ff,
+ firebrick: 0xb22222,
+ floralwhite: 0xfffaf0,
+ forestgreen: 0x228b22,
+ fuchsia: 0xff00ff,
+ gainsboro: 0xdcdcdc,
+ ghostwhite: 0xf8f8ff,
+ gold: 0xffd700,
+ goldenrod: 0xdaa520,
+ gray: 0x808080,
+ green: 0x008000,
+ greenyellow: 0xadff2f,
+ grey: 0x808080,
+ honeydew: 0xf0fff0,
+ hotpink: 0xff69b4,
+ indianred: 0xcd5c5c,
+ indigo: 0x4b0082,
+ ivory: 0xfffff0,
+ khaki: 0xf0e68c,
+ lavender: 0xe6e6fa,
+ lavenderblush: 0xfff0f5,
+ lawngreen: 0x7cfc00,
+ lemonchiffon: 0xfffacd,
+ lightblue: 0xadd8e6,
+ lightcoral: 0xf08080,
+ lightcyan: 0xe0ffff,
+ lightgoldenrodyellow: 0xfafad2,
+ lightgray: 0xd3d3d3,
+ lightgreen: 0x90ee90,
+ lightgrey: 0xd3d3d3,
+ lightpink: 0xffb6c1,
+ lightsalmon: 0xffa07a,
+ lightseagreen: 0x20b2aa,
+ lightskyblue: 0x87cefa,
+ lightslategray: 0x778899,
+ lightslategrey: 0x778899,
+ lightsteelblue: 0xb0c4de,
+ lightyellow: 0xffffe0,
+ lime: 0x00ff00,
+ limegreen: 0x32cd32,
+ linen: 0xfaf0e6,
+ magenta: 0xff00ff,
+ maroon: 0x800000,
+ mediumaquamarine: 0x66cdaa,
+ mediumblue: 0x0000cd,
+ mediumorchid: 0xba55d3,
+ mediumpurple: 0x9370db,
+ mediumseagreen: 0x3cb371,
+ mediumslateblue: 0x7b68ee,
+ mediumspringgreen: 0x00fa9a,
+ mediumturquoise: 0x48d1cc,
+ mediumvioletred: 0xc71585,
+ midnightblue: 0x191970,
+ mintcream: 0xf5fffa,
+ mistyrose: 0xffe4e1,
+ moccasin: 0xffe4b5,
+ navajowhite: 0xffdead,
+ navy: 0x000080,
+ oldlace: 0xfdf5e6,
+ olive: 0x808000,
+ olivedrab: 0x6b8e23,
+ orange: 0xffa500,
+ orangered: 0xff4500,
+ orchid: 0xda70d6,
+ palegoldenrod: 0xeee8aa,
+ palegreen: 0x98fb98,
+ paleturquoise: 0xafeeee,
+ palevioletred: 0xdb7093,
+ papayawhip: 0xffefd5,
+ peachpuff: 0xffdab9,
+ peru: 0xcd853f,
+ pink: 0xffc0cb,
+ plum: 0xdda0dd,
+ powderblue: 0xb0e0e6,
+ purple: 0x800080,
+ rebeccapurple: 0x663399,
+ red: 0xff0000,
+ rosybrown: 0xbc8f8f,
+ royalblue: 0x4169e1,
+ saddlebrown: 0x8b4513,
+ salmon: 0xfa8072,
+ sandybrown: 0xf4a460,
+ seagreen: 0x2e8b57,
+ seashell: 0xfff5ee,
+ sienna: 0xa0522d,
+ silver: 0xc0c0c0,
+ skyblue: 0x87ceeb,
+ slateblue: 0x6a5acd,
+ slategray: 0x708090,
+ slategrey: 0x708090,
+ snow: 0xfffafa,
+ springgreen: 0x00ff7f,
+ steelblue: 0x4682b4,
+ tan: 0xd2b48c,
+ teal: 0x008080,
+ thistle: 0xd8bfd8,
+ tomato: 0xff6347,
+ turquoise: 0x40e0d0,
+ violet: 0xee82ee,
+ wheat: 0xf5deb3,
+ white: 0xffffff,
+ whitesmoke: 0xf5f5f5,
+ yellow: 0xffff00,
+ yellowgreen: 0x9acd32,
+ };
+
+ src_define(Color, color, {
+ copy: function (channels) {
+ return Object.assign(new this.constructor(), this, channels);
+ },
+ displayable: function () {
+ return this.rgb().displayable();
+ },
+ hex: color_formatHex, // Deprecated! Use color.formatHex.
+ formatHex: color_formatHex,
+ formatHsl: color_formatHsl,
+ formatRgb: color_formatRgb,
+ toString: color_formatRgb,
+ });
+
+ function color_formatHex() {
+ return this.rgb().formatHex();
+ }
+
+ function color_formatHsl() {
+ return hslConvert(this).formatHsl();
+ }
+
+ function color_formatRgb() {
+ return this.rgb().formatRgb();
+ }
+
+ function color(format) {
+ var m, l;
+ format = (format + '').trim().toLowerCase();
+ return (m = reHex.exec(format))
+ ? ((l = m[1].length),
+ (m = parseInt(m[1], 16)),
+ l === 6
+ ? rgbn(m) // #ff0000
+ : l === 3
+ ? new Rgb(
+ ((m >> 8) & 0xf) | ((m >> 4) & 0xf0),
+ ((m >> 4) & 0xf) | (m & 0xf0),
+ ((m & 0xf) << 4) | (m & 0xf),
+ 1,
+ ) // #f00
+ : l === 8
+ ? rgba(
+ (m >> 24) & 0xff,
+ (m >> 16) & 0xff,
+ (m >> 8) & 0xff,
+ (m & 0xff) / 0xff,
+ ) // #ff000000
+ : l === 4
+ ? rgba(
+ ((m >> 12) & 0xf) | ((m >> 8) & 0xf0),
+ ((m >> 8) & 0xf) | ((m >> 4) & 0xf0),
+ ((m >> 4) & 0xf) | (m & 0xf0),
+ (((m & 0xf) << 4) | (m & 0xf)) / 0xff,
+ ) // #f000
+ : null) // invalid hex
+ : (m = reRgbInteger.exec(format))
+ ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0)
+ : (m = reRgbPercent.exec(format))
+ ? new Rgb((m[1] * 255) / 100, (m[2] * 255) / 100, (m[3] * 255) / 100, 1) // rgb(100%, 0%, 0%)
+ : (m = reRgbaInteger.exec(format))
+ ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1)
+ : (m = reRgbaPercent.exec(format))
+ ? rgba((m[1] * 255) / 100, (m[2] * 255) / 100, (m[3] * 255) / 100, m[4]) // rgb(100%, 0%, 0%, 1)
+ : (m = reHslPercent.exec(format))
+ ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%)
+ : (m = reHslaPercent.exec(format))
+ ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1)
+ : named.hasOwnProperty(format)
+ ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins
+ : format === 'transparent'
+ ? new Rgb(NaN, NaN, NaN, 0)
+ : null;
+ }
+
+ function rgbn(n) {
+ return new Rgb((n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff, 1);
+ }
+
+ function rgba(r, g, b, a) {
+ if (a <= 0) r = g = b = NaN;
+ return new Rgb(r, g, b, a);
+ }
+
+ function rgbConvert(o) {
+ if (!(o instanceof Color)) o = color(o);
+ if (!o) return new Rgb();
+ o = o.rgb();
+ return new Rgb(o.r, o.g, o.b, o.opacity);
+ }
+
+ function color_rgb(r, g, b, opacity) {
+ return arguments.length === 1
+ ? rgbConvert(r)
+ : new Rgb(r, g, b, opacity == null ? 1 : opacity);
+ }
+
+ function Rgb(r, g, b, opacity) {
+ this.r = +r;
+ this.g = +g;
+ this.b = +b;
+ this.opacity = +opacity;
+ }
+
+ src_define(
+ Rgb,
+ color_rgb,
+ extend(Color, {
+ brighter: function (k) {
+ k = k == null ? brighter : Math.pow(brighter, k);
+ return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
+ },
+ darker: function (k) {
+ k = k == null ? darker : Math.pow(darker, k);
+ return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
+ },
+ rgb: function () {
+ return this;
+ },
+ displayable: function () {
+ return (
+ -0.5 <= this.r &&
+ this.r < 255.5 &&
+ -0.5 <= this.g &&
+ this.g < 255.5 &&
+ -0.5 <= this.b &&
+ this.b < 255.5 &&
+ 0 <= this.opacity &&
+ this.opacity <= 1
+ );
+ },
+ hex: rgb_formatHex, // Deprecated! Use color.formatHex.
+ formatHex: rgb_formatHex,
+ formatRgb: rgb_formatRgb,
+ toString: rgb_formatRgb,
+ }),
+ );
+
+ function rgb_formatHex() {
+ return '#' + hex(this.r) + hex(this.g) + hex(this.b);
+ }
+
+ function rgb_formatRgb() {
+ var a = this.opacity;
+ a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
+ return (
+ (a === 1 ? 'rgb(' : 'rgba(') +
+ Math.max(0, Math.min(255, Math.round(this.r) || 0)) +
+ ', ' +
+ Math.max(0, Math.min(255, Math.round(this.g) || 0)) +
+ ', ' +
+ Math.max(0, Math.min(255, Math.round(this.b) || 0)) +
+ (a === 1 ? ')' : ', ' + a + ')')
+ );
+ }
+
+ function hex(value) {
+ value = Math.max(0, Math.min(255, Math.round(value) || 0));
+ return (value < 16 ? '0' : '') + value.toString(16);
+ }
+
+ function hsla(h, s, l, a) {
+ if (a <= 0) h = s = l = NaN;
+ else if (l <= 0 || l >= 1) h = s = NaN;
+ else if (s <= 0) h = NaN;
+ return new Hsl(h, s, l, a);
+ }
+
+ function hslConvert(o) {
+ if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity);
+ if (!(o instanceof Color)) o = color(o);
+ if (!o) return new Hsl();
+ if (o instanceof Hsl) return o;
+ o = o.rgb();
+ var r = o.r / 255,
+ g = o.g / 255,
+ b = o.b / 255,
+ min = Math.min(r, g, b),
+ max = Math.max(r, g, b),
+ h = NaN,
+ s = max - min,
+ l = (max + min) / 2;
+ if (s) {
+ if (r === max) h = (g - b) / s + (g < b) * 6;
+ else if (g === max) h = (b - r) / s + 2;
+ else h = (r - g) / s + 4;
+ s /= l < 0.5 ? max + min : 2 - max - min;
+ h *= 60;
+ } else {
+ s = l > 0 && l < 1 ? 0 : h;
+ }
+ return new Hsl(h, s, l, o.opacity);
+ }
+
+ function hsl(h, s, l, opacity) {
+ return arguments.length === 1
+ ? hslConvert(h)
+ : new Hsl(h, s, l, opacity == null ? 1 : opacity);
+ }
+
+ function Hsl(h, s, l, opacity) {
+ this.h = +h;
+ this.s = +s;
+ this.l = +l;
+ this.opacity = +opacity;
+ }
+
+ src_define(
+ Hsl,
+ hsl,
+ extend(Color, {
+ brighter: function (k) {
+ k = k == null ? brighter : Math.pow(brighter, k);
+ return new Hsl(this.h, this.s, this.l * k, this.opacity);
+ },
+ darker: function (k) {
+ k = k == null ? darker : Math.pow(darker, k);
+ return new Hsl(this.h, this.s, this.l * k, this.opacity);
+ },
+ rgb: function () {
+ var h = (this.h % 360) + (this.h < 0) * 360,
+ s = isNaN(h) || isNaN(this.s) ? 0 : this.s,
+ l = this.l,
+ m2 = l + (l < 0.5 ? l : 1 - l) * s,
+ m1 = 2 * l - m2;
+ return new Rgb(
+ hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2),
+ hsl2rgb(h, m1, m2),
+ hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2),
+ this.opacity,
+ );
+ },
+ displayable: function () {
+ return (
+ ((0 <= this.s && this.s <= 1) || isNaN(this.s)) &&
+ 0 <= this.l &&
+ this.l <= 1 &&
+ 0 <= this.opacity &&
+ this.opacity <= 1
+ );
+ },
+ formatHsl: function () {
+ var a = this.opacity;
+ a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
+ return (
+ (a === 1 ? 'hsl(' : 'hsla(') +
+ (this.h || 0) +
+ ', ' +
+ (this.s || 0) * 100 +
+ '%, ' +
+ (this.l || 0) * 100 +
+ '%' +
+ (a === 1 ? ')' : ', ' + a + ')')
+ );
+ },
+ }),
+ );
+
+ /* From FvD 13.37, CSS Color Module Level 3 */
+ function hsl2rgb(h, m1, m2) {
+ return (
+ (h < 60
+ ? m1 + ((m2 - m1) * h) / 60
+ : h < 180
+ ? m2
+ : h < 240
+ ? m1 + ((m2 - m1) * (240 - h)) / 60
+ : m1) * 255
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basis.js
+
+ function basis(t1, v0, v1, v2, v3) {
+ var t2 = t1 * t1,
+ t3 = t2 * t1;
+ return (
+ ((1 - 3 * t1 + 3 * t2 - t3) * v0 +
+ (4 - 6 * t2 + 3 * t3) * v1 +
+ (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2 +
+ t3 * v3) /
+ 6
+ );
+ }
+
+ /* harmony default export */ function src_basis(values) {
+ var n = values.length - 1;
+ return function (t) {
+ var i =
+ t <= 0 ? (t = 0) : t >= 1 ? ((t = 1), n - 1) : Math.floor(t * n),
+ v1 = values[i],
+ v2 = values[i + 1],
+ v0 = i > 0 ? values[i - 1] : 2 * v1 - v2,
+ v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1;
+ return basis((t - i / n) * n, v0, v1, v2, v3);
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basisClosed.js
+
+ /* harmony default export */ function basisClosed(values) {
+ var n = values.length;
+ return function (t) {
+ var i = Math.floor(((t %= 1) < 0 ? ++t : t) * n),
+ v0 = values[(i + n - 1) % n],
+ v1 = values[i % n],
+ v2 = values[(i + 1) % n],
+ v3 = values[(i + 2) % n];
+ return basis((t - i / n) * n, v0, v1, v2, v3);
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/constant.js
+
+ /* harmony default export */ const d3_interpolate_src_constant = (
+ x,
+ ) => () => x; // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/color.js
+
+ function linear(a, d) {
+ return function (t) {
+ return a + t * d;
+ };
+ }
+
+ function exponential(a, b, y) {
+ return (
+ (a = Math.pow(a, y)),
+ (b = Math.pow(b, y) - a),
+ (y = 1 / y),
+ function (t) {
+ return Math.pow(a + t * b, y);
+ }
+ );
+ }
+
+ function hue(a, b) {
+ var d = b - a;
+ return d
+ ? linear(a, d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d)
+ : constant(isNaN(a) ? b : a);
+ }
+
+ function gamma(y) {
+ return (y = +y) === 1
+ ? nogamma
+ : function (a, b) {
+ return b - a
+ ? exponential(a, b, y)
+ : d3_interpolate_src_constant(isNaN(a) ? b : a);
+ };
+ }
+
+ function nogamma(a, b) {
+ var d = b - a;
+ return d ? linear(a, d) : d3_interpolate_src_constant(isNaN(a) ? b : a);
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/rgb.js
+
+ /* harmony default export */ const rgb = (function rgbGamma(y) {
+ var color = gamma(y);
+
+ function rgb(start, end) {
+ var r = color((start = color_rgb(start)).r, (end = color_rgb(end)).r),
+ g = color(start.g, end.g),
+ b = color(start.b, end.b),
+ opacity = nogamma(start.opacity, end.opacity);
+ return function (t) {
+ start.r = r(t);
+ start.g = g(t);
+ start.b = b(t);
+ start.opacity = opacity(t);
+ return start + '';
+ };
+ }
+
+ rgb.gamma = rgbGamma;
+
+ return rgb;
+ })(1);
+
+ function rgbSpline(spline) {
+ return function (colors) {
+ var n = colors.length,
+ r = new Array(n),
+ g = new Array(n),
+ b = new Array(n),
+ i,
+ color;
+ for (i = 0; i < n; ++i) {
+ color = color_rgb(colors[i]);
+ r[i] = color.r || 0;
+ g[i] = color.g || 0;
+ b[i] = color.b || 0;
+ }
+ r = spline(r);
+ g = spline(g);
+ b = spline(b);
+ color.opacity = 1;
+ return function (t) {
+ color.r = r(t);
+ color.g = g(t);
+ color.b = b(t);
+ return color + '';
+ };
+ };
+ }
+
+ var rgbBasis = rgbSpline(src_basis);
+ var rgbBasisClosed = rgbSpline(basisClosed); // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/array.js
+
+ /* harmony default export */ function src_array(a, b) {
+ return (isNumberArray(b) ? numberArray : genericArray)(a, b);
+ }
+
+ function genericArray(a, b) {
+ var nb = b ? b.length : 0,
+ na = a ? Math.min(nb, a.length) : 0,
+ x = new Array(na),
+ c = new Array(nb),
+ i;
+
+ for (i = 0; i < na; ++i) x[i] = value(a[i], b[i]);
+ for (; i < nb; ++i) c[i] = b[i];
+
+ return function (t) {
+ for (i = 0; i < na; ++i) c[i] = x[i](t);
+ return c;
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/date.js
+
+ /* harmony default export */ function date(a, b) {
+ var d = new Date();
+ return (
+ (a = +a),
+ (b = +b),
+ function (t) {
+ return d.setTime(a * (1 - t) + b * t), d;
+ }
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/number.js
+
+ /* harmony default export */ function src_number(a, b) {
+ return (
+ (a = +a),
+ (b = +b),
+ function (t) {
+ return a * (1 - t) + b * t;
+ }
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/object.js
+
+ /* harmony default export */ function object(a, b) {
+ var i = {},
+ c = {},
+ k;
+
+ if (a === null || typeof a !== 'object') a = {};
+ if (b === null || typeof b !== 'object') b = {};
+
+ for (k in b) {
+ if (k in a) {
+ i[k] = value(a[k], b[k]);
+ } else {
+ c[k] = b[k];
+ }
+ }
+
+ return function (t) {
+ for (k in i) c[k] = i[k](t);
+ return c;
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/string.js
+
+ var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,
+ reB = new RegExp(reA.source, 'g');
+
+ function zero(b) {
+ return function () {
+ return b;
+ };
+ }
+
+ function one(b) {
+ return function (t) {
+ return b(t) + '';
+ };
+ }
+
+ /* harmony default export */ function string(a, b) {
+ var bi = (reA.lastIndex = reB.lastIndex = 0), // scan index for next number in b
+ am, // current match in a
+ bm, // current match in b
+ bs, // string preceding current number in b, if any
+ i = -1, // index in s
+ s = [], // string constants and placeholders
+ q = []; // number interpolators
+
+ // Coerce inputs to strings.
+ (a = a + ''), (b = b + '');
+
+ // Interpolate pairs of numbers in a & b.
+ while ((am = reA.exec(a)) && (bm = reB.exec(b))) {
+ if ((bs = bm.index) > bi) {
+ // a string precedes the next number in b
+ bs = b.slice(bi, bs);
+ if (s[i]) s[i] += bs;
+ // coalesce with previous string
+ else s[++i] = bs;
+ }
+ if ((am = am[0]) === (bm = bm[0])) {
+ // numbers in a & b match
+ if (s[i]) s[i] += bm;
+ // coalesce with previous string
+ else s[++i] = bm;
+ } else {
+ // interpolate non-matching numbers
+ s[++i] = null;
+ q.push({ i: i, x: src_number(am, bm) });
+ }
+ bi = reB.lastIndex;
+ }
+
+ // Add remains of b.
+ if (bi < b.length) {
+ bs = b.slice(bi);
+ if (s[i]) s[i] += bs;
+ // coalesce with previous string
+ else s[++i] = bs;
+ }
+
+ // Special optimization for only a single match.
+ // Otherwise, interpolate each of the numbers and rejoin the string.
+ return s.length < 2
+ ? q[0]
+ ? one(q[0].x)
+ : zero(b)
+ : ((b = q.length),
+ function (t) {
+ for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
+ return s.join('');
+ });
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/numberArray.js
+
+ /* harmony default export */ function src_numberArray(a, b) {
+ if (!b) b = [];
+ var n = a ? Math.min(b.length, a.length) : 0,
+ c = b.slice(),
+ i;
+ return function (t) {
+ for (i = 0; i < n; ++i) c[i] = a[i] * (1 - t) + b[i] * t;
+ return c;
+ };
+ }
+
+ function numberArray_isNumberArray(x) {
+ return ArrayBuffer.isView(x) && !(x instanceof DataView);
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/value.js
+
+ /* harmony default export */ function value(a, b) {
+ var t = typeof b,
+ c;
+ return b == null || t === 'boolean'
+ ? d3_interpolate_src_constant(b)
+ : (t === 'number'
+ ? src_number
+ : t === 'string'
+ ? (c = color(b))
+ ? ((b = c), rgb)
+ : string
+ : b instanceof color
+ ? rgb
+ : b instanceof Date
+ ? date
+ : numberArray_isNumberArray(b)
+ ? src_numberArray
+ : Array.isArray(b)
+ ? genericArray
+ : (typeof b.valueOf !== 'function' &&
+ typeof b.toString !== 'function') ||
+ isNaN(b)
+ ? object
+ : src_number)(a, b);
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/round.js
+
+ /* harmony default export */ function round(a, b) {
+ return (
+ (a = +a),
+ (b = +b),
+ function (t) {
+ return Math.round(a * (1 - t) + b * t);
+ }
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-scale/src/constant.js
+
+ function constants(x) {
+ return function () {
+ return x;
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-scale/src/number.js
+
+ function number_number(x) {
+ return +x;
+ } // CONCATENATED MODULE: ../node_modules/d3-scale/src/continuous.js
+
+ var unit = [0, 1];
+
+ function continuous_identity(x) {
+ return x;
+ }
+
+ function normalize(a, b) {
+ return (b -= a = +a)
+ ? function (x) {
+ return (x - a) / b;
+ }
+ : constants(isNaN(b) ? NaN : 0.5);
+ }
+
+ function clamper(a, b) {
+ var t;
+ if (a > b) (t = a), (a = b), (b = t);
+ return function (x) {
+ return Math.max(a, Math.min(b, x));
+ };
+ }
+
+ // normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1].
+ // interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b].
+ function bimap(domain, range, interpolate) {
+ var d0 = domain[0],
+ d1 = domain[1],
+ r0 = range[0],
+ r1 = range[1];
+ if (d1 < d0) (d0 = normalize(d1, d0)), (r0 = interpolate(r1, r0));
+ else (d0 = normalize(d0, d1)), (r0 = interpolate(r0, r1));
+ return function (x) {
+ return r0(d0(x));
+ };
+ }
+
+ function polymap(domain, range, interpolate) {
+ var j = Math.min(domain.length, range.length) - 1,
+ d = new Array(j),
+ r = new Array(j),
+ i = -1;
+
+ // Reverse descending domains.
+ if (domain[j] < domain[0]) {
+ domain = domain.slice().reverse();
+ range = range.slice().reverse();
+ }
+
+ while (++i < j) {
+ d[i] = normalize(domain[i], domain[i + 1]);
+ r[i] = interpolate(range[i], range[i + 1]);
+ }
+
+ return function (x) {
+ var i = bisect(domain, x, 1, j) - 1;
+ return r[i](d[i](x));
+ };
+ }
+
+ function copy(source, target) {
+ return target
+ .domain(source.domain())
+ .range(source.range())
+ .interpolate(source.interpolate())
+ .clamp(source.clamp())
+ .unknown(source.unknown());
+ }
+
+ function transformer() {
+ var domain = unit,
+ range = unit,
+ interpolate = value,
+ transform,
+ untransform,
+ unknown,
+ clamp = continuous_identity,
+ piecewise,
+ output,
+ input;
+
+ function rescale() {
+ var n = Math.min(domain.length, range.length);
+ if (clamp !== continuous_identity)
+ clamp = clamper(domain[0], domain[n - 1]);
+ piecewise = n > 2 ? polymap : bimap;
+ output = input = null;
+ return scale;
+ }
+
+ function scale(x) {
+ return x == null || isNaN((x = +x))
+ ? unknown
+ : (
+ output ||
+ (output = piecewise(domain.map(transform), range, interpolate))
+ )(transform(clamp(x)));
+ }
+
+ scale.invert = function (y) {
+ return clamp(
+ untransform(
+ (
+ input ||
+ (input = piecewise(range, domain.map(transform), src_number))
+ )(y),
+ ),
+ );
+ };
+
+ scale.domain = function (_) {
+ return arguments.length
+ ? ((domain = Array.from(_, number_number)), rescale())
+ : domain.slice();
+ };
+
+ scale.range = function (_) {
+ return arguments.length
+ ? ((range = Array.from(_)), rescale())
+ : range.slice();
+ };
+
+ scale.rangeRound = function (_) {
+ return (range = Array.from(_)), (interpolate = round), rescale();
+ };
+
+ scale.clamp = function (_) {
+ return arguments.length
+ ? ((clamp = _ ? true : continuous_identity), rescale())
+ : clamp !== continuous_identity;
+ };
+
+ scale.interpolate = function (_) {
+ return arguments.length ? ((interpolate = _), rescale()) : interpolate;
+ };
+
+ scale.unknown = function (_) {
+ return arguments.length ? ((unknown = _), scale) : unknown;
+ };
+
+ return function (t, u) {
+ (transform = t), (untransform = u);
+ return rescale();
+ };
+ }
+
+ function continuous() {
+ return transformer()(continuous_identity, continuous_identity);
+ } // CONCATENATED MODULE: ../node_modules/d3-scale/src/init.js
+
+ function initRange(domain, range) {
+ switch (arguments.length) {
+ case 0:
+ break;
+ case 1:
+ this.range(domain);
+ break;
+ default:
+ this.range(range).domain(domain);
+ break;
+ }
+ return this;
+ }
+
+ function initInterpolator(domain, interpolator) {
+ switch (arguments.length) {
+ case 0:
+ break;
+ case 1: {
+ if (typeof domain === 'function') this.interpolator(domain);
+ else this.range(domain);
+ break;
+ }
+ default: {
+ this.domain(domain);
+ if (typeof interpolator === 'function')
+ this.interpolator(interpolator);
+ else this.range(interpolator);
+ break;
+ }
+ }
+ return this;
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/precisionPrefix.js
+
+ /* harmony default export */ function precisionPrefix(step, value) {
+ return Math.max(
+ 0,
+ Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 -
+ exponent(Math.abs(step)),
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/precisionRound.js
+
+ /* harmony default export */ function precisionRound(step, max) {
+ (step = Math.abs(step)), (max = Math.abs(max) - step);
+ return Math.max(0, exponent(max) - exponent(step)) + 1;
+ } // CONCATENATED MODULE: ../node_modules/d3-format/src/precisionFixed.js
+
+ /* harmony default export */ function precisionFixed(step) {
+ return Math.max(0, -exponent(Math.abs(step)));
+ } // CONCATENATED MODULE: ../node_modules/d3-scale/src/tickFormat.js
+
+ function tickFormat(start, stop, count, specifier) {
+ var step = tickStep(start, stop, count),
+ precision;
+ specifier = formatSpecifier(specifier == null ? ',f' : specifier);
+ switch (specifier.type) {
+ case 's': {
+ var value = Math.max(Math.abs(start), Math.abs(stop));
+ if (
+ specifier.precision == null &&
+ !isNaN((precision = precisionPrefix(step, value)))
+ )
+ specifier.precision = precision;
+ return formatPrefix(specifier, value);
+ }
+ case '':
+ case 'e':
+ case 'g':
+ case 'p':
+ case 'r': {
+ if (
+ specifier.precision == null &&
+ !isNaN(
+ (precision = precisionRound(
+ step,
+ Math.max(Math.abs(start), Math.abs(stop)),
+ )),
+ )
+ )
+ specifier.precision = precision - (specifier.type === 'e');
+ break;
+ }
+ case 'f':
+ case '%': {
+ if (
+ specifier.precision == null &&
+ !isNaN((precision = precisionFixed(step)))
+ )
+ specifier.precision = precision - (specifier.type === '%') * 2;
+ break;
+ }
+ }
+ return format(specifier);
+ } // CONCATENATED MODULE: ../node_modules/d3-scale/src/linear.js
+
+ function linearish(scale) {
+ var domain = scale.domain;
+
+ scale.ticks = function (count) {
+ var d = domain();
+ return ticks(d[0], d[d.length - 1], count == null ? 10 : count);
+ };
+
+ scale.tickFormat = function (count, specifier) {
+ var d = domain();
+ return tickFormat(
+ d[0],
+ d[d.length - 1],
+ count == null ? 10 : count,
+ specifier,
+ );
+ };
+
+ scale.nice = function (count) {
+ if (count == null) count = 10;
+
+ var d = domain();
+ var i0 = 0;
+ var i1 = d.length - 1;
+ var start = d[i0];
+ var stop = d[i1];
+ var prestep;
+ var step;
+ var maxIter = 10;
+
+ if (stop < start) {
+ (step = start), (start = stop), (stop = step);
+ (step = i0), (i0 = i1), (i1 = step);
+ }
+
+ while (maxIter-- > 0) {
+ step = tickIncrement(start, stop, count);
+ if (step === prestep) {
+ d[i0] = start;
+ d[i1] = stop;
+ return domain(d);
+ } else if (step > 0) {
+ start = Math.floor(start / step) * step;
+ stop = Math.ceil(stop / step) * step;
+ } else if (step < 0) {
+ start = Math.ceil(start * step) / step;
+ stop = Math.floor(stop * step) / step;
+ } else {
+ break;
+ }
+ prestep = step;
+ }
+
+ return scale;
+ };
+
+ return scale;
+ }
+
+ function linear_linear() {
+ var scale = continuous();
+
+ scale.copy = function () {
+ return copy(scale, linear_linear());
+ };
+
+ initRange.apply(scale, arguments);
+
+ return linearish(scale);
+ } // CONCATENATED MODULE: ../node_modules/d3-ease/src/cubic.js
+
+ function cubicIn(t) {
+ return t * t * t;
+ }
+
+ function cubicOut(t) {
+ return --t * t * t + 1;
+ }
+
+ function cubicInOut(t) {
+ return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2;
+ } // CONCATENATED MODULE: ../node_modules/d3-dispatch/src/dispatch.js
+
+ var noop = { value: () => {} };
+
+ function dispatch_dispatch() {
+ for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
+ if (!(t = arguments[i] + '') || t in _ || /[\s.]/.test(t))
+ throw new Error('illegal type: ' + t);
+ _[t] = [];
+ }
+ return new Dispatch(_);
+ }
+
+ function Dispatch(_) {
+ this._ = _;
+ }
+
+ function dispatch_parseTypenames(typenames, types) {
+ return typenames
+ .trim()
+ .split(/^|\s+/)
+ .map(function (t) {
+ var name = '',
+ i = t.indexOf('.');
+ if (i >= 0) (name = t.slice(i + 1)), (t = t.slice(0, i));
+ if (t && !types.hasOwnProperty(t))
+ throw new Error('unknown type: ' + t);
+ return { type: t, name: name };
+ });
+ }
+
+ Dispatch.prototype = dispatch_dispatch.prototype = {
+ constructor: Dispatch,
+ on: function (typename, callback) {
+ var _ = this._,
+ T = dispatch_parseTypenames(typename + '', _),
+ t,
+ i = -1,
+ n = T.length;
+
+ // If no callback was specified, return the callback of the given type and name.
+ if (arguments.length < 2) {
+ while (++i < n)
+ if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name)))
+ return t;
+ return;
+ }
+
+ // If a type was specified, set the callback for the given type and name.
+ // Otherwise, if a null callback was specified, remove callbacks of the given name.
+ if (callback != null && typeof callback !== 'function')
+ throw new Error('invalid callback: ' + callback);
+ while (++i < n) {
+ if ((t = (typename = T[i]).type))
+ _[t] = set(_[t], typename.name, callback);
+ else if (callback == null)
+ for (t in _) _[t] = set(_[t], typename.name, null);
+ }
+
+ return this;
+ },
+ copy: function () {
+ var copy = {},
+ _ = this._;
+ for (var t in _) copy[t] = _[t].slice();
+ return new Dispatch(copy);
+ },
+ call: function (type, that) {
+ if ((n = arguments.length - 2) > 0)
+ for (var args = new Array(n), i = 0, n, t; i < n; ++i)
+ args[i] = arguments[i + 2];
+ if (!this._.hasOwnProperty(type))
+ throw new Error('unknown type: ' + type);
+ for (t = this._[type], i = 0, n = t.length; i < n; ++i)
+ t[i].value.apply(that, args);
+ },
+ apply: function (type, that, args) {
+ if (!this._.hasOwnProperty(type))
+ throw new Error('unknown type: ' + type);
+ for (var t = this._[type], i = 0, n = t.length; i < n; ++i)
+ t[i].value.apply(that, args);
+ },
+ };
+
+ function get(type, name) {
+ for (var i = 0, n = type.length, c; i < n; ++i) {
+ if ((c = type[i]).name === name) {
+ return c.value;
+ }
+ }
+ }
+
+ function set(type, name, callback) {
+ for (var i = 0, n = type.length; i < n; ++i) {
+ if (type[i].name === name) {
+ (type[i] = noop), (type = type.slice(0, i).concat(type.slice(i + 1)));
+ break;
+ }
+ }
+ if (callback != null) type.push({ name: name, value: callback });
+ return type;
+ }
+
+ /* harmony default export */ const src_dispatch = dispatch_dispatch; // CONCATENATED MODULE: ../node_modules/d3-timer/src/timer.js
+
+ var timer_frame = 0, // is an animation frame pending?
+ timeout = 0, // is a timeout pending?
+ interval = 0, // are any timers active?
+ pokeDelay = 1000, // how frequently we check for clock skew
+ taskHead,
+ taskTail,
+ clockLast = 0,
+ clockNow = 0,
+ clockSkew = 0,
+ clock =
+ typeof performance === 'object' && performance.now ? performance : Date,
+ setFrame =
+ typeof window === 'object' && window.requestAnimationFrame
+ ? window.requestAnimationFrame.bind(window)
+ : function (f) {
+ setTimeout(f, 17);
+ };
+
+ function now() {
+ return (
+ clockNow || (setFrame(clearNow), (clockNow = clock.now() + clockSkew))
+ );
+ }
+
+ function clearNow() {
+ clockNow = 0;
+ }
+
+ function Timer() {
+ this._call = this._time = this._next = null;
+ }
+
+ Timer.prototype = timer.prototype = {
+ constructor: Timer,
+ restart: function (callback, delay, time) {
+ if (typeof callback !== 'function')
+ throw new TypeError('callback is not a function');
+ time = (time == null ? now() : +time) + (delay == null ? 0 : +delay);
+ if (!this._next && taskTail !== this) {
+ if (taskTail) taskTail._next = this;
+ else taskHead = this;
+ taskTail = this;
+ }
+ this._call = callback;
+ this._time = time;
+ sleep();
+ },
+ stop: function () {
+ if (this._call) {
+ this._call = null;
+ this._time = Infinity;
+ sleep();
+ }
+ },
+ };
+
+ function timer(callback, delay, time) {
+ var t = new Timer();
+ t.restart(callback, delay, time);
+ return t;
+ }
+
+ function timerFlush() {
+ now(); // Get the current time, if not already set.
+ ++timer_frame; // Pretend we’ve set an alarm, if we haven’t already.
+ var t = taskHead,
+ e;
+ while (t) {
+ if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e);
+ t = t._next;
+ }
+ --timer_frame;
+ }
+
+ function wake() {
+ clockNow = (clockLast = clock.now()) + clockSkew;
+ timer_frame = timeout = 0;
+ try {
+ timerFlush();
+ } finally {
+ timer_frame = 0;
+ nap();
+ clockNow = 0;
+ }
+ }
+
+ function poke() {
+ var now = clock.now(),
+ delay = now - clockLast;
+ if (delay > pokeDelay) (clockSkew -= delay), (clockLast = now);
+ }
+
+ function nap() {
+ var t0,
+ t1 = taskHead,
+ t2,
+ time = Infinity;
+ while (t1) {
+ if (t1._call) {
+ if (time > t1._time) time = t1._time;
+ (t0 = t1), (t1 = t1._next);
+ } else {
+ (t2 = t1._next), (t1._next = null);
+ t1 = t0 ? (t0._next = t2) : (taskHead = t2);
+ }
+ }
+ taskTail = t0;
+ sleep(time);
+ }
+
+ function sleep(time) {
+ if (timer_frame) return; // Soonest alarm already set, or will be.
+ if (timeout) timeout = clearTimeout(timeout);
+ var delay = time - clockNow; // Strictly less than if we recomputed clockNow.
+ if (delay > 24) {
+ if (time < Infinity)
+ timeout = setTimeout(wake, time - clock.now() - clockSkew);
+ if (interval) interval = clearInterval(interval);
+ } else {
+ if (!interval)
+ (clockLast = clock.now()), (interval = setInterval(poke, pokeDelay));
+ (timer_frame = 1), setFrame(wake);
+ }
+ } // CONCATENATED MODULE: ../node_modules/d3-timer/src/timeout.js
+
+ /* harmony default export */ function src_timeout(callback, delay, time) {
+ var t = new Timer();
+ delay = delay == null ? 0 : +delay;
+ t.restart(
+ (elapsed) => {
+ t.stop();
+ callback(elapsed + delay);
+ },
+ delay,
+ time,
+ );
+ return t;
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/schedule.js
+
+ var emptyOn = src_dispatch('start', 'end', 'cancel', 'interrupt');
+ var emptyTween = [];
+
+ var CREATED = 0;
+ var SCHEDULED = 1;
+ var STARTING = 2;
+ var STARTED = 3;
+ var RUNNING = 4;
+ var ENDING = 5;
+ var ENDED = 6;
+
+ /* harmony default export */ function schedule(
+ node,
+ name,
+ id,
+ index,
+ group,
+ timing,
+ ) {
+ var schedules = node.__transition;
+ if (!schedules) node.__transition = {};
+ else if (id in schedules) return;
+ create(node, id, {
+ name: name,
+ index: index, // For context during callback.
+ group: group, // For context during callback.
+ on: emptyOn,
+ tween: emptyTween,
+ time: timing.time,
+ delay: timing.delay,
+ duration: timing.duration,
+ ease: timing.ease,
+ timer: null,
+ state: CREATED,
+ });
+ }
+
+ function init(node, id) {
+ var schedule = schedule_get(node, id);
+ if (schedule.state > CREATED)
+ throw new Error('too late; already scheduled');
+ return schedule;
+ }
+
+ function schedule_set(node, id) {
+ var schedule = schedule_get(node, id);
+ if (schedule.state > STARTED)
+ throw new Error('too late; already running');
+ return schedule;
+ }
+
+ function schedule_get(node, id) {
+ var schedule = node.__transition;
+ if (!schedule || !(schedule = schedule[id]))
+ throw new Error('transition not found');
+ return schedule;
+ }
+
+ function create(node, id, self) {
+ var schedules = node.__transition,
+ tween;
+
+ // Initialize the self timer when the transition is created.
+ // Note the actual delay is not known until the first callback!
+ schedules[id] = self;
+ self.timer = timer(schedule, 0, self.time);
+
+ function schedule(elapsed) {
+ self.state = SCHEDULED;
+ self.timer.restart(start, self.delay, self.time);
+
+ // If the elapsed delay is less than our first sleep, start immediately.
+ if (self.delay <= elapsed) start(elapsed - self.delay);
+ }
+
+ function start(elapsed) {
+ var i, j, n, o;
+
+ // If the state is not SCHEDULED, then we previously errored on start.
+ if (self.state !== SCHEDULED) return stop();
+
+ for (i in schedules) {
+ o = schedules[i];
+ if (o.name !== self.name) continue;
+
+ // While this element already has a starting transition during this frame,
+ // defer starting an interrupting transition until that transition has a
+ // chance to tick (and possibly end); see d3/d3-transition#54!
+ if (o.state === STARTED) return src_timeout(start);
+
+ // Interrupt the active transition, if any.
+ if (o.state === RUNNING) {
+ o.state = ENDED;
+ o.timer.stop();
+ o.on.call('interrupt', node, node.__data__, o.index, o.group);
+ delete schedules[i];
+ }
+
+ // Cancel any pre-empted transitions.
+ else if (+i < id) {
+ o.state = ENDED;
+ o.timer.stop();
+ o.on.call('cancel', node, node.__data__, o.index, o.group);
+ delete schedules[i];
+ }
+ }
+
+ // Defer the first tick to end of the current frame; see d3/d3#1576.
+ // Note the transition may be canceled after start and before the first tick!
+ // Note this must be scheduled before the start event; see d3/d3-transition#16!
+ // Assuming this is successful, subsequent callbacks go straight to tick.
+ src_timeout(function () {
+ if (self.state === STARTED) {
+ self.state = RUNNING;
+ self.timer.restart(tick, self.delay, self.time);
+ tick(elapsed);
+ }
+ });
+
+ // Dispatch the start event.
+ // Note this must be done before the tween are initialized.
+ self.state = STARTING;
+ self.on.call('start', node, node.__data__, self.index, self.group);
+ if (self.state !== STARTING) return; // interrupted
+ self.state = STARTED;
+
+ // Initialize the tween, deleting null tween.
+ tween = new Array((n = self.tween.length));
+ for (i = 0, j = -1; i < n; ++i) {
+ if (
+ (o = self.tween[i].value.call(
+ node,
+ node.__data__,
+ self.index,
+ self.group,
+ ))
+ ) {
+ tween[++j] = o;
+ }
+ }
+ tween.length = j + 1;
+ }
+
+ function tick(elapsed) {
+ var t =
+ elapsed < self.duration
+ ? self.ease.call(null, elapsed / self.duration)
+ : (self.timer.restart(stop), (self.state = ENDING), 1),
+ i = -1,
+ n = tween.length;
+
+ while (++i < n) {
+ tween[i].call(node, t);
+ }
+
+ // Dispatch the end event.
+ if (self.state === ENDING) {
+ self.on.call('end', node, node.__data__, self.index, self.group);
+ stop();
+ }
+ }
+
+ function stop() {
+ self.state = ENDED;
+ self.timer.stop();
+ delete schedules[id];
+ for (var i in schedules) return; // eslint-disable-line no-unused-vars
+ delete node.__transition;
+ }
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/interrupt.js
+
+ /* harmony default export */ function interrupt(node, name) {
+ var schedules = node.__transition,
+ schedule,
+ active,
+ empty = true,
+ i;
+
+ if (!schedules) return;
+
+ name = name == null ? null : name + '';
+
+ for (i in schedules) {
+ if ((schedule = schedules[i]).name !== name) {
+ empty = false;
+ continue;
+ }
+ active = schedule.state > STARTING && schedule.state < ENDING;
+ schedule.state = ENDED;
+ schedule.timer.stop();
+ schedule.on.call(
+ active ? 'interrupt' : 'cancel',
+ node,
+ node.__data__,
+ schedule.index,
+ schedule.group,
+ );
+ delete schedules[i];
+ }
+
+ if (empty) delete node.__transition;
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/interrupt.js
+
+ /* harmony default export */ function selection_interrupt(name) {
+ return this.each(function () {
+ interrupt(this, name);
+ });
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/decompose.js
+
+ var degrees = 180 / Math.PI;
+
+ var decompose_identity = {
+ translateX: 0,
+ translateY: 0,
+ rotate: 0,
+ skewX: 0,
+ scaleX: 1,
+ scaleY: 1,
+ };
+
+ /* harmony default export */ function decompose(a, b, c, d, e, f) {
+ var scaleX, scaleY, skewX;
+ if ((scaleX = Math.sqrt(a * a + b * b))) (a /= scaleX), (b /= scaleX);
+ if ((skewX = a * c + b * d)) (c -= a * skewX), (d -= b * skewX);
+ if ((scaleY = Math.sqrt(c * c + d * d)))
+ (c /= scaleY), (d /= scaleY), (skewX /= scaleY);
+ if (a * d < b * c)
+ (a = -a), (b = -b), (skewX = -skewX), (scaleX = -scaleX);
+ return {
+ translateX: e,
+ translateY: f,
+ rotate: Math.atan2(b, a) * degrees,
+ skewX: Math.atan(skewX) * degrees,
+ scaleX: scaleX,
+ scaleY: scaleY,
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/parse.js
+
+ var svgNode;
+
+ /* eslint-disable no-undef */
+ function parseCss(value) {
+ const m = new (typeof DOMMatrix === 'function'
+ ? DOMMatrix
+ : WebKitCSSMatrix)(value + '');
+ return m.isIdentity
+ ? decompose_identity
+ : decompose(m.a, m.b, m.c, m.d, m.e, m.f);
+ }
+
+ function parseSvg(value) {
+ if (value == null) return decompose_identity;
+ if (!svgNode)
+ svgNode = document.createElementNS('http://www.w3.org/2000/svg', 'g');
+ svgNode.setAttribute('transform', value);
+ if (!(value = svgNode.transform.baseVal.consolidate()))
+ return decompose_identity;
+ value = value.matrix;
+ return decompose(value.a, value.b, value.c, value.d, value.e, value.f);
+ } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/index.js
+
+ function interpolateTransform(parse, pxComma, pxParen, degParen) {
+ function pop(s) {
+ return s.length ? s.pop() + ' ' : '';
+ }
+
+ function translate(xa, ya, xb, yb, s, q) {
+ if (xa !== xb || ya !== yb) {
+ var i = s.push('translate(', null, pxComma, null, pxParen);
+ q.push(
+ { i: i - 4, x: src_number(xa, xb) },
+ { i: i - 2, x: src_number(ya, yb) },
+ );
+ } else if (xb || yb) {
+ s.push('translate(' + xb + pxComma + yb + pxParen);
+ }
+ }
+
+ function rotate(a, b, s, q) {
+ if (a !== b) {
+ if (a - b > 180) b += 360;
+ else if (b - a > 180) a += 360; // shortest path
+ q.push({
+ i: s.push(pop(s) + 'rotate(', null, degParen) - 2,
+ x: src_number(a, b),
+ });
+ } else if (b) {
+ s.push(pop(s) + 'rotate(' + b + degParen);
+ }
+ }
+
+ function skewX(a, b, s, q) {
+ if (a !== b) {
+ q.push({
+ i: s.push(pop(s) + 'skewX(', null, degParen) - 2,
+ x: src_number(a, b),
+ });
+ } else if (b) {
+ s.push(pop(s) + 'skewX(' + b + degParen);
+ }
+ }
+
+ function scale(xa, ya, xb, yb, s, q) {
+ if (xa !== xb || ya !== yb) {
+ var i = s.push(pop(s) + 'scale(', null, ',', null, ')');
+ q.push(
+ { i: i - 4, x: src_number(xa, xb) },
+ { i: i - 2, x: src_number(ya, yb) },
+ );
+ } else if (xb !== 1 || yb !== 1) {
+ s.push(pop(s) + 'scale(' + xb + ',' + yb + ')');
+ }
+ }
+
+ return function (a, b) {
+ var s = [], // string constants and placeholders
+ q = []; // number interpolators
+ (a = parse(a)), (b = parse(b));
+ translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q);
+ rotate(a.rotate, b.rotate, s, q);
+ skewX(a.skewX, b.skewX, s, q);
+ scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q);
+ a = b = null; // gc
+ return function (t) {
+ var i = -1,
+ n = q.length,
+ o;
+ while (++i < n) s[(o = q[i]).i] = o.x(t);
+ return s.join('');
+ };
+ };
+ }
+
+ var interpolateTransformCss = interpolateTransform(
+ parseCss,
+ 'px, ',
+ 'px)',
+ 'deg)',
+ );
+ var interpolateTransformSvg = interpolateTransform(
+ parseSvg,
+ ', ',
+ ')',
+ ')',
+ ); // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/tween.js
+
+ function tweenRemove(id, name) {
+ var tween0, tween1;
+ return function () {
+ var schedule = schedule_set(this, id),
+ tween = schedule.tween;
+
+ // If this node shared tween with the previous node,
+ // just assign the updated shared tween and we’re done!
+ // Otherwise, copy-on-write.
+ if (tween !== tween0) {
+ tween1 = tween0 = tween;
+ for (var i = 0, n = tween1.length; i < n; ++i) {
+ if (tween1[i].name === name) {
+ tween1 = tween1.slice();
+ tween1.splice(i, 1);
+ break;
+ }
+ }
+ }
+
+ schedule.tween = tween1;
+ };
+ }
+
+ function tweenFunction(id, name, value) {
+ var tween0, tween1;
+ if (typeof value !== 'function') throw new Error();
+ return function () {
+ var schedule = schedule_set(this, id),
+ tween = schedule.tween;
+
+ // If this node shared tween with the previous node,
+ // just assign the updated shared tween and we’re done!
+ // Otherwise, copy-on-write.
+ if (tween !== tween0) {
+ tween1 = (tween0 = tween).slice();
+ for (
+ var t = { name: name, value: value }, i = 0, n = tween1.length;
+ i < n;
+ ++i
+ ) {
+ if (tween1[i].name === name) {
+ tween1[i] = t;
+ break;
+ }
+ }
+ if (i === n) tween1.push(t);
+ }
+
+ schedule.tween = tween1;
+ };
+ }
+
+ /* harmony default export */ function tween(name, value) {
+ var id = this._id;
+
+ name += '';
+
+ if (arguments.length < 2) {
+ var tween = schedule_get(this.node(), id).tween;
+ for (var i = 0, n = tween.length, t; i < n; ++i) {
+ if ((t = tween[i]).name === name) {
+ return t.value;
+ }
+ }
+ return null;
+ }
+
+ return this.each(
+ (value == null ? tweenRemove : tweenFunction)(id, name, value),
+ );
+ }
+
+ function tweenValue(transition, name, value) {
+ var id = transition._id;
+
+ transition.each(function () {
+ var schedule = schedule_set(this, id);
+ (schedule.value || (schedule.value = {}))[name] = value.apply(
+ this,
+ arguments,
+ );
+ });
+
+ return function (node) {
+ return schedule_get(node, id).value[name];
+ };
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/interpolate.js
+
+ /* harmony default export */ function interpolate(a, b) {
+ var c;
+ return (typeof b === 'number'
+ ? src_number
+ : b instanceof color
+ ? rgb
+ : (c = color(b))
+ ? ((b = c), rgb)
+ : string)(a, b);
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attr.js
+
+ function attr_attrRemove(name) {
+ return function () {
+ this.removeAttribute(name);
+ };
+ }
+
+ function attr_attrRemoveNS(fullname) {
+ return function () {
+ this.removeAttributeNS(fullname.space, fullname.local);
+ };
+ }
+
+ function attr_attrConstant(name, interpolate, value1) {
+ var string00,
+ string1 = value1 + '',
+ interpolate0;
+ return function () {
+ var string0 = this.getAttribute(name);
+ return string0 === string1
+ ? null
+ : string0 === string00
+ ? interpolate0
+ : (interpolate0 = interpolate((string00 = string0), value1));
+ };
+ }
+
+ function attr_attrConstantNS(fullname, interpolate, value1) {
+ var string00,
+ string1 = value1 + '',
+ interpolate0;
+ return function () {
+ var string0 = this.getAttributeNS(fullname.space, fullname.local);
+ return string0 === string1
+ ? null
+ : string0 === string00
+ ? interpolate0
+ : (interpolate0 = interpolate((string00 = string0), value1));
+ };
+ }
+
+ function attr_attrFunction(name, interpolate, value) {
+ var string00, string10, interpolate0;
+ return function () {
+ var string0,
+ value1 = value(this),
+ string1;
+ if (value1 == null) return void this.removeAttribute(name);
+ string0 = this.getAttribute(name);
+ string1 = value1 + '';
+ return string0 === string1
+ ? null
+ : string0 === string00 && string1 === string10
+ ? interpolate0
+ : ((string10 = string1),
+ (interpolate0 = interpolate((string00 = string0), value1)));
+ };
+ }
+
+ function attr_attrFunctionNS(fullname, interpolate, value) {
+ var string00, string10, interpolate0;
+ return function () {
+ var string0,
+ value1 = value(this),
+ string1;
+ if (value1 == null)
+ return void this.removeAttributeNS(fullname.space, fullname.local);
+ string0 = this.getAttributeNS(fullname.space, fullname.local);
+ string1 = value1 + '';
+ return string0 === string1
+ ? null
+ : string0 === string00 && string1 === string10
+ ? interpolate0
+ : ((string10 = string1),
+ (interpolate0 = interpolate((string00 = string0), value1)));
+ };
+ }
+
+ /* harmony default export */ function transition_attr(name, value) {
+ var fullname = namespace(name),
+ i = fullname === 'transform' ? interpolateTransformSvg : interpolate;
+ return this.attrTween(
+ name,
+ typeof value === 'function'
+ ? (fullname.local ? attr_attrFunctionNS : attr_attrFunction)(
+ fullname,
+ i,
+ tweenValue(this, 'attr.' + name, value),
+ )
+ : value == null
+ ? (fullname.local ? attr_attrRemoveNS : attr_attrRemove)(fullname)
+ : (fullname.local ? attr_attrConstantNS : attr_attrConstant)(
+ fullname,
+ i,
+ value,
+ ),
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attrTween.js
+
+ function attrInterpolate(name, i) {
+ return function (t) {
+ this.setAttribute(name, i.call(this, t));
+ };
+ }
+
+ function attrInterpolateNS(fullname, i) {
+ return function (t) {
+ this.setAttributeNS(fullname.space, fullname.local, i.call(this, t));
+ };
+ }
+
+ function attrTweenNS(fullname, value) {
+ var t0, i0;
+ function tween() {
+ var i = value.apply(this, arguments);
+ if (i !== i0) t0 = (i0 = i) && attrInterpolateNS(fullname, i);
+ return t0;
+ }
+ tween._value = value;
+ return tween;
+ }
+
+ function attrTween(name, value) {
+ var t0, i0;
+ function tween() {
+ var i = value.apply(this, arguments);
+ if (i !== i0) t0 = (i0 = i) && attrInterpolate(name, i);
+ return t0;
+ }
+ tween._value = value;
+ return tween;
+ }
+
+ /* harmony default export */ function transition_attrTween(name, value) {
+ var key = 'attr.' + name;
+ if (arguments.length < 2) return (key = this.tween(key)) && key._value;
+ if (value == null) return this.tween(key, null);
+ if (typeof value !== 'function') throw new Error();
+ var fullname = namespace(name);
+ return this.tween(
+ key,
+ (fullname.local ? attrTweenNS : attrTween)(fullname, value),
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/delay.js
+
+ function delayFunction(id, value) {
+ return function () {
+ init(this, id).delay = +value.apply(this, arguments);
+ };
+ }
+
+ function delayConstant(id, value) {
+ return (
+ (value = +value),
+ function () {
+ init(this, id).delay = value;
+ }
+ );
+ }
+
+ /* harmony default export */ function delay(value) {
+ var id = this._id;
+
+ return arguments.length
+ ? this.each(
+ (typeof value === 'function' ? delayFunction : delayConstant)(
+ id,
+ value,
+ ),
+ )
+ : schedule_get(this.node(), id).delay;
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/duration.js
+
+ function durationFunction(id, value) {
+ return function () {
+ schedule_set(this, id).duration = +value.apply(this, arguments);
+ };
+ }
+
+ function durationConstant(id, value) {
+ return (
+ (value = +value),
+ function () {
+ schedule_set(this, id).duration = value;
+ }
+ );
+ }
+
+ /* harmony default export */ function duration(value) {
+ var id = this._id;
+
+ return arguments.length
+ ? this.each(
+ (typeof value === 'function' ? durationFunction : durationConstant)(
+ id,
+ value,
+ ),
+ )
+ : schedule_get(this.node(), id).duration;
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/ease.js
+
+ function easeConstant(id, value) {
+ if (typeof value !== 'function') throw new Error();
+ return function () {
+ schedule_set(this, id).ease = value;
+ };
+ }
+
+ /* harmony default export */ function ease(value) {
+ var id = this._id;
+
+ return arguments.length
+ ? this.each(easeConstant(id, value))
+ : schedule_get(this.node(), id).ease;
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/easeVarying.js
+
+ function easeVarying(id, value) {
+ return function () {
+ var v = value.apply(this, arguments);
+ if (typeof v !== 'function') throw new Error();
+ schedule_set(this, id).ease = v;
+ };
+ }
+
+ /* harmony default export */ function transition_easeVarying(value) {
+ if (typeof value !== 'function') throw new Error();
+ return this.each(easeVarying(this._id, value));
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/filter.js
+
+ /* harmony default export */ function transition_filter(match) {
+ if (typeof match !== 'function') match = matcher(match);
+
+ for (
+ var groups = this._groups,
+ m = groups.length,
+ subgroups = new Array(m),
+ j = 0;
+ j < m;
+ ++j
+ ) {
+ for (
+ var group = groups[j],
+ n = group.length,
+ subgroup = (subgroups[j] = []),
+ node,
+ i = 0;
+ i < n;
+ ++i
+ ) {
+ if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
+ subgroup.push(node);
+ }
+ }
+ }
+
+ return new Transition(subgroups, this._parents, this._name, this._id);
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/merge.js
+
+ /* harmony default export */ function transition_merge(transition) {
+ if (transition._id !== this._id) throw new Error();
+
+ for (
+ var groups0 = this._groups,
+ groups1 = transition._groups,
+ m0 = groups0.length,
+ m1 = groups1.length,
+ m = Math.min(m0, m1),
+ merges = new Array(m0),
+ j = 0;
+ j < m;
+ ++j
+ ) {
+ for (
+ var group0 = groups0[j],
+ group1 = groups1[j],
+ n = group0.length,
+ merge = (merges[j] = new Array(n)),
+ node,
+ i = 0;
+ i < n;
+ ++i
+ ) {
+ if ((node = group0[i] || group1[i])) {
+ merge[i] = node;
+ }
+ }
+ }
+
+ for (; j < m0; ++j) {
+ merges[j] = groups0[j];
+ }
+
+ return new Transition(merges, this._parents, this._name, this._id);
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/on.js
+
+ function start(name) {
+ return (name + '')
+ .trim()
+ .split(/^|\s+/)
+ .every(function (t) {
+ var i = t.indexOf('.');
+ if (i >= 0) t = t.slice(0, i);
+ return !t || t === 'start';
+ });
+ }
+
+ function onFunction(id, name, listener) {
+ var on0,
+ on1,
+ sit = start(name) ? init : schedule_set;
+ return function () {
+ var schedule = sit(this, id),
+ on = schedule.on;
+
+ // If this node shared a dispatch with the previous node,
+ // just assign the updated shared dispatch and we’re done!
+ // Otherwise, copy-on-write.
+ if (on !== on0) (on1 = (on0 = on).copy()).on(name, listener);
+
+ schedule.on = on1;
+ };
+ }
+
+ /* harmony default export */ function transition_on(name, listener) {
+ var id = this._id;
+
+ return arguments.length < 2
+ ? schedule_get(this.node(), id).on.on(name)
+ : this.each(onFunction(id, name, listener));
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/remove.js
+
+ function removeFunction(id) {
+ return function () {
+ var parent = this.parentNode;
+ for (var i in this.__transition) if (+i !== id) return;
+ if (parent) parent.removeChild(this);
+ };
+ }
+
+ /* harmony default export */ function transition_remove() {
+ return this.on('end.remove', removeFunction(this._id));
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/select.js
+
+ /* harmony default export */ function transition_select(select) {
+ var name = this._name,
+ id = this._id;
+
+ if (typeof select !== 'function') select = selector(select);
+
+ for (
+ var groups = this._groups,
+ m = groups.length,
+ subgroups = new Array(m),
+ j = 0;
+ j < m;
+ ++j
+ ) {
+ for (
+ var group = groups[j],
+ n = group.length,
+ subgroup = (subgroups[j] = new Array(n)),
+ node,
+ subnode,
+ i = 0;
+ i < n;
+ ++i
+ ) {
+ if (
+ (node = group[i]) &&
+ (subnode = select.call(node, node.__data__, i, group))
+ ) {
+ if ('__data__' in node) subnode.__data__ = node.__data__;
+ subgroup[i] = subnode;
+ schedule(
+ subgroup[i],
+ name,
+ id,
+ i,
+ subgroup,
+ schedule_get(node, id),
+ );
+ }
+ }
+ }
+
+ return new Transition(subgroups, this._parents, name, id);
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selectAll.js
+
+ /* harmony default export */ function transition_selectAll(select) {
+ var name = this._name,
+ id = this._id;
+
+ if (typeof select !== 'function') select = selectorAll(select);
+
+ for (
+ var groups = this._groups,
+ m = groups.length,
+ subgroups = [],
+ parents = [],
+ j = 0;
+ j < m;
+ ++j
+ ) {
+ for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
+ if ((node = group[i])) {
+ for (
+ var children = select.call(node, node.__data__, i, group),
+ child,
+ inherit = schedule_get(node, id),
+ k = 0,
+ l = children.length;
+ k < l;
+ ++k
+ ) {
+ if ((child = children[k])) {
+ schedule(child, name, id, k, children, inherit);
+ }
+ }
+ subgroups.push(children);
+ parents.push(node);
+ }
+ }
+ }
+
+ return new Transition(subgroups, parents, name, id);
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selection.js
+
+ var selection_Selection = src_selection.prototype.constructor;
+
+ /* harmony default export */ function transition_selection() {
+ return new selection_Selection(this._groups, this._parents);
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/style.js
+
+ function styleNull(name, interpolate) {
+ var string00, string10, interpolate0;
+ return function () {
+ var string0 = styleValue(this, name),
+ string1 = (this.style.removeProperty(name), styleValue(this, name));
+ return string0 === string1
+ ? null
+ : string0 === string00 && string1 === string10
+ ? interpolate0
+ : (interpolate0 = interpolate(
+ (string00 = string0),
+ (string10 = string1),
+ ));
+ };
+ }
+
+ function style_styleRemove(name) {
+ return function () {
+ this.style.removeProperty(name);
+ };
+ }
+
+ function style_styleConstant(name, interpolate, value1) {
+ var string00,
+ string1 = value1 + '',
+ interpolate0;
+ return function () {
+ var string0 = styleValue(this, name);
+ return string0 === string1
+ ? null
+ : string0 === string00
+ ? interpolate0
+ : (interpolate0 = interpolate((string00 = string0), value1));
+ };
+ }
+
+ function style_styleFunction(name, interpolate, value) {
+ var string00, string10, interpolate0;
+ return function () {
+ var string0 = styleValue(this, name),
+ value1 = value(this),
+ string1 = value1 + '';
+ if (value1 == null)
+ string1 = value1 =
+ (this.style.removeProperty(name), styleValue(this, name));
+ return string0 === string1
+ ? null
+ : string0 === string00 && string1 === string10
+ ? interpolate0
+ : ((string10 = string1),
+ (interpolate0 = interpolate((string00 = string0), value1)));
+ };
+ }
+
+ function styleMaybeRemove(id, name) {
+ var on0,
+ on1,
+ listener0,
+ key = 'style.' + name,
+ event = 'end.' + key,
+ remove;
+ return function () {
+ var schedule = schedule_set(this, id),
+ on = schedule.on,
+ listener =
+ schedule.value[key] == null
+ ? remove || (remove = style_styleRemove(name))
+ : undefined;
+
+ // If this node shared a dispatch with the previous node,
+ // just assign the updated shared dispatch and we’re done!
+ // Otherwise, copy-on-write.
+ if (on !== on0 || listener0 !== listener)
+ (on1 = (on0 = on).copy()).on(event, (listener0 = listener));
+
+ schedule.on = on1;
+ };
+ }
+
+ /* harmony default export */ function transition_style(
+ name,
+ value,
+ priority,
+ ) {
+ var i =
+ (name += '') === 'transform' ? interpolateTransformCss : interpolate;
+ return value == null
+ ? this.styleTween(name, styleNull(name, i)).on(
+ 'end.style.' + name,
+ style_styleRemove(name),
+ )
+ : typeof value === 'function'
+ ? this.styleTween(
+ name,
+ style_styleFunction(
+ name,
+ i,
+ tweenValue(this, 'style.' + name, value),
+ ),
+ ).each(styleMaybeRemove(this._id, name))
+ : this.styleTween(
+ name,
+ style_styleConstant(name, i, value),
+ priority,
+ ).on('end.style.' + name, null);
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/styleTween.js
+
+ function styleInterpolate(name, i, priority) {
+ return function (t) {
+ this.style.setProperty(name, i.call(this, t), priority);
+ };
+ }
+
+ function styleTween(name, value, priority) {
+ var t, i0;
+ function tween() {
+ var i = value.apply(this, arguments);
+ if (i !== i0) t = (i0 = i) && styleInterpolate(name, i, priority);
+ return t;
+ }
+ tween._value = value;
+ return tween;
+ }
+
+ /* harmony default export */ function transition_styleTween(
+ name,
+ value,
+ priority,
+ ) {
+ var key = 'style.' + (name += '');
+ if (arguments.length < 2) return (key = this.tween(key)) && key._value;
+ if (value == null) return this.tween(key, null);
+ if (typeof value !== 'function') throw new Error();
+ return this.tween(
+ key,
+ styleTween(name, value, priority == null ? '' : priority),
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/text.js
+
+ function text_textConstant(value) {
+ return function () {
+ this.textContent = value;
+ };
+ }
+
+ function text_textFunction(value) {
+ return function () {
+ var value1 = value(this);
+ this.textContent = value1 == null ? '' : value1;
+ };
+ }
+
+ /* harmony default export */ function transition_text(value) {
+ return this.tween(
+ 'text',
+ typeof value === 'function'
+ ? text_textFunction(tweenValue(this, 'text', value))
+ : text_textConstant(value == null ? '' : value + ''),
+ );
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/textTween.js
+
+ function textInterpolate(i) {
+ return function (t) {
+ this.textContent = i.call(this, t);
+ };
+ }
+
+ function textTween(value) {
+ var t0, i0;
+ function tween() {
+ var i = value.apply(this, arguments);
+ if (i !== i0) t0 = (i0 = i) && textInterpolate(i);
+ return t0;
+ }
+ tween._value = value;
+ return tween;
+ }
+
+ /* harmony default export */ function transition_textTween(value) {
+ var key = 'text';
+ if (arguments.length < 1) return (key = this.tween(key)) && key._value;
+ if (value == null) return this.tween(key, null);
+ if (typeof value !== 'function') throw new Error();
+ return this.tween(key, textTween(value));
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/transition.js
+
+ /* harmony default export */ function transition() {
+ var name = this._name,
+ id0 = this._id,
+ id1 = newId();
+
+ for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
+ if ((node = group[i])) {
+ var inherit = schedule_get(node, id0);
+ schedule(node, name, id1, i, group, {
+ time: inherit.time + inherit.delay + inherit.duration,
+ delay: 0,
+ duration: inherit.duration,
+ ease: inherit.ease,
+ });
+ }
+ }
+ }
+
+ return new Transition(groups, this._parents, name, id1);
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/end.js
+
+ /* harmony default export */ function end() {
+ var on0,
+ on1,
+ that = this,
+ id = that._id,
+ size = that.size();
+ return new Promise(function (resolve, reject) {
+ var cancel = { value: reject },
+ end = {
+ value: function () {
+ if (--size === 0) resolve();
+ },
+ };
+
+ that.each(function () {
+ var schedule = schedule_set(this, id),
+ on = schedule.on;
+
+ // If this node shared a dispatch with the previous node,
+ // just assign the updated shared dispatch and we’re done!
+ // Otherwise, copy-on-write.
+ if (on !== on0) {
+ on1 = (on0 = on).copy();
+ on1._.cancel.push(cancel);
+ on1._.interrupt.push(cancel);
+ on1._.end.push(end);
+ }
+
+ schedule.on = on1;
+ });
+
+ // The selection was empty, resolve end immediately
+ if (size === 0) resolve();
+ });
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/index.js
+
+ var id = 0;
+
+ function Transition(groups, parents, name, id) {
+ this._groups = groups;
+ this._parents = parents;
+ this._name = name;
+ this._id = id;
+ }
+
+ function transition_transition(name) {
+ return src_selection().transition(name);
+ }
+
+ function newId() {
+ return ++id;
+ }
+
+ var selection_prototype = src_selection.prototype;
+
+ Transition.prototype = transition_transition.prototype = {
+ constructor: Transition,
+ select: transition_select,
+ selectAll: transition_selectAll,
+ selectChild: selection_prototype.selectChild,
+ selectChildren: selection_prototype.selectChildren,
+ filter: transition_filter,
+ merge: transition_merge,
+ selection: transition_selection,
+ transition: transition,
+ call: selection_prototype.call,
+ nodes: selection_prototype.nodes,
+ node: selection_prototype.node,
+ size: selection_prototype.size,
+ empty: selection_prototype.empty,
+ each: selection_prototype.each,
+ on: transition_on,
+ attr: transition_attr,
+ attrTween: transition_attrTween,
+ style: transition_style,
+ styleTween: transition_styleTween,
+ text: transition_text,
+ textTween: transition_textTween,
+ remove: transition_remove,
+ tween: tween,
+ delay: delay,
+ duration: duration,
+ ease: ease,
+ easeVarying: transition_easeVarying,
+ end: end,
+ [Symbol.iterator]: selection_prototype[Symbol.iterator],
+ }; // CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/transition.js
+
+ var defaultTiming = {
+ time: null, // Set on use.
+ delay: 0,
+ duration: 250,
+ ease: cubicInOut,
+ };
+
+ function inherit(node, id) {
+ var timing;
+ while (!(timing = node.__transition) || !(timing = timing[id])) {
+ if (!(node = node.parentNode)) {
+ throw new Error(`transition ${id} not found`);
+ }
+ }
+ return timing;
+ }
+
+ /* harmony default export */ function selection_transition(name) {
+ var id, timing;
+
+ if (name instanceof Transition) {
+ (id = name._id), (name = name._name);
+ } else {
+ (id = newId()),
+ ((timing = defaultTiming).time = now()),
+ (name = name == null ? null : name + '');
+ }
+
+ for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) {
+ for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
+ if ((node = group[i])) {
+ schedule(node, name, id, i, group, timing || inherit(node, id));
+ }
+ }
+ }
+
+ return new Transition(groups, this._parents, name, id);
+ } // CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/index.js
+
+ src_selection.prototype.interrupt = selection_interrupt;
+ src_selection.prototype.transition = selection_transition; // CONCATENATED MODULE: ../node_modules/d3-transition/src/index.js // CONCATENATED MODULE: ./colorUtils.js
+
+ function generateHash(name) {
+ // Return a vector (0.0->1.0) that is a hash of the input string.
+ // The hash is computed to favor early characters over later ones, so
+ // that strings with similar starts have similar vectors. Only the first
+ // 6 characters are considered.
+ const MAX_CHAR = 6;
+
+ let hash = 0;
+ let maxHash = 0;
+ let weight = 1;
+ const mod = 10;
+
+ if (name) {
+ for (let i = 0; i < name.length; i++) {
+ if (i > MAX_CHAR) {
+ break;
+ }
+ hash += weight * (name.charCodeAt(i) % mod);
+ maxHash += weight * (mod - 1);
+ weight *= 0.7;
+ }
+ if (maxHash > 0) {
+ hash = hash / maxHash;
+ }
+ }
+ return hash;
+ }
+
+ function generateColorVector(name) {
+ let vector = 0;
+ if (name) {
+ const nameArr = name.split('`');
+ if (nameArr.length > 1) {
+ name = nameArr[nameArr.length - 1]; // drop module name if present
+ }
+ name = name.split('(')[0]; // drop extra info
+ vector = generateHash(name);
+ }
+ return vector;
+ } // CONCATENATED MODULE: ./colorScheme.js
+
+ function calculateColor(hue, vector) {
+ let r;
+ let g;
+ let b;
+
+ if (hue === 'red') {
+ r = 200 + Math.round(55 * vector);
+ g = 50 + Math.round(80 * vector);
+ b = g;
+ } else if (hue === 'orange') {
+ r = 190 + Math.round(65 * vector);
+ g = 90 + Math.round(65 * vector);
+ b = 0;
+ } else if (hue === 'yellow') {
+ r = 175 + Math.round(55 * vector);
+ g = r;
+ b = 50 + Math.round(20 * vector);
+ } else if (hue === 'green') {
+ r = 50 + Math.round(60 * vector);
+ g = 200 + Math.round(55 * vector);
+ b = r;
+ } else if (hue === 'pastelgreen') {
+ // rgb(163,195,72) - rgb(238,244,221)
+ r = 163 + Math.round(75 * vector);
+ g = 195 + Math.round(49 * vector);
+ b = 72 + Math.round(149 * vector);
+ } else if (hue === 'blue') {
+ // rgb(91,156,221) - rgb(217,232,247)
+ r = 91 + Math.round(126 * vector);
+ g = 156 + Math.round(76 * vector);
+ b = 221 + Math.round(26 * vector);
+ } else if (hue === 'aqua') {
+ r = 50 + Math.round(60 * vector);
+ g = 165 + Math.round(55 * vector);
+ b = g;
+ } else if (hue === 'cold') {
+ r = 0 + Math.round(55 * (1 - vector));
+ g = 0 + Math.round(230 * (1 - vector));
+ b = 200 + Math.round(55 * vector);
+ } else {
+ // original warm palette
+ r = 200 + Math.round(55 * vector);
+ g = 0 + Math.round(230 * (1 - vector));
+ b = 0 + Math.round(55 * (1 - vector));
+ }
+
+ return 'rgb(' + r + ',' + g + ',' + b + ')';
+ } // CONCATENATED MODULE: ./flamegraph.js
+
+ /* harmony default export */ function flamegraph() {
+ let w = 960; // graph width
+ let h = null; // graph height
+ let c = 18; // cell height
+ let selection = null; // selection
+ let tooltip = null; // tooltip
+ let title = ''; // graph title
+ let transitionDuration = 750;
+ let transitionEase = cubicInOut; // tooltip offset
+ let sort = false;
+ let inverted = false; // invert the graph direction
+ let clickHandler = null;
+ let hoverHandler = null;
+ let minFrameSize = 0;
+ let detailsElement = null;
+ let searchDetails = null;
+ let selfValue = false;
+ let resetHeightOnZoom = false;
+ let scrollOnZoom = false;
+ let minHeight = null;
+ let computeDelta = false;
+ let colorHue = null;
+
+ let getName = function (d) {
+ return d.data.n || d.data.name;
+ };
+
+ let getValue = function (d) {
+ if ('v' in d) {
+ return d.v;
+ } else {
+ return d.value;
+ }
+ };
+
+ let getChildren = function (d) {
+ return d.c || d.children;
+ };
+
+ let getLibtype = function (d) {
+ return d.data.l || d.data.libtype;
+ };
+
+ let getDelta = function (d) {
+ if ('d' in d.data) {
+ return d.data.d;
+ } else {
+ return d.data.delta;
+ }
+ };
+
+ let searchHandler = function (searchResults, searchSum, totalValue) {
+ searchDetails = () => {
+ if (detailsElement) {
+ detailsElement.textContent =
+ 'search: ' +
+ searchSum +
+ ' of ' +
+ totalValue +
+ ' total time ( ' +
+ format('.3f')(100 * (searchSum / totalValue), 3) +
+ '%)';
+ }
+ };
+ searchDetails();
+ };
+ const originalSearchHandler = searchHandler;
+
+ let searchMatch = (d, term, ignoreCase = false) => {
+ if (!term) {
+ return false;
+ }
+ let label = getName(d);
+ if (ignoreCase) {
+ term = term.toLowerCase();
+ label = label.toLowerCase();
+ }
+ const re = new RegExp(term);
+ return typeof label !== 'undefined' && label && label.match(re);
+ };
+ const originalSearchMatch = searchMatch;
+
+ let detailsHandler = function (d) {
+ if (detailsElement) {
+ if (d) {
+ detailsElement.textContent = d;
+ } else {
+ if (typeof searchDetails === 'function') {
+ searchDetails();
+ } else {
+ detailsElement.textContent = '';
+ }
+ }
+ }
+ };
+ const originalDetailsHandler = detailsHandler;
+
+ let labelHandler = function (d) {
+ return (
+ getName(d) +
+ ' (' +
+ format('.3f')(100 * (d.x1 - d.x0), 3) +
+ '%, ' +
+ getValue(d) +
+ ' ms)'
+ );
+ };
+
+ let colorMapper = function (d) {
+ return d.highlight ? '#E600E6' : colorHash(getName(d), getLibtype(d));
+ };
+ const originalColorMapper = colorMapper;
+
+ function colorHash(name, libtype) {
+ // Return a color for the given name and library type. The library type
+ // selects the hue, and the name is hashed to a color in that hue.
+
+ // default when libtype is not in use
+ let hue = colorHue || 'warm';
+
+ if (!colorHue && !(typeof libtype === 'undefined' || libtype === '')) {
+ // Select hue. Order is important.
+ hue = 'red';
+ if (typeof name !== 'undefined' && name && name.match(/::/)) {
+ hue = 'yellow';
+ }
+ if (libtype === 'kernel') {
+ hue = 'orange';
+ } else if (libtype === 'jit') {
+ hue = 'green';
+ } else if (libtype === 'inlined') {
+ hue = 'aqua';
+ }
+ }
+
+ const vector = generateColorVector(name);
+ return calculateColor(hue, vector);
+ }
+
+ function show(d) {
+ d.data.fade = false;
+ d.data.hide = false;
+ if (d.children) {
+ d.children.forEach(show);
+ }
+ }
+
+ function hideSiblings(node) {
+ let child = node;
+ let parent = child.parent;
+ let children, i, sibling;
+ while (parent) {
+ children = parent.children;
+ i = children.length;
+ while (i--) {
+ sibling = children[i];
+ if (sibling !== child) {
+ sibling.data.hide = true;
+ }
+ }
+ child = parent;
+ parent = child.parent;
+ }
+ }
+
+ function fadeAncestors(d) {
+ if (d.parent) {
+ d.parent.data.fade = true;
+ fadeAncestors(d.parent);
+ }
+ }
+
+ function zoom(d) {
+ if (tooltip) tooltip.hide();
+ hideSiblings(d);
+ show(d);
+ fadeAncestors(d);
+ update();
+ if (scrollOnZoom) {
+ const chartOffset = src_select(this).select('svg')._groups[0][0]
+ .parentNode.offsetTop;
+ const maxFrames = (window.innerHeight - chartOffset) / c;
+ const frameOffset = (d.height - maxFrames + 10) * c;
+ window.scrollTo({
+ top: chartOffset + frameOffset,
+ left: 0,
+ behavior: 'smooth',
+ });
+ }
+ if (typeof clickHandler === 'function') {
+ clickHandler(d);
+ }
+ }
+
+ function searchTree(d, term) {
+ const results = [];
+ let sum = 0;
+
+ function searchInner(d, foundParent) {
+ let found = false;
+
+ if (searchMatch(d, term)) {
+ d.highlight = true;
+ found = true;
+ if (!foundParent) {
+ sum += getValue(d);
+ }
+ results.push(d);
+ } else {
+ d.highlight = false;
+ }
+
+ if (getChildren(d)) {
+ getChildren(d).forEach(function (child) {
+ searchInner(child, foundParent || found);
+ });
+ }
+ }
+ searchInner(d, false);
+
+ return [results, sum];
+ }
+
+ function findTree(d, id) {
+ if (d.id === id) {
+ return d;
+ } else {
+ const children = getChildren(d);
+ if (children) {
+ for (let i = 0; i < children.length; i++) {
+ const found = findTree(children[i], id);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ }
+ }
+
+ function clear(d) {
+ d.highlight = false;
+ if (getChildren(d)) {
+ getChildren(d).forEach(function (child) {
+ clear(child);
+ });
+ }
+ }
+
+ function doSort(a, b) {
+ if (typeof sort === 'function') {
+ return sort(a, b);
+ } else if (sort) {
+ return ascending_ascending(getName(a), getName(b));
+ }
+ }
+
+ const p = partition();
+
+ function filterNodes(root) {
+ let nodeList = root.descendants();
+ if (minFrameSize > 0) {
+ const kx = w / (root.x1 - root.x0);
+ nodeList = nodeList.filter(function (el) {
+ return (el.x1 - el.x0) * kx > minFrameSize;
+ });
+ }
+ return nodeList;
+ }
+
+ function update() {
+ selection.each(function (root) {
+ const x = linear_linear().range([0, w]);
+ const y = linear_linear().range([0, c]);
+
+ reappraiseNode(root);
+
+ if (sort) root.sort(doSort);
+
+ p(root);
+
+ const kx = w / (root.x1 - root.x0);
+ function width(d) {
+ return (d.x1 - d.x0) * kx;
+ }
+
+ const descendants = filterNodes(root);
+ const svg = src_select(this).select('svg');
+ svg.attr('width', w);
+
+ let g = svg.selectAll('g').data(descendants, function (d) {
+ return d.id;
+ });
+
+ // if height is not set: set height on first update, after nodes were filtered by minFrameSize
+ if (!h || resetHeightOnZoom) {
+ const maxDepth = Math.max.apply(
+ null,
+ descendants.map(function (n) {
+ return n.depth;
+ }),
+ );
+
+ h = (maxDepth + 3) * c;
+ if (h < minHeight) h = minHeight;
+
+ svg.attr('height', h);
+ }
+
+ g.transition()
+ .duration(transitionDuration)
+ .ease(transitionEase)
+ .attr('transform', function (d) {
+ return (
+ 'translate(' +
+ x(d.x0) +
+ ',' +
+ (inverted ? y(d.depth) : h - y(d.depth) - c) +
+ ')'
+ );
+ });
+
+ g.select('rect')
+ .transition()
+ .duration(transitionDuration)
+ .ease(transitionEase)
+ .attr('width', width);
+
+ const node = g
+ .enter()
+ .append('svg:g')
+ .attr('transform', function (d) {
+ return (
+ 'translate(' +
+ x(d.x0) +
+ ',' +
+ (inverted ? y(d.depth) : h - y(d.depth) - c) +
+ ')'
+ );
+ });
+
+ node
+ .append('svg:rect')
+ .transition()
+ .delay(transitionDuration / 2)
+ .attr('width', width);
+
+ if (!tooltip) {
+ node.append('svg:title');
+ }
+
+ node.append('foreignObject').append('xhtml:div');
+
+ // Now we have to re-select to see the new elements (why?).
+ g = svg.selectAll('g').data(descendants, function (d) {
+ return d.id;
+ });
+
+ g.attr('width', width)
+ .attr('height', function (d) {
+ return c;
+ })
+ .attr('name', function (d) {
+ return getName(d);
+ })
+ .attr('class', function (d) {
+ return d.data.fade ? 'frame fade' : 'frame';
+ });
+
+ g.select('rect')
+ .attr('height', function (d) {
+ return c;
+ })
+ .attr('fill', function (d) {
+ return colorMapper(d);
+ });
+
+ if (!tooltip) {
+ g.select('title').text(labelHandler);
+ }
+
+ g.select('foreignObject')
+ .attr('width', width)
+ .attr('height', function (d) {
+ return c;
+ })
+ .select('div')
+ .attr('class', 'd3-flame-graph-label')
+ .style('display', function (d) {
+ return width(d) < 35 ? 'none' : 'block';
+ })
+ .transition()
+ .delay(transitionDuration)
+ .text(getName);
+
+ g.on('click', (_, d) => {
+ zoom(d);
+ });
+
+ g.exit().remove();
+
+ g.on('mouseover', function (_, d) {
+ if (tooltip) tooltip.show(d, this);
+ detailsHandler(labelHandler(d));
+ if (typeof hoverHandler === 'function') {
+ hoverHandler(d);
+ }
+ }).on('mouseout', function () {
+ if (tooltip) tooltip.hide();
+ detailsHandler(null);
+ });
+ });
+ }
+
+ function merge(data, samples) {
+ samples.forEach(function (sample) {
+ const node = data.find(function (element) {
+ return element.name === sample.name;
+ });
+
+ if (node) {
+ node.value += sample.value;
+ if (sample.children) {
+ if (!node.children) {
+ node.children = [];
+ }
+ merge(node.children, sample.children);
+ }
+ } else {
+ data.push(sample);
+ }
+ });
+ }
+
+ function forEachNode(node, f) {
+ f(node);
+ let children = node.children;
+ if (children) {
+ const stack = [children];
+ let count, child, grandChildren;
+ while (stack.length) {
+ children = stack.pop();
+ count = children.length;
+ while (count--) {
+ child = children[count];
+ f(child);
+ grandChildren = child.children;
+ if (grandChildren) {
+ stack.push(grandChildren);
+ }
+ }
+ }
+ }
+ }
+
+ function adoptNode(node) {
+ let id = 0;
+ forEachNode(node, function (n) {
+ n.id = id++;
+ });
+ }
+
+ function reappraiseNode(root) {
+ let node,
+ children,
+ grandChildren,
+ childrenValue,
+ i,
+ j,
+ child,
+ childValue;
+ const stack = [];
+ const included = [];
+ const excluded = [];
+ const compoundValue = !selfValue;
+ let item = root.data;
+ if (item.hide) {
+ root.value = 0;
+ children = root.children;
+ if (children) {
+ excluded.push(children);
+ }
+ } else {
+ root.value = item.fade ? 0 : getValue(item);
+ stack.push(root);
+ }
+ // First DFS pass:
+ // 1. Update node.value with node's self value
+ // 2. Populate excluded list with children under hidden nodes
+ // 3. Populate included list with children under visible nodes
+ while ((node = stack.pop())) {
+ children = node.children;
+ if (children && (i = children.length)) {
+ childrenValue = 0;
+ while (i--) {
+ child = children[i];
+ item = child.data;
+ if (item.hide) {
+ child.value = 0;
+ grandChildren = child.children;
+ if (grandChildren) {
+ excluded.push(grandChildren);
+ }
+ continue;
+ }
+ if (item.fade) {
+ child.value = 0;
+ } else {
+ childValue = getValue(item);
+ child.value = childValue;
+ childrenValue += childValue;
+ }
+ stack.push(child);
+ }
+ // Here second part of `&&` is actually checking for `node.data.fade`. However,
+ // checking for node.value is faster and presents more oportunities for JS optimizer.
+ if (compoundValue && node.value) {
+ node.value -= childrenValue;
+ }
+ included.push(children);
+ }
+ }
+ // Postorder traversal to compute compound value of each visible node.
+ i = included.length;
+ while (i--) {
+ children = included[i];
+ childrenValue = 0;
+ j = children.length;
+ while (j--) {
+ childrenValue += children[j].value;
+ }
+ children[0].parent.value += childrenValue;
+ }
+ // Continue DFS to set value of all hidden nodes to 0.
+ while (excluded.length) {
+ children = excluded.pop();
+ j = children.length;
+ while (j--) {
+ child = children[j];
+ child.value = 0;
+ grandChildren = child.children;
+ if (grandChildren) {
+ excluded.push(grandChildren);
+ }
+ }
+ }
+ }
+
+ function processData() {
+ selection.datum((data) => {
+ if (data.constructor.name !== 'Node') {
+ // creating a root hierarchical structure
+ const root = hierarchy(data, getChildren);
+
+ // augumenting nodes with ids
+ adoptNode(root);
+
+ // calculate actual value
+ reappraiseNode(root);
+
+ // store value for later use
+ root.originalValue = root.value;
+
+ // computing deltas for differentials
+ if (computeDelta) {
+ root.eachAfter((node) => {
+ let sum = getDelta(node);
+ const children = node.children;
+ let i = children && children.length;
+ while (--i >= 0) sum += children[i].delta;
+ node.delta = sum;
+ });
+ }
+
+ // setting the bound data for the selection
+ return root;
+ }
+ });
+ }
+
+ function chart(s) {
+ if (!arguments.length) {
+ return chart;
+ }
+
+ // saving the selection on `.call`
+ selection = s;
+
+ // processing raw data to be used in the chart
+ processData();
+
+ // create chart svg
+ selection.each(function (data) {
+ if (src_select(this).select('svg').size() === 0) {
+ const svg = src_select(this)
+ .append('svg:svg')
+ .attr('width', w)
+ .attr('class', 'partition d3-flame-graph');
+
+ if (h) {
+ if (h < minHeight) h = minHeight;
+ svg.attr('height', h);
+ }
+
+ svg
+ .append('svg:text')
+ .attr('class', 'title')
+ .attr('text-anchor', 'middle')
+ .attr('y', '25')
+ .attr('x', w / 2)
+ .attr('fill', '#808080')
+ .text(title);
+
+ if (tooltip) svg.call(tooltip);
+ }
+ });
+
+ // first draw
+ update();
+ }
+
+ chart.height = function (_) {
+ if (!arguments.length) {
+ return h;
+ }
+ h = _;
+ return chart;
+ };
+
+ chart.minHeight = function (_) {
+ if (!arguments.length) {
+ return minHeight;
+ }
+ minHeight = _;
+ return chart;
+ };
+
+ chart.width = function (_) {
+ if (!arguments.length) {
+ return w;
+ }
+ w = _;
+ return chart;
+ };
+
+ chart.cellHeight = function (_) {
+ if (!arguments.length) {
+ return c;
+ }
+ c = _;
+ return chart;
+ };
+
+ chart.tooltip = function (_) {
+ if (!arguments.length) {
+ return tooltip;
+ }
+ if (typeof _ === 'function') {
+ tooltip = _;
+ }
+ return chart;
+ };
+
+ chart.title = function (_) {
+ if (!arguments.length) {
+ return title;
+ }
+ title = _;
+ return chart;
+ };
+
+ chart.transitionDuration = function (_) {
+ if (!arguments.length) {
+ return transitionDuration;
+ }
+ transitionDuration = _;
+ return chart;
+ };
+
+ chart.transitionEase = function (_) {
+ if (!arguments.length) {
+ return transitionEase;
+ }
+ transitionEase = _;
+ return chart;
+ };
+
+ chart.sort = function (_) {
+ if (!arguments.length) {
+ return sort;
+ }
+ sort = _;
+ return chart;
+ };
+
+ chart.inverted = function (_) {
+ if (!arguments.length) {
+ return inverted;
+ }
+ inverted = _;
+ return chart;
+ };
+
+ chart.computeDelta = function (_) {
+ if (!arguments.length) {
+ return computeDelta;
+ }
+ computeDelta = _;
+ return chart;
+ };
+
+ chart.setLabelHandler = function (_) {
+ if (!arguments.length) {
+ return labelHandler;
+ }
+ labelHandler = _;
+ return chart;
+ };
+ // Kept for backwards compatibility.
+ chart.label = chart.setLabelHandler;
+
+ chart.search = function (term) {
+ const searchResults = [];
+ let searchSum = 0;
+ let totalValue = 0;
+ selection.each(function (data) {
+ const res = searchTree(data, term);
+ searchResults.push(...res[0]);
+ searchSum += res[1];
+ totalValue += data.originalValue;
+ });
+ searchHandler(searchResults, searchSum, totalValue);
+ update();
+ };
+
+ chart.findById = function (id) {
+ if (typeof id === 'undefined' || id === null) {
+ return null;
+ }
+ let found = null;
+ selection.each(function (data) {
+ if (found === null) {
+ found = findTree(data, id);
+ }
+ });
+ return found;
+ };
+
+ chart.clear = function () {
+ detailsHandler(null);
+ selection.each(function (root) {
+ clear(root);
+ update();
+ });
+ };
+
+ chart.zoomTo = function (d) {
+ zoom(d);
+ };
+
+ chart.resetZoom = function () {
+ selection.each(function (root) {
+ zoom(root); // zoom to root
+ });
+ };
+
+ chart.onClick = function (_) {
+ if (!arguments.length) {
+ return clickHandler;
+ }
+ clickHandler = _;
+ return chart;
+ };
+
+ chart.onHover = function (_) {
+ if (!arguments.length) {
+ return hoverHandler;
+ }
+ hoverHandler = _;
+ return chart;
+ };
+
+ chart.merge = function (data) {
+ if (!selection) {
+ return chart;
+ }
+
+ // TODO: Fix merge with zoom
+ // Merging a zoomed chart doesn't work properly, so
+ // clearing zoom before merge.
+ // To apply zoom on merge, we would need to set hide
+ // and fade on new data according to current data.
+ // New ids are generated for the whole data structure,
+ // so previous ids might not be the same. For merge to
+ // work with zoom, previous ids should be maintained.
+ this.resetZoom();
+
+ // Clear search details
+ // Merge requires a new search, updating data and
+ // the details handler with search results.
+ // Since we don't store the search term, can't
+ // perform search again.
+ searchDetails = null;
+ detailsHandler(null);
+
+ selection.datum((root) => {
+ merge([root.data], [data]);
+ return root.data;
+ });
+ processData();
+ update();
+ return chart;
+ };
+
+ chart.update = function (data) {
+ if (!selection) {
+ return chart;
+ }
+ if (data) {
+ selection.datum(data);
+ processData();
+ }
+ update();
+ return chart;
+ };
+
+ chart.destroy = function () {
+ if (!selection) {
+ return chart;
+ }
+ if (tooltip) {
+ tooltip.hide();
+ if (typeof tooltip.destroy === 'function') {
+ tooltip.destroy();
+ }
+ }
+ selection.selectAll('svg').remove();
+ return chart;
+ };
+
+ chart.setColorMapper = function (_) {
+ if (!arguments.length) {
+ colorMapper = originalColorMapper;
+ return chart;
+ }
+ colorMapper = (d) => {
+ const originalColor = originalColorMapper(d);
+ return _(d, originalColor);
+ };
+ return chart;
+ };
+ // Kept for backwards compatibility.
+ chart.color = chart.setColorMapper;
+
+ chart.setColorHue = function (_) {
+ if (!arguments.length) {
+ colorHue = null;
+ return chart;
+ }
+ colorHue = _;
+ return chart;
+ };
+
+ chart.minFrameSize = function (_) {
+ if (!arguments.length) {
+ return minFrameSize;
+ }
+ minFrameSize = _;
+ return chart;
+ };
+
+ chart.setDetailsElement = function (_) {
+ if (!arguments.length) {
+ return detailsElement;
+ }
+ detailsElement = _;
+ return chart;
+ };
+ // Kept for backwards compatibility.
+ chart.details = chart.setDetailsElement;
+
+ chart.selfValue = function (_) {
+ if (!arguments.length) {
+ return selfValue;
+ }
+ selfValue = _;
+ return chart;
+ };
+
+ chart.resetHeightOnZoom = function (_) {
+ if (!arguments.length) {
+ return resetHeightOnZoom;
+ }
+ resetHeightOnZoom = _;
+ return chart;
+ };
+
+ chart.scrollOnZoom = function (_) {
+ if (!arguments.length) {
+ return scrollOnZoom;
+ }
+ scrollOnZoom = _;
+ return chart;
+ };
+
+ chart.getName = function (_) {
+ if (!arguments.length) {
+ return getName;
+ }
+ getName = _;
+ return chart;
+ };
+
+ chart.getValue = function (_) {
+ if (!arguments.length) {
+ return getValue;
+ }
+ getValue = _;
+ return chart;
+ };
+
+ chart.getChildren = function (_) {
+ if (!arguments.length) {
+ return getChildren;
+ }
+ getChildren = _;
+ return chart;
+ };
+
+ chart.getLibtype = function (_) {
+ if (!arguments.length) {
+ return getLibtype;
+ }
+ getLibtype = _;
+ return chart;
+ };
+
+ chart.getDelta = function (_) {
+ if (!arguments.length) {
+ return getDelta;
+ }
+ getDelta = _;
+ return chart;
+ };
+
+ chart.setSearchHandler = function (_) {
+ if (!arguments.length) {
+ searchHandler = originalSearchHandler;
+ return chart;
+ }
+ searchHandler = _;
+ return chart;
+ };
+
+ chart.setDetailsHandler = function (_) {
+ if (!arguments.length) {
+ detailsHandler = originalDetailsHandler;
+ return chart;
+ }
+ detailsHandler = _;
+ return chart;
+ };
+
+ chart.setSearchMatch = function (_) {
+ if (!arguments.length) {
+ searchMatch = originalSearchMatch;
+ return chart;
+ }
+ searchMatch = _;
+ return chart;
+ };
+
+ return chart;
+ }
+
+ __webpack_exports__ = __webpack_exports__['default'];
+ /******/ return __webpack_exports__;
+ /******/
+ })();
+});
diff --git a/development/charts/table/index.html b/development/charts/table/index.html
new file mode 100644
index 000000000..ec11ecfde
--- /dev/null
+++ b/development/charts/table/index.html
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
S.No
+
Name
+
TotalTime
+
+
+
+
+
+
+
diff --git a/development/charts/table/jquery.min.js b/development/charts/table/jquery.min.js
new file mode 100644
index 000000000..8cdc80eb8
--- /dev/null
+++ b/development/charts/table/jquery.min.js
@@ -0,0 +1,18 @@
+/*!
+ * jQuery JavaScript Library v1.6.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu Jun 30 14:16:56 2011 -0400
+ */
+(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="