1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-24 02:58:09 +01:00

Merge branch 'develop' of github.com:MetaMask/metamask-extension into minimal

This commit is contained in:
Matthias Kretschmann 2023-08-08 11:15:04 +01:00
commit f732845aa5
Signed by: m
GPG Key ID: 606EEEF3C479A91F
455 changed files with 28032 additions and 18854 deletions

View File

@ -3,16 +3,16 @@ version: 2.1
executors: executors:
node-browsers: node-browsers:
docker: docker:
- image: cimg/node:16.20-browsers - image: cimg/node:18.17-browsers
node-browsers-medium-plus: node-browsers-medium-plus:
docker: docker:
- image: cimg/node:16.20-browsers - image: cimg/node:18.17-browsers
resource_class: medium+ resource_class: medium+
environment: environment:
NODE_OPTIONS: --max_old_space_size=2048 NODE_OPTIONS: --max_old_space_size=2048
node-browsers-large: node-browsers-large:
docker: docker:
- image: cimg/node:16.20-browsers - image: cimg/node:18.17-browsers
resource_class: large resource_class: large
environment: environment:
NODE_OPTIONS: --max_old_space_size=2048 NODE_OPTIONS: --max_old_space_size=2048
@ -147,6 +147,9 @@ workflows:
- test-e2e-firefox: - test-e2e-firefox:
requires: requires:
- prep-build-test - prep-build-test
- test-e2e-chrome-rpc:
requires:
- prep-build-test
- test-e2e-chrome-snaps: - test-e2e-chrome-snaps:
requires: requires:
- prep-build-test-flask - prep-build-test-flask
@ -350,27 +353,9 @@ jobs:
# required and add the new dependencies, and the cache will be persisted. # required and add the new dependencies, and the cache will be persisted.
- dependency-cache-v1- - dependency-cache-v1-
- gh/install - gh/install
- run:
name: Set IS_DRAFT environment variable
command: |
PR_NUMBER="${CIRCLE_PULL_REQUEST##*/}"
if [ -n "$PR_NUMBER" ]
then
echo "IS_DRAFT=$(gh pr view --json isDraft --jq '.isDraft' "$PR_NUMBER")" >> "$BASH_ENV"
source "$BASH_ENV"
else
echo "Not a PR; skipping"
fi
- run: - run:
name: Install dependencies name: Install dependencies
command: | command: .circleci/scripts/install-dependencies.sh
if [[ $IS_DRAFT == 'true' ]]
then
# Use GitHub registry on draft PRs, allowing the use of preview builds
METAMASK_NPM_REGISTRY=https://npm.pkg.github.com yarn --immutable
else
yarn --immutable
fi
- save_cache: - save_cache:
key: dependency-cache-v1-{{ checksum "yarn.lock" }} key: dependency-cache-v1-{{ checksum "yarn.lock" }}
paths: paths:
@ -709,6 +694,9 @@ jobs:
- run: - run:
name: lockfile-lint name: lockfile-lint
command: yarn lint:lockfile command: yarn lint:lockfile
- run:
name: check yarn resolutions
command: yarn --check-resolutions
test-lint-changelog: test-lint-changelog:
executor: node-browsers executor: node-browsers
@ -744,7 +732,7 @@ jobs:
at: . at: .
- run: - run:
name: yarn audit name: yarn audit
command: .circleci/scripts/yarn-audit.sh command: yarn audit
test-deps-depcheck: test-deps-depcheck:
executor: node-browsers executor: node-browsers
@ -821,6 +809,43 @@ jobs:
path: test-artifacts path: test-artifacts
destination: test-artifacts destination: test-artifacts
test-e2e-chrome-rpc:
executor: node-browsers
parallelism: 1
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: test:e2e:chrome:rpc
command: |
if .circleci/scripts/test-run-e2e.sh
then
yarn test:e2e:chrome:rpc --retries 2
fi
no_output_timeout: 20m
- run:
name: Merge JUnit report
command: |
if [ "$(ls -A test/test-results/e2e)" ]; then
yarn test:e2e:report
fi
when: always
- store_artifacts:
path: test-artifacts
destination: test-artifacts
- store_test_results:
path: test/test-results/e2e.xml
test-e2e-firefox-snaps: test-e2e-firefox-snaps:
executor: node-browsers executor: node-browsers
parallelism: 4 parallelism: 4

View File

@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -e
set -o pipefail
IS_NON_FORK_DRAFT='false'
if [[ -n $CIRCLE_PULL_REQUEST ]] && gh auth status
then
PR_NUMBER="${CIRCLE_PULL_REQUEST##*/}"
if [ -n "$PR_NUMBER" ]
then
IS_NON_FORK_DRAFT="$(gh pr view --json isDraft --jq '.isDraft' "$PR_NUMBER")"
fi
fi
# Build query to see whether there are any "preview-like" packages in the manifest
# A "preview-like" package is a `@metamask`-scoped package with a prerelease version that has no period.
QUERY='.dependencies + .devDependencies' # Get list of all dependencies
QUERY+=' | with_entries( select(.key | startswith("@metamask") ) )' # filter to @metamask-scoped packages
QUERY+=' | to_entries[].value' # Get version ranges
QUERY+=' | select(test("^\\d+\\.\\d+\\.\\d+-[^.]+$"))' # Get pinned versions where the prerelease part has no "."
# Use `-e` flag so that exit code indicates whether any matches were found
if jq -e "${QUERY}" < ./package.json
then
echo "Preview builds detected"
HAS_PREVIEW_BUILDS='true'
else
echo "No preview builds detected"
HAS_PREVIEW_BUILDS='false'
fi
if [[ $IS_NON_FORK_DRAFT == 'true' && $HAS_PREVIEW_BUILDS == 'true' ]]
then
# Use GitHub registry on draft PRs, allowing the use of preview builds
echo "Installing with preview builds"
METAMASK_NPM_REGISTRY=https://npm.pkg.github.com yarn --immutable
else
echo "Installing without preview builds"
yarn --immutable
fi

View File

@ -1,24 +0,0 @@
#!/usr/bin/env bash
set -e
set -u
set -x
set -o pipefail
# use `improved-yarn-audit` since that allows for exclude
# exclusions are in .iyarc now
yarn run improved-yarn-audit \
--ignore-dev-deps \
--min-severity moderate \
--fail-on-missing-exclusions
audit_status="$?"
if [[ "$audit_status" != 0 ]]
then
count="$(yarn npm audit --severity moderate --environment production --json | tail -1 | jq '.data.vulnerabilities.moderate + .data.vulnerabilities.high + .data.vulnerabilities.critical')"
printf "Audit shows %s moderate or high severity advisories _in the production dependencies_\n" "$count"
exit 1
else
printf "Audit shows _zero_ moderate or high severity advisories _in the production dependencies_\n"
fi

View File

@ -45,7 +45,6 @@ ignores:
- 'playwright' - 'playwright'
- 'wait-on' - 'wait-on'
# development tool # development tool
- 'improved-yarn-audit'
- 'nyc' - 'nyc'
# storybook # storybook
- '@storybook/cli' - '@storybook/cli'

View File

@ -246,6 +246,7 @@ module.exports = {
'shared/**/*.test.js', 'shared/**/*.test.js',
'ui/**/*.test.js', 'ui/**/*.test.js',
'ui/__mocks__/*.js', 'ui/__mocks__/*.js',
'test/e2e/helpers.test.js',
], ],
extends: ['@metamask/eslint-config-mocha'], extends: ['@metamask/eslint-config-mocha'],
rules: { rules: {
@ -271,10 +272,12 @@ module.exports = {
'app/scripts/migrations/*.test.js', 'app/scripts/migrations/*.test.js',
'app/scripts/platforms/*.test.js', 'app/scripts/platforms/*.test.js',
'development/**/*.test.js', 'development/**/*.test.js',
'development/**/*.test.ts',
'shared/**/*.test.js', 'shared/**/*.test.js',
'shared/**/*.test.ts', 'shared/**/*.test.ts',
'test/helpers/*.js', 'test/helpers/*.js',
'test/jest/*.js', 'test/jest/*.js',
'test/e2e/helpers.test.js',
'ui/**/*.test.js', 'ui/**/*.test.js',
'ui/__mocks__/*.js', 'ui/__mocks__/*.js',
'shared/lib/error-utils.test.js', 'shared/lib/error-utils.test.js',

View File

@ -20,5 +20,6 @@ When you're done with your project / bugfix / feature and ready to submit a PR,
- [ ] **Keep it simple**: Try not to include multiple features in a single PR, and don't make extraneous changes outside the scope of your contribution. All those touched files make things harder to review ;) - [ ] **Keep it simple**: Try not to include multiple features in a single PR, and don't make extraneous changes outside the scope of your contribution. All those touched files make things harder to review ;)
- [ ] **PR against `develop`**: Submit your PR against the `develop` branch. This is where we merge new features so they get some time to receive extra testing before being pushed to `master` for production. If your PR is a hot-fix that needs to be published urgently, you may submit a PR against the `master` branch, but this PR will receive tighter scrutiny before merging. - [ ] **PR against `develop`**: Submit your PR against the `develop` branch. This is where we merge new features so they get some time to receive extra testing before being pushed to `master` for production. If your PR is a hot-fix that needs to be published urgently, you may submit a PR against the `master` branch, but this PR will receive tighter scrutiny before merging.
- [ ] **Get reviewed by a core contributor**: Make sure you get a `:thumbsup`, `:+1`, or `LGTM` from a user with a `Member` badge before merging. - [ ] **Get reviewed by a core contributor**: Make sure you get a `:thumbsup`, `:+1`, or `LGTM` from a user with a `Member` badge before merging.
- [ ] **Ensure the PR is correctly labeled.**: More detail about labels definitions can be found [here](https://github.com/MetaMask/metamask-extension/blob/develop/.github/LABELING_GUIDELINES.md).
And that's it! Thanks for helping out. And that's it! Thanks for helping out.

24
.github/LABELING_GUIDELINES.md vendored Normal file
View File

@ -0,0 +1,24 @@
# PR Labeling Guidelines
To maintain a consistent and efficient development workflow, we have set specific label guidelines for all pull requests (PRs). Please ensure you adhere to the following instructions:
### Mandatory Labels:
- **Internal Developers**: Every PR raised by an internal developer must include a label prefixed with `team-` (e.g., `team-extension-ux`, `team-extension-platform`, etc.). This indicates the respective internal team responsible for the PR.
- **External Contributors**: PRs from contributors outside the organization must have the `external-contributor` label.
It's essential to ensure that PRs have the appropriate labels before they are considered for merging.
### Prohibited Labels:
Any PR that includes one of the following labels can not be merged:
- **needs-qa**: The PR requires a full manual QA prior to being added to a release.
- **QA'd but questions**: The PR has been checked by QA, but there are pending questions or clarifications needed on minor issues that were found.
- **issues-found**: The PR has been checked by QA or other reviewers, and appeared to include issues that need to be addressed.
- **need-ux-ds-review**: The PR requires a review from the User Experience or Design System teams.
- **blocked**: There are unresolved dependencies or other issues blocking the progress of this PR.
- **stale**: The PR has not had recent activity in the last 90 days. It will be closed in 7 days.
- **DO-NOT-MERGE**: The PR should not be merged under any circumstances.
To maintain code quality and project integrity, it's crucial to respect these label guidelines. Please ensure you review and update labels appropriately throughout the PR lifecycle.
Thank you for your cooperation!

View File

@ -0,0 +1,97 @@
import * as core from '@actions/core';
import { context, getOctokit } from '@actions/github';
import { GitHub } from '@actions/github/lib/utils';
main().catch((error: Error): void => {
console.error(error);
process.exit(1);
});
async function main(): Promise<void> {
// "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions.
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
core.setFailed('GITHUB_TOKEN not found');
process.exit(1);
}
// Initialise octokit, required to call Github GraphQL API
const octokit: InstanceType<typeof GitHub> = getOctokit(githubToken);
// Retrieve pull request info from context
const prRepoOwner = context.repo.owner;
const prRepoName = context.repo.repo;
const prNumber = context.payload.pull_request?.number;
if (!prNumber) {
core.setFailed('Pull request number not found');
process.exit(1);
}
// Retrieve pull request labels
const prLabels = await retrievePullRequestLabels(octokit, prRepoOwner, prRepoName, prNumber);
const preventMergeLabels = ["needs-qa", "QA'd but questions", "issues-found", "need-ux-ds-review", "blocked", "stale", "DO-NOT-MERGE"];
let hasTeamLabel = false;
// Check pull request has at least required QA label and team label
for (const label of prLabels) {
if (label.startsWith("team-") || label === "external-contributor") {
console.log(`PR contains a team label as expected: ${label}`);
hasTeamLabel = true;
}
if (preventMergeLabels.includes(label)) {
throw new Error(`PR cannot be merged because it still contains this label: ${label}`);
}
if (hasTeamLabel) {
return;
}
}
// Otherwise, throw an arror to prevent from merging
let errorMessage = '';
if (!hasTeamLabel) {
errorMessage += 'No team labels found on the PR. ';
}
errorMessage += `Please make sure the PR is appropriately labeled before merging it.\n\nSee labeling guidelines for more detail: https://github.com/MetaMask/metamask-extension/blob/develop/.github/LABELING_GUIDELINES.md`;
throw new Error(errorMessage);
}
// This function retrieves the pull request on a specific repo
async function retrievePullRequestLabels(octokit: InstanceType<typeof GitHub>, repoOwner: string, repoName: string, prNumber: number): Promise<string[]> {
const retrievePullRequestLabelsQuery = `
query RetrievePullRequestLabels($repoOwner: String!, $repoName: String!, $prNumber: Int!) {
repository(owner: $repoOwner, name: $repoName) {
pullRequest(number: $prNumber) {
labels(first: 100) {
nodes {
name
}
}
}
}
}
`;
const retrievePullRequestLabelsResult: {
repository: {
pullRequest: {
labels: {
nodes: {
name: string;
}[];
}
};
};
} = await octokit.graphql(retrievePullRequestLabelsQuery, {
repoOwner,
repoName,
prNumber,
});
const pullRequestLabels = retrievePullRequestLabelsResult?.repository?.pullRequest?.labels?.nodes?.map(labelObject => labelObject?.name);
return pullRequestLabels || [];
}

38
.github/workflows/check-pr-labels.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: "Check PR has required labels"
on:
pull_request:
branches:
- develop
types:
- opened
- reopened
- synchronize
- labeled
- unlabeled
jobs:
check-pr-labels:
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0 # This is needed to checkout all branches
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
- name: Install dependencies
run: yarn --immutable
- name: Check PR has required labels
id: check-pr-has-required-labels
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm run check-pr-has-required-labels

View File

@ -1,17 +0,0 @@
# Fails the pull request if it has the "DO-NOT-MERGE" label
name: Check "DO-NOT-MERGE" label
on:
pull_request:
types: [opened, reopened, labeled, unlabeled, synchronize]
jobs:
do-not-merge:
runs-on: ubuntu-latest
if: ${{ contains(github.event.pull_request.labels.*.name, 'DO-NOT-MERGE') }}
steps:
- name: 'Check for label "DO-NOT-MERGE"'
run: |
echo 'This check fails PRs with the "DO-NOT-MERGE" label to block merging'
exit 1

7
.iyarc
View File

@ -1,7 +0,0 @@
# improved-yarn-audit advisory exclusions
GHSA-257v-vj4p-3w2h
# Prototype pollution
# Not easily patched
# Minimal risk to us because we're using lockdown which also prevents this case of prototype pollution
GHSA-h755-8qp9-cq85

View File

@ -13,3 +13,4 @@ INFURA_PROJECT_ID=00000000000
; Set this to test changes to the phishing warning page. ; Set this to test changes to the phishing warning page.
;PHISHING_WARNING_PAGE_URL= ;PHISHING_WARNING_PAGE_URL=
BLOCKAID_FILE_CDN= BLOCKAID_FILE_CDN=
BLOCKAID_PUBLIC_KEY=

View File

@ -9,6 +9,8 @@ module.exports = {
'./app/scripts/controllers/permissions/**/*.test.js', './app/scripts/controllers/permissions/**/*.test.js',
'./app/scripts/controllers/mmi-controller.test.js', './app/scripts/controllers/mmi-controller.test.js',
'./app/scripts/constants/error-utils.test.js', './app/scripts/constants/error-utils.test.js',
'./development/fitness-functions/**/*.test.ts',
'./test/e2e/helpers.test.js',
], ],
recursive: true, recursive: true,
require: ['test/env.js', 'test/setup.js'], require: ['test/env.js', 'test/setup.js'],

2
.nvmrc
View File

@ -1 +1 @@
v16 v18

View File

@ -1,5 +1,7 @@
import { draftTransactionInitialState } from '../ui/ducks/send'; import { draftTransactionInitialState } from '../ui/ducks/send';
import { KeyringType } from '../shared/constants/keyring'; import { KeyringType } from '../shared/constants/keyring';
import { NetworkType } from '@metamask/controller-utils';
import { NetworkStatus } from '@metamask/network-controller';
const state = { const state = {
invalidCustomNetwork: { invalidCustomNetwork: {
@ -164,6 +166,15 @@ const state = {
1559: true, 1559: true,
}, },
}, },
selectedNetworkClientId: NetworkType.mainnet,
networksMetadata: {
[NetworkType.mainnet]: {
EIPS: {
1559: true,
},
status: NetworkStatus.Available,
},
},
gasFeeEstimates: '0x5208', gasFeeEstimates: '0x5208',
swapsState: { swapsState: {
quotes: {}, quotes: {},
@ -1599,7 +1610,7 @@ const state = {
}, },
}; };
export const networkList = [ export const networkList = [
{ {
blockExplorerUrl: 'https://etherscan.io', blockExplorerUrl: 'https://etherscan.io',
chainId: '0x1', chainId: '0x1',
@ -1673,6 +1684,6 @@ export const networkList = [
rpcUrl: 'https://polygon-rpc.com', rpcUrl: 'https://polygon-rpc.com',
ticker: 'MATIC', ticker: 'MATIC',
}, },
] ];
export default state; export default state;

View File

@ -1,69 +0,0 @@
# Improved yarn audit is patched to work with yarn version 2+. The primary need
# is to retool the script to first use yarn's new audit command and parameters
# as well as to change the process for how it reads the result due to an update
# in returned shape of audit command's data.
diff --git a/bin/improved-yarn-audit b/bin/improved-yarn-audit
index 52df548151aa28289565e3335b2cd7a92fa38325..7e058df6a4a159596df72c9475a36b747580cd98 100755
--- a/bin/improved-yarn-audit
+++ b/bin/improved-yarn-audit
@@ -15,6 +15,7 @@ const { tmpdir } = require("os")
const path = require("path")
const { env, exit, platform } = require("process")
const { createInterface } = require("readline")
+const { Stream } = require("stream")
const GITHUB_ADVISORY_CODE = "GHSA"
@@ -250,7 +251,15 @@ async function iterateOverAuditResults(action) {
const auditResultsFileStream = getAuditResultsFileStream("r")
const iterator = createInterface(auditResultsFileStream)
- iterator.on("line", action)
+ iterator.on("line", async (result) => {
+ const parsed = parseAuditJson(result);
+ const advisories = Stream.Readable.from(
+ Object.values(parsed.advisories).map(advisory => JSON.stringify(advisory))
+ );
+ for await (const data of advisories) {
+ action(data);
+ }
+ });
await new Promise((resolve) => iterator.on("close", resolve))
@@ -305,10 +314,10 @@ async function streamYarnAuditOutput(auditParams, auditResultsFileStream) {
}
async function invokeYarnAudit() {
- const auditParams = ["audit", "--json", `--level=${minSeverityName}`]
+ const auditParams = ["npm", "audit", "--recursive", "--json", `--severity=${minSeverityName}`]
if (ignoreDevDependencies) {
- auditParams.push("--groups=dependencies")
+ auditParams.push("--environment=production")
}
cleanupAuditResultsFile()
@@ -420,17 +429,17 @@ async function runAuditReport() {
let devDependencyAdvisories = []
let devDependencyAdvisoryIds = []
- await iterateOverAuditResults((resultJson) => {
- const potentialResult = parseAuditJson(resultJson)
+ await iterateOverAuditResults((resultJsonString) => {
+ const potentialResult = parseAuditJson(resultJsonString);
if (
- typeof potentialResult.type !== "string" ||
- potentialResult.type !== "auditAdvisory"
+ typeof potentialResult.github_advisory_id !== "string"
) {
return
}
- const result = potentialResult.data.advisory
+
+ const result = potentialResult;
allAdvisories.push(result)

View File

@ -1,48 +0,0 @@
# Lockfile lint's current version does not work with the updated structure of the yarn v2 lockfile
# This patch updates it so that it can parse and read the lockfile entries.
diff --git a/src/ParseLockfile.js b/src/ParseLockfile.js
index 0f0c951027ec83c61769bb6a48943420dff133b8..bad2d251cf376bf3ef4b444a0d49f03a602d7a6e 100644
--- a/src/ParseLockfile.js
+++ b/src/ParseLockfile.js
@@ -21,13 +21,13 @@ const {
* @return boolean
*/
function checkSampleContent (lockfile) {
- const [sampleKey, sampleValue] = Object.entries(lockfile)[0]
+ const [sampleKey, sampleValue] = Object.entries(lockfile)[1]
return (
sampleKey.match(/.*@.*/) &&
(sampleValue &&
typeof sampleValue === 'object' &&
sampleValue.hasOwnProperty('version') &&
- sampleValue.hasOwnProperty('resolved'))
+ sampleValue.hasOwnProperty('resolution'))
)
}
/**
@@ -41,7 +41,24 @@ function yarnParseAndVerify (lockfileBuffer) {
if (!hasSensibleContent) {
throw Error('Lockfile does not seem to contain a valid dependency list')
}
- return {type: 'success', object: lockfile}
+ const normalized = Object.fromEntries(Object.entries(lockfile).map(([packageName, packageDetails]) => {
+ const resolution = packageDetails.resolution;
+ if (!resolution) {
+ return [packageName, packageDetails];
+ }
+ const splitByAt = resolution.split('@');
+ let resolvedPackageName;
+ let host;
+ if (splitByAt.length > 2 && resolution[0] === '@') {
+ resolvedPackageName = `@${splitByAt[1]}`;
+ host = splitByAt[2];
+ } else {
+ [resolvedPackageName, host] = splitByAt;
+ }
+
+ return [packageName, { ...packageDetails, resolved: host}]
+ }))
+ return {type: 'success', object: normalized}
}
class ParseLockfile {
/**

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

881
.yarn/releases/yarn-4.0.0-rc.48.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,7 @@
compressionLevel: mixed
enableGlobalCache: false
enableScripts: false enableScripts: false
checksumBehavior: "ignore" checksumBehavior: "ignore"
enableTelemetry: false enableTelemetry: false
@ -8,21 +12,101 @@ logFilters:
nodeLinker: node-modules nodeLinker: node-modules
npmAuditIgnoreAdvisories:
### Advisories:
# Issue: yargs-parser Vulnerable to Prototype Pollution
# URL - https://github.com/advisories/GHSA-p9pc-299p-vxgp
# The affected version (<5.0.0) is only included via @ensdomains/ens via
# 'solc' which is not used in the imports we use from this package.
- 1088783
# Issue: protobufjs Prototype Pollution vulnerability
# URL - https://github.com/advisories/GHSA-h755-8qp9-cq85
# Not easily patched. Minimally effects the extension due to usage of
# LavaMoat lockdown.
- 1092429
# Issue: Regular Expression Denial of Service (ReDOS)
# URL: https://github.com/advisories/GHSA-257v-vj4p-3w2h
# color-string is listed as a dependency of 'color' which is brought in by
# @metamask/jazzicon v2.0.0 but there is work done on that repository to
# remove the color dependency. We should upgrade
- 1089718
# Issue: semver vulnerable to Regular Expression Denial of Service
# URL: https://github.com/advisories/GHSA-c2qf-rxjj-qqgw
# semver is used in the solidity compiler portion of @truffle/codec that does
# not appear to be used.
- 1092461
### Package Deprecations:
# React-tippy brings in popper.js and react-tippy has not been updated in
# three years.
- 'popper.js (deprecation)'
# React-router is out of date and brings in the following deprecated package
- 'mini-create-react-context (deprecation)'
# The affected version, which is less than 7.0.0, is brought in by
# ethereumjs-wallet version 0.6.5 used in the extension but only in a single
# file app/scripts/account-import-strategies/index.js, which may be easy to
# upgrade.
- 'uuid (deprecation)'
# @npmcli/move-file is brought in via CopyWebpackPlugin used in the storybook
# main.js file, which can be upgraded to remove this dependency in favor of
# @npmcli/fs
- '@npmcli/move-file (deprecation)'
# Upgrading babel will result in the following deprecated packages being
# updated:
- 'core-js (deprecation)'
# Material UI dependencies are planned for removal
- '@material-ui/core (deprecation)'
- '@material-ui/styles (deprecation)'
- '@material-ui/system (deprecation)'
# @ensdomains/ens should be explored for upgrade. The following packages are
# deprecated and would be resolved by upgrading to newer versions of
# ensdomains packages:
- '@ensdomains/ens (deprecation)'
- '@ensdomains/resolver (deprecation)'
- 'testrpc (deprecation)'
# Dependencies brought in by @truffle/decoder that are deprecated:
- 'cids (deprecation)' # via @ensdomains/content-hash
- 'multibase (deprecation)' # via cids
- 'multicodec (deprecation)' # via cids
# MetaMask owned repositories brought in by other MetaMask dependencies that
# can be resolved by updating the versions throughout the dependency tree
- 'eth-sig-util (deprecation)' # via @metamask/eth-ledger-bridge-keyring
- '@metamask/controller-utils (deprecation)' # via @metamask/phishin-controller
- 'safe-event-emitter (deprecation)' # via eth-block-tracker and others
# @metamask-institutional relies upon crypto which is deprecated
- 'crypto (deprecation)'
# @metamask/providers uses webextension-polyfill-ts which has been moved to
# @types/webextension-polyfill
- 'webextension-polyfill-ts (deprecation)'
npmRegistries: npmRegistries:
"https://npm.pkg.github.com": 'https://npm.pkg.github.com':
npmAlwaysAuth: true npmAlwaysAuth: true
npmAuthToken: "${GITHUB_PACKAGE_READ_TOKEN-}" npmAuthToken: '${GITHUB_PACKAGE_READ_TOKEN-}'
npmScopes: npmScopes:
metamask: metamask:
npmRegistryServer: "${METAMASK_NPM_REGISTRY:-https://registry.yarnpkg.com}" npmRegistryServer: '${METAMASK_NPM_REGISTRY:-https://registry.yarnpkg.com}'
plugins: plugins:
- path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs - path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs
spec: "https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js" spec: 'https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js'
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
spec: "@yarnpkg/plugin-version"
- path: .yarn/plugins/@yarnpkg/plugin-engines.cjs - path: .yarn/plugins/@yarnpkg/plugin-engines.cjs
spec: "https://raw.githubusercontent.com/devoto13/yarn-plugin-engines/main/bundles/%40yarnpkg/plugin-engines.js" spec: 'https://raw.githubusercontent.com/devoto13/yarn-plugin-engines/main/bundles/%40yarnpkg/plugin-engines.js'
yarnPath: .yarn/releases/yarn-3.2.4.cjs yarnPath: .yarn/releases/yarn-4.0.0-rc.48.cjs

View File

@ -6,6 +6,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [10.34.2]
### Added
- Add Address Details and View on Explorer to Global Menu ([#20013](https://github.com/MetaMask/metamask-extension/pull/20013))
## Changed
- Increase copy clipboard time ([#20008](https://github.com/MetaMask/metamask-extension/pull/20008))
- Show checksum addresses on account list menu ([#20217](https://github.com/MetaMask/metamask-extension/pull/20217/commits/41bab4a6e14682388f4021f2f51bc74bddcbe80e))
- Scroll to selected account when opening account list menu ([#20166](https://github.com/MetaMask/metamask-extension/pull/20166))
- Remove fallback phishing warning configuration ([#20327](https://github.com/MetaMask/metamask-extension/pull/20327))
- The phishing warning feature will no longer function if the wallet is unable to receive configuration updates. Previously a fallback config was used in this case, but we found that it was too outdated to be helpful and it caused many problems for users.
- Improved UI for downloading state logs on Chromium-based browsers ([#19872](https://github.com/MetaMask/metamask-extension/pull/19872))
- We now use a file picker to let you select the download location, rather than saving the state logs in your downloads folder.
### Fixed
- Fixed bug that could cause loss of network or token data for users upgrading from old versions ([#20276](https://github.com/MetaMask/metamask-extension/pull/20276))
- Fix crash on open of MetaMask for some users that have old network data in state ([#20345](https://github.com/MetaMask/metamask-extension/pull/20345))
## [10.34.1] ## [10.34.1]
### Fixed ### Fixed
- Fix bug that could cause a failure in the persistence of network related data ([#20080](https://github.com/MetaMask/metamask-extension/pull/20080)) - Fix bug that could cause a failure in the persistence of network related data ([#20080](https://github.com/MetaMask/metamask-extension/pull/20080))
@ -3858,7 +3875,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Uncategorized ### Uncategorized
- Added the ability to restore accounts from seed words. - Added the ability to restore accounts from seed words.
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.34.1...HEAD [Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.34.2...HEAD
[10.34.2]: https://github.com/MetaMask/metamask-extension/compare/v10.34.1...v10.34.2
[10.34.1]: https://github.com/MetaMask/metamask-extension/compare/v10.34.0...v10.34.1 [10.34.1]: https://github.com/MetaMask/metamask-extension/compare/v10.34.0...v10.34.1
[10.34.0]: https://github.com/MetaMask/metamask-extension/compare/v10.33.1...v10.34.0 [10.34.0]: https://github.com/MetaMask/metamask-extension/compare/v10.33.1...v10.34.0
[10.33.1]: https://github.com/MetaMask/metamask-extension/compare/v10.33.0...v10.33.1 [10.33.1]: https://github.com/MetaMask/metamask-extension/compare/v10.33.0...v10.33.1

View File

@ -49,11 +49,11 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D
## Building locally ## Building locally
- Install [Node.js](https://nodejs.org) version 16 - Install [Node.js](https://nodejs.org) version 18
- If you are using [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) (recommended) running `nvm use` will automatically choose the right node version for you. - If you are using [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) (recommended) running `nvm use` will automatically choose the right node version for you.
- Install [Yarn v3](https://yarnpkg.com/getting-started/install) - Install [Yarn v3](https://yarnpkg.com/getting-started/install)
- ONLY follow the steps in the "Install Corepack" and "Updating the global Yarn version" sections - ONLY follow the steps in the "Install Corepack" and "Updating the global Yarn version" sections
- DO NOT take any of the steps in the "Initializing your project", "Updating to the latest versions" or "Installing the latest build fresh from master" sections. These steps could result in your repo being reset or installing the wrong yarn version, which can break your build. - DO NOT take any of the steps in the "Initializing your project", "Updating to the latest versions" or "Installing the latest build fresh from master" sections. These steps could result in your repo being reset or installing the wrong yarn version, which can break your build.
- Duplicate `.metamaskrc.dist` within the root and rename it to `.metamaskrc` - Duplicate `.metamaskrc.dist` within the root and rename it to `.metamaskrc`
- Replace the `INFURA_PROJECT_ID` value with your own personal [Infura Project ID](https://infura.io/docs). - Replace the `INFURA_PROJECT_ID` value with your own personal [Infura Project ID](https://infura.io/docs).
- If debugging MetaMetrics, you'll need to add a value for `SEGMENT_WRITE_KEY` [Segment write key](https://segment.com/docs/connections/find-writekey/), see [Developing on MetaMask - Segment](./development/README.md#segment). - If debugging MetaMetrics, you'll need to add a value for `SEGMENT_WRITE_KEY` [Segment write key](https://segment.com/docs/connections/find-writekey/), see [Developing on MetaMask - Segment](./development/README.md#segment).

View File

@ -205,9 +205,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "አውታረ መረብ ይሰረዝ?" "message": "አውታረ መረብ ይሰረዝ?"
}, },
"deleteNetworkDescription": {
"message": "ይህን አውታረ መረብ ለመሰረዝ እንደሚፈልጉ እርግጠኛ ነዎት?"
},
"details": { "details": {
"message": "ዝርዝሮች" "message": "ዝርዝሮች"
}, },

View File

@ -218,9 +218,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "هل تريد حذف الشبكة؟" "message": "هل تريد حذف الشبكة؟"
}, },
"deleteNetworkDescription": {
"message": "هل أنت متأكد أنك تريد حذف هذه الشبكة؟"
},
"details": { "details": {
"message": "التفاصيل" "message": "التفاصيل"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Да се изтрие ли мрежата?" "message": "Да се изтрие ли мрежата?"
}, },
"deleteNetworkDescription": {
"message": "Наистина ли искате да изтриете тази мрежа?"
},
"details": { "details": {
"message": "Подробности" "message": "Подробности"
}, },

View File

@ -211,9 +211,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "নেটওয়ার্ক মুছবেন?" "message": "নেটওয়ার্ক মুছবেন?"
}, },
"deleteNetworkDescription": {
"message": "আপনি কি এই নেটওয়ার্কটি মোছার বিষয়ে নিশ্চিত?"
},
"details": { "details": {
"message": "বিশদ বিবরণ" "message": "বিশদ বিবরণ"
}, },

View File

@ -211,9 +211,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Esborrar Xarxa?" "message": "Esborrar Xarxa?"
}, },
"deleteNetworkDescription": {
"message": "Estàs segur que vols eliminar aquesta xarxa?"
},
"details": { "details": {
"message": "Detalls" "message": "Detalls"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Slet Netværk?" "message": "Slet Netværk?"
}, },
"deleteNetworkDescription": {
"message": "Er du sikker på, at du vil slette dette netværk?"
},
"details": { "details": {
"message": "Detaljer" "message": "Detaljer"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "Kontakt hinzufügen" "message": "Kontakt hinzufügen"
}, },
"addCustomIPFSGateway": {
"message": "Benutzerdefiniertes IPFS-Gateway hinzufügen"
},
"addCustomIPFSGatewayDescription": {
"message": "Das IPFS-Gateway ermöglicht es, auf von Dritten gehostete Daten zuzugreifen und diese einzusehen. Sie können ein benutzerdefiniertes IPFS-Gateway hinzufügen oder weiterhin das Standard-Gateway verwenden."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Benutzerdefiniertes Netzwerk hinzufügen" "message": "Benutzerdefiniertes Netzwerk hinzufügen"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Netzwerk löschen?" "message": "Netzwerk löschen?"
}, },
"deleteNetworkDescription": {
"message": "Sind Sie sicher, dass Sie dieses Netzwerk löschen möchten?"
},
"deposit": { "deposit": {
"message": "Einzahlung" "message": "Einzahlung"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " In den Einstellungen aktivieren." "message": " In den Einstellungen aktivieren."
}, },
"enableOpenSeaAPI": {
"message": "OpenSea API aktivieren"
},
"enableOpenSeaAPIDescription": {
"message": "Verwenden Sie die OpenSea's API, um NFT-Daten abzurufen. Die NFT-Auto-Erkennung basiert auf der OpenSea's API und wird nicht verfügbar sein, wenn diese deaktiviert ist."
},
"enableToken": { "enableToken": {
"message": "$1 aktivieren", "message": "$1 aktivieren",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Hoppla! Da hat etwas nicht geklappt." "message": "Hoppla! Da hat etwas nicht geklappt."
}, },
"source": {
"message": "Quelle"
},
"speedUp": { "speedUp": {
"message": "Beschleunigen" "message": "Beschleunigen"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Kontoguthaben-Anfragen sammeln" "message": "Kontoguthaben-Anfragen sammeln"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "Wir sammeln Konten und fragen bei Infura an, um Ihr Guthaben anzuzeigen. Wenn Sie dies deaktivieren, werden nur aktive Konten abgefragt. Einige Dapps funktionieren nicht, wenn Sie Ihr Wallet nicht verbinden."
},
"useNftDetection": { "useNftDetection": {
"message": "NFTs automatisch erkennen" "message": "NFTs automatisch erkennen"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "Προσθήκη επαφής" "message": "Προσθήκη επαφής"
}, },
"addCustomIPFSGateway": {
"message": "Προσθήκη προσαρμοσμένης πύλης IPFS"
},
"addCustomIPFSGatewayDescription": {
"message": "Η πύλη IPFS επιτρέπει την πρόσβαση και την προβολή δεδομένων που φιλοξενούνται από τρίτους. Μπορείτε να προσθέσετε μια προσαρμοσμένη πύλη IPFS ή να συνεχίσετε να χρησιμοποιείτε την προεπιλεγμένη."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Προσθήκη προσαρμοσμένου δικτύου" "message": "Προσθήκη προσαρμοσμένου δικτύου"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Διαγραφή Δικτύου;" "message": "Διαγραφή Δικτύου;"
}, },
"deleteNetworkDescription": {
"message": "Θέλετε σίγουρα να διαγράψετε αυτό το δίκτυο;"
},
"deposit": { "deposit": {
"message": "Κατάθεση" "message": "Κατάθεση"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Ενεργοποίηση του από τις Ρυθμίσεις." "message": " Ενεργοποίηση του από τις Ρυθμίσεις."
}, },
"enableOpenSeaAPI": {
"message": "Ενεργοποίηση OpenSea API"
},
"enableOpenSeaAPIDescription": {
"message": "Χρησιμοποιήστε το API OpenSea για λήψη δεδομένων NFT. Η αυτόματη ανίχνευση NFT βασίζεται στο API του OpenSea, και δεν θα είναι διαθέσιμη όταν αυτό είναι απενεργοποιημένο."
},
"enableToken": { "enableToken": {
"message": "ενεργοποίηση $1", "message": "ενεργοποίηση $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3258,9 +3243,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Ουπς! Κάτι πήγε στραβά." "message": "Ουπς! Κάτι πήγε στραβά."
}, },
"source": {
"message": "Πηγή"
},
"speedUp": { "speedUp": {
"message": "Επιτάχυνση" "message": "Επιτάχυνση"
}, },
@ -4157,9 +4139,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Μαζικά αιτήματα υπολοίπου λογαριασμού" "message": "Μαζικά αιτήματα υπολοίπου λογαριασμού"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "Συγκεντρώνουμε τους λογαριασμούς και ζητάμε από την Infura να εμφανίσει το υπόλοιπό σας. Αν το απενεργοποιήσετε αυτό, θα ζητηθούν μόνο οι ενεργοί λογαριασμοί. Ορισμένες αποκεντρωμένες εφαρμογές δεν θα λειτουργούν αν δεν συνδέσετε το πορτοφόλι σας."
},
"useNftDetection": { "useNftDetection": {
"message": "Αυτόματη Ανίχνευση NFT" "message": "Αυτόματη Ανίχνευση NFT"
}, },

View File

@ -189,12 +189,6 @@
"addContact": { "addContact": {
"message": "Add contact" "message": "Add contact"
}, },
"addCustomIPFSGateway": {
"message": "Add custom IPFS gateway"
},
"addCustomIPFSGatewayDescription": {
"message": "The IPFS gateway makes it possible to access and view data hosted by third parties. You can add a custom IPFS gateway or continue using the default."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Add custom network" "message": "Add custom network"
}, },
@ -249,6 +243,9 @@
"addHardwareWallet": { "addHardwareWallet": {
"message": "Add hardware wallet" "message": "Add hardware wallet"
}, },
"addIPFSGateway": {
"message": "Add your preferred IPFS gateway"
},
"addMemo": { "addMemo": {
"message": "Add memo" "message": "Add memo"
}, },
@ -614,6 +611,10 @@
"message": "Buy $1", "message": "Buy $1",
"description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase"
}, },
"buyMoreAsset": {
"message": "Buy more $1",
"description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase"
},
"buyNow": { "buyNow": {
"message": "Buy Now" "message": "Buy Now"
}, },
@ -1119,8 +1120,12 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Delete network?" "message": "Delete network?"
}, },
"deleteNetworkDescription": { "deleteNetworkIntro": {
"message": "Are you sure you want to delete this network?" "message": "If you delete this network, you will need to add it again to view your assets in this network"
},
"deleteNetworkTitle": {
"message": "Delete $1 network?",
"description": "$1 represents the name of the network"
}, },
"deposit": { "deposit": {
"message": "Deposit" "message": "Deposit"
@ -1268,6 +1273,12 @@
"dismissReminderField": { "dismissReminderField": {
"message": "Dismiss Secret Recovery Phrase backup reminder" "message": "Dismiss Secret Recovery Phrase backup reminder"
}, },
"displayNftMedia": {
"message": "Display NFT media"
},
"displayNftMediaDescription": {
"message": "Displaying NFT media and data exposes your IP address to OpenSea or other third parties. This can allow attackers to associate your IP address with your Ethereum address. NFT autodetection relies on this setting, and won't be available when this is turned off."
},
"domain": { "domain": {
"message": "Domain" "message": "Domain"
}, },
@ -1390,15 +1401,12 @@
"enableAutoDetect": { "enableAutoDetect": {
"message": " Enable autodetect" "message": " Enable autodetect"
}, },
"enableForAllNetworks": {
"message": "Enable for all networks"
},
"enableFromSettings": { "enableFromSettings": {
"message": " Enable it from Settings." "message": " Enable it from Settings."
}, },
"enableOpenSeaAPI": {
"message": "Enable OpenSea API"
},
"enableOpenSeaAPIDescription": {
"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."
},
"enableSmartSwaps": { "enableSmartSwaps": {
"message": "Enable smart swaps" "message": "Enable smart swaps"
}, },
@ -1426,6 +1434,24 @@
"enhancedTokenDetectionAlertMessage": { "enhancedTokenDetectionAlertMessage": {
"message": "Enhanced token detection is currently available on $1. $2" "message": "Enhanced token detection is currently available on $1. $2"
}, },
"ensDomainsSettingDescriptionIntro": {
"message": "MetaMask lets you see ENS domains like \"https://metamask.eth\" right in your browser's address bar. Here's how it works:"
},
"ensDomainsSettingDescriptionOutro": {
"message": "Regular browsers don't usually handle ENS or IPFS addresses, but MetaMask helps with that. Using this feature might share your IP address with IPFS third-party services."
},
"ensDomainsSettingDescriptionPoint1": {
"message": "MetaMask checks with Ethereum's ENS contract to find the code connected to the ENS name."
},
"ensDomainsSettingDescriptionPoint2": {
"message": "If the code is linked to IPFS, it gets the content from the IPFS network."
},
"ensDomainsSettingDescriptionPoint3": {
"message": "Then, you can see the content, usually a website or something similar."
},
"ensDomainsSettingTitle": {
"message": "Show ENS domains in address bar"
},
"ensIllegalCharacter": { "ensIllegalCharacter": {
"message": "Illegal character for ENS." "message": "Illegal character for ENS."
}, },
@ -2036,6 +2062,22 @@
"invalidSeedPhraseCaseSensitive": { "invalidSeedPhraseCaseSensitive": {
"message": "Invalid input! Secret Recovery Phrase is case sensitive." "message": "Invalid input! Secret Recovery Phrase is case sensitive."
}, },
"ipfsGateway": {
"message": "IPFS gateway"
},
"ipfsGatewayDescription": {
"message": "MetaMask uses third-party services to show images of your NFTs stored on IPFS, display information related to ENS addresses entered in your browser's address bar, and fetch icons for different tokens. Your IP address may be exposed to these services when youre using them."
},
"ipfsToggleModalDescriptionOne": {
"message": "We use third-party services to show images of your NFTs stored on IPFS, display information related to ENS addresses entered in your browser's address bar, and fetch icons for different tokens. Your IP address may be exposed to these services when youre using them."
},
"ipfsToggleModalDescriptionTwo": {
"message": "Selecting Confirm turns on IPFS resolution. You can turn it off in $1 at any time.",
"description": "$1 is the method to turn off ipfs"
},
"ipfsToggleModalSettings": {
"message": "Settings > Security and privacy"
},
"jazzAndBlockies": { "jazzAndBlockies": {
"message": "Jazzicons and Blockies are two different styles of unique icons that help you identify an account at a glance." "message": "Jazzicons and Blockies are two different styles of unique icons that help you identify an account at a glance."
}, },
@ -2301,7 +2343,7 @@
"message": "more" "message": "more"
}, },
"moreComingSoon": { "moreComingSoon": {
"message": "More coming soon..." "message": "More providers coming soon"
}, },
"multipleSnapConnectionWarning": { "multipleSnapConnectionWarning": {
"message": "$1 wants to connect with $2 snaps. Only proceed if you trust this website.", "message": "$1 wants to connect with $2 snaps. Only proceed if you trust this website.",
@ -2520,6 +2562,9 @@
"noNFTs": { "noNFTs": {
"message": "No NFTs yet" "message": "No NFTs yet"
}, },
"noNetworksFound": {
"message": "No networks found for the given search query"
},
"noSnaps": { "noSnaps": {
"message": "You don't have any snaps installed." "message": "You don't have any snaps installed."
}, },
@ -2716,6 +2761,30 @@
"notifications21Title": { "notifications21Title": {
"message": "Introducing new and refreshed Swaps!" "message": "Introducing new and refreshed Swaps!"
}, },
"notifications22ActionText": {
"message": "Got it"
},
"notifications22Description": {
"message": "💡 Just click the global menu or account menu to find them!"
},
"notifications22Title": {
"message": "Looking for your account details or the block explorer URL?"
},
"notifications23ActionText": {
"message": "Enable security alerts"
},
"notifications23DescriptionOne": {
"message": "Steer clear of known scams while still preserving your privacy with security alerts powered by Blockaid."
},
"notifications23DescriptionThree": {
"message": "If you enabled security alerts from OpenSea, we've moved you over to this feature."
},
"notifications23DescriptionTwo": {
"message": "Always do your own due diligence before approving requests."
},
"notifications23Title": {
"message": "Stay safe with security alerts"
},
"notifications3ActionText": { "notifications3ActionText": {
"message": "Read more", "message": "Read more",
"description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website."
@ -3124,6 +3193,14 @@
"message": "Allow the snap to derive arbitrary keys unique to this snap, without exposing them. These keys are separate from your MetaMask account(s) and not related to your private keys or Secret Recovery Phrase. Other snaps cannot access this information.", "message": "Allow the snap to derive arbitrary keys unique to this snap, without exposing them. These keys are separate from your MetaMask account(s) and not related to your private keys or Secret Recovery Phrase. Other snaps cannot access this information.",
"description": "An extended description for the `snap_getEntropy` permission" "description": "An extended description for the `snap_getEntropy` permission"
}, },
"permission_lifecycleHooks": {
"message": "Use lifecycle hooks.",
"description": "The description for the `endowment:lifecycle-hooks` permission"
},
"permission_lifecycleHooksDescription": {
"message": "Allow the snap to use lifecycle hooks to run code at specific times during its lifecycle.",
"description": "An extended description for the `endowment:lifecycle-hooks` permission"
},
"permission_longRunning": { "permission_longRunning": {
"message": "Run indefinitely.", "message": "Run indefinitely.",
"description": "The description for the `endowment:long-running` permission" "description": "The description for the `endowment:long-running` permission"
@ -3595,7 +3672,7 @@
"message": "Security alerts" "message": "Security alerts"
}, },
"securityAlertsDescription1": { "securityAlertsDescription1": {
"message": "Enable this to have your transactions and signature requests reviewed locally (no data shared with third parties) and warnings displayed when malicious activity is detected." "message": "This feature alerts you to malicious activity by locally reviewing your transactions and signature requests. Your data isn't shared with the third parties providing this service. Always do your own due diligence before approving any requests. There's no guarantee that this feature will detect all malicious activity."
}, },
"securityAlertsDescription2": { "securityAlertsDescription2": {
"message": "Always be sure to do your own due diligence before approving any requests. There's no guarantee all mailcious activity will be detected by this feature." "message": "Always be sure to do your own due diligence before approving any requests. There's no guarantee all mailcious activity will be detected by this feature."
@ -3780,9 +3857,15 @@
"message": "This relies on $1 which will have access to your Ethereum address and your IP address. $2", "message": "This relies on $1 which will have access to your Ethereum address and your IP address. $2",
"description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs"
}, },
"showIncomingTransactionsInformation": {
"message": "This relies on each network which will have access to your Ethereum address and your IP address."
},
"showMore": { "showMore": {
"message": "Show more" "message": "Show more"
}, },
"showNft": {
"message": "Show NFT"
},
"showPermissions": { "showPermissions": {
"message": "Show permissions" "message": "Show permissions"
}, },
@ -3831,6 +3914,9 @@
"skipAccountSecurityDetails": { "skipAccountSecurityDetails": {
"message": "I understand that until I back up my Secret Recovery Phrase, I may lose my accounts and all of their assets." "message": "I understand that until I back up my Secret Recovery Phrase, I may lose my accounts and all of their assets."
}, },
"smartContracts": {
"message": "Smart contracts"
},
"smartSwap": { "smartSwap": {
"message": "Smart swap" "message": "Smart swap"
}, },
@ -4021,9 +4107,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Oops! Something went wrong." "message": "Oops! Something went wrong."
}, },
"source": {
"message": "Source"
},
"speedUp": { "speedUp": {
"message": "Speed up" "message": "Speed up"
}, },
@ -5124,11 +5207,17 @@
"urlExistsErrorMsg": { "urlExistsErrorMsg": {
"message": "This URL is currently used by the $1 network." "message": "This URL is currently used by the $1 network."
}, },
"use4ByteResolution": {
"message": "Decode smart contracts"
},
"use4ByteResolutionDescription": {
"message": "To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contact that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared."
},
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Batch account balance requests" "message": "Batch account balance requests"
}, },
"useMultiAccountBalanceCheckerDescription": { "useMultiAccountBalanceCheckerSettingDescription": {
"message": "We batch accounts and query Infura to responsively show your balances. If you turn this off, only active accounts will be queried. Some dapps won't work unless you connect your wallet." "message": "Get faster balance updates by batching account balance requests. This lets us fetch your account balances together, so you get quicker updates for an improved experience. When this feature is off, third parties may be less likely to associate your accounts with each other."
}, },
"useNftDetection": { "useNftDetection": {
"message": "Autodetect NFTs" "message": "Autodetect NFTs"

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "Agregar contacto" "message": "Agregar contacto"
}, },
"addCustomIPFSGateway": {
"message": "Agregar puerta de enlace IPFS personalizada"
},
"addCustomIPFSGatewayDescription": {
"message": "La puerta de enlace de IPFS permite acceder y visualizar datos alojados por terceros. Puede agregar una puerta de enlace de IPFS personalizada o continuar usando la predeterminada."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Agregar red personalizada" "message": "Agregar red personalizada"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "¿Eliminar red?" "message": "¿Eliminar red?"
}, },
"deleteNetworkDescription": {
"message": "¿Está seguro de que quiere eliminar esta red?"
},
"deposit": { "deposit": {
"message": "Depositar" "message": "Depositar"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Actívela en Configuración." "message": " Actívela en Configuración."
}, },
"enableOpenSeaAPI": {
"message": "Habilite el API de OpenSea"
},
"enableOpenSeaAPIDescription": {
"message": "Utilice la API de OpenSea para obtener los datos de NFT. La autodetección de NFT depende de la API de OpenSea y no estará disponible si la API está desactivada."
},
"enableToken": { "enableToken": {
"message": "activar $1", "message": "activar $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Lo lamentamos, se produjo un error." "message": "Lo lamentamos, se produjo un error."
}, },
"source": {
"message": "Fuente"
},
"speedUp": { "speedUp": {
"message": "Acelerar" "message": "Acelerar"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Solicitudes de saldo de cuenta por lotes" "message": "Solicitudes de saldo de cuenta por lotes"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "Procesamos cuentas por lotes y consultamos a Infura para mostrar sus saldos de manera receptiva. Si desactiva esta opción, solo se consultarán las cuentas activas. Algunas dapps no funcionarán a menos que conecte su biletera."
},
"useNftDetection": { "useNftDetection": {
"message": "Detección automática de NFT" "message": "Detección automática de NFT"
}, },

View File

@ -584,9 +584,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "¿Eliminar red?" "message": "¿Eliminar red?"
}, },
"deleteNetworkDescription": {
"message": "¿Está seguro de que quiere eliminar esta red?"
},
"description": { "description": {
"message": "Descripción" "message": "Descripción"
}, },
@ -740,12 +737,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Actívela en Configuración." "message": " Actívela en Configuración."
}, },
"enableOpenSeaAPI": {
"message": "Activar API de OpenSea"
},
"enableOpenSeaAPIDescription": {
"message": "Utilice la API de OpenSea para obtener los datos de NFT. La autodetección de NFT depende de la API de OpenSea y no estará disponible si la API está desactivada."
},
"enableToken": { "enableToken": {
"message": "activar $1", "message": "activar $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -2071,9 +2062,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Lo lamentamos, se produjo un error." "message": "Lo lamentamos, se produjo un error."
}, },
"source": {
"message": "Fuente"
},
"speedUp": { "speedUp": {
"message": "Acelerar" "message": "Acelerar"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Võrk kustutada?" "message": "Võrk kustutada?"
}, },
"deleteNetworkDescription": {
"message": "Olete kindel, et soovite selle võrgu kustutada?"
},
"details": { "details": {
"message": "Üksikasjad" "message": "Üksikasjad"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "شبکه حذف شود؟" "message": "شبکه حذف شود؟"
}, },
"deleteNetworkDescription": {
"message": "آیا مطمئن هستید که این شبکه حذف شود؟"
},
"details": { "details": {
"message": "جزئیات" "message": "جزئیات"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Poistetaanko verkko?" "message": "Poistetaanko verkko?"
}, },
"deleteNetworkDescription": {
"message": "Haluatko varmasti poistaa tämän verkon?"
},
"details": { "details": {
"message": "Tiedot" "message": "Tiedot"
}, },

View File

@ -190,9 +190,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "I-delete ang Network?" "message": "I-delete ang Network?"
}, },
"deleteNetworkDescription": {
"message": "Sigurado ka bang gusto mong i-delete ang network na ito?"
},
"details": { "details": {
"message": "Mga Detalye" "message": "Mga Detalye"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "Ajouter un contact" "message": "Ajouter un contact"
}, },
"addCustomIPFSGateway": {
"message": "Ajouter une passerelle IPFS personnalisée"
},
"addCustomIPFSGatewayDescription": {
"message": "La passerelle IPFS vous permet daccéder aux données hébergées par des tiers et de les visualiser. Vous pouvez ajouter une passerelle IPFS personnalisée ou continuer à utiliser la passerelle par défaut."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Ajouter un réseau personnalisé" "message": "Ajouter un réseau personnalisé"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Supprimer le réseau ?" "message": "Supprimer le réseau ?"
}, },
"deleteNetworkDescription": {
"message": "Souhaitez-vous vraiment supprimer ce réseau ?"
},
"deposit": { "deposit": {
"message": "Effectuez un dépôt" "message": "Effectuez un dépôt"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Activez-la depuis les Paramètres." "message": " Activez-la depuis les Paramètres."
}, },
"enableOpenSeaAPI": {
"message": "Activer lAPI OpenSea"
},
"enableOpenSeaAPIDescription": {
"message": "Utilisez lAPI OpenSea pour récupérer les données de NFT. La détection automatique de NFT repose sur lAPI OpenSea et ne sera pas disponible si elle est désactivée."
},
"enableToken": { "enableToken": {
"message": "activer $1", "message": "activer $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Oups ! Quelque chose a mal tourné. " "message": "Oups ! Quelque chose a mal tourné. "
}, },
"source": {
"message": "Source"
},
"speedUp": { "speedUp": {
"message": "Accélérer" "message": "Accélérer"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Demandes dinformations concernant le solde de plusieurs comptes" "message": "Demandes dinformations concernant le solde de plusieurs comptes"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "Nous regroupons vos comptes et demandons à Infura de fournir des informations sur le solde de chacun de vos comptes. Si vous désactivez cette fonction, Infura ne fournira que des informations sur le solde des comptes actifs. Certaines applications décentralisées (dapps) ne fonctionneront pas si vous ne connectez pas votre portefeuille."
},
"useNftDetection": { "useNftDetection": {
"message": "Détection automatique des NFT" "message": "Détection automatique des NFT"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "למחוק את הרשת?" "message": "למחוק את הרשת?"
}, },
"deleteNetworkDescription": {
"message": "הנך בטוח/ה שברצונך למחוק רשת זו?"
},
"details": { "details": {
"message": "פרטים" "message": "פרטים"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "संपर्क जोड़ें" "message": "संपर्क जोड़ें"
}, },
"addCustomIPFSGateway": {
"message": "कस्टम IPFS गेटवे जोड़ें"
},
"addCustomIPFSGatewayDescription": {
"message": "IPFS (आईपीएफएस) गेटवे तृतीय पक्षों द्वारा होस्ट किए गए डेटा को एक्सेस करना और देखना संभव बनाता है। आप एक कस्टम IPFS गेटवे जोड़ सकते हैं या डिफॉल्ट का उपयोग जारी रख सकते हैं।"
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "कस्टम नेटवर्क जोड़ें" "message": "कस्टम नेटवर्क जोड़ें"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "नेटवर्क हटाएं?" "message": "नेटवर्क हटाएं?"
}, },
"deleteNetworkDescription": {
"message": "क्या आप वाकई इस नेटवर्क को हटाना चाहते हैं?"
},
"deposit": { "deposit": {
"message": "जमा करें" "message": "जमा करें"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " इसे सेटिंग्स से इनेबल करें।" "message": " इसे सेटिंग्स से इनेबल करें।"
}, },
"enableOpenSeaAPI": {
"message": "OpenSea API इनेबल करें"
},
"enableOpenSeaAPIDescription": {
"message": "NFT डेटा लाने के लिए OpenSea के API का उपयोग करें। NFT ऑटो-डिटेक्शन OpenSea के API पर निर्भर करता है, और इसके बंद होने पर उपलब्ध नहीं होगा।"
},
"enableToken": { "enableToken": {
"message": "$1 इनेबल करें", "message": "$1 इनेबल करें",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "ओह! कुछ गलत हो गया।" "message": "ओह! कुछ गलत हो गया।"
}, },
"source": {
"message": "स्रोत"
},
"speedUp": { "speedUp": {
"message": "जल्दी करें" "message": "जल्दी करें"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "खाता के शेष राशि के अनुरोधों को बैच करें" "message": "खाता के शेष राशि के अनुरोधों को बैच करें"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "हम खातों को बैच करते हैं और Infura से आपकी शेष राशि को प्रतिक्रियात्मक रूप से दिखाने के लिए पूछताछ करते हैं। यदि आप इसे बंद कर देते हैं, तो केवल सक्रिय खातों के बारे में पूछताछ की जाएगी। कुछ डैप तब तक काम नहीं करेंगे जब तक आप अपने वॉलेट को कनेक्ट नहीं करते।"
},
"useNftDetection": { "useNftDetection": {
"message": "एनएफटी को ऑटो-डिटेक्ट करें" "message": "एनएफटी को ऑटो-डिटेक्ट करें"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Izbrisati mrežu?" "message": "Izbrisati mrežu?"
}, },
"deleteNetworkDescription": {
"message": "Sigurno želite izbrisati ovu mrežu?"
},
"details": { "details": {
"message": "Detalji" "message": "Detalji"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Törli a hálózatot?" "message": "Törli a hálózatot?"
}, },
"deleteNetworkDescription": {
"message": "Biztosan törli ezt a hálózatot?"
},
"details": { "details": {
"message": "Részletek" "message": "Részletek"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "Tambah kontak" "message": "Tambah kontak"
}, },
"addCustomIPFSGateway": {
"message": "Tambahkan gerbang IPFS khusus"
},
"addCustomIPFSGatewayDescription": {
"message": "Gerbang IPFS memungkinkan untuk mengakses dan melihat data yang diselenggarakan oleh pihak ketiga. Anda dapat menambahkan gerbang IPFS khusus atau melanjutkan menggunakan yang awal."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Tambahkan jaringan khusus" "message": "Tambahkan jaringan khusus"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Hapus jaringan?" "message": "Hapus jaringan?"
}, },
"deleteNetworkDescription": {
"message": "Anda yakin ingin menghapus jaringan ini?"
},
"deposit": { "deposit": {
"message": "Deposit" "message": "Deposit"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Aktifkan dari Pengaturan." "message": " Aktifkan dari Pengaturan."
}, },
"enableOpenSeaAPI": {
"message": "Aktifkan API OpenSea"
},
"enableOpenSeaAPIDescription": {
"message": "Gunakan API OpenSea untuk mengambil data NFT. Deteksi otomatis NFT bergantung pada API OpenSea, dan tidak akan tersedia saat API ditutup."
},
"enableToken": { "enableToken": {
"message": "aktifkan $1", "message": "aktifkan $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Ups! Ada yang salah." "message": "Ups! Ada yang salah."
}, },
"source": {
"message": "Sumber"
},
"speedUp": { "speedUp": {
"message": "Percepat" "message": "Percepat"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Kelompokkan permintaan saldo akun" "message": "Kelompokkan permintaan saldo akun"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "Kami mengelompokkan akun dan meminta Infura untuk menunjukkan saldo Anda secara responsif. Jika Anda menonaktifkannya, hanya akun aktif yang akan diminta. Beberapa aplikasi terdesentralisasi (dapp) tidak akan berfungsi kecuali dompet Anda terhubung."
},
"useNftDetection": { "useNftDetection": {
"message": "Deteksi otomatis NFT" "message": "Deteksi otomatis NFT"
}, },

View File

@ -719,9 +719,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Cancella la rete?" "message": "Cancella la rete?"
}, },
"deleteNetworkDescription": {
"message": "Sei sicuro di voler eliminare questa rete?"
},
"deprecatedTestNetworksLink": { "deprecatedTestNetworksLink": {
"message": "Scopri di più" "message": "Scopri di più"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "連絡先を追加" "message": "連絡先を追加"
}, },
"addCustomIPFSGateway": {
"message": "カスタム IPFS ゲートウェイを追加"
},
"addCustomIPFSGatewayDescription": {
"message": "IPFS ゲートウェイにより、第三者がホスティングしているデータへのアクセスと表示が可能になります。カスタム IPFS ゲートウェイを追加するか、デフォルトを引き続き使用できます。"
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "カスタムネットワークを追加" "message": "カスタムネットワークを追加"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "ネットワークを削除しますか?" "message": "ネットワークを削除しますか?"
}, },
"deleteNetworkDescription": {
"message": "このネットワークを削除しますか?"
},
"deposit": { "deposit": {
"message": "入金" "message": "入金"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " 設定で有効にします。" "message": " 設定で有効にします。"
}, },
"enableOpenSeaAPI": {
"message": "OpenSea APIを有効にする"
},
"enableOpenSeaAPIDescription": {
"message": "OpenSea APIを使用してNFTデータを取得します。NFT自動検出はOpenSea APIを使用するため、この設定をオフにすると利用できなくなります。"
},
"enableToken": { "enableToken": {
"message": "$1を有効にする", "message": "$1を有効にする",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "申し訳ありません。問題が発生しました。" "message": "申し訳ありません。問題が発生しました。"
}, },
"source": {
"message": "ソース"
},
"speedUp": { "speedUp": {
"message": "スピードアップ" "message": "スピードアップ"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "アカウント残高の一括リクエスト" "message": "アカウント残高の一括リクエスト"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "当社は残高を素早く表示できるよう、アカウントをまとめて Infura にクエリを送ります。この機能をオフにすると、アクティブなアカウントのみクエリの対象となります。Dapps によっては、ウォレットを接続しないと機能しないものもあります。"
},
"useNftDetection": { "useNftDetection": {
"message": "NFTを自動検出" "message": "NFTを自動検出"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "ನೆಟ್‌ವರ್ಕ್ ಅಳಿಸುವುದೇ?" "message": "ನೆಟ್‌ವರ್ಕ್ ಅಳಿಸುವುದೇ?"
}, },
"deleteNetworkDescription": {
"message": "ನೀವು ಈ ನೆಟ್‌ವರ್ಕ್ ಅನ್ನು ಖಚಿತವಾಗಿ ಅಳಿಸಲು ಬಯಸುತ್ತೀರಾ?"
},
"details": { "details": {
"message": "ವಿವರಗಳು" "message": "ವಿವರಗಳು"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "연락처 추가" "message": "연락처 추가"
}, },
"addCustomIPFSGateway": {
"message": "사용자 지정 IPFS 게이트웨이 추가"
},
"addCustomIPFSGatewayDescription": {
"message": "IPFS 게이트웨이를 사용하면 타사 호스팅 데이터에 액세스하여 이를 볼 수 있습니다. 사용자 지정 IPFS 게이트웨이를 추가하셔도 좋고 기본 설정을 계속 사용하셔도 됩니다."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "커스텀 네트워크 추가" "message": "커스텀 네트워크 추가"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "네트워크를 삭제할까요?" "message": "네트워크를 삭제할까요?"
}, },
"deleteNetworkDescription": {
"message": "이 네트워크를 삭제할까요?"
},
"deposit": { "deposit": {
"message": "예치" "message": "예치"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " 설정에서 이 기능을 활성화합니다." "message": " 설정에서 이 기능을 활성화합니다."
}, },
"enableOpenSeaAPI": {
"message": "OpenSea API 활성화"
},
"enableOpenSeaAPIDescription": {
"message": "OpenSea의 API를 사용하여 NFT 데이터를 가져옵니다. NFT 자동 감지는 OpenSea의 API에 의존하며 이 API가 꺼져 있으면 사용할 수 없습니다."
},
"enableToken": { "enableToken": {
"message": "$1 활성화", "message": "$1 활성화",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "죄송합니다! 문제가 생겼습니다." "message": "죄송합니다! 문제가 생겼습니다."
}, },
"source": {
"message": "소스"
},
"speedUp": { "speedUp": {
"message": "가속화" "message": "가속화"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "일괄 계정 잔액 요청" "message": "일괄 계정 잔액 요청"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "모든 계정을 일괄 처리하여 잔액을 신속하게 표시하도록 Infura에 요청합니다. 이 기능을 끄면 활성 계정에 대한 잔액만 요청합니다. 일부 dapp은 지갑을 연결하지 않으면 작동하지 않습니다."
},
"useNftDetection": { "useNftDetection": {
"message": "NFT 자동 감지" "message": "NFT 자동 감지"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Panaikinti tinklą?" "message": "Panaikinti tinklą?"
}, },
"deleteNetworkDescription": {
"message": "Ar tikrai norite panaikinti šį tinklą?"
},
"details": { "details": {
"message": "Išsami informacija" "message": "Išsami informacija"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Dzēst tīklu?" "message": "Dzēst tīklu?"
}, },
"deleteNetworkDescription": {
"message": "Vai tiešām vēlaties dzēst šo tīklu?"
},
"details": { "details": {
"message": "Informācija" "message": "Informācija"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Padamkan Rangkaian?" "message": "Padamkan Rangkaian?"
}, },
"deleteNetworkDescription": {
"message": "Anda pasti anda ingin padamkan rangkaian ini?"
},
"details": { "details": {
"message": "Butiran" "message": "Butiran"
}, },

View File

@ -211,9 +211,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Slette nettverk? " "message": "Slette nettverk? "
}, },
"deleteNetworkDescription": {
"message": "Er du sikker på at du vil slette dette nettverket?"
},
"details": { "details": {
"message": "Detaljer" "message": "Detaljer"
}, },

View File

@ -409,9 +409,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "I-delete ang Network?" "message": "I-delete ang Network?"
}, },
"deleteNetworkDescription": {
"message": "Sigurado ka bang gusto mong i-delete ang network na ito?"
},
"details": { "details": {
"message": "Mga Detalye" "message": "Mga Detalye"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Usunąć sieć?" "message": "Usunąć sieć?"
}, },
"deleteNetworkDescription": {
"message": "Czy na pewno chcesz usunąć tę sieć?"
},
"details": { "details": {
"message": "Szczegóły" "message": "Szczegóły"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "Adicionar contato" "message": "Adicionar contato"
}, },
"addCustomIPFSGateway": {
"message": "Adicionar gateway IPFS personalizado"
},
"addCustomIPFSGatewayDescription": {
"message": "O gateway IPFS possibilita acessar e visualizar dados hospedados por terceiros. Você pode adicionar um gateway IPFS personalizado ou continuar usando o padrão."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Adicionar rede personalizada" "message": "Adicionar rede personalizada"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Excluir rede?" "message": "Excluir rede?"
}, },
"deleteNetworkDescription": {
"message": "Quer mesmo excluir essa rede?"
},
"deposit": { "deposit": {
"message": "Depositar" "message": "Depositar"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Habilite-a nas configurações." "message": " Habilite-a nas configurações."
}, },
"enableOpenSeaAPI": {
"message": "Habilitar API do OpenSea"
},
"enableOpenSeaAPIDescription": {
"message": "Use a API OpenSea para recuperar dados de NFTs. A detecção automática de NFTs depende da API OpenSea e não estará disponível quando essa opção estiver desativada."
},
"enableToken": { "enableToken": {
"message": "ativar $1", "message": "ativar $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Ops! Algo deu errado." "message": "Ops! Algo deu errado."
}, },
"source": {
"message": "Origem"
},
"speedUp": { "speedUp": {
"message": "Acelerar" "message": "Acelerar"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Agrupar solicitações de saldo de contas" "message": "Agrupar solicitações de saldo de contas"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "Agrupamos as contas e consultamos a Infura para exibir seus saldos de forma responsiva. Se isso for desativado, somente contas ativas serão consultadas. Alguns dapps só funcionam se você conecta a sua carteira."
},
"useNftDetection": { "useNftDetection": {
"message": "Detectar NFTs automaticamente" "message": "Detectar NFTs automaticamente"
}, },

View File

@ -584,9 +584,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Excluir rede?" "message": "Excluir rede?"
}, },
"deleteNetworkDescription": {
"message": "Quer mesmo excluir essa rede?"
},
"description": { "description": {
"message": "Descrição" "message": "Descrição"
}, },
@ -740,12 +737,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Ative nas Configurações." "message": " Ative nas Configurações."
}, },
"enableOpenSeaAPI": {
"message": "Ativar a API OpenSea"
},
"enableOpenSeaAPIDescription": {
"message": "Use a API OpenSea para recuperar dados de NFTs. A detecção automática de NFTs depende da API OpenSea e não estará disponível quando essa opção estiver desativada."
},
"enableToken": { "enableToken": {
"message": "ativar $1", "message": "ativar $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -2071,9 +2062,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Opa! Ocorreu algum erro." "message": "Opa! Ocorreu algum erro."
}, },
"source": {
"message": "Fonte"
},
"speedUp": { "speedUp": {
"message": "Acelerar" "message": "Acelerar"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Ștergeți rețeaua?" "message": "Ștergeți rețeaua?"
}, },
"deleteNetworkDescription": {
"message": "Sigur vreți să ștergeți această rețea?"
},
"details": { "details": {
"message": "Detalii" "message": "Detalii"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "Добавить контакт" "message": "Добавить контакт"
}, },
"addCustomIPFSGateway": {
"message": "Добавить пользовательский шлюз IPFS"
},
"addCustomIPFSGatewayDescription": {
"message": "Шлюз IPFS позволяет получать доступ к данным, размещенным третьими сторонами, и просматривать их. Вы можете добавить пользовательский шлюз IPFS или продолжить использовать шлюз по умолчанию."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Добавить пользовательскую сеть" "message": "Добавить пользовательскую сеть"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Удалить сеть?" "message": "Удалить сеть?"
}, },
"deleteNetworkDescription": {
"message": "Уверены, что хотите удалить эту сеть?"
},
"deposit": { "deposit": {
"message": "Внести деньги" "message": "Внести деньги"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Включите его в Настройках." "message": " Включите его в Настройках."
}, },
"enableOpenSeaAPI": {
"message": "Включить API OpenSea"
},
"enableOpenSeaAPIDescription": {
"message": "Используйте API OpenSea для получения данных NFT. Для автоматического обнаружения NFT используется API OpenSea, и такое обнаружение будет недоступно, если этот API отключен."
},
"enableToken": { "enableToken": {
"message": "активирует для $1", "message": "активирует для $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Ой! Что-то пошло не так." "message": "Ой! Что-то пошло не так."
}, },
"source": {
"message": "Источник"
},
"speedUp": { "speedUp": {
"message": "Ускорить" "message": "Ускорить"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Пакетные запросы баланса счета" "message": "Пакетные запросы баланса счета"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "Мы создаем пакет счетов и запрашиваем Infura, чтобы оперативно отображать ваши остатки. Если вы отключите это, запрос будет отправляться только в отношении активных счетов. Некоторые dapps не будут работать, пока вы не подключите свой кошелек."
},
"useNftDetection": { "useNftDetection": {
"message": "Автообнаружение NFT" "message": "Автообнаружение NFT"
}, },

View File

@ -208,9 +208,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Odstrániť sieť?" "message": "Odstrániť sieť?"
}, },
"deleteNetworkDescription": {
"message": "Naozaj chcete túto sieť odstrániť?"
},
"details": { "details": {
"message": "Podrobnosti" "message": "Podrobnosti"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Izbrišem to omrežje?" "message": "Izbrišem to omrežje?"
}, },
"deleteNetworkDescription": {
"message": "Ali ste prepričani, da želite izbrisati to omrežje?"
},
"details": { "details": {
"message": "Podrobnosti" "message": "Podrobnosti"
}, },

View File

@ -211,9 +211,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Da li želite da obrišete mrežu?" "message": "Da li želite da obrišete mrežu?"
}, },
"deleteNetworkDescription": {
"message": "Da li ste sigurni da želite da izbrišete ovu mrežu?"
},
"details": { "details": {
"message": "Детаљи" "message": "Детаљи"
}, },

View File

@ -208,9 +208,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Radera nätverk?" "message": "Radera nätverk?"
}, },
"deleteNetworkDescription": {
"message": "Är du säker på att du vill ta bort detta nätverk?"
},
"details": { "details": {
"message": "Info" "message": "Info"
}, },

View File

@ -208,9 +208,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Futa Mtandao?" "message": "Futa Mtandao?"
}, },
"deleteNetworkDescription": {
"message": "Una uhakika unataka kufuta mtandao huu?"
},
"details": { "details": {
"message": "Maelezo" "message": "Maelezo"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "Magdagdag ng contact" "message": "Magdagdag ng contact"
}, },
"addCustomIPFSGateway": {
"message": "Magdagdag ng custom na IPFS gateway"
},
"addCustomIPFSGatewayDescription": {
"message": "Ginagawang posible ng IPFS gateway na ma-access at makita ang datos na pinangangasiwaan ng mga third party. Maaari kang magdagdag ng custom na IPFS gateway o magpatuloy sa paggamit ng default."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Magdagdag ng custom na network" "message": "Magdagdag ng custom na network"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "I-delete ang network?" "message": "I-delete ang network?"
}, },
"deleteNetworkDescription": {
"message": "Sigurado ka bang gusto mong i-delete ang network na ito?"
},
"deposit": { "deposit": {
"message": "Deposito" "message": "Deposito"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Paganahin ito mula sa Settings." "message": " Paganahin ito mula sa Settings."
}, },
"enableOpenSeaAPI": {
"message": "Paganahin sa OpenSea API"
},
"enableOpenSeaAPIDescription": {
"message": "Gamitin ang API ng Opensea upang kunin ang NFT data. ang NFT auto-detection ay umaasa sa API ng OpenSea, at hindi magiging available kapag ito ay isinara."
},
"enableToken": { "enableToken": {
"message": "paganahin ang $1", "message": "paganahin ang $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Oops! Nagkaproblema." "message": "Oops! Nagkaproblema."
}, },
"source": {
"message": "Pinagmulan"
},
"speedUp": { "speedUp": {
"message": "Pabilisin" "message": "Pabilisin"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Mga kahilingan sa balanse ng batch account" "message": "Mga kahilingan sa balanse ng batch account"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "Pinagsasama-sama namin ang mga account at tanong sa Infura para maipakita ang inyong balanse. Kung io-off mo ito, ang mga aktibong account lamang ang makakapagtanong. Ang ilan sa dapps ay hindi gagana maliban kung nakakonekta ang iyongg wallet."
},
"useNftDetection": { "useNftDetection": {
"message": "Awtomatikong tuklasin ang mga NFT" "message": "Awtomatikong tuklasin ang mga NFT"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "Kişi ekle" "message": "Kişi ekle"
}, },
"addCustomIPFSGateway": {
"message": "Özel IPFS ağ geçidi ekle"
},
"addCustomIPFSGatewayDescription": {
"message": "IPFS ağ geçidi üçüncü tarafların barındırdığı veriler için erişim ve görüntülemeyi mümkün kılar. Özel bir IPFS ağ geçidi ekleyebilir veya varsayılanı kullanmaya devam edebilirsiniz."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Özel ağ ekle" "message": "Özel ağ ekle"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Ağı Sil?" "message": "Ağı Sil?"
}, },
"deleteNetworkDescription": {
"message": "Bu ağı silmek istediğinizden emin misiniz?"
},
"deposit": { "deposit": {
"message": "Para Yatır" "message": "Para Yatır"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Ayarlardan etkinleştir." "message": " Ayarlardan etkinleştir."
}, },
"enableOpenSeaAPI": {
"message": "OpenSea API'yi etkinleştir"
},
"enableOpenSeaAPIDescription": {
"message": "NFT verilerini almak için OpenSea API'sini kullanın. NFT otomatik algılama OpenSea API'ye dayalıdır ve bu kapatılırsa mevcut olmayacaktır."
},
"enableToken": { "enableToken": {
"message": "şunu etkinleştir: $1", "message": "şunu etkinleştir: $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Eyvah! Bir şeyler ters gitti." "message": "Eyvah! Bir şeyler ters gitti."
}, },
"source": {
"message": "Kaynak"
},
"speedUp": { "speedUp": {
"message": "Hızlandır" "message": "Hızlandır"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Toplu hesap bakiyesi talepleri" "message": "Toplu hesap bakiyesi talepleri"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "Hesapları toplarız ve bakiyelerinizi göstermek için Infura'yı sorgularız. Bunu kapattığınız takdirde sadece aktif hesaplar sorgulanır. Bazı merkeziyetsiz uygulamalar siz cüzdanınızı bağlamadığınız sürece çalışmaz."
},
"useNftDetection": { "useNftDetection": {
"message": "NFT'leri otomatik algıla" "message": "NFT'leri otomatik algıla"
}, },

View File

@ -214,9 +214,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Видалити мережу?" "message": "Видалити мережу?"
}, },
"deleteNetworkDescription": {
"message": "Ви впевнені, що хочете видалити цю мережу?"
},
"details": { "details": {
"message": "Деталі" "message": "Деталі"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "Thêm địa chỉ liên hệ" "message": "Thêm địa chỉ liên hệ"
}, },
"addCustomIPFSGateway": {
"message": "Thêm cổng IPFS tùy chỉnh"
},
"addCustomIPFSGatewayDescription": {
"message": "Cổng IPFS cho phép truy cập và xem dữ liệu do bên thứ ba lưu trữ. Bạn có thể thêm cổng IPFS tùy chỉnh hoặc tiếp tục sử dụng cổng mặc định."
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "Thêm mạng tùy chỉnh" "message": "Thêm mạng tùy chỉnh"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "Xóa mạng?" "message": "Xóa mạng?"
}, },
"deleteNetworkDescription": {
"message": "Bạn có chắc chắn muốn xóa mạng này không?"
},
"deposit": { "deposit": {
"message": "Nạp" "message": "Nạp"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " Bật lên trong Cài Đặt." "message": " Bật lên trong Cài Đặt."
}, },
"enableOpenSeaAPI": {
"message": "Bật API OpenSea"
},
"enableOpenSeaAPIDescription": {
"message": "Sử dụng API của OpenSea để tìm nạp dữ liệu NFT. Tính năng tự động phát hiện NFT dựa vào API của OpenSea và sẽ không khả dụng nếu tính năng này bị tắt."
},
"enableToken": { "enableToken": {
"message": "bật $1", "message": "bật $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "Rất tiếc! Đã xảy ra sự cố." "message": "Rất tiếc! Đã xảy ra sự cố."
}, },
"source": {
"message": "Nguồn"
},
"speedUp": { "speedUp": {
"message": "Tăng tốc" "message": "Tăng tốc"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "Xử lý hàng loạt yêu cầu số dư tài khoản" "message": "Xử lý hàng loạt yêu cầu số dư tài khoản"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "Chúng tôi xử lý hàng loạt tài khoản và kiểm tra với Infura để hiển thị các số dư tài khoản của bạn một cách nhanh chóng. Nếu bạn tắt tính năng này, chỉ những tài khoản đang hoạt động mới được truy vấn. Một số dapp sẽ không hoạt động trừ khi bạn kết nối với ví của mình."
},
"useNftDetection": { "useNftDetection": {
"message": "Tự động phát hiện NFT" "message": "Tự động phát hiện NFT"
}, },

View File

@ -183,12 +183,6 @@
"addContact": { "addContact": {
"message": "添加联系信息" "message": "添加联系信息"
}, },
"addCustomIPFSGateway": {
"message": "添加自定义IPFS网关"
},
"addCustomIPFSGatewayDescription": {
"message": "IPFS 网关使访问和查看第三方托管的数据成为可能。您可以添加自定义 IPFS 网关或继续使用默认网关。"
},
"addCustomNetwork": { "addCustomNetwork": {
"message": "添加自定义网络" "message": "添加自定义网络"
}, },
@ -914,9 +908,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "删除网络?" "message": "删除网络?"
}, },
"deleteNetworkDescription": {
"message": "您确定要删除该网络吗?"
},
"deposit": { "deposit": {
"message": "存入" "message": "存入"
}, },
@ -1184,12 +1175,6 @@
"enableFromSettings": { "enableFromSettings": {
"message": " 从设置中启用它。" "message": " 从设置中启用它。"
}, },
"enableOpenSeaAPI": {
"message": "启用 OpenSea API"
},
"enableOpenSeaAPIDescription": {
"message": "使用 OpenSea 的 API 获取 NFT 数据。NFT 自动检测依赖于 OpenSea 的 API在后者关闭时自动检测将不可用。"
},
"enableToken": { "enableToken": {
"message": "启用 $1", "message": "启用 $1",
"description": "$1 is a token symbol, e.g. ETH" "description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": { "somethingWentWrong": {
"message": "哎呀!出了点问题。" "message": "哎呀!出了点问题。"
}, },
"source": {
"message": "来源"
},
"speedUp": { "speedUp": {
"message": "加速" "message": "加速"
}, },
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": { "useMultiAccountBalanceChecker": {
"message": "账户余额分批请求" "message": "账户余额分批请求"
}, },
"useMultiAccountBalanceCheckerDescription": {
"message": "我们会将账户分批并向 Infura 查询以对应地显示您的余额。如果禁用此选项将仅查询活动账户。必须连接钱包否则有一些去中心化应用程序dapp将无法工作。"
},
"useNftDetection": { "useNftDetection": {
"message": "自动检测NFT" "message": "自动检测NFT"
}, },

View File

@ -411,9 +411,6 @@
"deleteNetwork": { "deleteNetwork": {
"message": "刪除網路?" "message": "刪除網路?"
}, },
"deleteNetworkDescription": {
"message": "你確定要刪除網路嗎?"
},
"details": { "details": {
"message": "詳情" "message": "詳情"
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
app/images/default_nft.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 400 KiB

View File

@ -2,6 +2,10 @@
* @file The entry point for the web extension singleton process. * @file The entry point for the web extension singleton process.
*/ */
// This import sets up a global function required for Sentry to function.
// It must be run first in case an error is thrown later during initialization.
import './lib/setup-persisted-state-hook';
import EventEmitter from 'events'; import EventEmitter from 'events';
import endOfStream from 'end-of-stream'; import endOfStream from 'end-of-stream';
import pump from 'pump'; import pump from 'pump';
@ -466,6 +470,9 @@ export function setupController(
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind( getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(
controller.preferencesController, controller.preferencesController,
), ),
getUseAddressBarEnsResolution: () =>
controller.preferencesController.store.getState()
.useAddressBarEnsResolution,
provider: controller.provider, provider: controller.provider,
}); });
@ -794,6 +801,13 @@ export function setupController(
}); });
} }
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(snaps)
// Updates the snaps registry and check for newly blocked snaps to block if the user has at least one snap installed.
if (Object.keys(controller.snapController.state.snaps).length > 0) {
controller.snapController.updateBlockedSnaps();
}
///: END:ONLY_INCLUDE_IN
} }
// //

View File

@ -615,6 +615,12 @@ function redirectToPhishingWarning() {
const querystring = new URLSearchParams({ hostname, href }); const querystring = new URLSearchParams({ hostname, href });
window.location.href = `${baseUrl}#${querystring}`; window.location.href = `${baseUrl}#${querystring}`;
// eslint-disable-next-line no-constant-condition
while (1) {
console.log(
'MetaMask: Locking js execution, redirection will complete shortly',
);
}
} }
const start = () => { const start = () => {

View File

@ -452,7 +452,6 @@ export default class AppStateController extends EventEmitter {
_acceptApproval() { _acceptApproval() {
if (!this._approvalRequestId) { if (!this._approvalRequestId) {
log.error('Attempted to accept missing unlock approval request');
return; return;
} }
try { try {
@ -461,7 +460,7 @@ export default class AppStateController extends EventEmitter {
this._approvalRequestId, this._approvalRequestId,
); );
} catch (error) { } catch (error) {
log.error('Failed to accept transaction approval request', error); log.error('Failed to unlock approval request', error);
} }
this._approvalRequestId = null; this._approvalRequestId = null;

View File

@ -1,10 +1,7 @@
import { ObservableStore } from '@metamask/obs-store'; import { ObservableStore } from '@metamask/obs-store';
import log from 'loglevel';
import { ORIGIN_METAMASK } from '../../../shared/constants/app'; import { ORIGIN_METAMASK } from '../../../shared/constants/app';
import AppStateController from './app-state'; import AppStateController from './app-state';
jest.mock('loglevel');
let appStateController, mockStore; let appStateController, mockStore;
describe('AppStateController', () => { describe('AppStateController', () => {
@ -147,52 +144,6 @@ describe('AppStateController', () => {
expect.any(String), expect.any(String),
); );
}); });
it('logs if rejecting approval request throws', async () => {
appStateController._approvalRequestId = 'mock-approval-request-id';
appStateController = new AppStateController({
addUnlockListener: jest.fn(),
isUnlocked: jest.fn(() => true),
onInactiveTimeout: jest.fn(),
showUnlockRequest: jest.fn(),
preferencesStore: {
subscribe: jest.fn(),
getState: jest.fn(() => ({
preferences: {
autoLockTimeLimit: 0,
},
})),
},
qrHardwareStore: {
subscribe: jest.fn(),
},
messenger: {
call: jest.fn(() => {
throw new Error('mock error');
}),
},
});
appStateController.handleUnlock();
expect(log.error).toHaveBeenCalledTimes(1);
expect(log.error).toHaveBeenCalledWith(
'Attempted to accept missing unlock approval request',
);
});
it('returns without call messenger if no approval request in pending', async () => {
const emitSpy = jest.spyOn(appStateController, 'emit');
appStateController.handleUnlock();
expect(emitSpy).toHaveBeenCalledTimes(0);
expect(appStateController.messagingSystem.call).toHaveBeenCalledTimes(0);
expect(log.error).toHaveBeenCalledTimes(1);
expect(log.error).toHaveBeenCalledWith(
'Attempted to accept missing unlock approval request',
);
});
}); });
describe('setDefaultHomeActiveTabName', () => { describe('setDefaultHomeActiveTabName', () => {

View File

@ -19,7 +19,7 @@ const messageIdMock2 = '456';
const stateMock = { test: 123 }; const stateMock = { test: 123 };
const addressMock = '0xc38bf1ad06ef69f0c04e29dbeb4152b4175f0a8d'; const addressMock = '0xc38bf1ad06ef69f0c04e29dbeb4152b4175f0a8d';
const publicKeyMock = '32762347862378feb87123781623a='; const publicKeyMock = '32762347862378feb87123781623a=';
const keyringMock = { type: KeyringType.hdKeyTree }; const keyringTypeMock = KeyringType.hdKeyTree;
const messageParamsMock = { const messageParamsMock = {
from: addressMock, from: addressMock,
@ -73,11 +73,6 @@ const createEncryptionPublicKeyManagerMock = <T>() =>
}, },
} as any as jest.Mocked<T>); } as any as jest.Mocked<T>);
const createKeyringControllerMock = () => ({
getKeyringForAccount: jest.fn(),
getEncryptionPublicKey: jest.fn(),
});
describe('EncryptionPublicKeyController', () => { describe('EncryptionPublicKeyController', () => {
let encryptionPublicKeyController: EncryptionPublicKeyController; let encryptionPublicKeyController: EncryptionPublicKeyController;
@ -88,7 +83,8 @@ describe('EncryptionPublicKeyController', () => {
const encryptionPublicKeyManagerMock = const encryptionPublicKeyManagerMock =
createEncryptionPublicKeyManagerMock<EncryptionPublicKeyManager>(); createEncryptionPublicKeyManagerMock<EncryptionPublicKeyManager>();
const messengerMock = createMessengerMock(); const messengerMock = createMessengerMock();
const keyringControllerMock = createKeyringControllerMock(); const getEncryptionPublicKeyMock = jest.fn();
const getAccountKeyringTypeMock = jest.fn();
const getStateMock = jest.fn(); const getStateMock = jest.fn();
const metricsEventMock = jest.fn(); const metricsEventMock = jest.fn();
@ -101,7 +97,8 @@ describe('EncryptionPublicKeyController', () => {
encryptionPublicKeyController = new EncryptionPublicKeyController({ encryptionPublicKeyController = new EncryptionPublicKeyController({
messenger: messengerMock as any, messenger: messengerMock as any,
keyringController: keyringControllerMock as any, getEncryptionPublicKey: getEncryptionPublicKeyMock as any,
getAccountKeyringType: getAccountKeyringTypeMock as any,
getState: getStateMock as any, getState: getStateMock as any,
metricsEvent: metricsEventMock as any, metricsEvent: metricsEventMock as any,
} as EncryptionPublicKeyControllerOptions); } as EncryptionPublicKeyControllerOptions);
@ -203,9 +200,7 @@ describe('EncryptionPublicKeyController', () => {
])( ])(
'throws if keyring is not supported', 'throws if keyring is not supported',
async (keyringName, keyringType) => { async (keyringName, keyringType) => {
keyringControllerMock.getKeyringForAccount.mockResolvedValueOnce({ getAccountKeyringTypeMock.mockResolvedValueOnce(keyringType);
type: keyringType,
});
await expect( await expect(
encryptionPublicKeyController.newRequestEncryptionPublicKey( encryptionPublicKeyController.newRequestEncryptionPublicKey(
@ -219,9 +214,7 @@ describe('EncryptionPublicKeyController', () => {
); );
it('adds message to message manager', async () => { it('adds message to message manager', async () => {
keyringControllerMock.getKeyringForAccount.mockResolvedValueOnce( getAccountKeyringTypeMock.mockResolvedValueOnce(keyringTypeMock);
keyringMock,
);
await encryptionPublicKeyController.newRequestEncryptionPublicKey( await encryptionPublicKeyController.newRequestEncryptionPublicKey(
addressMock, addressMock,
@ -243,9 +236,7 @@ describe('EncryptionPublicKeyController', () => {
from: messageParamsMock.data, from: messageParamsMock.data,
}); });
keyringControllerMock.getEncryptionPublicKey.mockResolvedValueOnce( getEncryptionPublicKeyMock.mockResolvedValueOnce(publicKeyMock);
publicKeyMock,
);
}); });
it('approves message and signs', async () => { it('approves message and signs', async () => {
@ -253,10 +244,8 @@ describe('EncryptionPublicKeyController', () => {
messageParamsMock, messageParamsMock,
); );
expect( expect(getEncryptionPublicKeyMock).toHaveBeenCalledTimes(1);
keyringControllerMock.getEncryptionPublicKey, expect(getEncryptionPublicKeyMock).toHaveBeenCalledWith(
).toHaveBeenCalledTimes(1);
expect(keyringControllerMock.getEncryptionPublicKey).toHaveBeenCalledWith(
messageParamsMock.data, messageParamsMock.data,
); );
@ -294,10 +283,8 @@ describe('EncryptionPublicKeyController', () => {
}); });
it('rejects message on error', async () => { it('rejects message on error', async () => {
keyringControllerMock.getEncryptionPublicKey.mockReset(); getEncryptionPublicKeyMock.mockReset();
keyringControllerMock.getEncryptionPublicKey.mockRejectedValue( getEncryptionPublicKeyMock.mockRejectedValue(new Error('Test Error'));
new Error('Test Error'),
);
await expect( await expect(
encryptionPublicKeyController.encryptionPublicKey(messageParamsMock), encryptionPublicKeyController.encryptionPublicKey(messageParamsMock),
@ -312,10 +299,8 @@ describe('EncryptionPublicKeyController', () => {
}); });
it('rejects approval on error', async () => { it('rejects approval on error', async () => {
keyringControllerMock.getEncryptionPublicKey.mockReset(); getEncryptionPublicKeyMock.mockReset();
keyringControllerMock.getEncryptionPublicKey.mockRejectedValue( getEncryptionPublicKeyMock.mockRejectedValue(new Error('Test Error'));
new Error('Test Error'),
);
await expect( await expect(
encryptionPublicKeyController.encryptionPublicKey(messageParamsMock), encryptionPublicKeyController.encryptionPublicKey(messageParamsMock),

View File

@ -4,7 +4,6 @@ import {
EncryptionPublicKeyManager, EncryptionPublicKeyManager,
EncryptionPublicKeyParamsMetamask, EncryptionPublicKeyParamsMetamask,
} from '@metamask/message-manager'; } from '@metamask/message-manager';
import { KeyringController } from '@metamask/eth-keyring-controller';
import { import {
AbstractMessageManager, AbstractMessageManager,
AbstractMessage, AbstractMessage,
@ -83,7 +82,8 @@ export type EncryptionPublicKeyControllerMessenger =
export type EncryptionPublicKeyControllerOptions = { export type EncryptionPublicKeyControllerOptions = {
messenger: EncryptionPublicKeyControllerMessenger; messenger: EncryptionPublicKeyControllerMessenger;
keyringController: KeyringController; getEncryptionPublicKey: (address: string) => Promise<string>;
getAccountKeyringType: (account: string) => Promise<string>;
getState: () => any; getState: () => any;
metricsEvent: (payload: any, options?: any) => void; metricsEvent: (payload: any, options?: any) => void;
}; };
@ -98,7 +98,9 @@ export default class EncryptionPublicKeyController extends BaseControllerV2<
> { > {
hub: EventEmitter; hub: EventEmitter;
private _keyringController: KeyringController; private _getEncryptionPublicKey: (address: string) => Promise<string>;
private _getAccountKeyringType: (account: string) => Promise<string>;
private _getState: () => any; private _getState: () => any;
@ -111,13 +113,15 @@ export default class EncryptionPublicKeyController extends BaseControllerV2<
* *
* @param options - The controller options. * @param options - The controller options.
* @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller. * @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller.
* @param options.keyringController - An instance of a keyring controller used to extract the encryption public key. * @param options.getEncryptionPublicKey - Callback to get the keyring encryption public key.
* @param options.getAccountKeyringType - Callback to get the keyring type.
* @param options.getState - Callback to retrieve all user state. * @param options.getState - Callback to retrieve all user state.
* @param options.metricsEvent - A function for emitting a metric event. * @param options.metricsEvent - A function for emitting a metric event.
*/ */
constructor({ constructor({
messenger, messenger,
keyringController, getEncryptionPublicKey,
getAccountKeyringType,
getState, getState,
metricsEvent, metricsEvent,
}: EncryptionPublicKeyControllerOptions) { }: EncryptionPublicKeyControllerOptions) {
@ -128,7 +132,8 @@ export default class EncryptionPublicKeyController extends BaseControllerV2<
state: getDefaultState(), state: getDefaultState(),
}); });
this._keyringController = keyringController; this._getEncryptionPublicKey = getEncryptionPublicKey;
this._getAccountKeyringType = getAccountKeyringType;
this._getState = getState; this._getState = getState;
this._metricsEvent = metricsEvent; this._metricsEvent = metricsEvent;
@ -186,9 +191,9 @@ export default class EncryptionPublicKeyController extends BaseControllerV2<
address: string, address: string,
req: OriginalRequest, req: OriginalRequest,
): Promise<string> { ): Promise<string> {
const keyring = await this._keyringController.getKeyringForAccount(address); const keyringType = await this._getAccountKeyringType(address);
switch (keyring.type) { switch (keyringType) {
case KeyringType.ledger: { case KeyringType.ledger: {
return new Promise((_, reject) => { return new Promise((_, reject) => {
reject( reject(
@ -244,7 +249,7 @@ export default class EncryptionPublicKeyController extends BaseControllerV2<
await this._encryptionPublicKeyManager.approveMessage(msgParams); await this._encryptionPublicKeyManager.approveMessage(msgParams);
// EncryptionPublicKey message // EncryptionPublicKey message
const publicKey = await this._keyringController.getEncryptionPublicKey( const publicKey = await this._getEncryptionPublicKey(
cleanMessageParams.from, cleanMessageParams.from,
); );

View File

@ -419,18 +419,18 @@ export default class MetaMetricsController {
* *
* @param {boolean} participateInMetaMetrics - Whether or not the user wants * @param {boolean} participateInMetaMetrics - Whether or not the user wants
* to participate in MetaMetrics * to participate in MetaMetrics
* @returns {string|null} the string of the new metametrics id, or null * @returns {Promise<string|null>} the string of the new metametrics id, or null
* if not set * if not set
*/ */
setParticipateInMetaMetrics(participateInMetaMetrics) { async setParticipateInMetaMetrics(participateInMetaMetrics) {
let { metaMetricsId } = this.state; let { metaMetricsId } = this.state;
if (participateInMetaMetrics && !metaMetricsId) { if (participateInMetaMetrics && !metaMetricsId) {
// We also need to start sentry automatic session tracking at this point // We also need to start sentry automatic session tracking at this point
globalThis.sentry?.startSession(); await globalThis.sentry?.startSession();
metaMetricsId = this.generateMetaMetricsId(); metaMetricsId = this.generateMetaMetricsId();
} else if (participateInMetaMetrics === false) { } else if (participateInMetaMetrics === false) {
// We also need to stop sentry automatic session tracking at this point // We also need to stop sentry automatic session tracking at this point
globalThis.sentry?.endSession(); await globalThis.sentry?.endSession();
metaMetricsId = null; metaMetricsId = null;
} }
this.store.updateState({ participateInMetaMetrics, metaMetricsId }); this.store.updateState({ participateInMetaMetrics, metaMetricsId });

View File

@ -313,30 +313,30 @@ describe('MetaMetricsController', function () {
}); });
describe('setParticipateInMetaMetrics', function () { describe('setParticipateInMetaMetrics', function () {
it('should update the value of participateInMetaMetrics', function () { it('should update the value of participateInMetaMetrics', async function () {
const metaMetricsController = getMetaMetricsController({ const metaMetricsController = getMetaMetricsController({
participateInMetaMetrics: null, participateInMetaMetrics: null,
metaMetricsId: null, metaMetricsId: null,
}); });
assert.equal(metaMetricsController.state.participateInMetaMetrics, null); assert.equal(metaMetricsController.state.participateInMetaMetrics, null);
metaMetricsController.setParticipateInMetaMetrics(true); await metaMetricsController.setParticipateInMetaMetrics(true);
assert.ok(globalThis.sentry.startSession.calledOnce); assert.ok(globalThis.sentry.startSession.calledOnce);
assert.equal(metaMetricsController.state.participateInMetaMetrics, true); assert.equal(metaMetricsController.state.participateInMetaMetrics, true);
metaMetricsController.setParticipateInMetaMetrics(false); await metaMetricsController.setParticipateInMetaMetrics(false);
assert.equal(metaMetricsController.state.participateInMetaMetrics, false); assert.equal(metaMetricsController.state.participateInMetaMetrics, false);
}); });
it('should generate and update the metaMetricsId when set to true', function () { it('should generate and update the metaMetricsId when set to true', async function () {
const metaMetricsController = getMetaMetricsController({ const metaMetricsController = getMetaMetricsController({
participateInMetaMetrics: null, participateInMetaMetrics: null,
metaMetricsId: null, metaMetricsId: null,
}); });
assert.equal(metaMetricsController.state.metaMetricsId, null); assert.equal(metaMetricsController.state.metaMetricsId, null);
metaMetricsController.setParticipateInMetaMetrics(true); await metaMetricsController.setParticipateInMetaMetrics(true);
assert.equal(typeof metaMetricsController.state.metaMetricsId, 'string'); assert.equal(typeof metaMetricsController.state.metaMetricsId, 'string');
}); });
it('should nullify the metaMetricsId when set to false', function () { it('should nullify the metaMetricsId when set to false', async function () {
const metaMetricsController = getMetaMetricsController(); const metaMetricsController = getMetaMetricsController();
metaMetricsController.setParticipateInMetaMetrics(false); await metaMetricsController.setParticipateInMetaMetrics(false);
assert.ok(globalThis.sentry.endSession.calledOnce); assert.ok(globalThis.sentry.endSession.calledOnce);
assert.equal(metaMetricsController.state.metaMetricsId, null); assert.equal(metaMetricsController.state.metaMetricsId, null);
}); });

View File

@ -39,6 +39,7 @@ export default class PreferencesController {
// set to false will be using the static list from contract-metadata // set to false will be using the static list from contract-metadata
useTokenDetection: false, useTokenDetection: false,
useNftDetection: false, useNftDetection: false,
use4ByteResolution: true,
useCurrencyRateCheck: true, useCurrencyRateCheck: true,
openSeaEnabled: false, openSeaEnabled: false,
///: BEGIN:ONLY_INCLUDE_IN(blockaid) ///: BEGIN:ONLY_INCLUDE_IN(blockaid)
@ -67,6 +68,7 @@ export default class PreferencesController {
}, },
// ENS decentralized website resolution // ENS decentralized website resolution
ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, ipfsGateway: IPFS_DEFAULT_GATEWAY_URL,
useAddressBarEnsResolution: true,
infuraBlocked: null, infuraBlocked: null,
ledgerTransportType: window.navigator.hid ledgerTransportType: window.navigator.hid
? LedgerTransportTypes.webhid ? LedgerTransportTypes.webhid
@ -168,6 +170,15 @@ export default class PreferencesController {
this.store.updateState({ useNftDetection }); this.store.updateState({ useNftDetection });
} }
/**
* Setter for the `use4ByteResolution` property
*
* @param {boolean} use4ByteResolution - (Privacy) Whether or not the user prefers to have smart contract name details resolved with 4byte.directory
*/
setUse4ByteResolution(use4ByteResolution) {
this.store.updateState({ use4ByteResolution });
}
/** /**
* Setter for the `useCurrencyRateCheck` property * Setter for the `useCurrencyRateCheck` property
* *
@ -480,6 +491,15 @@ export default class PreferencesController {
return domain; return domain;
} }
/**
* A setter for the `useAddressBarEnsResolution` property
*
* @param {boolean} useAddressBarEnsResolution - Whether or not user prefers IPFS resolution for domains
*/
async setUseAddressBarEnsResolution(useAddressBarEnsResolution) {
this.store.updateState({ useAddressBarEnsResolution });
}
/** /**
* A setter for the `ledgerTransportType` property. * A setter for the `ledgerTransportType` property.
* *

View File

@ -233,6 +233,25 @@ describe('preferences controller', function () {
}); });
}); });
describe('setUse4ByteResolution', function () {
it('should default to true', function () {
const state = preferencesController.store.getState();
assert.equal(state.use4ByteResolution, true);
});
it('should set the use4ByteResolution property in state', function () {
assert.equal(
preferencesController.store.getState().use4ByteResolution,
true,
);
preferencesController.setUse4ByteResolution(false);
assert.equal(
preferencesController.store.getState().use4ByteResolution,
false,
);
});
});
describe('setOpenSeaEnabled', function () { describe('setOpenSeaEnabled', function () {
it('should default to false', function () { it('should default to false', function () {
const state = preferencesController.store.getState(); const state = preferencesController.store.getState();

View File

@ -149,9 +149,12 @@ export default class SwapsController {
this.ethersProvider = new Web3Provider(provider); this.ethersProvider = new Web3Provider(provider);
this._currentNetworkId = networkController.state.networkId; this._currentNetworkId = networkController.state.networkId;
onNetworkStateChange(() => { onNetworkStateChange(() => {
const { networkId, networkStatus } = networkController.state; const { networkId, networksMetadata, selectedNetworkClientId } =
networkController.state;
const selectedNetworkStatus =
networksMetadata[selectedNetworkClientId]?.status;
if ( if (
networkStatus === NetworkStatus.Available && selectedNetworkStatus === NetworkStatus.Available &&
networkId !== this._currentNetworkId networkId !== this._currentNetworkId
) { ) {
this._currentNetworkId = networkId; this._currentNetworkId = networkId;

View File

@ -4,6 +4,7 @@ import sinon from 'sinon';
import { BigNumber } from '@ethersproject/bignumber'; import { BigNumber } from '@ethersproject/bignumber';
import { mapValues } from 'lodash'; import { mapValues } from 'lodash';
import BigNumberjs from 'bignumber.js'; import BigNumberjs from 'bignumber.js';
import { NetworkType } from '@metamask/controller-utils';
import { import {
CHAIN_IDS, CHAIN_IDS,
NETWORK_IDS, NETWORK_IDS,
@ -102,7 +103,13 @@ function getMockNetworkController() {
return { return {
state: { state: {
networkId: NETWORK_IDS.GOERLI, networkId: NETWORK_IDS.GOERLI,
networkStatus: NetworkStatus.Available, selectedNetworkClientId: NetworkType.goerli,
networksMetadata: {
[NetworkType.goerli]: {
EIPS: {},
status: NetworkStatus.Available,
},
},
}, },
}; };
} }
@ -224,7 +231,13 @@ describe('SwapsController', function () {
networkController.state = { networkController.state = {
networkId: NETWORK_IDS.MAINNET, networkId: NETWORK_IDS.MAINNET,
networkStatus: NetworkStatus.Available, selectedNetworkClientId: NetworkType.mainnet,
networksMetadata: {
[NetworkType.mainnet]: {
EIPS: {},
status: NetworkStatus.Available,
},
},
}; };
networkStateChangeListener(); networkStateChangeListener();
@ -256,7 +269,13 @@ describe('SwapsController', function () {
networkController.state = { networkController.state = {
networkId: null, networkId: null,
networkStatus: NetworkStatus.Unknown, selectedNetworkClientId: NetworkType.goerli,
networksMetadata: {
[NetworkType.goerli]: {
EIPS: {},
status: NetworkStatus.Unknown,
},
},
}; };
networkStateChangeListener(); networkStateChangeListener();
@ -288,7 +307,13 @@ describe('SwapsController', function () {
networkController.state = { networkController.state = {
networkId: NETWORK_IDS.GOERLI, networkId: NETWORK_IDS.GOERLI,
networkStatus: NetworkStatus.Available, selectedNetworkClientId: NetworkType.goerli,
networksMetadata: {
[NetworkType.goerli]: {
EIPS: {},
status: NetworkStatus.Available,
},
},
}; };
networkStateChangeListener(); networkStateChangeListener();

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,10 @@ import { ApprovalType } from '@metamask/controller-utils';
import sinon from 'sinon'; import sinon from 'sinon';
import { errorCodes, ethErrors } from 'eth-rpc-errors'; import { errorCodes, ethErrors } from 'eth-rpc-errors';
import {
BlockaidReason,
BlockaidResultType,
} from '../../../../shared/constants/security-provider';
import { import {
createTestProviderTools, createTestProviderTools,
getTestAccounts, getTestAccounts,
@ -746,11 +750,11 @@ describe('Transaction Controller', function () {
providerResultStub.eth_estimateGas = '0x5209'; providerResultStub.eth_estimateGas = '0x5209';
signStub = sinon signStub = sinon
.stub(txController, 'signTransaction') .stub(txController, '_signTransaction')
.callsFake(() => Promise.resolve()); .callsFake(() => Promise.resolve());
const pubStub = sinon const pubStub = sinon
.stub(txController, 'publishTransaction') .stub(txController, '_publishTransaction')
.callsFake(() => { .callsFake(() => {
const txId = getLastTxMeta().id; const txId = getLastTxMeta().id;
txController.setTxHash(txId, originalValue); txController.setTxHash(txId, originalValue);
@ -1208,7 +1212,7 @@ describe('Transaction Controller', function () {
}); });
}); });
describe('#addTxGasDefaults', function () { describe('_addTxGasDefaults', function () {
it('should add the tx defaults if their are none', async function () { it('should add the tx defaults if their are none', async function () {
txController.txStateManager._addTransactionsToState([ txController.txStateManager._addTransactionsToState([
{ {
@ -1234,7 +1238,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }; providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
providerResultStub.eth_estimateGas = '5209'; providerResultStub.eth_estimateGas = '5209';
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta); const txMetaWithDefaults = await txController._addTxGasDefaults(txMeta);
assert.ok( assert.ok(
txMetaWithDefaults.txParams.gasPrice, txMetaWithDefaults.txParams.gasPrice,
'should have added the gas price', 'should have added the gas price',
@ -1250,7 +1254,7 @@ describe('Transaction Controller', function () {
const TEST_MAX_PRIORITY_FEE_PER_GAS = '0x77359400'; const TEST_MAX_PRIORITY_FEE_PER_GAS = '0x77359400';
const stub1 = sinon const stub1 = sinon
.stub(txController, 'getEIP1559Compatibility') .stub(txController, '_getEIP1559Compatibility')
.returns(true); .returns(true);
const stub2 = sinon const stub2 = sinon
@ -1283,7 +1287,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }; providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
providerResultStub.eth_estimateGas = '5209'; providerResultStub.eth_estimateGas = '5209';
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta); const txMetaWithDefaults = await txController._addTxGasDefaults(txMeta);
assert.equal( assert.equal(
txMetaWithDefaults.txParams.maxFeePerGas, txMetaWithDefaults.txParams.maxFeePerGas,
@ -1303,7 +1307,7 @@ describe('Transaction Controller', function () {
const TEST_GASPRICE = '0x12a05f200'; const TEST_GASPRICE = '0x12a05f200';
const stub1 = sinon const stub1 = sinon
.stub(txController, 'getEIP1559Compatibility') .stub(txController, '_getEIP1559Compatibility')
.returns(true); .returns(true);
const stub2 = sinon const stub2 = sinon
@ -1333,7 +1337,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }; providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
providerResultStub.eth_estimateGas = '5209'; providerResultStub.eth_estimateGas = '5209';
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta); const txMetaWithDefaults = await txController._addTxGasDefaults(txMeta);
assert.equal( assert.equal(
txMetaWithDefaults.txParams.maxFeePerGas, txMetaWithDefaults.txParams.maxFeePerGas,
@ -1353,7 +1357,7 @@ describe('Transaction Controller', function () {
const TEST_GASPRICE = '0x12a05f200'; const TEST_GASPRICE = '0x12a05f200';
const stub1 = sinon const stub1 = sinon
.stub(txController, 'getEIP1559Compatibility') .stub(txController, '_getEIP1559Compatibility')
.returns(true); .returns(true);
const stub2 = sinon const stub2 = sinon
@ -1385,7 +1389,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }; providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
providerResultStub.eth_estimateGas = '5209'; providerResultStub.eth_estimateGas = '5209';
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta); const txMetaWithDefaults = await txController._addTxGasDefaults(txMeta);
assert.equal( assert.equal(
txMetaWithDefaults.txParams.maxFeePerGas, txMetaWithDefaults.txParams.maxFeePerGas,
@ -1407,7 +1411,7 @@ describe('Transaction Controller', function () {
const TEST_MAX_PRIORITY_FEE_PER_GAS = '0x77359400'; const TEST_MAX_PRIORITY_FEE_PER_GAS = '0x77359400';
const stub1 = sinon const stub1 = sinon
.stub(txController, 'getEIP1559Compatibility') .stub(txController, '_getEIP1559Compatibility')
.returns(true); .returns(true);
const stub2 = sinon const stub2 = sinon
@ -1439,7 +1443,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }; providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
providerResultStub.eth_estimateGas = '5209'; providerResultStub.eth_estimateGas = '5209';
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta); const txMetaWithDefaults = await txController._addTxGasDefaults(txMeta);
assert.equal( assert.equal(
txMetaWithDefaults.txParams.maxFeePerGas, txMetaWithDefaults.txParams.maxFeePerGas,
@ -1543,27 +1547,27 @@ describe('Transaction Controller', function () {
}, },
noop, noop,
); );
const rawTx = await txController.signTransaction('1'); const rawTx = await txController._signTransaction('1');
const ethTx = TransactionFactory.fromSerializedData(toBuffer(rawTx)); const ethTx = TransactionFactory.fromSerializedData(toBuffer(rawTx));
assert.equal(Number(ethTx.common.chainId()), 5); assert.equal(Number(ethTx.common.chainId()), 5);
}); });
}); });
describe('#getChainId', function () { describe('_getChainId', function () {
it('returns the chain ID of the network when it is available', function () { it('returns the chain ID of the network when it is available', function () {
networkStatusStore.putState(NetworkStatus.Available); networkStatusStore.putState(NetworkStatus.Available);
assert.equal(txController.getChainId(), 5); assert.equal(txController._getChainId(), 5);
}); });
it('returns 0 when the network is not available', function () { it('returns 0 when the network is not available', function () {
networkStatusStore.putState('asdflsfadf'); networkStatusStore.putState('NOT_INTEGER');
assert.equal(txController.getChainId(), 0); assert.equal(txController._getChainId(), 0);
}); });
it('returns 0 when the chain ID cannot be parsed as a hex string', function () { it('returns 0 when the chain ID cannot be parsed as a hex string', function () {
networkStatusStore.putState(NetworkStatus.Available); networkStatusStore.putState(NetworkStatus.Available);
getCurrentChainId.returns('$fdsjfldf'); getCurrentChainId.returns('NOT_INTEGER');
assert.equal(txController.getChainId(), 0); assert.equal(txController._getChainId(), 0);
}); });
}); });
@ -1791,13 +1795,13 @@ describe('Transaction Controller', function () {
}, },
}, },
]); ]);
await txController.signTransaction('1'); await txController._signTransaction('1');
assert.equal(fromTxDataSpy.getCall(0).args[0].type, '0x0'); assert.equal(fromTxDataSpy.getCall(0).args[0].type, '0x0');
}); });
it('sets txParams.type to 0x2 (EIP-1559)', async function () { it('sets txParams.type to 0x2 (EIP-1559)', async function () {
const eip1559CompatibilityStub = sinon const eip1559CompatibilityStub = sinon
.stub(txController, 'getEIP1559Compatibility') .stub(txController, '_getEIP1559Compatibility')
.returns(true); .returns(true);
txController.txStateManager._addTransactionsToState([ txController.txStateManager._addTransactionsToState([
{ {
@ -1815,13 +1819,13 @@ describe('Transaction Controller', function () {
}, },
}, },
]); ]);
await txController.signTransaction('2'); await txController._signTransaction('2');
assert.equal(fromTxDataSpy.getCall(0).args[0].type, '0x2'); assert.equal(fromTxDataSpy.getCall(0).args[0].type, '0x2');
eip1559CompatibilityStub.restore(); eip1559CompatibilityStub.restore();
}); });
}); });
describe('#publishTransaction', function () { describe('_publishTransaction', function () {
let hash, txMeta, trackTransactionMetricsEventSpy; let hash, txMeta, trackTransactionMetricsEventSpy;
beforeEach(function () { beforeEach(function () {
@ -1852,7 +1856,7 @@ describe('Transaction Controller', function () {
const rawTx = const rawTx =
'0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c'; '0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c';
txController.txStateManager.addTransaction(txMeta); txController.txStateManager.addTransaction(txMeta);
await txController.publishTransaction(txMeta.id, rawTx); await txController._publishTransaction(txMeta.id, rawTx);
const publishedTx = txController.txStateManager.getTransaction(1); const publishedTx = txController.txStateManager.getTransaction(1);
assert.equal(publishedTx.hash, hash); assert.equal(publishedTx.hash, hash);
assert.equal(publishedTx.status, TransactionStatus.submitted); assert.equal(publishedTx.status, TransactionStatus.submitted);
@ -1865,7 +1869,7 @@ describe('Transaction Controller', function () {
const rawTx = const rawTx =
'0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a'; '0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a';
txController.txStateManager.addTransaction(txMeta); txController.txStateManager.addTransaction(txMeta);
await txController.publishTransaction(txMeta.id, rawTx); await txController._publishTransaction(txMeta.id, rawTx);
const publishedTx = txController.txStateManager.getTransaction(1); const publishedTx = txController.txStateManager.getTransaction(1);
assert.equal( assert.equal(
publishedTx.hash, publishedTx.hash,
@ -1878,7 +1882,7 @@ describe('Transaction Controller', function () {
const rawTx = const rawTx =
'0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c'; '0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c';
txController.txStateManager.addTransaction(txMeta); txController.txStateManager.addTransaction(txMeta);
await txController.publishTransaction(txMeta.id, rawTx); await txController._publishTransaction(txMeta.id, rawTx);
assert.equal(trackTransactionMetricsEventSpy.callCount, 1); assert.equal(trackTransactionMetricsEventSpy.callCount, 1);
assert.deepEqual( assert.deepEqual(
trackTransactionMetricsEventSpy.getCall(0).args[0], trackTransactionMetricsEventSpy.getCall(0).args[0],
@ -2163,6 +2167,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A', device_model: 'N/A',
transaction_speed_up: false, transaction_speed_up: false,
ui_customizations: null, ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
}, },
sensitiveProperties: { sensitiveProperties: {
default_gas: '0.000031501', default_gas: '0.000031501',
@ -2173,7 +2180,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined, transaction_replaced: undefined,
first_seen: 1624408066355, first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
}, },
}; };
@ -2250,6 +2256,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A', device_model: 'N/A',
transaction_speed_up: false, transaction_speed_up: false,
ui_customizations: null, ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
}, },
sensitiveProperties: { sensitiveProperties: {
default_gas: '0.000031501', default_gas: '0.000031501',
@ -2260,7 +2269,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined, transaction_replaced: undefined,
first_seen: 1624408066355, first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
}, },
}; };
@ -2349,6 +2357,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A', device_model: 'N/A',
transaction_speed_up: false, transaction_speed_up: false,
ui_customizations: null, ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
}, },
sensitiveProperties: { sensitiveProperties: {
default_gas: '0.000031501', default_gas: '0.000031501',
@ -2359,7 +2370,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined, transaction_replaced: undefined,
first_seen: 1624408066355, first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
}, },
}; };
@ -2438,6 +2448,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A', device_model: 'N/A',
transaction_speed_up: false, transaction_speed_up: false,
ui_customizations: null, ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
}, },
sensitiveProperties: { sensitiveProperties: {
default_gas: '0.000031501', default_gas: '0.000031501',
@ -2448,7 +2461,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined, transaction_replaced: undefined,
first_seen: 1624408066355, first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
}, },
}; };
@ -2505,6 +2517,10 @@ describe('Transaction Controller', function () {
securityProviderResponse: { securityProviderResponse: {
flagAsDangerous: 0, flagAsDangerous: 0,
}, },
securityAlertResponse: {
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
},
}; };
const expectedPayload = { const expectedPayload = {
@ -2529,6 +2545,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A', device_model: 'N/A',
transaction_speed_up: false, transaction_speed_up: false,
ui_customizations: null, ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
}, },
sensitiveProperties: { sensitiveProperties: {
gas_price: '2', gas_price: '2',
@ -2537,7 +2556,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined, transaction_replaced: undefined,
first_seen: 1624408066355, first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
}, },
}; };
await txController._trackTransactionMetricsEvent( await txController._trackTransactionMetricsEvent(
@ -2601,6 +2619,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A', device_model: 'N/A',
transaction_speed_up: false, transaction_speed_up: false,
ui_customizations: null, ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
}, },
sensitiveProperties: { sensitiveProperties: {
baz: 3.0, baz: 3.0,
@ -2611,7 +2632,83 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined, transaction_replaced: undefined,
first_seen: 1624408066355, first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
},
};
await txController._trackTransactionMetricsEvent(
txMeta,
TransactionMetaMetricsEvent.added,
actionId,
{
baz: 3.0,
foo: 'bar',
},
);
assert.equal(createEventFragmentSpy.callCount, 1);
assert.equal(finalizeEventFragmentSpy.callCount, 0);
assert.deepEqual(
createEventFragmentSpy.getCall(0).args[0],
expectedPayload,
);
});
it('should call _trackMetaMetricsEvent with the correct payload when blockaid verification fails', async function () {
const txMeta = {
id: 1,
status: TransactionStatus.unapproved,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: TransactionType.simpleSend,
origin: 'other',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
securityAlertResponse: {
result_type: BlockaidResultType.Failed,
reason: 'some error',
},
};
const expectedPayload = {
actionId,
initialEvent: 'Transaction Added',
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
persist: true,
category: MetaMetricsEventCategory.Transactions,
properties: {
network: '5',
referrer: 'other',
source: MetaMetricsTransactionEventSource.Dapp,
status: 'unapproved', status: 'unapproved',
transaction_type: TransactionType.simpleSend,
chain_id: '0x5',
eip_1559_version: '0',
gas_edit_attempted: 'none',
gas_edit_type: 'none',
account_type: 'MetaMask',
asset_type: AssetType.native,
token_standard: TokenStandard.none,
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: ['security_alert_failed'],
security_alert_reason: 'some error',
security_alert_response: BlockaidResultType.Failed,
},
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,
}, },
}; };
@ -2675,6 +2772,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A', device_model: 'N/A',
transaction_speed_up: false, transaction_speed_up: false,
ui_customizations: ['flagged_as_malicious'], ui_customizations: ['flagged_as_malicious'],
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
}, },
sensitiveProperties: { sensitiveProperties: {
baz: 3.0, baz: 3.0,
@ -2685,7 +2785,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined, transaction_replaced: undefined,
first_seen: 1624408066355, first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
}, },
}; };
@ -2749,6 +2848,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A', device_model: 'N/A',
transaction_speed_up: false, transaction_speed_up: false,
ui_customizations: ['flagged_as_safety_unknown'], ui_customizations: ['flagged_as_safety_unknown'],
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
}, },
sensitiveProperties: { sensitiveProperties: {
baz: 3.0, baz: 3.0,
@ -2759,7 +2861,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined, transaction_replaced: undefined,
first_seen: 1624408066355, first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
}, },
}; };
@ -2831,6 +2932,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A', device_model: 'N/A',
transaction_speed_up: false, transaction_speed_up: false,
ui_customizations: null, ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
}, },
sensitiveProperties: { sensitiveProperties: {
baz: 3.0, baz: 3.0,
@ -2842,7 +2946,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined, transaction_replaced: undefined,
first_seen: 1624408066355, first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET, transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET,
status: 'unapproved',
estimate_suggested: GasRecommendations.medium, estimate_suggested: GasRecommendations.medium,
estimate_used: GasRecommendations.high, estimate_used: GasRecommendations.high,
default_estimate: 'medium', default_estimate: 'medium',

View File

@ -13,6 +13,7 @@ export default function setupEnsIpfsResolver({
provider, provider,
getCurrentChainId, getCurrentChainId,
getIpfsGateway, getIpfsGateway,
getUseAddressBarEnsResolution,
}) { }) {
// install listener // install listener
const urlPatterns = supportedTopLevelDomains.map((tld) => `*://*.${tld}/*`); const urlPatterns = supportedTopLevelDomains.map((tld) => `*://*.${tld}/*`);
@ -33,7 +34,12 @@ export default function setupEnsIpfsResolver({
const { tabId, url } = details; const { tabId, url } = details;
// ignore requests that are not associated with tabs // ignore requests that are not associated with tabs
// only attempt ENS resolution on mainnet // only attempt ENS resolution on mainnet
if (tabId === -1 || getCurrentChainId() !== '0x1') { if (
(tabId === -1 || getCurrentChainId() !== '0x1') &&
// E2E tests use a chain other than 0x1, so for testing,
// allow the reuqest to pass through
!process.env.IN_TEST
) {
return; return;
} }
// parse ens name // parse ens name
@ -50,9 +56,23 @@ export default function setupEnsIpfsResolver({
async function attemptResolve({ tabId, name, pathname, search, fragment }) { async function attemptResolve({ tabId, name, pathname, search, fragment }) {
const ipfsGateway = getIpfsGateway(); const ipfsGateway = getIpfsGateway();
const useAddressBarEnsResolution = getUseAddressBarEnsResolution();
if (!useAddressBarEnsResolution || ipfsGateway === '') {
return;
}
browser.tabs.update(tabId, { url: `loading.html` }); browser.tabs.update(tabId, { url: `loading.html` });
let url = `https://app.ens.domains/name/${name}`; let url = `https://app.ens.domains/name/${name}`;
// If we're testing ENS domain resolution support,
// we assume the ENS domains URL
if (process.env.IN_TEST) {
browser.tabs.update(tabId, { url });
return;
}
try { try {
const { type, hash } = await resolveEnsToIpfsContentId({ const { type, hash } = await resolveEnsToIpfsContentId({
provider, provider,

View File

@ -1,3 +1,7 @@
import {
BlockaidReason,
BlockaidResultType,
} from '../../../../shared/constants/security-provider';
import { createPPOMMiddleware } from './ppom-middleware'; import { createPPOMMiddleware } from './ppom-middleware';
Object.defineProperty(globalThis, 'fetch', { Object.defineProperty(globalThis, 'fetch', {
@ -13,10 +17,16 @@ Object.defineProperty(globalThis, 'performance', {
describe('PPOMMiddleware', () => { describe('PPOMMiddleware', () => {
it('should call ppomController.usePPOM for requests of type confirmation', async () => { it('should call ppomController.usePPOM for requests of type confirmation', async () => {
const useMock = jest.fn(); const useMock = jest.fn();
const controller = { const ppomController = {
usePPOM: useMock, usePPOM: useMock,
}; };
const middlewareFunction = createPPOMMiddleware(controller as any); const preferenceController = {
store: { getState: () => ({ securityAlertsEnabled: true }) },
};
const middlewareFunction = createPPOMMiddleware(
ppomController as any,
preferenceController as any,
);
await middlewareFunction( await middlewareFunction(
{ method: 'eth_sendTransaction' }, { method: 'eth_sendTransaction' },
undefined, undefined,
@ -26,25 +36,85 @@ describe('PPOMMiddleware', () => {
}); });
it('should add validation response on confirmation requests', async () => { it('should add validation response on confirmation requests', async () => {
const controller = { const ppomController = {
usePPOM: async () => Promise.resolve('VALIDATION_RESULT'), usePPOM: async () => Promise.resolve('VALIDATION_RESULT'),
}; };
const middlewareFunction = createPPOMMiddleware(controller as any); const preferenceController = {
const req = { method: 'eth_sendTransaction', ppomResponse: undefined }; store: { getState: () => ({ securityAlertsEnabled: true }) },
};
const middlewareFunction = createPPOMMiddleware(
ppomController as any,
preferenceController as any,
);
const req = {
method: 'eth_sendTransaction',
securityAlertResponse: undefined,
};
await middlewareFunction(req, undefined, () => undefined); await middlewareFunction(req, undefined, () => undefined);
expect(req.ppomResponse).toBeDefined(); expect(req.securityAlertResponse).toBeDefined();
});
it('should not do validation if user has not enabled preference', async () => {
const ppomController = {
usePPOM: async () => Promise.resolve('VALIDATION_RESULT'),
};
const preferenceController = {
store: { getState: () => ({ securityAlertsEnabled: false }) },
};
const middlewareFunction = createPPOMMiddleware(
ppomController as any,
preferenceController as any,
);
const req = {
method: 'eth_sendTransaction',
securityAlertResponse: undefined,
};
await middlewareFunction(req, undefined, () => undefined);
expect(req.securityAlertResponse).toBeUndefined();
});
it('should set Failed type in response if usePPOM throw error', async () => {
const ppomController = {
usePPOM: async () => {
throw new Error('some error');
},
};
const preferenceController = {
store: { getState: () => ({ securityAlertsEnabled: true }) },
};
const middlewareFunction = createPPOMMiddleware(
ppomController as any,
preferenceController as any,
);
const req = {
method: 'eth_sendTransaction',
securityAlertResponse: undefined,
};
await middlewareFunction(req, undefined, () => undefined);
expect((req.securityAlertResponse as any)?.result_type).toBe(
BlockaidResultType.Failed,
);
expect((req.securityAlertResponse as any)?.reason).toBe(
BlockaidReason.failed,
);
}); });
it('should call next method when ppomController.usePPOM completes', async () => { it('should call next method when ppomController.usePPOM completes', async () => {
const ppom = { const ppom = {
validateJsonRpc: () => undefined, validateJsonRpc: () => undefined,
}; };
const controller = { const ppomController = {
usePPOM: async (callback: any) => { usePPOM: async (callback: any) => {
callback(ppom); callback(ppom);
}, },
}; };
const middlewareFunction = createPPOMMiddleware(controller as any); const preferenceController = {
store: { getState: () => ({ securityAlertsEnabled: true }) },
};
const middlewareFunction = createPPOMMiddleware(
ppomController as any,
preferenceController as any,
);
const nextMock = jest.fn(); const nextMock = jest.fn();
await middlewareFunction( await middlewareFunction(
{ method: 'eth_sendTransaction' }, { method: 'eth_sendTransaction' },
@ -55,12 +125,18 @@ describe('PPOMMiddleware', () => {
}); });
it('should call next method when ppomController.usePPOM throws error', async () => { it('should call next method when ppomController.usePPOM throws error', async () => {
const controller = { const ppomController = {
usePPOM: async (_callback: any) => { usePPOM: async (_callback: any) => {
throw Error('Some error'); throw Error('Some error');
}, },
}; };
const middlewareFunction = createPPOMMiddleware(controller as any); const preferenceController = {
store: { getState: () => ({ securityAlertsEnabled: true }) },
};
const middlewareFunction = createPPOMMiddleware(
ppomController as any,
preferenceController as any,
);
const nextMock = jest.fn(); const nextMock = jest.fn();
await middlewareFunction( await middlewareFunction(
{ method: 'eth_sendTransaction' }, { method: 'eth_sendTransaction' },
@ -75,12 +151,18 @@ describe('PPOMMiddleware', () => {
const ppom = { const ppom = {
validateJsonRpc: validateMock, validateJsonRpc: validateMock,
}; };
const controller = { const ppomController = {
usePPOM: async (callback: any) => { usePPOM: async (callback: any) => {
callback(ppom); callback(ppom);
}, },
}; };
const middlewareFunction = createPPOMMiddleware(controller as any); const preferenceController = {
store: { getState: () => ({ securityAlertsEnabled: true }) },
};
const middlewareFunction = createPPOMMiddleware(
ppomController as any,
preferenceController as any,
);
await middlewareFunction( await middlewareFunction(
{ method: 'eth_sendTransaction' }, { method: 'eth_sendTransaction' },
undefined, undefined,
@ -94,12 +176,18 @@ describe('PPOMMiddleware', () => {
const ppom = { const ppom = {
validateJsonRpc: validateMock, validateJsonRpc: validateMock,
}; };
const controller = { const ppomController = {
usePPOM: async (callback: any) => { usePPOM: async (callback: any) => {
callback(ppom); callback(ppom);
}, },
}; };
const middlewareFunction = createPPOMMiddleware(controller as any); const preferenceController = {
store: { getState: () => ({ securityAlertsEnabled: true }) },
};
const middlewareFunction = createPPOMMiddleware(
ppomController as any,
preferenceController as any,
);
await middlewareFunction( await middlewareFunction(
{ method: 'eth_someRequest' }, { method: 'eth_someRequest' },
undefined, undefined,

View File

@ -1,7 +1,14 @@
import { PPOM } from '@blockaid/ppom'; import { PPOM } from '@blockaid/ppom';
import { PPOMController } from '@metamask/ppom-validator'; import { PPOMController } from '@metamask/ppom-validator';
import {
BlockaidReason,
BlockaidResultType,
} from '../../../../shared/constants/security-provider';
import PreferencesController from '../../controllers/preferences';
const { sentry } = global as any;
const ConfirmationMethods = Object.freeze([ const ConfirmationMethods = Object.freeze([
'eth_sendRawTransaction', 'eth_sendRawTransaction',
'eth_sendTransaction', 'eth_sendTransaction',
@ -23,19 +30,33 @@ const ConfirmationMethods = Object.freeze([
* the request will be forwarded to the next middleware, together with the PPOM response. * the request will be forwarded to the next middleware, together with the PPOM response.
* *
* @param ppomController - Instance of PPOMController. * @param ppomController - Instance of PPOMController.
* @param preferencesController - Instance of PreferenceController.
* @returns PPOMMiddleware function. * @returns PPOMMiddleware function.
*/ */
export function createPPOMMiddleware(ppomController: PPOMController) { export function createPPOMMiddleware(
ppomController: PPOMController,
preferencesController: PreferencesController,
) {
return async (req: any, _res: any, next: () => void) => { return async (req: any, _res: any, next: () => void) => {
try { try {
if (ConfirmationMethods.includes(req.method)) { const securityAlertsEnabled =
preferencesController.store.getState()?.securityAlertsEnabled;
if (securityAlertsEnabled && ConfirmationMethods.includes(req.method)) {
// eslint-disable-next-line require-atomic-updates // eslint-disable-next-line require-atomic-updates
req.ppomResponse = await ppomController.usePPOM(async (ppom: PPOM) => { req.securityAlertResponse = await ppomController.usePPOM(
return ppom.validateJsonRpc(req); async (ppom: PPOM) => {
}); return ppom.validateJsonRpc(req);
},
);
} }
} catch (error: unknown) { } catch (error: any) {
sentry?.captureException(error);
console.error('Error validating JSON RPC using PPOM: ', error); console.error('Error validating JSON RPC using PPOM: ', error);
req.securityAlertResponse = {
result_type: BlockaidResultType.Failed,
reason: BlockaidReason.failed,
description: 'Validating the confirmation failed by throwing error.',
};
} finally { } finally {
next(); next();
} }

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@ export class FilterEvents implements Integration {
* @returns `true` if MetaMask's state has been initialized, and MetaMetrics * @returns `true` if MetaMask's state has been initialized, and MetaMetrics
* is enabled, `false` otherwise. * is enabled, `false` otherwise.
*/ */
private getMetaMetricsEnabled: () => boolean; private getMetaMetricsEnabled: () => Promise<boolean>;
/** /**
* @param options - Constructor options. * @param options - Constructor options.
@ -40,7 +40,7 @@ export class FilterEvents implements Integration {
constructor({ constructor({
getMetaMetricsEnabled, getMetaMetricsEnabled,
}: { }: {
getMetaMetricsEnabled: () => boolean; getMetaMetricsEnabled: () => Promise<boolean>;
}) { }) {
this.getMetaMetricsEnabled = getMetaMetricsEnabled; this.getMetaMetricsEnabled = getMetaMetricsEnabled;
} }
@ -56,13 +56,13 @@ export class FilterEvents implements Integration {
addGlobalEventProcessor: (callback: EventProcessor) => void, addGlobalEventProcessor: (callback: EventProcessor) => void,
getCurrentHub: () => Hub, getCurrentHub: () => Hub,
): void { ): void {
addGlobalEventProcessor((currentEvent: SentryEvent) => { addGlobalEventProcessor(async (currentEvent: SentryEvent) => {
// Sentry integrations use the Sentry hub to get "this" references, for // Sentry integrations use the Sentry hub to get "this" references, for
// reasons I don't fully understand. // reasons I don't fully understand.
// eslint-disable-next-line consistent-this // eslint-disable-next-line consistent-this
const self = getCurrentHub().getIntegration(FilterEvents); const self = getCurrentHub().getIntegration(FilterEvents);
if (self) { if (self) {
if (!self.getMetaMetricsEnabled()) { if (!(await self.getMetaMetricsEnabled())) {
logger.warn(`Event dropped due to MetaMetrics setting.`); logger.warn(`Event dropped due to MetaMetrics setting.`);
return null; return null;
} }

View File

@ -0,0 +1,10 @@
import LocalStore from './local-store';
import ReadOnlyNetworkStore from './network-store';
const localStore = process.env.IN_TEST
? new ReadOnlyNetworkStore()
: new LocalStore();
globalThis.stateHooks.getPersistedState = async function () {
return await localStore.get();
};

View File

@ -118,16 +118,20 @@ export default function setupSentry({ release, getState }) {
* @returns `true` if MetaMask's state has been initialized, and MetaMetrics * @returns `true` if MetaMask's state has been initialized, and MetaMetrics
* is enabled, `false` otherwise. * is enabled, `false` otherwise.
*/ */
function getMetaMetricsEnabled() { async function getMetaMetricsEnabled() {
if (getState) { const appState = getState();
const appState = getState(); if (Object.keys(appState) > 0) {
if (!appState?.store?.metamask?.participateInMetaMetrics) { return Boolean(appState?.store?.metamask?.participateInMetaMetrics);
return false; }
} try {
} else { const persistedState = await globalThis.stateHooks.getPersistedState();
return Boolean(
persistedState?.data?.MetaMetricsController?.participateInMetaMetrics,
);
} catch (error) {
console.error(error);
return false; return false;
} }
return true;
} }
Sentry.init({ Sentry.init({
@ -186,10 +190,10 @@ export default function setupSentry({ release, getState }) {
* opted into MetaMetrics, change the autoSessionTracking option and start * opted into MetaMetrics, change the autoSessionTracking option and start
* a new sentry session. * a new sentry session.
*/ */
const startSession = () => { const startSession = async () => {
const hub = Sentry.getCurrentHub?.(); const hub = Sentry.getCurrentHub?.();
const options = hub.getClient?.().getOptions?.() ?? {}; const options = hub.getClient?.().getOptions?.() ?? {};
if (hub && getMetaMetricsEnabled() === true) { if (hub && (await getMetaMetricsEnabled()) === true) {
options.autoSessionTracking = true; options.autoSessionTracking = true;
hub.startSession(); hub.startSession();
} }
@ -200,10 +204,10 @@ export default function setupSentry({ release, getState }) {
* opted out of MetaMetrics, change the autoSessionTracking option and end * opted out of MetaMetrics, change the autoSessionTracking option and end
* the current sentry session. * the current sentry session.
*/ */
const endSession = () => { const endSession = async () => {
const hub = Sentry.getCurrentHub?.(); const hub = Sentry.getCurrentHub?.();
const options = hub.getClient?.().getOptions?.() ?? {}; const options = hub.getClient?.().getOptions?.() ?? {};
if (hub && getMetaMetricsEnabled() === false) { if (hub && (await getMetaMetricsEnabled()) === false) {
options.autoSessionTracking = false; options.autoSessionTracking = false;
hub.endSession(); hub.endSession();
} }
@ -214,22 +218,22 @@ export default function setupSentry({ release, getState }) {
* on the state of metaMetrics optin and the state of autoSessionTracking on * on the state of metaMetrics optin and the state of autoSessionTracking on
* the Sentry client. * the Sentry client.
*/ */
const toggleSession = () => { const toggleSession = async () => {
const hub = Sentry.getCurrentHub?.(); const hub = Sentry.getCurrentHub?.();
const options = hub.getClient?.().getOptions?.() ?? { const options = hub.getClient?.().getOptions?.() ?? {
autoSessionTracking: false, autoSessionTracking: false,
}; };
const isMetaMetricsEnabled = getMetaMetricsEnabled(); const isMetaMetricsEnabled = await getMetaMetricsEnabled();
if ( if (
isMetaMetricsEnabled === true && isMetaMetricsEnabled === true &&
options.autoSessionTracking === false options.autoSessionTracking === false
) { ) {
startSession(); await startSession();
} else if ( } else if (
isMetaMetricsEnabled === false && isMetaMetricsEnabled === false &&
options.autoSessionTracking === true options.autoSessionTracking === true
) { ) {
endSession(); await endSession();
} }
}; };

View File

@ -1,7 +1,14 @@
import { strict as assert } from 'assert'; import { strict as assert } from 'assert';
import sinon from 'sinon'; import sinon from 'sinon';
import proxyquire from 'proxyquire'; import proxyquire from 'proxyquire';
import {
ListNames,
METAMASK_STALELIST_URL,
METAMASK_HOTLIST_DIFF_URL,
PHISHING_CONFIG_BASE_URL,
METAMASK_STALELIST_FILE,
METAMASK_HOTLIST_DIFF_FILE,
} from '@metamask/phishing-controller';
import { ApprovalRequestNotFoundError } from '@metamask/approval-controller'; import { ApprovalRequestNotFoundError } from '@metamask/approval-controller';
import { PermissionsRequestNotFoundError } from '@metamask/permission-controller'; import { PermissionsRequestNotFoundError } from '@metamask/permission-controller';
import nock from 'nock'; import nock from 'nock';
@ -59,21 +66,28 @@ describe('MetaMaskController', function () {
}); });
beforeEach(function () { beforeEach(function () {
nock('https://static.metafi.codefi.network') nock(PHISHING_CONFIG_BASE_URL)
.persist() .persist()
.get('/api/v1/lists/stalelist.json') .get(METAMASK_STALELIST_FILE)
.reply( .reply(
200, 200,
JSON.stringify({ JSON.stringify({
version: 2, version: 2,
tolerance: 2, tolerance: 2,
fuzzylist: [], lastUpdated: 1,
allowlist: [], eth_phishing_detect_config: {
blocklist: ['127.0.0.1'], fuzzylist: [],
lastUpdated: 0, allowlist: [],
blocklist: ['127.0.0.1'],
name: ListNames.MetaMask,
},
phishfort_hotlist: {
blocklist: [],
name: ListNames.Phishfort,
},
}), }),
) )
.get('/api/v1/lists/hotlist.json') .get(METAMASK_HOTLIST_DIFF_FILE)
.reply( .reply(
200, 200,
JSON.stringify([ JSON.stringify([
@ -110,6 +124,20 @@ describe('MetaMaskController', function () {
await ganacheServer.quit(); await ganacheServer.quit();
}); });
describe('Phishing Detection Mock', function () {
it('should be updated to use v1 of the API', function () {
// Update the fixture above if this test fails
assert.equal(
METAMASK_STALELIST_URL,
'https://phishing-detection.metafi.codefi.network/v1/stalelist',
);
assert.equal(
METAMASK_HOTLIST_DIFF_URL,
'https://phishing-detection.metafi.codefi.network/v1/diffsSince',
);
});
});
describe('#addNewAccount', function () { describe('#addNewAccount', function () {
it('two parallel calls with same accountCount give same result', async function () { it('two parallel calls with same accountCount give same result', async function () {
await metamaskController.createNewVaultAndKeychain('test@123'); await metamaskController.createNewVaultAndKeychain('test@123');

View File

@ -656,6 +656,7 @@ export default class MetamaskController extends EventEmitter {
this.preferencesController.store, this.preferencesController.store,
), ),
cdnBaseUrl: process.env.BLOCKAID_FILE_CDN, cdnBaseUrl: process.env.BLOCKAID_FILE_CDN,
blockaidPublicKey: process.env.BLOCKAID_PUBLIC_KEY,
}); });
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
@ -904,9 +905,8 @@ export default class MetamaskController extends EventEmitter {
(address) => !identities[address], (address) => !identities[address],
); );
const keyringTypesWithMissingIdentities = const keyringTypesWithMissingIdentities =
accountsMissingIdentities.map( accountsMissingIdentities.map((address) =>
(address) => this.coreKeyringController.getAccountKeyringType(address),
this.keyringController.getKeyringForAccount(address)?.type,
); );
const identitiesCount = Object.keys(identities || {}).length; const identitiesCount = Object.keys(identities || {}).length;
@ -962,6 +962,8 @@ export default class MetamaskController extends EventEmitter {
'ExecutionService:unhandledError', 'ExecutionService:unhandledError',
'ExecutionService:outboundRequest', 'ExecutionService:outboundRequest',
'ExecutionService:outboundResponse', 'ExecutionService:outboundResponse',
'SnapController:snapInstalled',
'SnapController:snapUpdated',
], ],
allowedActions: [ allowedActions: [
`${this.permissionController.name}:getEndowments`, `${this.permissionController.name}:getEndowments`,
@ -985,6 +987,7 @@ export default class MetamaskController extends EventEmitter {
'ExecutionService:handleRpcRequest', 'ExecutionService:handleRpcRequest',
'SnapsRegistry:get', 'SnapsRegistry:get',
'SnapsRegistry:getMetadata', 'SnapsRegistry:getMetadata',
'SnapsRegistry:update',
], ],
}); });
@ -1165,7 +1168,10 @@ export default class MetamaskController extends EventEmitter {
getCurrentAccountEIP1559Compatibility: getCurrentAccountEIP1559Compatibility:
this.getCurrentAccountEIP1559Compatibility.bind(this), this.getCurrentAccountEIP1559Compatibility.bind(this),
getNetworkId: () => this.networkController.state.networkId, getNetworkId: () => this.networkController.state.networkId,
getNetworkStatus: () => this.networkController.state.networkStatus, getNetworkStatus: () =>
this.networkController.state.networksMetadata?.[
this.networkController.state.selectedNetworkClientId
]?.status,
onNetworkStateChange: (listener) => { onNetworkStateChange: (listener) => {
networkControllerMessenger.subscribe( networkControllerMessenger.subscribe(
'NetworkController:stateChange', 'NetworkController:stateChange',
@ -1347,7 +1353,14 @@ export default class MetamaskController extends EventEmitter {
`${this.approvalController.name}:rejectRequest`, `${this.approvalController.name}:rejectRequest`,
], ],
}), }),
keyringController: this.keyringController, getEncryptionPublicKey:
this.keyringController.getEncryptionPublicKey.bind(
this.keyringController,
),
getAccountKeyringType:
this.coreKeyringController.getAccountKeyringType.bind(
this.coreKeyringController,
),
getState: this.getState.bind(this), getState: this.getState.bind(this),
metricsEvent: this.metaMetricsController.trackEvent.bind( metricsEvent: this.metaMetricsController.trackEvent.bind(
this.metaMetricsController, this.metaMetricsController,
@ -1809,7 +1822,7 @@ export default class MetamaskController extends EventEmitter {
*/ */
async getSnapKeyring() { async getSnapKeyring() {
if (!this.snapKeyring) { if (!this.snapKeyring) {
let [snapKeyring] = this.keyringController.getKeyringsByType( let [snapKeyring] = this.coreKeyringController.getKeyringsByType(
KeyringType.snap, KeyringType.snap,
); );
if (!snapKeyring) { if (!snapKeyring) {
@ -2079,8 +2092,10 @@ export default class MetamaskController extends EventEmitter {
updatePublicConfigStore(this.getState()); updatePublicConfigStore(this.getState());
function updatePublicConfigStore(memState) { function updatePublicConfigStore(memState) {
const networkStatus =
memState.networksMetadata[memState.selectedNetworkClientId]?.status;
const { chainId } = networkController.state.providerConfig; const { chainId } = networkController.state.providerConfig;
if (memState.networkStatus === NetworkStatus.Available) { if (networkStatus === NetworkStatus.Available) {
publicConfigStore.putState(selectPublicState(chainId, memState)); publicConfigStore.putState(selectPublicState(chainId, memState));
} }
} }
@ -2215,6 +2230,9 @@ export default class MetamaskController extends EventEmitter {
setUseNftDetection: preferencesController.setUseNftDetection.bind( setUseNftDetection: preferencesController.setUseNftDetection.bind(
preferencesController, preferencesController,
), ),
setUse4ByteResolution: preferencesController.setUse4ByteResolution.bind(
preferencesController,
),
setUseCurrencyRateCheck: setUseCurrencyRateCheck:
preferencesController.setUseCurrencyRateCheck.bind( preferencesController.setUseCurrencyRateCheck.bind(
preferencesController, preferencesController,
@ -2231,6 +2249,10 @@ export default class MetamaskController extends EventEmitter {
setIpfsGateway: preferencesController.setIpfsGateway.bind( setIpfsGateway: preferencesController.setIpfsGateway.bind(
preferencesController, preferencesController,
), ),
setUseAddressBarEnsResolution:
preferencesController.setUseAddressBarEnsResolution.bind(
preferencesController,
),
setParticipateInMetaMetrics: setParticipateInMetaMetrics:
metaMetricsController.setParticipateInMetaMetrics.bind( metaMetricsController.setParticipateInMetaMetrics.bind(
metaMetricsController, metaMetricsController,
@ -2920,7 +2942,7 @@ export default class MetamaskController extends EventEmitter {
ethQuery, ethQuery,
); );
const [primaryKeyring] = keyringController.getKeyringsByType( const [primaryKeyring] = this.coreKeyringController.getKeyringsByType(
KeyringType.hdKeyTree, KeyringType.hdKeyTree,
); );
if (!primaryKeyring) { if (!primaryKeyring) {
@ -3110,7 +3132,7 @@ export default class MetamaskController extends EventEmitter {
* Gets the mnemonic of the user's primary keyring. * Gets the mnemonic of the user's primary keyring.
*/ */
getPrimaryKeyringMnemonic() { getPrimaryKeyringMnemonic() {
const [keyring] = this.keyringController.getKeyringsByType( const [keyring] = this.coreKeyringController.getKeyringsByType(
KeyringType.hdKeyTree, KeyringType.hdKeyTree,
); );
if (!keyring.mnemonic) { if (!keyring.mnemonic) {
@ -3125,7 +3147,8 @@ export default class MetamaskController extends EventEmitter {
const custodyType = this.custodyController.getCustodyTypeByAddress( const custodyType = this.custodyController.getCustodyTypeByAddress(
toChecksumHexAddress(address), toChecksumHexAddress(address),
); );
const keyring = this.keyringController.getKeyringsByType(custodyType)[0]; const keyring =
this.coreKeyringController.getKeyringsByType(custodyType)[0];
return keyring?.getAccountDetails(address) ? keyring : undefined; return keyring?.getAccountDetails(address) ? keyring : undefined;
} }
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
@ -3162,7 +3185,9 @@ export default class MetamaskController extends EventEmitter {
'MetamaskController:getKeyringForDevice - Unknown device', 'MetamaskController:getKeyringForDevice - Unknown device',
); );
} }
let [keyring] = await this.keyringController.getKeyringsByType(keyringName); let [keyring] = await this.coreKeyringController.getKeyringsByType(
keyringName,
);
if (!keyring) { if (!keyring) {
keyring = await this.keyringController.addNewKeyring(keyringName); keyring = await this.keyringController.addNewKeyring(keyringName);
} }
@ -3259,8 +3284,10 @@ export default class MetamaskController extends EventEmitter {
* @returns {'hardware' | 'imported' | 'MetaMask'} * @returns {'hardware' | 'imported' | 'MetaMask'}
*/ */
async getAccountType(address) { async getAccountType(address) {
const keyring = await this.keyringController.getKeyringForAccount(address); const keyringType = await this.coreKeyringController.getAccountKeyringType(
switch (keyring.type) { address,
);
switch (keyringType) {
case KeyringType.trezor: case KeyringType.trezor:
case KeyringType.lattice: case KeyringType.lattice:
case KeyringType.qr: case KeyringType.qr:
@ -3282,7 +3309,9 @@ export default class MetamaskController extends EventEmitter {
* @returns {'ledger' | 'lattice' | 'N/A' | string} * @returns {'ledger' | 'lattice' | 'N/A' | string}
*/ */
async getDeviceModel(address) { async getDeviceModel(address) {
const keyring = await this.keyringController.getKeyringForAccount(address); const keyring = await this.coreKeyringController.getKeyringForAccount(
address,
);
switch (keyring.type) { switch (keyring.type) {
case KeyringType.trezor: case KeyringType.trezor:
return keyring.getModel(); return keyring.getModel();
@ -3371,7 +3400,7 @@ export default class MetamaskController extends EventEmitter {
await new Promise((resolve) => setTimeout(resolve, 5_000)); await new Promise((resolve) => setTimeout(resolve, 5_000));
} }
const [primaryKeyring] = this.keyringController.getKeyringsByType( const [primaryKeyring] = this.coreKeyringController.getKeyringsByType(
KeyringType.hdKeyTree, KeyringType.hdKeyTree,
); );
if (!primaryKeyring) { if (!primaryKeyring) {
@ -3416,7 +3445,7 @@ export default class MetamaskController extends EventEmitter {
* encoded as an array of UTF-8 bytes. * encoded as an array of UTF-8 bytes.
*/ */
async verifySeedPhrase() { async verifySeedPhrase() {
const [primaryKeyring] = this.keyringController.getKeyringsByType( const [primaryKeyring] = this.coreKeyringController.getKeyringsByType(
KeyringType.hdKeyTree, KeyringType.hdKeyTree,
); );
if (!primaryKeyring) { if (!primaryKeyring) {
@ -3521,7 +3550,9 @@ export default class MetamaskController extends EventEmitter {
this.custodyController.removeAccount(address); this.custodyController.removeAccount(address);
///: END:ONLY_INCLUDE_IN(build-mmi) ///: END:ONLY_INCLUDE_IN(build-mmi)
const keyring = await this.keyringController.getKeyringForAccount(address); const keyring = await this.coreKeyringController.getKeyringForAccount(
address,
);
// Remove account from the keyring // Remove account from the keyring
await this.keyringController.removeAccount(address); await this.keyringController.removeAccount(address);
const updatedKeyringAccounts = keyring ? await keyring.getAccounts() : {}; const updatedKeyringAccounts = keyring ? await keyring.getAccounts() : {};
@ -3574,6 +3605,9 @@ export default class MetamaskController extends EventEmitter {
origin: req.origin, origin: req.origin,
// This is the default behaviour but specified here for clarity // This is the default behaviour but specified here for clarity
requireApproval: true, requireApproval: true,
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
securityAlertResponse: req.securityAlertResponse,
///: END:ONLY_INCLUDE_IN
}); });
return await result; return await result;
@ -4029,7 +4063,9 @@ export default class MetamaskController extends EventEmitter {
engine.push(this.permissionLogController.createMiddleware()); engine.push(this.permissionLogController.createMiddleware());
///: BEGIN:ONLY_INCLUDE_IN(blockaid) ///: BEGIN:ONLY_INCLUDE_IN(blockaid)
engine.push(createPPOMMiddleware(this.ppomController)); engine.push(
createPPOMMiddleware(this.ppomController, this.preferencesController),
);
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
engine.push( engine.push(
@ -4631,14 +4667,14 @@ export default class MetamaskController extends EventEmitter {
* Locks MetaMask * Locks MetaMask
*/ */
setLocked() { setLocked() {
const [trezorKeyring] = this.keyringController.getKeyringsByType( const [trezorKeyring] = this.coreKeyringController.getKeyringsByType(
KeyringType.trezor, KeyringType.trezor,
); );
if (trezorKeyring) { if (trezorKeyring) {
trezorKeyring.dispose(); trezorKeyring.dispose();
} }
const [ledgerKeyring] = this.keyringController.getKeyringsByType( const [ledgerKeyring] = this.coreKeyringController.getKeyringsByType(
KeyringType.ledger, KeyringType.ledger,
); );
ledgerKeyring?.destroy?.(); ledgerKeyring?.destroy?.();

View File

@ -7,6 +7,15 @@ import EthQuery from 'eth-query';
import proxyquire from 'proxyquire'; import proxyquire from 'proxyquire';
import browser from 'webextension-polyfill'; import browser from 'webextension-polyfill';
import { wordlist as englishWordlist } from '@metamask/scure-bip39/dist/wordlists/english'; import { wordlist as englishWordlist } from '@metamask/scure-bip39/dist/wordlists/english';
import {
ListNames,
METAMASK_STALELIST_URL,
METAMASK_HOTLIST_DIFF_URL,
PHISHING_CONFIG_BASE_URL,
METAMASK_STALELIST_FILE,
METAMASK_HOTLIST_DIFF_FILE,
} from '@metamask/phishing-controller';
import { NetworkType } from '@metamask/controller-utils';
import { TransactionStatus } from '../../shared/constants/transaction'; import { TransactionStatus } from '../../shared/constants/transaction';
import createTxMeta from '../../test/lib/createTxMeta'; import createTxMeta from '../../test/lib/createTxMeta';
import { NETWORK_TYPES } from '../../shared/constants/network'; import { NETWORK_TYPES } from '../../shared/constants/network';
@ -152,9 +161,13 @@ const firstTimeState = {
id: NETWORK_CONFIGURATION_ID_1, id: NETWORK_CONFIGURATION_ID_1,
}, },
}, },
networkDetails: { selectedNetworkClientId: NetworkType.mainnet,
EIPS: { networksMetadata: {
1559: false, [NetworkType.mainnet]: {
EIPS: {
1559: false,
},
status: 'available',
}, },
}, },
}, },
@ -169,6 +182,18 @@ const firstTimeState = {
}, },
}, },
}, },
PhishingController: {
phishingLists: [
{
allowlist: [],
blocklist: ['test.metamask-phishing.io'],
fuzzylist: [],
tolerance: 0,
version: 0,
name: 'MetaMask',
},
],
},
}; };
const noop = () => undefined; const noop = () => undefined;
@ -185,25 +210,36 @@ describe('MetaMaskController', function () {
.persist() .persist()
.get(/.*/u) .get(/.*/u)
.reply(200, '{"JPY":12415.9}'); .reply(200, '{"JPY":12415.9}');
nock('https://static.metafi.codefi.network') nock(PHISHING_CONFIG_BASE_URL)
.persist() .persist()
.get('/api/v1/lists/stalelist.json') .get(METAMASK_STALELIST_FILE)
.reply( .reply(
200, 200,
JSON.stringify({ JSON.stringify({
version: 2, version: 2,
tolerance: 2, tolerance: 2,
fuzzylist: [], lastUpdated: 1,
allowlist: [], eth_phishing_detect_config: {
blocklist: ['127.0.0.1'], fuzzylist: [],
lastUpdated: 0, allowlist: [],
blocklist: ['test.metamask-phishing.io'],
name: ListNames.MetaMask,
},
phishfort_hotlist: {
blocklist: [],
name: ListNames.Phishfort,
},
}), }),
) )
.get('/api/v1/lists/hotlist.json') .get(METAMASK_HOTLIST_DIFF_FILE)
.reply( .reply(
200, 200,
JSON.stringify([ JSON.stringify([
{ url: '127.0.0.1', targetList: 'blocklist', timestamp: 0 }, {
url: 'test.metamask-phishing.io',
targetList: 'blocklist',
timestamp: 0,
},
]), ]),
); );
@ -223,6 +259,20 @@ describe('MetaMaskController', function () {
await ganacheServer.quit(); await ganacheServer.quit();
}); });
describe('Phishing Detection Mock', function () {
it('should be updated to use v1 of the API', function () {
// Update the fixture above if this test fails
assert.equal(
METAMASK_STALELIST_URL,
'https://phishing-detection.metafi.codefi.network/v1/stalelist',
);
assert.equal(
METAMASK_HOTLIST_DIFF_URL,
'https://phishing-detection.metafi.codefi.network/v1/diffsSince',
);
});
});
describe('MetaMaskController Behaviour', function () { describe('MetaMaskController Behaviour', function () {
let metamaskController; let metamaskController;
@ -281,9 +331,9 @@ describe('MetaMaskController', function () {
]); ]);
}); });
it('adds private key to keyrings in KeyringController', async function () { it('adds private key to keyrings in core KeyringController', async function () {
const simpleKeyrings = const simpleKeyrings =
metamaskController.keyringController.getKeyringsByType( metamaskController.coreKeyringController.getKeyringsByType(
KeyringType.imported, KeyringType.imported,
); );
const pubAddressHexArr = await simpleKeyrings[0].getAccounts(); const pubAddressHexArr = await simpleKeyrings[0].getAccounts();
@ -566,7 +616,7 @@ describe('MetaMaskController', function () {
.connectHardware(HardwareDeviceNames.trezor, 0) .connectHardware(HardwareDeviceNames.trezor, 0)
.catch(() => null); .catch(() => null);
const keyrings = const keyrings =
await metamaskController.keyringController.getKeyringsByType( await metamaskController.coreKeyringController.getKeyringsByType(
KeyringType.trezor, KeyringType.trezor,
); );
assert.deepEqual( assert.deepEqual(
@ -582,7 +632,7 @@ describe('MetaMaskController', function () {
.connectHardware(HardwareDeviceNames.ledger, 0) .connectHardware(HardwareDeviceNames.ledger, 0)
.catch(() => null); .catch(() => null);
const keyrings = const keyrings =
await metamaskController.keyringController.getKeyringsByType( await metamaskController.coreKeyringController.getKeyringsByType(
KeyringType.ledger, KeyringType.ledger,
); );
assert.deepEqual( assert.deepEqual(
@ -609,7 +659,7 @@ describe('MetaMaskController', function () {
mnemonic: uint8ArrayMnemonic, mnemonic: uint8ArrayMnemonic,
}; };
sinon sinon
.stub(metamaskController.keyringController, 'getKeyringsByType') .stub(metamaskController.coreKeyringController, 'getKeyringsByType')
.returns([mockHDKeyring]); .returns([mockHDKeyring]);
const recoveredMnemonic = const recoveredMnemonic =
@ -663,7 +713,7 @@ describe('MetaMaskController', function () {
.catch(() => null); .catch(() => null);
await metamaskController.forgetDevice(HardwareDeviceNames.trezor); await metamaskController.forgetDevice(HardwareDeviceNames.trezor);
const keyrings = const keyrings =
await metamaskController.keyringController.getKeyringsByType( await metamaskController.coreKeyringController.getKeyringsByType(
KeyringType.trezor, KeyringType.trezor,
); );
@ -726,7 +776,7 @@ describe('MetaMaskController', function () {
it('should set unlockedAccount in the keyring', async function () { it('should set unlockedAccount in the keyring', async function () {
const keyrings = const keyrings =
await metamaskController.keyringController.getKeyringsByType( await metamaskController.coreKeyringController.getKeyringsByType(
KeyringType.trezor, KeyringType.trezor,
); );
assert.equal(keyrings[0].unlockedAccount, accountToUnlock); assert.equal(keyrings[0].unlockedAccount, accountToUnlock);
@ -859,7 +909,10 @@ describe('MetaMaskController', function () {
sinon.stub(metamaskController.keyringController, 'removeAccount'); sinon.stub(metamaskController.keyringController, 'removeAccount');
sinon.stub(metamaskController, 'removeAllAccountPermissions'); sinon.stub(metamaskController, 'removeAllAccountPermissions');
sinon sinon
.stub(metamaskController.keyringController, 'getKeyringForAccount') .stub(
metamaskController.coreKeyringController,
'getKeyringForAccount',
)
.returns(Promise.resolve(mockKeyring)); .returns(Promise.resolve(mockKeyring));
ret = await metamaskController.removeAccount(addressToRemove); ret = await metamaskController.removeAccount(addressToRemove);
@ -906,9 +959,9 @@ describe('MetaMaskController', function () {
it('should return address', async function () { it('should return address', async function () {
assert.equal(ret, '0x1'); assert.equal(ret, '0x1');
}); });
it('should call keyringController.getKeyringForAccount', async function () { it('should call coreKeyringController.getKeyringForAccount', async function () {
assert( assert(
metamaskController.keyringController.getKeyringForAccount.calledWith( metamaskController.coreKeyringController.getKeyringForAccount.calledWith(
addressToRemove, addressToRemove,
), ),
); );
@ -931,7 +984,7 @@ describe('MetaMaskController', function () {
it('sets up phishing stream for untrusted communication', async function () { it('sets up phishing stream for untrusted communication', async function () {
const phishingMessageSender = { const phishingMessageSender = {
url: 'http://myethereumwalletntw.com', url: 'http://test.metamask-phishing.io',
tab: {}, tab: {},
}; };

View File

@ -0,0 +1,25 @@
import { hasProperty, isObject } from '@metamask/utils';
/**
* Deletes frequentRpcListDetail if networkConfigurations exists, on the NetworkController state.
* Further explanation in ./077-supplements.md
*
* @param state - The persisted MetaMask state, keyed by controller.
* @returns Updated versioned MetaMask extension state.
*/
export default function transformState077For082(
state: Record<string, unknown>,
) {
if (
hasProperty(state, 'PreferencesController') &&
isObject(state.PreferencesController) &&
hasProperty(state.PreferencesController, 'frequentRpcListDetail') &&
isObject(state.NetworkController) &&
hasProperty(state.NetworkController, 'networkConfigurations')
) {
delete state.PreferencesController.frequentRpcListDetail;
}
return { ...state };
}

View File

@ -0,0 +1,24 @@
import { hasProperty, isObject } from '@metamask/utils';
/**
* Deletes network if networkId exists, on the NetworkController state.
* Further explanation in ./077-supplements.md
*
* @param state - The persisted MetaMask state, keyed by controller.
* @returns Updated versioned MetaMask extension state.
*/
export default function transformState077For084(
state: Record<string, unknown>,
) {
if (
hasProperty(state, 'NetworkController') &&
isObject(state.NetworkController) &&
hasProperty(state.NetworkController, 'network') &&
hasProperty(state.NetworkController, 'networkId')
) {
delete state.NetworkController.network;
}
return { ...state };
}

View File

@ -0,0 +1,23 @@
import { hasProperty, isObject } from '@metamask/utils';
/**
* Prior to token detection v2 the data property in tokensChainsCache was an array,
* in v2 we changes that to an object. In this migration we are converting the data as array to object.
*
* @param state - The persisted MetaMask state, keyed by controller.
* @returns Updated versioned MetaMask extension state.
*/
export default function transformState077For086(
state: Record<string, unknown>,
) {
if (
hasProperty(state, 'NetworkController') &&
isObject(state.NetworkController) &&
hasProperty(state.NetworkController, 'provider') &&
hasProperty(state.NetworkController, 'providerConfig')
) {
delete state.NetworkController.provider;
}
return { ...state };
}

View File

@ -0,0 +1,152 @@
import { hasProperty, isObject, isStrictHexString } from '@metamask/utils';
/**
* Deletes properties of `NftController.allNftContracts`, `NftController.allNfts`,
* `TokenListController.tokensChainsCache`, `TokensController.allTokens`,
* `TokensController.allIgnoredTokens` and `TokensController.allDetectedTokens` if
* their keyed by decimal number chainId and another hexadecimal chainId property
* exists within the same object.
* Further explanation in ./077-supplements.md
*
* @param state - The persisted MetaMask state, keyed by controller.
* @returns Updated versioned MetaMask extension state.
*/
export default function transformState077For086(
state: Record<string, unknown>,
): Record<string, unknown> {
if (hasProperty(state, 'NftController') && isObject(state.NftController)) {
const nftControllerState = state.NftController;
// Migrate NftController.allNftContracts
if (
hasProperty(nftControllerState, 'allNftContracts') &&
isObject(nftControllerState.allNftContracts)
) {
const { allNftContracts } = nftControllerState;
if (
Object.keys(allNftContracts).every((address) =>
isObject(allNftContracts[address]),
)
) {
Object.keys(allNftContracts).forEach((address) => {
const nftContractsByChainId = allNftContracts[address];
if (
isObject(nftContractsByChainId) &&
anyKeysAreHex(nftContractsByChainId)
) {
for (const chainId of Object.keys(nftContractsByChainId)) {
if (!isStrictHexString(chainId)) {
delete nftContractsByChainId[chainId];
}
}
}
});
}
}
// Migrate NftController.allNfts
if (
hasProperty(nftControllerState, 'allNfts') &&
isObject(nftControllerState.allNfts)
) {
const { allNfts } = nftControllerState;
if (Object.keys(allNfts).every((address) => isObject(allNfts[address]))) {
Object.keys(allNfts).forEach((address) => {
const nftsByChainId = allNfts[address];
if (isObject(nftsByChainId) && anyKeysAreHex(nftsByChainId)) {
for (const chainId of Object.keys(nftsByChainId)) {
if (!isStrictHexString(chainId)) {
delete nftsByChainId[chainId];
}
}
}
});
}
}
state.NftController = nftControllerState;
}
if (
hasProperty(state, 'TokenListController') &&
isObject(state.TokenListController)
) {
const tokenListControllerState = state.TokenListController;
// Migrate TokenListController.tokensChainsCache
if (
hasProperty(tokenListControllerState, 'tokensChainsCache') &&
isObject(tokenListControllerState.tokensChainsCache) &&
anyKeysAreHex(tokenListControllerState.tokensChainsCache)
) {
for (const chainId of Object.keys(
tokenListControllerState.tokensChainsCache,
)) {
if (!isStrictHexString(chainId)) {
delete tokenListControllerState.tokensChainsCache[chainId];
}
}
}
}
if (
hasProperty(state, 'TokensController') &&
isObject(state.TokensController)
) {
const tokensControllerState = state.TokensController;
// Migrate TokensController.allTokens
if (
hasProperty(tokensControllerState, 'allTokens') &&
isObject(tokensControllerState.allTokens) &&
anyKeysAreHex(tokensControllerState.allTokens)
) {
const { allTokens } = tokensControllerState;
for (const chainId of Object.keys(allTokens)) {
if (!isStrictHexString(chainId)) {
delete tokensControllerState.allTokens[chainId];
}
}
}
// Migrate TokensController.allIgnoredTokens
if (
hasProperty(tokensControllerState, 'allIgnoredTokens') &&
isObject(tokensControllerState.allIgnoredTokens) &&
anyKeysAreHex(tokensControllerState.allIgnoredTokens)
) {
const { allIgnoredTokens } = tokensControllerState;
for (const chainId of Object.keys(allIgnoredTokens)) {
if (!isStrictHexString(chainId)) {
delete tokensControllerState.allIgnoredTokens[chainId];
}
}
}
// Migrate TokensController.allDetectedTokens
if (
hasProperty(tokensControllerState, 'allDetectedTokens') &&
isObject(tokensControllerState.allDetectedTokens) &&
anyKeysAreHex(tokensControllerState.allDetectedTokens)
) {
const { allDetectedTokens } = tokensControllerState;
for (const chainId of Object.keys(allDetectedTokens)) {
if (!isStrictHexString(chainId)) {
delete tokensControllerState.allDetectedTokens[chainId];
}
}
}
state.TokensController = tokensControllerState;
}
return state;
}
function anyKeysAreHex(obj: Record<string, unknown>) {
return Object.keys(obj).some((chainId) => isStrictHexString(chainId));
}

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