1
0
Fork 0

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:
node-browsers:
docker:
- image: cimg/node:16.20-browsers
- image: cimg/node:18.17-browsers
node-browsers-medium-plus:
docker:
- image: cimg/node:16.20-browsers
- image: cimg/node:18.17-browsers
resource_class: medium+
environment:
NODE_OPTIONS: --max_old_space_size=2048
node-browsers-large:
docker:
- image: cimg/node:16.20-browsers
- image: cimg/node:18.17-browsers
resource_class: large
environment:
NODE_OPTIONS: --max_old_space_size=2048
@ -147,6 +147,9 @@ workflows:
- test-e2e-firefox:
requires:
- prep-build-test
- test-e2e-chrome-rpc:
requires:
- prep-build-test
- test-e2e-chrome-snaps:
requires:
- prep-build-test-flask
@ -350,27 +353,9 @@ jobs:
# required and add the new dependencies, and the cache will be persisted.
- dependency-cache-v1-
- 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:
name: Install dependencies
command: |
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
command: .circleci/scripts/install-dependencies.sh
- save_cache:
key: dependency-cache-v1-{{ checksum "yarn.lock" }}
paths:
@ -709,6 +694,9 @@ jobs:
- run:
name: lockfile-lint
command: yarn lint:lockfile
- run:
name: check yarn resolutions
command: yarn --check-resolutions
test-lint-changelog:
executor: node-browsers
@ -744,7 +732,7 @@ jobs:
at: .
- run:
name: yarn audit
command: .circleci/scripts/yarn-audit.sh
command: yarn audit
test-deps-depcheck:
executor: node-browsers
@ -821,6 +809,43 @@ jobs:
path: 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:
executor: node-browsers
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'
- 'wait-on'
# development tool
- 'improved-yarn-audit'
- 'nyc'
# storybook
- '@storybook/cli'

View File

@ -246,6 +246,7 @@ module.exports = {
'shared/**/*.test.js',
'ui/**/*.test.js',
'ui/__mocks__/*.js',
'test/e2e/helpers.test.js',
],
extends: ['@metamask/eslint-config-mocha'],
rules: {
@ -271,10 +272,12 @@ module.exports = {
'app/scripts/migrations/*.test.js',
'app/scripts/platforms/*.test.js',
'development/**/*.test.js',
'development/**/*.test.ts',
'shared/**/*.test.js',
'shared/**/*.test.ts',
'test/helpers/*.js',
'test/jest/*.js',
'test/e2e/helpers.test.js',
'ui/**/*.test.js',
'ui/__mocks__/*.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 ;)
- [ ] **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.
- [ ] **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.

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.
;PHISHING_WARNING_PAGE_URL=
BLOCKAID_FILE_CDN=
BLOCKAID_PUBLIC_KEY=

View File

@ -9,6 +9,8 @@ module.exports = {
'./app/scripts/controllers/permissions/**/*.test.js',
'./app/scripts/controllers/mmi-controller.test.js',
'./app/scripts/constants/error-utils.test.js',
'./development/fitness-functions/**/*.test.ts',
'./test/e2e/helpers.test.js',
],
recursive: true,
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 { KeyringType } from '../shared/constants/keyring';
import { NetworkType } from '@metamask/controller-utils';
import { NetworkStatus } from '@metamask/network-controller';
const state = {
invalidCustomNetwork: {
@ -164,6 +166,15 @@ const state = {
1559: true,
},
},
selectedNetworkClientId: NetworkType.mainnet,
networksMetadata: {
[NetworkType.mainnet]: {
EIPS: {
1559: true,
},
status: NetworkStatus.Available,
},
},
gasFeeEstimates: '0x5208',
swapsState: {
quotes: {},
@ -1599,7 +1610,7 @@ const state = {
},
};
export const networkList = [
export const networkList = [
{
blockExplorerUrl: 'https://etherscan.io',
chainId: '0x1',
@ -1673,6 +1684,6 @@ export const networkList = [
rpcUrl: 'https://polygon-rpc.com',
ticker: 'MATIC',
},
]
];
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
checksumBehavior: "ignore"
enableTelemetry: false
@ -8,21 +12,101 @@ logFilters:
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:
"https://npm.pkg.github.com":
'https://npm.pkg.github.com':
npmAlwaysAuth: true
npmAuthToken: "${GITHUB_PACKAGE_READ_TOKEN-}"
npmAuthToken: '${GITHUB_PACKAGE_READ_TOKEN-}'
npmScopes:
metamask:
npmRegistryServer: "${METAMASK_NPM_REGISTRY:-https://registry.yarnpkg.com}"
npmRegistryServer: '${METAMASK_NPM_REGISTRY:-https://registry.yarnpkg.com}'
plugins:
- 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"
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
spec: "@yarnpkg/plugin-version"
spec: 'https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js'
- 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]
## [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]
### Fixed
- 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
- 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.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

View File

@ -49,11 +49,11 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D
## 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.
- Install [Yarn v3](https://yarnpkg.com/getting-started/install)
- 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.
- 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.
- 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).
- 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": {
"message": "አውታረ መረብ ይሰረዝ?"
},
"deleteNetworkDescription": {
"message": "ይህን አውታረ መረብ ለመሰረዝ እንደሚፈልጉ እርግጠኛ ነዎት?"
},
"details": {
"message": "ዝርዝሮች"
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -183,12 +183,6 @@
"addContact": {
"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": {
"message": "Benutzerdefiniertes Netzwerk hinzufügen"
},
@ -914,9 +908,6 @@
"deleteNetwork": {
"message": "Netzwerk löschen?"
},
"deleteNetworkDescription": {
"message": "Sind Sie sicher, dass Sie dieses Netzwerk löschen möchten?"
},
"deposit": {
"message": "Einzahlung"
},
@ -1184,12 +1175,6 @@
"enableFromSettings": {
"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": {
"message": "$1 aktivieren",
"description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": {
"message": "Hoppla! Da hat etwas nicht geklappt."
},
"source": {
"message": "Quelle"
},
"speedUp": {
"message": "Beschleunigen"
},
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": {
"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": {
"message": "NFTs automatisch erkennen"
},

View File

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

View File

@ -189,12 +189,6 @@
"addContact": {
"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": {
"message": "Add custom network"
},
@ -249,6 +243,9 @@
"addHardwareWallet": {
"message": "Add hardware wallet"
},
"addIPFSGateway": {
"message": "Add your preferred IPFS gateway"
},
"addMemo": {
"message": "Add memo"
},
@ -614,6 +611,10 @@
"message": "Buy $1",
"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": {
"message": "Buy Now"
},
@ -1119,8 +1120,12 @@
"deleteNetwork": {
"message": "Delete network?"
},
"deleteNetworkDescription": {
"message": "Are you sure you want to delete this network?"
"deleteNetworkIntro": {
"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": {
"message": "Deposit"
@ -1268,6 +1273,12 @@
"dismissReminderField": {
"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": {
"message": "Domain"
},
@ -1390,15 +1401,12 @@
"enableAutoDetect": {
"message": " Enable autodetect"
},
"enableForAllNetworks": {
"message": "Enable for all networks"
},
"enableFromSettings": {
"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": {
"message": "Enable smart swaps"
},
@ -1426,6 +1434,24 @@
"enhancedTokenDetectionAlertMessage": {
"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": {
"message": "Illegal character for ENS."
},
@ -2036,6 +2062,22 @@
"invalidSeedPhraseCaseSensitive": {
"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": {
"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"
},
"moreComingSoon": {
"message": "More coming soon..."
"message": "More providers coming soon"
},
"multipleSnapConnectionWarning": {
"message": "$1 wants to connect with $2 snaps. Only proceed if you trust this website.",
@ -2520,6 +2562,9 @@
"noNFTs": {
"message": "No NFTs yet"
},
"noNetworksFound": {
"message": "No networks found for the given search query"
},
"noSnaps": {
"message": "You don't have any snaps installed."
},
@ -2716,6 +2761,30 @@
"notifications21Title": {
"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": {
"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."
@ -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.",
"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": {
"message": "Run indefinitely.",
"description": "The description for the `endowment:long-running` permission"
@ -3595,7 +3672,7 @@
"message": "Security alerts"
},
"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": {
"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",
"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": {
"message": "Show more"
},
"showNft": {
"message": "Show NFT"
},
"showPermissions": {
"message": "Show permissions"
},
@ -3831,6 +3914,9 @@
"skipAccountSecurityDetails": {
"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": {
"message": "Smart swap"
},
@ -4021,9 +4107,6 @@
"somethingWentWrong": {
"message": "Oops! Something went wrong."
},
"source": {
"message": "Source"
},
"speedUp": {
"message": "Speed up"
},
@ -5124,11 +5207,17 @@
"urlExistsErrorMsg": {
"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": {
"message": "Batch account balance requests"
},
"useMultiAccountBalanceCheckerDescription": {
"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."
"useMultiAccountBalanceCheckerSettingDescription": {
"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": {
"message": "Autodetect NFTs"

View File

@ -183,12 +183,6 @@
"addContact": {
"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": {
"message": "Agregar red personalizada"
},
@ -914,9 +908,6 @@
"deleteNetwork": {
"message": "¿Eliminar red?"
},
"deleteNetworkDescription": {
"message": "¿Está seguro de que quiere eliminar esta red?"
},
"deposit": {
"message": "Depositar"
},
@ -1184,12 +1175,6 @@
"enableFromSettings": {
"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": {
"message": "activar $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": {
"message": "Lo lamentamos, se produjo un error."
},
"source": {
"message": "Fuente"
},
"speedUp": {
"message": "Acelerar"
},
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": {
"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": {
"message": "Detección automática de NFT"
},

View File

@ -584,9 +584,6 @@
"deleteNetwork": {
"message": "¿Eliminar red?"
},
"deleteNetworkDescription": {
"message": "¿Está seguro de que quiere eliminar esta red?"
},
"description": {
"message": "Descripción"
},
@ -740,12 +737,6 @@
"enableFromSettings": {
"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": {
"message": "activar $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -2071,9 +2062,6 @@
"somethingWentWrong": {
"message": "Lo lamentamos, se produjo un error."
},
"source": {
"message": "Fuente"
},
"speedUp": {
"message": "Acelerar"
},

View File

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

View File

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

View File

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

View File

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

View File

@ -183,12 +183,6 @@
"addContact": {
"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": {
"message": "Ajouter un réseau personnalisé"
},
@ -914,9 +908,6 @@
"deleteNetwork": {
"message": "Supprimer le réseau ?"
},
"deleteNetworkDescription": {
"message": "Souhaitez-vous vraiment supprimer ce réseau ?"
},
"deposit": {
"message": "Effectuez un dépôt"
},
@ -1184,12 +1175,6 @@
"enableFromSettings": {
"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": {
"message": "activer $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": {
"message": "Oups ! Quelque chose a mal tourné. "
},
"source": {
"message": "Source"
},
"speedUp": {
"message": "Accélérer"
},
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": {
"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": {
"message": "Détection automatique des NFT"
},

View File

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

View File

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

View File

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

View File

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

View File

@ -183,12 +183,6 @@
"addContact": {
"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": {
"message": "Tambahkan jaringan khusus"
},
@ -914,9 +908,6 @@
"deleteNetwork": {
"message": "Hapus jaringan?"
},
"deleteNetworkDescription": {
"message": "Anda yakin ingin menghapus jaringan ini?"
},
"deposit": {
"message": "Deposit"
},
@ -1184,12 +1175,6 @@
"enableFromSettings": {
"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": {
"message": "aktifkan $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": {
"message": "Ups! Ada yang salah."
},
"source": {
"message": "Sumber"
},
"speedUp": {
"message": "Percepat"
},
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": {
"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": {
"message": "Deteksi otomatis NFT"
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -183,12 +183,6 @@
"addContact": {
"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": {
"message": "Adicionar rede personalizada"
},
@ -914,9 +908,6 @@
"deleteNetwork": {
"message": "Excluir rede?"
},
"deleteNetworkDescription": {
"message": "Quer mesmo excluir essa rede?"
},
"deposit": {
"message": "Depositar"
},
@ -1184,12 +1175,6 @@
"enableFromSettings": {
"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": {
"message": "ativar $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": {
"message": "Ops! Algo deu errado."
},
"source": {
"message": "Origem"
},
"speedUp": {
"message": "Acelerar"
},
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": {
"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": {
"message": "Detectar NFTs automaticamente"
},

View File

@ -584,9 +584,6 @@
"deleteNetwork": {
"message": "Excluir rede?"
},
"deleteNetworkDescription": {
"message": "Quer mesmo excluir essa rede?"
},
"description": {
"message": "Descrição"
},
@ -740,12 +737,6 @@
"enableFromSettings": {
"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": {
"message": "ativar $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -2071,9 +2062,6 @@
"somethingWentWrong": {
"message": "Opa! Ocorreu algum erro."
},
"source": {
"message": "Fonte"
},
"speedUp": {
"message": "Acelerar"
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -183,12 +183,6 @@
"addContact": {
"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": {
"message": "Magdagdag ng custom na network"
},
@ -914,9 +908,6 @@
"deleteNetwork": {
"message": "I-delete ang network?"
},
"deleteNetworkDescription": {
"message": "Sigurado ka bang gusto mong i-delete ang network na ito?"
},
"deposit": {
"message": "Deposito"
},
@ -1184,12 +1175,6 @@
"enableFromSettings": {
"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": {
"message": "paganahin ang $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": {
"message": "Oops! Nagkaproblema."
},
"source": {
"message": "Pinagmulan"
},
"speedUp": {
"message": "Pabilisin"
},
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": {
"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": {
"message": "Awtomatikong tuklasin ang mga NFT"
},

View File

@ -183,12 +183,6 @@
"addContact": {
"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": {
"message": "Özel ağ ekle"
},
@ -914,9 +908,6 @@
"deleteNetwork": {
"message": "Ağı Sil?"
},
"deleteNetworkDescription": {
"message": "Bu ağı silmek istediğinizden emin misiniz?"
},
"deposit": {
"message": "Para Yatır"
},
@ -1184,12 +1175,6 @@
"enableFromSettings": {
"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": {
"message": "şunu etkinleştir: $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": {
"message": "Eyvah! Bir şeyler ters gitti."
},
"source": {
"message": "Kaynak"
},
"speedUp": {
"message": "Hızlandır"
},
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": {
"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": {
"message": "NFT'leri otomatik algıla"
},

View File

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

View File

@ -183,12 +183,6 @@
"addContact": {
"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": {
"message": "Thêm mạng tùy chỉnh"
},
@ -914,9 +908,6 @@
"deleteNetwork": {
"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": {
"message": "Nạp"
},
@ -1184,12 +1175,6 @@
"enableFromSettings": {
"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": {
"message": "bật $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3261,9 +3246,6 @@
"somethingWentWrong": {
"message": "Rất tiếc! Đã xảy ra sự cố."
},
"source": {
"message": "Nguồn"
},
"speedUp": {
"message": "Tăng tốc"
},
@ -4160,9 +4142,6 @@
"useMultiAccountBalanceChecker": {
"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": {
"message": "Tự động phát hiện NFT"
},

View File

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

View File

@ -411,9 +411,6 @@
"deleteNetwork": {
"message": "刪除網路?"
},
"deleteNetworkDescription": {
"message": "你確定要刪除網路嗎?"
},
"details": {
"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.
*/
// 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 endOfStream from 'end-of-stream';
import pump from 'pump';
@ -466,6 +470,9 @@ export function setupController(
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(
controller.preferencesController,
),
getUseAddressBarEnsResolution: () =>
controller.preferencesController.store.getState()
.useAddressBarEnsResolution,
provider: controller.provider,
});
@ -794,6 +801,13 @@ export function setupController(
});
}
///: 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 });
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 = () => {

View File

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

View File

@ -1,10 +1,7 @@
import { ObservableStore } from '@metamask/obs-store';
import log from 'loglevel';
import { ORIGIN_METAMASK } from '../../../shared/constants/app';
import AppStateController from './app-state';
jest.mock('loglevel');
let appStateController, mockStore;
describe('AppStateController', () => {
@ -147,52 +144,6 @@ describe('AppStateController', () => {
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', () => {

View File

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

View File

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

View File

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

View File

@ -313,30 +313,30 @@ describe('MetaMetricsController', function () {
});
describe('setParticipateInMetaMetrics', function () {
it('should update the value of participateInMetaMetrics', function () {
it('should update the value of participateInMetaMetrics', async function () {
const metaMetricsController = getMetaMetricsController({
participateInMetaMetrics: null,
metaMetricsId: null,
});
assert.equal(metaMetricsController.state.participateInMetaMetrics, null);
metaMetricsController.setParticipateInMetaMetrics(true);
await metaMetricsController.setParticipateInMetaMetrics(true);
assert.ok(globalThis.sentry.startSession.calledOnce);
assert.equal(metaMetricsController.state.participateInMetaMetrics, true);
metaMetricsController.setParticipateInMetaMetrics(false);
await metaMetricsController.setParticipateInMetaMetrics(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({
participateInMetaMetrics: null,
metaMetricsId: null,
});
assert.equal(metaMetricsController.state.metaMetricsId, null);
metaMetricsController.setParticipateInMetaMetrics(true);
await metaMetricsController.setParticipateInMetaMetrics(true);
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();
metaMetricsController.setParticipateInMetaMetrics(false);
await metaMetricsController.setParticipateInMetaMetrics(false);
assert.ok(globalThis.sentry.endSession.calledOnce);
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
useTokenDetection: false,
useNftDetection: false,
use4ByteResolution: true,
useCurrencyRateCheck: true,
openSeaEnabled: false,
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
@ -67,6 +68,7 @@ export default class PreferencesController {
},
// ENS decentralized website resolution
ipfsGateway: IPFS_DEFAULT_GATEWAY_URL,
useAddressBarEnsResolution: true,
infuraBlocked: null,
ledgerTransportType: window.navigator.hid
? LedgerTransportTypes.webhid
@ -168,6 +170,15 @@ export default class PreferencesController {
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
*
@ -480,6 +491,15 @@ export default class PreferencesController {
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.
*

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 () {
it('should default to false', function () {
const state = preferencesController.store.getState();

View File

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

View File

@ -4,6 +4,7 @@ import sinon from 'sinon';
import { BigNumber } from '@ethersproject/bignumber';
import { mapValues } from 'lodash';
import BigNumberjs from 'bignumber.js';
import { NetworkType } from '@metamask/controller-utils';
import {
CHAIN_IDS,
NETWORK_IDS,
@ -102,7 +103,13 @@ function getMockNetworkController() {
return {
state: {
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 = {
networkId: NETWORK_IDS.MAINNET,
networkStatus: NetworkStatus.Available,
selectedNetworkClientId: NetworkType.mainnet,
networksMetadata: {
[NetworkType.mainnet]: {
EIPS: {},
status: NetworkStatus.Available,
},
},
};
networkStateChangeListener();
@ -256,7 +269,13 @@ describe('SwapsController', function () {
networkController.state = {
networkId: null,
networkStatus: NetworkStatus.Unknown,
selectedNetworkClientId: NetworkType.goerli,
networksMetadata: {
[NetworkType.goerli]: {
EIPS: {},
status: NetworkStatus.Unknown,
},
},
};
networkStateChangeListener();
@ -288,7 +307,13 @@ describe('SwapsController', function () {
networkController.state = {
networkId: NETWORK_IDS.GOERLI,
networkStatus: NetworkStatus.Available,
selectedNetworkClientId: NetworkType.goerli,
networksMetadata: {
[NetworkType.goerli]: {
EIPS: {},
status: NetworkStatus.Available,
},
},
};
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 { errorCodes, ethErrors } from 'eth-rpc-errors';
import {
BlockaidReason,
BlockaidResultType,
} from '../../../../shared/constants/security-provider';
import {
createTestProviderTools,
getTestAccounts,
@ -746,11 +750,11 @@ describe('Transaction Controller', function () {
providerResultStub.eth_estimateGas = '0x5209';
signStub = sinon
.stub(txController, 'signTransaction')
.stub(txController, '_signTransaction')
.callsFake(() => Promise.resolve());
const pubStub = sinon
.stub(txController, 'publishTransaction')
.stub(txController, '_publishTransaction')
.callsFake(() => {
const txId = getLastTxMeta().id;
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 () {
txController.txStateManager._addTransactionsToState([
{
@ -1234,7 +1238,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
providerResultStub.eth_estimateGas = '5209';
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta);
const txMetaWithDefaults = await txController._addTxGasDefaults(txMeta);
assert.ok(
txMetaWithDefaults.txParams.gasPrice,
'should have added the gas price',
@ -1250,7 +1254,7 @@ describe('Transaction Controller', function () {
const TEST_MAX_PRIORITY_FEE_PER_GAS = '0x77359400';
const stub1 = sinon
.stub(txController, 'getEIP1559Compatibility')
.stub(txController, '_getEIP1559Compatibility')
.returns(true);
const stub2 = sinon
@ -1283,7 +1287,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
providerResultStub.eth_estimateGas = '5209';
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta);
const txMetaWithDefaults = await txController._addTxGasDefaults(txMeta);
assert.equal(
txMetaWithDefaults.txParams.maxFeePerGas,
@ -1303,7 +1307,7 @@ describe('Transaction Controller', function () {
const TEST_GASPRICE = '0x12a05f200';
const stub1 = sinon
.stub(txController, 'getEIP1559Compatibility')
.stub(txController, '_getEIP1559Compatibility')
.returns(true);
const stub2 = sinon
@ -1333,7 +1337,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
providerResultStub.eth_estimateGas = '5209';
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta);
const txMetaWithDefaults = await txController._addTxGasDefaults(txMeta);
assert.equal(
txMetaWithDefaults.txParams.maxFeePerGas,
@ -1353,7 +1357,7 @@ describe('Transaction Controller', function () {
const TEST_GASPRICE = '0x12a05f200';
const stub1 = sinon
.stub(txController, 'getEIP1559Compatibility')
.stub(txController, '_getEIP1559Compatibility')
.returns(true);
const stub2 = sinon
@ -1385,7 +1389,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
providerResultStub.eth_estimateGas = '5209';
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta);
const txMetaWithDefaults = await txController._addTxGasDefaults(txMeta);
assert.equal(
txMetaWithDefaults.txParams.maxFeePerGas,
@ -1407,7 +1411,7 @@ describe('Transaction Controller', function () {
const TEST_MAX_PRIORITY_FEE_PER_GAS = '0x77359400';
const stub1 = sinon
.stub(txController, 'getEIP1559Compatibility')
.stub(txController, '_getEIP1559Compatibility')
.returns(true);
const stub2 = sinon
@ -1439,7 +1443,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
providerResultStub.eth_estimateGas = '5209';
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta);
const txMetaWithDefaults = await txController._addTxGasDefaults(txMeta);
assert.equal(
txMetaWithDefaults.txParams.maxFeePerGas,
@ -1543,27 +1547,27 @@ describe('Transaction Controller', function () {
},
noop,
);
const rawTx = await txController.signTransaction('1');
const rawTx = await txController._signTransaction('1');
const ethTx = TransactionFactory.fromSerializedData(toBuffer(rawTx));
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 () {
networkStatusStore.putState(NetworkStatus.Available);
assert.equal(txController.getChainId(), 5);
assert.equal(txController._getChainId(), 5);
});
it('returns 0 when the network is not available', function () {
networkStatusStore.putState('asdflsfadf');
assert.equal(txController.getChainId(), 0);
networkStatusStore.putState('NOT_INTEGER');
assert.equal(txController._getChainId(), 0);
});
it('returns 0 when the chain ID cannot be parsed as a hex string', function () {
networkStatusStore.putState(NetworkStatus.Available);
getCurrentChainId.returns('$fdsjfldf');
assert.equal(txController.getChainId(), 0);
getCurrentChainId.returns('NOT_INTEGER');
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');
});
it('sets txParams.type to 0x2 (EIP-1559)', async function () {
const eip1559CompatibilityStub = sinon
.stub(txController, 'getEIP1559Compatibility')
.stub(txController, '_getEIP1559Compatibility')
.returns(true);
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');
eip1559CompatibilityStub.restore();
});
});
describe('#publishTransaction', function () {
describe('_publishTransaction', function () {
let hash, txMeta, trackTransactionMetricsEventSpy;
beforeEach(function () {
@ -1852,7 +1856,7 @@ describe('Transaction Controller', function () {
const rawTx =
'0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c';
txController.txStateManager.addTransaction(txMeta);
await txController.publishTransaction(txMeta.id, rawTx);
await txController._publishTransaction(txMeta.id, rawTx);
const publishedTx = txController.txStateManager.getTransaction(1);
assert.equal(publishedTx.hash, hash);
assert.equal(publishedTx.status, TransactionStatus.submitted);
@ -1865,7 +1869,7 @@ describe('Transaction Controller', function () {
const rawTx =
'0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a';
txController.txStateManager.addTransaction(txMeta);
await txController.publishTransaction(txMeta.id, rawTx);
await txController._publishTransaction(txMeta.id, rawTx);
const publishedTx = txController.txStateManager.getTransaction(1);
assert.equal(
publishedTx.hash,
@ -1878,7 +1882,7 @@ describe('Transaction Controller', function () {
const rawTx =
'0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c';
txController.txStateManager.addTransaction(txMeta);
await txController.publishTransaction(txMeta.id, rawTx);
await txController._publishTransaction(txMeta.id, rawTx);
assert.equal(trackTransactionMetricsEventSpy.callCount, 1);
assert.deepEqual(
trackTransactionMetricsEventSpy.getCall(0).args[0],
@ -2163,6 +2167,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
},
sensitiveProperties: {
default_gas: '0.000031501',
@ -2173,7 +2180,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
@ -2250,6 +2256,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
},
sensitiveProperties: {
default_gas: '0.000031501',
@ -2260,7 +2269,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
@ -2349,6 +2357,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
},
sensitiveProperties: {
default_gas: '0.000031501',
@ -2359,7 +2370,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
@ -2438,6 +2448,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
},
sensitiveProperties: {
default_gas: '0.000031501',
@ -2448,7 +2461,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
@ -2505,6 +2517,10 @@ describe('Transaction Controller', function () {
securityProviderResponse: {
flagAsDangerous: 0,
},
securityAlertResponse: {
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
},
};
const expectedPayload = {
@ -2529,6 +2545,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
},
sensitiveProperties: {
gas_price: '2',
@ -2537,7 +2556,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
await txController._trackTransactionMetricsEvent(
@ -2601,6 +2619,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
},
sensitiveProperties: {
baz: 3.0,
@ -2611,7 +2632,83 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined,
first_seen: 1624408066355,
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',
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',
transaction_speed_up: false,
ui_customizations: ['flagged_as_malicious'],
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
},
sensitiveProperties: {
baz: 3.0,
@ -2685,7 +2785,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
@ -2749,6 +2848,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: ['flagged_as_safety_unknown'],
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
},
sensitiveProperties: {
baz: 3.0,
@ -2759,7 +2861,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
@ -2831,6 +2932,9 @@ describe('Transaction Controller', function () {
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
security_alert_reason: BlockaidReason.notApplicable,
security_alert_response: BlockaidResultType.NotApplicable,
status: 'unapproved',
},
sensitiveProperties: {
baz: 3.0,
@ -2842,7 +2946,6 @@ describe('Transaction Controller', function () {
transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET,
status: 'unapproved',
estimate_suggested: GasRecommendations.medium,
estimate_used: GasRecommendations.high,
default_estimate: 'medium',

View File

@ -13,6 +13,7 @@ export default function setupEnsIpfsResolver({
provider,
getCurrentChainId,
getIpfsGateway,
getUseAddressBarEnsResolution,
}) {
// install listener
const urlPatterns = supportedTopLevelDomains.map((tld) => `*://*.${tld}/*`);
@ -33,7 +34,12 @@ export default function setupEnsIpfsResolver({
const { tabId, url } = details;
// ignore requests that are not associated with tabs
// only attempt ENS resolution on mainnet
if (tabId === -1 || 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;
}
// parse ens name
@ -50,9 +56,23 @@ export default function setupEnsIpfsResolver({
async function attemptResolve({ tabId, name, pathname, search, fragment }) {
const ipfsGateway = getIpfsGateway();
const useAddressBarEnsResolution = getUseAddressBarEnsResolution();
if (!useAddressBarEnsResolution || ipfsGateway === '') {
return;
}
browser.tabs.update(tabId, { url: `loading.html` });
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 {
const { type, hash } = await resolveEnsToIpfsContentId({
provider,

View File

@ -1,3 +1,7 @@
import {
BlockaidReason,
BlockaidResultType,
} from '../../../../shared/constants/security-provider';
import { createPPOMMiddleware } from './ppom-middleware';
Object.defineProperty(globalThis, 'fetch', {
@ -13,10 +17,16 @@ Object.defineProperty(globalThis, 'performance', {
describe('PPOMMiddleware', () => {
it('should call ppomController.usePPOM for requests of type confirmation', async () => {
const useMock = jest.fn();
const controller = {
const ppomController = {
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(
{ method: 'eth_sendTransaction' },
undefined,
@ -26,25 +36,85 @@ describe('PPOMMiddleware', () => {
});
it('should add validation response on confirmation requests', async () => {
const controller = {
const ppomController = {
usePPOM: async () => Promise.resolve('VALIDATION_RESULT'),
};
const middlewareFunction = createPPOMMiddleware(controller as any);
const req = { method: 'eth_sendTransaction', ppomResponse: undefined };
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.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 () => {
const ppom = {
validateJsonRpc: () => undefined,
};
const controller = {
const ppomController = {
usePPOM: async (callback: any) => {
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();
await middlewareFunction(
{ method: 'eth_sendTransaction' },
@ -55,12 +125,18 @@ describe('PPOMMiddleware', () => {
});
it('should call next method when ppomController.usePPOM throws error', async () => {
const controller = {
const ppomController = {
usePPOM: async (_callback: any) => {
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();
await middlewareFunction(
{ method: 'eth_sendTransaction' },
@ -75,12 +151,18 @@ describe('PPOMMiddleware', () => {
const ppom = {
validateJsonRpc: validateMock,
};
const controller = {
const ppomController = {
usePPOM: async (callback: any) => {
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(
{ method: 'eth_sendTransaction' },
undefined,
@ -94,12 +176,18 @@ describe('PPOMMiddleware', () => {
const ppom = {
validateJsonRpc: validateMock,
};
const controller = {
const ppomController = {
usePPOM: async (callback: any) => {
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(
{ method: 'eth_someRequest' },
undefined,

View File

@ -1,7 +1,14 @@
import { PPOM } from '@blockaid/ppom';
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([
'eth_sendRawTransaction',
'eth_sendTransaction',
@ -23,19 +30,33 @@ const ConfirmationMethods = Object.freeze([
* the request will be forwarded to the next middleware, together with the PPOM response.
*
* @param ppomController - Instance of PPOMController.
* @param preferencesController - Instance of PreferenceController.
* @returns PPOMMiddleware function.
*/
export function createPPOMMiddleware(ppomController: PPOMController) {
export function createPPOMMiddleware(
ppomController: PPOMController,
preferencesController: PreferencesController,
) {
return async (req: any, _res: any, next: () => void) => {
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
req.ppomResponse = await ppomController.usePPOM(async (ppom: PPOM) => {
return ppom.validateJsonRpc(req);
});
req.securityAlertResponse = await ppomController.usePPOM(
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);
req.securityAlertResponse = {
result_type: BlockaidResultType.Failed,
reason: BlockaidReason.failed,
description: 'Validating the confirmation failed by throwing error.',
};
} finally {
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
* is enabled, `false` otherwise.
*/
private getMetaMetricsEnabled: () => boolean;
private getMetaMetricsEnabled: () => Promise<boolean>;
/**
* @param options - Constructor options.
@ -40,7 +40,7 @@ export class FilterEvents implements Integration {
constructor({
getMetaMetricsEnabled,
}: {
getMetaMetricsEnabled: () => boolean;
getMetaMetricsEnabled: () => Promise<boolean>;
}) {
this.getMetaMetricsEnabled = getMetaMetricsEnabled;
}
@ -56,13 +56,13 @@ export class FilterEvents implements Integration {
addGlobalEventProcessor: (callback: EventProcessor) => void,
getCurrentHub: () => Hub,
): void {
addGlobalEventProcessor((currentEvent: SentryEvent) => {
addGlobalEventProcessor(async (currentEvent: SentryEvent) => {
// Sentry integrations use the Sentry hub to get "this" references, for
// reasons I don't fully understand.
// eslint-disable-next-line consistent-this
const self = getCurrentHub().getIntegration(FilterEvents);
if (self) {
if (!self.getMetaMetricsEnabled()) {
if (!(await self.getMetaMetricsEnabled())) {
logger.warn(`Event dropped due to MetaMetrics setting.`);
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
* is enabled, `false` otherwise.
*/
function getMetaMetricsEnabled() {
if (getState) {
const appState = getState();
if (!appState?.store?.metamask?.participateInMetaMetrics) {
return false;
}
} else {
async function getMetaMetricsEnabled() {
const appState = getState();
if (Object.keys(appState) > 0) {
return Boolean(appState?.store?.metamask?.participateInMetaMetrics);
}
try {
const persistedState = await globalThis.stateHooks.getPersistedState();
return Boolean(
persistedState?.data?.MetaMetricsController?.participateInMetaMetrics,
);
} catch (error) {
console.error(error);
return false;
}
return true;
}
Sentry.init({
@ -186,10 +190,10 @@ export default function setupSentry({ release, getState }) {
* opted into MetaMetrics, change the autoSessionTracking option and start
* a new sentry session.
*/
const startSession = () => {
const startSession = async () => {
const hub = Sentry.getCurrentHub?.();
const options = hub.getClient?.().getOptions?.() ?? {};
if (hub && getMetaMetricsEnabled() === true) {
if (hub && (await getMetaMetricsEnabled()) === true) {
options.autoSessionTracking = true;
hub.startSession();
}
@ -200,10 +204,10 @@ export default function setupSentry({ release, getState }) {
* opted out of MetaMetrics, change the autoSessionTracking option and end
* the current sentry session.
*/
const endSession = () => {
const endSession = async () => {
const hub = Sentry.getCurrentHub?.();
const options = hub.getClient?.().getOptions?.() ?? {};
if (hub && getMetaMetricsEnabled() === false) {
if (hub && (await getMetaMetricsEnabled()) === false) {
options.autoSessionTracking = false;
hub.endSession();
}
@ -214,22 +218,22 @@ export default function setupSentry({ release, getState }) {
* on the state of metaMetrics optin and the state of autoSessionTracking on
* the Sentry client.
*/
const toggleSession = () => {
const toggleSession = async () => {
const hub = Sentry.getCurrentHub?.();
const options = hub.getClient?.().getOptions?.() ?? {
autoSessionTracking: false,
};
const isMetaMetricsEnabled = getMetaMetricsEnabled();
const isMetaMetricsEnabled = await getMetaMetricsEnabled();
if (
isMetaMetricsEnabled === true &&
options.autoSessionTracking === false
) {
startSession();
await startSession();
} else if (
isMetaMetricsEnabled === false &&
options.autoSessionTracking === true
) {
endSession();
await endSession();
}
};

View File

@ -1,7 +1,14 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
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 { PermissionsRequestNotFoundError } from '@metamask/permission-controller';
import nock from 'nock';
@ -59,21 +66,28 @@ describe('MetaMaskController', function () {
});
beforeEach(function () {
nock('https://static.metafi.codefi.network')
nock(PHISHING_CONFIG_BASE_URL)
.persist()
.get('/api/v1/lists/stalelist.json')
.get(METAMASK_STALELIST_FILE)
.reply(
200,
JSON.stringify({
version: 2,
tolerance: 2,
fuzzylist: [],
allowlist: [],
blocklist: ['127.0.0.1'],
lastUpdated: 0,
lastUpdated: 1,
eth_phishing_detect_config: {
fuzzylist: [],
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(
200,
JSON.stringify([
@ -110,6 +124,20 @@ describe('MetaMaskController', function () {
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 () {
it('two parallel calls with same accountCount give same result', async function () {
await metamaskController.createNewVaultAndKeychain('test@123');

View File

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

View File

@ -7,6 +7,15 @@ import EthQuery from 'eth-query';
import proxyquire from 'proxyquire';
import browser from 'webextension-polyfill';
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 createTxMeta from '../../test/lib/createTxMeta';
import { NETWORK_TYPES } from '../../shared/constants/network';
@ -152,9 +161,13 @@ const firstTimeState = {
id: NETWORK_CONFIGURATION_ID_1,
},
},
networkDetails: {
EIPS: {
1559: false,
selectedNetworkClientId: NetworkType.mainnet,
networksMetadata: {
[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;
@ -185,25 +210,36 @@ describe('MetaMaskController', function () {
.persist()
.get(/.*/u)
.reply(200, '{"JPY":12415.9}');
nock('https://static.metafi.codefi.network')
nock(PHISHING_CONFIG_BASE_URL)
.persist()
.get('/api/v1/lists/stalelist.json')
.get(METAMASK_STALELIST_FILE)
.reply(
200,
JSON.stringify({
version: 2,
tolerance: 2,
fuzzylist: [],
allowlist: [],
blocklist: ['127.0.0.1'],
lastUpdated: 0,
lastUpdated: 1,
eth_phishing_detect_config: {
fuzzylist: [],
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(
200,
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();
});
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 () {
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 =
metamaskController.keyringController.getKeyringsByType(
metamaskController.coreKeyringController.getKeyringsByType(
KeyringType.imported,
);
const pubAddressHexArr = await simpleKeyrings[0].getAccounts();
@ -566,7 +616,7 @@ describe('MetaMaskController', function () {
.connectHardware(HardwareDeviceNames.trezor, 0)
.catch(() => null);
const keyrings =
await metamaskController.keyringController.getKeyringsByType(
await metamaskController.coreKeyringController.getKeyringsByType(
KeyringType.trezor,
);
assert.deepEqual(
@ -582,7 +632,7 @@ describe('MetaMaskController', function () {
.connectHardware(HardwareDeviceNames.ledger, 0)
.catch(() => null);
const keyrings =
await metamaskController.keyringController.getKeyringsByType(
await metamaskController.coreKeyringController.getKeyringsByType(
KeyringType.ledger,
);
assert.deepEqual(
@ -609,7 +659,7 @@ describe('MetaMaskController', function () {
mnemonic: uint8ArrayMnemonic,
};
sinon
.stub(metamaskController.keyringController, 'getKeyringsByType')
.stub(metamaskController.coreKeyringController, 'getKeyringsByType')
.returns([mockHDKeyring]);
const recoveredMnemonic =
@ -663,7 +713,7 @@ describe('MetaMaskController', function () {
.catch(() => null);
await metamaskController.forgetDevice(HardwareDeviceNames.trezor);
const keyrings =
await metamaskController.keyringController.getKeyringsByType(
await metamaskController.coreKeyringController.getKeyringsByType(
KeyringType.trezor,
);
@ -726,7 +776,7 @@ describe('MetaMaskController', function () {
it('should set unlockedAccount in the keyring', async function () {
const keyrings =
await metamaskController.keyringController.getKeyringsByType(
await metamaskController.coreKeyringController.getKeyringsByType(
KeyringType.trezor,
);
assert.equal(keyrings[0].unlockedAccount, accountToUnlock);
@ -859,7 +909,10 @@ describe('MetaMaskController', function () {
sinon.stub(metamaskController.keyringController, 'removeAccount');
sinon.stub(metamaskController, 'removeAllAccountPermissions');
sinon
.stub(metamaskController.keyringController, 'getKeyringForAccount')
.stub(
metamaskController.coreKeyringController,
'getKeyringForAccount',
)
.returns(Promise.resolve(mockKeyring));
ret = await metamaskController.removeAccount(addressToRemove);
@ -906,9 +959,9 @@ describe('MetaMaskController', function () {
it('should return address', async function () {
assert.equal(ret, '0x1');
});
it('should call keyringController.getKeyringForAccount', async function () {
it('should call coreKeyringController.getKeyringForAccount', async function () {
assert(
metamaskController.keyringController.getKeyringForAccount.calledWith(
metamaskController.coreKeyringController.getKeyringForAccount.calledWith(
addressToRemove,
),
);
@ -931,7 +984,7 @@ describe('MetaMaskController', function () {
it('sets up phishing stream for untrusted communication', async function () {
const phishingMessageSender = {
url: 'http://myethereumwalletntw.com',
url: 'http://test.metamask-phishing.io',
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