Merge branch 'develop' of github.com:MetaMask/metamask-extension into minimal
@ -3,13 +3,19 @@ version: 2.1
|
||||
executors:
|
||||
node-browsers:
|
||||
docker:
|
||||
- image: circleci/node:16-browsers
|
||||
- image: cimg/node:16.20-browsers
|
||||
node-browsers-medium-plus:
|
||||
docker:
|
||||
- image: circleci/node:16-browsers
|
||||
- image: cimg/node:16.20-browsers
|
||||
resource_class: medium+
|
||||
environment:
|
||||
NODE_OPTIONS: --max_old_space_size=2048
|
||||
node-browsers-large:
|
||||
docker:
|
||||
- image: cimg/node:16.20-browsers
|
||||
resource_class: large
|
||||
environment:
|
||||
NODE_OPTIONS: --max_old_space_size=2048
|
||||
shellcheck:
|
||||
docker:
|
||||
- image: koalaman/shellcheck-alpine@sha256:dfaf08fab58c158549d3be64fb101c626abc5f16f341b569092577ae207db199
|
||||
@ -500,7 +506,7 @@ jobs:
|
||||
- builds-test
|
||||
|
||||
prep-build-storybook:
|
||||
executor: node-browsers-medium-plus
|
||||
executor: node-browsers-large
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
@ -551,7 +557,7 @@ jobs:
|
||||
command: yarn verify-locales --quiet
|
||||
|
||||
test-storybook:
|
||||
executor: node-browsers-medium-plus
|
||||
executor: node-browsers-large
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
|
@ -4,6 +4,8 @@ set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
sudo apt-get update
|
||||
|
||||
# To get the latest version, see <https://www.ubuntuupdates.org/ppa/google_chrome?dist=stable>
|
||||
CHROME_VERSION='111.0.5563.64-1'
|
||||
CHROME_BINARY="google-chrome-stable_${CHROME_VERSION}_amd64.deb"
|
||||
|
@ -9,10 +9,6 @@ FIREFOX_BINARY="firefox-${FIREFOX_VERSION}.tar.bz2"
|
||||
FIREFOX_BINARY_URL="https://ftp.mozilla.org/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/${FIREFOX_BINARY}"
|
||||
FIREFOX_PATH='/opt/firefox'
|
||||
|
||||
printf '%s\n' "Removing old Firefox installation"
|
||||
|
||||
sudo rm -r "${FIREFOX_PATH}"
|
||||
|
||||
printf '%s\n' "Downloading & installing Firefox ${FIREFOX_VERSION}"
|
||||
|
||||
wget --quiet --show-progress -O- "${FIREFOX_BINARY_URL}" | sudo tar xj -C /opt
|
||||
|
@ -10,6 +10,14 @@ ignores:
|
||||
- '@fortawesome/fontawesome-free'
|
||||
- 'punycode'
|
||||
|
||||
#
|
||||
# snaps flask deps
|
||||
#
|
||||
- '@metamask/rpc-methods-flask'
|
||||
- '@metamask/snaps-controllers-flask'
|
||||
- '@metamask/snaps-ui-flask'
|
||||
- '@metamask/snaps-utils-flask'
|
||||
|
||||
#
|
||||
# dev deps
|
||||
#
|
||||
@ -40,11 +48,14 @@ ignores:
|
||||
- 'improved-yarn-audit'
|
||||
- 'nyc'
|
||||
# storybook
|
||||
- '@storybook/cli'
|
||||
- '@storybook/core'
|
||||
- '@storybook/addon-essentials'
|
||||
- '@storybook/addon-a11y'
|
||||
- '@storybook/addon-mdx-gfm'
|
||||
- '@storybook/builder-webpack5'
|
||||
- '@storybook/manager-webpack5'
|
||||
- '@storybook/react-webpack5'
|
||||
- 'storybook-dark-mode'
|
||||
- '@whitespace/storybook-addon-html'
|
||||
- 'react-syntax-highlighter'
|
||||
|
14
.github/CODEOWNERS
vendored
@ -1,20 +1,22 @@
|
||||
# Lines starting with '#' are comments.
|
||||
# Each line is a file pattern followed by one or more owners.
|
||||
# Owners bear a responsibility to the organization and the users of this
|
||||
# application. Repository administrators have the ability to merge pull
|
||||
# requests that have not yet received the requisite reviews as outlined
|
||||
# in this file. Do not force merge any PR without confidence that it
|
||||
# follows all policies or without full understanding of the impact of
|
||||
# application. Repository administrators have the ability to merge pull
|
||||
# requests that have not yet received the requisite reviews as outlined
|
||||
# in this file. Do not force merge any PR without confidence that it
|
||||
# follows all policies or without full understanding of the impact of
|
||||
# those changes on build, release and publishing outcomes.
|
||||
|
||||
* @MetaMask/extension-devs
|
||||
**/snaps/** @MetaMask/snaps-devs
|
||||
**/flask/** @MetaMask/snaps-devs
|
||||
development/ @MetaMask/extension-devs @kumavis
|
||||
lavamoat/ @MetaMask/supply-chain
|
||||
lavamoat/ @MetaMask/extension-devs @MetaMask/supply-chain @MetaMask/snaps-devs
|
||||
|
||||
# The .circleci/ folder instructs Circle CI on the process by which it
|
||||
# should test, build and publish releases of our application. Due to the
|
||||
# impact that changes to the files contained within this folder may have
|
||||
# on our releases, only those with the knowledge and responsibility to
|
||||
# on our releases, only those with the knowledge and responsibility to
|
||||
# publish libraries under the MetaMask name may approve those changes.
|
||||
# Note to reviewers: We employ the use of CircleCI "Orbs", which are
|
||||
# remotely hosted sections of CircleCI configuration and scripts, to
|
||||
|
177
.github/scripts/label-prs.ts
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
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> {
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
|
||||
if (!token) {
|
||||
core.setFailed('GITHUB_TOKEN not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const octokit = getOctokit(token);
|
||||
|
||||
const headRef = context.payload.pull_request?.head.ref || '';
|
||||
|
||||
let issueNumber = await getIssueNumberFromPullRequestBody();
|
||||
if (issueNumber === "") {
|
||||
bailIfIsBranchNameInvalid(headRef);
|
||||
bailIfIsNotFeatureBranch(headRef);
|
||||
issueNumber = getIssueNumberFromBranchName(headRef);
|
||||
}
|
||||
|
||||
if (Number(issueNumber) === 0) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
await updateLabels(octokit, issueNumber);
|
||||
}
|
||||
|
||||
async function getIssueNumberFromPullRequestBody(): Promise<string> {
|
||||
console.log("Checking if the PR's body references an issue...");
|
||||
|
||||
let ISSUE_LINK_IN_PR_DESCRIPTION_REGEX =
|
||||
/(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s#\d+/gi;
|
||||
|
||||
const prBody = await getPullRequestBody();
|
||||
|
||||
let matches = prBody.match(ISSUE_LINK_IN_PR_DESCRIPTION_REGEX);
|
||||
if (!matches || matches?.length === 0) {
|
||||
console.log(
|
||||
'No direct link can be drawn between the PR and an issue from the PR body because no issue number was referenced.',
|
||||
);
|
||||
return "";
|
||||
}
|
||||
|
||||
if (matches?.length > 1) {
|
||||
console.log(
|
||||
'No direct link can be drawn between the PR and an issue from the PR body because more than one issue number was referenced.',
|
||||
);
|
||||
return "";
|
||||
}
|
||||
|
||||
const ISSUE_NUMBER_REGEX = /\d+/;
|
||||
const issueNumber = matches[0].match(ISSUE_NUMBER_REGEX)?.[0] || '';
|
||||
|
||||
console.log(`Found issue number ${issueNumber} in PR body.`);
|
||||
|
||||
return issueNumber;
|
||||
}
|
||||
|
||||
async function getPullRequestBody(): Promise<string> {
|
||||
if (context.eventName !== 'pull_request') {
|
||||
console.log('This action should only run on pull_request events.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const prBody = context.payload.pull_request?.body || '';
|
||||
return prBody;
|
||||
}
|
||||
|
||||
function bailIfIsBranchNameInvalid(branchName: string): void {
|
||||
const BRANCH_REGEX =
|
||||
/^(main|develop|(ci|chore|docs|feat|feature|fix|perf|refactor|revert|style)\/\d*(?:[-](?![-])\w*)*|Version-v\d+\.\d+\.\d+)$/;
|
||||
const isValidBranchName = new RegExp(BRANCH_REGEX).test(branchName);
|
||||
|
||||
if (!isValidBranchName) {
|
||||
console.log('This branch name does not follow the convention.');
|
||||
console.log(
|
||||
'Here are some example branch names that are accepted: "fix/123-description", "feat/123-longer-description", "feature/123", "main", "develop", "Version-v10.24.2".',
|
||||
);
|
||||
console.log(
|
||||
'No issue could be linked to this PR, so no labels were copied',
|
||||
);
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
function bailIfIsNotFeatureBranch(branchName: string): void {
|
||||
if (
|
||||
branchName === 'main' ||
|
||||
branchName === 'develop' ||
|
||||
branchName.startsWith('Version-v')
|
||||
) {
|
||||
console.log(`${branchName} is not a feature branch.`);
|
||||
console.log(
|
||||
'No issue could be linked to this PR, so no labels were copied',
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateLabels(octokit: InstanceType<typeof GitHub>, issueNumber: string): Promise<void> {
|
||||
interface ILabel {
|
||||
name: string;
|
||||
};
|
||||
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
|
||||
const issue = await octokit.rest.issues.get({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: Number(issueNumber),
|
||||
});
|
||||
|
||||
const getNameFromLabel = (label: ILabel): string => label.name
|
||||
|
||||
const issueLabels = issue.data.labels.map(label => getNameFromLabel(label as ILabel));
|
||||
|
||||
const prNumber = context.payload.number;
|
||||
|
||||
const pr = await octokit.rest.issues.get({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: prNumber,
|
||||
});
|
||||
|
||||
const startingPRLabels = pr.data.labels.map(label => getNameFromLabel(label as ILabel));
|
||||
|
||||
const dedupedFinalPRLabels = [
|
||||
...new Set([...startingPRLabels, ...issueLabels]),
|
||||
];
|
||||
|
||||
const hasIssueAdditionalLabels = !sortedArrayEqual(
|
||||
startingPRLabels,
|
||||
dedupedFinalPRLabels,
|
||||
);
|
||||
if (hasIssueAdditionalLabels) {
|
||||
await octokit.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
labels: dedupedFinalPRLabels,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getIssueNumberFromBranchName(branchName: string): string {
|
||||
console.log('Checking if the branch name references an issue...');
|
||||
|
||||
let issueNumber: string;
|
||||
if (branchName.split('/').length > 1) {
|
||||
issueNumber = branchName.split('/')[1].split('-')[0];
|
||||
} else {
|
||||
issueNumber = branchName.split('-')[0];
|
||||
}
|
||||
|
||||
console.log(`Found issue number ${issueNumber} in branch name.`);
|
||||
|
||||
return issueNumber;
|
||||
}
|
||||
|
||||
function sortedArrayEqual(array1: string[], array2: string[]): boolean {
|
||||
const lengthsAreEqual = array1.length === array2.length;
|
||||
const everyElementMatchesByIndex = array1.every(
|
||||
(value: string, index: number): boolean => value === array2[index],
|
||||
);
|
||||
|
||||
return lengthsAreEqual && everyElementMatchesByIndex;
|
||||
}
|
9
.github/workflows/fitness-functions.yml
vendored
@ -11,6 +11,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
@ -24,6 +26,9 @@ jobs:
|
||||
env:
|
||||
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||
run: |
|
||||
git fetch origin $BASE_REF
|
||||
git diff origin/$BASE_REF HEAD -- . > diff
|
||||
# The following command generates a diff of changes between the common
|
||||
# ancestor of $BASE_REF and HEAD, and the current commit (HEAD), for
|
||||
# files in the current directory and its subdirectories. The output is
|
||||
# then saved to a file called "diff".
|
||||
git diff "$(git merge-base "origin/$BASE_REF" HEAD)" HEAD -- . > ./diff
|
||||
npm run fitness-functions -- "ci" "./diff"
|
27
.github/workflows/label-prs.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
name: Label PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [assigned, opened, edited, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
label-pr:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
- name: Install Yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
15
.iyarc
@ -1,21 +1,6 @@
|
||||
# improved-yarn-audit advisory exclusions
|
||||
GHSA-257v-vj4p-3w2h
|
||||
|
||||
# yarn berry's `yarn npm audit` script reports the following vulnerability but
|
||||
# it is a false positive. The offending version of 'ws' that is installed is
|
||||
# 7.1.1 and is included only via remote-redux-devtools which is a devDependency
|
||||
GHSA-6fc8-4gx4-v693
|
||||
|
||||
# yarn npm audit reports on a fast-json-patch version < 3.1.1 but due to patch
|
||||
# resolution, the only version of fast-json-patch that we use is 3.1.1. We also
|
||||
# have 2.2.1 installed but it is a dev only dependency. The "violation" reports
|
||||
# smart-transacton-controller as the culprit but if you run
|
||||
# `yarn info -A -R dependents fast-json-patch` you can see that only 2.2.1 and
|
||||
# 3.3.1 are installed and that smart-transaction-controller resolves to the
|
||||
# patched version of 3.3.1. We can remove this once the
|
||||
# smart-transaction-controller updates its dependency.
|
||||
GHSA-8gh8-hqwg-xf34
|
||||
|
||||
# request library is subject to SSRF.
|
||||
# addressed by temporary patch in .yarn/patches/request-npm-2.88.2-f4a57c72c4.patch
|
||||
GHSA-p8p7-x288-28g6
|
||||
|
@ -1,49 +1,39 @@
|
||||
const path = require('path');
|
||||
|
||||
const { ProvidePlugin } = require('webpack');
|
||||
const {
|
||||
ProvidePlugin
|
||||
} = require('webpack');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const { generateIconNames } = require('../development/generate-icon-names');
|
||||
|
||||
const {
|
||||
generateIconNames
|
||||
} = require('../development/generate-icon-names');
|
||||
module.exports = {
|
||||
core: {
|
||||
builder: 'webpack5',
|
||||
disableTelemetry: true
|
||||
},
|
||||
features: { buildStoriesJson: true },
|
||||
stories: [
|
||||
'../ui/**/*.stories.js',
|
||||
'../ui/**/*.stories.tsx',
|
||||
'../ui/**/*.stories.mdx',
|
||||
'./*.stories.mdx',
|
||||
],
|
||||
addons: [
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-actions',
|
||||
'@storybook/addon-a11y',
|
||||
'@storybook/addon-knobs',
|
||||
'./i18n-party-addon/register.js',
|
||||
'storybook-dark-mode',
|
||||
'@whitespace/storybook-addon-html'
|
||||
],
|
||||
features: {
|
||||
buildStoriesJson: true
|
||||
},
|
||||
stories: ['../ui/**/*.stories.js', '../ui/**/*.stories.tsx', '../ui/**/*.stories.mdx', './*.stories.mdx'],
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-actions', '@storybook/addon-a11y', '@storybook/addon-knobs', './i18n-party-addon/register.js', 'storybook-dark-mode', '@whitespace/storybook-addon-html', '@storybook/addon-mdx-gfm'],
|
||||
staticDirs: ['../app', './images'],
|
||||
// Uses babel.config.js settings and prevents "Missing class properties transform" error
|
||||
babel: async (options) => ({ overrides: options.overrides }),
|
||||
babel: async options => ({
|
||||
overrides: options.overrides
|
||||
}),
|
||||
// Sets env variables https://storybook.js.org/docs/react/configure/environment-variables/
|
||||
env: async (config) => {
|
||||
env: async config => {
|
||||
return {
|
||||
...config,
|
||||
// Creates the icon names environment variable for the component-library/icon/icon.js component
|
||||
ICON_NAMES: generateIconNames(),
|
||||
ICON_NAMES: generateIconNames()
|
||||
};
|
||||
},
|
||||
webpackFinal: async (config) => {
|
||||
webpackFinal: async config => {
|
||||
config.context = process.cwd();
|
||||
config.node = {
|
||||
__filename: true,
|
||||
__filename: true
|
||||
};
|
||||
config.resolve.alias['webextension-polyfill'] = require.resolve(
|
||||
'../ui/__mocks__/webextension-polyfill.js',
|
||||
);
|
||||
config.resolve.alias['webextension-polyfill'] = require.resolve('../ui/__mocks__/webextension-polyfill.js');
|
||||
config.resolve.fallback = {
|
||||
child_process: false,
|
||||
constants: false,
|
||||
@ -54,52 +44,45 @@ module.exports = {
|
||||
os: false,
|
||||
path: false,
|
||||
stream: require.resolve('stream-browserify'),
|
||||
zlib: false,
|
||||
_stream_transform: require.resolve('readable-stream/lib/_stream_transform.js'),
|
||||
};
|
||||
config.module.strictExportPresence = true;
|
||||
config.module.rules.push({
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
import: false,
|
||||
url: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
implementation: require('sass'),
|
||||
sassOptions: {
|
||||
includePaths: ['ui/css/'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
use: ['style-loader', {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
import: false,
|
||||
url: false
|
||||
}
|
||||
}, {
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
implementation: require('sass'),
|
||||
sassOptions: {
|
||||
includePaths: ['ui/css/']
|
||||
}
|
||||
}
|
||||
}]
|
||||
});
|
||||
config.plugins.push(
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.join(
|
||||
'node_modules',
|
||||
'@fortawesome',
|
||||
'fontawesome-free',
|
||||
'webfonts',
|
||||
),
|
||||
to: path.join('fonts', 'fontawesome'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
config.plugins.push(
|
||||
new ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
}),
|
||||
);
|
||||
config.plugins.push(new CopyWebpackPlugin({
|
||||
patterns: [{
|
||||
from: path.join('node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'),
|
||||
to: path.join('fonts', 'fontawesome')
|
||||
}]
|
||||
}));
|
||||
config.plugins.push(new ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer']
|
||||
}));
|
||||
return config;
|
||||
},
|
||||
docs: {
|
||||
autodocs: true
|
||||
},
|
||||
framework: {
|
||||
name: '@storybook/react-webpack5',
|
||||
options: {}
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,9 @@
|
||||
/*
|
||||
* The addParameters and addDecorator APIs to add global decorators and parameters, exported by the various frameworks (e.g. @storybook/react) and @storybook/client were deprecated in 6.0 and have been removed in 7.0.
|
||||
|
||||
Instead, use export const parameters = {}; and export const decorators = []; in your .storybook/preview.js. Addon authors similarly should use such an export in a preview entry file (see Preview entries).
|
||||
* */
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { addDecorator, addParameters } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from '../ui/store/store';
|
||||
@ -13,9 +17,9 @@ import { Router } from 'react-router-dom';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { _setBackgroundConnection } from '../ui/store/action-queue';
|
||||
import MetaMaskStorybookTheme from './metamask-storybook-theme';
|
||||
import addons from '@storybook/addons';
|
||||
import { addons } from '@storybook/addons';
|
||||
|
||||
addParameters({
|
||||
export const parameters = {
|
||||
backgrounds: {
|
||||
default: 'default',
|
||||
values: [
|
||||
@ -41,7 +45,7 @@ addParameters({
|
||||
controls: {
|
||||
expanded: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const globalTypes = {
|
||||
locale: {
|
||||
@ -117,4 +121,6 @@ const metamaskDecorator = (story, context) => {
|
||||
);
|
||||
};
|
||||
|
||||
addDecorator(metamaskDecorator);
|
||||
export const decorators = [
|
||||
metamaskDecorator,
|
||||
];
|
||||
|
@ -598,8 +598,8 @@ const state = {
|
||||
nextNonce: 71,
|
||||
connectedStatusPopoverHasBeenShown: true,
|
||||
swapsWelcomeMessageHasBeenShown: true,
|
||||
defaultHomeActiveTabName: 'Assets',
|
||||
provider: {
|
||||
defaultHomeActiveTabName: 'Tokens',
|
||||
providerConfig: {
|
||||
type: 'goerli',
|
||||
ticker: 'ETH',
|
||||
nickname: '',
|
||||
|
12
.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch
Normal file
@ -0,0 +1,12 @@
|
||||
diff --git a/lib/index.js b/lib/index.js
|
||||
index c991f62dc64553502e9911a7f21e77e008d7f438..e503c7494d21b13df85b10e1657b2af8ca4d964f 100644
|
||||
--- a/lib/index.js
|
||||
+++ b/lib/index.js
|
||||
@@ -222,7 +222,6 @@ var _transform = require("./transform");
|
||||
var _transformFile = require("./transform-file");
|
||||
var _transformAst = require("./transform-ast");
|
||||
var _parse = require("./parse");
|
||||
-var thisFile = require("./index");
|
||||
const version = "7.21.5";
|
||||
exports.version = version;
|
||||
const DEFAULT_EXTENSIONS = Object.freeze([".js", ".jsx", ".es6", ".es", ".mjs", ".cjs"]);
|
59
.yarn/patches/@eslint-eslintrc-npm-2.0.2-d308674d86.patch
Normal file
@ -0,0 +1,38 @@
|
||||
diff --git a/dist/AssetsContractController.js b/dist/AssetsContractController.js
|
||||
index 332c87d7cc5687dec4d80ec3c51dcc4bda632c32..1d88cd313efb00c9c9b57f6824ed74bbca6349e3 100644
|
||||
--- a/dist/AssetsContractController.js
|
||||
+++ b/dist/AssetsContractController.js
|
||||
@@ -33,6 +33,7 @@ exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = {
|
||||
[assetsUtil_1.SupportedTokenDetectionNetworks.bsc]: '0x2352c63A83f9Fd126af8676146721Fa00924d7e4',
|
||||
[assetsUtil_1.SupportedTokenDetectionNetworks.polygon]: '0x2352c63A83f9Fd126af8676146721Fa00924d7e4',
|
||||
[assetsUtil_1.SupportedTokenDetectionNetworks.avax]: '0xD023D153a0DFa485130ECFdE2FAA7e612EF94818',
|
||||
+ [assetsUtil_1.SupportedTokenDetectionNetworks.aurora]: '0x1286415D333855237f89Df27D388127181448538',
|
||||
};
|
||||
exports.MISSING_PROVIDER_ERROR = 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available';
|
||||
/**
|
||||
diff --git a/dist/assetsUtil.d.ts b/dist/assetsUtil.d.ts
|
||||
index 1b69903a3f8e19f5a451c94791ea16fb18e94d93..d45fe80652fea24d19b2b30dd9af7e6219939153 100644
|
||||
--- a/dist/assetsUtil.d.ts
|
||||
+++ b/dist/assetsUtil.d.ts
|
||||
@@ -45,7 +45,8 @@ export declare enum SupportedTokenDetectionNetworks {
|
||||
mainnet = "1",
|
||||
bsc = "56",
|
||||
polygon = "137",
|
||||
- avax = "43114"
|
||||
+ avax = "43114",
|
||||
+ aurora = "1313161554"
|
||||
}
|
||||
/**
|
||||
* Check if token detection is enabled for certain networks.
|
||||
diff --git a/dist/assetsUtil.js b/dist/assetsUtil.js
|
||||
index 4b54e82504cdfae1d937860b0b89b14860385b8e..232228688454d32df818065119dff104f5b0c16c 100644
|
||||
--- a/dist/assetsUtil.js
|
||||
+++ b/dist/assetsUtil.js
|
||||
@@ -120,6 +120,7 @@ var SupportedTokenDetectionNetworks;
|
||||
SupportedTokenDetectionNetworks["bsc"] = "56";
|
||||
SupportedTokenDetectionNetworks["polygon"] = "137";
|
||||
SupportedTokenDetectionNetworks["avax"] = "43114";
|
||||
+ SupportedTokenDetectionNetworks["aurora"] = "1313161554";
|
||||
})(SupportedTokenDetectionNetworks = exports.SupportedTokenDetectionNetworks || (exports.SupportedTokenDetectionNetworks = {}));
|
||||
/**
|
||||
* Check if token detection is enabled for certain networks.
|
@ -1,8 +1,8 @@
|
||||
diff --git a/lib/linter/linter.js b/lib/linter/linter.js
|
||||
index 29d78da3969e2a3560d056af5683a08083562984..a6ae07b7142a353fcd8d58b55a7e68b8f81b2846 100644
|
||||
index 0f1bd4f77611aa5ccc43c94385efd4f9b5639327..59ff9f14727e22bbec2cc7539af30305f759b23a 100644
|
||||
--- a/lib/linter/linter.js
|
||||
+++ b/lib/linter/linter.js
|
||||
@@ -704,7 +704,7 @@ function createLanguageOptions({ globals: configuredGlobals, parser, parserOptio
|
||||
@@ -708,7 +708,7 @@ function createLanguageOptions({ globals: configuredGlobals, parser, parserOptio
|
||||
*/
|
||||
function resolveGlobals(providedGlobals, enabledEnvironments) {
|
||||
return Object.assign(
|
46
CHANGELOG.md
@ -6,6 +6,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [10.30.4]
|
||||
### Fixed
|
||||
- Fix error upon submitting multiple requests that require approval ([#19050](https://github.com/MetaMask/metamask-extension/pull/19050))
|
||||
- The affected requests were `eth_sendTransaction`, `wallet_watchAsset`, `eth_getEncryptionPublicKey`, and `eth_decrypt`
|
||||
|
||||
## [10.30.3]
|
||||
### Fixed
|
||||
- Restore support for chains that return hex or number responses to `net_version` ([#19156](https://github.com/MetaMask/metamask-extension/pull/19156))
|
||||
|
||||
## [10.30.2]
|
||||
### Changed
|
||||
- Improve `eth_signTypedData_v4` validation ([#19110](https://github.com/MetaMask/metamask-extension/pull/19110))
|
||||
|
||||
### Fixed
|
||||
- Fix crash when confirming an approval where the `maxPriorityFeePerGas` is zero ([#19102](https://github.com/MetaMask/metamask-extension/pull/19102))
|
||||
|
||||
## [10.30.1]
|
||||
### Fixed
|
||||
- Disable Flask RPC test to fix failing build ([#19011](https://github.com/MetaMask/metamask-extension/pull/19011))
|
||||
|
||||
## [10.30.0]
|
||||
### Added
|
||||
- Updating Terms of Use, Adding popover and onboarding flow check ([#18221](https://github.com/MetaMask/metamask-extension/pull/18221))
|
||||
|
||||
### Changed
|
||||
- Update ethereum logo icon ([#18528](https://github.com/MetaMask/metamask-extension/pull/18528))
|
||||
- Update send icon ([#18411](https://github.com/MetaMask/metamask-extension/pull/18411))
|
||||
- Disabling network and account changes after the send flow is initiated ([#18086](https://github.com/MetaMask/metamask-extension/pull/18086))
|
||||
- [FLASK] Redesign `dropdown-tab` ([#18546](https://github.com/MetaMask/metamask-extension/pull/18546))
|
||||
- New reusable gas-display component ([#17976](https://github.com/MetaMask/metamask-extension/pull/17976))
|
||||
- "Insufficient balance for gas" error no longer prevents from continuing to confirm transaction screen ([#18554](https://github.com/MetaMask/metamask-extension/pull/18554))
|
||||
|
||||
### Removed
|
||||
- Remove mobile sync feature ([#18692](https://github.com/MetaMask/metamask-extension/pull/18692))
|
||||
|
||||
### Fixed
|
||||
- Fix ability to close "NFT successful import" modal ([#18504](https://github.com/MetaMask/metamask-extension/pull/18504))
|
||||
- Fix "Unable to determine contract standard" error ([#18300](https://github.com/MetaMask/metamask-extension/pull/18300))
|
||||
|
||||
## [10.29.0]
|
||||
### Added
|
||||
- [FLASK] Redesign snaps permission screens ([#18372](https://github.com/MetaMask/metamask-extension/pull/18372))
|
||||
@ -3688,7 +3727,12 @@ 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.29.0...HEAD
|
||||
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.30.4...HEAD
|
||||
[10.30.4]: https://github.com/MetaMask/metamask-extension/compare/v10.30.3...v10.30.4
|
||||
[10.30.3]: https://github.com/MetaMask/metamask-extension/compare/v10.30.2...v10.30.3
|
||||
[10.30.2]: https://github.com/MetaMask/metamask-extension/compare/v10.30.1...v10.30.2
|
||||
[10.30.1]: https://github.com/MetaMask/metamask-extension/compare/v10.30.0...v10.30.1
|
||||
[10.30.0]: https://github.com/MetaMask/metamask-extension/compare/v10.29.0...v10.30.0
|
||||
[10.29.0]: https://github.com/MetaMask/metamask-extension/compare/v10.28.3...v10.29.0
|
||||
[10.28.3]: https://github.com/MetaMask/metamask-extension/compare/v10.28.2...v10.28.3
|
||||
[10.28.2]: https://github.com/MetaMask/metamask-extension/compare/v10.28.1...v10.28.2
|
||||
|
12
app/_locales/de/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Asset-Optionen"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Vermögenswerte"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Wenn Sie versuchen, Assets direkt von einem Netzwerk in ein anderes zu senden, kann dies zu einem dauerhaften Asset-Verlust führen. Verwenden Sie unbedingt eine Bridge."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "Verlauf"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "Halten, um GWP anzuzeigen"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Ihre geheime Wiederherstellungsphrase bietet $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "Betrüger aber schon.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "Bewahren Sie Ihre GWP sicher auf"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Alle ignorieren"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "Was Sie beachten sollten:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "Dies basiert auf Informationen von "
|
||||
},
|
||||
"time": {
|
||||
"message": "Zeit"
|
||||
},
|
||||
|
12
app/_locales/el/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Επιλογές περιουσιακών στοιχείων"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Περιουσιακά στοιχεία"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Εάν επιχειρήσετε να στείλετε περιουσιακά στοιχεία απευθείας από ένα δίκτυο σε ένα άλλο, αυτό ενδέχεται να οδηγήσει σε μόνιμη απώλεια περιουσιακών στοιχείων. Βεβαιωθείτε ότι χρησιμοποιείτε μια διασύνδεση."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "Ιστορικό"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "Κρατήστε πατημένο για αποκάλυψη της ΜΦΑ"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Η Μυστική σας Φράση Ανάκτησης παρέχει $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "αλλά οι απατεώνες μπορεί να το κάνουν.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "Κρατήστε τη ΜΦΑ σας ασφαλή"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Αγνόηση όλων"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "Πράγματα που πρέπει να έχετε υπόψη σας:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "Αυτό βασίζεται σε πληροφορίες από"
|
||||
},
|
||||
"time": {
|
||||
"message": "Ώρα"
|
||||
},
|
||||
|
137
app/_locales/en/messages.json
generated
@ -103,9 +103,6 @@
|
||||
"SIWEWarningTitle": {
|
||||
"message": "Are you sure?"
|
||||
},
|
||||
"ShowMore": {
|
||||
"message": "Show more"
|
||||
},
|
||||
"about": {
|
||||
"message": "About"
|
||||
},
|
||||
@ -361,6 +358,9 @@
|
||||
"message": "Allow $1 to withdraw and spend up to the following amount:",
|
||||
"description": "The url of the site that requested permission to 'withdraw and spend'"
|
||||
},
|
||||
"amlCompliance": {
|
||||
"message": "AML/CFT Compliance"
|
||||
},
|
||||
"amount": {
|
||||
"message": "Amount"
|
||||
},
|
||||
@ -441,9 +441,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Asset options"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Assets"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "If you attempt to send assets directly from one network to another, this may result in permanent asset loss. Make sure to use a bridge."
|
||||
},
|
||||
@ -555,6 +552,9 @@
|
||||
"message": "View account at $1",
|
||||
"description": "$1 replaced by URL for custom block explorer"
|
||||
},
|
||||
"blockaid": {
|
||||
"message": "Blockaid"
|
||||
},
|
||||
"blockies": {
|
||||
"message": "Blockies"
|
||||
},
|
||||
@ -670,6 +670,9 @@
|
||||
"coingecko": {
|
||||
"message": "CoinGecko"
|
||||
},
|
||||
"compliance": {
|
||||
"message": "Compliance"
|
||||
},
|
||||
"complianceActivatedDesc": {
|
||||
"message": "You can now use compliance in MetaMask Institutional. Receiving AML/CFT analysis within the confirmation screen on all the addresses you interact with."
|
||||
},
|
||||
@ -730,6 +733,9 @@
|
||||
"connectAccountOrCreate": {
|
||||
"message": "Connect account or create new"
|
||||
},
|
||||
"connectCustodialAccountMenu": {
|
||||
"message": "Connect Custodial Account"
|
||||
},
|
||||
"connectCustodialAccountMsg": {
|
||||
"message": "Please choose the custodian you want to connect in order to add or refresh a token."
|
||||
},
|
||||
@ -842,10 +848,10 @@
|
||||
"message": "NFT contract"
|
||||
},
|
||||
"contractRequestingAccess": {
|
||||
"message": "Contract requesting access"
|
||||
"message": "Third party requesting access"
|
||||
},
|
||||
"contractRequestingSignature": {
|
||||
"message": "Contract requesting signature"
|
||||
"message": "Third party requesting signature"
|
||||
},
|
||||
"contractRequestingSpendingCap": {
|
||||
"message": "Third party requesting spending cap"
|
||||
@ -986,6 +992,9 @@
|
||||
"custodySessionExpired": {
|
||||
"message": "Custodian session expired."
|
||||
},
|
||||
"custodyWrongChain": {
|
||||
"message": "This account is not set up for use with $1"
|
||||
},
|
||||
"custom": {
|
||||
"message": "Advanced"
|
||||
},
|
||||
@ -1415,6 +1424,9 @@
|
||||
"enterPasswordContinue": {
|
||||
"message": "Enter password to continue"
|
||||
},
|
||||
"enterYourPassword": {
|
||||
"message": "Enter your password"
|
||||
},
|
||||
"errorCode": {
|
||||
"message": "Code: $1",
|
||||
"description": "Displayed error code for debugging purposes. $1 is the error code"
|
||||
@ -1748,9 +1760,6 @@
|
||||
"history": {
|
||||
"message": "History"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "Hold to reveal SRP"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Your Secret Recovery Phrase provides $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1771,9 +1780,28 @@
|
||||
"message": "but phishers might.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"holdToRevealContentPrivateKey1": {
|
||||
"message": "Your Private Key provides $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'"
|
||||
},
|
||||
"holdToRevealContentPrivateKey2": {
|
||||
"message": "full access to your wallet and funds.",
|
||||
"description": "Is the bolded text in 'holdToRevealContentPrivateKey2'"
|
||||
},
|
||||
"holdToRevealLockedLabel": { "message": "hold to reveal circle locked" },
|
||||
"holdToRevealPrivateKey": {
|
||||
"message": "Hold to reveal Private Key"
|
||||
},
|
||||
"holdToRevealPrivateKeyTitle": {
|
||||
"message": "Keep your private key safe"
|
||||
},
|
||||
"holdToRevealSRP": {
|
||||
"message": "Hold to reveal SRP"
|
||||
},
|
||||
"holdToRevealSRPTitle": {
|
||||
"message": "Keep your SRP safe"
|
||||
},
|
||||
"holdToRevealUnlockedLabel": { "message": "hold to reveal circle unlocked" },
|
||||
"id": {
|
||||
"message": "Id"
|
||||
},
|
||||
@ -2284,6 +2312,9 @@
|
||||
"networkIsBusy": {
|
||||
"message": "Network is busy. Gas prices are high and estimates are less accurate."
|
||||
},
|
||||
"networkMenu": {
|
||||
"message": "Network Menu"
|
||||
},
|
||||
"networkMenuHeading": {
|
||||
"message": "Select a network"
|
||||
},
|
||||
@ -2337,6 +2368,10 @@
|
||||
"message": "Gas fees are $1 relative to the past 72 hours.",
|
||||
"description": "$1 is networks stability value - stable, low, high"
|
||||
},
|
||||
"networkSwitchConnectionError": {
|
||||
"message": "We can't connect to $1",
|
||||
"description": "$1 represents the network name"
|
||||
},
|
||||
"networkURL": {
|
||||
"message": "Network URL"
|
||||
},
|
||||
@ -2487,6 +2522,12 @@
|
||||
"notEnoughGas": {
|
||||
"message": "Not enough gas"
|
||||
},
|
||||
"note": {
|
||||
"message": "Note"
|
||||
},
|
||||
"notePlaceholder": {
|
||||
"message": "The approver will see this note when approving the transaction at the custodian."
|
||||
},
|
||||
"notifications": {
|
||||
"message": "Notifications"
|
||||
},
|
||||
@ -2860,7 +2901,7 @@
|
||||
"message": "Open in block explorer"
|
||||
},
|
||||
"openSea": {
|
||||
"message": "OpenSea (Beta)"
|
||||
"message": "OpenSea + Blockaid (Beta)"
|
||||
},
|
||||
"openSeaNew": {
|
||||
"message": "OpenSea"
|
||||
@ -3132,6 +3173,9 @@
|
||||
"portfolio": {
|
||||
"message": "Portfolio"
|
||||
},
|
||||
"portfolioDashboard": {
|
||||
"message": "Portfolio Dashboard"
|
||||
},
|
||||
"portfolioView": {
|
||||
"message": "Portfolio view"
|
||||
},
|
||||
@ -3167,6 +3211,10 @@
|
||||
"message": "Private Key",
|
||||
"description": "select this type of file to use to import an account"
|
||||
},
|
||||
"privateKeyCopyWarning": {
|
||||
"message": "Private key for $1",
|
||||
"description": "$1 represents the account name"
|
||||
},
|
||||
"privateKeyWarning": {
|
||||
"message": "Warning: Never disclose this key. Anyone with your private keys can steal any assets held in your account."
|
||||
},
|
||||
@ -3278,6 +3326,12 @@
|
||||
"removeAccountDescription": {
|
||||
"message": "This account will be removed from your wallet. Please make sure you have the original Secret Recovery Phrase or private key for this imported account before continuing. You can import or create accounts again from the account drop-down. "
|
||||
},
|
||||
"removeJWT": {
|
||||
"message": "Remove custodian token"
|
||||
},
|
||||
"removeJWTDescription": {
|
||||
"message": "Are you sure you want to remove this token? All accounts assigned to this token will be removed from extension as well: "
|
||||
},
|
||||
"removeNFT": {
|
||||
"message": "Remove NFT"
|
||||
},
|
||||
@ -3483,6 +3537,9 @@
|
||||
"security": {
|
||||
"message": "Security"
|
||||
},
|
||||
"securityAlert": {
|
||||
"message": "Security alert from $1 and $2"
|
||||
},
|
||||
"securityAndPrivacy": {
|
||||
"message": "Security & privacy"
|
||||
},
|
||||
@ -3661,9 +3718,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"
|
||||
},
|
||||
"showMore": {
|
||||
"message": "Show more"
|
||||
},
|
||||
"showPermissions": {
|
||||
"message": "Show permissions"
|
||||
},
|
||||
"showPrivateKey": {
|
||||
"message": "Show private key"
|
||||
},
|
||||
"showPrivateKeys": {
|
||||
"message": "Show Private Keys"
|
||||
},
|
||||
@ -4410,9 +4473,6 @@
|
||||
"thisCollection": {
|
||||
"message": "this collection"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "This is based on information from "
|
||||
},
|
||||
"thisServiceIsExperimental": {
|
||||
"message": "This service is experimental. By enabling this feature, you agree to OpenSea's $1.",
|
||||
"description": "$1 is link to open sea terms of use"
|
||||
@ -4430,11 +4490,44 @@
|
||||
"message": "To: $1",
|
||||
"description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress"
|
||||
},
|
||||
"toggleEthSignBannerDescription": {
|
||||
"message": "You’re at risk for phishing attacks. Protect yourself by turning off eth_sign."
|
||||
},
|
||||
"toggleEthSignDescriptionField": {
|
||||
"message": "Turn this on to let dapps request your signature using eth_sign requests. eth_sign is an open-ended signing method that lets you sign an arbitrary hash, making it a dangerous phishing risk. Only sign eth_sign requests if you can read what you are signing and trust the origin of the request."
|
||||
"message": "If you enable this setting, you might get signature requests that aren’t readable. By signing a message you don't understand, you could be agreeing to give away your funds and NFTs."
|
||||
},
|
||||
"toggleEthSignField": {
|
||||
"message": "Toggle eth_sign requests"
|
||||
"message": "Eth_sign requests"
|
||||
},
|
||||
"toggleEthSignModalBannerBoldText": {
|
||||
"message": " you might be getting scammed"
|
||||
},
|
||||
"toggleEthSignModalBannerText": {
|
||||
"message": "If you've been asked to turn this setting on,"
|
||||
},
|
||||
"toggleEthSignModalCheckBox": {
|
||||
"message": "I understand that I can lose all of my funds and NFTs if I enable eth_sign requests. "
|
||||
},
|
||||
"toggleEthSignModalDescription": {
|
||||
"message": "Allowing eth_sign requests can make you vulnerable to phishing attacks. Always review the URL and be careful when signing messages that contain code."
|
||||
},
|
||||
"toggleEthSignModalFormError": {
|
||||
"message": "The text is incorrect"
|
||||
},
|
||||
"toggleEthSignModalFormLabel": {
|
||||
"message": "Enter “I only sign what I understand” to continue"
|
||||
},
|
||||
"toggleEthSignModalFormValidation": {
|
||||
"message": "I only sign what I understand"
|
||||
},
|
||||
"toggleEthSignModalTitle": {
|
||||
"message": "Use at your own risk"
|
||||
},
|
||||
"toggleEthSignOff": {
|
||||
"message": "OFF (Recommended)"
|
||||
},
|
||||
"toggleEthSignOn": {
|
||||
"message": "ON (Not recommended)"
|
||||
},
|
||||
"toggleTestNetworks": {
|
||||
"message": "$1 test networks",
|
||||
@ -4482,6 +4575,9 @@
|
||||
"tokenSymbol": {
|
||||
"message": "Token symbol"
|
||||
},
|
||||
"tokens": {
|
||||
"message": "Tokens"
|
||||
},
|
||||
"tokensFoundTitle": {
|
||||
"message": "$1 new tokens found",
|
||||
"description": "$1 is the number of new tokens detected"
|
||||
@ -4594,11 +4690,14 @@
|
||||
"transactionHistoryTotalGasFee": {
|
||||
"message": "Total gas fee"
|
||||
},
|
||||
"transactionNote": {
|
||||
"message": "Transaction note"
|
||||
},
|
||||
"transactionResubmitted": {
|
||||
"message": "Transaction resubmitted with estimated gas fee increased to $1 at $2"
|
||||
},
|
||||
"transactionSecurityCheck": {
|
||||
"message": "Enable transaction security providers"
|
||||
"message": "Enable security alerts"
|
||||
},
|
||||
"transactionSecurityCheckDescription": {
|
||||
"message": "We use third-party APIs to detect and display risks involved in unsigned transaction and signature requests before you sign them. These services will have access to your unsigned transaction and signature requests, your account address, and your preferred language."
|
||||
|
12
app/_locales/es/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Opciones de activos"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Activos"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Si intenta enviar activos directamente de una red a otra, esto puede provocar la pérdida permanente de activos. Asegúrese de utilizar un puente."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "Historial"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "Mantenga presionado para mostrar la SRP"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Su frase secreta de recuperación proporciona $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "pero los defraudadores sí.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "Mantenga segura su SRP"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Ignorar todo"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "Cosas a tener en cuenta:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "Esto se basa en información de "
|
||||
},
|
||||
"time": {
|
||||
"message": "Tiempo"
|
||||
},
|
||||
|
3
app/_locales/es_419/messages.json
generated
@ -229,9 +229,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Opciones de activos"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Activos"
|
||||
},
|
||||
"attemptingConnect": {
|
||||
"message": "Intentando una conexión a la cadena de bloques."
|
||||
},
|
||||
|
12
app/_locales/fr/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Options d’actifs"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Actifs"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Si vous essayez d’envoyer des actifs directement d’un réseau à un autre, une perte permanente des actifs pourrait en résulter. Assurez-vous d’utiliser une passerelle."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "Historique"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "Appuyez longuement pour révéler la PSR"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Votre phrase secrète de récupération donne $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "mais les hameçonneurs pourraient le faire.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "Conservez votre PSR en lieu sûr"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Ignorer tout"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "Les choses que vous devez garder à l’esprit :"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "Ces informations proviennent de "
|
||||
},
|
||||
"time": {
|
||||
"message": "Temps"
|
||||
},
|
||||
|
12
app/_locales/hi/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "एसेट विकल्प"
|
||||
},
|
||||
"assets": {
|
||||
"message": "परिसंपत्तियां"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "यदि आप ऐसेट्स को सीधे एक नेटवर्क से दूसरे नेटवर्क पर भेजने का प्रयास करते हैं, तो इसके परिणामस्वरूप स्थायी ऐसेट्स का नुकसान हो सकता है। ब्रिज का उपयोग करना सुनिश्चित करें।"
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "इतिहास"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "SRP देखने के लिए होल्ड करें"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "आपका सीक्रेट रिकवरी फ्रेज $1 प्रदान करता है",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "लेकिन फिशर कर सकते हैं।",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "अपने SRP को सुरक्षित रखें"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "सभी को अनदेखा करें"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "ध्यान रखने योग्य बातें"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "से प्राप्त जानकारी पर आधारित है"
|
||||
},
|
||||
"time": {
|
||||
"message": "समय"
|
||||
},
|
||||
|
12
app/_locales/id/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Opsi aset"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Aset"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Jika Anda mencoba untuk mengirim aset secara langsung dari satu jaringan ke jaringan lain, aset Anda berpotensi hilang secara permanen. Pastikan untuk menggunakan penghubung."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "Riwayat"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "Tahan untuk mengungkap FPR"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Frasa Pemulihan Rahasia memberikan $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "tetapi penipu akan mencoba memintanya.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "Jaga keamanan FPR Anda"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Abaikan semua"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "Hal-hal yang perlu diingat:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "Hal ini berdasarkan informasi dari "
|
||||
},
|
||||
"time": {
|
||||
"message": "Waktu"
|
||||
},
|
||||
|
3
app/_locales/it/messages.json
generated
@ -315,9 +315,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Opzioni asset"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Patrimonio"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Se si tenta di inviare risorse direttamente da una rete all'altra, ciò potrebbe comportare una perdita permanente della risorca coinvolta. Assicurati di usare un bridge."
|
||||
},
|
||||
|
12
app/_locales/ja/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "アセットのオプション"
|
||||
},
|
||||
"assets": {
|
||||
"message": "アセット"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "1 つのネットワークから別のネットワークに直接アセットを送ろうとすると、アセットが永久に失われる可能性があります。必ずブリッジを使用してください。"
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "履歴"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "長押しして SRP を表示"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "秘密のリカバリーフレーズは$1を提供します。",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "もし尋ねられた場合はフィッシング詐欺の可能性があります。",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "SRP は安全に保管してください"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "すべて無視"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "留意点:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "これは次の情報源からの情報に基づくものです: "
|
||||
},
|
||||
"time": {
|
||||
"message": "時間"
|
||||
},
|
||||
|
12
app/_locales/ko/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "자산 옵션"
|
||||
},
|
||||
"assets": {
|
||||
"message": "자산"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "한 네트워크에서 다른 네트워크로 자산을 직접 전송하면 자산이 영구적으로 손실될 수 있습니다. 반드시 브릿지를 이용하세요."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "기록"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "눌러서 SRP 확인"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "비밀 복구 구문이 있으면 $1 기능을 사용할 수 있습니다",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "오히려 피싱 사기꾼들이 요구할 수 있으니 주의가 필요합니다.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "SRP를 안전하게 보관하세요"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "모두 무시"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "유의 사항:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "다음에 기반한 정보입니다: "
|
||||
},
|
||||
"time": {
|
||||
"message": "시간"
|
||||
},
|
||||
|
3
app/_locales/ph/messages.json
generated
@ -140,9 +140,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Mga opsyon sa asset"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Mga Asset"
|
||||
},
|
||||
"attemptingConnect": {
|
||||
"message": "Sinusubukang kumonekta sa blockchain."
|
||||
},
|
||||
|
12
app/_locales/pt/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Opções do ativo"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Ativos"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Se você tentar enviar ativos diretamente de uma rede para outra, isso poderá resultar na perda permanente deles. Certifique-se de usar uma ponte."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "Histórico"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "Segure para revelar a FRS"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Sua Frase de Recuperação Secreta concede $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "mas os phishers talvez solicitem.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "Mantenha sua FRS em segurança"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Ignorar tudo"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "Informações importantes:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "Isso se baseia em informações de "
|
||||
},
|
||||
"time": {
|
||||
"message": "Hora"
|
||||
},
|
||||
|
3
app/_locales/pt_BR/messages.json
generated
@ -229,9 +229,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Opções do ativo"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Ativos"
|
||||
},
|
||||
"attemptingConnect": {
|
||||
"message": "Tentando conexão com o blockchain."
|
||||
},
|
||||
|
12
app/_locales/ru/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Параметры актива"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Активы"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Попытка отправки активов напрямую из одной сети в другую может привести к необратимой потере активов. Следует использовать мост."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "История"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "Удерживайте для отображения СВФ"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Ваша секретная фраза для восстановления дает $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "но злоумышленники-фишеры могут.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "Обеспечьте безопасность своей СВФ"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Игнорировать все"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "Что нужно помнить:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "Это основано на информации от "
|
||||
},
|
||||
"time": {
|
||||
"message": "Время"
|
||||
},
|
||||
|
12
app/_locales/tl/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Mga opsyon ng asset"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Mga Asset"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Kung tatangkain mong magpadala ng mga asset nang direkta mula sa isang network papunta sa isa pa, maaari itong magresulta sa permanenteng pagkawala ng asset. Siguraduhing gumamit ng tulay."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "History"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "I-hold para ipakita ang SRP"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Ang iyong Secret Recovery Phrase ay nagbibigay ng $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "ngunit maaring hingin ng mga phisher.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "Ingatan ang iyong SRP"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Huwag pansinin ang lahat"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "Mga bagay na dapat tandaan:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "Ito ay batay sa impormasyon mula sa "
|
||||
},
|
||||
"time": {
|
||||
"message": "Oras"
|
||||
},
|
||||
|
12
app/_locales/tr/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Varlık seçenekleri"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Varlıklar"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Varlıkları doğrudan bir ağdan diğerine göndermeye çalışırsanız bu durum kalıcı varlık kaybına neden olabilir. Bir köprü kullandığınızdan emin olun."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "Geçmiş"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "GKİ'yi göstermek için basılı tut"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Gizli Kurtarma İfadeniz: $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "ancak dolandırıcılar talep edilebilir.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "GKİ'nizi güvende tutun"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Tümünü yoksay"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "Unutulmaması gerekenler:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "Bu, şu kaynaktan alınan bilgilere dayanır: "
|
||||
},
|
||||
"time": {
|
||||
"message": "Zaman"
|
||||
},
|
||||
|
12
app/_locales/vi/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "Tùy chọn tài sản"
|
||||
},
|
||||
"assets": {
|
||||
"message": "Tài sản"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "Nếu bạn cố gắng gửi tài sản trực tiếp từ mạng này sang mạng khác, bạn có thể bị mất tài sản vĩnh viễn. Hãy nhớ sử dụng cầu nối."
|
||||
},
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "Lịch sử"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "Giữ để hiển thị Cụm từ khôi phục bí mật"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "Cụm từ khôi phục bí mật của bạn cung cấp $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "nhưng những kẻ lừa đảo qua mạng thì có.",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "Đảm bảo an toàn cho Cụm từ khôi phục bí mật của bạn"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Bỏ qua tất cả"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "Những điều cần lưu ý:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "Điều này dựa trên thông tin từ "
|
||||
},
|
||||
"time": {
|
||||
"message": "Thời gian"
|
||||
},
|
||||
|
14
app/_locales/zh_CN/messages.json
generated
@ -398,9 +398,6 @@
|
||||
"assetOptions": {
|
||||
"message": "资产选项"
|
||||
},
|
||||
"assets": {
|
||||
"message": "资产"
|
||||
},
|
||||
"attemptSendingAssets": {
|
||||
"message": "如果您试图将资产从一个网络直接发送到另一个网络,这可能会导致永久的资产损失。必须使用桥来进行。"
|
||||
},
|
||||
@ -427,7 +424,7 @@
|
||||
"message": "设置 MetaMask 将被锁定前的空闲时间(单位:分钟)。"
|
||||
},
|
||||
"average": {
|
||||
"message": "平均值"
|
||||
"message": "中等"
|
||||
},
|
||||
"back": {
|
||||
"message": "返回"
|
||||
@ -1570,9 +1567,6 @@
|
||||
"history": {
|
||||
"message": "历史记录"
|
||||
},
|
||||
"holdToReveal": {
|
||||
"message": "按住以显示 SRP"
|
||||
},
|
||||
"holdToRevealContent1": {
|
||||
"message": "您的助记词提供 $1",
|
||||
"description": "$1 is a bolded text with the message from 'holdToRevealContent2'"
|
||||
@ -1593,9 +1587,6 @@
|
||||
"message": "但网络钓鱼者可能会。",
|
||||
"description": "The text link in 'holdToRevealContent3'"
|
||||
},
|
||||
"holdToRevealTitle": {
|
||||
"message": "确保 SRP 的安全"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "忽略所有"
|
||||
},
|
||||
@ -3920,9 +3911,6 @@
|
||||
"thingsToKeep": {
|
||||
"message": "注意事项:"
|
||||
},
|
||||
"thisIsBasedOn": {
|
||||
"message": "所根据的信息是来自"
|
||||
},
|
||||
"time": {
|
||||
"message": "时间"
|
||||
},
|
||||
|
3
app/_locales/zh_TW/messages.json
generated
@ -139,9 +139,6 @@
|
||||
"assetOptions": {
|
||||
"message": "資產選項"
|
||||
},
|
||||
"assets": {
|
||||
"message": "資產"
|
||||
},
|
||||
"attemptingConnect": {
|
||||
"message": "正在嘗試連結區塊鏈。"
|
||||
},
|
||||
|
6
app/build-types/mmi/images/icons/compliance.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.36 6L12.76 8H18V14H14.64L14.24 12H7V6H12.36ZM14 4H5V21H7V14H12.6L13 16H20V6H14.4L14 4Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 231 B |
8
app/build-types/mmi/images/icons/custody.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg width="20" height="20" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="surface1">
|
||||
<path
|
||||
d="M 10.351562 9.167969 L 10.351562 9.777344 C 10.351562 10.449219 9.835938 11 9.203125 11 L 1.148438 11 C 0.511719 11 0 10.449219 0 9.777344 L 0 1.222656 C 0 0.550781 0.511719 0 1.148438 0 L 9.203125 0 C 9.835938 0 10.351562 0.550781 10.351562 1.222656 L 10.351562 1.832031 L 5.175781 1.832031 C 4.539062 1.832031 4.027344 2.382812 4.027344 3.054688 L 4.027344 7.945312 C 4.027344 8.617188 4.539062 9.167969 5.175781 9.167969 Z M 5.175781 7.945312 L 10.929688 7.945312 L 10.929688 3.054688 L 5.175781 3.054688 Z M 7.476562 6.417969 C 7 6.417969 6.613281 6.007812 6.613281 5.5 C 6.613281 4.992188 7 4.582031 7.476562 4.582031 C 7.953125 4.582031 8.339844 4.992188 8.339844 5.5 C 8.339844 6.007812 7.953125 6.417969 7.476562 6.417969 Z M 7.476562 6.417969 "
|
||||
fill="black"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 928 B |
6
app/build-types/mmi/images/icons/portfolio-dashboard.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M0.75 17.25V15.5083L2.125 14.1333V17.25H0.75ZM4.53125 17.25V11.8417L5.90625 10.4667V17.25H4.53125ZM8.3125 17.25V10.4667L9.6875 11.8646V17.25H8.3125ZM12.0938 17.25V11.8646L13.4688 10.4896V17.25H12.0938ZM15.875 17.25V8.175L17.25 6.8V17.25H15.875ZM0.75 11.8417V9.89375L7.16667 3.52292L10.8333 7.18958L17.25 0.75V2.69792L10.8333 9.1375L7.16667 5.47083L0.75 11.8417Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 502 B |
@ -1,4 +0,0 @@
|
||||
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 0L0 16H16L8 0ZM8 3.36842L13.4764 14.3158H2.52364L8 3.36842ZM7.27273 6.73684V10.1053H8.72727V6.73684H7.27273ZM7.27273 11.7895V13.4737H8.72727V11.7895" fill="#D73A49"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 282 B |
1
app/images/icons/ban.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m43 256c0-118 95-213 213-213 118 0 213 95 213 213 0 118-95 213-213 213-118 0-213-95-213-213z m77-109c-24 30-39 68-39 109 0 96 79 175 175 175 41 0 79-15 109-39z m27-27l245 245c24-30 39-68 39-109 0-96-79-175-175-175-41 0-79 15-109 39z"/></svg>
|
After Width: | Height: | Size: 313 B |
1
app/images/icons/bold.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m107 85c0-11 9-21 21-21l171 0c28 0 55 11 75 31 20 20 31 47 31 76 0 28-11 55-31 75-1 1-1 1-2 2 8 5 16 11 23 18 20 20 32 47 32 75 0 29-12 56-32 76-20 20-47 31-75 31l-192 0c-12 0-21-10-21-21z m42 150l150 0c17 0 33-7 45-19 12-12 19-28 19-45 0-17-7-34-19-46-12-12-28-18-45-18l-150 0z m0 42l0 128 171 0c17 0 33-6 45-18 12-12 19-29 19-46 0-17-7-33-19-45-12-12-28-19-45-19z"/></svg>
|
After Width: | Height: | Size: 446 B |
1
app/images/icons/circle-x.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m256 43c-118 0-213 95-213 213 0 118 95 213 213 213 118 0 213-95 213-213 0-118-95-213-213-213z m73 140c8 9 8 22 0 30l-43 43 43 43c8 8 8 21 0 30-9 8-22 8-30 0l-43-43-43 43c-8 8-21 8-30 0-8-9-8-22 0-30l43-43-43-43c-8-8-8-21 0-30 9-8 22-8 30 0l43 43 43-43c8-8 21-8 30 0z"/></svg>
|
After Width: | Height: | Size: 347 B |
1
app/images/icons/download.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m277 64c0-12-9-21-21-21-12 0-21 9-21 21l0 204-71-70c-8-8-21-8-30 0-8 9-8 22 0 30l107 107c0 0 0 0 0 0 2 2 4 4 7 5 2 1 5 1 8 1 0 0 0 0 0 0 0 0 0 0 0 0 6 0 11-2 15-6m0 0l107-107c8-8 8-21 0-30-9-8-22-8-30 0l-71 70 0-204m-213 235c12 0 21 9 21 21l0 85c0 6 3 11 7 15 4 4 9 7 15 7l298 0c6 0 11-3 15-7 4-4 7-9 7-15l0-85c0-12 9-21 21-21 12 0 21 9 21 21l0 85c0 17-6 34-18 46-12 12-29 18-46 18l-298 0c-17 0-34-6-46-18-12-12-18-29-18-46l0-85c0-12 9-21 21-21z"/></svg>
|
After Width: | Height: | Size: 526 B |
1
app/images/icons/file.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m276 43c9 0 18 8 18 18l0 118 118 0c10 0 18 8 18 18 0 10-8 18-18 18l-136 0c-10 0-18-8-18-18l0-136c0-10 8-18 18-18z m-178 16c11-10 26-16 41-16l137 0c4 0 9 2 12 5l137 137c3 3 5 8 5 12l0 215c0 15-6 30-16 41-11 10-26 16-41 16l-234 0c-15 0-30-6-41-16-10-11-16-26-16-41l0-312c0-15 6-30 16-41z m41 20c-6 0-11 2-15 6-4 4-6 9-6 15l0 312c0 6 2 11 6 15 4 4 9 6 15 6l234 0c6 0 11-2 15-6 4-4 6-9 6-15l0-207-126-126z"/></svg>
|
After Width: | Height: | Size: 482 B |
1
app/images/icons/flask.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m434 379l-98-157 0-126 7 0c11 0 20-9 20-20l0-13c0-11-9-20-20-20l-174 0c-11 0-20 9-20 20l0 13c0 11 9 20 20 20l7 0 0 126-98 157c-24 39 4 90 50 90l256 0c46 0 74-51 50-90z m-250-70l40-64c3-5 5-10 5-16l0-133 54 0 0 133c0 6 1 11 4 16l41 64z"/></svg>
|
After Width: | Height: | Size: 315 B |
1
app/images/icons/plug.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m309 163l0-94c0-14 12-26 27-26 15 0 27 12 27 26l0 94z m94 13l-294 0c-7 0-13 6-13 13l0 27c0 7 6 13 13 13l14 0 0 27c0 65 45 118 106 131l0 82 54 0 0-82c61-13 106-66 106-131l0-27 14 0c7 0 13-6 13-13l0-27c0-7-6-13-13-13z m-200-13l0-94c0-14-12-26-27-26-15 0-27 12-27 26l0 94z"/></svg>
|
After Width: | Height: | Size: 350 B |
1
app/images/icons/qr-code.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m64 229l165 0 0-165-165 0z m55-110l55 0 0 55-55 0z m164-55l0 165 165 0 0-165z m110 110l-55 0 0-55 55 0z m-329 274l165 0 0-165-165 0z m55-110l55 0 0 55-55 0z m302-55l27 0 0 110-82 0 0-27-28 0 0 82-55 0 0-165 83 0 0 28 55 0z m0 138l27 0 0 27-27 0z m-55 0l27 0 0 27-27 0z"/></svg>
|
After Width: | Height: | Size: 349 B |
1
app/images/icons/share.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m329 188c1-12 12-20 24-19 41 3 75 16 97 44 22 27 30 64 30 109l0 3c0 50-10 90-37 118-27 27-68 37-118 37l-139 0c-49 0-90-10-117-37-28-28-37-68-37-118l0-3c0-44 8-82 29-109 22-27 55-40 96-44 12-1 23 7 24 19 1 12-8 22-20 23-34 4-54 14-67 29-12 16-20 41-20 82l0 3c0 46 10 72 25 88 16 15 42 24 87 24l139 0c46 0 72-9 88-24 15-16 25-42 25-88l0-3c0-41-8-66-21-82-12-16-33-26-68-29-12-1-21-11-20-23z m-58-150c-8-8-22-8-30 0l-72 72c-8 8-8 22 0 30 9 8 22 8 31 0l35-35 0 215c0 12 9 21 21 21 12 0 21-9 21-21l0-215 35 35c9 8 22 8 31 0 8-8 8-22 0-30z"/></svg>
|
After Width: | Height: | Size: 614 B |
1
app/images/icons/square.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m107 85c-12 0-22 10-22 22l0 298c0 12 10 22 22 22l298 0c12 0 22-10 22-22l0-298c0-12-10-22-22-22z m-64 22c0-36 28-64 64-64l298 0c36 0 64 28 64 64l0 298c0 36-28 64-64 64l-298 0c-36 0-64-28-64-64z"/></svg>
|
After Width: | Height: | Size: 273 B |
1
app/images/icons/tint.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path d="m256 43c6 0 11 2 15 6l112 112c0 0 0 0 0 0 26 26 43 58 50 93 7 35 3 71-10 104-14 33-37 61-67 81-29 20-64 30-100 30-36 0-71-10-100-30-30-20-53-48-67-81-13-33-17-69-10-104 7-35 24-67 50-93 0 0 0 0 0 0l112-112c4-4 9-6 15-6z m0 51l-97 98c-20 19-33 43-38 70-5 27-3 54 8 80 10 25 28 46 50 61 23 16 50 24 77 24 27 0 54-8 77-24 22-15 40-36 50-61 11-26 13-53 8-80-5-27-18-51-38-70z"/></svg>
|
After Width: | Height: | Size: 454 B |
1
app/images/icons/twitter.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m299 85c19-8 40-9 59-4 15 4 29 11 41 21 13-5 25-12 37-20 7-5 17-5 24 0 7 5 11 14 9 23-6 24-18 47-35 66 0 2 0 5 0 7l0 0c0 108-52 187-127 227-74 40-169 39-253-8-9-4-13-14-11-24 3-9 12-16 22-15 26 1 52-4 77-14-24-16-41-34-54-54-18-28-24-58-26-85-1-26 3-50 8-68 2-8 4-15 6-20 1-3 2-5 2-6 1-1 1-1 1-2l0 0 0-1 0 0 0 0c0 0 0 0 20 9l-20-9c4-7 10-11 18-12 7-1 15 2 19 9 16 22 61 52 61 52 0 0 38 15 58 17 0-19 6-37 17-52 11-17 28-30 47-37z"/></svg>
|
After Width: | Height: | Size: 510 B |
1
app/images/icons/upload.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m263 65c3 1 6 3 8 5l95 95c8 8 8 22 0 30-8 9-22 9-30 0l-59-58 0 176c0 12-9 21-21 21-12 0-21-9-21-21l0-176-59 58c-8 9-22 9-30 0-8-8-8-22 0-30l95-95c0 0 1-1 2-2 1 0 2-1 4-2 2-1 6-2 9-2m0 0c3 0 5 0 7 1z m-171 228c12 0 22 9 22 21l0 76c0 4 1 8 5 11 3 4 7 5 11 5l266 0c4 0 8-1 11-5 4-3 5-7 5-11l0-76c0-12 10-21 22-21 11 0 21 9 21 21l0 76c0 15-6 31-17 42-11 11-27 17-42 17l-266 0c-15 0-31-6-42-17-11-11-17-27-17-42l0-76c0-12 10-21 21-21z"/></svg>
|
After Width: | Height: | Size: 510 B |
1
app/images/icons/usb.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m469 256c0 2-1 4-3 5l-59 36c-1 0-2 0-3 0-1 0-2 0-3 0-2-1-3-3-3-5l0-24-159 0c17 26 27 71 47 71l17 0 0-18c0-3 3-6 6-6l60 0c3 0 5 3 5 6l0 59c0 4-2 6-5 6l-60 0c-3 0-6-2-6-6l0-17-17 0c-51 0-54-95-83-95l-67 0c-5 20-24 35-46 35-26 0-47-21-47-47 0-26 21-47 47-47 22 0 41 15 46 35 26 0 29 6 50-40 26-59 38-55 72-55 5-14 18-23 34-23 19 0 35 16 35 35 0 20-16 36-35 36-16 0-29-10-34-24l-20 0c-19 0-29 45-46 71l206 0 0-23c0-3 1-4 3-6 2-1 4-1 6 1l59 35c2 1 3 3 3 5z"/></svg>
|
After Width: | Height: | Size: 532 B |
1
app/images/icons/user-check.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m279 169c0 46-38 83-84 83-46 0-84-37-84-83 0-46 38-84 84-84 46 0 84 38 84 84z m-236 249c0-69 68-125 152-125 84 0 152 56 152 125 0 5-3 9-8 9l-288 0c-5 0-8-4-8-9z m420-200c8-9 8-22 0-30-8-9-22-9-30 0l-55 56-21-21c-8-8-21-8-30 0-8 8-8 22 0 30l36 36c8 8 21 8 30 0z"/></svg>
|
After Width: | Height: | Size: 341 B |
1
app/images/icons/wifi.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m257 232c-42 0-82 15-114 41-9 8-23 7-30-2-8-10-7-23 2-31 40-33 90-51 142-51 51 0 101 18 141 51 9 8 10 21 3 31-8 9-21 10-30 2-32-26-73-41-114-41z m-1-91c-66 0-129 24-178 67-9 8-22 7-30-1-8-9-7-23 2-31 57-50 130-77 206-77 76 0 149 27 206 77 9 8 10 22 2 31-8 8-21 9-30 1-49-43-113-67-178-67z m0 182c-18 0-36 5-51 16-9 7-23 4-29-5-7-10-5-23 5-30 22-16 48-24 75-24 27 0 53 8 76 24 9 7 11 20 5 30-7 9-21 12-30 5-15-11-33-16-51-16z m-21 69c0-12 9-21 21-21l0 0c12 0 21 9 21 21 0 12-9 21-21 21l0 0c-12 0-21-9-21-21z"/></svg>
|
After Width: | Height: | Size: 587 B |
@ -9,6 +9,9 @@ import debounce from 'debounce-stream';
|
||||
import log from 'loglevel';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { storeAsStream } from '@metamask/obs-store';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import PortStream from 'extension-port-stream';
|
||||
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
@ -18,9 +21,6 @@ import {
|
||||
ENVIRONMENT_TYPE_FULLSCREEN,
|
||||
EXTENSION_MESSAGES,
|
||||
PLATFORM_FIREFOX,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
MESSAGE_TYPE,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from '../../shared/constants/app';
|
||||
import {
|
||||
REJECT_NOTIFICATION_CLOSE,
|
||||
@ -32,6 +32,7 @@ import {
|
||||
import { checkForLastErrorAndLog } from '../../shared/modules/browser-runtime.utils';
|
||||
import { isManifestV3 } from '../../shared/modules/mv3.utils';
|
||||
import { maskObject } from '../../shared/modules/object.utils';
|
||||
import { getEnvironmentType, deferredPromise, getPlatform } from './lib/util';
|
||||
import migrations from './migrations';
|
||||
import Migrator from './lib/migrator';
|
||||
import ExtensionPlatform from './platforms/extension';
|
||||
@ -50,7 +51,6 @@ import rawFirstTimeState from './first-time-state';
|
||||
import getFirstPreferredLangCode from './lib/get-first-preferred-lang-code';
|
||||
import getObjStructure from './lib/getObjStructure';
|
||||
import setupEnsIpfsResolver from './lib/ens-ipfs/setup';
|
||||
import { deferredPromise, getPlatform } from './lib/util';
|
||||
|
||||
/* eslint-enable import/first */
|
||||
|
||||
@ -84,6 +84,7 @@ let popupIsOpen = false;
|
||||
let notificationIsOpen = false;
|
||||
let uiIsTriggering = false;
|
||||
const openMetamaskTabsIDs = {};
|
||||
const openMetamaskConnections = new Map();
|
||||
const requestAccountTabIds = {};
|
||||
let controller;
|
||||
|
||||
@ -184,10 +185,28 @@ let connectExternal;
|
||||
browser.runtime.onConnect.addListener(async (...args) => {
|
||||
// Queue up connection attempts here, waiting until after initialization
|
||||
await isInitialized;
|
||||
const remotePort = args[0];
|
||||
const { sender } = remotePort;
|
||||
|
||||
// This is set in `setupController`, which is called as part of initialization
|
||||
connectRemote(...args);
|
||||
const url = sender?.url;
|
||||
const detectedProcessName = url ? getEnvironmentType(url) : '';
|
||||
|
||||
const connectionId = generateConnectionId(remotePort, detectedProcessName);
|
||||
const openConnections = openMetamaskConnections.get(connectionId) || 0;
|
||||
|
||||
if (
|
||||
openConnections === 0 ||
|
||||
(detectedProcessName === 'background' && openConnections < 2)
|
||||
// 2 background connections are allowed, one for phishing warning page and one for the ledger bridge keyring
|
||||
) {
|
||||
// This is set in `setupController`, which is called as part of initialization
|
||||
connectRemote(...args);
|
||||
openMetamaskConnections.set(connectionId, openConnections + 1);
|
||||
} else {
|
||||
throw new Error('CONNECTION_ALREADY_EXISTS');
|
||||
}
|
||||
});
|
||||
|
||||
browser.runtime.onConnectExternal.addListener(async (...args) => {
|
||||
// Queue up connection attempts here, waiting until after initialization
|
||||
await isInitialized;
|
||||
@ -219,9 +238,9 @@ browser.runtime.onConnectExternal.addListener(async (...args) => {
|
||||
* @property {object} featureFlags - An object for optional feature flags.
|
||||
* @property {boolean} welcomeScreen - True if welcome screen should be shown.
|
||||
* @property {string} currentLocale - A locale string matching the user's preferred display language.
|
||||
* @property {object} provider - The current selected network provider.
|
||||
* @property {string} provider.rpcUrl - The address for the RPC API, if using an RPC API.
|
||||
* @property {string} provider.type - An identifier for the type of network selected, allows MetaMask to use custom provider strategies for known networks.
|
||||
* @property {object} providerConfig - The current selected network provider.
|
||||
* @property {string} providerConfig.rpcUrl - The address for the RPC API, if using an RPC API.
|
||||
* @property {string} providerConfig.type - An identifier for the type of network selected, allows MetaMask to use custom provider strategies for known networks.
|
||||
* @property {string} networkId - The stringified number of the current network ID.
|
||||
* @property {string} networkStatus - Either "unknown", "available", "unavailable", or "blocked", depending on the status of the currently selected network.
|
||||
* @property {object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values.
|
||||
@ -416,6 +435,21 @@ export async function loadStateFromPersistence() {
|
||||
return versionedData.data;
|
||||
}
|
||||
|
||||
function generateConnectionId(remotePort, detectedProcessName) {
|
||||
const { sender } = remotePort;
|
||||
const id = sender?.tab ? sender.tab.id : sender?.id;
|
||||
if (!id || !detectedProcessName) {
|
||||
console.error(
|
||||
'Must provide id and detectedProcessName to generate connection id.',
|
||||
id,
|
||||
detectedProcessName,
|
||||
); // eslint-disable-line no-console
|
||||
throw new Error(
|
||||
'Must provide id and detectedProcessName to generate connection id.',
|
||||
);
|
||||
}
|
||||
return `${id}-${detectedProcessName}`;
|
||||
}
|
||||
/**
|
||||
* Initializes the MetaMask Controller with any initial state and default language.
|
||||
* Configures platform-specific error reporting strategy.
|
||||
@ -462,7 +496,7 @@ export function setupController(
|
||||
|
||||
setupEnsIpfsResolver({
|
||||
getCurrentChainId: () =>
|
||||
controller.networkController.store.getState().provider.chainId,
|
||||
controller.networkController.store.getState().providerConfig.chainId,
|
||||
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(
|
||||
controller.preferencesController,
|
||||
),
|
||||
@ -570,7 +604,6 @@ export function setupController(
|
||||
// communication with popup
|
||||
controller.isClientOpen = true;
|
||||
controller.setupTrustedCommunication(portStream, remotePort.sender);
|
||||
|
||||
if (isManifestV3) {
|
||||
// If we get a WORKER_KEEP_ALIVE message, we respond with an ACK
|
||||
remotePort.onMessage.addListener((message) => {
|
||||
@ -585,9 +618,11 @@ export function setupController(
|
||||
});
|
||||
}
|
||||
|
||||
const connectionId = generateConnectionId(remotePort, processName);
|
||||
if (processName === ENVIRONMENT_TYPE_POPUP) {
|
||||
popupIsOpen = true;
|
||||
endOfStream(portStream, () => {
|
||||
openMetamaskConnections.set(connectionId, 0);
|
||||
popupIsOpen = false;
|
||||
const isClientOpen = isClientOpenStatus();
|
||||
controller.isClientOpen = isClientOpen;
|
||||
@ -597,8 +632,8 @@ export function setupController(
|
||||
|
||||
if (processName === ENVIRONMENT_TYPE_NOTIFICATION) {
|
||||
notificationIsOpen = true;
|
||||
|
||||
endOfStream(portStream, () => {
|
||||
openMetamaskConnections.set(connectionId, 0);
|
||||
notificationIsOpen = false;
|
||||
const isClientOpen = isClientOpenStatus();
|
||||
controller.isClientOpen = isClientOpen;
|
||||
@ -614,6 +649,7 @@ export function setupController(
|
||||
openMetamaskTabsIDs[tabId] = true;
|
||||
|
||||
endOfStream(portStream, () => {
|
||||
openMetamaskConnections.set(connectionId, 0);
|
||||
delete openMetamaskTabsIDs[tabId];
|
||||
const isClientOpen = isClientOpenStatus();
|
||||
controller.isClientOpen = isClientOpen;
|
||||
@ -677,7 +713,9 @@ export function setupController(
|
||||
// User Interface setup
|
||||
//
|
||||
|
||||
updateBadge();
|
||||
controller.txController.initApprovals().then(() => {
|
||||
updateBadge();
|
||||
});
|
||||
controller.txController.on(
|
||||
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
|
||||
updateBadge,
|
||||
@ -690,7 +728,7 @@ export function setupController(
|
||||
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
|
||||
updateBadge,
|
||||
);
|
||||
controller.signController.hub.on(
|
||||
controller.signatureController.hub.on(
|
||||
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
|
||||
updateBadge,
|
||||
);
|
||||
@ -749,7 +787,9 @@ export function setupController(
|
||||
).forEach((txId) =>
|
||||
controller.txController.txStateManager.setTxStatusRejected(txId),
|
||||
);
|
||||
controller.signController.rejectUnapproved(REJECT_NOTIFICATION_CLOSE_SIG);
|
||||
controller.signatureController.rejectUnapproved(
|
||||
REJECT_NOTIFICATION_CLOSE_SIG,
|
||||
);
|
||||
controller.decryptMessageController.rejectUnapproved(
|
||||
REJECT_NOTIFICATION_CLOSE,
|
||||
);
|
||||
@ -762,11 +802,11 @@ export function setupController(
|
||||
({ id, type }) => {
|
||||
switch (type) {
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
case MESSAGE_TYPE.SNAP_DIALOG_ALERT:
|
||||
case MESSAGE_TYPE.SNAP_DIALOG_PROMPT:
|
||||
case ApprovalType.SnapDialogAlert:
|
||||
case ApprovalType.SnapDialogPrompt:
|
||||
controller.approvalController.accept(id, null);
|
||||
break;
|
||||
case MESSAGE_TYPE.SNAP_DIALOG_CONFIRMATION:
|
||||
case ApprovalType.SnapDialogConfirmation:
|
||||
controller.approvalController.accept(id, false);
|
||||
break;
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
@ -2,6 +2,7 @@ import EventEmitter from 'events';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import log from 'loglevel';
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
|
||||
import { MINUTE } from '../../../shared/constants/time';
|
||||
import { AUTO_LOCK_TIMEOUT_ALARM } from '../../../shared/constants/alarms';
|
||||
@ -13,8 +14,6 @@ import {
|
||||
ORIGIN_METAMASK,
|
||||
} from '../../../shared/constants/app';
|
||||
|
||||
const APPROVAL_REQUEST_TYPE = 'unlock';
|
||||
|
||||
export default class AppStateController extends EventEmitter {
|
||||
/**
|
||||
* @param {object} opts
|
||||
@ -408,7 +407,7 @@ export default class AppStateController extends EventEmitter {
|
||||
{
|
||||
id: this._approvalRequestId,
|
||||
origin: ORIGIN_METAMASK,
|
||||
type: APPROVAL_REQUEST_TYPE,
|
||||
type: ApprovalType.Unlock,
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
@ -209,7 +209,7 @@ export default class DetectTokensController {
|
||||
}
|
||||
|
||||
getChainIdFromNetworkStore(network) {
|
||||
return network?.store.getState().provider.chainId;
|
||||
return network?.store.getState().providerConfig.chainId;
|
||||
}
|
||||
|
||||
/* eslint-disable accessor-pairs */
|
||||
|
@ -235,7 +235,7 @@ describe('DetectTokensController', function () {
|
||||
const modifiedNetworkState = {
|
||||
...networkState,
|
||||
providerConfig: {
|
||||
...networkState.provider,
|
||||
...networkState.providerConfig,
|
||||
},
|
||||
};
|
||||
return cb(modifiedNetworkState);
|
||||
@ -254,8 +254,10 @@ describe('DetectTokensController', function () {
|
||||
const modifiedNetworkState = {
|
||||
...networkState,
|
||||
providerConfig: {
|
||||
...networkState.provider,
|
||||
chainId: convertHexToDecimal(networkState.provider.chainId),
|
||||
...networkState.providerConfig,
|
||||
chainId: convertHexToDecimal(
|
||||
networkState.providerConfig.chainId,
|
||||
),
|
||||
},
|
||||
};
|
||||
return cb(modifiedNetworkState);
|
||||
|
@ -65,7 +65,7 @@ const DEFAULT_PAGE_PROPERTIES = {
|
||||
|
||||
function getMockNetworkController() {
|
||||
let state = {
|
||||
provider: {
|
||||
providerConfig: {
|
||||
type: NETWORK_TYPES.GOERLI,
|
||||
chainId: FAKE_CHAIN_ID,
|
||||
},
|
||||
@ -134,7 +134,7 @@ function getMetaMetricsController({
|
||||
return new MetaMetricsController({
|
||||
segment: segmentInstance || segment,
|
||||
getCurrentChainId: () =>
|
||||
networkController.store.getState().provider.chainId,
|
||||
networkController.store.getState().providerConfig.chainId,
|
||||
onNetworkDidChange:
|
||||
networkController.onNetworkDidChange.bind(networkController),
|
||||
preferencesStore,
|
||||
@ -203,7 +203,7 @@ describe('MetaMetricsController', function () {
|
||||
networkController,
|
||||
});
|
||||
networkController.store.updateState({
|
||||
provider: {
|
||||
providerConfig: {
|
||||
type: 'NEW_NETWORK',
|
||||
chainId: '0xaab',
|
||||
},
|
||||
|
@ -10,10 +10,11 @@ import {
|
||||
import EthQuery from 'eth-query';
|
||||
import { RestrictedControllerMessenger } from '@metamask/base-controller';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Hex, isPlainObject } from '@metamask/utils';
|
||||
import { Hex, isPlainObject, isStrictHexString } from '@metamask/utils';
|
||||
import { errorCodes } from 'eth-rpc-errors';
|
||||
import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider';
|
||||
import { PollingBlockTracker } from 'eth-block-tracker';
|
||||
import { hexToDecimal } from '../../../../shared/modules/conversion.utils';
|
||||
import {
|
||||
INFURA_PROVIDER_TYPES,
|
||||
INFURA_BLOCKED_KEY,
|
||||
@ -266,7 +267,7 @@ type NetworkConfigurations = Record<
|
||||
* The state that NetworkController holds after combining its individual stores.
|
||||
*/
|
||||
export type NetworkControllerState = {
|
||||
provider: ProviderConfiguration;
|
||||
providerConfig: ProviderConfiguration;
|
||||
networkId: NetworkIdState;
|
||||
networkStatus: NetworkStatus;
|
||||
networkDetails: NetworkDetails;
|
||||
@ -279,7 +280,7 @@ export type NetworkControllerState = {
|
||||
export type NetworkControllerOptions = {
|
||||
messenger: NetworkControllerMessenger;
|
||||
state?: {
|
||||
provider?: ProviderConfiguration;
|
||||
providerConfig?: ProviderConfiguration;
|
||||
networkDetails?: NetworkDetails;
|
||||
networkConfigurations?: NetworkConfigurations;
|
||||
};
|
||||
@ -301,16 +302,22 @@ function isErrorWithCode(error: unknown): error is { code: string | number } {
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given value is a network ID, i.e., that it is a decimal
|
||||
* number represented as a string.
|
||||
* Convert the given value into a valid network ID. The ID is accepted
|
||||
* as either a number, a decimal string, or a 0x-prefixed hex string.
|
||||
*
|
||||
* @param value - The value to check.
|
||||
* @param value - The network ID to convert, in an unknown format.
|
||||
* @returns A valid network ID (as a decimal string)
|
||||
* @throws If the given value cannot be safely parsed.
|
||||
*/
|
||||
function assertNetworkId(value: any): asserts value is NetworkId {
|
||||
assert(
|
||||
/^\d+$/u.test(value) && !Number.isNaN(Number(value)),
|
||||
'value is not a number',
|
||||
);
|
||||
function convertNetworkId(value: unknown): NetworkId {
|
||||
if (typeof value === 'number' && !Number.isNaN(value)) {
|
||||
return `${value}`;
|
||||
} else if (isStrictHexString(value)) {
|
||||
return hexToDecimal(value) as NetworkId;
|
||||
} else if (typeof value === 'string' && /^\d+$/u.test(value)) {
|
||||
return value as NetworkId;
|
||||
}
|
||||
throw new Error(`Cannot parse as a valid network ID: '${value}'`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -386,7 +393,7 @@ function buildDefaultNetworkConfigurationsState(): NetworkConfigurations {
|
||||
*/
|
||||
function buildDefaultState() {
|
||||
return {
|
||||
provider: buildDefaultProviderConfigState(),
|
||||
providerConfig: buildDefaultProviderConfigState(),
|
||||
networkId: buildDefaultNetworkIdState(),
|
||||
networkStatus: buildDefaultNetworkStatusState(),
|
||||
networkDetails: buildDefaultNetworkDetailsState(),
|
||||
@ -477,7 +484,7 @@ export class NetworkController extends EventEmitter {
|
||||
...buildDefaultState(),
|
||||
...state,
|
||||
});
|
||||
this.#previousProviderConfig = this.store.getState().provider;
|
||||
this.#previousProviderConfig = this.store.getState().providerConfig;
|
||||
|
||||
// provider and block tracker
|
||||
this.#provider = null;
|
||||
@ -508,7 +515,7 @@ export class NetworkController extends EventEmitter {
|
||||
* using the provider to gather details about the network.
|
||||
*/
|
||||
async initializeProvider(): Promise<void> {
|
||||
const { type, rpcUrl, chainId } = this.store.getState().provider;
|
||||
const { type, rpcUrl, chainId } = this.store.getState().providerConfig;
|
||||
this.#configureProvider({ type, rpcUrl, chainId });
|
||||
await this.lookupNetwork();
|
||||
}
|
||||
@ -575,7 +582,7 @@ export class NetworkController extends EventEmitter {
|
||||
* blocking requests, or if the network is not Infura-supported.
|
||||
*/
|
||||
async lookupNetwork(): Promise<void> {
|
||||
const { chainId, type } = this.store.getState().provider;
|
||||
const { chainId, type } = this.store.getState().providerConfig;
|
||||
const { provider } = this.getProviderAndBlockTracker();
|
||||
let networkChanged = false;
|
||||
let networkId: NetworkIdState = null;
|
||||
@ -619,17 +626,18 @@ export class NetworkController extends EventEmitter {
|
||||
this.#determineEIP1559Compatibility(provider),
|
||||
]);
|
||||
const possibleNetworkId = results[0];
|
||||
assertNetworkId(possibleNetworkId);
|
||||
networkId = possibleNetworkId;
|
||||
networkId = convertNetworkId(possibleNetworkId);
|
||||
supportsEIP1559 = results[1];
|
||||
networkStatus = NetworkStatus.Available;
|
||||
} catch (error) {
|
||||
if (isErrorWithCode(error) && isErrorWithMessage(error)) {
|
||||
if (isErrorWithCode(error)) {
|
||||
let responseBody;
|
||||
try {
|
||||
responseBody = JSON.parse(error.message);
|
||||
} catch {
|
||||
// error.message must not be JSON
|
||||
if (isInfura && isErrorWithMessage(error)) {
|
||||
try {
|
||||
responseBody = JSON.parse(error.message);
|
||||
} catch {
|
||||
// error.message must not be JSON
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@ -754,7 +762,7 @@ export class NetworkController extends EventEmitter {
|
||||
* Re-initializes the provider and block tracker for the current network.
|
||||
*/
|
||||
async resetConnection() {
|
||||
await this.#setProviderConfig(this.store.getState().provider);
|
||||
await this.#setProviderConfig(this.store.getState().providerConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -765,7 +773,7 @@ export class NetworkController extends EventEmitter {
|
||||
async rollbackToPreviousProvider() {
|
||||
const config = this.#previousProviderConfig;
|
||||
this.store.updateState({
|
||||
provider: config,
|
||||
providerConfig: config,
|
||||
});
|
||||
await this.#switchNetwork(config);
|
||||
}
|
||||
@ -850,8 +858,8 @@ export class NetworkController extends EventEmitter {
|
||||
* @param providerConfig - The provider configuration.
|
||||
*/
|
||||
async #setProviderConfig(providerConfig: ProviderConfiguration) {
|
||||
this.#previousProviderConfig = this.store.getState().provider;
|
||||
this.store.updateState({ provider: providerConfig });
|
||||
this.#previousProviderConfig = this.store.getState().providerConfig;
|
||||
this.store.updateState({ providerConfig });
|
||||
await this.#switchNetwork(providerConfig);
|
||||
}
|
||||
|
||||
|
@ -149,7 +149,7 @@ function mockRpcCall({
|
||||
}
|
||||
}
|
||||
/* @ts-expect-error The types for Nock do not include `basePath` in the interface for Nock.Scope. */
|
||||
const url = nockScope.basePath.includes('infura.io')
|
||||
const url = new URL(nockScope.basePath).hostname.match(/(\.|^)infura.io$/u)
|
||||
? `/v3/${MOCK_INFURA_PROJECT_ID}`
|
||||
: '/';
|
||||
|
||||
|
@ -295,9 +295,8 @@ export function testsForProviderType(providerType: ProviderType) {
|
||||
// tests on the core side.
|
||||
|
||||
{ name: 'net_listening', numberOfParameters: 0 },
|
||||
// TODO: Methods to add back when we add testing for subscribe middleware
|
||||
// { name: 'eth_subscribe', numberOfParameters: 1 },
|
||||
// { name: 'eth_unsubscribe', numberOfParameters: 1 },
|
||||
{ name: 'eth_subscribe', numberOfParameters: 1 },
|
||||
{ name: 'eth_unsubscribe', numberOfParameters: 1 },
|
||||
{ name: 'custom_rpc_method', numberOfParameters: 1 },
|
||||
{ name: 'net_peerCount', numberOfParameters: 0 },
|
||||
{ name: 'parity_nextNonce', numberOfParameters: 1 },
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
EndowmentPermissions,
|
||||
RestrictedMethods,
|
||||
ExcludedSnapEndowments,
|
||||
} from '../../../../../shared/constants/permissions';
|
||||
import {
|
||||
buildSnapEndowmentSpecifications,
|
||||
@ -39,6 +40,13 @@ describe('buildSnapEndowmentSpecifications', () => {
|
||||
it('creates valid permission specification objects', () => {
|
||||
expect(
|
||||
Object.keys(buildSnapEndowmentSpecifications()).sort(),
|
||||
).toStrictEqual(Object.keys(EndowmentPermissions).sort());
|
||||
).toStrictEqual(
|
||||
Object.keys(EndowmentPermissions)
|
||||
.filter(
|
||||
(targetKey) =>
|
||||
!Object.keys(ExcludedSnapEndowments).includes(targetKey),
|
||||
)
|
||||
.sort(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1,588 +0,0 @@
|
||||
import {
|
||||
MessageManager,
|
||||
PersonalMessageManager,
|
||||
TypedMessageManager,
|
||||
} from '@metamask/message-manager';
|
||||
import {
|
||||
AbstractMessage,
|
||||
OriginalRequest,
|
||||
} from '@metamask/message-manager/dist/AbstractMessageManager';
|
||||
import { MetaMetricsEventCategory } from '../../../shared/constants/metametrics';
|
||||
import SignController, {
|
||||
SignControllerMessenger,
|
||||
SignControllerOptions,
|
||||
} from './sign';
|
||||
|
||||
jest.mock('@metamask/message-manager', () => ({
|
||||
MessageManager: jest.fn(),
|
||||
PersonalMessageManager: jest.fn(),
|
||||
TypedMessageManager: jest.fn(),
|
||||
}));
|
||||
|
||||
const messageIdMock = '123';
|
||||
const messageIdMock2 = '456';
|
||||
const versionMock = '1';
|
||||
const signatureMock = '0xAABBCC';
|
||||
const stateMock = { test: 123 };
|
||||
const securityProviderResponseMock = { test2: 345 };
|
||||
|
||||
const messageParamsMock = {
|
||||
from: '0x123',
|
||||
origin: 'http://test.com',
|
||||
data: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
|
||||
metamaskId: messageIdMock,
|
||||
version: 'V1',
|
||||
};
|
||||
|
||||
const messageParamsMock2 = {
|
||||
from: '0x124',
|
||||
origin: 'http://test4.com',
|
||||
data: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA',
|
||||
metamaskId: messageIdMock,
|
||||
};
|
||||
|
||||
const messageMock = {
|
||||
id: messageIdMock,
|
||||
time: 123,
|
||||
status: 'unapproved',
|
||||
type: 'testType',
|
||||
rawSig: undefined,
|
||||
} as any as AbstractMessage;
|
||||
|
||||
const coreMessageMock = {
|
||||
...messageMock,
|
||||
messageParams: messageParamsMock,
|
||||
securityProviderResponse: securityProviderResponseMock,
|
||||
};
|
||||
|
||||
const stateMessageMock = {
|
||||
...messageMock,
|
||||
msgParams: messageParamsMock,
|
||||
securityProviderResponse: securityProviderResponseMock,
|
||||
};
|
||||
|
||||
const requestMock = {
|
||||
origin: 'http://test2.com',
|
||||
} as OriginalRequest;
|
||||
|
||||
const createMessengerMock = () =>
|
||||
({
|
||||
registerActionHandler: jest.fn(),
|
||||
publish: jest.fn(),
|
||||
call: jest.fn(),
|
||||
} as any as jest.Mocked<SignControllerMessenger>);
|
||||
|
||||
const createMessageManagerMock = <T>() =>
|
||||
({
|
||||
getUnapprovedMessages: jest.fn(),
|
||||
getUnapprovedMessagesCount: jest.fn(),
|
||||
addUnapprovedMessageAsync: jest.fn(),
|
||||
approveMessage: jest.fn(),
|
||||
setMessageStatusSigned: jest.fn(),
|
||||
rejectMessage: jest.fn(),
|
||||
subscribe: jest.fn(),
|
||||
update: jest.fn(),
|
||||
hub: {
|
||||
on: jest.fn(),
|
||||
},
|
||||
} as any as jest.Mocked<T>);
|
||||
|
||||
const createPreferencesControllerMock = () => ({
|
||||
store: {
|
||||
getState: jest.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
const createKeyringControllerMock = () => ({
|
||||
signMessage: jest.fn(),
|
||||
signPersonalMessage: jest.fn(),
|
||||
signTypedMessage: jest.fn(),
|
||||
});
|
||||
|
||||
describe('SignController', () => {
|
||||
let signController: SignController;
|
||||
|
||||
const messageManagerConstructorMock = MessageManager as jest.MockedClass<
|
||||
typeof MessageManager
|
||||
>;
|
||||
const personalMessageManagerConstructorMock =
|
||||
PersonalMessageManager as jest.MockedClass<typeof PersonalMessageManager>;
|
||||
const typedMessageManagerConstructorMock =
|
||||
TypedMessageManager as jest.MockedClass<typeof TypedMessageManager>;
|
||||
const messageManagerMock = createMessageManagerMock<MessageManager>();
|
||||
const personalMessageManagerMock =
|
||||
createMessageManagerMock<PersonalMessageManager>();
|
||||
const typedMessageManagerMock =
|
||||
createMessageManagerMock<TypedMessageManager>();
|
||||
const messengerMock = createMessengerMock();
|
||||
const preferencesControllerMock = createPreferencesControllerMock();
|
||||
const keyringControllerMock = createKeyringControllerMock();
|
||||
const getStateMock = jest.fn();
|
||||
const securityProviderRequestMock = jest.fn();
|
||||
const metricsEventMock = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
messageManagerConstructorMock.mockReturnValue(messageManagerMock);
|
||||
personalMessageManagerConstructorMock.mockReturnValue(
|
||||
personalMessageManagerMock,
|
||||
);
|
||||
|
||||
typedMessageManagerConstructorMock.mockReturnValue(typedMessageManagerMock);
|
||||
|
||||
preferencesControllerMock.store.getState.mockReturnValue({
|
||||
disabledRpcMethodPreferences: { eth_sign: true },
|
||||
});
|
||||
|
||||
signController = new SignController({
|
||||
messenger: messengerMock as any,
|
||||
preferencesController: preferencesControllerMock as any,
|
||||
keyringController: keyringControllerMock as any,
|
||||
getState: getStateMock as any,
|
||||
securityProviderRequest: securityProviderRequestMock as any,
|
||||
metricsEvent: metricsEventMock as any,
|
||||
} as SignControllerOptions);
|
||||
});
|
||||
|
||||
describe('unapprovedMsgCount', () => {
|
||||
it('returns value from message manager getter', () => {
|
||||
messageManagerMock.getUnapprovedMessagesCount.mockReturnValueOnce(10);
|
||||
expect(signController.unapprovedMsgCount).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unapprovedPersonalMessagesCount', () => {
|
||||
it('returns value from personal message manager getter', () => {
|
||||
personalMessageManagerMock.getUnapprovedMessagesCount.mockReturnValueOnce(
|
||||
11,
|
||||
);
|
||||
expect(signController.unapprovedPersonalMessagesCount).toBe(11);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unapprovedTypedMessagesCount', () => {
|
||||
it('returns value from typed message manager getter', () => {
|
||||
typedMessageManagerMock.getUnapprovedMessagesCount.mockReturnValueOnce(
|
||||
12,
|
||||
);
|
||||
expect(signController.unapprovedTypedMessagesCount).toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetState', () => {
|
||||
it('sets state to initial state', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
signController.update(() => ({
|
||||
unapprovedMsgs: { [messageIdMock]: messageMock } as any,
|
||||
unapprovedPersonalMsgs: { [messageIdMock]: messageMock } as any,
|
||||
unapprovedTypedMessages: { [messageIdMock]: messageMock } as any,
|
||||
unapprovedMsgCount: 1,
|
||||
unapprovedPersonalMsgCount: 2,
|
||||
unapprovedTypedMessagesCount: 3,
|
||||
}));
|
||||
|
||||
signController.resetState();
|
||||
|
||||
expect(signController.state).toEqual({
|
||||
unapprovedMsgs: {},
|
||||
unapprovedPersonalMsgs: {},
|
||||
unapprovedTypedMessages: {},
|
||||
unapprovedMsgCount: 0,
|
||||
unapprovedPersonalMsgCount: 0,
|
||||
unapprovedTypedMessagesCount: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('rejectUnapproved', () => {
|
||||
beforeEach(() => {
|
||||
const messages = {
|
||||
[messageIdMock]: messageMock,
|
||||
[messageIdMock2]: messageMock,
|
||||
};
|
||||
|
||||
messageManagerMock.getUnapprovedMessages.mockReturnValueOnce(
|
||||
messages as any,
|
||||
);
|
||||
personalMessageManagerMock.getUnapprovedMessages.mockReturnValueOnce(
|
||||
messages as any,
|
||||
);
|
||||
typedMessageManagerMock.getUnapprovedMessages.mockReturnValueOnce(
|
||||
messages as any,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
signController.update(() => ({
|
||||
unapprovedMsgs: messages as any,
|
||||
unapprovedPersonalMsgs: messages as any,
|
||||
unapprovedTypedMessages: messages as any,
|
||||
}));
|
||||
});
|
||||
|
||||
it('rejects all messages in all message managers', () => {
|
||||
signController.rejectUnapproved('Test Reason');
|
||||
|
||||
expect(messageManagerMock.rejectMessage).toHaveBeenCalledTimes(2);
|
||||
expect(messageManagerMock.rejectMessage).toHaveBeenCalledWith(
|
||||
messageIdMock,
|
||||
);
|
||||
expect(messageManagerMock.rejectMessage).toHaveBeenCalledWith(
|
||||
messageIdMock2,
|
||||
);
|
||||
|
||||
expect(personalMessageManagerMock.rejectMessage).toHaveBeenCalledTimes(2);
|
||||
expect(personalMessageManagerMock.rejectMessage).toHaveBeenCalledWith(
|
||||
messageIdMock,
|
||||
);
|
||||
expect(personalMessageManagerMock.rejectMessage).toHaveBeenCalledWith(
|
||||
messageIdMock2,
|
||||
);
|
||||
|
||||
expect(typedMessageManagerMock.rejectMessage).toHaveBeenCalledTimes(2);
|
||||
expect(typedMessageManagerMock.rejectMessage).toHaveBeenCalledWith(
|
||||
messageIdMock,
|
||||
);
|
||||
expect(typedMessageManagerMock.rejectMessage).toHaveBeenCalledWith(
|
||||
messageIdMock2,
|
||||
);
|
||||
});
|
||||
|
||||
it('fires metrics event with reject reason', () => {
|
||||
signController.rejectUnapproved('Test Reason');
|
||||
|
||||
expect(metricsEventMock).toHaveBeenCalledTimes(6);
|
||||
expect(metricsEventMock).toHaveBeenLastCalledWith({
|
||||
event: 'Test Reason',
|
||||
category: MetaMetricsEventCategory.Transactions,
|
||||
properties: {
|
||||
action: 'Sign Request',
|
||||
type: messageMock.type,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearUnapproved', () => {
|
||||
it('resets state in all message managers', () => {
|
||||
signController.clearUnapproved();
|
||||
|
||||
const defaultState = {
|
||||
unapprovedMessages: {},
|
||||
unapprovedMessagesCount: 0,
|
||||
};
|
||||
|
||||
expect(messageManagerMock.update).toHaveBeenCalledTimes(1);
|
||||
expect(messageManagerMock.update).toHaveBeenCalledWith(defaultState);
|
||||
|
||||
expect(personalMessageManagerMock.update).toHaveBeenCalledTimes(1);
|
||||
expect(personalMessageManagerMock.update).toHaveBeenCalledWith(
|
||||
defaultState,
|
||||
);
|
||||
|
||||
expect(typedMessageManagerMock.update).toHaveBeenCalledTimes(1);
|
||||
expect(typedMessageManagerMock.update).toHaveBeenCalledWith(defaultState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('newUnsignedMessage', () => {
|
||||
it('throws if eth_sign disabled in preferences', async () => {
|
||||
preferencesControllerMock.store.getState.mockReturnValueOnce({
|
||||
disabledRpcMethodPreferences: { eth_sign: false },
|
||||
});
|
||||
|
||||
await expect(
|
||||
signController.newUnsignedMessage(messageParamsMock, requestMock),
|
||||
).rejects.toThrowError(
|
||||
'eth_sign has been disabled. You must enable it in the advanced settings',
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if data has wrong length', async () => {
|
||||
await expect(
|
||||
signController.newUnsignedMessage(
|
||||
{ ...messageParamsMock, data: '0xFF' },
|
||||
requestMock,
|
||||
),
|
||||
).rejects.toThrowError('eth_sign requires 32 byte message hash');
|
||||
});
|
||||
|
||||
it('adds message to message manager', async () => {
|
||||
await signController.newUnsignedMessage(messageParamsMock, requestMock);
|
||||
|
||||
expect(
|
||||
messageManagerMock.addUnapprovedMessageAsync,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(messageManagerMock.addUnapprovedMessageAsync).toHaveBeenCalledWith(
|
||||
messageParamsMock,
|
||||
requestMock,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('newUnsignedPersonalMessage', () => {
|
||||
it('adds message to personal message manager', async () => {
|
||||
await signController.newUnsignedPersonalMessage(
|
||||
messageParamsMock,
|
||||
requestMock,
|
||||
);
|
||||
|
||||
expect(
|
||||
personalMessageManagerMock.addUnapprovedMessageAsync,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(
|
||||
personalMessageManagerMock.addUnapprovedMessageAsync,
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining(messageParamsMock),
|
||||
requestMock,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('newUnsignedTypedMessage', () => {
|
||||
it('adds message to typed message manager', async () => {
|
||||
signController.newUnsignedTypedMessage(
|
||||
messageParamsMock,
|
||||
requestMock,
|
||||
versionMock,
|
||||
);
|
||||
|
||||
expect(
|
||||
typedMessageManagerMock.addUnapprovedMessageAsync,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
typedMessageManagerMock.addUnapprovedMessageAsync,
|
||||
).toHaveBeenCalledWith(messageParamsMock, versionMock, requestMock);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
['signMessage', messageManagerMock],
|
||||
['signPersonalMessage', personalMessageManagerMock],
|
||||
['signTypedMessage', typedMessageManagerMock],
|
||||
])('%s', (signMethodName, messageManager) => {
|
||||
beforeEach(() => {
|
||||
messageManager.approveMessage.mockResolvedValueOnce(messageParamsMock2);
|
||||
|
||||
keyringControllerMock[signMethodName].mockResolvedValueOnce(
|
||||
signatureMock,
|
||||
);
|
||||
});
|
||||
|
||||
it('approves message and signs', async () => {
|
||||
await signController[signMethodName](messageParamsMock);
|
||||
|
||||
const keyringControllerExtraArgs =
|
||||
signMethodName === 'signTypedMessage'
|
||||
? [{ version: messageParamsMock.version }]
|
||||
: [];
|
||||
|
||||
expect(keyringControllerMock[signMethodName]).toHaveBeenCalledTimes(1);
|
||||
expect(keyringControllerMock[signMethodName]).toHaveBeenCalledWith(
|
||||
messageParamsMock2,
|
||||
...keyringControllerExtraArgs,
|
||||
);
|
||||
|
||||
expect(messageManager.setMessageStatusSigned).toHaveBeenCalledTimes(1);
|
||||
expect(messageManager.setMessageStatusSigned).toHaveBeenCalledWith(
|
||||
messageParamsMock2.metamaskId,
|
||||
signatureMock,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns current state', async () => {
|
||||
getStateMock.mockReturnValueOnce(stateMock);
|
||||
expect(await signController[signMethodName](messageParamsMock)).toEqual(
|
||||
stateMock,
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts approval', async () => {
|
||||
await signController[signMethodName](messageParamsMock);
|
||||
|
||||
expect(messengerMock.call).toHaveBeenCalledTimes(1);
|
||||
expect(messengerMock.call).toHaveBeenCalledWith(
|
||||
'ApprovalController:acceptRequest',
|
||||
messageParamsMock.metamaskId,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not throw if accepting approval throws', async () => {
|
||||
messengerMock.call.mockImplementation(() => {
|
||||
throw new Error('Test Error');
|
||||
});
|
||||
|
||||
await signController[signMethodName](messageParamsMock);
|
||||
});
|
||||
|
||||
it('rejects message on error', async () => {
|
||||
keyringControllerMock[signMethodName].mockReset();
|
||||
keyringControllerMock[signMethodName].mockRejectedValue(
|
||||
new Error('Test Error'),
|
||||
);
|
||||
|
||||
await expect(
|
||||
signController[signMethodName](messageParamsMock),
|
||||
).rejects.toThrow('Test Error');
|
||||
|
||||
expect(messageManager.rejectMessage).toHaveBeenCalledTimes(1);
|
||||
expect(messageManager.rejectMessage).toHaveBeenCalledWith(
|
||||
messageParamsMock.metamaskId,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects approval on error', async () => {
|
||||
keyringControllerMock[signMethodName].mockReset();
|
||||
keyringControllerMock[signMethodName].mockRejectedValue(
|
||||
new Error('Test Error'),
|
||||
);
|
||||
|
||||
await expect(
|
||||
signController[signMethodName](messageParamsMock),
|
||||
).rejects.toThrow('Test Error');
|
||||
|
||||
expect(messengerMock.call).toHaveBeenCalledTimes(1);
|
||||
expect(messengerMock.call).toHaveBeenCalledWith(
|
||||
'ApprovalController:rejectRequest',
|
||||
messageParamsMock.metamaskId,
|
||||
'Cancel',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
['cancelMessage', messageManagerMock],
|
||||
['cancelPersonalMessage', personalMessageManagerMock],
|
||||
['cancelTypedMessage', typedMessageManagerMock],
|
||||
])('%s', (cancelMethodName, messageManager) => {
|
||||
it('rejects message using message manager', async () => {
|
||||
signController[cancelMethodName](messageIdMock);
|
||||
|
||||
expect(messageManager.rejectMessage).toHaveBeenCalledTimes(1);
|
||||
expect(messageManager.rejectMessage).toHaveBeenCalledWith(
|
||||
messageParamsMock.metamaskId,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects approval using approval controller', async () => {
|
||||
signController[cancelMethodName](messageIdMock);
|
||||
|
||||
expect(messengerMock.call).toHaveBeenCalledTimes(1);
|
||||
expect(messengerMock.call).toHaveBeenCalledWith(
|
||||
'ApprovalController:rejectRequest',
|
||||
messageParamsMock.metamaskId,
|
||||
'Cancel',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not throw if rejecting approval throws', async () => {
|
||||
messengerMock.call.mockImplementation(() => {
|
||||
throw new Error('Test Error');
|
||||
});
|
||||
|
||||
await signController[cancelMethodName](messageParamsMock);
|
||||
});
|
||||
});
|
||||
|
||||
describe('message manager events', () => {
|
||||
it.each([
|
||||
['message manager', messageManagerMock],
|
||||
['personal message manager', personalMessageManagerMock],
|
||||
['typed message manager', typedMessageManagerMock],
|
||||
])('bubbles update badge event from %s', (_, messageManager) => {
|
||||
const mockListener = jest.fn();
|
||||
|
||||
signController.hub.on('updateBadge', mockListener);
|
||||
messageManager.hub.on.mock.calls[0][1]();
|
||||
|
||||
expect(mockListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['message manager', messageManagerMock, 'eth_sign'],
|
||||
['personal message manager', personalMessageManagerMock, 'personal_sign'],
|
||||
['typed message manager', typedMessageManagerMock, 'eth_signTypedData'],
|
||||
])(
|
||||
'requires approval on unapproved message event from %s',
|
||||
(_, messageManager, methodName) => {
|
||||
messengerMock.call.mockResolvedValueOnce({});
|
||||
|
||||
messageManager.hub.on.mock.calls[1][1](messageParamsMock);
|
||||
|
||||
expect(messengerMock.call).toHaveBeenCalledTimes(1);
|
||||
expect(messengerMock.call).toHaveBeenCalledWith(
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
id: messageIdMock,
|
||||
origin: messageParamsMock.origin,
|
||||
type: methodName,
|
||||
},
|
||||
true,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it('updates state on message manager state change', async () => {
|
||||
securityProviderRequestMock.mockResolvedValue(
|
||||
securityProviderResponseMock,
|
||||
);
|
||||
|
||||
await messageManagerMock.subscribe.mock.calls[0][0]({
|
||||
unapprovedMessages: { [messageIdMock]: coreMessageMock as any },
|
||||
unapprovedMessagesCount: 3,
|
||||
});
|
||||
|
||||
expect(await signController.state).toEqual({
|
||||
unapprovedMsgs: { [messageIdMock]: stateMessageMock as any },
|
||||
unapprovedPersonalMsgs: {},
|
||||
unapprovedTypedMessages: {},
|
||||
unapprovedMsgCount: 3,
|
||||
unapprovedPersonalMsgCount: 0,
|
||||
unapprovedTypedMessagesCount: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('updates state on personal message manager state change', async () => {
|
||||
securityProviderRequestMock.mockResolvedValue(
|
||||
securityProviderResponseMock,
|
||||
);
|
||||
|
||||
await personalMessageManagerMock.subscribe.mock.calls[0][0]({
|
||||
unapprovedMessages: { [messageIdMock]: coreMessageMock as any },
|
||||
unapprovedMessagesCount: 4,
|
||||
});
|
||||
|
||||
expect(await signController.state).toEqual({
|
||||
unapprovedMsgs: {},
|
||||
unapprovedPersonalMsgs: { [messageIdMock]: stateMessageMock as any },
|
||||
unapprovedTypedMessages: {},
|
||||
unapprovedMsgCount: 0,
|
||||
unapprovedPersonalMsgCount: 4,
|
||||
unapprovedTypedMessagesCount: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('updates state on typed message manager state change', async () => {
|
||||
securityProviderRequestMock.mockResolvedValue(
|
||||
securityProviderResponseMock,
|
||||
);
|
||||
|
||||
await typedMessageManagerMock.subscribe.mock.calls[0][0]({
|
||||
unapprovedMessages: { [messageIdMock]: coreMessageMock as any },
|
||||
unapprovedMessagesCount: 5,
|
||||
});
|
||||
|
||||
expect(await signController.state).toEqual({
|
||||
unapprovedMsgs: {},
|
||||
unapprovedPersonalMsgs: {},
|
||||
unapprovedTypedMessages: { [messageIdMock]: stateMessageMock as any },
|
||||
unapprovedMsgCount: 0,
|
||||
unapprovedPersonalMsgCount: 0,
|
||||
unapprovedTypedMessagesCount: 5,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,658 +0,0 @@
|
||||
import EventEmitter from 'events';
|
||||
import log from 'loglevel';
|
||||
import {
|
||||
MessageManager,
|
||||
MessageParams,
|
||||
MessageParamsMetamask,
|
||||
PersonalMessageManager,
|
||||
PersonalMessageParams,
|
||||
PersonalMessageParamsMetamask,
|
||||
TypedMessageManager,
|
||||
TypedMessageParams,
|
||||
TypedMessageParamsMetamask,
|
||||
} from '@metamask/message-manager';
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
import { bufferToHex } from 'ethereumjs-util';
|
||||
import { KeyringController } from '@metamask/eth-keyring-controller';
|
||||
import {
|
||||
AbstractMessageManager,
|
||||
AbstractMessage,
|
||||
MessageManagerState,
|
||||
AbstractMessageParams,
|
||||
AbstractMessageParamsMetamask,
|
||||
OriginalRequest,
|
||||
SecurityProviderRequest,
|
||||
} from '@metamask/message-manager/dist/AbstractMessageManager';
|
||||
import {
|
||||
BaseControllerV2,
|
||||
RestrictedControllerMessenger,
|
||||
} from '@metamask/base-controller';
|
||||
import { Patch } from 'immer';
|
||||
import {
|
||||
AcceptRequest,
|
||||
AddApprovalRequest,
|
||||
RejectRequest,
|
||||
} from '@metamask/approval-controller';
|
||||
import { MetaMetricsEventCategory } from '../../../shared/constants/metametrics';
|
||||
import { MESSAGE_TYPE } from '../../../shared/constants/app';
|
||||
import PreferencesController from './preferences';
|
||||
|
||||
const controllerName = 'SignController';
|
||||
const methodNameSign = MESSAGE_TYPE.ETH_SIGN;
|
||||
const methodNamePersonalSign = MESSAGE_TYPE.PERSONAL_SIGN;
|
||||
const methodNameTypedSign = MESSAGE_TYPE.ETH_SIGN_TYPED_DATA;
|
||||
|
||||
const stateMetadata = {
|
||||
unapprovedMsgs: { persist: false, anonymous: false },
|
||||
unapprovedPersonalMsgs: { persist: false, anonymous: false },
|
||||
unapprovedTypedMessages: { persist: false, anonymous: false },
|
||||
unapprovedMsgCount: { persist: false, anonymous: false },
|
||||
unapprovedPersonalMsgCount: { persist: false, anonymous: false },
|
||||
unapprovedTypedMessagesCount: { persist: false, anonymous: false },
|
||||
};
|
||||
|
||||
const getDefaultState = () => ({
|
||||
unapprovedMsgs: {},
|
||||
unapprovedPersonalMsgs: {},
|
||||
unapprovedTypedMessages: {},
|
||||
unapprovedMsgCount: 0,
|
||||
unapprovedPersonalMsgCount: 0,
|
||||
unapprovedTypedMessagesCount: 0,
|
||||
});
|
||||
|
||||
export type CoreMessage = AbstractMessage & {
|
||||
messageParams: AbstractMessageParams;
|
||||
};
|
||||
|
||||
export type StateMessage = Required<
|
||||
Omit<AbstractMessage, 'securityProviderResponse'>
|
||||
> & {
|
||||
msgParams: Required<AbstractMessageParams>;
|
||||
};
|
||||
|
||||
export type SignControllerState = {
|
||||
unapprovedMsgs: Record<string, StateMessage>;
|
||||
unapprovedPersonalMsgs: Record<string, StateMessage>;
|
||||
unapprovedTypedMessages: Record<string, StateMessage>;
|
||||
unapprovedMsgCount: number;
|
||||
unapprovedPersonalMsgCount: number;
|
||||
unapprovedTypedMessagesCount: number;
|
||||
};
|
||||
|
||||
export type GetSignState = {
|
||||
type: `${typeof controllerName}:getState`;
|
||||
handler: () => SignControllerState;
|
||||
};
|
||||
|
||||
export type SignStateChange = {
|
||||
type: `${typeof controllerName}:stateChange`;
|
||||
payload: [SignControllerState, Patch[]];
|
||||
};
|
||||
|
||||
export type SignControllerActions = GetSignState;
|
||||
|
||||
export type SignControllerEvents = SignStateChange;
|
||||
|
||||
type AllowedActions = AddApprovalRequest | AcceptRequest | RejectRequest;
|
||||
|
||||
export type SignControllerMessenger = RestrictedControllerMessenger<
|
||||
typeof controllerName,
|
||||
SignControllerActions | AllowedActions,
|
||||
SignControllerEvents,
|
||||
AllowedActions['type'],
|
||||
never
|
||||
>;
|
||||
|
||||
export type SignControllerOptions = {
|
||||
messenger: SignControllerMessenger;
|
||||
keyringController: KeyringController;
|
||||
preferencesController: PreferencesController;
|
||||
getState: () => any;
|
||||
metricsEvent: (payload: any, options?: any) => void;
|
||||
securityProviderRequest: SecurityProviderRequest;
|
||||
};
|
||||
|
||||
/**
|
||||
* Controller for creating signing requests requiring user approval.
|
||||
*/
|
||||
export default class SignController extends BaseControllerV2<
|
||||
typeof controllerName,
|
||||
SignControllerState,
|
||||
SignControllerMessenger
|
||||
> {
|
||||
hub: EventEmitter;
|
||||
|
||||
private _keyringController: KeyringController;
|
||||
|
||||
private _preferencesController: PreferencesController;
|
||||
|
||||
private _getState: () => any;
|
||||
|
||||
private _messageManager: MessageManager;
|
||||
|
||||
private _personalMessageManager: PersonalMessageManager;
|
||||
|
||||
private _typedMessageManager: TypedMessageManager;
|
||||
|
||||
private _messageManagers: AbstractMessageManager<
|
||||
AbstractMessage,
|
||||
AbstractMessageParams,
|
||||
AbstractMessageParamsMetamask
|
||||
>[];
|
||||
|
||||
private _metricsEvent: (payload: any, options?: any) => void;
|
||||
|
||||
/**
|
||||
* Construct a Sign controller.
|
||||
*
|
||||
* @param options - The controller options.
|
||||
* @param options.messenger - The restricted controller messenger for the sign controller.
|
||||
* @param options.keyringController - An instance of a keyring controller used to perform the signing operations.
|
||||
* @param options.preferencesController - An instance of a preferences controller to limit operations based on user configuration.
|
||||
* @param options.getState - Callback to retrieve all user state.
|
||||
* @param options.metricsEvent - A function for emitting a metric event.
|
||||
* @param options.securityProviderRequest - A function for verifying a message, whether it is malicious or not.
|
||||
*/
|
||||
constructor({
|
||||
messenger,
|
||||
keyringController,
|
||||
preferencesController,
|
||||
getState,
|
||||
metricsEvent,
|
||||
securityProviderRequest,
|
||||
}: SignControllerOptions) {
|
||||
super({
|
||||
name: controllerName,
|
||||
metadata: stateMetadata,
|
||||
messenger,
|
||||
state: getDefaultState(),
|
||||
});
|
||||
|
||||
this._keyringController = keyringController;
|
||||
this._preferencesController = preferencesController;
|
||||
this._getState = getState;
|
||||
this._metricsEvent = metricsEvent;
|
||||
|
||||
this.hub = new EventEmitter();
|
||||
this._messageManager = new MessageManager(
|
||||
undefined,
|
||||
undefined,
|
||||
securityProviderRequest,
|
||||
);
|
||||
this._personalMessageManager = new PersonalMessageManager(
|
||||
undefined,
|
||||
undefined,
|
||||
securityProviderRequest,
|
||||
);
|
||||
this._typedMessageManager = new TypedMessageManager(
|
||||
undefined,
|
||||
undefined,
|
||||
securityProviderRequest,
|
||||
);
|
||||
|
||||
this._messageManagers = [
|
||||
this._messageManager,
|
||||
this._personalMessageManager,
|
||||
this._typedMessageManager,
|
||||
];
|
||||
|
||||
const methodNames = [
|
||||
methodNameSign,
|
||||
methodNamePersonalSign,
|
||||
methodNameTypedSign,
|
||||
];
|
||||
|
||||
this._messageManagers.forEach((messageManager, index) => {
|
||||
this._bubbleEvents(messageManager);
|
||||
|
||||
messageManager.hub.on(
|
||||
'unapprovedMessage',
|
||||
(msgParams: AbstractMessageParamsMetamask) => {
|
||||
this._requestApproval(msgParams, methodNames[index]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
this._subscribeToMessageState(
|
||||
this._messageManager,
|
||||
(state, newMessages, messageCount) => {
|
||||
state.unapprovedMsgs = newMessages;
|
||||
state.unapprovedMsgCount = messageCount;
|
||||
},
|
||||
);
|
||||
|
||||
this._subscribeToMessageState(
|
||||
this._personalMessageManager,
|
||||
(state, newMessages, messageCount) => {
|
||||
state.unapprovedPersonalMsgs = newMessages;
|
||||
state.unapprovedPersonalMsgCount = messageCount;
|
||||
},
|
||||
);
|
||||
|
||||
this._subscribeToMessageState(
|
||||
this._typedMessageManager,
|
||||
(state, newMessages, messageCount) => {
|
||||
state.unapprovedTypedMessages = newMessages;
|
||||
state.unapprovedTypedMessagesCount = messageCount;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the number of 'unapproved' Messages in this.messages
|
||||
*
|
||||
* @returns The number of 'unapproved' Messages in this.messages
|
||||
*/
|
||||
get unapprovedMsgCount(): number {
|
||||
return this._messageManager.getUnapprovedMessagesCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the number of 'unapproved' PersonalMessages in this.messages
|
||||
*
|
||||
* @returns The number of 'unapproved' PersonalMessages in this.messages
|
||||
*/
|
||||
get unapprovedPersonalMessagesCount(): number {
|
||||
return this._personalMessageManager.getUnapprovedMessagesCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the number of 'unapproved' TypedMessages in this.messages
|
||||
*
|
||||
* @returns The number of 'unapproved' TypedMessages in this.messages
|
||||
*/
|
||||
get unapprovedTypedMessagesCount(): number {
|
||||
return this._typedMessageManager.getUnapprovedMessagesCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the controller state to the initial state.
|
||||
*/
|
||||
resetState() {
|
||||
this.update(() => getDefaultState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a Dapp uses the eth_sign method, to request user approval.
|
||||
* eth_sign is a pure signature of arbitrary data. It is on a deprecation
|
||||
* path, since this data can be a transaction, or can leak private key
|
||||
* information.
|
||||
*
|
||||
* @param msgParams - The params passed to eth_sign.
|
||||
* @param [req] - The original request, containing the origin.
|
||||
*/
|
||||
async newUnsignedMessage(
|
||||
msgParams: MessageParams,
|
||||
req: OriginalRequest,
|
||||
): Promise<string> {
|
||||
const {
|
||||
// eslint-disable-next-line camelcase
|
||||
disabledRpcMethodPreferences: { eth_sign },
|
||||
} = this._preferencesController.store.getState() as any;
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
if (!eth_sign) {
|
||||
throw ethErrors.rpc.methodNotFound(
|
||||
'eth_sign has been disabled. You must enable it in the advanced settings',
|
||||
);
|
||||
}
|
||||
|
||||
const data = this._normalizeMsgData(msgParams.data);
|
||||
|
||||
// 64 hex + "0x" at the beginning
|
||||
// This is needed because Ethereum's EcSign works only on 32 byte numbers
|
||||
// For 67 length see: https://github.com/MetaMask/metamask-extension/pull/12679/files#r749479607
|
||||
if (data.length !== 66 && data.length !== 67) {
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
'eth_sign requires 32 byte message hash',
|
||||
);
|
||||
}
|
||||
|
||||
return this._messageManager.addUnapprovedMessageAsync(msgParams, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a dapp uses the personal_sign method.
|
||||
* This is identical to the Geth eth_sign method, and may eventually replace
|
||||
* eth_sign.
|
||||
*
|
||||
* We currently define our eth_sign and personal_sign mostly for legacy Dapps.
|
||||
*
|
||||
* @param msgParams - The params of the message to sign & return to the Dapp.
|
||||
* @param req - The original request, containing the origin.
|
||||
*/
|
||||
async newUnsignedPersonalMessage(
|
||||
msgParams: PersonalMessageParams,
|
||||
req: OriginalRequest,
|
||||
): Promise<string> {
|
||||
return this._personalMessageManager.addUnapprovedMessageAsync(
|
||||
msgParams,
|
||||
req,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a dapp uses the eth_signTypedData method, per EIP 712.
|
||||
*
|
||||
* @param msgParams - The params passed to eth_signTypedData.
|
||||
* @param req - The original request, containing the origin.
|
||||
* @param version
|
||||
*/
|
||||
async newUnsignedTypedMessage(
|
||||
msgParams: TypedMessageParams,
|
||||
req: OriginalRequest,
|
||||
version: string,
|
||||
): Promise<string> {
|
||||
return this._typedMessageManager.addUnapprovedMessageAsync(
|
||||
msgParams,
|
||||
version,
|
||||
req,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signifies user intent to complete an eth_sign method.
|
||||
*
|
||||
* @param msgParams - The params passed to eth_call.
|
||||
* @returns Full state update.
|
||||
*/
|
||||
async signMessage(msgParams: MessageParamsMetamask) {
|
||||
return await this._signAbstractMessage(
|
||||
this._messageManager,
|
||||
methodNameSign,
|
||||
msgParams,
|
||||
async (cleanMsgParams) =>
|
||||
await this._keyringController.signMessage(cleanMsgParams),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signifies a user's approval to sign a personal_sign message in queue.
|
||||
* Triggers signing, and the callback function from newUnsignedPersonalMessage.
|
||||
*
|
||||
* @param msgParams - The params of the message to sign & return to the Dapp.
|
||||
* @returns A full state update.
|
||||
*/
|
||||
async signPersonalMessage(msgParams: PersonalMessageParamsMetamask) {
|
||||
return await this._signAbstractMessage(
|
||||
this._personalMessageManager,
|
||||
methodNamePersonalSign,
|
||||
msgParams,
|
||||
async (cleanMsgParams) =>
|
||||
await this._keyringController.signPersonalMessage(cleanMsgParams),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method for a user approving a call to eth_signTypedData, per EIP 712.
|
||||
* Triggers the callback in newUnsignedTypedMessage.
|
||||
*
|
||||
* @param msgParams - The params passed to eth_signTypedData.
|
||||
* @returns Full state update.
|
||||
*/
|
||||
async signTypedMessage(msgParams: TypedMessageParamsMetamask) {
|
||||
const { version } = msgParams;
|
||||
|
||||
return await this._signAbstractMessage(
|
||||
this._typedMessageManager,
|
||||
methodNameTypedSign,
|
||||
msgParams,
|
||||
async (cleanMsgParams) => {
|
||||
// For some reason every version after V1 used stringified params.
|
||||
if (version !== 'V1') {
|
||||
// But we don't have to require that. We can stop suggesting it now:
|
||||
if (typeof cleanMsgParams.data === 'string') {
|
||||
cleanMsgParams.data = JSON.parse(cleanMsgParams.data);
|
||||
}
|
||||
}
|
||||
|
||||
return await this._keyringController.signTypedMessage(cleanMsgParams, {
|
||||
version,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cancel a message submitted via eth_sign.
|
||||
*
|
||||
* @param msgId - The id of the message to cancel.
|
||||
* @returns A full state update.
|
||||
*/
|
||||
cancelMessage(msgId: string) {
|
||||
return this._cancelAbstractMessage(this._messageManager, msgId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cancel a personal_sign type message.
|
||||
*
|
||||
* @param msgId - The ID of the message to cancel.
|
||||
* @returns A full state update.
|
||||
*/
|
||||
cancelPersonalMessage(msgId: string) {
|
||||
return this._cancelAbstractMessage(this._personalMessageManager, msgId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cancel a eth_signTypedData type message.
|
||||
*
|
||||
* @param msgId - The ID of the message to cancel.
|
||||
* @returns A full state update.
|
||||
*/
|
||||
cancelTypedMessage(msgId: string) {
|
||||
return this._cancelAbstractMessage(this._typedMessageManager, msgId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject all unapproved messages of any type.
|
||||
*
|
||||
* @param reason - A message to indicate why.
|
||||
*/
|
||||
rejectUnapproved(reason?: string) {
|
||||
this._messageManagers.forEach((messageManager) => {
|
||||
Object.keys(messageManager.getUnapprovedMessages()).forEach(
|
||||
(messageId) => {
|
||||
this._cancelAbstractMessage(messageManager, messageId, reason);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all unapproved messages from memory.
|
||||
*/
|
||||
clearUnapproved() {
|
||||
this._messageManagers.forEach((messageManager) => {
|
||||
messageManager.update({
|
||||
unapprovedMessages: {},
|
||||
unapprovedMessagesCount: 0,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async _signAbstractMessage<P extends AbstractMessageParams>(
|
||||
messageManager: AbstractMessageManager<
|
||||
AbstractMessage,
|
||||
P,
|
||||
AbstractMessageParamsMetamask
|
||||
>,
|
||||
methodName: string,
|
||||
msgParams: AbstractMessageParamsMetamask,
|
||||
getSignature: (cleanMessageParams: P) => Promise<any>,
|
||||
) {
|
||||
log.info(`MetaMaskController - ${methodName}`);
|
||||
|
||||
const messageId = msgParams.metamaskId as string;
|
||||
|
||||
try {
|
||||
const cleanMessageParams = await messageManager.approveMessage(msgParams);
|
||||
const signature = await getSignature(cleanMessageParams);
|
||||
|
||||
messageManager.setMessageStatusSigned(messageId, signature);
|
||||
|
||||
this._acceptApproval(messageId);
|
||||
|
||||
return this._getState();
|
||||
} catch (error) {
|
||||
log.info(`MetaMaskController - ${methodName} failed.`, error);
|
||||
this._cancelAbstractMessage(messageManager, messageId);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private _cancelAbstractMessage(
|
||||
messageManager: AbstractMessageManager<
|
||||
AbstractMessage,
|
||||
AbstractMessageParams,
|
||||
AbstractMessageParamsMetamask
|
||||
>,
|
||||
messageId: string,
|
||||
reason?: string,
|
||||
) {
|
||||
if (reason) {
|
||||
const message = this._getMessage(messageId);
|
||||
|
||||
this._metricsEvent({
|
||||
event: reason,
|
||||
category: MetaMetricsEventCategory.Transactions,
|
||||
properties: {
|
||||
action: 'Sign Request',
|
||||
type: message.type,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
messageManager.rejectMessage(messageId);
|
||||
this._rejectApproval(messageId);
|
||||
|
||||
return this._getState();
|
||||
}
|
||||
|
||||
private _bubbleEvents(
|
||||
messageManager: AbstractMessageManager<
|
||||
AbstractMessage,
|
||||
AbstractMessageParams,
|
||||
AbstractMessageParamsMetamask
|
||||
>,
|
||||
) {
|
||||
messageManager.hub.on('updateBadge', () => {
|
||||
this.hub.emit('updateBadge');
|
||||
});
|
||||
}
|
||||
|
||||
private _subscribeToMessageState(
|
||||
messageManager: AbstractMessageManager<
|
||||
AbstractMessage,
|
||||
AbstractMessageParams,
|
||||
AbstractMessageParamsMetamask
|
||||
>,
|
||||
updateState: (
|
||||
state: SignControllerState,
|
||||
newMessages: Record<string, StateMessage>,
|
||||
messageCount: number,
|
||||
) => void,
|
||||
) {
|
||||
messageManager.subscribe(
|
||||
async (state: MessageManagerState<AbstractMessage>) => {
|
||||
const newMessages = await this._migrateMessages(
|
||||
state.unapprovedMessages as any,
|
||||
);
|
||||
|
||||
this.update((draftState) => {
|
||||
updateState(draftState, newMessages, state.unapprovedMessagesCount);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async _migrateMessages(
|
||||
coreMessages: Record<string, CoreMessage>,
|
||||
): Promise<Record<string, StateMessage>> {
|
||||
const stateMessages: Record<string, StateMessage> = {};
|
||||
|
||||
for (const messageId of Object.keys(coreMessages)) {
|
||||
const coreMessage = coreMessages[messageId];
|
||||
const stateMessage = await this._migrateMessage(coreMessage);
|
||||
|
||||
stateMessages[messageId] = stateMessage;
|
||||
}
|
||||
|
||||
return stateMessages;
|
||||
}
|
||||
|
||||
private async _migrateMessage(
|
||||
coreMessage: CoreMessage,
|
||||
): Promise<StateMessage> {
|
||||
const { messageParams, ...coreMessageData } = coreMessage;
|
||||
|
||||
// Core message managers use messageParams but frontend uses msgParams with lots of references
|
||||
const stateMessage = {
|
||||
...coreMessageData,
|
||||
rawSig: coreMessage.rawSig as string,
|
||||
msgParams: {
|
||||
...messageParams,
|
||||
origin: messageParams.origin as string,
|
||||
},
|
||||
};
|
||||
return stateMessage;
|
||||
}
|
||||
|
||||
private _normalizeMsgData(data: string) {
|
||||
if (data.slice(0, 2) === '0x') {
|
||||
// data is already hex
|
||||
return data;
|
||||
}
|
||||
// data is unicode, convert to hex
|
||||
return bufferToHex(Buffer.from(data, 'utf8'));
|
||||
}
|
||||
|
||||
private _getMessage(messageId: string): StateMessage {
|
||||
return {
|
||||
...this.state.unapprovedMsgs,
|
||||
...this.state.unapprovedPersonalMsgs,
|
||||
...this.state.unapprovedTypedMessages,
|
||||
}[messageId];
|
||||
}
|
||||
|
||||
private _requestApproval(
|
||||
msgParams: AbstractMessageParamsMetamask,
|
||||
type: string,
|
||||
) {
|
||||
const id = msgParams.metamaskId as string;
|
||||
const origin = msgParams.origin || controllerName;
|
||||
|
||||
this.messagingSystem
|
||||
.call(
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
id,
|
||||
origin,
|
||||
type,
|
||||
},
|
||||
true,
|
||||
)
|
||||
.catch(() => {
|
||||
// Intentionally ignored as promise not currently used
|
||||
});
|
||||
}
|
||||
|
||||
private _acceptApproval(messageId: string) {
|
||||
try {
|
||||
this.messagingSystem.call('ApprovalController:acceptRequest', messageId);
|
||||
} catch (error) {
|
||||
log.info('Failed to accept signature approval request', error);
|
||||
}
|
||||
}
|
||||
|
||||
private _rejectApproval(messageId: string) {
|
||||
try {
|
||||
this.messagingSystem.call(
|
||||
'ApprovalController:rejectRequest',
|
||||
messageId,
|
||||
'Cancel',
|
||||
);
|
||||
} catch (error) {
|
||||
log.info('Failed to reject signature approval request', error);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,8 +3,9 @@ import { ObservableStore } from '@metamask/obs-store';
|
||||
import { bufferToHex, keccak, toBuffer, isHexString } from 'ethereumjs-util';
|
||||
import EthQuery from 'ethjs-query';
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
import Common from '@ethereumjs/common';
|
||||
import { Common, Hardfork } from '@ethereumjs/common';
|
||||
import { TransactionFactory } from '@ethereumjs/tx';
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import NonceTracker from 'nonce-tracker';
|
||||
import log from 'loglevel';
|
||||
import BigNumber from 'bignumber.js';
|
||||
@ -41,7 +42,6 @@ import {
|
||||
import { isSwapsDefaultTokenAddress } from '../../../../shared/modules/swaps.utils';
|
||||
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
|
||||
import {
|
||||
HARDFORKS,
|
||||
CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP,
|
||||
NETWORK_TYPES,
|
||||
NetworkStatus,
|
||||
@ -52,10 +52,7 @@ import {
|
||||
determineTransactionType,
|
||||
isEIP1559Transaction,
|
||||
} from '../../../../shared/modules/transaction.utils';
|
||||
import {
|
||||
ORIGIN_METAMASK,
|
||||
MESSAGE_TYPE,
|
||||
} from '../../../../shared/constants/app';
|
||||
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
|
||||
import {
|
||||
calcGasTotal,
|
||||
getSwapsTokensReceivedFromTxMeta,
|
||||
@ -225,6 +222,10 @@ export default class TransactionController extends EventEmitter {
|
||||
// request state update to finalize initialization
|
||||
this._updatePendingTxsAfterFirstBlock();
|
||||
this._onBootCleanUp();
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
this.transactionUpdateController = opts.transactionUpdateController;
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
|
||||
/**
|
||||
@ -271,7 +272,7 @@ export default class TransactionController extends EventEmitter {
|
||||
// This logic below will have to be updated each time a hardfork happens
|
||||
// that carries with it a new Transaction type. It is inconsequential for
|
||||
// hardforks that do not include new types.
|
||||
const hardfork = supportsEIP1559 ? HARDFORKS.LONDON : HARDFORKS.BERLIN;
|
||||
const hardfork = supportsEIP1559 ? Hardfork.London : Hardfork.Berlin;
|
||||
|
||||
// type will be one of our default network names or 'rpc'. the default
|
||||
// network names are sufficient configuration, simply pass the name as the
|
||||
@ -291,7 +292,7 @@ export default class TransactionController extends EventEmitter {
|
||||
const networkStatus = this.getNetworkStatus();
|
||||
const networkId = this.getNetworkId();
|
||||
|
||||
const customChainParams = {
|
||||
return Common.custom({
|
||||
name,
|
||||
chainId,
|
||||
// It is improbable for a transaction to be signed while the network
|
||||
@ -305,13 +306,8 @@ export default class TransactionController extends EventEmitter {
|
||||
// hardcoding networkId to 'loading'.
|
||||
networkId:
|
||||
networkStatus === NetworkStatus.Available ? parseInt(networkId, 10) : 0,
|
||||
};
|
||||
|
||||
return Common.forCustomChain(
|
||||
NETWORK_TYPES.MAINNET,
|
||||
customChainParams,
|
||||
hardfork,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -398,6 +394,22 @@ export default class TransactionController extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates approvals for all unapproved transactions in the txStateManager.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initApprovals() {
|
||||
const unapprovedTxs = this.txStateManager.getUnapprovedTxList();
|
||||
return Promise.all(
|
||||
Object.values(unapprovedTxs).map((txMeta) =>
|
||||
this._requestApproval(txMeta, {
|
||||
shouldShowRequest: false,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ====================================================================================================================================================
|
||||
|
||||
/**
|
||||
@ -1248,7 +1260,9 @@ export default class TransactionController extends EventEmitter {
|
||||
}
|
||||
|
||||
this.addTransaction(newTxMeta);
|
||||
await this.approveTransaction(newTxMeta.id, actionId);
|
||||
await this.approveTransaction(newTxMeta.id, actionId, {
|
||||
hasApprovalRequest: false,
|
||||
});
|
||||
return newTxMeta;
|
||||
}
|
||||
|
||||
@ -1306,7 +1320,9 @@ export default class TransactionController extends EventEmitter {
|
||||
}
|
||||
|
||||
this.addTransaction(newTxMeta);
|
||||
await this.approveTransaction(newTxMeta.id, actionId);
|
||||
await this.approveTransaction(newTxMeta.id, actionId, {
|
||||
hasApprovalRequest: false,
|
||||
});
|
||||
return newTxMeta;
|
||||
}
|
||||
|
||||
@ -1345,14 +1361,26 @@ export default class TransactionController extends EventEmitter {
|
||||
*
|
||||
* @param {number} txId - the tx's Id
|
||||
* @param {string} actionId - actionId passed from UI
|
||||
* @param opts - options object
|
||||
* @param opts.hasApprovalRequest - whether the transaction has an approval request
|
||||
*/
|
||||
async approveTransaction(txId, actionId) {
|
||||
async approveTransaction(txId, actionId, { hasApprovalRequest = true } = {}) {
|
||||
// TODO: Move this safety out of this function.
|
||||
// Since this transaction is async,
|
||||
// we need to keep track of what is currently being signed,
|
||||
// So that we do not increment nonce + resubmit something
|
||||
// that is already being incremented & signed.
|
||||
const txMeta = this.txStateManager.getTransaction(txId);
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
// MMI does not broadcast transactions, as that is the responsibility of the custodian
|
||||
if (txMeta.custodyStatus) {
|
||||
this.inProcessOfSigning.delete(txId);
|
||||
await this.signTransaction(txId);
|
||||
return;
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
if (this.inProcessOfSigning.has(txId)) {
|
||||
return;
|
||||
}
|
||||
@ -1361,7 +1389,9 @@ export default class TransactionController extends EventEmitter {
|
||||
try {
|
||||
// approve
|
||||
this.txStateManager.setTxStatusApproved(txId);
|
||||
this._acceptApproval(txMeta);
|
||||
if (hasApprovalRequest) {
|
||||
this._acceptApproval(txMeta);
|
||||
}
|
||||
// get next nonce
|
||||
const fromAddress = txMeta.txParams.from;
|
||||
// wait for a nonce
|
||||
@ -1504,13 +1534,31 @@ export default class TransactionController extends EventEmitter {
|
||||
const fromAddress = txParams.from;
|
||||
const common = await this.getCommonConfiguration(txParams.from);
|
||||
const unsignedEthTx = TransactionFactory.fromTxData(txParams, { common });
|
||||
const signedEthTx = await this.signEthTx(unsignedEthTx, fromAddress);
|
||||
const signedEthTx = await this.signEthTx(
|
||||
unsignedEthTx,
|
||||
fromAddress,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
txMeta.custodyStatus ? txMeta : undefined,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
);
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
if (txMeta.custodyStatus) {
|
||||
txMeta.custodyId = signedEthTx.custodian_transactionId;
|
||||
txMeta.custodyStatus = signedEthTx.transactionStatus;
|
||||
|
||||
this.transactionUpdateController.addTransactionToWatchList(
|
||||
txMeta.custodyId,
|
||||
fromAddress,
|
||||
);
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
// add r,s,v values for provider request purposes see createMetamaskMiddleware
|
||||
// and JSON rpc standard for further explanation
|
||||
txMeta.r = bufferToHex(signedEthTx.r);
|
||||
txMeta.s = bufferToHex(signedEthTx.s);
|
||||
txMeta.v = bufferToHex(signedEthTx.v);
|
||||
txMeta.r = addHexPrefix(signedEthTx.r.toString(16));
|
||||
txMeta.s = addHexPrefix(signedEthTx.s.toString(16));
|
||||
txMeta.v = addHexPrefix(signedEthTx.v.toString(16));
|
||||
|
||||
this.txStateManager.updateTransaction(
|
||||
txMeta,
|
||||
@ -1868,9 +1916,18 @@ export default class TransactionController extends EventEmitter {
|
||||
},
|
||||
})
|
||||
.forEach((txMeta) => {
|
||||
// Line below will try to publish transaction which is in
|
||||
// APPROVED state at the time of controller bootup
|
||||
this.approveTransaction(txMeta.id);
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
// If you create a Tx and its still inside the custodian waiting to be approved we don't want to approve it right away
|
||||
if (!txMeta.custodyStatus) {
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
// Line below will try to publish transaction which is in
|
||||
// APPROVED state at the time of controller bootup
|
||||
this.approveTransaction(txMeta.id);
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
});
|
||||
}
|
||||
|
||||
@ -2605,13 +2662,16 @@ export default class TransactionController extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
_requestApproval(txMeta) {
|
||||
async _requestApproval(
|
||||
txMeta,
|
||||
{ shouldShowRequest } = { shouldShowRequest: true },
|
||||
) {
|
||||
const id = this._getApprovalId(txMeta);
|
||||
const { origin } = txMeta;
|
||||
const type = MESSAGE_TYPE.TRANSACTION;
|
||||
const type = ApprovalType.Transaction;
|
||||
const requestData = { txId: txMeta.id };
|
||||
|
||||
this.messagingSystem
|
||||
return this.messagingSystem
|
||||
.call(
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
@ -2620,7 +2680,7 @@ export default class TransactionController extends EventEmitter {
|
||||
type,
|
||||
requestData,
|
||||
},
|
||||
true,
|
||||
shouldShowRequest,
|
||||
)
|
||||
.catch(() => {
|
||||
// Intentionally ignored as promise not currently used
|
||||
|
@ -3,6 +3,7 @@ import EventEmitter from 'events';
|
||||
import { toBuffer } from 'ethereumjs-util';
|
||||
import { TransactionFactory } from '@ethereumjs/tx';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import {
|
||||
@ -29,10 +30,7 @@ import {
|
||||
GasRecommendations,
|
||||
} from '../../../../shared/constants/gas';
|
||||
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
|
||||
import {
|
||||
MESSAGE_TYPE,
|
||||
ORIGIN_METAMASK,
|
||||
} from '../../../../shared/constants/app';
|
||||
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
|
||||
import { NetworkStatus } from '../../../../shared/constants/network';
|
||||
import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils';
|
||||
import TransactionController from '.';
|
||||
@ -513,7 +511,7 @@ describe('Transaction Controller', function () {
|
||||
id: String(txMeta.id),
|
||||
origin: ORIGIN_METAMASK,
|
||||
requestData: { txId: txMeta.id },
|
||||
type: MESSAGE_TYPE.TRANSACTION,
|
||||
type: ApprovalType.Transaction,
|
||||
},
|
||||
true, // Show popup
|
||||
]);
|
||||
@ -551,7 +549,7 @@ describe('Transaction Controller', function () {
|
||||
id: String(secondTxMeta.id),
|
||||
origin: ORIGIN_METAMASK,
|
||||
requestData: { txId: secondTxMeta.id },
|
||||
type: MESSAGE_TYPE.TRANSACTION,
|
||||
type: ApprovalType.Transaction,
|
||||
},
|
||||
true, // Show popup
|
||||
]);
|
||||
@ -608,6 +606,8 @@ describe('Transaction Controller', function () {
|
||||
cancelTxMeta.id,
|
||||
);
|
||||
assert.deepEqual(cancelTxMeta, memTxMeta);
|
||||
// One for the initial addUnapprovedTransaction, one for the approval
|
||||
assert.equal(messengerMock.call.callCount, 2);
|
||||
});
|
||||
|
||||
it('should add only 1 cancel transaction when called twice with same actionId', async function () {
|
||||
@ -1150,7 +1150,7 @@ describe('Transaction Controller', function () {
|
||||
);
|
||||
const rawTx = await txController.signTransaction('1');
|
||||
const ethTx = TransactionFactory.fromSerializedData(toBuffer(rawTx));
|
||||
assert.equal(ethTx.common.chainIdBN().toNumber(), 5);
|
||||
assert.equal(Number(ethTx.common.chainId()), 5);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1385,6 +1385,7 @@ describe('Transaction Controller', function () {
|
||||
type: TransactionType.retry,
|
||||
},
|
||||
);
|
||||
assert.equal(messengerMock.call.callCount, 0);
|
||||
});
|
||||
|
||||
it('should call this.approveTransaction with the id of the returned tx', async function () {
|
||||
@ -2991,4 +2992,58 @@ describe('Transaction Controller', function () {
|
||||
assert.equal(result.type, TransactionType.simpleSend);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initApprovals', function () {
|
||||
it('adds unapprovedTxs as approvals', async function () {
|
||||
const firstTxId = '1';
|
||||
txController.addTransaction(
|
||||
{
|
||||
id: firstTxId,
|
||||
origin: ORIGIN_METAMASK,
|
||||
status: TransactionStatus.unapproved,
|
||||
metamaskNetworkId: currentNetworkId,
|
||||
txParams: {
|
||||
to: VALID_ADDRESS,
|
||||
from: VALID_ADDRESS_TWO,
|
||||
},
|
||||
},
|
||||
noop,
|
||||
);
|
||||
const secondTxId = '2';
|
||||
txController.addTransaction(
|
||||
{
|
||||
id: secondTxId,
|
||||
origin: ORIGIN_METAMASK,
|
||||
status: TransactionStatus.unapproved,
|
||||
metamaskNetworkId: currentNetworkId,
|
||||
txParams: {
|
||||
to: VALID_ADDRESS,
|
||||
from: VALID_ADDRESS_TWO,
|
||||
},
|
||||
},
|
||||
noop,
|
||||
);
|
||||
await txController.initApprovals();
|
||||
assert.deepEqual(messengerMock.call.getCall(0).args, [
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
id: firstTxId,
|
||||
origin: ORIGIN_METAMASK,
|
||||
requestData: { txId: firstTxId },
|
||||
type: ApprovalType.Transaction,
|
||||
},
|
||||
false,
|
||||
]);
|
||||
assert.deepEqual(messengerMock.call.getCall(1).args, [
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
id: secondTxId,
|
||||
origin: ORIGIN_METAMASK,
|
||||
requestData: { txId: secondTxId },
|
||||
type: ApprovalType.Transaction,
|
||||
},
|
||||
false,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -144,6 +144,13 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
// Don't ever resubmit custodian transactions
|
||||
if (txMeta.custodyId) {
|
||||
return undefined;
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
// Only auto-submit already-signed txs:
|
||||
if (!('rawTx' in txMeta)) {
|
||||
return this.approveTransaction(txMeta.id);
|
||||
@ -180,7 +187,15 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
|
||||
// extra check in case there was an uncaught error during the
|
||||
// signature and submission process
|
||||
if (!txHash) {
|
||||
|
||||
let hasNoHash = !txHash;
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
// Don't emit noTxHashErr for custodian transactions
|
||||
hasNoHash ||= !txMeta.custodyId;
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
if (hasNoHash) {
|
||||
const noTxHashErr = new Error(
|
||||
'We had an error while submitting this transaction, please try again.',
|
||||
);
|
||||
|
@ -374,6 +374,43 @@ describe('PendingTransactionTracker', function () {
|
||||
'should NOT try to publish transaction',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return undefined if txMeta has custodyId property', async function () {
|
||||
const txMeta = {
|
||||
custodyId: 1,
|
||||
id: 1,
|
||||
hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
|
||||
status: TransactionStatus.signed,
|
||||
txParams: {
|
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
nonce: '0x1',
|
||||
value: '0xfffff',
|
||||
},
|
||||
history: [{}],
|
||||
rawTx:
|
||||
'0xf86c808504a817c80086a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
|
||||
};
|
||||
const approveTransaction = sinon.spy();
|
||||
const publishTransaction = sinon.spy();
|
||||
const pendingTxTracker = new PendingTransactionTracker({
|
||||
query: {
|
||||
getTransactionReceipt: sinon.stub(),
|
||||
},
|
||||
nonceTracker: {
|
||||
getGlobalLock: sinon.stub().resolves({
|
||||
releaseLock: sinon.spy(),
|
||||
}),
|
||||
},
|
||||
getPendingTransactions: sinon.stub().returns([]),
|
||||
getCompletedTransactions: sinon.stub().returns([]),
|
||||
approveTransaction,
|
||||
publishTransaction,
|
||||
confirmTransaction: sinon.spy(),
|
||||
});
|
||||
|
||||
const result = await pendingTxTracker._resubmitTx(txMeta);
|
||||
assert.equal(result, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_checkIfTxWasDropped', function () {
|
||||
@ -576,6 +613,7 @@ describe('PendingTransactionTracker', function () {
|
||||
},
|
||||
history: [{}],
|
||||
rawTx: '0xf86c808504a817c80082471d',
|
||||
custodyId: 'testid',
|
||||
};
|
||||
const nonceBN = new BN(2);
|
||||
const pendingTxTracker = new PendingTransactionTracker({
|
||||
@ -720,6 +758,7 @@ describe('PendingTransactionTracker', function () {
|
||||
id: '123',
|
||||
value: '0x02',
|
||||
hash: '0x2a919d2512ec963f524bfd9730fb66b6d5a2e399d1dd957abb5e2b544a12644b',
|
||||
custodyId: 'testid',
|
||||
},
|
||||
];
|
||||
const pendingTxTracker = new PendingTransactionTracker({
|
||||
@ -771,6 +810,7 @@ describe('PendingTransactionTracker', function () {
|
||||
},
|
||||
history: [{}],
|
||||
rawTx: '0xf86c808504a817c80082471d',
|
||||
custodyId: 'testid',
|
||||
};
|
||||
const pendingTxTracker = new PendingTransactionTracker({
|
||||
query: {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { TransactionFactory } from '@ethereumjs/tx';
|
||||
import Common from '@ethereumjs/common';
|
||||
import { Common } from '@ethereumjs/common';
|
||||
import { hexToBn } from '../../lib/util';
|
||||
import { bnToHex } from '../../../../shared/modules/conversion.utils';
|
||||
import TxUtils from './tx-gas-utils';
|
||||
@ -37,7 +37,7 @@ describe('txUtils', function () {
|
||||
common: new Common({ chain: 'goerli' }),
|
||||
});
|
||||
assert.equal(
|
||||
ethTx.common.chainIdBN().toNumber(),
|
||||
Number(ethTx.common.chainId()),
|
||||
5,
|
||||
'chainId is set from tx params',
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
import { handlers as permittedSnapMethods } from '@metamask/rpc-methods/dist/permitted';
|
||||
import { permittedMethods as permittedSnapMethods } from '@metamask/rpc-methods';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import { permissionRpcMethods } from '@metamask/permission-controller';
|
||||
import { selectHooks } from '@metamask/rpc-methods/dist/utils';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ethErrors, errorCodes } from 'eth-rpc-errors';
|
||||
import validUrl from 'valid-url';
|
||||
import { omit } from 'lodash';
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import {
|
||||
MESSAGE_TYPE,
|
||||
UNKNOWN_TICKER_SYMBOL,
|
||||
@ -158,7 +159,7 @@ async function addEthereumChainHandler(
|
||||
try {
|
||||
await requestUserApproval({
|
||||
origin,
|
||||
type: MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN,
|
||||
type: ApprovalType.SwitchEthereumChain,
|
||||
requestData: {
|
||||
rpcUrl: existingNetwork.rpcUrl,
|
||||
chainId: existingNetwork.chainId,
|
||||
@ -244,7 +245,7 @@ async function addEthereumChainHandler(
|
||||
try {
|
||||
await requestUserApproval({
|
||||
origin,
|
||||
type: MESSAGE_TYPE.ADD_ETHEREUM_CHAIN,
|
||||
type: ApprovalType.AddEthereumChain,
|
||||
requestData: {
|
||||
chainId: _chainId,
|
||||
rpcPrefs: { blockExplorerUrl: firstValidBlockExplorerUrl },
|
||||
@ -275,7 +276,7 @@ async function addEthereumChainHandler(
|
||||
try {
|
||||
await requestUserApproval({
|
||||
origin,
|
||||
type: MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN,
|
||||
type: ApprovalType.SwitchEthereumChain,
|
||||
requestData: {
|
||||
rpcUrl: firstValidRPCUrl,
|
||||
chainId: _chainId,
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
import { omit } from 'lodash';
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import { MESSAGE_TYPE } from '../../../../../shared/constants/app';
|
||||
import {
|
||||
CHAIN_ID_TO_TYPE_MAP,
|
||||
NETWORK_TO_NAME_MAP,
|
||||
CHAIN_ID_TO_RPC_URL_MAP,
|
||||
CURRENCY_SYMBOLS,
|
||||
NETWORK_TYPES,
|
||||
BUILT_IN_INFURA_NETWORKS,
|
||||
} from '../../../../../shared/constants/network';
|
||||
import {
|
||||
isPrefixedFormattedHexString,
|
||||
@ -27,7 +28,11 @@ const switchEthereumChain = {
|
||||
export default switchEthereumChain;
|
||||
|
||||
function findExistingNetwork(chainId, findNetworkConfigurationBy) {
|
||||
if (chainId in CHAIN_ID_TO_TYPE_MAP) {
|
||||
if (
|
||||
Object.values(BUILT_IN_INFURA_NETWORKS)
|
||||
.map(({ chainId: id }) => id)
|
||||
.includes(chainId)
|
||||
) {
|
||||
return {
|
||||
chainId,
|
||||
ticker: CURRENCY_SYMBOLS.ETH,
|
||||
@ -105,13 +110,13 @@ async function switchEthereumChainHandler(
|
||||
try {
|
||||
const approvedRequestData = await requestUserApproval({
|
||||
origin,
|
||||
type: MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN,
|
||||
type: ApprovalType.SwitchEthereumChain,
|
||||
requestData,
|
||||
});
|
||||
if (
|
||||
chainId in CHAIN_ID_TO_TYPE_MAP &&
|
||||
approvedRequestData.type !== NETWORK_TYPES.LOCALHOST &&
|
||||
approvedRequestData.type !== NETWORK_TYPES.LINEA_TESTNET
|
||||
Object.values(BUILT_IN_INFURA_NETWORKS)
|
||||
.map(({ chainId: id }) => id)
|
||||
.includes(chainId)
|
||||
) {
|
||||
await setProviderType(approvedRequestData.type);
|
||||
} else {
|
||||
|
@ -56,7 +56,7 @@ export const SENTRY_STATE = {
|
||||
nextNonce: true,
|
||||
participateInMetaMetrics: true,
|
||||
preferences: true,
|
||||
provider: {
|
||||
providerConfig: {
|
||||
nickname: true,
|
||||
ticker: true,
|
||||
type: true,
|
||||
|
@ -6,6 +6,7 @@ try {
|
||||
errorTaming: 'unsafe',
|
||||
mathTaming: 'unsafe',
|
||||
dateTaming: 'unsafe',
|
||||
domainTaming: 'unsafe',
|
||||
overrideTaming: 'severe',
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -15,7 +15,7 @@ import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager'
|
||||
import { errorCodes as rpcErrorCodes, EthereumRpcError } from 'eth-rpc-errors';
|
||||
import { Mutex } from 'await-semaphore';
|
||||
import log from 'loglevel';
|
||||
import TrezorKeyring from 'eth-trezor-keyring';
|
||||
import TrezorKeyring from '@metamask/eth-trezor-keyring';
|
||||
import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring';
|
||||
import LatticeKeyring from 'eth-lattice-keyring';
|
||||
import { MetaMaskKeyring as QRHardwareKeyring } from '@keystonehq/metamask-airgapped-keyring';
|
||||
@ -49,6 +49,7 @@ import {
|
||||
SubjectType,
|
||||
} from '@metamask/subject-metadata-controller';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
import { encrypt, decrypt } from '@metamask/browser-passworder';
|
||||
import { RateLimitController } from '@metamask/rate-limit-controller';
|
||||
import { NotificationController } from '@metamask/notification-controller';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
@ -62,6 +63,8 @@ import {
|
||||
} from '@metamask/snaps-controllers';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
import { SignatureController } from '@metamask/signature-controller';
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import {
|
||||
AssetType,
|
||||
TransactionStatus,
|
||||
@ -93,7 +96,6 @@ import { UI_NOTIFICATIONS } from '../../shared/notifications';
|
||||
import { MILLISECOND, SECOND } from '../../shared/constants/time';
|
||||
import {
|
||||
ORIGIN_METAMASK,
|
||||
MESSAGE_TYPE,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
SNAP_DIALOG_TYPES,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
@ -160,7 +162,6 @@ import { segment } from './lib/segment';
|
||||
import createMetaRPCHandler from './lib/createMetaRPCHandler';
|
||||
import { previousValueComparator } from './lib/util';
|
||||
import createMetamaskMiddleware from './lib/createMetamaskMiddleware';
|
||||
import SignController from './controllers/sign';
|
||||
import EncryptionPublicKeyController from './controllers/encryption-public-key';
|
||||
|
||||
import {
|
||||
@ -256,9 +257,13 @@ export default class MetamaskController extends EventEmitter {
|
||||
}),
|
||||
showApprovalRequest: opts.showUserConfirmation,
|
||||
typesExcludedFromRateLimiting: [
|
||||
MESSAGE_TYPE.ETH_SIGN,
|
||||
MESSAGE_TYPE.PERSONAL_SIGN,
|
||||
MESSAGE_TYPE.ETH_SIGN_TYPED_DATA,
|
||||
ApprovalType.EthSign,
|
||||
ApprovalType.PersonalSign,
|
||||
ApprovalType.EthSignTypedData,
|
||||
ApprovalType.Transaction,
|
||||
ApprovalType.WatchAsset,
|
||||
ApprovalType.EthGetEncryptionPublicKey,
|
||||
ApprovalType.EthDecrypt,
|
||||
],
|
||||
});
|
||||
|
||||
@ -285,7 +290,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
this.tokenListController = new TokenListController({
|
||||
chainId: hexToDecimal(
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
),
|
||||
preventPollingOnNetworkRestart: initState.TokenListController
|
||||
? initState.TokenListController.preventPollingOnNetworkRestart
|
||||
@ -295,8 +300,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
const modifiedNetworkState = {
|
||||
...networkState,
|
||||
providerConfig: {
|
||||
...networkState.provider,
|
||||
chainId: hexToDecimal(networkState.provider.chainId),
|
||||
...networkState.providerConfig,
|
||||
chainId: hexToDecimal(networkState.providerConfig.chainId),
|
||||
},
|
||||
};
|
||||
return cb(modifiedNetworkState);
|
||||
@ -330,7 +335,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
const modifiedNetworkState = {
|
||||
...networkState,
|
||||
providerConfig: {
|
||||
...networkState.provider,
|
||||
...networkState.providerConfig,
|
||||
},
|
||||
};
|
||||
return cb(modifiedNetworkState);
|
||||
@ -367,8 +372,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
const modifiedNetworkState = {
|
||||
...networkState,
|
||||
providerConfig: {
|
||||
...networkState.provider,
|
||||
chainId: hexToDecimal(networkState.provider.chainId),
|
||||
...networkState.providerConfig,
|
||||
chainId: hexToDecimal(networkState.providerConfig.chainId),
|
||||
},
|
||||
};
|
||||
return cb(modifiedNetworkState);
|
||||
@ -392,8 +397,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
const modifiedNetworkState = {
|
||||
...networkState,
|
||||
providerConfig: {
|
||||
...networkState.provider,
|
||||
chainId: hexToDecimal(networkState.provider.chainId),
|
||||
...networkState.providerConfig,
|
||||
chainId: hexToDecimal(networkState.providerConfig.chainId),
|
||||
},
|
||||
};
|
||||
return cb(modifiedNetworkState);
|
||||
@ -452,8 +457,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
const modifiedNetworkState = {
|
||||
...networkState,
|
||||
providerConfig: {
|
||||
...networkState.provider,
|
||||
chainId: hexToDecimal(networkState.provider.chainId),
|
||||
...networkState.providerConfig,
|
||||
chainId: hexToDecimal(networkState.providerConfig.chainId),
|
||||
},
|
||||
};
|
||||
return cb(modifiedNetworkState);
|
||||
@ -476,11 +481,11 @@ export default class MetamaskController extends EventEmitter {
|
||||
),
|
||||
getNetworkIdentifier: () => {
|
||||
const { type, rpcUrl } =
|
||||
this.networkController.store.getState().provider;
|
||||
this.networkController.store.getState().providerConfig;
|
||||
return type === NETWORK_TYPES.RPC ? rpcUrl : type;
|
||||
},
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
version: this.platform.getVersion(),
|
||||
environment: process.env.METAMASK_ENVIRONMENT,
|
||||
extension: this.extension,
|
||||
@ -522,13 +527,14 @@ export default class MetamaskController extends EventEmitter {
|
||||
legacyAPIEndpoint: `${gasApiBaseUrl}/networks/<chain_id>/gasPrices`,
|
||||
EIP1559APIEndpoint: `${gasApiBaseUrl}/networks/<chain_id>/suggestedGasFees`,
|
||||
getCurrentNetworkLegacyGasAPICompatibility: () => {
|
||||
const { chainId } = this.networkController.store.getState().provider;
|
||||
const { chainId } =
|
||||
this.networkController.store.getState().providerConfig;
|
||||
return process.env.IN_TEST || chainId === CHAIN_IDS.MAINNET;
|
||||
},
|
||||
getChainId: () => {
|
||||
return process.env.IN_TEST
|
||||
? CHAIN_IDS.MAINNET
|
||||
: this.networkController.store.getState().provider.chainId;
|
||||
: this.networkController.store.getState().providerConfig.chainId;
|
||||
},
|
||||
});
|
||||
|
||||
@ -558,7 +564,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
messenger: currencyRateMessenger,
|
||||
state: {
|
||||
...initState.CurrencyController,
|
||||
nativeCurrency: this.networkController.store.getState().provider.ticker,
|
||||
nativeCurrency:
|
||||
this.networkController.store.getState().providerConfig.ticker,
|
||||
},
|
||||
});
|
||||
|
||||
@ -598,8 +605,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
const modifiedNetworkState = {
|
||||
...networkState,
|
||||
providerConfig: {
|
||||
...networkState.provider,
|
||||
chainId: hexToDecimal(networkState.provider.chainId),
|
||||
...networkState.providerConfig,
|
||||
chainId: hexToDecimal(networkState.providerConfig.chainId),
|
||||
},
|
||||
};
|
||||
return cb(modifiedNetworkState);
|
||||
@ -632,7 +639,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.ensController = new EnsController({
|
||||
provider: this.provider,
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
@ -650,7 +657,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
preferencesController: this.preferencesController,
|
||||
onboardingController: this.onboardingController,
|
||||
initState: initState.IncomingTransactionsController,
|
||||
@ -661,10 +668,10 @@ export default class MetamaskController extends EventEmitter {
|
||||
provider: this.provider,
|
||||
blockTracker: this.blockTracker,
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
getNetworkIdentifier: () => {
|
||||
const { type, rpcUrl } =
|
||||
this.networkController.store.getState().provider;
|
||||
this.networkController.store.getState().providerConfig;
|
||||
return type === NETWORK_TYPES.RPC ? rpcUrl : type;
|
||||
},
|
||||
preferencesController: this.preferencesController,
|
||||
@ -701,7 +708,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.cachedBalancesController = new CachedBalancesController({
|
||||
accountTracker: this.accountTracker,
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
initState: initState.CachedBalancesController,
|
||||
});
|
||||
|
||||
@ -748,6 +755,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
`${this.approvalController.name}:rejectRequest`,
|
||||
`SnapController:getPermitted`,
|
||||
`SnapController:install`,
|
||||
`SubjectMetadataController:getSubjectMetadata`,
|
||||
],
|
||||
}),
|
||||
state: initState.PermissionController,
|
||||
@ -807,7 +815,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
const snapExecutionServiceArgs = {
|
||||
iframeUrl: new URL('https://execution.metamask.io/0.15.1/index.html'),
|
||||
iframeUrl: new URL(process.env.IFRAME_EXECUTION_ENVIRONMENT_URL),
|
||||
messenger: this.controllerMessenger.getRestricted({
|
||||
name: 'ExecutionService',
|
||||
}),
|
||||
@ -987,7 +995,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
initState:
|
||||
initState.TransactionController || initState.TransactionManager,
|
||||
getPermittedAccounts: this.getPermittedAccounts.bind(this),
|
||||
getProviderConfig: () => this.networkController.store.getState().provider,
|
||||
getProviderConfig: () =>
|
||||
this.networkController.store.getState().providerConfig,
|
||||
getCurrentNetworkEIP1559Compatibility:
|
||||
this.networkController.getEIP1559Compatibility.bind(
|
||||
this.networkController,
|
||||
@ -1008,7 +1017,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
});
|
||||
},
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
preferencesStore: this.preferencesController.store,
|
||||
txHistoryLimit: 60,
|
||||
signTransaction: this.keyringController.signTransaction.bind(
|
||||
@ -1144,7 +1153,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
async () => {
|
||||
const { ticker } = this.networkController.store.getState().provider;
|
||||
const { ticker } =
|
||||
this.networkController.store.getState().providerConfig;
|
||||
try {
|
||||
await this.currencyRateController.setNativeCurrency(ticker);
|
||||
} catch (error) {
|
||||
@ -1187,9 +1197,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
),
|
||||
});
|
||||
|
||||
this.signController = new SignController({
|
||||
this.signatureController = new SignatureController({
|
||||
messenger: this.controllerMessenger.getRestricted({
|
||||
name: 'SignController',
|
||||
name: 'SignatureController',
|
||||
allowedActions: [
|
||||
`${this.approvalController.name}:addRequest`,
|
||||
`${this.approvalController.name}:acceptRequest`,
|
||||
@ -1197,12 +1207,24 @@ export default class MetamaskController extends EventEmitter {
|
||||
],
|
||||
}),
|
||||
keyringController: this.keyringController,
|
||||
preferencesController: this.preferencesController,
|
||||
getState: this.getState.bind(this),
|
||||
isEthSignEnabled: () =>
|
||||
this.preferencesController.store.getState()
|
||||
?.disabledRpcMethodPreferences?.eth_sign,
|
||||
getAllState: this.getState.bind(this),
|
||||
securityProviderRequest: this.securityProviderRequest.bind(this),
|
||||
metricsEvent: this.metaMetricsController.trackEvent.bind(
|
||||
this.metaMetricsController,
|
||||
),
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
});
|
||||
|
||||
this.signatureController.hub.on('cancelWithReason', (message, reason) => {
|
||||
this.metaMetricsController.trackEvent({
|
||||
event: reason,
|
||||
category: MetaMetricsEventCategory.Transactions,
|
||||
properties: {
|
||||
action: 'Sign Request',
|
||||
type: message.type,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
this.swapsController = new SwapsController({
|
||||
@ -1213,10 +1235,11 @@ export default class MetamaskController extends EventEmitter {
|
||||
onNetworkStateChange: (listener) =>
|
||||
this.networkController.store.subscribe(listener),
|
||||
provider: this.provider,
|
||||
getProviderConfig: () => this.networkController.store.getState().provider,
|
||||
getProviderConfig: () =>
|
||||
this.networkController.store.getState().providerConfig,
|
||||
getTokenRatesState: () => this.tokenRatesController.state,
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
getEIP1559GasFeeEstimates:
|
||||
this.gasFeeController.fetchGasFeeEstimates.bind(this.gasFeeController),
|
||||
});
|
||||
@ -1227,7 +1250,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
const modifiedNetworkState = {
|
||||
...networkState,
|
||||
providerConfig: {
|
||||
...networkState.provider,
|
||||
...networkState.providerConfig,
|
||||
},
|
||||
};
|
||||
return cb(modifiedNetworkState);
|
||||
@ -1266,11 +1289,11 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.txController.txStateManager.clearUnapprovedTxs();
|
||||
this.encryptionPublicKeyController.clearUnapproved();
|
||||
this.decryptMessageController.clearUnapproved();
|
||||
this.signController.clearUnapproved();
|
||||
this.signatureController.clearUnapproved();
|
||||
},
|
||||
);
|
||||
|
||||
if (isManifestV3 && globalThis.isFirstTimeProfileLoaded === false) {
|
||||
if (isManifestV3 && globalThis.isFirstTimeProfileLoaded === undefined) {
|
||||
const { serviceWorkerLastActiveTime } =
|
||||
this.appStateController.store.getState();
|
||||
const metametricsPayload = {
|
||||
@ -1314,21 +1337,24 @@ export default class MetamaskController extends EventEmitter {
|
||||
// tx signing
|
||||
processTransaction: this.newUnapprovedTransaction.bind(this),
|
||||
// msg signing
|
||||
processEthSignMessage: this.signController.newUnsignedMessage.bind(
|
||||
this.signController,
|
||||
),
|
||||
processTypedMessage: this.signController.newUnsignedTypedMessage.bind(
|
||||
this.signController,
|
||||
),
|
||||
processTypedMessageV3: this.signController.newUnsignedTypedMessage.bind(
|
||||
this.signController,
|
||||
),
|
||||
processTypedMessageV4: this.signController.newUnsignedTypedMessage.bind(
|
||||
this.signController,
|
||||
processEthSignMessage: this.signatureController.newUnsignedMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
processTypedMessage:
|
||||
this.signatureController.newUnsignedTypedMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
processTypedMessageV3:
|
||||
this.signatureController.newUnsignedTypedMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
processTypedMessageV4:
|
||||
this.signatureController.newUnsignedTypedMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
processPersonalMessage:
|
||||
this.signController.newUnsignedPersonalMessage.bind(
|
||||
this.signController,
|
||||
this.signatureController.newUnsignedPersonalMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
processEncryptionPublicKey:
|
||||
this.encryptionPublicKeyController.newRequestEncryptionPublicKey.bind(
|
||||
@ -1361,7 +1387,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
TokenRatesController: this.tokenRatesController,
|
||||
DecryptMessageController: this.decryptMessageController,
|
||||
EncryptionPublicKeyController: this.encryptionPublicKeyController,
|
||||
SignController: this.signController,
|
||||
SignatureController: this.signatureController,
|
||||
SwapsController: this.swapsController.store,
|
||||
EnsController: this.ensController.store,
|
||||
ApprovalController: this.approvalController,
|
||||
@ -1451,7 +1477,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.encryptionPublicKeyController.resetState.bind(
|
||||
this.encryptionPublicKeyController,
|
||||
),
|
||||
this.signController.resetState.bind(this.signController),
|
||||
this.signatureController.resetState.bind(this.signatureController),
|
||||
this.swapsController.resetState,
|
||||
this.ensController.resetState,
|
||||
this.approvalController.clear.bind(this.approvalController),
|
||||
@ -1548,6 +1574,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
return {
|
||||
...buildSnapEndowmentSpecifications(),
|
||||
...buildSnapRestrictedMethodSpecifications({
|
||||
encrypt,
|
||||
decrypt,
|
||||
clearSnapState: this.controllerMessenger.call.bind(
|
||||
this.controllerMessenger,
|
||||
'SnapController:clearSnapState',
|
||||
@ -1783,7 +1811,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
updatePublicConfigStore(this.getState());
|
||||
|
||||
function updatePublicConfigStore(memState) {
|
||||
const { chainId } = networkController.store.getState().provider;
|
||||
const { chainId } = networkController.store.getState().providerConfig;
|
||||
if (memState.networkStatus === NetworkStatus.Available) {
|
||||
publicConfigStore.putState(selectPublicState(chainId, memState));
|
||||
}
|
||||
@ -1824,7 +1852,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
getProviderNetworkState(memState) {
|
||||
const { networkId } = memState || this.getState();
|
||||
return {
|
||||
chainId: this.networkController.store.getState().provider.chainId,
|
||||
chainId: this.networkController.store.getState().providerConfig.chainId,
|
||||
networkVersion: networkId ?? 'loading',
|
||||
};
|
||||
}
|
||||
@ -2137,22 +2165,25 @@ export default class MetamaskController extends EventEmitter {
|
||||
updatePreviousGasParams:
|
||||
txController.updatePreviousGasParams.bind(txController),
|
||||
|
||||
// signController
|
||||
signMessage: this.signController.signMessage.bind(this.signController),
|
||||
cancelMessage: this.signController.cancelMessage.bind(
|
||||
this.signController,
|
||||
// signatureController
|
||||
signMessage: this.signatureController.signMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
signPersonalMessage: this.signController.signPersonalMessage.bind(
|
||||
this.signController,
|
||||
cancelMessage: this.signatureController.cancelMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
cancelPersonalMessage: this.signController.cancelPersonalMessage.bind(
|
||||
this.signController,
|
||||
signPersonalMessage: this.signatureController.signPersonalMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
signTypedMessage: this.signController.signTypedMessage.bind(
|
||||
this.signController,
|
||||
cancelPersonalMessage:
|
||||
this.signatureController.cancelPersonalMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
signTypedMessage: this.signatureController.signTypedMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
cancelTypedMessage: this.signController.cancelTypedMessage.bind(
|
||||
this.signController,
|
||||
cancelTypedMessage: this.signatureController.cancelTypedMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
|
||||
// decryptMessageController
|
||||
@ -2796,7 +2827,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.appStateController.setTrezorModel(model);
|
||||
}
|
||||
|
||||
keyring.network = this.networkController.store.getState().provider.type;
|
||||
keyring.network =
|
||||
this.networkController.store.getState().providerConfig.type;
|
||||
|
||||
return keyring;
|
||||
}
|
||||
@ -3668,9 +3700,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
),
|
||||
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
getCurrentRpcUrl: () =>
|
||||
this.networkController.store.getState().provider.rpcUrl,
|
||||
this.networkController.store.getState().providerConfig.rpcUrl,
|
||||
// network configuration-related
|
||||
getNetworkConfigurations: () =>
|
||||
this.networkController.store.getState().networkConfigurations,
|
||||
@ -4262,7 +4294,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
if (transactionSecurityCheckEnabled) {
|
||||
const chainId = Number(
|
||||
hexToDecimal(this.networkController.store.getState().provider.chainId),
|
||||
hexToDecimal(
|
||||
this.networkController.store.getState().providerConfig.chainId,
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
|
@ -116,7 +116,7 @@ const MAINNET_CHAIN_ID = '0x1';
|
||||
const firstTimeState = {
|
||||
config: {},
|
||||
NetworkController: {
|
||||
provider: {
|
||||
providerConfig: {
|
||||
type: NETWORK_TYPES.RPC,
|
||||
rpcUrl: ALT_MAINNET_RPC_URL,
|
||||
chainId: MAINNET_CHAIN_ID,
|
||||
|
88
app/scripts/migrations/086.test.js
Normal file
@ -0,0 +1,88 @@
|
||||
import { migrate, version } from './086';
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
v4: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('migration #86', () => {
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 85,
|
||||
},
|
||||
data: {},
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.meta).toStrictEqual({
|
||||
version,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network controller state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 85,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network controller provider state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 85,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should rename the provider config state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
provider: {
|
||||
some: 'provider',
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 85,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual({
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
providerConfig: {
|
||||
some: 'provider',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
41
app/scripts/migrations/086.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { hasProperty, isObject } from '@metamask/utils';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
export const version = 86;
|
||||
|
||||
/**
|
||||
* Rename network controller `provider` state to `providerConfig`.
|
||||
*
|
||||
* @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist.
|
||||
* @param originalVersionedData.meta - State metadata.
|
||||
* @param originalVersionedData.meta.version - The current state version.
|
||||
* @param originalVersionedData.data - The persisted MetaMask state, keyed by controller.
|
||||
* @returns Updated versioned MetaMask extension state.
|
||||
*/
|
||||
export async function migrate(originalVersionedData: {
|
||||
meta: { version: number };
|
||||
data: Record<string, unknown>;
|
||||
}) {
|
||||
const versionedData = cloneDeep(originalVersionedData);
|
||||
versionedData.meta.version = version;
|
||||
versionedData.data = transformState(versionedData.data);
|
||||
return versionedData;
|
||||
}
|
||||
|
||||
function transformState(state: Record<string, unknown>) {
|
||||
if (
|
||||
hasProperty(state, 'NetworkController') &&
|
||||
isObject(state.NetworkController) &&
|
||||
hasProperty(state.NetworkController, 'provider')
|
||||
) {
|
||||
const networkControllerState = state.NetworkController;
|
||||
networkControllerState.providerConfig = networkControllerState.provider;
|
||||
delete networkControllerState.provider;
|
||||
|
||||
return {
|
||||
...state,
|
||||
NetworkController: networkControllerState,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
}
|
@ -89,6 +89,7 @@ import * as m082 from './082';
|
||||
import * as m083 from './083';
|
||||
import * as m084 from './084';
|
||||
import * as m085 from './085';
|
||||
import * as m086 from './086';
|
||||
|
||||
const migrations = [
|
||||
m002,
|
||||
@ -175,6 +176,7 @@ const migrations = [
|
||||
m083,
|
||||
m084,
|
||||
m085,
|
||||
m086,
|
||||
];
|
||||
|
||||
export default migrations;
|
||||
|
17
builds.yml
@ -21,6 +21,8 @@ buildTypes:
|
||||
- SEGMENT_PROD_WRITE_KEY
|
||||
- INFURA_ENV_KEY_REF: INFURA_PROD_PROJECT_ID
|
||||
- SEGMENT_WRITE_KEY_REF: SEGMENT_PROD_WRITE_KEY
|
||||
# Main build uses the default browser manifest
|
||||
manifestOverrides: false
|
||||
|
||||
beta:
|
||||
features:
|
||||
@ -34,7 +36,7 @@ buildTypes:
|
||||
# eg. instead of 10.25.0 -> 10.25.0-beta.2
|
||||
isPrerelease: true
|
||||
# Folder which contains overrides to browser manifests
|
||||
manifestOverrides: ./app/build-types/mmi/manifest/
|
||||
manifestOverrides: ./app/build-types/beta/manifest/
|
||||
|
||||
flask:
|
||||
# Code surrounded using code fences for that feature
|
||||
@ -48,11 +50,13 @@ buildTypes:
|
||||
- SEGMENT_FLASK_WRITE_KEY
|
||||
- ALLOW_LOCAL_SNAPS: true
|
||||
- REQUIRE_SNAPS_ALLOWLIST: false
|
||||
- IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.16.0-flask.1/index.html
|
||||
- SUPPORT_LINK: https://metamask-flask.zendesk.com/hc
|
||||
- SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new
|
||||
- INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID
|
||||
- SEGMENT_WRITE_KEY_REF: SEGMENT_FLASK_WRITE_KEY
|
||||
isPrerelease: true
|
||||
manifestOverrides: ./app/build-types/flask/manifest/
|
||||
|
||||
desktop:
|
||||
features:
|
||||
@ -64,11 +68,13 @@ buildTypes:
|
||||
- SEGMENT_FLASK_WRITE_KEY
|
||||
- ALLOW_LOCAL_SNAPS: true
|
||||
- REQUIRE_SNAPS_ALLOWLIST: false
|
||||
- IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.16.0-flask.1/index.html
|
||||
- SUPPORT_LINK: https://metamask-flask.zendesk.com/hc
|
||||
- SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new
|
||||
- INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID
|
||||
- SEGMENT_WRITE_KEY_REF: SEGMENT_FLASK_WRITE_KEY
|
||||
isPrerelease: true
|
||||
manifestOverrides: ./app/build-types/desktop/manifest/
|
||||
|
||||
mmi:
|
||||
features:
|
||||
@ -83,6 +89,7 @@ buildTypes:
|
||||
# For some reason, MMI uses this type of versioning
|
||||
# Leaving it on for backwards compatibility
|
||||
isPrerelease: true
|
||||
manifestOverrides: ./app/build-types/mmi/manifest/
|
||||
|
||||
# Build types are composed of a set of features.
|
||||
# Each feature can have code fences that add new code
|
||||
@ -96,6 +103,7 @@ features:
|
||||
- ALLOW_LOCAL_SNAPS
|
||||
# Whether to verify that a snap can be installed using an allow list
|
||||
- REQUIRE_SNAPS_ALLOWLIST
|
||||
- IFRAME_EXECUTION_ENVIRONMENT_URL
|
||||
assets:
|
||||
- ./{app,shared,ui}/**/snaps/**
|
||||
desktop:
|
||||
@ -122,7 +130,7 @@ features:
|
||||
assets:
|
||||
- src: ./app/build-types/mmi/images/
|
||||
dest: images
|
||||
- ./{app,shared,ui}/**/mmi/**
|
||||
- ./{app,shared,ui}/**/institutional/**
|
||||
build-flask:
|
||||
assets:
|
||||
- src: ./app/build-types/flask/images/
|
||||
@ -154,6 +162,9 @@ env:
|
||||
- APPLY_LAVAMOAT
|
||||
- FILE_NAMES
|
||||
|
||||
# This variable is read by Trezor's source and breaks build if not included
|
||||
- ASSET_PREFIX: null
|
||||
|
||||
###
|
||||
# API keys to 3rd party services
|
||||
###
|
||||
@ -205,6 +216,8 @@ env:
|
||||
# if it's not inside process.env
|
||||
# Also see DEBUG and METAMASK_DEBUG
|
||||
- NODE_DEBUG: ''
|
||||
# Used by react-devtools-core
|
||||
- EDITOR_URL: ''
|
||||
|
||||
###
|
||||
# Meta variables
|
||||
|
@ -6,10 +6,10 @@
|
||||
// subset of files to check against these targets.
|
||||
module.exports = {
|
||||
global: {
|
||||
lines: 69.92,
|
||||
branches: 57.63,
|
||||
statements: 69.24,
|
||||
functions: 62.51,
|
||||
lines: 70.26,
|
||||
branches: 57.91,
|
||||
statements: 69.59,
|
||||
functions: 62.97,
|
||||
},
|
||||
transforms: {
|
||||
branches: 100,
|
||||
|
@ -7,15 +7,11 @@ const { loadBuildTypesConfig } = require('../lib/build-type');
|
||||
const { Variables } = require('../lib/variables');
|
||||
const { ENVIRONMENT } = require('./constants');
|
||||
|
||||
const VARIABLES_REQUIRED_IN_PRODUCTION = [
|
||||
'INFURA_BETA_PROJECT_ID',
|
||||
'INFURA_FLASK_PROJECT_ID',
|
||||
'INFURA_PROD_PROJECT_ID',
|
||||
'SEGMENT_BETA_WRITE_KEY',
|
||||
'SEGMENT_FLASK_WRITE_KEY',
|
||||
'SEGMENT_PROD_WRITE_KEY',
|
||||
'SENTRY_DSN',
|
||||
];
|
||||
const VARIABLES_REQUIRED_IN_PRODUCTION = {
|
||||
main: ['INFURA_PROD_PROJECT_ID', 'SEGMENT_PROD_WRITE_KEY', 'SENTRY_DSN'],
|
||||
beta: ['INFURA_BETA_PROJECT_ID', 'SEGMENT_BETA_WRITE_KEY', 'SENTRY_DSN'],
|
||||
flask: ['INFURA_FLASK_PROJECT_ID', 'SEGMENT_FLASK_WRITE_KEY', 'SENTRY_DSN'],
|
||||
};
|
||||
|
||||
async function fromIniFile(filepath) {
|
||||
let configContents = '';
|
||||
@ -137,9 +133,9 @@ async function getConfig(buildType, environment) {
|
||||
|
||||
// TODO(ritave): Move build targets and environments to builds.yml
|
||||
if (environment === ENVIRONMENT.PRODUCTION) {
|
||||
const undefinedVariables = VARIABLES_REQUIRED_IN_PRODUCTION.filter(
|
||||
(variable) => !variables.isDefined(variable),
|
||||
);
|
||||
const undefinedVariables = VARIABLES_REQUIRED_IN_PRODUCTION[
|
||||
buildType
|
||||
].filter((variable) => !variables.isDefined(variable));
|
||||
if (undefinedVariables.length !== 0) {
|
||||
const message = `Some variables required to build production target are not defined.
|
||||
- ${undefinedVariables.join('\n - ')}
|
||||
|
@ -14,7 +14,7 @@ const difference = require('lodash/difference');
|
||||
const { intersection } = require('lodash');
|
||||
const { getVersion } = require('../lib/get-version');
|
||||
const { loadBuildTypesConfig } = require('../lib/build-type');
|
||||
const { TASKS, ENVIRONMENT } = require('./constants');
|
||||
const { TASKS } = require('./constants');
|
||||
const {
|
||||
createTask,
|
||||
composeSeries,
|
||||
@ -27,7 +27,7 @@ const createStyleTasks = require('./styles');
|
||||
const createStaticAssetTasks = require('./static');
|
||||
const createEtcTasks = require('./etc');
|
||||
const { getBrowserVersionMap, getEnvironment } = require('./utils');
|
||||
const { getConfig, getProductionConfig } = require('./config');
|
||||
const { getConfig } = require('./config');
|
||||
const { BUILD_TARGETS } = require('./constants');
|
||||
|
||||
// Packages required dynamically via browserify configuration in dependencies
|
||||
@ -125,6 +125,8 @@ async function defineAndRunBuildTasks() {
|
||||
'Uint8Array',
|
||||
'String',
|
||||
'Promise',
|
||||
'JSON',
|
||||
'Date',
|
||||
// globals sentry needs to function
|
||||
'__SENTRY__',
|
||||
'appState',
|
||||
@ -360,13 +362,8 @@ testDev: Create an unoptimized, live-reloading build for debugging e2e tests.`,
|
||||
const highLevelTasks = Object.values(BUILD_TARGETS);
|
||||
if (highLevelTasks.includes(task)) {
|
||||
const environment = getEnvironment({ buildTarget: task });
|
||||
if (environment === ENVIRONMENT.PRODUCTION) {
|
||||
// Output ignored, this is only called to ensure config is validated
|
||||
await getProductionConfig(buildType);
|
||||
} else {
|
||||
// Output ignored, this is only called to ensure config is validated
|
||||
await getConfig();
|
||||
}
|
||||
// Output ignored, this is only called to ensure config is validated
|
||||
await getConfig(buildType, environment);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -176,7 +176,7 @@ async function getBuildModifications(buildType, platform) {
|
||||
}
|
||||
|
||||
const overridesPath = buildConfig.buildTypes[buildType].manifestOverrides;
|
||||
if (overridesPath === undefined) {
|
||||
if (!overridesPath) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ const Sqrl = require('squirrelly');
|
||||
const lavapack = require('@lavamoat/lavapack');
|
||||
const lavamoatBrowserify = require('lavamoat-browserify');
|
||||
const terser = require('terser');
|
||||
const moduleResolver = require('babel-plugin-module-resolver');
|
||||
|
||||
const bifyModuleGroups = require('bify-module-groups');
|
||||
|
||||
@ -125,7 +126,7 @@ function getInfuraProjectId({ buildType, variables, environment, testing }) {
|
||||
return variables.get('INFURA_PROJECT_ID');
|
||||
}
|
||||
/** @type {string|undefined} */
|
||||
const infuraKeyReference = process.env.INFURA_ENV_KEY_REF;
|
||||
const infuraKeyReference = variables.get('INFURA_ENV_KEY_REF');
|
||||
assert(
|
||||
typeof infuraKeyReference === 'string' && infuraKeyReference.length > 0,
|
||||
`Build type "${buildType}" has improperly set INFURA_ENV_KEY_REF in builds.yml. Current value: "${infuraKeyReference}"`,
|
||||
@ -154,7 +155,7 @@ function getSegmentWriteKey({ buildType, variables, environment }) {
|
||||
return variables.get('SEGMENT_WRITE_KEY');
|
||||
}
|
||||
|
||||
const segmentKeyReference = process.env.SEGMENT_WRITE_KEY_REF;
|
||||
const segmentKeyReference = variables.get('SEGMENT_WRITE_KEY_REF');
|
||||
assert(
|
||||
typeof segmentKeyReference === 'string' && segmentKeyReference.length > 0,
|
||||
`Build type "${buildType}" has improperly set SEGMENT_WRITE_KEY_REF in builds.yml. Current value: "${segmentKeyReference}"`,
|
||||
@ -922,6 +923,9 @@ function setupBundlerDefaults(
|
||||
const { bundlerOpts } = buildConfiguration;
|
||||
const extensions = ['.js', '.ts', '.tsx'];
|
||||
|
||||
const isSnapsFlask =
|
||||
features.active.has('snaps') && features.active.has('build-flask');
|
||||
|
||||
Object.assign(bundlerOpts, {
|
||||
// Source transforms
|
||||
transform: [
|
||||
@ -931,7 +935,25 @@ function setupBundlerDefaults(
|
||||
[
|
||||
babelify,
|
||||
// Run TypeScript files through Babel
|
||||
{ extensions },
|
||||
{
|
||||
extensions,
|
||||
plugins: isSnapsFlask
|
||||
? [
|
||||
[
|
||||
moduleResolver,
|
||||
{
|
||||
alias: {
|
||||
'@metamask/snaps-controllers':
|
||||
'@metamask/snaps-controllers-flask',
|
||||
'@metamask/snaps-ui': '@metamask/snaps-ui-flask',
|
||||
'@metamask/snaps-utils': '@metamask/snaps-utils-flask',
|
||||
'@metamask/rpc-methods': '@metamask/rpc-methods-flask',
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
: [],
|
||||
},
|
||||
],
|
||||
// Inline `fs.readFileSync` files
|
||||
brfs,
|
||||
|
@ -1,12 +1,24 @@
|
||||
#! /bin/bash
|
||||
|
||||
validate-number(){
|
||||
re='^[0-9]+$'
|
||||
if [[ ! $1 =~ $re ]]; then
|
||||
echo "Error: The value must be a number." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
g-migration() {
|
||||
[[ -z "$1" ]] && { echo "Migration version is required!" ; exit 1; }
|
||||
local vnum=$1
|
||||
|
||||
validate-number "$vnum"
|
||||
|
||||
if (($1 < 100)); then
|
||||
vnum=0$1
|
||||
fi
|
||||
touch app/scripts/migrations/"$vnum".js
|
||||
cp app/scripts/migrations/template.js app/scripts/migrations/"$vnum".js
|
||||
touch app/scripts/migrations/"$vnum".ts
|
||||
cp app/scripts/migrations/template.ts app/scripts/migrations/"$vnum".ts
|
||||
|
||||
touch app/scripts/migrations/"$vnum".test.js
|
||||
cp app/scripts/migrations/template.test.js app/scripts/migrations/"$vnum".test.js
|
||||
|
@ -16,6 +16,7 @@ const {
|
||||
validate,
|
||||
nullable,
|
||||
never,
|
||||
literal,
|
||||
} = require('superstruct');
|
||||
const yaml = require('js-yaml');
|
||||
const { uniqWith } = require('lodash');
|
||||
@ -65,7 +66,7 @@ const BuildTypeStruct = object({
|
||||
features: optional(unique(array(string()))),
|
||||
env: optional(EnvArrayStruct),
|
||||
isPrerelease: optional(boolean()),
|
||||
manifestOverrides: optional(string()),
|
||||
manifestOverrides: union([string(), literal(false)]),
|
||||
});
|
||||
|
||||
const CopyAssetStruct = object({ src: string(), dest: string() });
|
||||
|
@ -29,7 +29,11 @@ class Variables {
|
||||
assert(
|
||||
value !== DeclaredOnly,
|
||||
new TypeError(
|
||||
`Tried to access a declared, but not defined environmental variable "${key}"`,
|
||||
`Tried to access a declared, but not defined environmental variable "${key}"
|
||||
\tWhy am I seeing this: "${key}" is declared in builds.yml, but had no actual value when we tried loading it.
|
||||
\tHow do I fix this: You could provide a default value for the variable in builds.yml under "env" property and commit to git. For example:
|
||||
\t\tenv:
|
||||
\t\t - ${key}: ''`,
|
||||
),
|
||||
);
|
||||
return value;
|
||||
@ -46,7 +50,9 @@ class Variables {
|
||||
assert(
|
||||
this.isDeclared(key),
|
||||
new TypeError(
|
||||
`Tried to access an environmental variable "${key}" that wasn't declared in builds.yml`,
|
||||
`Tried to access an environmental variable "${key}" that wasn't declared in builds.yml
|
||||
\tWhy am I seeing this: We've made use of new variables be explicit to keep track of all of them in one place
|
||||
\tHow do I fix this: Adding your variable in builds.yml under "env" property and committing to git will fix this`,
|
||||
),
|
||||
);
|
||||
return this.#definitions.get(key);
|
||||
|
@ -45,12 +45,7 @@ async function start() {
|
||||
return `<a href="${url}">${platform}</a>`;
|
||||
})
|
||||
.join(', ');
|
||||
const betaBuildLinks = platforms
|
||||
.map((platform) => {
|
||||
const url = `${BUILD_LINK_BASE}/builds-beta/metamask-beta-${platform}-${VERSION}.zip`;
|
||||
return `<a href="${url}">${platform}</a>`;
|
||||
})
|
||||
.join(', ');
|
||||
const betaBuildLinks = `<a href="${BUILD_LINK_BASE}/builds-beta/metamask-beta-chrome-${VERSION}.zip">chrome</a>`;
|
||||
const flaskBuildLinks = platforms
|
||||
.map((platform) => {
|
||||
const url = `${BUILD_LINK_BASE}/builds-flask/metamask-flask-${platform}-${VERSION}-flask.0.zip`;
|
||||
|
@ -225,7 +225,7 @@
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
},
|
||||
"provider": {
|
||||
"providerConfig": {
|
||||
"type": "goerli"
|
||||
},
|
||||
"network": "4",
|
||||
|